| Line |
Branch |
Exec |
Source |
| 1 |
|
|
/***************************************************************************** |
| 2 |
|
|
* |
| 3 |
|
|
* $Id$ |
| 4 |
|
|
* |
| 5 |
|
|
* vim: tw=78 |
| 6 |
|
|
* |
| 7 |
|
|
* This is a very simple single tasking example. |
| 8 |
|
|
* |
| 9 |
|
|
* Copyright (C) 2017,2018 Richard Hacker <lerichi at gmx dot net> |
| 10 |
|
|
* License: LGPLv3 |
| 11 |
|
|
* |
| 12 |
|
|
* It demonstrates: |
| 13 |
|
|
* - signals |
| 14 |
|
|
* - sub-rated signals (signal that is calculated every N-th cycle) |
| 15 |
|
|
* - parameters |
| 16 |
|
|
* - events |
| 17 |
|
|
* |
| 18 |
|
|
* In the cyclic calculation, an oscillator producing a sine and cosine |
| 19 |
|
|
* signal with variable amplitude and frequency is calculated. |
| 20 |
|
|
* |
| 21 |
|
|
* With a decimation of 10 (subrating), a saw tooth signal is also produced |
| 22 |
|
|
* |
| 23 |
|
|
****************************************************************************/ |
| 24 |
|
|
|
| 25 |
|
|
#include <pdserv.h> // obviously! |
| 26 |
|
|
|
| 27 |
|
|
#include <stdio.h> |
| 28 |
|
|
#include <stdint.h> |
| 29 |
|
|
#include <errno.h> |
| 30 |
|
|
#include <getopt.h> // getopt() |
| 31 |
|
|
#include <unistd.h> // getopt() |
| 32 |
|
|
#include <string.h> // memset(), strcmp(), strerror() |
| 33 |
|
|
#include <time.h> // clock_gettime(), clock_nanosleep() |
| 34 |
|
|
#include <sys/mman.h> // mlockall() |
| 35 |
|
|
#include <sched.h> // sched_setscheduler() |
| 36 |
|
|
#include <stdlib.h> // strtoul(), exit() |
| 37 |
|
|
#include <pthread.h> // pthread_mutex_lock(), pthread_mutex_unlock() |
| 38 |
|
|
|
| 39 |
|
|
/****************************************************************************/ |
| 40 |
|
|
|
| 41 |
|
|
#define MAX_SAFE_STACK (8 * 1024) /** The maximum stack size which is |
| 42 |
|
|
guranteed safe to access without faulting. |
| 43 |
|
|
*/ |
| 44 |
|
|
|
| 45 |
|
|
#define NSEC_PER_SEC (1000000000) |
| 46 |
|
|
#define DIFF_NS(A, B) (((long long) (B).tv_sec - (A).tv_sec) * NSEC_PER_SEC \ |
| 47 |
|
|
+ (B).tv_nsec - (A).tv_nsec) |
| 48 |
|
|
|
| 49 |
|
|
/****************************************************************************/ |
| 50 |
|
|
|
| 51 |
|
|
/* Command-line option variables. */ |
| 52 |
|
|
|
| 53 |
|
|
int priority = -1; /**< Task priority, -1 means RT (maximum). */ |
| 54 |
|
|
int daemonize = 0; /**< Become a daemon. */ |
| 55 |
|
|
unsigned int duration_ns = 0; |
| 56 |
|
|
const char *config = 0; |
| 57 |
|
|
|
| 58 |
|
|
/*************************************************************************** |
| 59 |
|
|
* Support functions |
| 60 |
|
|
***************************************************************************/ |
| 61 |
|
|
|
| 62 |
|
|
/** Increment the time. |
| 63 |
|
|
* Arguments: |
| 64 |
|
|
* - t: timespec pointer |
| 65 |
|
|
* - dt_ns: increment in nanoseconds |
| 66 |
|
|
*/ |
| 67 |
|
✗ |
struct timespec* timer_add(struct timespec *t, unsigned int dt_ns) |
| 68 |
|
|
{ |
| 69 |
|
✗ |
t->tv_nsec += dt_ns; |
| 70 |
|
✗ |
while (t->tv_nsec >= NSEC_PER_SEC) { |
| 71 |
|
✗ |
t->tv_nsec -= NSEC_PER_SEC; |
| 72 |
|
✗ |
t->tv_sec++; |
| 73 |
|
|
} |
| 74 |
|
✗ |
return t; |
| 75 |
|
|
} |
| 76 |
|
|
|
| 77 |
|
|
/** Return the current system time. |
| 78 |
|
|
* |
| 79 |
|
|
* This is a callback needed by pdserv. |
| 80 |
|
|
*/ |
| 81 |
|
✗ |
int gettime(struct timespec *time) |
| 82 |
|
|
{ |
| 83 |
|
✗ |
return clock_gettime(CLOCK_REALTIME, time); |
| 84 |
|
|
} |
| 85 |
|
|
|
| 86 |
|
|
/** Cause a stack fault before entering cyclic operation. |
| 87 |
|
|
*/ |
| 88 |
|
✗ |
void stack_prefault(void) |
| 89 |
|
|
{ |
| 90 |
|
✗ |
unsigned char dummy[MAX_SAFE_STACK]; |
| 91 |
|
|
|
| 92 |
|
✗ |
memset(dummy, 0, MAX_SAFE_STACK); |
| 93 |
|
|
} |
| 94 |
|
|
|
| 95 |
|
|
/** Output the usage. |
| 96 |
|
|
*/ |
| 97 |
|
✗ |
void usage(FILE *f, const char *base_name) |
| 98 |
|
|
{ |
| 99 |
|
✗ |
fprintf(f, |
| 100 |
|
|
"Usage: %s [OPTIONS]\n" |
| 101 |
|
|
"Options:\n" |
| 102 |
|
|
" --duration -d secs Set duration <float>\n" |
| 103 |
|
|
" --config -c conffile Set configuration file\n" |
| 104 |
|
|
" --priority -p <PRIO> Set task priority. Default: RT.\n" |
| 105 |
|
|
" --help -h Show this help.\n", |
| 106 |
|
|
base_name); |
| 107 |
|
|
} |
| 108 |
|
|
|
| 109 |
|
|
/** Get the command-line options. |
| 110 |
|
|
*/ |
| 111 |
|
✗ |
void get_options(int argc, char **argv) |
| 112 |
|
|
{ |
| 113 |
|
|
int c, arg_count; |
| 114 |
|
|
|
| 115 |
|
|
static struct option longOptions[] = { |
| 116 |
|
|
//name, has_arg, flag, val |
| 117 |
|
|
{"duration", required_argument, NULL, 'd'}, |
| 118 |
|
|
{"config", required_argument, NULL, 'c'}, |
| 119 |
|
|
{"priority", required_argument, NULL, 'p'}, |
| 120 |
|
|
{"help", no_argument, NULL, 'h'}, |
| 121 |
|
|
{NULL, no_argument, NULL, 0} |
| 122 |
|
|
}; |
| 123 |
|
|
|
| 124 |
|
|
do { |
| 125 |
|
✗ |
c = getopt_long(argc, argv, "d:c:p:h", longOptions, NULL); |
| 126 |
|
|
|
| 127 |
|
✗ |
switch (c) { |
| 128 |
|
✗ |
case 'p': |
| 129 |
|
✗ |
if (!strcmp(optarg, "RT")) { |
| 130 |
|
✗ |
priority = -1; |
| 131 |
|
✗ |
} else { |
| 132 |
|
✗ |
char *end; |
| 133 |
|
✗ |
priority = strtoul(optarg, &end, 10); |
| 134 |
|
✗ |
if (!*optarg || *end) { |
| 135 |
|
✗ |
fprintf(stderr, "Invalid priority: %s\n", optarg); |
| 136 |
|
✗ |
exit(1); |
| 137 |
|
|
} |
| 138 |
|
|
} |
| 139 |
|
✗ |
break; |
| 140 |
|
|
|
| 141 |
|
✗ |
case 'd': |
| 142 |
|
✗ |
duration_ns = atof(optarg) * NSEC_PER_SEC; |
| 143 |
|
✗ |
break; |
| 144 |
|
|
|
| 145 |
|
✗ |
case 'c': |
| 146 |
|
✗ |
config = optarg; |
| 147 |
|
✗ |
break; |
| 148 |
|
|
|
| 149 |
|
✗ |
case 'h': |
| 150 |
|
✗ |
usage(stdout, argv[0]); |
| 151 |
|
✗ |
exit(0); |
| 152 |
|
|
|
| 153 |
|
✗ |
case '?': |
| 154 |
|
✗ |
usage(stderr, argv[0]); |
| 155 |
|
✗ |
exit(1); |
| 156 |
|
|
|
| 157 |
|
✗ |
default: |
| 158 |
|
✗ |
break; |
| 159 |
|
|
} |
| 160 |
|
|
} |
| 161 |
|
✗ |
while (c != -1); |
| 162 |
|
|
|
| 163 |
|
✗ |
arg_count = argc - optind; |
| 164 |
|
|
|
| 165 |
|
✗ |
if (arg_count) { |
| 166 |
|
✗ |
fprintf(stderr, "%s takes no arguments!\n", argv[0]); |
| 167 |
|
✗ |
usage(stderr, argv[0]); |
| 168 |
|
✗ |
exit(1); |
| 169 |
|
|
} |
| 170 |
|
|
} |
| 171 |
|
|
|
| 172 |
|
|
/* Callback to test the limit of a parameter to be set */ |
| 173 |
|
✗ |
int limit_test(const struct pdvariable* param, |
| 174 |
|
|
void *dst, const void* src, size_t len, |
| 175 |
|
|
struct timespec *time, void* priv_data) |
| 176 |
|
|
{ |
| 177 |
|
✗ |
double value = *(double*)src; |
| 178 |
|
✗ |
double limit = *(double*)priv_data; /* pointer to limit of double */ |
| 179 |
|
|
(void)time; |
| 180 |
|
|
(void)param; |
| 181 |
|
|
|
| 182 |
|
✗ |
if (value > limit || value < -limit) |
| 183 |
|
✗ |
return -EINVAL; |
| 184 |
|
|
|
| 185 |
|
✗ |
memcpy(dst, src, len); |
| 186 |
|
✗ |
clock_gettime(CLOCK_REALTIME, time); |
| 187 |
|
|
|
| 188 |
|
✗ |
return 0; |
| 189 |
|
|
} |
| 190 |
|
|
|
| 191 |
|
|
/* Callback used to protect signals and parameters */ |
| 192 |
|
✗ |
void lock_fn(int lock, void* priv_data) |
| 193 |
|
|
{ |
| 194 |
|
✗ |
if (lock) |
| 195 |
|
✗ |
pthread_mutex_lock(priv_data); |
| 196 |
|
|
else |
| 197 |
|
✗ |
pthread_mutex_unlock(priv_data); |
| 198 |
|
|
} |
| 199 |
|
|
|
| 200 |
|
|
enum { |
| 201 |
|
|
NUM_EVENTS = 5, |
| 202 |
|
|
}; |
| 203 |
|
|
|
| 204 |
|
|
/**************************************************************************** |
| 205 |
|
|
* Main function |
| 206 |
|
|
****************************************************************************/ |
| 207 |
|
✗ |
int main(int argc, char **argv) |
| 208 |
|
|
{ |
| 209 |
|
|
struct pdserv* pdserv; |
| 210 |
|
|
struct pdtask* pdtask; |
| 211 |
|
|
const struct pdevent* event; |
| 212 |
|
|
struct pdvariable* var; |
| 213 |
|
✗ |
unsigned int tsample_ns = (uint64_t)(0.01e9); // 10ms |
| 214 |
|
✗ |
const char* err = NULL; |
| 215 |
|
✗ |
int running = 1; |
| 216 |
|
✗ |
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
| 217 |
|
|
double exec_time, cycle_time; |
| 218 |
|
✗ |
unsigned int overruns = 0; |
| 219 |
|
✗ |
struct timespec monotonic_time, world_time; |
| 220 |
|
✗ |
struct timespec start_time, stop_time = {0,0}, end_time, last_start_time; |
| 221 |
|
|
|
| 222 |
|
|
// Variables for real time task: sin/cos oscillator and saw tooth generator |
| 223 |
|
|
// Parameters: |
| 224 |
|
✗ |
double omega = 1.2; |
| 225 |
|
✗ |
char enable = 1; |
| 226 |
|
✗ |
char reset = 0; |
| 227 |
|
✗ |
double omega_limit = 5.0; |
| 228 |
|
✗ |
double amplitude_set = 10.0; |
| 229 |
|
✗ |
double ampl_limit = 20.0; |
| 230 |
|
✗ |
uint32_t event_state[NUM_EVENTS] = {0,0,0,0,0}; |
| 231 |
|
|
// Signals: |
| 232 |
|
✗ |
double sin = 0.0, cos = amplitude_set; |
| 233 |
|
✗ |
double amplitude; |
| 234 |
|
✗ |
double ampl_modulation; |
| 235 |
|
✗ |
double derivative[2]; |
| 236 |
|
✗ |
uint8_t counter; |
| 237 |
|
✗ |
int decimation_counter = 1; |
| 238 |
|
|
|
| 239 |
|
✗ |
get_options(argc, argv); |
| 240 |
|
|
|
| 241 |
|
|
/////////////////////// setup pdserv ////////////////////////////// |
| 242 |
|
|
|
| 243 |
|
|
/* Create a pdserv instance */ |
| 244 |
|
✗ |
if (!(pdserv = pdserv_create("PdServ Test", "1.234", gettime))) { |
| 245 |
|
✗ |
err = "Failed to init pdserv."; |
| 246 |
|
✗ |
goto out; |
| 247 |
|
|
} |
| 248 |
|
|
|
| 249 |
|
✗ |
if (config) |
| 250 |
|
✗ |
pdserv_config_file(pdserv, config); |
| 251 |
|
|
|
| 252 |
|
|
/* Create a task */ |
| 253 |
|
✗ |
if (!(pdtask = pdserv_create_task(pdserv, 1.0e-9*tsample_ns, NULL))) { |
| 254 |
|
✗ |
err = "Failed to create task."; |
| 255 |
|
✗ |
goto out; |
| 256 |
|
|
} |
| 257 |
|
|
|
| 258 |
|
|
/* Register parameters */ |
| 259 |
|
✗ |
pdserv_parameter(pdserv, "/osc/omega", |
| 260 |
|
|
0666, pd_double_T, &omega, 1, 0, 0, 0); |
| 261 |
|
✗ |
pdserv_parameter(pdserv, "/osc/amplitude/Setpoint", |
| 262 |
|
|
0666, pd_double_T, &litude_set, 1, 0, 0, 0); |
| 263 |
|
✗ |
pdserv_parameter(pdserv, "/osc/enable", |
| 264 |
|
|
0666, pd_sint8_T, &enable, 1, 0, 0, 0); |
| 265 |
|
✗ |
pdserv_parameter(pdserv, "/osc/reset", |
| 266 |
|
|
0666, pd_sint8_T, &reset, 1, 0, 0, 0); |
| 267 |
|
✗ |
pdserv_parameter(pdserv, "/osc/amplitude/Limit", |
| 268 |
|
|
0666, pd_double_T, &l_limit, 1, 0, 0, 0); |
| 269 |
|
✗ |
pdserv_parameter(pdserv, "/Event/State", |
| 270 |
|
|
0666, pd_uint32_T, &event_state, 5, 0, 0, 0); |
| 271 |
|
|
|
| 272 |
|
|
/* Register signals */ |
| 273 |
|
✗ |
pdserv_signal(pdtask, 1, "/osc/cos", |
| 274 |
|
|
pd_double_T, &cos, 1, 0); |
| 275 |
|
✗ |
pdserv_signal(pdtask, 1, "/osc/sin", |
| 276 |
|
|
pd_double_T, &sin, 1, 0); |
| 277 |
|
✗ |
pdserv_signal(pdtask, 1, "/osc/amplitude", |
| 278 |
|
|
pd_double_T, &litude, 1, 0); |
| 279 |
|
✗ |
pdserv_signal(pdtask, 1, "/osc/amplitude/Modulation", |
| 280 |
|
|
pd_double_T, &l_modulation, 1, 0); |
| 281 |
|
✗ |
var = pdserv_signal(pdtask, 1, "/osc/derivative", |
| 282 |
|
|
pd_double_T, derivative, 2, 0); |
| 283 |
|
✗ |
pdserv_set_comment(var, |
| 284 |
|
|
"Derivative of [cos,sin]"); |
| 285 |
|
|
|
| 286 |
|
|
/* This signal is updated every 10th calculation cycle. This has no |
| 287 |
|
|
* consequence to pdserv, it is only an indication to the clients |
| 288 |
|
|
* that the signal is decimated */ |
| 289 |
|
✗ |
pdserv_signal(pdtask, 10, "/SawTooth", |
| 290 |
|
|
pd_uint8_T, &counter, 1, 0); |
| 291 |
|
|
|
| 292 |
|
|
static const char *text[] = { |
| 293 |
|
|
"Event message 1", |
| 294 |
|
|
"Event message 2", |
| 295 |
|
|
"Event message 3", |
| 296 |
|
|
"Event message 4", |
| 297 |
|
|
"Event message 5", |
| 298 |
|
|
}; |
| 299 |
|
|
/* Register a vector event */ |
| 300 |
|
✗ |
event = pdserv_event(pdserv, "/Limit", WARN_EVENT, NUM_EVENTS, text); |
| 301 |
|
|
|
| 302 |
|
|
/* At this time, the setup for pdserv is finished. Up to now |
| 303 |
|
|
* - pdserv instance and pdserv tasks were created |
| 304 |
|
|
* - signals, parameters and events were registered. |
| 305 |
|
|
* |
| 306 |
|
|
* Now tell pdserv to prepare itself. Here it prepares the non-real time |
| 307 |
|
|
* interface (network sockets), support threads, restore persistent |
| 308 |
|
|
* parameters, etc |
| 309 |
|
|
*/ |
| 310 |
|
✗ |
int ret = pdserv_prepare(pdserv); |
| 311 |
|
✗ |
if (ret) { |
| 312 |
|
✗ |
err = "Failed to prepare pdserv."; |
| 313 |
|
✗ |
goto out; |
| 314 |
|
|
} |
| 315 |
|
|
|
| 316 |
|
|
///////////////////// setup real time task ///////////////////////// |
| 317 |
|
|
|
| 318 |
|
|
// Only _after_ pdserv_prepare() was called, the real time setup, like |
| 319 |
|
|
// locking memory, prefaulting the stack, setting scheduler and priority, |
| 320 |
|
|
// etc, is done. This is important, otherwise the real time setup will |
| 321 |
|
|
// leak into non-real time tasks which is not a good idea. |
| 322 |
|
|
|
| 323 |
|
|
/* Lock all memory forever - prevents it from being swapped out. */ |
| 324 |
|
✗ |
if (mlockall(MCL_CURRENT | MCL_FUTURE)) |
| 325 |
|
✗ |
fprintf(stderr, "mlockall() failed: %s\n", strerror(errno)); |
| 326 |
|
|
|
| 327 |
|
|
/* Provoke the first stack fault before cyclic operation. */ |
| 328 |
|
✗ |
stack_prefault(); |
| 329 |
|
|
|
| 330 |
|
|
/* Set task priority and scheduler. */ |
| 331 |
|
|
{ |
| 332 |
|
✗ |
struct sched_param param = { |
| 333 |
|
✗ |
.sched_priority = (priority == -1 |
| 334 |
|
|
? sched_get_priority_max(SCHED_FIFO) |
| 335 |
|
✗ |
: priority), |
| 336 |
|
|
}; |
| 337 |
|
|
|
| 338 |
|
✗ |
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) { |
| 339 |
|
✗ |
fprintf(stderr, |
| 340 |
|
|
"Setting SCHED_FIFO with priority %i failed: %s\n", |
| 341 |
|
✗ |
param.sched_priority, strerror(errno)); |
| 342 |
|
|
|
| 343 |
|
|
/* Reset priority, so that sub-threads start */ |
| 344 |
|
✗ |
priority = -1; |
| 345 |
|
|
} |
| 346 |
|
|
} |
| 347 |
|
|
|
| 348 |
|
|
///////////////////// cyclic task ////////////////////////////////// |
| 349 |
|
|
|
| 350 |
|
✗ |
clock_gettime(CLOCK_MONOTONIC, &monotonic_time); |
| 351 |
|
✗ |
last_start_time = monotonic_time; |
| 352 |
|
✗ |
if (duration_ns) { |
| 353 |
|
✗ |
stop_time = last_start_time; |
| 354 |
|
✗ |
timer_add(&stop_time, duration_ns); |
| 355 |
|
|
} |
| 356 |
|
✗ |
while (running && (!duration_ns || DIFF_NS(last_start_time, stop_time) > 0)) { |
| 357 |
|
✗ |
clock_gettime(CLOCK_MONOTONIC, &start_time); |
| 358 |
|
✗ |
clock_gettime(CLOCK_REALTIME, &world_time); |
| 359 |
|
|
|
| 360 |
|
|
/* Get a read lock on parameters and a write lock on signals */ |
| 361 |
|
✗ |
pthread_mutex_lock(&mutex); |
| 362 |
|
✗ |
pdserv_get_parameters(pdserv, pdtask, &world_time); |
| 363 |
|
|
|
| 364 |
|
|
/* Calculation sin/cos oscillator at base rate */ |
| 365 |
|
✗ |
if (reset) { |
| 366 |
|
✗ |
cos = amplitude_set; |
| 367 |
|
✗ |
sin = 0.0; |
| 368 |
|
|
} |
| 369 |
|
✗ |
else if (enable) { |
| 370 |
|
|
// Calculate amplitude |
| 371 |
|
✗ |
amplitude = cos*cos + sin*sin; |
| 372 |
|
|
|
| 373 |
|
|
// Amplitude error, limiting upper value (for very small |
| 374 |
|
|
// amplitudes) |
| 375 |
|
✗ |
ampl_modulation = 1.0/3.1415 |
| 376 |
|
✗ |
* (amplitude/amplitude_set/amplitude_set - 1); |
| 377 |
|
✗ |
if (ampl_modulation > 1.0) |
| 378 |
|
✗ |
ampl_modulation = 1.0; |
| 379 |
|
|
|
| 380 |
|
|
// Calculate derivatives |
| 381 |
|
✗ |
derivative[0] = -omega*sin - ampl_modulation*cos; |
| 382 |
|
✗ |
derivative[1] = omega*cos - ampl_modulation*sin; |
| 383 |
|
|
|
| 384 |
|
|
// Integrate |
| 385 |
|
✗ |
cos += 1.0e-9*tsample_ns*derivative[0]; |
| 386 |
|
✗ |
sin += 1.0e-9*tsample_ns*derivative[1]; |
| 387 |
|
|
|
| 388 |
|
|
// Check amplitude |
| 389 |
|
✗ |
if (cos > ampl_limit) cos = ampl_limit; |
| 390 |
|
✗ |
if (cos < -ampl_limit) cos = -ampl_limit; |
| 391 |
|
✗ |
if (sin > ampl_limit) sin = ampl_limit; |
| 392 |
|
✗ |
if (sin < -ampl_limit) sin = -ampl_limit; |
| 393 |
|
|
} |
| 394 |
|
|
|
| 395 |
|
|
/* Sub-rating task for saw tooth */ |
| 396 |
|
✗ |
if (!--decimation_counter) { |
| 397 |
|
✗ |
decimation_counter = 10; // Reset decimation counter |
| 398 |
|
|
|
| 399 |
|
✗ |
++counter; // Sawtooth with natural wrap |
| 400 |
|
|
} |
| 401 |
|
|
|
| 402 |
|
✗ |
for (unsigned i = 0; i < NUM_EVENTS; ++i) |
| 403 |
|
✗ |
pdserv_event_set(event, i, event_state[i], &world_time); |
| 404 |
|
|
|
| 405 |
|
|
/* Release locks */ |
| 406 |
|
✗ |
pthread_mutex_unlock(&mutex); |
| 407 |
|
|
|
| 408 |
|
|
/* Call at end of calculation task, so that pdserv updates itself */ |
| 409 |
|
✗ |
pdserv_update(pdtask, &world_time); |
| 410 |
|
|
|
| 411 |
|
|
/* Calculate timing statistics */ |
| 412 |
|
✗ |
cycle_time = 1.0e-9 * DIFF_NS(last_start_time, start_time); |
| 413 |
|
✗ |
exec_time = 1.0e-9 * DIFF_NS(last_start_time, end_time); |
| 414 |
|
✗ |
last_start_time = start_time; |
| 415 |
|
✗ |
pdserv_update_statistics(pdtask, exec_time, cycle_time, overruns); |
| 416 |
|
|
|
| 417 |
|
✗ |
timer_add(&monotonic_time, tsample_ns); // Increment timer |
| 418 |
|
|
|
| 419 |
|
✗ |
clock_gettime(CLOCK_MONOTONIC, &end_time); |
| 420 |
|
|
|
| 421 |
|
✗ |
overruns += DIFF_NS(monotonic_time, end_time) > 0; |
| 422 |
|
|
|
| 423 |
|
|
/* Wait for next cycle */ |
| 424 |
|
✗ |
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &monotonic_time, 0); |
| 425 |
|
|
} |
| 426 |
|
|
|
| 427 |
|
|
///////////////////// clean up ///////////////////////////////////// |
| 428 |
|
|
|
| 429 |
|
|
/* Clean up */ |
| 430 |
|
✗ |
pdserv_exit(pdserv); |
| 431 |
|
|
|
| 432 |
|
✗ |
pthread_mutex_destroy(&mutex); |
| 433 |
|
|
|
| 434 |
|
✗ |
out: |
| 435 |
|
✗ |
if (err) { |
| 436 |
|
✗ |
fprintf(stderr, "Fatal error: %s\n", err); |
| 437 |
|
✗ |
return 1; |
| 438 |
|
|
} |
| 439 |
|
✗ |
return 0; |
| 440 |
|
|
} |
| 441 |
|
|
|
| 442 |
|
|
/****************************************************************************/ |
| 443 |
|
|
|