OCFreaks!

LPC1768 GPIO Programming Tutorial

In this tutorial we will go through LPC1768 GPIO Programming. LPC1768 is an ARM Cortex-M3 based MCU by Phillips/NXP and has plenty of General Purpose Input Output pins to play with. The Name of Registers, Data structures that I have used in this guide are defined in LPC17xx.h header file. LPC17xx.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-M3 LPC17xx MCUs are organized into groups depending on their functionality using “C Structure” definitions. From a programming point of view, this makes interfacing peripherals simple. For example all registers for Port 0 are grouped into structure defined as LPC_GPIO0.

Prerequisite : Before getting into this you need to have basic understanding of Binary and Hexadecimal system and Bitwise operations in C, here are two tutorials which can go through (or if you are already acquainted with these you can skip them and continue below) :

Most of the function oriented pins on LPC176x Microcontrollers are grouped into Ports. LPC1768 has 5 ports viz. Port 0 to 4. The associated registers for each port are grouped into a structure with the following naming convention : LPC_GPIOx , where x is the port number. From the programming point of view, these ports are 32-bit wide i.e. a maximum 32 pins can be mapped, but each port may have a few or many pins which cannot be used i.e. they are ‘reserved’. For this tutorial will be using LPC1768 in LQFP100 package as reference. If you are using LQFP80 package please refer the manual on which pins are available.

The naming convention for Pins on MCU is ‘Px.y’ where ‘x’ is the port number (0,1,2,3 or 4 in our case since we have only 5 ports to play with in lpc1768) and ‘y’ is simply the pin number in port ‘x’. For example : P0.7 refers to Pin number 7 of Port 0 , P2.11 refers to Pin number 11 in Port 2.

GPIO Registers in LPC1768

Registers on LPC1768 are present on Peripheral AHB bus(Advanced High performance Bus) for fast read/write timing. So, these are basically Fast I/O or Enhanced I/O and hence the naming convention in datasheet uses a prefix of “FIO” instead of something like “GIO” for all the registers related to GPIO. Lets go through these as given below.

1) FIODIR : This is the GPIO direction control register. Setting a bit to 0 in this register will configure the corresponding pin to be used as an Input while setting it to 1 will configure it as Output.

2) FIOMASK : This gives masking mechanism for any pin i.e. it is used for Pin access control. Setting a bit to 0 means that the corresponding pin will be affected by changes to other registers like FIOPIN, FIOSET, FIOCLR. Writing a 1 means that the corresponding pin won’t be affected by other registers.

3) FIOPIN : This register can be used to Read or Write values directly to the pins. Regardless of the direction set for the particular pins it gives the current start of the GPIO pin when read.

4) FIOSET : It is used to drive an ‘output’ configured pin to Logic 1 i.e HIGH. Writing Zero does NOT have any effect and hence it cannot be used to drive a pin to Logic 0 i.e LOW. For driving pins LOW FIOCLR is used which is explained below.

5) FIOCLR : It is used to drive an ‘output’ configured pin to Logic 0 i.e LOW. Writing Zero does NOT have any effect and hence it cannot be used to drive a pin to Logic 1.

Most of the PINS of LPC176x MCU are Multiplexed i.e. these pins can be configured to provide up to 4 different functions. By default, after Power-On or Reset : all pins of all ports are set as GPIO so we can directly use them when learning GPIO usage. The different functions that any particular pin provides can be selected by setting appropriate value in the PINSEL register for the corresponding pin. Each pin on any port has 2 corresponding bits in PINSEL register. The first 16 pins (0-15) on a given port will have a corresponding 32 bit PINSEL register and the rest 16 bits will have another register. For example bits 0 & 1 in PINSEL0 are used to select function for Pin 1 of Port 0, bits 2 & 3 in PINSEL0 are used to select function for PIN 2 of port 0 and so on. The same is applicable for PINMODE register which will go through in last section of this article. Have a look at the diagram given below.

Here ‘bx’ refers to xth Bit and ‘Px.y’ refers to yth Pin on port ‘x’. Just remember that assigning ‘0’ to the corresponding PINSEL registers forces the corresponding pins to be used as GPIO. Since by default all pins are configured as GPIOs we don’t need to explicitly assign a ‘0’ value to PINSEL register in our programming examples.

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 LPC17xx.h header. These registers are defined as members of this structure. Hence to use any register, for e.g. FIODIR, 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->FIODIR = some value.

Note : LPC17xx MCUs have Peripheral Power Control feature which allows individual peripherals to be turned off for power saving using PCONP Register which is a member of LPC_SC (system control) structure. We don’t need to explicitly Power-On the GPIO block because its Power is always enabled.

Pins on LPC176x are 5V tolerant when used for GPIO but when used as inputs for ADC block they are not. However, I would recommend that wherever possible, use a buffer for level translation between 5V and 3.3V. If you need any help regarding level translation just let me know in the you comment below.

GPIO Programming & Examples

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 3 on Port 0 as output. It can be done in following ways:


CASE 1. LPC_GPIO0->FIODIR = (1<<3); //(binary using left shift - direct assign: other pins set to 0)

CASE 2. LPC_GPIO0->FIODIR |= 0x0000008; // or 0x8; (hexadecimal - OR and assign: other pins not affected)

CASE 3. LPC_GPIO0->FIODIR |= (1<<3); //(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. 0x2F and 0x02F and 0x002F 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.

Finally, All GPIO pins are configured as Input with pullup after Reset by default!

Now, Lets go through some examples :

Example #1)

Consider that we want to configure Pin 4 of Port 0 i.e P0.4 as Ouput and want to drive it High(Logic 1). This can be done as :


LPC_GPIO0->FIODIR |= (1<<4); // Config P0.4 as Ouput
LPC_GPIO0->FIOSET |= (1<<4); // Make ouput High for P0.4

Example #2)

Making output configured Pin 17 High of Port 0 i.e P0.17 and then Low can be does as follows:


LPC_GPIO0->FIODIR |= (1<<17); // P0.17 is Output pin
LPC_GPIO0->FIOSET |= (1<<17); // Output for P0.17 becomes High
LPC_GPIO0->FIOCLR |= (1<<17); // Output for P0.17 becomes Low

Example #3)

Configuring P0.5 and P0.11 as Ouput and Setting them High:


LPC_GPIO0->FIODIR |= (1<<5) | (1<<11); // Config P0.5 and P0.11 as Ouput
LPC_GPIO0->FIOSET |= (1<<5) | (1<<11); // Make ouput High for P0.5 and P0.11

Example #4)

Configuring 1st 8 Pins of Port 0 (P0.0 to P0.7) as Ouput and Setting them High:


LPC_GPIO0->FIODIR |= 0xFF; // Config P0.0 to P0.7 as Ouput
LPC_GPIO0->FIOSET |= 0xFF; // Make output High for P0.0 to P0.7

Now lets play with some real world examples.

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

Example #5)

Simple Blinky Example - Here we repeatedly make pins 0 to 3 in port 0 (P0.0 to P0.3) High then Low then High and so on. You can connect LED to any 1 pin using 330Ohm series resistor or connect LEDs to all Pins using a buffer ICs like 74HC245 to drive the LEDs. The LED(s) are to be connected between output and ground. Here we will introduce some delay between making all pins High and Low so it can be noticed.


#include <lpc17xx.h>

void delay(void);

int main(void)
{
	LPC_GPIO0->FIODIR = 0xF; // Configure pins 0 to 3 on Port 0 as Output
	
	while(1)
	{
		LPC_GPIO0->FIOSET = 0xF; // Output HIGH
		delay();
		LPC_GPIO0->FIOCLR = 0xF; // Output LOW
		delay();
	}
	return 0; // normally this wont execute
}	

void delay(void) //Hardcoded delay function
{
	int count,i=0;
	for(count=0; count < 6000000; count++) // You can edit this as per your needs
	{
		i++; // something needs to be here else compiler will remove the for loop!
	}
}

Example #6)

Configuring P0.5 as Input and monitoring it for a external event like connecting it to LOW or GND. P0.8 is configured as output and connected to LED. If Input for P0.5 is a 'Low' (GND) then output for P0.8 is made High which will activate the LED and make it glow (Since the other END of LED is connected to LOW i.e GND). Since by default, internal Pull-ups are enabled the 'default' state of the pins configured as Input will be always 'High' unless it is explicitly pulled 'Low' by connecting it to Ground. Consider one end of a tactile switch connected to P0.5 and other to ground. When the switch is pressed a 'LOW' will be applied to P0.5. The setup is shown in the figure below:


#include <lpc17xx.h>

int main(void)
{
	LPC_GPIO0->FIODIR &= ~((1<<5)) ; // explicitly making P0.5 as Input - even though by default its already Input
	LPC_GPIO0->FIODIR |= (1<<8); // Configuring P0.8 as Output 

	while(1)
	{
		if( !(LPC_GPIO0->FIOPIN & (1<<5)) ) // Evaluates to True for a 'LOW' on P0.5
		{
			LPC_GPIO0->FIOSET |= (1<<8); // drive P0.8 High
		}
	}
	return 0; // this wont execute normally
}

Example #7)

Now lets extended example 6 so that when the button is pressed, the LED will glow and when released or not pressed the LED won't glow. Capturing inputs in this way, using switches, leads to a phenomenon called 'bouncing' which needs to be resolved using 'debouncing' which I have explained in a previous GPIO tutorial for LPC2100 MCUs - please refer it for explanation. The algorithm used below is very basic for the sake of brevity, you can find a proper debouncing algorithms online.


#include <lpc17xx.h>

void tinyDelay(void);

int main(void)
{
	int flag=0, pinSamplePrev, pinSampleCurr;
	LPC_GPIO0->FIODIR &= ~((1<<5)); 
	LPC_GPIO0->FIODIR |= (1<<8);
	
	pinSamplePrev = LPC_GPIO0->FIOPIN & (1<<5); //Initial Sample

	while(1)
	{
		pinSampleCurr = LPC_GPIO0->FIOPIN & (1<<5); //New Sample
		
		if( pinSampleCurr != pinSamplePrev )
		{
			//P0.5 might get low or high momentarily due to noise depending the external conditions or some other reason
			//hence we again take a sample to insure its not due to noise
			
			tinyDelay(); // momentary delay
			
			// now we again read current status of P0.5 from IO0PIN	
			pinSampleCurr = LPC_GPIO0->FIOPIN & (1<<5);
			
			if( pinSampleCurr != pinSamplePrev )
			{
				//State of P0.5 has indeed changed
				if(flag) //First time Flag will be = 0 hence else part will execute
				{
					LPC_GPIO0->FIOSET |= (1<<8); // drive P0.8 High, so LED turns on
					flag=0; //next time 'else' part will excute
				} 
				else
				{
					LPC_GPIO0->FIOCLR |= (1<<8); // drive P0.8 Low, so LED turns off
					flag=1; //next time 'if' part will excute
				}
				
				//set current value as previous since it has been processed
				pinSamplePrev = pinSampleCurr; 
			}
		}
	}
	return 0; // this wont execute ever, you can comment it to shut up the compiler complaining.
}

void tinyDelay(void) //Hardcoded delay - use can use timer to get precise delays
{
	int z,c;
	c=0;
	for(z=0; z<3000; z++) //Higher value for higher clock speed 
	{
		c++; //just so compiler doesn't remove the 'for' loop
	}
}

Easing play with Bits

Using the left shift operation is not confusing but when too many are used together I find it a little bit messy and affects code readability to some extent. For this I define a Macro BIT(x) as:


#define BIT(x) (1<<x) 

After that is defined we can directly use BIT(x). Using this, Example 3 can be re-written as:


LPC_GPIO0->FIODIR |= BIT(5) | BIT(11); // Config P0.4 and P0.21 as Ouput
LPC_GPIO0->FIOSET |= BIT(5) | BIT(11); // Make ouput High for P0.4 and P0.21

Example 6 can be re-written as:


#include <lpc17xx.h>

#define BIT(x) (1<<x)

int main(void)
{
	LPC_GPIO0->FIODIR &= ~(BIT(5)); 
	LPC_GPIO0->FIODIR |= BIT(8);

	while(1)
	{
		if( !( LPC_GPIO0->FIOPIN & BIT(5) ) ) 
		{
			LPC_GPIO0->FIOSET |= BIT(8);
		}
	}
	return 0;
}	

Similarly, we can define BITN(x) as follow:


#define BITN(x) (!(1<<x))

This gives you 1's complement or Negation for (1<<x). Here xth bit will be 0 and all other bits will be 1s.

Pin modes of Port pins in Cortex-M3 LPC176x MCUs

LPC1768 MCU supports 4 pin modes. These include the internal (on-chip) pull-up and pull-down resistor modes and a special operating mode. Pull-up and Pull-down resistors prevent the inputs from 'floating' by either pulling them to logic HIGH or LOW. The state of the inputs is therefore always defined.

The PINMODE and PINMODE_OD registers together are used to control the pin mode. A total of 3 bits are used to control the mode of a any pin, 2 bits from PINMODE and 1 bit from PINMODE_OD register. Hence we have a total of 10 PINMODE registers and a total of 5 PINMODE_OD. For the pins which are not available the corresponding bits are reserved. Bits 0 & 1 of PINMODE0 corresponds to Pin 0 of Port 0, bits 2 & 3 of PINMODE0 corresponds to Pin 1 of Port 0 and so on. PINMODE_ODx has 1:1 correspondence to all the pins in PORTx.

Note : The on-chip pull-up/pull-down resistor can be selected for every pin regardless of the function selected for that pin with the exception of the I2C pins for the I2C0 interface and the USB pins.

Pin mode select register bits for PINMODE0-9:

00 Internal pull-up resistor enabled.
01 Repeater mode - Retains its last state if it is configured as an input and is not driven externally.
10 Both pull-up and pull-down resistors disabled.
11 Internal pull-down resistor enabled.

The PINMODE_OD register controls the open drain mode for port pins. Setting any bit to 1 will enable open-drain mode for the corresponding pin. Setting any bit to 0 will enable the normal(default) mode for the pin.

As a beginner you will be mostly using either Pull-up or Pull-down mode with non open-drain mode (default). You can read more about the Pin Modes on Page 103 of the LPC176x User Manual(Rev. 01).

References and Further reading: