Timers
CMRX kernel provides two basic timing functions:
- synchronous timer that provides way to delay execution of the code
- asynchronous periodic timer that executes code repeatedly at steady rate
These timers loosely follow the semantics of POSIX timers so any user familiar with basic POSIX API for timing should be familiar with them.
Delaying execution
It is a common case that your code needs to wait for something or someone. In CMRX this is possible using the usleep call.
#include <cmrx/ipc/timer.h>
#include <cmrx/application.h>
void toggle_led(unsigned led) { /* ... */ }
int main(void * data) {
while (true)
toggle_led(LED1);
usleep(500000U);
}
}
OS_APPLICATION_MMIO_RANGE(init, 0, 0);
OS_APPLICATION(init);
OS_THREAD_CREATE(init, init_main, NULL, 32);
The code above implements a blinky code which blinks a LED at 1Hz frequency. The call to usleep function will stop execution of the thread for at least given amount of time. The real time the thread is suspended depends on the state of other threads at the time it is about to be woken up.
Thread scheduler honors thread priorities, so if any other thread is ready to be scheduled, or running when the timer expires which has higher priority, the actual delay may be longer.
Note that the sleep function accepts input in microsecond granularity. The actual granularity of the delay depends on the timing provider used while integrating the software. If the granularity of timing provider is much higher than the delay requested, this call will transparently perform a busy-wait instead of thread switch.
Delays are always synchronous. The execution of the thread requesting delay is stopped until the delay time elapses.
Periodic code execution
In case thread needs to perform some periodic action with low jitter, there’s another API provided exactly for this purpose.
The low jitter criteria means that the interval between two executions of code are kept as equal as possible. This is usually a key requirement in signal processing.
Unlike usleep the periodic timer API is asynchronous. That means timers are delivered regardless of what the thread is doing right now.
To set up periodic timer, you first have to define and set up its handler. Periodic timer events are delivered via signals. Signals are another concept known from POSIX API.
void timer_handler(unsigned long sigmask)
{
/* handle timer */
}
int setup_timer()
{
signal(SIGALRM, timer_handler);
/* ... */
setitimer(500000U);
}
The code above will set up signal handler that handles SIGALRM signal delivery. Then the interval timer is configured to deliver the signal every 500 microseconds.
Note that if timer_handler takes longer than 500 microseconds, it will be called again preempting previous execution.
High precision recurrent timers
Both mechanisms outlined above are limited by the resolution of kernel timing infrastructure. This mechanism has to ballance between high sub-millisecond precision and timing overhead. If you need higher precision timing, you can leverage the power of hardware timers.
Hardware timer can trigger hardware interrupts. This mechanism is completely independent on the operating system kernel and thus have zero added latency. Hardware interrupts have always priority higher than any user code running on the CPU. The net result is high precision and minimal delay of timers triggered by hardware timer interrupts.
#include <cmrx/ipc/isr.h>
unsigned timer_object;
void TIM0_Handler()
{
isr_notify_object(&timer_object);
}
void periodic_task()
{
/* set up hardware timer to trigger TIM0 IRQ */
while (true) {
wait_for_object(&timer_object, 0U);
/* ... periodic workload ... */
}
}
The code above will configure the hardware to trigger timer overflow handler periodically. This code is hardware dependent but may be carried via driver that hides implementation details. Once the IRQ is called, your application is free to service it via its own handler.
IRQ handlers in CMRX run in privileged mod and are therefore not intended for heavy workload. Instead of processing your periodic workload inside the interrupt handler, you notify a userspace thread that can do the work in normal, protected environment.
To wake up thread use the notification mechanism. This mechanism will wake up the thread which is waiting for the notification. If thread priority of the notified thread is high enough, it will resume operation immediately.
While wait_for_object API is blocking, thread won’t waste CPU time waiting for the right time to execute its workload. As IRQ handlers are in fast-lane regarding kernel scheduling, the precision of such timing is only limited by the precision of the hardware timed used to trigger it.
Another advantage of this method is that it is essentially synchronous. If your workload takes longer to execute than the timer period, it won’t preempt itself. This avoids hard-to-debug scenarios.