Getting start with timer_create on NuttX
I tried to port timer service management code from Linux to NuttX, I found it much easier than expectation. Here I will share the example code I wrote on NuttX for a simple timer servicer management.
In the demo code, I change USEC_PER_TICK to 1000 in order to get 1ms tick accuracy for the timer service.
Here is the timer service initialization code, it uses timer_create to create 1ms interval timer and timer timeout notify function will post a semaphore to a timer service thread:
int init_timer_manager(int cycle_ms)
{
struct sigaction act;
struct sigaction oact;
struct sigevent notify;
struct itimerspec timer;
timer_t timerid;
int status;
struct sched_param sparam;
int prio_min;
int prio_max;
int prio_mid;
pthread_attr_t attr;
sem_init(&timer_sem, 0, 0);
INIT_LIST_HEAD(&oneshoot_list);
INIT_LIST_HEAD(&periodic_list);
pthread_mutex_init(&periodic_mut, NULL);
pthread_mutex_init(&oneshoot_mut,NULL);
status = pthread_attr_init(&attr);
if (status != OK)
{
printf("%s: pthread_attr_init failed, status=%d\n",__FUNCTION__, status);
}
prio_min = sched_get_priority_min(SCHED_FIFO);
prio_max = sched_get_priority_max(SCHED_FIFO);
prio_mid = (prio_min + prio_max) / 2;
sparam.sched_priority = (prio_mid + prio_max) / 2;
status = pthread_attr_setschedparam(&attr,&sparam);
if (status != OK)
{
printf("%s: ERROR: pthread_attr_setschedparam failed, status=%d\n",__FUNCTION__, status);
goto errorout;
}
status = pthread_create(&timer_threadid, &attr, timer_thread, NULL);
if (status != 0)
{
printf("%s: ERROR: thread creation failed\n", __FUNCTION__);
goto errorout;
}
/* Set timer timeout action */
act.sa_sigaction = timer_expiration;
act.sa_flags = SA_SIGINFO;
(void)sigfillset(&act.sa_mask);
(void)sigdelset(&act.sa_mask, TIMER_MANAGER_SIGNAL);
status = sigaction(TIMER_MANAGER_SIGNAL, &act, &oact);
if (status != OK)
{
printf("%s: ERROR sigaction failed, status=%d\n" ,__FUNCTION__, status);
goto errorout;
}
/* Create the POSIX timer */
notify.sigev_notify = SIGEV_SIGNAL;
notify.sigev_signo = TIMER_MANAGER_SIGNAL;
notify.sigev_value.sival_int = TIMER_SIGVALUE_INT;
#ifdef CONFIG_SIG_EVTHREAD
notify.sigev_notify_function = NULL;
notify.sigev_notify_attributes = NULL;
#endif
status = timer_create(CLOCK_REALTIME, ¬ify, &timerid);
if (status != OK)
{
printf("%s: timer_create failed, errno=%d\n",__FUNCTION__, errno);
goto errorout;
}
/* Start the POSIX timer */
timer.it_value.tv_sec = 2; /* initial value shouldn't set to 0 */
timer.it_value.tv_nsec = 0;
timer.it_interval.tv_sec = (cycle_ms/NSEC_PER_USEC);
timer.it_interval.tv_nsec = ((cycle_ms%NSEC_PER_USEC)*NSEC_PER_MSEC);
status = timer_settime(timerid, TIMER_ABSTIME, &timer, NULL);
if (status != OK)
{
printf("%s: timer_settime failed, errno=%d\n",__FUNCTION__, errno);
goto errorout;
}
return status;
errorout:
sem_destroy(&timer_sem);
return status;
}
Timeout notify function:
static void timer_expiration(int signo, siginfo_t *info, void *ucontext)
{
int status;
// printf("timer_expiration: Received signal %d\n" , signo);
// fflush(stdout);
/* Check signo */
if (signo != TIMER_MANAGER_SIGNAL)
{
printf("timer_expiration: ERROR expected signo=%d\n" , TIMER_MANAGER_SIGNAL);
}
else if (timer_taskcount > 0)
{
status = sem_post(&timer_sem);
if (status != OK)
{
printf("poster_func: ERROR: sem_post failed\n");
}
}
}
The timer service thread was waiting for semaphore, when it got the semaphore from timeout notify function, the thread begin to scan all registered timer service and check if there is any timer service timeout. If timer service timeout, registered timer service will get executed on timer service thread.
static void *timer_thread(FAR void *para)
{
int status;
s_timer_unit* timer;
struct list_head* curr_list;
while (1)
{
status = sem_wait(&timer_sem);
if (status != 0)
{
printf("%s: sem_wait ERROR \n", __FUNCTION__);
continue;
}
// printf("timer_thread run\n");
// fflush(stdout);
/* Scan oneshoot timer list and found if any timer task timeout */
status = pthread_mutex_lock(&oneshoot_mut);
if (status == 0)
{
for (curr_list = oneshoot_list.next; curr_list != &oneshoot_list; )
{
timer = container_of(curr_list, s_timer_unit, list);
curr_list = timer->list.next;
if(++timer->curr_time >= timer->timeout_time)
{
timer_taskcount--;
timer->notify(timer->timer_param);
list_del(&timer->list);
free((void *)timer);
}
}
pthread_mutex_unlock(&oneshoot_mut);
}
/* Scan periodic timer list and found if any timer task timeout */
status = pthread_mutex_lock(&periodic_mut);
if (status == 0)
{
for (curr_list = periodic_list.next; curr_list != &periodic_list; )
{
timer = container_of(curr_list, s_timer_unit, list);
curr_list = timer->list.next;
if(++timer->curr_time >= timer->timeout_time)
{
timer->notify(timer->timer_param);
timer->curr_time = 0;
}
}
pthread_mutex_unlock(&periodic_mut);
}
}
pthread_exit(NULL);
return NULL; /* Non-reachable -- needed for some compilers */
}
It provides periodic and oneshoot timer service attach/detach API, all registered timer services will be added to oneshoot_list or periodic_list. The demo was written in C, so I use list to manage all these services.
int attach_timer(timer_notify notify, int timeout_ms,
e_periodic_attr attr, void* param)
{
int status;
s_timer_unit* new_timer;
pthread_mutex_t* mux;
struct list_head* list;
if ((notify == NULL) || (timeout_ms == 0))
{
return -ENOENT;
}
new_timer = (s_timer_unit* )malloc(sizeof(s_timer_unit));
if (new_timer == NULL)
{
return -ENOMEM;
}
INIT_LIST_HEAD(&new_timer->list);
new_timer->notify = notify;
new_timer->curr_time = 0;
new_timer->timeout_time = timeout_ms;
new_timer->timer_param = param;
if (attr == ONESHOOT_TYPE)
{
mux = &oneshoot_mut;
list = &oneshoot_list;
}
else if (attr == PERIODIC_TYPE)
{
mux = &periodic_mut;
list = &periodic_list;
}
else
{
goto error_out;
}
status = pthread_mutex_lock(mux);
if (status != 0)
{
printf("%s: mutex_lock error\n",__FUNCTION__);
goto error_out;
}
timer_taskcount++;
list_add_tail(&new_timer->list, list);
pthread_mutex_unlock(mux);
return status;
error_out:
free((void *)new_timer);
return status;
}
For the list operation source code, you can refer to Linux kernel code.
At the end of this blog, I add a simple testing code for testing this timer service management code.
static uint32_t oneshoot_count = 0;
static uint32_t periodic_count = 0;
static void oneshoot_timer(void* param)
{
uint32_t* pbuf = (uint32_t* )param;
printf("%s: run time=%d\n",__FUNCTION__,*pbuf);
*pbuf = *pbuf + 1;
}
static void periodic_timer(void* param)
{
uint32_t* pbuf = (uint32_t* )param;
printf("%s: run time=%d\n",__FUNCTION__,*pbuf);
*pbuf = *pbuf + 1;
}
void run_timer_example(void)
{
/* make sure CONFIG_USEC_PER_TICK was set to 1000 - system tick resolution 1ms */
init_timer_manager(1);
/* oneshoot timer API testing code here - 1000ms interval */
attach_timer(oneshoot_timer, 1000, PERIODIC_TYPE, (void *)&oneshoot_count);
while(oneshoot_count <= 10)
{
sleep(10);
}
detach_timer(oneshoot_timer);
oneshoot_count = 0;
attach_timer(oneshoot_timer, 1000, ONESHOOT_TYPE, (void *)&oneshoot_count);
while(oneshoot_count == 0)
{
sleep(10);
}
/* periodic timer API testing code here - 500ms timeout */
attach_timer(periodic_timer, 500, PERIODIC_TYPE, (void *)&periodic_count);
while(periodic_count <= 10)
{
sleep(10);
}
detach_timer(periodic_timer);
}