In this tutorial we will go through MSP430 Timer programming for MSP430x2xx devices like MSP430G2553, MSP430G2231 found on Launchpad development board. MSP430G2 devices have two 16-bit timers i.e. Timer_A and Timer_B. Timer_B is slightly different than Timer_A and also has few more features, but it is NOT implemented in both MSP430G2553 and MSP430G2331 micro-controllers. Hence, we will focus on Timer_A for this tutorial.
Introduction
MSP430G2553 has two Timer_As viz. Timer0_A3 and Timer1_A3 which features 3 capture/compare registers while MSP430G2231 has only 1 timer called Timer0_A2 with only 2 capture/compare registers. In addition to Capture, Timer_A also supports PWM outputs and interrupts. They also include a Watchdog Timer (WDT+) which I will discuss in another tutorial.
Timer_A supports four different clock sources: ACLK, SMCLK and 2 external sources: TACLK or INCLK. The selected clock source can then be divided by 1,2,4 or 8. The register used for counting is called TAR(16-bit R/W) and can increment or decrement for each rising edge of clock signal. Compared to Timer blocks of other microcontrollers, these MCUs don’t support prescaler.
Timer Modes:
Timer_A supports 4 modes of operation:
- Stop Mode: In this mode the Timer is Halted.
- Up Mode: Timer repeatedly counts from Zero to value stored in Capture/Compare Register 0 (TACCR0).
- Continuous: Timer repeatedly counts from Zero to 0xFFFF, which is maximum value for 16-bit TAR.
- Up/Down Mode: Timer repeatedly counts from Zero up to the value in TACCR0 and back down to zero.
MSP430 Timer Registers
1) TAR – Timer Counter Register: Holds the current count for Timer_A.
2) TACCRx – Timer Capture/Compare Register: In Compare mode, it holds compare value to be compared against TAR. In Capture mode, it holds the current value of TAR when a capture is performed. Maximum value it can store is 0xFFFF since its a 16 bit register.
3) TACTL – Control Register: Used to configure Timer_A. This register is divided as follows:
- Bit[0] – TAIFG – Timer_A Interrupt Flag. 0 = No interrupt pending, 1 = Interrupt pending.
- Bit[1] – TAIE – Timer_A Interrupt Enable. 0 = Interrupt Disabled, 1 = Interrupt Enabled.
- Bit[2] – TACLR – Timer_A clear. Setting this bit resets TAR, clock divider, and the count direction. It is automatically reset and is always read as zero.
- Bits[5:4] – MCx – Mode Control bits. Used to select between 4 different modes as given:
[00] = MC_0 – Stop mode: Timer is halted.
[01] = MC_1 – Up mode: Timer counts up to TACCR0.
[10] = MC_2 – Continuous mode: Timer counts up to 0xFFFF.
[11] = MC_3 – Up/down mode: Timer counts up to TACCR0 then down to 0x0000. - Bit[7:6] – IDx – Input divider. Selects input clock divider.
[00] = ID_0 – /1
[01] = ID_1 – /2
[10] = ID_2 – /4
[11] = ID_3 – /8 - Bits[9:8] TASSELx – Timer_A clock source select.
[00] = TASSEL_0 – TACLK
[01] = TASSEL_1 – ACLK
[10] = TASSEL_2 – SMCLK
[11] = TASSEL_3 – INCLK (check datasheet) - Other Bits – Reserved
3) TACCTLx – Capture/Compare Control Register: Used to configure capture/compare options. I will only cover the parts of this register which are applicable to this tutorial. We will see it in detail in other tutorials(for Capture mode & PWM).
- Bit[0] – CCIFG – Capture/compare interrupt flag: 0 = interrupt pending, 1 = Interrupt pending.
- Bit[4] – CCIE – Capture/compare interrupt enable: This bit enables the interrupt request of the corresponding CCIFG flag. 0 = Interrupt disabled = 1 Interrupt enabled.
- Bit[8] – CAP – Capture mode: Used to select between Compare and Capture mode. We will this bit in its default setting i.e. = 0. 0 = Compare mode, 1 = Capture mode.
4) TAIV – Interrupt Vector Register: Used to identify the flag which requested an interrupt. This is a read only register and only uses 3 bits [3:1] called TAIVx. The values for TAIVx which corresponds to various sources is as given below:
- 0x00 = No Interrupt Pending.
- 0x02 = Capture/Compare 1 – TACCR1 CCIFG.
- 0x04 = Capture/Compare 2 – TACCR2 CCIFG.
- 0x0A = Timer(TAR) Overflow – TAIFG.
- Other – Reserved.
Configuring & Setting Up Timer in MSP430
Given, clocks are configured properly, basic setup of Timer_A can be done follows:
- Set the Compare value in TACCR0 if using Up mode or Up/Down mode. Timer may be stopped by using TACCR0 = 0; and can be started/restarted any time by writing a non-zero compare value in TACCR0 whenever Timer is to be used. Total Counts by timer will be TACCR0 + 1.
- Set CCIE(Capture/Counter Interrupt Enable) bit in TACCTL0 to enable interrupt for CCR0. We also keep the CAP bit to default value(=0) to enable Compare mode.
- Select Clock Source, Input Clock divider and Timer mode using TASSELx, IDx, MCx fields respectively.
- Enable global Interrupts and we are done.
In examples given below we will use Up mode with input clock source as SMCLK(MCx = MC_1) without an divider(IDx = ID_0 i.e. divide by 1). We will set MCLK and SMCLK to run at 1MHz, both sourcing clock from DCO. This is the default configuration.
TACCR0 = ..; //Compare Value for TAR
TACCTL0 |= CCIE; //Enable interrupt for CCR0.
TACTL = TASSEL_2 + ID_0 + MC_1; //Select SMCLK, SMCLK/1 , Up Mode
_enable_interrupt();
/* More code */
Handy MSP430 Timer Formulae for delay calculations
Formula for amount of time taken to increment TAR count by 1 is given as:
where DIV = Input Clock divider either 1,2,4 or 8.
E.g.: When using divider of /2 and Input clock of 4MHz we get timer resolution as,
Seconds = 0.5 x 10-6 Seconds = 0.5 µS
The time required for TAR to count from 0 and reach TACCR0 (i.e. overflow or TAR period) is given as:
We subtract 1 from TACCR0 since TAR counts “TACCR0+1” times to overflow. This is because count starts from 0.
E.g.: When using divider of /2, Input clock of 4MHz and TACCR0 = 1000-1 = 999, we get Timer Period as,
= 0.5 x 1000 x 10-6 S = 500 µS
MSP430 Timer Examples
Now, lets cover 2 Timer examples. Both examples are valid for MSP430G2553 , MSP430G2231 and similar MCUs.
Example 1: A simple Delay function using Interrupt
Of-course, we have the option of using inbuilt function __delay_cycles(..), but wheres the fun if we don’t implement a similar function using Timer? Anyways, in this example we will define a function called delayMS(int msec) which generates delay as per given input in mill-seconds. This function is used in conjunction with Timer_A Interrupt for CCR0. The time is counting, ISR continuously increments an Overflow counter when TAR reaches value in TACCR0. We set the value in TACCR0 such that counting from 0 to TACCR0 takes exactly 1ms, and hence we get a resolution of 1ms for our delay function. This function will give more accurate delay as MCLK increases, since it contains a few statements which eat up proportionate amount CPU cycles. Now, since we are using Timer Clock = 1MHz (SMCLK=1MHz), 1000 ticks will equal to 1ms. Hence, we use TACCR0 = 1000 inside delay function. In general for Y MHz timer clock, Y x 1000 ticks are required for 1ms delay. Before exiting the function we use TACCR0 = 0 to stop the Timer.
#include <msp430.h>
void initTimer_A(void);
void delayMS(int msecs);
unsigned int OFCount;
int main(void)
{
WDTCTL = WDTPW + WDTHOLD; //Stop watchdog timer
P1DIR |= BIT0; //Configure P1.0 as Output
//Set MCLK = SMCLK = 1MHz
BCSCTL1 = CALBC1_1MHZ;
DCOCTL = CALDCO_1MHZ;
initTimer_A();
_enable_interrupt();
while(1)
{
P1OUT |= BIT0; //Drive P1.0 HIGH - LED1 ON
delayMS(500); //Wait 0.5 Secs
P1OUT &= ~BIT0; //Drive P1.0 LOW - LED1 OFF
delayMS(500); //Wait 0.5 Secs
}
}
void initTimer_A(void)
{
//Timer0_A3 Configuration
TACCR0 = 0; //Initially, Stop the Timer
TACCTL0 |= CCIE; //Enable interrupt for CCR0.
TACTL = TASSEL_2 + ID_0 + MC_1; //Select SMCLK, SMCLK/1, Up Mode
}
void delayMS(int msecs)
{
OFCount = 0; //Reset Over-Flow counter
TACCR0 = 1000-1; //Start Timer, Compare value for Up Mode to get 1ms delay per loop
//Total count = TACCR0 + 1. Hence we need to subtract 1.
while(i<=msecs);
TACCR0 = 0; //Stop Timer
}
//Timer ISR
#pragma vector = TIMER0_A0_VECTOR
__interrupt void Timer_A_CCR0_ISR(void)
{
OFCount++; //Increment Over-Flow Counter
}
Example 2: Blinky using MSP430 Timer Interrupt
In this example, instead of using a dedicated delay function we place the blinky code inside the Timer_A Interrupt itself. The Timer initialization code is same as before. The Timer is never stopped and it repeatedly restarts counting when TAR reaches TACCR0 to generate 1ms delay. Similar to previous example, an overflow counter is maintained by ISR itself. We just need to define how much delay(in ms) we require. This is done by defining a MACRO BLINKY_DELAY_MS. Also, note that we won't be using Low Power Mode (LMP0). If you want to use LMP0 than make sure TACCR0 has a suitable maximum value along with higher clock divider, so CPU stays disabled most of the time. In our case we can use TACCR0 = 50000; and use overflow count limit of 10 for 500ms delay. In this way the ISR is called every 50ms when clock divider is 1, and every 200ms when clock divider is 4.
#include <msp430.h>
#define BLINKY_DELAY_MS 500 //Change this as per your needs
void initTimer_A(void);
void delayMS(int msecs);
unsigned int OFCount;
int main(void)
{
WDTCTL = WDTPW + WDTHOLD; //Stop watchdog timer
P1DIR |= BIT0; //Configure P1.0 as Output
//Set MCLK = SMCLK = 1MHz
BCSCTL1 = CALBC1_1MHZ;
DCOCTL = CALDCO_1MHZ;
initTimer_A();
_enable_interrupt();
OFCount = 0;
TACCR0 = 1000-1; //Start Timer, Compare value for Up Mode to get 1ms delay per loop
/*Total count = TACCR0 + 1. Hence we need to subtract 1.
1000 ticks @ 1MHz will yield a delay of 1ms.*/
while(1);
}
void initTimer_A(void)
{
//Timer Configuration
TACCR0 = 0; //Initially, Stop the Timer
TACCTL0 |= CCIE; //Enable interrupt for CCR0.
TACTL = TASSEL_2 + ID_0 + MC_1; //Select SMCLK, SMCLK/1 , Up Mode
}
//Timer ISR
#pragma vector = TIMER0_A0_VECTOR
__interrupt void Timer_A_CCR0_ISR(void)
{
OFCount++;
if(OFCount >= BLINKY_DELAY_MS)
{
P1OUT ^= BIT0;
OFCount = 0;
}
}