Timers are a fundamental building block in embedded programming and are integral when implementing an RTOS or an RTOS like solution. The high-level basic premise is that the internal module counts “ticks” either from an internal clock source or an external oscillator. When this count overflows an interrupt is thrown. Depending on how you configure the clock source, how it’s scaled, and what value you start on, the timer can be configured to trigger anywhere in the order of nano-seconds to seconds. In this basic tutorial I’ll focus on the PIC18F6622 microcontroller.
There are several critical signals that define how fast, or slow, the timer “ticks over”.
- The clock source (internal vs external oscillator)
- Prescalar selector. Selects whether or not we use the prescalar.
- Prescalar. The prescalar slows our timer down by dividing out our clock pulses.
- Timer High and Low register bytes. This is the register that increments and can be pre-loaded after the timer overflows to give us more precise control over our timing.
For this example we are going to configure the Timer0 module on the PIC18F6622 to overflow once every 100ms.
Initialization
The first two things you need to do is to look at how often you want the timer to trigger and how fast our clock is. In this case that would be 100ms and 64 MHz. The register that represents our timer’s counter (TMR0H and TMR0L) is 16-bits long. This unsigned value can range from 1 to 65,535. This means that with a prescalar disabled our effective range is ~15ns to 983,025ns (983us). Since the maximum value is too fast we increment to the next prescalar until our target timer value is within our bounds. In this case it will be the 1:32 prescalar. Finally we need to tune our timer to the appropriate range by setting the starting value of the timer register.
1 2 3 4 5 6 7 8 9 |
// Configure timer to 100ms // Internal oscilator is set to 64 MHz (~15ns period) void InitTimer0(){ T0CON = 0x84; // 1000 0100; 16-bit mode, internal oscillator, low-high, 1:32 prescalar TMR0H = 0x3C; // Pre-load to 0x3CB0 (decimal 15,536) TMR0L = 0xB0; // GIE_bit = 1; // Enable general purpose interrupts TMR0IE_bit = 1; // Enable our timer } |
Interrupt Handler
In mikroC, whenever an interrupt is triggered the Interrupt() function is called at the next available opportunity. It’s a shared function for all interrupts so to determine what vector triggered entry you need to perform a conditional test by looking at all of the available interrupt flags. As per standard practice the first thing you need to do is to reset your flags to the default state and reload the timer starting point. If for example, you need to stagger whatever code is executed on based on the timer you can implement a secondary counter so that each entry can trigger different code. You could also use this to give yourself longer timer values. Since our timer is configured to trigger every 100ms you can count up with an unsigned int this 100ms timer can be extended to over 12 hours.
1 2 3 4 5 6 7 8 9 |
void Interrupt(){ if (TMR0IF_bit){ TMR0IF_bit = 0; TMR0H = 0x3C; TMR0L = 0xB0; // Enter your code here // The timer can be increased by using a secondary counter } } |