ESE101: This Time We Really Turn on the LED with a Button, I Promise

Quick recap: We’re trying to make the MSP430 LaunchPad button on pin P1.1 turn on and off a light.

Three weeks ago we:

  • learned how to configure GPIO pin P1.1 as an input
  • tried reading the voltage on P1.1 when the  button was pushed and released, but that didn’t work

Two weeks ago we:

  • read the schematic and figured out why reading the voltage on P1.1 didn’t work
  • learned that we needed a pull-up resistor on P1.1 to get a different voltage reading on P1.1 when the button is pushed and not pushed. The pull-up resistor looked something like this:

This week we’ll learn how to use the MSP430’s internal pull-up resistors to read the button state and finally turn on and off the LED on command!

An internal pull-up or pull-down resistor is like the resistor drawing above, except that the resistor is inside the chip - hence the name “internal pull-up/pull-down.” It looks like this:

The MSP430 uses three GPIO configuration registers to control pull-up or pull-down resistors. TI sums it up in Table 12-1 of the MSP430 User’s Guide:

Remember that ‘x’ means “don’t care”. ‘x’ is used in the register names to show that PxDIR could be PADIR, PBDIR, or any of the GPIO port registers - it applies to all of the GPIO port’s registers. The ‘x’ is also in the table entries: if PxDIR==0 and PxREN==0, then PxOUT can be either 0 or 1, it doesn’t matter, the pin is configured as an input and the value of PxOUT is a don’t care.

Let’s look at that table starting from the last line:

  • If PxDIR == 1, then the pin is an output, regardless of PxREN or PxOUT.
  • If PxDIR == 0, then the pin is an input and PxREN is used to configure a resistor (PxREN stands for Port x Resistor ENable):
    • If PxREN == 0 then no resistor is used - the pin is connected directly to the outside of the chip
    • If PxREN == 1 then a resistor is used:
      • If PxOUT == 0 then a pull-down resistor is used
      • If PxOUT == 1 then a pull-up resistor is used

So if we want P1.1 configured as an input with a pull-up resistor, we need to set bit 1 of PADIR==0, PAREN==1, and PAOUT==1.

Let’s add the pull-up resistor configuration to our original code and see how it works. The new lines are marked with ; *NEW*:

;-------------------------------------------------------------------------------
; Main loop here
;-------------------------------------------------------------------------------

       	; Set GPIO P1.0 to be an output (PADIR bit 0 == 1)
       	BIS.B  	#1, &PADIR_L
       	; Set GPIO P1.1 to be an input (PADIR bit 1 == 0)
       	BIC.B  	#2, &PADIR_L ; *NEW*
       	; Set GPIO P1.1 to be pulled up or down (PAREN bit 1 == 1)
       	BIS.B  	#2, &PAREN_L ; *NEW*
       	; Set GPIO P1.1 to use a pull-up resistor (PAOUT bit 1 == 1)
       	BIS.B  	#2, &PAOUT_L ; *NEW*

MainLoop:  ; infinite loop that does nothing

       	JMP MainLoop
        NOP

As we did in the first post about GPIO inputs, let’s download the code onto the MSP430, single step into the infinite MainLoop, and look at the PAIN values in the Registers view when the P1.1 button is not being pushed. We expect the input voltage on P1.1 to be high since P1.1 has a pull-up resistor:

So far, so good! PAIN bit 1 is 1, meaning that P1.1’s input voltage is being seen as high.

Now push and hold the button and single step through the main loop again: with the button pressed we have connected P1.1 to ground, and so we expect to see a low voltage on PAIN bit 1. You have to press the button while single stepping because the CCS debugger only updates the Registers view when code is running.

Success! We can now tell when the button is pushed by reading the PAIN bit 1. If the button is pushed, PAIN bit 1 == 1. If the button is not pushed, PAIN bit 1 == 0.

If you’re still a bit confused over why P1.1 is 0 when the button is pushed and 1 when the button is released, please re-read the schematic post. If it’s still not clear, please leave a comment and I’ll try to help! EDIT: I had the 0/1 logic backwards here earlier: it's correct now.

Our original goal was to turn on/off the LED on GPIO P1.0 when the button was pushed/not-pushed. Now that we know how to tell if the button is pushed, let’s hook up the LED. Here’s the code I came up with. I started with the previous example and added all the code after MainLoop:

;-------------------------------------------------------------------------------
; Main loop here
;-------------------------------------------------------------------------------

       	; Set GPIO P1.0 to be an output (DIR bit == 1)
       	BIS.B  	#1, &PADIR_L
       	; Set GPIO P1.1 to be an input (DIR bit == 0)
       	BIC.B  	#2, &PADIR_L
       	; Set GPIO P1.1 to be pulled up or down (PAREN bit 1 == 1)
       	BIS.B  	#2, &PAREN_L
       	; Set GPIO P1.1 as a pull-up resistor (PAOUT bit 1 == 1)
       	BIS.B  	#2, &PAOUT_L

MainLoop:
       	; Test if GPIO P1.1 input voltage is 0 or 1
       	BIT.B  #2, &PAIN_L

       	; If P1.1 == 0, button is pushed: turn on the LED on P1.0
       	JZ TurnOnLED

       	; Button is not pushed if we get here.
       	; P1.1 == 1 if we get here, so turn off the LED on P1.0
       	BIC.B #1, &PAOUT_L

       	JMP AfterLEDSet

TurnOnLED:
        ; We jumped here because the button is pressed.
        ; Turn on LED and fall through to AfterLEDSet
       	BIS.B #1, &PAOUT_L

AfterLEDSet:

       	JMP MainLoop
       	NOP

This is the most complicated assembly code we’ve seen, so let’s walk through it:

  • The BIT.B instruction checks PAIN_L bit 1.
    • If PAIN_L bit 1 == 1, then the SR register’s Zero bit is set to 0
    • If PAIN_L bit 1 == 0, then the SR register’s Zero bit is set to 1
  • “JZ TurnOnLED” checks the SR.Z bit.
    • If SR.Z == 1, then that means that PAIN_L bit 1 was 0, which means the button is pushed. This JZ instruction then jumps to the label TurnOnLED.
    • If SR.Z == 0, then PAIN_L bit 1 was 1, which means the button was not pushed. The JZ instruction does nothing and simply goes to the next instruction after the JZ.
  • The BIC.B instruction is run if the button is not pushed. It turns off the LED on P1.0 by setting PAOUT bit 0 to 0.
  • “JMP AfterLEDSet” jumps to near the end of MainLoop. It jumps over the code that turns on the LED since we don’t want to run it.
  • The BIS.B instruction only runs if the button was pushed and the “JZ TurnOnLED” instruction jumped to the TurnOnLED label. BIS.B turns on the LED on P1.0 by setting PAOUT bit 0 to 1.

If you load and run this code it should start with the light off. When you push the button the light should turn on, and when you release the button the light should turn off. Here’s what it looked like when I pressed the button:

This may seem like a simple example, but we’ve just built a fully functional embedded system! Every embedded system does three things:

  1. Takes inputs from the outside world
  2. Processes those inputs
  3. Produces some output based on that processing

Our button/light example does exactly that!