In this tutorial we will learn LPC1114/LPC1115 GPIO Programming. This tutorial is also applicable for LPC11U14, LPC11C14 as well. LPC1114 is a ARM Cortex-M0 based MCU by NXP. The Name of Registers, Data structures that I have used in this guide are defined in LPC11xx.h header file.
Most of the function pins on LPC1114, LPC1115, LPC11C14 Micro-controllers are grouped into Ports. LPC1114 has maximum of 4 ports viz. Port 0 to 3. For some LPC1100 devices ports 2 & 3 may be disabled; and even if enabled some pins may be disabled/reserved. The GPIO block in LPC11xx devices is connected to Peripheral AHB-Lite bus (Advanced High performance Bus – Lite) for fast read/write timing. By default, all pins are configured as inputs after reset.
The registers for each port are grouped into a structure with the following naming convention: LPC_GPIOx , where x is the port number. The GPIO ports are 12-bit wide i.e. a maximum 12 pins can be mapped, but each port may some pins which cannot be used i.e. they are ‘reserved’. Since the ports are only 12 bits wide, half-word(16 bit) values can be used to assign GPIO registers. For this tutorial I will be using LPC1114F/302 in LQFP48 package as reference. For other packages like TSSOP28, QFN33, etc. please refer table no. 172 on page 191 of the manual (Rev 12.4) for which pins are available.
For LPC1114 (LQFP48):
- All 12 pins on Ports 0, 1 and 2 are available.
- On Port 3 only first 6 pins (i.e. PIO3_0 to PIO3_5) are available. Rest are reserved.
GPIO Registers in LPC111x
The GPIO block has many registers. We will only go through some of these registers that are within the scope of this tutorial.
1) DATA : This register can be used to Read or Write values directly to the pins. Regardless of the direction set or any function begin selected for the particular pins, it gives the current state of the GPIO pin when read. When the a pin is configured as GPIO input, then writing to this register won’t have any affect on the pin. Similarly when a pin is configured for any other digital function, a write will have no effect. Used as LPC_GPIOn->DATA in programming when using CMSIS.
2) DIR : This is the GPIO direction control register. Setting a bit to 0 in this register will configure the corresponding Pin[0 to 11] to be used as an Input while setting it to 1 will configure it as Output. Used as LPC_GPIOn->DIR in programming when using CMSIS.
IOCON (I/O Configuration) Registers – Each pin has a dedicated IOCON register with which we can control: the function of PIN, enable pull-up or pull-down, Hysteresis to filter out spurious changes in input, and other modes like I2C, ADC and Open-Drain. For using digital GPIO we are only concerned with : Changing Pin function , enabling internal pull-up/down resistors and hysteresis. In CMSIS, all the IOCON registers are grouped under LPC_IOCON structure. While programming these can be accessed as LPC_IOCON->[register-name]. Where [register-name] is the name of the IOCON register for a specific PIN as given in datasheet/manual.
The general bit description for IOCON register is as shown in the diagram below:
- First 2 bits [2:0] are used for selecting PIN function: 0x0 – 1st function, 0x1 – 2nd function, 0x2 – 3rd function, 0x3 – 4th function. Refer datasheet for which functions are available for the given PIN.
- Next 2 bits [3:4] are used to select on-chip pull resistors: 0x0 – Both pull-up/down resistors disabled, 0x1 – Pull-down resistor enabled, 0x2 – Pull-up resistor enabled, 0x3 – Repeater mode.
- 5th Bit [5] is for Hysteresis. Setting it to 0 will disable Hysteresis and 1 will enable it.
I have discussed IOCON register in detail for LPC1114 in my previous tutorial : LPC11xx and LPC13xx LPC_IOCON Register Tutorial
Most of the PINS of LPC11xx MCU are Multiplexed i.e. these pins can be configured to provide up to 4 alternate functions. Not all pins on LPC111x are configured as GPIO by default after reset! For these pins you will need to explicitly re-assign the function to GPIO using IOCON register for the respective pins. Other pins can be directly used as GPIO, since their default function is GPIO (configured as inputs with pull-ups enabled) after reset.
LPC111x GPIO Programming & Example Code in C/C++
LPC11xx.h header is based on CMSIS(Cortex Microcontroller System Interface Standard) developed by ARM. System startup, core CPU access and peripheral definitions are given by CMSIS-CORE component of CMSIS. We will be using the definitions given by CMSIS-CORE. The register definitions for Cortex-M0 LPC111x MCUs are organized into groups depending on their functionality using “C Structure” definitions. From C/C++ programming point of view, this makes interfacing peripherals simple. For example all registers for Port 0 are grouped into structure defined as LPC_GPIO0. This is similar when programming for any LPC ARM Cortex-M0 MCU using CMSIS.
As per the CMSIS convention, the registers that we saw are grouped into structures. LPC_GPIOx is defined as a pointer to this structure in LPC11xx.h header. These registers are defined as members of this structure. Hence to use any register, for e.g. DATA, we must use the arrow “->” operator to de-reference members of structure (since the structure itself is a pointer) to access the register as follows : LPC_GPIO0->DATA = value. For creating LPC1114 projects you can either use KEIL uV5, LPCXpresso or MCUXpresso, but make sure you include CMSIS library. If you are using LPCXpresso LPC1114 board you can check out this tutorial.
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
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 5 of Port 0 as output. It can be done in following ways:
CASE 1. LPC_GPIO0->DIR = (1<<5); //(binary using left shift - direct assign: other pins set to 0)
CASE 2. LPC_GPIO0->DIR |= 0x0000020; //or 0x20; (hexadecimal - OR and assign: other pins not affected)
CASE 3. LPC_GPIO0->DIR |= (1<<5); //(binary using left shift - OR and assign: other pins not affected)
- In many scenarios, Case 1 must be avoided since we are directly assigning a value to the register. So while we are making PIO0_5 ‘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 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. 0x1C and 0x01C and 0x001C all mean the same.
Now, Lets go through some example codes:
Ex. 1)
Consider that we want to configure Pin 1 of Port 0 i.e PIO0_1(P0.1) as Output and want to drive it HIGH. This can be done as :
LPC_GPIO0->DIR |= 0x2; //same as (1<<1), Config PIO0_1 as Ouput
LPC_GPIO0->DATA |= 0x2; //Drive Output High for PIO0_1
Ex. 2)
Making output configured Pin 7 High of Port 0 i.e PIO0_7(P0.7) and then Low can be does as follows:
LPC_GPIO0->DIR |= (1<<7); //PIO0_7 is Output pin
LPC_GPIO0->DATA |= (1<<7); //Output for PIO0_7 becomes High
LPC_GPIO0->DATA &= ~(1<<7); //Output for PIO0_7 becomes Low
Ex. 3)
Configuring PIO0_9(P0.9) and PIO0_3(P0.3) as Output and Setting them High:
LPC_GPIO0->DIR |= (1<<9) | (1<<3); //Config PIO0_9 and PIO0_3 as Output
LPC_GPIO0->DATA |= (1<<9) | (1<<3); //Drive Output High for PIO0_9 and PIO0_3
Ex. 4)
Configuring Pins 4 to 11 of Port 1 (PIO1_4 to PIO1_11) as Output and Setting them High:
LPC_GPIO1->DIR |= 0xFF0; //Config PIO1_4 to PIO1_11 as Output
LPC_GPIO1->DATA |= 0xFF0; //Make output High for PIO1_4 to PIO1_11
Ex. 5)
In this example code, we will configure Pin 5 of Port 1 as Input with Pull-Down & Hysteresis enabled :
LPC_GPIO1->DIR &= ~(1<<5); //Config PIO1_5 as input (It will be anyways input after reset)
LPC_IOCON->PIO1_5 = (1<<3) | (1<<5);
//Enable on-chip Pull-down resistor [4,3]=01, Enable HYS [5]=1
When using switches as inputs, you can use an RC filter with Hysteresis enabled to debounce the input. Bouncing is the spurious changes in input until the contacts of the switch have stabilized. This can be filtered out using debouncing techniques, either in Software or Hardware.
Now lets play with some real world examples.
Ex. 6)
LPC11xx Blinky Example Code - Here we drive pin 7 of port 0 (PIO0_7) repeatedly high to low. PIO0_7(P0.7) of LPC11xx devices has 20mA current capability so you can directly drive an LED with it. Connect LED between PIO0_7 and GND. Here we will introduce some "hard-coded" delay between making all pins High and Low (and vice-versa) so it can be noticed.
#include <lpc11xx.h>
void delay(void);
int main(void)
{
LPC_GPIO0->DIR = (1<<7); //Configure PIO0_7 as Output
while(1)
{
LPC_GPIO0->DATA = (1<<7); //Drive output high to turn on LED
// Better way would be LPC_GPIO0->DATA |= (1<<7);
delay();
LPC_GPIO0->DATA = 0x0; //Drive output low to turn off LED
// Better way would be LPC_GPIO0->DATA &= ~(1<<7);
delay();
}
return 0; //normally this wont execute
}
void delay(void) //Hard-coded delay function
{
int count,i=0;
for(count=0; count < 3000000; count++) //You can edit this as per your needs
{
i++; //something needs to be here else compiler will remove the for loop!
}
}
Ex. 7)
In this example we will configure PIO1_5(P1.5) as Input and monitor it for a logic LOW on the pin. Here we will use a tactile switch whose one end is connected to PIO1_5 and other to GND (+3.3V). PIO0_7(P0.7) is configured as output and connected to an LED. Initially LED will be off but when the switch is pressed, LED will be turned ON. Once the LED is turned ON it will stay ON until the MCU is reset externally. The setup is shown in the figure below:
#include <lpc11xx.h>
int main(void)
{
LPC_GPIO1->DIR &= ~((1<<5)) ; //explicitly making PIO1_5 as Input - even though by default its already Input
LPC_GPIO0->DIR |= (1<<7); //Configuring PIO0_7 as Output
LPC_GPIO0->DATA &= ~(1<<7); //drive output low initially
while(1)
{
if( LPC_GPIO1->DATA & (1<<5) ) //Evaluates to True for a 'HIGH' on PIO1_5
{
LPC_GPIO0->DATA |= (1<<7); //drive PIO0_7 High
//Now PIO0_7 will be held high unless the MCU is reset
}
}
return 0; //this wont execute normally
}
Ex. 8)
Now lets extend example 7 so that when the button is pressed, the LED will glow and when released or not pressed the LED won't glow. Note that in both Example 7 and 8: Since internal pulls are enabled by default when switch is not pressed, internal pull-up resistor will force the input to be HIGH.
#include <lpc11xx.h>
void tinyDelay(void);
int main(void)
{
LPC_GPIO1->DIR &= ~((1<<5));
LPC_GPIO0->DIR |= (1<<7);
LPC_GPIO0->DATA &= ~(1<<7); //Turn off LED initially
while(1)
{
if( !(LPC_GPIO1->DATA & (1<<5)) )
{
LPC_GPIO0->DATA |= (1<<7); //Input low, so turn led ON
}
else
{
LPC_GPIO0->DATA &= ~(1<<7); //Input high, so turn led OFF
}
}
return 0; //normally this won't execute
}