Using the Switches

The MSP430 launchpad comes with two switches labelled S2 (P1.3) and S1 (RESET). S2 is connected to the General Purpose Input Output (GPIO) pin P1.3 and is easy to use as an input. The switch S1 is connected to the RST (reset) pin on the microcontroller and under standard operation will cause the micro controller to reset the program counter to zero, however, this switch can also be configured as a general purpose input.

Here we will look at basic pin configuration and usage in Assembly and then look at some more advanced and typical configurations that use interrupts to debounce and poll the state of the pins.

General purpose input pins

Both switches are directly connected between the micro controller pin and ground (0V). Thus when the switch is pressed it will connect the pin to ground and the state of the pin will be read as 0 by the micro controller. When the switch is released the pin is open, this is problematic as an open pin can in principle be at any voltage. This is know as a floating input. To avoid this problem the MSP430’s come with internal software selectable pull-up or pull-down resistors which are used to pull the input either up to +Vcc or down to ground (0V) when the switch is not pressed (open). Most microcontrollers provide either pull-ups, -downs, or both. A simplified circuit diagram for the switch S2 on the MSP430 launchpad when the pull-up resistor is selected is shown below.

Switch 2 connection with pull-up enabled

Switch 2 connection with pull-up enabled

Because Switch 2 is from the pin to ground it is necessary to use the pull-up configuration so that when the switch is not pressed (open) the pin is pulled up to Vcc through the pull-up resistor. When the switch is pressed (closed) then the pin will be at 0V and a voltage drop of Vcc will occur across the pull-up resistor.

Configuration in Assembly

To use a GPIO pin as on input on the MSP430 it is necessary to:

1) Select GPIO operation by clearing the port 1 select register (P1SEL) Some pins can be used to for other functions e.g. analogue input

 mov.b #0b00000000,&P1SEL  ; select GPIO

2) Set the pin direction to input by clearing (set to 0) the bits corresponding to the input pins in the pin direction (P1DIR) register.

 mov.b #0b01000001,&P1DIR  ; set pin0 and pin6 as output all others as input.

3) Enable the pull -up (-down) resistor by setting (set to 1) bits corresponding to the input pins in the pin resistor enable (P1REN) register.

 mov.b #0b10111110,&P1REN  ; Enable pull-up or pulldown resistor on all inputs

4) Set to pull-up by setting the pin output register (P1OUT) to 1 (pull down is selected by clearing this register)

 mov.b #0b10111110,&P1OUT  ; Set Pull-ups on all input pins.

The current state of the pin can be determined by reading the P1IN register

 mov.b &P1IN,R14  ; Read the value on P1IN into general purpose register 14

Often we are only interested in the value of one of the bits in this register so we mask this off using an AND operation an then test the result using a branch instruction.

 and.b #0b00001000,R14  ;Mask off bit3 to read state of switch 2
 jz pressed  ; if switch pressed jump to pressed.

The following example uses this approach to switch on the LED connected to P1.0 when S2 is pressed and the LED connected to P1.6 when the switch is unpressed.

; Simple Switch example assembly code compatible with the MSP-gcc compiler

#include "msp430g2553.h";

;--------------------------------------------------------------------

; ---- gcc doesn't know about PC,SP,SR,CG1,CG2 ----
#define PC r0
#define SP r1
#define SR r2
#define CG1 r2
#define CG2 r3
;--------------------------------------------------------------------

.text

;--------------------------------------------------------------------

RESET_ISR:

;; disable watchdog and set stack to highest RAM addr

 mov.w   #WDTPW+WDTHOLD,&WDTCTL
 mov.w   #__stack,SP         ; gcc ldscripts compute stack based on mmcu

;; initialize gpio
 mov.b #0b00000000,&P1SEL    ; select GPIO
 mov.b #0b01000001,&P1DIR    ; Set bit0 and bit6 in port1 as output
 mov.b #0b11111111,&P1OUT    ; Set Pullup
 mov.b #0b10111110,&P1REN    ; Enable pullup or pulldown resistor

start: and.b #0b00001000,&P1IN  ;Mask off bit3
       jz press
       bic.b #0b00000001,&P1OUT ; clear bit0
       bis.b #0b01000000,&P1OUT ; set bit6
       jmp start
press: bic.b #0b01000000,&P1OUT ; clear bit6
       bis.b #0b00000001,&P1OUT ; set bit0
       jmp start          ; Jump to start

;--------------------------------------------------------------------
; UNEXPECTED_ISR - default handler for unhandled interrupt
;--------------------------------------------------------------------
UNEXPECTED_ISR:
 reti                    ; cycles: 5
;--------------------------------------------------------------------
; Interrupt Vectors - see the datasheet for your chip
;  *msp430g2553 vectors described below
;--------------------------------------------------------------------

.section ".vectors", "ax", @progbits
.word UNEXPECTED_ISR    ;0xffe0 slot  0  0
.word UNEXPECTED_ISR    ;0xffe2 slot  1  2
.word UNEXPECTED_ISR    ;0xffe4 slot  2  4 (PORT1_VECTOR)
.word UNEXPECTED_ISR    ;0xffe6 slot  3  6 (PORT2_VECTOR)
.word UNEXPECTED_ISR    ;0xffe8 slot  4  8
.word UNEXPECTED_ISR    ;0xffea slot  5  A (ADC10_VECTOR)
.word UNEXPECTED_ISR    ;0xffec slot  6  C (USCIAB0TX_VECTOR)
.word UNEXPECTED_ISR    ;0xffee slot  7  E (USCIAB0RX_VECTOR)
.word UNEXPECTED_ISR    ;0xfff0 slot  8 10 (TIMER0_A1_VECTOR)
.word UNEXPECTED_ISR    ;0xfff2 slot  9 12 (TIMER0_A0_VECTOR)
.word UNEXPECTED_ISR    ;0xfff4 slot 10 14 (WDT_VECTOR)
.word UNEXPECTED_ISR    ;0xfff6 slot 11 16 (COMPARATORA_VECTOR)
.word UNEXPECTED_ISR    ;0xfff8 slot 12 18 (TIMER1_A1_VECTOR)
.word UNEXPECTED_ISR    ;0xfffa slot 13 1a (TIMER1_A0_VECTOR)
.word UNEXPECTED_ISR    ;0xfffc slot 14 1c (NMI_VECTOR)
.word RESET_ISR         ;0xfffe slot 15 1e (RESET_VECTOR)
.end

 

Debouncing using interrupts

This example shows you how to poll the state of switch (S2) that is attached to P1.3 on the MSP430 launchpad using the rising and falling edge interrupts that are available on the MSP430. It also implements a software debounce using the watchdog timer to ensure that the fluctuations in the electrical signal as a result of the mechanical switching are not registered as actual button presses. More information on the need to debounce can be found here, however, the software solution that is used here is different because the MSP430 has an edge detect interrupt which means we simply detect the first edge, disable the edge detect interrupt, such that further oscillations are ignored and then start the watchdog timer to provide a delay that is larger than the period of the “bounce”. Once the bounce is over the watchdog timer interrupt then re-enables the edge detect interrupt ready for the next button press or release. In this example the interrupt is first set to trigger on the falling edge. If the falling edge is detected then the interrupt is set to detect the rising edge. In this way both the pressing and releasing of the switch can be detected.

The basic algorithm is as follows

1. Enable port1 interrupt to trigger on the falling edge.
2. When port1 falling edge interrupt occurs:

a) Disable port 1 interrupt (P1IE) (so that further edges caused by switch bouncing are ignored).
b) Let you main code know the switch was pressed.
c) Start the watchdog timer to create a 32ms delay and enable the watchdog timer interrupt. Most switches are finished bouncing and are stable after 32ms, furthermore the human response time is of this order so you won’t notice the delay when pressing the button.

3. When the watchdog timer interrupt occurs:

a) Re-enable the port 1 (P1IE) to detect the rising edge (or falling edge if to only want to detect presses then go back at step 2).

4. When the port 1 interrupt occurs:

a) Disable port 1 interrupt (P1IE) (so that further edges caused by with bouncing are ignored).
b) Let your main code know that the switch has been released.
c) Start the watchdog timer to create a 32ms delay and enable the watchdog timer interrupt.

5. When the watchdog timer interrupt occurs:

a) Goto step 1.

/*This code demonstrates the use of interrupts to detect the press of switch (S2) on the
 * MSP430 Launchpad. It also uses the watchdog timer to debounce the switch. The switch
 * interrupt is intially set to trigger on the falling edge and when detected the red LED1
 * is switched on and the interrupt is set to detect the rising edge. This will cause another
 * interrupt when the button is released which is indicated by turning off the red LED1 and
 * turning on the green LED2.
 * The debounce time is set to 32ms which means that if you press and release the button at
 * a rate faster than this it will be ignored. Practically you are unlikely to be able to do this.
 *
 * Benn Thomsen 2013
*/

#include "msp430.h";

#define BUTTON BIT3

unsigned char Mode = 0;

/* Function Prototype */
void InitializeButton(void);

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

 P1DIR |= (BIT0+BIT6);         		// P1.2 to output
 P1OUT |= (BIT0+BIT6); 				// Set the LEDs P1.0 and P1.6 to on

 InitializeButton();

 _BIS_SR(LPM0_bits+GIE);       	// Enter LPM0 with interrupts enabled
}

/* This function configures the button so it will trigger interrupts
 * when pressed.  Those interrupts will be handled by PORT1_ISR() */
void InitializeButton(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 (Mode == 0)
  {
	P1OUT |= (BIT0); 				// Set the LED P1.0 to on
	P1OUT &= ~(BIT6); 				// Set the LED P1.6 to off
    Mode  = 1;
    P1IES &= ~BIT3;					// Clear edge detect to rising edge
  }
  else
  {
		P1OUT |= (BIT6); 			// Set the LED P1.6 to on
		P1OUT &= ~(BIT0); 			// Set the LED P1.0 to off
	    Mode  = 0;
	    P1IES |= BUTTON;			// Set edge detect to rising edge
  }
}

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

Using both switches for input

This more advanced example shows how to poll and debounce the switches (S1 and S2) that are available on the MSP430 Launchpad. The press of a switch is indicated by turning on an LED (S1 turns on the LED2 and S2 turns on LED1). When the switch is released this is indicated by turning off the corresponding LED. In this example the detection of a long press, i.e. when the button is pressed and held down for more than 1.5  seconds is also carried out. If a long press is detected on S1 then LED1 is switched on and for S2, LED2 is switched on. Releasing the switch will turn both LEDs off.

Note in order to use the switch S1 that is connected to the RESET pin it is necessary to cycle the power to the launchpad i.e. disconnect and reconnect the USB cable after you have flashed the program to the microcontroller.

/* This code demonstrates the use of both switches, labelled S1 (RESET) and S2 (P1.3) on the MSP430 Launchpad.
 * The code implements a software debounce using the WDT and also indicates a long press i.e. switch pressed
 * for more than 1.5 seconds
 * Switch S1 uses the RST/NMI pin as an input
 * Switch S2 uses the P1.3 general purpose I/O pin
 * The P1.0 Red LED1 is used to indicate when S2 (P1.3) is pressed (Active Pressed).
 * The P1.6 Green LED2 is used to indicate when S1 (RESET) is pressed (Active Pressed).
 * These LEDs are turned off when the switches are released.
 * The Watchdog Timer (WDT) is used both to debounce the switches and to time the length of the press. The debounce
 * is achieved by using the delay provided by the WDT to re-enable the Switch interrupts only after the bouncing has
 * finished. The WDT interrupt service routine also increments separate counters (one for each switch) when the
 * switches are pressed. When the count exceeds a certain time the P1.0 LED (for Switch 1) and the P1.6 LED (for
 * Switch 2) are turned on to indicate a long press.
 * In order to run the code using the RESET pin as a switch input you need to cycle the power on the LaunchPad.
 */

#include "msp430g2553.h"
#define FLIP_HOLD (0x3300 | WDTHOLD) // flip HOLD while preserving other bits

#define S1 0x0001
#define S2 0x0002

unsigned char PressCountS1 = 0;
unsigned char PressCountS2 = 0;
unsigned char Pressed = 0;
unsigned char PressRelease = 0;

void InitialiseSwitch2(void);

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

P1DIR |= (BIT0|BIT6); // Set the LEDs on P1.0, P1.1, P1.2 and P1.6 as outputs
 P1OUT = 0;
 P1OUT |= (BIT0|BIT6); // Turn on P1.0 and P1.6 LEDs to indicate initial state

InitialiseSwitch2(); // Initialise Switch 2 which is attached to P1.3

// The Watchdog Timer (WDT) will be used to debounce s1 and s2
 WDTCTL = WDTPW | WDTHOLD | WDTNMIES | WDTNMI; //WDT password + Stop WDT + detect RST button falling edge + set RST/NMI pin to NMI
 IFG1 &= ~(WDTIFG | NMIIFG); // Clear the WDT and NMI interrupt flags
 IE1 |= WDTIE | NMIIE; // Enable the WDT and NMI interrupts
 // The CPU is free to do other tasks, or go to sleep
 __bis_SR_register(LPM0_bits | GIE);
}

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

// isr to detect make/break of s1 at the nRST/NMI pin
// Note the occurrence of an NMI interrupt automatically disables the NMI interrupt enable.
#pragma vector = NMI_VECTOR
__interrupt void nmi_isr(void)
{
 if (IFG1 & NMIIFG) // Check if NMI interrupt was caused by nRST/NMI pin
 {
 IFG1 &= ~NMIIFG; // clear NMI interrupt flag
 if (WDTCTL & WDTNMIES) // falling edge detected
 {
 P1OUT |= BIT6; // Turn on P1.0 red LED to indicate switch 1 is pressed
 Pressed |= S1; // Set Switch 2 Pressed flag
 PressCountS1 = 0; // Reset Switch 2 long press count
 WDTCTL = WDT_MDLY_32 | WDTNMI; // WDT 32ms delay + set RST/NMI pin to NMI
 // Note: WDT_MDLY_32 = WDTPW | WDTTMSEL | WDTCNTCL // WDT password + Interval mode + clear count
 // Note: this will also set the NMI interrupt to trigger on the rising edge

}
 else // rising edge detected
 {
 P1OUT &= ~(BIT6+BIT0); // Turn off P1.6 and P1.0 LEDs
 Pressed &= ~S1; // Reset Switch 1 Pressed flag
 PressRelease |= S1; // Set Press and Released flag
 WDTCTL = WDT_MDLY_32 | WDTNMIES | WDTNMI; // WDT 32ms delay + falling edge + set RST/NMI pin to NMI
 }
 } // Note that NMIIE is now cleared; the wdt_isr will set NMIIE 32ms later
 else {/* add code here to handle other kinds of NMI, if any */}
}

#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR(void)
{
 if (P1IFG & BIT3)
 {
 P1IE &= ~BIT3; // Disable Button interrupt to avoid bounces
 P1IFG &= ~BIT3; // Clear the interrupt flag for the button
 if (P1IES & BIT3)
 { // Falling edge detected
 P1OUT |= BIT0; // Turn on P1.0 red LED to indicate switch 2 is pressed
 Pressed |= S2; // Set Switch 2 Pressed flag
 PressCountS2 = 0; // Reset Switch 2 long press count
 }
 else
 { // Rising edge detected
 P1OUT &= ~(BIT0+BIT6); // Turn off P1.0 and P1.6 LEDs
 Pressed &= ~S2; // Reset Switch 2 Pressed flag
 PressRelease |= S2; // Set Press and Released flag
 }
 P1IES ^= BIT3; // Toggle edge detect
 IFG1 &= ~WDTIFG; // Clear the interrupt flag for the WDT
 WDTCTL = WDT_MDLY_32 | (WDTCTL & 0x007F); // Restart the WDT with the same NMI status as set by the NMI interrupt
 }
 else {/* add code here to handle other PORT1 interrupts, if any */}
}

// WDT is used to debounce s1 and s2 by delaying the re-enable of the NMIIE and P1IE interrupts
// and to time the length of the press
#pragma vector = WDT_VECTOR
__interrupt void wdt_isr(void)
{
 if (Pressed & S1) // Check if switch 1 is pressed
 {
 if (++PressCountS1 == 47 ) // Long press duration 47*32ms = 1.5s
 {
 P1OUT |= BIT0; // Turn on the P1.1 LED to indicate long press
 }
 }

if (Pressed & S2) // Check if switch 2 is pressed
 {
 if (++PressCountS2 == 47 ) // Long press duration 47*32ms = 1.5s
 {
 P1OUT |= BIT6; // Turn on the P1.2 LED to indicate long press
 }
 }

IFG1 &= ~NMIIFG; // Clear the NMI interrupt flag (in case it has been set by bouncing)
 P1IFG &= ~BIT3; // Clear the button interrupt flag (in case it has been set by bouncing)
 IE1 |= NMIIE; // Re-enable the NMI interrupt to detect the next edge
 P1IE |= BIT3; // Re-enable interrupt for the button on P1.3
}
Advertisements

14 thoughts on “Using the Switches

  1. Hi Benn!
    Thanks a ton for the fabulous examples!! Immensely useful!!

    I have one little question. If I wanted to print to the UART when S1 was pressed, and when S2 was pressed, how would I do it?
    I assume the port write would be slow, so the WDT trick wouldnt work anymore?

    • I would simply set a flag when each switch was pressed. This flag is then checked by your main program and if set a write to the UART would be performed. In this way the writing to the UART is independent of the switch interrupts.

      The WDT is simply used to control the checking of the switch state in order to avoid switch bouncing.

  2. Pingback: Using Energia to code in c » Hallo, here is Pane!

  3. Hey Benn,

    Is there a possibility of maintaining the P1IE without a Watchdog setup?
    Having an infinite loop at the end of main thereby controlling the PC. Thereby P1 interrupt is checked after the delay. I am actually trying this but it doesn’t work:
    while(1)
    {
    _BIS_SR(LPM0_bits+GIE);
    __delay_cycles(6000000);
    }

    • The watchdog timer is simply used to create the delay required to debounce the switch. The basic algorithm is as follows

      1. Enable port1 interrupt to trigger on the falling edge.
      2. When port1 falling edge interrupt occurs:
      a) Disable port 1 interrupt (P1IE) (so that further edges caused by switch bouncing are ignored).
      b) Let you main code know the switch was pressed.
      c) Start the watchdog timer to create a 32ms delay and enable the watchdog timer interrupt. Most switches are finished bouncing and are stable after 32ms, furthermore the human response time is of this order so you won’t notice the delay when pressing the button.
      3. When the watchdog timer interrupt occurs:
      a) Re-enable the port 1 (P1IE) to detect the rising edge (or falling edge if to only want to detect presses then go back at step 2).
      4. When the port 1 interrupt occurs:
      a) Disable port 1 interrupt (P1IE) (so that further edges caused by with bouncing are ignored).
      b) Let your main code know that the switch has been released.
      c) Start the watchdog timer to create a 32ms delay and enable the watchdog timer interrupt.
      5. When the watchdog timer interrupt occurs:
      a) Goto step 1.

      You could use __delay_cycles to provide the delay to debounce the switches in which case the P1 ISR would simply disable the interrupt and signal a switch press. your main code would then service the switch press and then used __delay_cycles to provide the debounce delay after which you would re-enable the port 1 interrupt (P1IE).
      However I would not recommend this approach. __delay_cycles(6000000) does exactly what it says on the tin it makes the microprocessor do 6000000 cycles of absolutely nothing which means that for 6000000 cycles it cannot do any thing else. Using the watchdog timer means that the CPU can do other useful stuff or be put to sleep to minimise power consumption. If you do not want to use the watchdog timer for this because it is doing other things then you would be better off to use one of the other timers rather than __delay_cycles.

      __delay_cycles is a lazy approach to delay and should only be used to generate very short delays i.e. <100 cycles. The delay is also indeterminate if interrupts are used.

  4. Hi, is there a particular reason why the unsigned char variables aren’t declared using the ‘volatile’ keyword?
    Thanks,
    Alex

    • Have a look at this page which explains what volatile variables are. In this case the variables are only changed inside on of the interrupt service routines, and so do not need to be volatile to ensure that changes that occur elsewhere in the code don’t effect the operation.

  5. Benn,

    Nice code examples, and as you note, using an interrupt-driven approach allows the app to respond efficiently to other interrupts (also conserves power, since the cpu is not used outside of the ISRs).
    My only niggling criticism of the first example is that it appears to blindly reset the edge-select bit without testing the current state of the port (is the button still down? up?).

    Regardless, a very clear and well constructed code example. Thank you.

    I found your blog while searching on button handling and was very pleased to the the assembler code. I’ve set the task of developing a literacy with assembler on the ‘430 in the belief it will enhance my understanding of the hardware, timing and handling of various events.

    bob

  6. Its good, Mr Benn how do we measure very long press, say more than 30 Seconds using if (Pressed & S1) // Check if switch 1 is pressed
    {
    if (++PressCountS1 == 47 ) // Long press duration 47*32ms = 1.5s. I have tried with this I am able to get around 8 Seconds only. Can you tell how to get to measure long press more than 30 Seconds or so.

    Summy

    • PressCountS1 is a char type variable i.e. 8 bits thus the maximum delay that you can measure is 255*32ms = 8s. If you change the variable type of PressCountS1 to a 16bit integer then you can measure longer delays.

      int PressCountS1;

  7. Thank you for your nice example code, one thing I would like to suggest is that in the example code for one switch debouncing, you forgot to clear the button interrupt flag before reenabling interrupts for the switch in the WDT ISR

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