In this tutorial we will learn MSP430 GPIO Programming. It is also applicable for MSP430x2xx devices like MSP430G2553, MSP430G2231, etc found on Launchpad Development board. Most of the pins on MSP430 Microcontrollers are grouped into a maximum of 8 Ports viz. P1 to P8. Each port is 8-bits wide and has eight associated I/O pins. These pins are directly mapped to the corresponding port registers and hence I/O pins can be manipulated independently. Only pins in Ports P1 and P2 support interrupts. Additionally, each I/O pin also has configurable pull-up and pull-down resistors. Each port has an associated groups of registers used to manipulate its individual pins. The bit mapping and port grouping is as shown below:
Current version of MSP430G2 Launchpad Ships with MSP430G2553 and MSP430G2452. Older version(Rev1.4) used to ship with MSP430G2231 and MSP430G2211. However, programming is same for all supported devices unless mentioned.
GPIO Registers in MSP430
The GPIO block has many registers. We will only go through some of the digital I/O registers that are within the scope of this tutorial. I will cover Interrupt related registers(viz. PxIFG, PxIES, PxIE) in a different tutorial.
1. PxDIR: This is the GPIO direction control register. Setting any bit to 0 in this register will configure the corresponding Pin[0 to 7] to be used as an Input while setting it to 1 will configure it as Output.
2. PxIN (Readonly): Used to Read values of the Digital I/O pins configured as Input. 0 = Input is LOW, 1 = Input is HIGH.
3. PxOUT: Used to directly write values to pins when pullup/pulldown resistors are disabled. 0 = Output is LOW, 1 = Output is HIGH. When pullup/pulldown resistors are enabled: 0 = Pin is pulled down, 1 = Pin is pulled up.
4. RxREN: For pins configured as input, PxREN is used to enable pullup/down resistor for a given pin and PxOUT in conjunction with PxREN is used to select either Pullup or pulldown resistor. Setting a bit to 1 will enable pullup/down resistor for the corresponding pin while setting it to 0 will disable the same.
PxDIR | PxREN | PxOUT | I/O Config |
---|---|---|---|
0 | 0 | X | Input with resistors disabled |
0 | 1 | 0 | Input with Internal Pulldown enabled |
0 | 1 | 1 | Input with Internal Pullup enabled |
1 | X | X | Output – PxREN has no effect |
5. PxSEL & PxSEL2: Since most of the port pins are multiplexed with upto 4 different functions, we need a mechanism to select these functions. This is achived using PxSEL and PxSEL2 registers. The bit combinations of these registers for a particular pin will select a particular pin function. The bit combination is as given below:
PxSEL2(nth bit) | PxSEL(nth bit) | Pin Function |
---|---|---|
0 | 0 | GPIO (Digital I/O) Function |
0 | 1 | Primary Peripheral Function |
1 | 0 | Reserved. Consult device specific datasheet. |
1 | 1 | Secondary Peripheral Function |
Also note that PxSEL/PxSEL2 registers do not change the pin directions as required by the module function. Make sure you set the proper pin direction, as required by the alternate function, using PxDIR register.
MSP430 GPIO Programming & Example Code in C/C++
Prerequisite: Before we start programming gpio you need to have basic understanding of Binary and Hexadecimal system and Bitwise operations in C/C++, here are two tutorials which can go through (or if you are already acquainted with these you can skip these and continue below) :
- Hexadecimal and Binary Number System basics for Embedded Programming
- Tutorial : Embedded programming basics in C – bitwise operations
msp430.h is common header for all MSP430 devices. This header identifies your device and accordingly includes the device specific header. Each of the device specific headers also include bit definitions viz. from BIT0 to BITF. Where BITn is equivalent to (1<<n) i.e the nth bit location is a 1 and rest bits are 0s.
Now lets see how we can assign values to registers. We can use Hexadecimal notation & decimal notation for assigning values. If your compiler supports other notations like binary notation use can use that too. Lets say, we want to set PIN 6 of Port 1 as output. It can be done in following ways:
CASE 1. P1DIR = (1<<6); //(binary using left shift - direct assign: other pins set to 0)
CASE 2. P1DIR |= 0x20; //or 0x20; (hexadecimal - OR and assign: other pins not affected)
CASE 3. P1DIR |= (1<<6); //(binary using left shift - OR and assign: other pins not affected)
CASE 4. P1DIR |= BIT6; //Same as above, use Standard BIT definitions
- In many scenarios, Case 1 must be avoided since we are directly assigning a value to the register. So while we are making P1.6 '1' others are forced to be assigned a '0' which can be avoided by ORing and then assigning Value.
- Case 2 can be used when bits need to be changed in bulk and
- Case 3(and 4) can be used when some or single bit needs to be changed.
First thing to note here is that preceding Zeros in Hexadecimal Notation can be ignored because they have no meaning since we are working with unsigned values here (positive only) which are assigned to Registers. For eg. 0x4 and 0x04 mean the same.
Basic example code in C/C++
Ex. 1)
Consider that we want to configure Pin 0 of Port 1 i.e P1.0 as Output and want to drive it HIGH. This can be done as:
P1DIR |= BIT0; //same as (1<<0) and 0x1, Configure P0.1 as Output
P1OUT |= BIT0; //Drive Output High for P1.0
Ex. 2)
Making output configured Pin P1.4 LOW individually, without affecting other bits can be does as follows:
P1DIR |= BIT4; //Configure P1.4 as Output
P1OUT &= ~BIT4; //Only P1.4 is made LOW
Ex. 3)
Configuring P1.4 and P1.6 as Output and Setting them HIGH together:
P1DIR |= BIT4 | BIT6; //Config P1.4 and P1.6 as Output
P1OUT |= BIT4 | BIT6; //Drive P1.4 and P1.6 HIGHT
Ex. 4)
Configuring all Pins of Port 1 (P1.0 to P1.7) as Output and Setting them High:
P1DIR = 0xFF; //Config all pins of P1 as Output
P1OUT = 0xFF; //Drive all pins HIGH
Ex. 5)
In this example code, we will configure Pin P1.3 as Input with internal Pullup enabled:
P1DIR &= ~BIT3; //Config P1.3 as input (It will be anyways input after reset)
P1REN = BIT3; //Enable Pullup/down for P1.3
P1OUT = BIT3; //Select Pullup resistor for P1.3
Ex. 6)
We can check the current state of Input pin P1.3, configured above, as:
if(P1IN & BIT3)
{
//P1.3 is HIGH
}
else
{
//P1.3 is LOW
}
Ex. 7)
Lets, say we have configured P1.4 as input with pullup resistor enabled and we want to continuously scan P1.4 until a LOW appears at the pin. This can be achieved as follows:
P1DIR &= ~BIT4; //Configure P1.4 as Input
P1REN = BIT4; //Enable Pullup/down for P1.4
P1OUT = BIT4; //Select Pullup resistor for P1.4
if(..) //some condition or parent loop
{
while( !(P1IN & BIT4) ); //wait until P1.4 is LOW (busy wait)
/*do something after loop terminates*/
}
Real World MSP430 GPIO Examples/Programs
Note: Examples(Demo Programs) given below assume default clock speeds. I have tested below programs using CCS V6.2.0 on MSP430G2231/MSP430G2211 but it will also work on MSP430G2553/MSP430G2452 and other compatible Microcontrollers.
Blinky Example Code
The most common example for GPIO is blinking an LED. Here we drive pin 0 of port 1 (P1.0) HIGH then LOW in a loop (Toggle). P1.0 is connected to LED1 on Launchpad Development board. We will use the intrinsic function __delay_cycles(unsigned long cycles) . As evident, it generates delay by the amount of clock cycles given as argument. However, this is not the recommended method for generating delays and must be avoided. I have used it in example below just for the sake of brevity. To generate delays using Timer please check my MSP430 Timer Tutorial.
#include <msp430.h>
int main(void)
{
WDTCTL = WDTPW | WDTHOLD; //Stop watchdog timer to Prevent PUC Reset
P1DIR |= BIT0; //Configure P1.0 as Output
while(1)
{
P1OUT ^= BIT0; //Toggle P1.0 - LED Blinks
__delay_cycles(400000);
/* The above code is same as:
P1OUT |= BIT0; //Drive P1.0 HIGH - LED1 ON
__delay_cycles(400000);
P1OUT &= ~BIT0; //Drive P1.0 LOW - LED1 OFF
__delay_cycles(400000); */
}
//return 0; //normally this won't execute
}
Toggle LED with Button Switch
In this example program we will configure P1.3 as Input and monitor it for a LOW on the pin. P.3 is connected to Push Button Switch (S2) on the Launchpad. We will use LED2 i.e. P1.6 as output. Initially LED2 will be ON. Whenever we press the Button S2 it will toggle the LED state.
#include <msp430.h>
int main(void)
{
WDTCTL = WDTPW | WDTHOLD; //Stop watchdog timer to Prevent PUC Reset
P1DIR &= ~BIT3 ; //explicitly making P1.3 as Input - even though by default its Input
P1REN = BIT3; //Enable Pullup/down
P1OUT = BIT3; //Select Pullup
P1DIR |= BIT6; //Configuring P1.6(LED2) as Output
P1OUT |= BIT6; //drive output HIGH initially
while(1)
{
if( !(P1IN & BIT3) ) //Evaluates to True for a 'LOW' on P1.3
{
P1OUT ^= BIT6; //Toggle the state of P1.6
while( !(P1IN & BIT3) ); //wait until button is released
}
}
//return 0; //this won't execute normally
}
Turn LED ON when Button is pressed else OFF
Now lets modify the above example so that when the button is pressed, LED2 will glow and when released or not pressed the LED won't glow. When switch is not pressed internal pull-up resistor will force the input to a HIGH state.
#include <msp430.h>
int main(void)
{
WDTCTL = WDTPW | WDTHOLD; //Stop watchdog timer to Prevent PUC Reset
P1DIR &= ~BIT3 ; //explicitly making P1.3 as Input - even though by default its Input
P1REN = BIT3; //Enable Pullup/down
P1OUT = BIT3; //Select Pullup
P1DIR |= BIT6; //Configuring P1.6(LED2) as Output
P1OUT &= ~BIT6; //Turn off LED2 initially
while(1)
{
if( !(P1IN & BIT3) )
{
P1OUT |= BIT6; //Input LOW - turn led ON
}
else
{
P1OUT &= ~BIT6; //Input HIGH - turn led OFF
}
}
//return 0; //normally this won't execute
}