Timers and PWM

The hardware timers, that are a feature of the MSP430 chips, are really useful for counting things. Timer is a bit of a misnomer as they are simply hardware registers whose value is either incremented (count up) or decremented (count down) each time the timer clock source makes a transition (usually the rising edge of the clock). The most typical application of the timers is to count the microcontroller clock cycles, which for the MSP430G2553 that you are using can readily be set to 16 or 1MHz when using the internal oscillator. Thus the timer count keeps track of time in clock cycles and hence the name timer. You can also use them for counting external events such as button presses.

The MSP430G2553 has two 16 bit type A timers as described in chapter 12 of the user guide. The control registers of these timers are referred to as TA0xxxx and TA1xxxx. Each timer has three counter compare registers TAxCCR0, TAxCCR1 and TAxCCR3. These can be used to for example set the maximum count using TAxCCR0 to define the maximum count value and setting the MCx bits 4 and 5 in the counter control register TAxCCTL1 to mode 01 in this mode the counter will count from zero to the count value in TAxCCR0 and then reset to zero.

In this c code example the timer is used to create a minute:second:tenths timer whose output is sent over the UART serial port for display in the serial terminal.


/*Demonstration timer. The timer outputs the time elasped in minutes : seconds : tenths
to the hardware serial port which may be received using a serial terminal.

After running this program you may need to unplug the USB cable before attempting to
upload new code.

Author: Benn Thomsen, December 2013
*/

#include "msp430g2553.h"

#define BUTTON BIT3
#define RELEASED 0
#define PRESSED 1

static char SwitchState = RELEASED;
static volatile char tenths = 0;
static volatile char seconds = 0;
static volatile char minutes = 0;

char numberStr[5];

/* Function Prototypes */
void ConfigureTimerA(void);
void ConfigureUART(void);
void UARTSendArray(unsigned char *TxArray, unsigned char ArrayLength);
char Int2DecStr(char *str, unsigned int value);
/* Main function */
void main(void)
{
WDTCTL = WDTPW + WDTHOLD; // Stop WDT
/* Use Factory stored presets to calibrate the internal oscillator */
BCSCTL1 = CALBC1_1MHZ; // Set DCO Clock to 1MHz
DCOCTL = CALDCO_1MHZ;

P1DIR |= BIT0; // P1.0 to output

ConfigureUART();
ConfigureTimerA();

//__enable_interrupt(); // Enable interrupts.
_BIS_SR(CPUOFF + GIE); // Turn CPU off and interrupts enabled
}

// TimerA interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A (void)
{
if (++tenths == 10){
tenths = 0;
P1OUT ^= BIT0;
if (++seconds == 60){
seconds = 0;
minutes++;
}
}

UARTSendArray(numberStr,Int2DecStr(numberStr, minutes));
UARTSendArray(":", 1);
UARTSendArray(numberStr,Int2DecStr(numberStr, seconds));
UARTSendArray(":", 1);
UARTSendArray(numberStr,Int2DecStr(numberStr, tenths));
UARTSendArray("\r\n", 2);
}

void ConfigureTimerA(void){
/* Configure timer A as a clock divider to generate timed interrupt */
TACCTL0 = CCIE; // Enable counter interrupt on counter compare register 0
TACTL = TASSEL_2 +ID_3 + MC_1; // Use the SMCLK to clock the counter, SMCLK/8, count up mode
TACCR0 = 12500-1; // Set maximum count (Interrupt frequency 1MHz/8/12500 = 10Hz)
}

void ConfigureUART(void){
#define RXD BIT1
#define TXD BIT2
/* Configure hardware UART */
P1SEL |= RXD + TXD ; // P1.1 = RXD, P1.2=TXD
P1SEL2 |= RXD + TXD ; // P1.1 = RXD, P1.2=TXD
UCA0CTL1 |= UCSSEL_2; // Use SMCLK
UCA0BR0 = 104; // Set baud rate to 9600 with 1MHz clock (Data Sheet 15.3.13)
UCA0BR1 = 0; // Set baud rate to 9600 with 1MHz clock
UCA0MCTL = UCBRS0; // Modulation UCBRSx = 1
UCA0CTL1 &= ~UCSWRST; // Initialize USCI state machine
IE2 |= UCA0RXIE; // Enable USCI_A0 RX interrupt
}

void UARTSendArray(unsigned char *TxArray, unsigned char ArrayLength){
// Send number of bytes Specified in ArrayLength in the array at using the hardware UART 0
// Example usage: UARTSendArray("Hello", 5);
// int data[2]={1023, 235};
// UARTSendArray(data, 4); // Note because the UART transmits bytes it is necessary to send two bytes for each integer hence the data length is twice the array length

while(ArrayLength--){ // Loop until StringLength == 0 and post decrement
while(!(IFG2 & UCA0TXIFG)); // Wait for TX buffer to be ready for new data
UCA0TXBUF = *TxArray; //Write the character at the location specified py the pointer
TxArray++; //Increment the TxString pointer to point to the next character
}
}

static const unsigned int dec[] = {
10000, // +5
1000, // +6
100, // +7
10, // +8
1, // +9
0
};
char Int2DecStr(char *str, unsigned int value){
// Convert unsigned 16 bit binary integer to ascii character string

char c;
char n=0;
int *dp = dec;

while (value < *dp) dp++; // Move to correct decade
do {
n++;
c = 0; // count binary
while((value >= *dp) && (*dp!=0)) ++c, value -= *dp;
*str++ = c+48; //convert to ASCII
}
while(*dp++ >1);
return n;
}

Another common application is in waveform generation such as Pulse width modulation (PWM) where you would like to be able to control the period T_{PWM} and duration T+{on} of the pulses. In this application the timer TA0 is used to divide the microcontroller clock frequency in order to obtain the desired period. E.g. if we required a period of 1kHz we would need to divide the 1MHz clock frequency by N=1000. This is readily done by configuring the count to count from 0 to 999, using the TA0CCR0 register, and then choosing count up mode where the counter counts from 0 to the value in TA0CCR0 and then resets itself back to zero to start counting again, as shown in the count axis of the figure below. We could then give an output (or trigger an interrupt) every time the counter resets which would give us an output at 1kHz.

PWM signal definitions

PWM signal definitions

We can also configure the counter to do something at a particular count using the counter compare registers TA0CCR1 and TA0CCR2. For example we could initialise the TA0CCR1 register to D=300 and set the TA0CCTL1 mode such that it sets the output every time the count restarts from zero and then resets the output when the counter reaches the value set in the TA0CCR1 register, as shown in the output axis of the above figure. In this case the output would remain on for 30% of the total count period. If we wish to change the PWM duty cycle, that is the ratio of the on time to period, then we simply write a new value between 0 and the value of TA0CCR0, 999 in this example. The code to implement this example is given below. Here the timer compare output is routed to P1.6 on the launchpad. The period of the pulse width modulated signal is determined by the input clock frequency f_{clk} the clock divider ratio R and the maximum count value N that is set in TA0CCR0 register. The PWM frequency is therefore given by

f_{PWM} = f_{clk}/R/N

PWM is typically used in micro controller applications to produce an analogue output waveform. This is achieved by choosing a PWM frequency that is at least 10 times larger than the frequency response of the system that you wish to control, the analogue output is then obtained by low pass filtering the output PWM signal. It many applications such as controlling the brightness of and LED or driving a motor the system itself has a low pass response so the lowpass filter is often omitted. For example, when controlling the brightness of an LED if you chose a PWM frequency greater than your eye response time (around 50Hz) then the LED will appear to be on continuously, only appearing dimmer as the duty cycle is reduced. When using PWM to produce an analogue output we are also interested in the output resolution, that is the minimum amplitude step size. This is determined by the max count value N as we can set TA0CCR1 to any integer value between 0 and N. Thus the resolution is simply

\Delta V = V_{cc}/N

Where V_{cc} is the logic high voltage.

Example code to create a 10% duty cycle PWM waveform on pin P1.6

#include "msp430G2553.h"

void main(void)
{
 WDTCTL = WDTPW + WDTHOLD;  // Stop WDT

 P1DIR |= BIT6;             // P1.6 to output
 P1SEL |= BIT6;             // P1.6 to TA0.1
 P1SEL2 &= ~BIT6;             // P1.6 to TA0.1

 TA0CCR0 = 1000-1; // Set maximum count value (PWM Period
 TA0CCTL1 = OUTMOD_7; // set output on counter reset, clear output on CCR1
 TA0CCR1 = 100; // initialise counter compare value
 TA0CTL = TASSEL_2 + MC_1; // Use the SMCLK to clock the counter and set to count up mode

 _BIS_SR(LPM0_bits); // Enter LPM0
}

Timer TA0 can be used to produce one PWM signals that can be routed to the output pins if the maximum count, and hence frequency, is set using TA1CCR0. If the maximum count is left at 2^16 then it can be used to produce two PWM outputs. Similarly timer TA1 can produce two or three PWM signals that can be routed to output pins. The PWM signals from each CCR register can be routed to the pins shown in the table below on the 20 pin DIL package by setting the bits corresponding to the pin number in the PxSEL register and clearing the bit in the PxSEL2 register. It is also necessary to set the pin direction as an output by setting the corresponding bit in the PxDIR register.

Timer output to pin mapping for the 20 pin DIL MSP430G2553

Timer output to pin mapping for the 20 pin DIL MSP430G2553

More information can also be found in this excellent post

Advertisements

2 thoughts on “Timers and PWM

  1. Is it possible to set up two pins as PWM outputs on the Launchpad? From what I have looked up there seems to be a CCR1 and a CCR2 but I haven’t been able to find the port associated with CCR2. I’m currently attempting to output 2 PWM waves on a MSP430G2553, if that helps.

    Thanks.

    • If you want to output two PWM signals then you should use timer TA1 (rather than TA0 as in the example above). TA1CCR1 is mapped to pins 9 and 10 and TA1CCR2 is mapped to pins 12 and 13. I have updated the post above to include this information.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s