OCFreaks!

MSP430 Timer Programming Tutorial

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.

The naming convention used in datasheet is “Timern_Ax” where n = Timer module number, x = no. of. capture/compare registers supported.

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:

  1. Stop Mode: In this mode the Timer is Halted.
  2. Up Mode: Timer repeatedly counts from Zero to value stored in Capture/Compare Register 0 (TACCR0).
  3. Continuous: Timer repeatedly counts from Zero to 0xFFFF, which is maximum value for 16-bit TAR.
  4. 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:

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).

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:

Timer Register Naming Convention: Using the register names as given in user manual will default to Timer0_A3 registers. For e.g. TACTL is same as TA0CTL. Note that Timer registers are defined as TAnCTL, TAnCCR0 and so on.. where n = Timer module number(in our case 0 or 1). For Timer1_A3 these names will be TA1CTL, TA1CCR0 and so on. TA0CTL and other Timer0_A3 are just redefined as TACTL, TACCR0 and so on.

Configuring & Setting Up Timer in MSP430

Given, clocks are configured properly, basic setup of Timer_A can be done follows:

Note that TACCR0 has dedicated interrupt vector. Other TACCRx have common interrupt vector(see TAIVx and section 12.2.6 on page 367 in User Manual).

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:

Resolution(Delay per TAR Count) in Seconds =
DIV/Input Clock in Hz

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,

Resolution =
2/4 x 106Hz

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:

Timer Period in Seconds =
DIV x (TACCR0 + 1)/Input Clock in Hz

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,

Timer Period =
2 x (999 + 1)/4 x 106Hz

= 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;
	}
}

Exit mobile version