OCFreaks!

LPC1114 GPIO Programming Tutorial

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):

The naming convention(as used in the Datasheet) for Pins on MCU is ‘PIOx_y’ where ‘x’ is the port number (0, 1, 2 or 3) and ‘y’ is simply the pin number in port ‘x’. For example : PIO0_2 refers to Pin number 2 of Port 0 , PIO1.10 refers to Pin number 10 in Port 1 on some development boards like LCPXpresso it will be marked Px.y instead of PIOx_y. Both mean the same.

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:

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) :

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)

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.

Note that bit 31 is MSB on extreme left and bit 0 is the LSB on extreme right i.e. we are using Big Endian Format. Hence bit 0 is the 1st bit from right , bit 1 is the 2nd bit from right and so on. BIT and PIN Numbers are Zero(0) indexed which is quite evident since Bit ‘x’ refers to (x-1)th location in the corresponding register.

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.

The below examples are given, assuming 48Mhz CCLK which is configured & initialized by system startup code generated by Keil UV5/UV4, MCUXpresso, LPCXpresso, CoIDE, etc.

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
}
Imp. Note: As mentioned in Datasheet, many of the Pins on LPC111x are 5V tolerant in digital mode but when used as inputs for ADC block the input voltage must not exceed Vdd. However, I would recommend that wherever possible, use a buffer/level-shifter for level translation between 5V and 3.3V for digital I/O. If you need any help regarding level translation just let me know in the comment section below.