19 July 2010

Tutorial 07: Pushing Buttons

We've gotten to the point now where we can program the MSP430 and make it respond by flashing an LED or two.  While being able to change pins to outputs is useful, it's the combination of input and output that make the microcontroller so powerful.  The pushbutton is one of the simplest types of inputs, and will be very helpful for learning more about the peripherals.

There are all kinds of switches available on the market, and there are some differences in the ways they need to be attached to the MSP430.  For now we'll look at single-pole, single-throw (SPST) types, such as the momentary push-button.  The LaunchPad platform includes two of these types of buttons; one is connected to P1.3 (that's the button on the lower left of the board) and the other to the RST/SBWTDIO pin (pin 16 on the DIP).

Connecting Buttons
If you are following the tutorial using the LaunchPad, then open up a copy of the LaunchPad User's Guide and turn to page 15 to see a circuit schematic of the target section of the board.  You can see here that the two buttons are connected between their respective pins and ground.  Note also that the same pins have 47 kΩ resistors connecting them to Vcc.  To use pushbuttons in digital logic, they need to be able to provide a 1 in one state and a 0 in the other.  When the button is up, the resistor ties the pins to Vcc, pulling up the voltage to whatever level the board is powered at (in the case of the LaunchPad, it's 3.6V).  When you press the button, it closes the short to ground.  So unpressed is logic 1, and depressed is logic 0.

You can use the opposite convention as well, reversing the positions of the button and the resistor.  For obvious reasons, we call the first configuration a "pull-up resistor" and the second a "pull-down resistor".  Without these resistors, the input pin would be floating, and could take on any value between ground and Vcc, which gives inconsistent behavior for your design.  Whichever case makes more sense for your design, always remember to use the resistors to pull the open state up or down.  A larger resistor (such as the 47 kΩ used on the LaunchPad) keeps the current draw from the button low.  Ideally we only want to change the potential on the input pin and not use any power to do so.

You might recall that there are internal resistors on each of the GPIO pins for the MSP430 that can act as pull-up and pull-down resistors.  The resistance value quoted for these in the MSP430 datasheets is large enough that the current draw would not be too high, but as readers have pointed out it may not be the best practice to use these for your push-buttons, especially while prototyping and testing.

With the resistors in place, using buttons in an MSP430 design is quite simple.  Let's illustrate them by writing a program that flashes the red LED on P1.0 a few times, then flashes the green LED on P1.6 a few times after the button is pressed.  Pressing the button again should return to the red LED, and so on.  For this program, we'll need to configure the two LED pins as outputs and be sure the button pin (P1.3) is configured as an input.  We'll use the technique from the Blinky program to take care of the LED delay again, so we'll need a counter as well.  A second counter will keep track of the number of times the LED flashes.

The new thing needed is an ability to tell the MSP430 to check the state of the button periodically; we call this polling.  When the program reaches the point where it is waiting for the user to push the button, it should check and recheck the button until it sees that it has been pressed.  We can do this by using a logic operation to check the value on P1IN.  Since the button is attached to P1.3, bit 3 of P1IN tells us if the button is pressed (logic 0) or unpressed (logic 1).  The other bits of P1IN could, in principle, be anything, so how can we extract just the value of bit 3?  A simple way is to use the & operator-- since x & 0 = 0 and x & 1 = x, then P1IN & BIT3 (0b00001000) will return 0b0000x000, where x is the current value of bit 3 in P1IN.  Note that the result is not either 0 or 1, but rather 0 or 0b00001000, which is also the value BIT3.  Since 0 corresponds to the pressed state, we can poll P1IN in a loop that continues while (P1IN & BIT3) == BIT3, or equivalently while (P1IN & BIT3) != 0.

I've written my own version of a program that uses this in pushy_G2211.c.  Try coding the program up yourself first, and then compare it to mine.  A couple of new techniques used in the code are the #define command in the header, and encapsulation of a re-used command in a function call.  If you're not familiar with either of these techniques, brush up on them in your favorite, reliable book on C programming.  Essentially, #define lets me rename the value BIT0 to something more descriptive-- in this case, red_LED.  Since this doesn't change the value of BIT0, it can still be used in the code, or you can even define two things to be BIT0, as you would if you had LEDs on P1.0 and P2.0 of a larger MSP430 device.  The delay() function also makes the code more readable, and illustrates the power of encapsulation.  Since a delay function is so common, I could put this into a library, and then recall it whenever I want without having to retype the same thing over and over.  I encourage you to use these and any other good coding practices as much as possible to help make your code easier to understand and reuse!

This code isn't perfect, as it can't account for all situations.  What happens if you press the button while an LED is flashing?  Since the input value only matters during the while loop, nothing.  If you have ninja reflexes and push the button in a way where the MSP430 polls it before and after the pressing, then  it never sees the button press.  And what happens if you just hold the button down?  The solution here is to use interrupts, which is a topic beyond this tutorial.  But for now, you have a rudimentary way to use buttons.

Reader Exercise 1:  Copy the code into a new CCS project and try loading Pushy into your G2211.  Step through the code with the debugger to be sure it works.  (Change the limit on count to save clicks.  Change the limit on flash too, if you're not feeling particularly patient.)  Note that to get the debugger to work with the button you need to be holding it down as you step into the next line.  Try watching the P1IN register as you step through to see the input value change.

Reader Exercise 2:  We didn't discuss the RST button on the LaunchPad; try letting the red LED blink, then push this button instead of the button on P1.3.  (You'll have to terminate the debugger to get this to work.)  What does it do?  While the LaunchPad is configured in a way where this is the only function this button can have, it's a very useful tool to have, especially if you ever need to restart you program!


OCY said...

Internal pullup/pulldown resistors are: min 20k typ 35k max 50k

Anonymous said...

Internal resistors are great but what happens if you accidentally mark the button pin as output and place it high and then pres the button?
Is there any current limit or overheat shutdown?

For final products you can save pcb are by using the internal resistors but for a protoboard it's better to use external resistors and save the chips from frying.

OCY said...

"What happens if you accidentally mark the button pin as output and place it high and then pres the button?"

The button will draw excessive amount of current. This has nothing to do with the pull-up resistor -- whether internal or external.

Anonymous said...

You mention the need to hold down the button while stepping through to the polling portion.
Why is this needed?

Why doesn't the chip detect a HIGH already available on the pin?
Is this because it is edge triggered?
or am I missing something here?

Unknown said...

It's because of the method being used here; instead of using the interrupts (which hasn't been discussed at this point in the tutorials), the code is watching the value of P1IN. This register updates automatically at each clock cycle. When you use the debugger, you're stepping the clock cycle manually. So while pushing the button does clear the pin's voltage, P1IN does not update until the next clock cycle. If you're not still holding the button when you step the debugger, the poll won't see that the pin voltage has gone to ground.

Anonymous said...

I don't think i follow...the poll condition is

while ((P1IN & BTN) == BTN)

Just after exiting the for loop my P1IN register showed the value 0x0E indication BIT3 was high...

so why is this condition not being satisfied?

Pressing the button causes BIT3 to be 0. why do we need this?
(I maybe missing something entirely here... :P )
One more strange thing i noted was that highlighting P1IN and adding a watch expression gives me
void *P1IN= 0x0020.
What is this value?

NKT said...

@March 12:

The resistor in the controller pulls the pin voltage up and down nearly instantly, and the value has to be exactly one or the other when the program actually looks.

When using the debugger, it looks (within microseconds, for microseconds) when you hit the "Next step" button onscreen.

If it sees that the button isn't held down at that precise point, then it won't look again until the code loops around again.

Anonymous said...

Just a note: in my experience, the code provided compiled fine, however it seemed to have "optimized" the delay subroutine out of the final program.

Changing the variable declaration to "volatile unsigned int count" made sure the leds blinked at the correct speed though (one that's actually visible)

Anonymous said...

This code compiles for me with GCC but it doesn't wait for a key to be pressed. It just starts blinking green after it finishes with red. I have an MSP430 G2553. I tried it on my computer with GCC and with the online Inventor Town editor that also uses GCC. I tried hanging the included header to msp430g2553.h and just msp430.h but it didn't make a difference.

Any ideas?

Unknown said...

Anon: I'm afraid I have yet to start working with GCC myself. The best I can suggest is to use a debugger, and step through. If you can figure out what is causing the while loop to exit automatically, that will lead you to the problem.

Anonymous said...

I have the same behaviour as Anon.

but something else is that I NEED to use nop() as an operation in the while loop

If I don't then the program won't run.

Matt said...

I have spent the last couple of hours trying to figure out why my delay function is not working. My code works perfectly without the function and I wanted to clean it up a bit by putting it in there. Is anyone else having problems compiling functions with CCS? When I debug the code, it just jumps right over the call for delay();. Please help!

Unknown said...

There seems to be a change in the newer versions of CCS that optimizes out the delay function. You might check the comments in Tutorial 8b; including a nop(); command inside the for loop for delay keeps it from being optimized out.

Hope that helps!

b42 said...

I have experienced the same problem as the two anons.

I also use mspgcc, but the important fact is that I have launchpad revision 1.5. If you look at "LaunchPad User's Guide", designated SLAU318B, page 7, it says: "Pullup resistor R34 and capacitor C24 on P1.3 removed to reduce the current consumption" and indeed, they are missing on the board.

To make the example work, simply enable the internal pullup resistor by adding these two lines of code to the initialization:

P1OUT |= BIT3;
P1REN |= BIT3;

This, together with marking the count variable as volatile, makes the example work for me.

Unknown said...

Thanks for the blog great work. I noticed on my LaunchPad that R34 wasn't populated. If you don't specify an internal pull up resistor PIN will never see the switch close. You should set P1REN|=BIT3 to enable pull up/pull down and P1OUT|=BIT3 to make it a pull up. Makes the switch in my case a lot more reliable.

Unknown said...

Hello David,

Last night I spent two hours just to realize that the 47K resistor is not existing on my launchpad. If you look at the launchpad user guide at page 7 you will see that on version 1.5 of the board TI removed this resistor to reduce the current consumption.
I think it is a good idea to update the tutorial a bit.

Eventual, my solution was to put P1.4 as high output with resistor to use as pullup resistor and connected P1.3 and P1.4 with a jumper.

By the way, thanks a lot for the tutorials, they are really really good. It was really frustrating to learn from TI materials until I've found this resource.


Unknown said...

Nagasaki45: thanks! I wasn't aware that had changed in the latest board revisions for the LaunchPad.

Another possibility is to connect your pullup resistor between P1.3 and Vcc directly, freeing up P1.4 for other uses. That's essentially what the original board design was doing anyway.

Unknown said...

That's what I get for going through comments in reverse order.. looks like Owen had posted a comment that also helps out; I suspect his problem stems from the same change. The MSP430 does have internal pull-up and pull-down resistors, and so those can also be used in this case.

Unknown said...

for solve the problem with optimization on CCS 5, I've found a partial solution using a volatile unsigned integer on the delay loop!

Pedro Bacchini said...

Alexis Pojomovsky, Thx.

arun said...

while ((P1IN & BTN) == BTN);
and when button is pressed (u have mentioned logic 0), P1IN will be 0b00000000 and BTN will be 0b000001000 and P1IN&BTN will be 0b00000000 which will not be equalt to BTN. Then how does it executes the next statements?

Unknown said...

The while loop continues as long as the statement is true, which as you point out is while the button is unpressed. Once the button is pressed, the while loop ends, and the next statements are executed.

The while loop is inside of the infinite for loop--the for loop is the actual sequence of events for the program. This sequence is to flash the red LED, then wait for the button to be pressed. (Stuck in a while loop as long as the bit at BTN is 1.) Then flash the green LED, and again wait for a button press. (Stuck in another while loop under the same conditions.) Once that while loop ends at a button press, the for loop repeats.

Hopefully that explains the reasoning behind the code a little more clearly!