The awesome embedded adventures logo goes here!


About Time

It soon becomes apparent when working with microcontrollers, that things happening at the right time is crucial to things working at all. In this tutorial we explore timers and some tools to help timing events.

PIC microcontrollers generally have at least one timer, and often three, and some of them have many timers.  These are generally registers that will clock over once per instruction cycle, and when they flip over (in the case of an 8 bit timer, from 0xff to 0x00) they can generate an interrupt.  Of course, counting from 0 to 255 (0xff) at 8 Mhz, which is 2mips, isn’t very long – and you’re not going to get much done in between each interrupt!  256 clocks at 2mips is 0.000128 seconds – or 128 microseconds. Not very long at all. Most of our existing designs use 64Mhz clocks – which means that each clock ends up being 16 microseconds.  So there’s a trade off here – faster clocks give more accurate timings, but don’t allow you to time any that takes much length of time.

To make it easier to timer longer periods, some timers are 16 bit (65536 clocks at 2 mips is 0.032768 seconds) and most timers allow for something called a prescaler.  The prescaler in an independent counter that divides down the clock so the timer is incremented less often.  They’re always in powers of two, so you’ll see them in ratios like 1 to 2, or 1 to 4, or 1 to 8 and so on.  In the case of a 1 to 2 prescaler setting for example, it results in the timer counting up every 2 clock cycles, not every clock cycle.  1 to 4 results in the timer counting up every 4 clock cycles, and so on. 

The PicPack library contains several tools for manipulating timers and keeping track of timings.  The first tool is a set of routines found in the pic_timer library, for setting up and starting timers.  The second is the pic_tick library, which makes it easier to work out how much time has elapsed between events.  Later on in this series, you’ll see how important these concepts are when we create our own meshed packet RF network.  But for now, we’ll just see how these timers work.

Open up the tick_demo project.  There’s two parts to using timers with the PicPack library.  The first is manipulating the timer hardware itself.  Have a look at the tick_demo.c file, and the configure_system routine:

#ifdef _PIC18
// 1ms at 12Mhz
timer_setup_0(TIMER_16BIT_MODE, TIMER_PRESCALER_OFF, 0xffff - 3000 + 13); 
//timer_setup_0(TIMER_16BIT_MODE, TIMER_PRESCALER_1_TO_2, 0xffff - 1500 + 7);
//timer_setup_0(TIMER_16BIT_MODE, TIMER_PRESCALER_1_TO_8, 0xffff - 375 + 1); 

In this example, we are assuming that the clock is running at 12Mhz and we’re using an 18f type of pic, which has a 16 bit mode for timer 0. Knowing that each instruction takes 4 clock cycles, we would expect this to take 3000 clocks.  The first (commented out) line takes that stance – the timer counts up from the value given, so we need to subtract the 3000 from 0xffff.  Note there’s an extra 13 added due to the number of instructions taken to get everything processed, the fact the sometimes updating the timer actually stops the timer for a few clocks on some chips.  Of course, if we were using the prescaler at 1 to 2, we would only need 1500 clocks – and less compensation.  At 1 to 8, we only need 625.  The timer is going to be more accurate at the lowest prescalar value (ie, off) – since you can compensate very accurately.  By the time you’re at 1 to 8, you’ve got a compensation level 8 times as coarse.  Still, unless your clock needs to be mission critical (ie, actually running a real time clock), any of these will be close enough (within 10 clocks of the required value, which is 2.5 microseconds.  The timer routines adjust where they can – given your value, they will adjust the next timer loop if the PIC has been busy while the timer interrupt occurred and didn’t get to it as quickly as it did the last time through the loop.  This means that while one timer interrupt might take slightly longer than you expected, the next one will be automatically shortened to compensate.  So on average, it’s pretty accurate and errors don’t get compound. Now, have a look at the pic 16 version:

#ifdef _PIC16
// 1ms at 8Mhz
timer_setup_0(TIMER_8BIT_MODE, TIMER_PRESCALER_1_TO_8, 0xff - 250 - 1);

Even though the 16f devices don’t have a 16 bit timer 0 (they have 16 bit timer1 and timer2 modules however), we can pass in the same types of parameters as the 18f equivalent routines.  If we assume that we have a 16f device running at 8Mhz, then we’re looking for 2000 clock cycles to make up 1 ms.  Counting to 255 isn’t going to get us far – so we have to employ the prescaler.  And if we set it to 1 to 8, then counting to 250 x 8 = 2000 clock cycles, give or take.  Of course, as we know, there’s not much room for correction at the 1 to 8 prescaler level.  However, for most cases, this should be accurate enough.  When you’re using timers, once you’ve set them up (as above), you’ll need to start them, as in the main routine:

timer_start_0();           // kick that timer off...

You can also use timer_stop_0() to stop the timer temporarily, with 18f devices you can do just that, stop the timer.  With 16f devices, the timer doesn’t have an “off”, so we just turn the timer 0 interrupts off instead. 

Right, so now we’ve got a 1ms timer, what can we do with it?  Well, let’s say we want to do something every second.  Or see how long it’s been since a particular event has occurred.  This is where the pic_tick library comes into play.  Back in tick_demo.c, have a look at the interrupt routine:

void interrupt() {

There’s the serial handlers, as usual, but also the timer_handle_0_isr() routine – which takes care of timer 0.  Note that in your code somewhere, you need to include a routine called timer_0_callback() like this:

void timer_0_callback() {
            // do something every timer interrupt

Every timer 0 interrupt, this routine will get called.  In the case of the pic_tick library, we call handle_tick() – which increments the pic_tick “tick” counter.  So we know our “ticks” in this program are every 1ms.  The ticks are counted using a 16 bit counter, with some routines to help work out timings.

Now, let’s say we want to do something every 1 second.  That’s 1,000ms.  Have a look in the main() routine:

test_tick = tick_get_count();   // find out what we're up to
if (tick_calc_diff(tick_marker, test_tick) >= 1000) { // 1000 - it's a second
            serial_print_str("  ");   // print something out
            tick_marker = test_tick;          // reset to find next 1000

Tick_get_count() gets the current tick value.  Then tick_calc_diff is used to calculate how many ticks there are between tick_marker and test_tick.  It’s assumed that the first parameter occurs earlier in time than the second.  The routine works even if the tick count wraps around 0xffff.  If the difference is greater than or equal to 1000, then we print something out, include the tick count we’re up to.  Then we reset tick_marker to the current tick count.

Why >= 1000? Why not just == 1000? Well, you can imagine that if the pic is busy doing something else, even if it’s in the main loop, then it may well be that the ticks creep past 1000, in which case, our next print out will happen after it wraps around 0xffff again, obviously not as good as just being out by 1 tick or so.  Note that this application is simple enough that we almost never miss the 1000 mark and print out our “Ting!” on time.  In more complicated programs, that may not be the case.

Have a go at tick_demo and see how accurate it is for yourself.  Make sure you adjust the timer_setup_0 values for your clock speed, don’t forget to change the values in config.h and set the target in the Settings | Target… menu.  You shouldn’t have to adjust the ‘13’ etc, just the main number, eg, 3000 or in the case of the 16f device, 250.  If you were running at 20Mhz, on a 16f88, so an 8 bit timer0, can you work out what prescalar and starting value you would need to get pretty close to 1ms?   As another exercise, can you work out how to print out how long has elapsed between the user entering terminal commands?