Design Examples

Design Example: Christmas Flasher

It’s Christmas so you have decided to build a Christmas light flasher using your MSP430 Launchpad. You would like the flasher to have 4 flashing modes and an off state. the flashing modes are as follows:

  1. Red only
  2. Green only
  3. Both flash
  4. Alternate flash

The Flashing mode is chosen using a switch and the lights should flash at around 10Hz.

We can specify the system requirements as far as the user is concerned using a black box model.

System requirements

System requirements

We now need to design the system. This is best done by first developing a complete system diagram that shows the main blocks and components that will be required.

System diagram

System diagram

We then need to develop algorithms and code to implement each block. Then the really important part implement and test each block individually.

Debounce block

Here we make use of the edge detection interrupts to detect the press and then release of the switch. When a falling edge is detected, due to the switch press, we disable the falling edge detect interrupt and enable the watchdog timer (WDT) so that we can re-enable the interrupt 32ms later after any voltage variations arising from switch bouncing have finished. When the WDT interrupt occurs we then re-enable the edge detect interrupt, this time to detect the rising edge. When the switch is released the corresponding rising edge triggers the interrupt and the interrupt service routine sets a flag to indicate that the switch has been pressed and released. The rising edge interrupt is disabled and WDT is also re-enabled to avoid any voltage variations on the input that occur in the next 32ms and the WDT interrupt then re-enables the falling edge interrupt ready for the next button press.

/*Christmas Lights Switch Test Code */

#include "msp430g2553.h"

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

static char SwitchState = RELEASED;
static volatile char Mode = 0;

/* Function Prototypes */
void ConfigureSwitch(void);
void ConfigureLEDs(void);

/* Main function */
void main(void)
{
 WDTCTL = WDTPW + WDTHOLD; // Stop WDT
 ConfigureSwitch();
 ConfigureLEDs();

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

/* This function configures the button so it will trigger interrupts
 * when pressed. Those interrupts will be handled by PORT1_ISR() */
void ConfigureSwitch(void)
{
 P1DIR &= ~BUTTON; // Set button pin as an input pin
 P1OUT |= BUTTON; // Set pull up resistor on for button
 P1REN |= BUTTON; // Enable pull up resistor for button to keep pin high until pressed
 P1IES |= BUTTON; // Enable Interrupt to trigger on the falling edge (high (unpressed) to low (pressed) transition)
 P1IFG &= ~BUTTON; // Clear the interrupt flag for the button
 P1IE |= BUTTON; // Enable interrupts on port 1 for the button
}

/* Port 1 interrupt to service the button press */
#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR(void)
{
 P1IFG &= ~BUTTON; // Clear the interrupt flag for the button
 P1IE &= ~BUTTON; // Disable Button interrupt
 WDTCTL = WDT_MDLY_32; // Start and set watchdog timer (WDT) to trigger every 32ms
 IFG1 &= ~WDTIFG; // Clear the interrupt flag for the WDT
 IE1 |= WDTIE; // enable WDT interrupt

if (SwitchState == RELEASED)
 {
 SwitchState = PRESSED;
 P1IES &= ~BIT3; // Clear edge detect to rising edge
 P1OUT |= BIT0; // Set the LEDs P1.0 to indicate press
 P1OUT &= ~BIT6; // Clear the LEDs P1.6 to indicate press
 }
 else
 {
 SwitchState = RELEASED;
 P1IES |= BUTTON; // Set edge detect to falling edge
 P1OUT |= BIT6; // Set the LEDs P1.6 to indicate release
 P1OUT &= ~BIT0; // Clear the LEDs P1.6 to indicate release
 }
}

// WDT Interrupt Service Routine used to de-bounce button press
#pragma vector=WDT_VECTOR
__interrupt void WDT_ISR(void)
{
 IE1 &= ~WDTIE; // disable Watchdog timer (WDT) interrupt
 IFG1 &= ~WDTIFG; // clear WDT interrupt flag
 WDTCTL = WDTPW + WDTHOLD; // put WDT back in hold state
 P1IE |= BUTTON; // Reenable interrupts for the button
}

void ConfigureLEDs(void){
 // Configure LEDs
 P1DIR |= (BIT0+BIT6); // P1.2 to output
 P1OUT |= (BIT0+BIT6); // Set the LEDs P1.0 and P1.6 to indicate correct configuration
}

Set Mode

The switch press flag is then used to control the set mode block. The mode can be seen as a state of the system. In this system we simply want the mode to move to the next state every time the switch is pressed. This can be represented as a simple state diagram.

Mode state diagram

Mode state diagram

Because this state diagram is simply a modulo 4 counter it can be implemented in the following way

if((++mode) > 4) mode = 0;

This code could then be added to the switch interrupt rather than simply turning on an LED (lines 60-61, in the switch test code) to update the mode each time the switch is pressed and released.  We could also use the two LEDs to test this operation, however, with two LEDs we can only represent four states, so we either need to reduce the number of states (just for testing) by setting the condition in the if statement above to 3, or get another LED so that we can represent 8 possible states.

We then simply output the binary value of the mode to the LEDs. We then need to map the bits in the mode variable to the pins that the LEDs are attached to. Assuming the Green LED  (P1.6) on the launch pad represents the least significant bit (LSB) and Red LED on the launch pad represents the most significant bit (MSB) then the mapping is mode(BIT0) -> P1OUT(BIT6) and mode(BIT1) -> P1OUT(BIT0)

P1OUT &= ~(BIT0+BIT6);           // Clear both LEDs
P1OUT |= ((Mode & BIT0) << 6);   // Map LSB Mode(BIT0) to P1.6
P1OUT |= ((Mode & BIT1) >> 1);   // Map MSB Mode(BIT1) to P1.0

Timer

We are going to use a timer to generate an interrupt to control the flashing of the lights. There are two aspects to using a timer to create a regularly timed interrupt. Firstly we need to initialise the timer. The following UML diagram shows the steps that are required to initialise the timer.

Timer Configuration UML Diagram

Timer Configuration UML Diagram

For this application we are going to set the CPU clock on the M430G2553 to use the internal digitally controlled oscillator (DCO) operating at 1MHz as follows.

/* Use Factory stored presets to calibrate the internal oscillator */
 BCSCTL1 = CALBC1_1MHZ; // Set DCO Clock to 1MHz
 DCOCTL = CALDCO_1MHZ; // Set DCO Clock to 1MHz

We then need to determine the clock divider ratio and the maximum count in order to get a timer interrupt every 0.1s (or 10Hz). The maximum count (and hence divide ratio) on the 16 bit timer is 65535 which is insufficient to reduce the 1MHz CPU clock to 10Hz. So we will need to set the clock divider to reduce the rate of the clock that is used to clock the timer. If we set the clock divider to /8 then the timer will be clocked at 125kHz. To obtain an interrupt rate of 10Hz we need to use the timer to further divide this by 12500. This is achieved by setting the maximum count to 12500-1, where the minus 1 is required because the counter starts counting from 0. The steps shown in the timer configuration UML diagram are then implemented in the function below. This function is then called in the main function to configure the timer. Remember also to add the function prototype for this function at the top of the code.

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

Then we need to define the function that is executed when the timer interrupt occurs. Note the format of the call (#pragma etc…) for this function is defined in the header file. To test that the interrupt is working as expected we simply toggle the state of the LEDs.

// TimerA interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A (void)
{
 P1OUT |= (BIT0+BIT6); // Toggle the LEDs P1.0 and P1.6
}

Control Outputs

Finally we need to change the state of the lights each time the timer interrupt occurs. The operation depends on the current operating state which is stored in the Mode variable. To execute the correct operation we will simply use a switch statement, inside the timer interrupt function replacing the toggle LED test code. The switch statement is controlled by the Mode variable to select the correct operation.

switch(Mode){
 case 0:
 {
 P1OUT &= ~(BIT0 + BIT6); // Off
 }
 break;
 case 1:
 {
 P1OUT &= ~(BIT6); // Green Off
 P1OUT ^= BIT0; // Toggle Red
 }
 break;
 case 2:
 {
 P1OUT &= ~(BIT0); // Red Off
 P1OUT ^= BIT6; // Toggle Green
 }
 break;
 case 3:
 {
 if (P1OUT & BIT0) P1OUT &= ~(BIT0+BIT6); // If Red LED on then turn both off
 else P1OUT |= (BIT0+BIT6); // else turn both On
 }
 break;
 case 4:
 {
 if (P1OUT & BIT0){
 P1OUT &= ~BIT0; // If Red LED on then turn red off
 P1OUT |= BIT6; // and turn green on
 }
 else {
 P1OUT &= ~BIT6; // else turn green off
 P1OUT |= BIT0; // and turn red on
 }
 }
 break;
 default:
 {
 P1OUT &= ~(BIT0 + BIT6); // Off
 }
 break;
 }

Complete code listing

/*Christmas Lights Switch Test Code */

#include "msp430g2553.h"

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

static char SwitchState = RELEASED;
static volatile char Mode = 0;

/* Function Prototypes */
void ConfigureSwitch(void);
void ConfigureLEDs(void);
void ConfigureTimerA(void);

/* 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;
 ConfigureSwitch();
 ConfigureLEDs();
 ConfigureTimerA();

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

/* This function configures the button so it will trigger interrupts
 * when pressed. Those interrupts will be handled by PORT1_ISR() */
void ConfigureSwitch(void)
{
 P1DIR &= ~BUTTON; // Set button pin as an input pin
 P1OUT |= BUTTON; // Set pull up resistor on for button
 P1REN |= BUTTON; // Enable pull up resistor for button to keep pin high until pressed
 P1IES |= BUTTON; // Enable Interrupt to trigger on the falling edge (high (unpressed) to low (pressed) transition)
 P1IFG &= ~BUTTON; // Clear the interrupt flag for the button
 P1IE |= BUTTON; // Enable interrupts on port 1 for the button
}

/* Port 1 interrupt to service the button press */
#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR(void)
{
 P1IFG &= ~BUTTON; // Clear the interrupt flag for the button
 P1IE &= ~BUTTON; // Disable Button interrupt
 WDTCTL = WDT_MDLY_32; // Start and set watchdog timer (WDT) to trigger every 32ms
 IFG1 &= ~WDTIFG; // Clear the interrupt flag for the WDT
 IE1 |= WDTIE; // enable WDT interrupt

if (SwitchState == RELEASED)
 {
 SwitchState = PRESSED;
 P1IES &= ~BIT3; // Clear edge detect to rising edge
 }
 else
 {
 SwitchState = RELEASED;
 P1IES |= BUTTON; // Set edge detect to falling edge
 if (++Mode > 4) Mode = 0;
 }
}

// WDT Interrupt Service Routine used to de-bounce button press
#pragma vector=WDT_VECTOR
__interrupt void WDT_ISR(void)
{
 IE1 &= ~WDTIE; // disable Watchdog timer (WDT) interrupt
 IFG1 &= ~WDTIFG; // clear WDT interrupt flag
 WDTCTL = WDTPW + WDTHOLD; // put WDT back in hold state
 P1IE |= BUTTON; // Reenable interrupts for the button
}

// TimerA interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A (void)
{
 switch(Mode){
 case 0:
 {
 P1OUT &= ~(BIT0 + BIT6); // Off
 }
 break;
 case 1:
 {
 P1OUT &= ~(BIT6); // Green Off
 P1OUT ^= BIT0; // Toggle Red
 }
 break;
 case 2:
 {
 P1OUT &= ~(BIT0); // Red Off
 P1OUT ^= BIT6; // Toggle Green
 }
 break;
 case 3:
 {
 if (P1OUT & BIT0) P1OUT &= ~(BIT0+BIT6); // If Red LED on then turn both off
 else P1OUT |= (BIT0+BIT6); // else turn both On
 }
 break;
 case 4:
 {
 if (P1OUT & BIT0){
 P1OUT &= ~BIT0; // If Red LED on then turn red off
 P1OUT |= BIT6; // and turn green on
 }
 else {
 P1OUT &= ~BIT6; // else turn green off
 P1OUT |= BIT0; // and turn red on
 }
 }
 break;
 default:
 {
 P1OUT &= ~(BIT0 + BIT6); // Off
 }
 break;
 }
}

void ConfigureLEDs(void){
 // Configure LEDs
 P1DIR |= (BIT0+BIT6); // P1.2 to output
 P1OUT |= (BIT0+BIT6); // Set the LEDs P1.0 and P1.6 to indicate correct configuration
}

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/10000 = 10Hz)
}

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