Interrupt driven Timer/Timing tutorial
This tutorial covers the basics of the TimerA0 capabilities including interrupt configuration. The Timer module counts clock cycles in an accumulation register called the “TAR” register. The chip I’m using (MSP430G2553) has 3x associated capture compare registers. These registers compare a set value (that they hold) to the TAR and throw an interrupt when they match. A simple way to look at this is that TimerA0 as 3x sub-modules (the capture compare registers) and all of them work together as a unit. Below is a screen shot from the datasheet.
Starting with a basic clock configuration of MCLK (main clock) as 8Mhz and SMCLK (sub-main clock) as 1 Mhz. I also configure the two LEDs on the launchpad so I can get some visual indication from the capture compare interrupts. After this, I’m at the TimerA0 configuration section.
// Setup TimeA0 TA0CTL |= TASSEL1; // Sets TimerA0 to use SMCLK as Timer clock TA0CTL |= ID0 + ID1; // Divide Timer clock by 8 to yield 125khz TA0CTL |= MC1; // set timer to continuous mode
The first line sets the TimerA module to use the sub-main clock as it’s clock. This is important because this, in conjunction with the clock division register, determines the basis of the Timer’s timing. The next line sets the divider. At this point, I’m dividing sub main by 8 because I’ve set the division bits to “11”. This yields a clock rate of 1Mhz/8 = 125Khz. This is how fast we increment the TAR register…125,000 times per second.
The last instruction sets the timer mode to Continuous Mode. This concept is best explained by a picture from the datasheet:
This tells the timer to run continuously. This means that the TAR register will count up to 0xFFFF and then roll over. This is pretty cool because it lets me use all 3 capture compare registers independently, but you have to remember to add your counting displacement to the current TA0CCRx register. If you don’t do this then your code will interrupt correctly the first time the TAR matches the CCRx value, but once you exit the routine (without adding to CCRx) you have to wait for the timer to roll over and then count back up to your CCRx value again. The time it takes for TAR to roll over will be all error in your timing.
// CCR0 TA0CCTL0 |= CCIE; // enable CCR0 interrupt TA0CCTL0 &=~ CCIFG; // clear CCR0 interrupt flag TA0CCR0 = 62500; // interrupt 2x / sec
For this write-up, I’m only going to go through the configuration of one of the capture compare registers. Above is an example of the code. These are very simple, all you really do is enable interrupts (assuming you want to use interrupts rather than polling) and set your capture/compare value. In CCR0 I’ve set my capture/compare value to 62500 which will yield an interrupt frequency of 2x / sec.
Calculating this out:
ISR_execution = (TA0CLK-125Khz)/(CCR value – 62500) = 2
Now, on to the interrupt service routine.
#pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0_A0_ISR(void) { P1OUT ^= (BIT0); // Toggle LED TA0CCR0 += 62500; TA0CCTL0 &=~ CCIFG; }
Keep in mind that the MSP430G’s use two interrupt vectors for their timer modules. In this example, we are looking at CCR0 (interrupt vector found at TIMER0_A0_VECTOR which is defined in the header file). This interrupt service routine is for CCR0 only. An interrupt is thrown for CCR0 and another interrupt is thrown for the rest of the capture compare registers (CCR1, CCR2). I’ll touch on the other ISR next.
The pragma is a compiler directive and the interrupt vector is defined by TIMER0_A0_VECTOR which is defined in the msp430g2553.h header file. The only thing I really do in this routine is blink an LED. After that I add the timing displacement to the CCR register and clear the flag.
In more detail, the tar register can count to 0xFFFF. The CCR0 value, in hex, is 0xF424. If you don’t add anything the timer will continue to run, accumulating bits in TAR, until you roll over. There is a period of time between 0xF424 and 0xFFFF which will add error to your timing because you have to burn these clock ticks up before you get back to 0 and then count back up to 0xF424. To solve this problem, just add 0xF424 to the CCR0 register every time you interrupt.
The second service routine is for all other capture compare routines.
#pragma vector=TIMER0_A1_VECTOR __interrupt void Timer0_A1_ISR(void) { switch(TAIV){ case 0x02: P1OUT ^= (BIT6); // Toggle LED TA0CCR1 += 15625; TA0CCTL1 &=~ CCIFG; break; case 0x04: // Pass TA0CCR2 += 15625; TA0CCTL2 &=~ CCIFG; break; } }
This ISR is similar to the first one with the exception of the switch statement. This is a great way to code up this service routine. Remember, this interrupt flag is thrown for any other capture compare registers and for a TAR overflow (which I’m not using). To enable TAR overflow interrupts, enable the CCIE bit in the main timer control register.
The TAIV bits represent (numerically) which capture compare registers threw the interrupt. This allows us to switch on the specific interrupt executing the appropriate code. There isn’t much for code in here, I only blink an LED and reset the interrupt flag, as well as add the appropriate timing displacement to the CCR1 routine. CCR1 blinks the LED and CCR2 doesn’t do anything.