Timers
Contents
Introduction
Timers can be used to control periodic tasks as well as implement sophisticated algorithms. The implementation of each type of timer is platform-dependent and has different properties that make them useful in specific situations; some timers have low granularity (seconds) and overflow once in tens of years, and others provide high granularity (microseconds), but overflow rapidly. There are 5 types of timers provided by Contiki:
- timer
- stimer
- ctimer
- etimer
- rtimer
Overview
The functions of all types of timers are located inside folder core/sys/{timer, stimer, ctimer, etimer, rtimer}.{c,h}. A complete documentation of Timers in Contiki can be found here.
The timer and stimer are the most basic types of timers and are used to check if a time interval has passed. They do not notice when the time period has elapsed, so the application needs to check periodically if they have expired. The difference between them is the resolution: timers use system clock ticks, which gives high granularity (order of microseconds) but short overflow periods (order of seconds). On the other hand, stimers use seconds to allow much longer time periods (order of years), but has lesser granularity.
The etimer provides event timers and are used to schedule events to the processes after a period of time. They are used in Contiki processes to wait for a time period while the rest of the system can work or enter low power mode.
The ctimer provides callback timers and are used to schedule calls to functions after a period of time. Like event timers, they are used to wait for some time while the rest of the system can work or enter low power mode. Since the callback timers call a function when a timer expires, they are especially useful in any code that do not have an explicit Contiki process such as protocol implementations.
The rtimer provides scheduling of real-time tasks. The rtimer library preempts any running Contiki process in order to let the real-time tasks execute at the scheduled time. The real-time tasks are used in time critical codes.
You will learn
- Simple implementations of each type of timer
- Tasks each timer can be used for
- Some functions in the timer libraries.
Step 1
Again, we will have to change folders. We will create a new file for this tutorial, but let's create it in the same directory we've been working with, so type
cd contiki-2.7/examples/rime
into the terminal. For this tutorial, we will be modifying the example-broadcast.c file we have previously used, so type
cp example-broadcast.c example-timer.c
into your terminal. You should now see a new file, "example-timer.c" in your directory. Open it up with an editor of your choice (we will use gedit for this tutorial):
gedit example-timer.c
Step 2
There is currently a working example of an etimer in this code, so before we proceed, let's understand this. At the beginning of the process, it is declared with
static struct etimer et;
This creates an etimer instance. Within the while loop, it is set with the function
etimer_set(&et, CLOCK_SECOND*4 + random_rand() % (CLOCK_SECOND * 4));
This function takes the arguments:
etimer_set(struct etimer *et, clock_time_t interval);
and sets the etimer et to expire after interval.
After that, we see the line:
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));
which tells the example_broadcast_process to pause until it receives an event, so the program is essentially giving up system control. When an etimer expires, it posts a PROCESS_EVENT_TIMER event to the example_broadcast_process. So this tells the process to continue and begin running again, as the etimer has expired, and an event has been posted. Now that we understand what the etimer is doing here, let's incorporate some other timers.
Step 3 - Adding a timer
To start simply, we are going to use a timer to just print out a message once it expires. Just below
static struct etimer et;
declare a timer with the line:
static struct timer t;
Before the while loop, set the timer with the line:
timer_set(&t, CLOCK_SECOND * 10);
This sets the timer to expire after 10 seconds.
Below the line
printf("broadcast message sent\n");
add in the code:
if(timer_expired(&t)){
printf("Timer expired\n");
}
Now, save and run your code on your Tmote Sky by typing into your terminal:
make TARGET=sky example-timer.upload
If you don't have a Sky, run this code on Mspsim with the line
make TARGET=sky example-timer.mspsim
Among the "broadcast message sent" messages, you should see a "Timer expired" notice. This only occurs one time, however. This is because we never reset the timer. So, return to your example-timer.c file. Underneath your printf("Timer expired\n") line, add the code
timer_reset(&t);
This will reset the timer to expire again in 10 * CLOCK_SECONDS. Run your code again. You should now see "Timer expired" every few broadcast messages. Now, say we only wanted to send broadcast messages for 30 seconds. Let's use our timer to exit our process after it expires. Change your original timer_set function to:
timer_set(&t, CLOCK_SECOND * 30);
Now, just delete the line
timer_reset(&t);
and replace it with
PROCESS_EXIT();
Run your code again, and it should stop after the timer expires. You have now learned some basic functionality of the timer.
Step 4 - Using ctimer
Begin by deleting the
PROCESS_EXIT();
line that you previously wrote. We are going to implement a ctimer and no longer want to exit the process. At the beginning of the process, where you declared the other timers, include the code
static struct ctimer ct;
This declares a ctimer, which has the property of calling a function when it expires. In order to do this, we will need to declare a function for it to call! So, just before the start of the process, underneath the line
static struct broadcast_conn broadcast;
Declare the following function:
static void timer_events(void *ptr){
printf("Congratulations, you called this function");
}
Now, go back to the process. Just before the while loop, set the ctimer by typing
ctimer_set(&ct, CLOCK_SECOND * 5, timer_events, NULL);
This set function takes in arguments that tell the ctimer which function to call, as well as what to pass into the function (NULL in this case). Run your code. You will see that your function is called one time, but since we never reset the code, it never gets called again. So, let's reset the ctimer as part of the function. Just before you set the ctimer, include this line:
void *ct_ptr = &ct;
And change your ctimer_set function to read:
ctimer_set(&ct, CLOCK_SECOND * 5, timer_events, ct_ptr);
Now go up to your function, and change it to read like this:
static void timer_events(void *ptr){
printf("Congratulations, you called this function"); struct ctimer* ct_ptr = ptr; ctimer_reset(ct_ptr);
}
Now run your code again. The function should be called over and over again. The last thing we will do with this function is learn how to terminate the process from the function, using our same ctimer. To begin, change your c_timer set function to
ctimer_set(&ct, CLOCK_SECOND * 30, timer_events, ct_ptr);
so the process runs longer before we exit it. Now go up to your function and delete the ctimer_reset line. Replace it with:
process_exit(&example_broadcast_process);
Run your code again. You will see that the example_broadcast_process stops running after 30 seconds, and you have learned how to do this from a function called by a ctimer!
Step 5 - Introduction to rtimer
The rtimer library provides real-time scheduling mechanisms, typically for applications where a response to an external event is extremely time sensitive. Rtimers use absolute system clock time as a reference.
We will modify our original broadcast process with an rtimer, to demonstrate rtimer's usefulness for calling a function at an absolute time in response to an event. In this case, our etimer produces random, simulated real-time events.
static void function( rtimer *rt, void *ptr) {
packetbuf_copyfrom("Event Occurred", 15); broadcast_send(&broadcast); printf("Real Time Task Completed\n");
}
The function 'responds' to the scheduling of a real-time task using rtimer, and can be used to answer an external stimulus.
PROCESS_THREAD(broadcast_example_process, ev, data) {
static struct etimer et; static struct rtimer rt;
PROCESS_EXIT_HANDLER(broadcast_close(&broadcast);) PROCESS_BEGIN(); while (1) { PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&t)); //Real Time Event printf("Real Time Event Recorded\n"); rtimer_set(&rt, RTIMER_NOW()+RTIMER_ARCH_SECOND,1,function,NULL); } PROCESS_END();
}
Note the setting method for rtimer takes 4 arguments: - real-time task (rtimer) - time at which task must be completed - duration - UNUSED - function to call - argument for the function, which is NULL in this case.
Upon simulating this code, you will see the rtimer respond almost immediately to each simulated real-time event. However, using the simulation does not allow for some of the features of rtimer, because rtimer is a low level struct that has significant dependence on physical aspects of the hardware. You will notice that replacing "RTIMER_ARCH_SECOND" with "4096*RTIMER_ARCH_SECOND" does not change the delay in simulation.
A complete example of rtimer's uses that can be run on hardware is "example-rudolph1.c" in rime.