GPIO Made Easy - MicroBuilder

Basic Input and Output (GPIO) Open Hardware and Micro-Manufacturing Made Easy(er) - microBuilder.eu

http://www.microbuilder.eu/Tutorials/LPC2148/GPIO.aspx

Tutorials > LPC2148 QuickStart Guide > Lesson 2 - Basic Input and Output (GPIO)

Lesson 2 - Basic Input and Output (GPIO)

Getting your microcontroller to talk to the outside world and vice-versa.

In our previous tutorial (Getting Started), we installed our development software (Crossworks for ARM), setup our JTAG debugger (an Olimex ARM-USB-OCD), and

ran our first program on the development board. As important as all of that is, it isn't particularly fun or exciting.

In this tutorial, we'll try to do something a bit more interesting and show you the most basic method for actually making your microcontroller interact with

the outside world via GPIO.

GPIO, or General Purpose Input/Output, is the easiest way for you to interact with basic peripherals like buttons, LEDs, switches, and other components. It

can also be used for more complex components like text and graphic LCDs, but for now we'll start with a few basic components that are relatively ease to get working.

In order to get started with GPIO, you need to understand the four 'registers' that control it: IODIR, IOSET, IOCLR and IOPIN.  Each of these registers is explained in detail below with some basic examples of how they work.

IODIR

IODIR controls the 'direction' of the GPIO pin. You use this register to set a GPIO pin to either Input (0) or Output (1). For example, if we want to use

our GPIO pin to send signals 'out' from the microcontroller to some external device, we need to set a pin (for example GPIO 0.10) to 'Output' (1).  We could do that with the following code:

GPIO0_IODIR |= (1 << 10);

If we wanted to use our GPIO pin to receive information from the outside world (reading it inside the microcontroller), we would need to set GPIO 0.10 to 'Input' (0).  That could be accomplished the following code:

GPIO0_IODIR &= ~(1 << 10);

To set several pins to output at once we could do either of the following (which will produce identical code when compiled):

// Set GPIO 0.10, 0.11, and 0.15 to output

GPIO0_IODIR |= (1 << 10) | (1 << 11) | (1 << 15);

// Set GPIO 0.10, 0.11, and 0.15 to output using hexadecimal

GPIO0_IODIR |= 00008C00; 

If you don't understand why these lines are equivalent, feel free to take a look at Understanding Hexadecimal Numbers.  

While ''(1 << 10) | (1 << 11) | (1 << 15)" is probably clearer to read and easier to understand, hexadecimal numbers are (for better or for worse) the norm in embedded development and you'll need to come to terms with them sooner or later.

IOSET and IOCLR

If your GPIO pin is set as Output (using the IODIR register mentionned above), you can use IOSET to set your GPIO pin to 'high' (providing a small 3.3V electrical current) or IOCLR to set it to 'low' (providing a connection to GND).  You shouldn't really think about high being 'on' and low being 'off',
though, since ... as we'll see in the example below ... you can often turn a device 'on' by setting it low and 'off' by setting it 'high', depending on how the components are connected.

Let's take the Olimex LPC-P2148 board as an example.  There are two LEDs provided on this board for testing purposes, connected to GPIO pins 0.10 and 0.11.

Looking at the schematic for this board, we can see that these two LEDs (located on the center right-hand side of the schematic) are connected to 3.3v on

one side, and GPIO 0.10 and 0.11 on the other side (with a 330 ohm resistor added to make sure the LEDs do not draw too much current and burn out).  What this means is that if we want to turn the LEDs 'on', we need to complete the electrical circuit by setting GPIO pins 0.10 and 0.11 to GND, or 'low'.  This is accomplished with IOCLR.  So, if we wanted to turn both LEDs ON and then OFF we would use the following code:

// Make sure GPIO 0.10 and 0.11 are set to output
GPIO0_IODIR |= (1 << 10) | (1 << 11);

// Turn the LEDs on using IOCLR (which gives a GND connection)
GPIO0_IOCLR |= (1 << 10) | (1 << 11); ???

// Turn the LEDs off using IOSET (which supplies 3.3V and breaks our circuit)
GPIO0_IOSET |= (1 << 10) | (1 << 11);

You might have noticed when running this code that as soon as you set the direction of the pins to 'Output' with IODIR (the first line of code), the LEDs turned on! This is because the pins will default to being 'low' (meaning a connection to GND) when you set them to output, which completes our circuit and turns the LEDs on before we even have time to try to clear the pins ourselves in the second line of code. In this particular case, the second line of code (where we use IOCLR to 'clear' the pins explicitly) won't actually do anything, since they are already 'low'. However, you should never depend on the 'default' value of a pin when writing your code since the pin's value may have been changed elsewhere (such as in an interrupt).

IOPIN

Regardless of whether your GPIO pin's direction is set to Input or Output, you can use the IOPIN register to read the current 'state' of every GPIO pin in the pin block (the collection of all 32 pins in GPIO0).  A 1 value means that the pin is currently 'high', and a 0 value means the pin is currently 'low'.

(Please note that since IOPIN returns the current state of ALL 32 pins in the pin block, you have to do a little bit of extra work to determine the value of one specific pin, but we'll give you an example below.)

You could read the IOPIN register, for example, to see if your LED was currently turned on or off, where IOPIN would return 0 for pin 10 (LED1) if it was currently turned on (since setting the GPIO pin to low turns the LED on) and 1 if the LED was off (since setting the GPIO pin high turns our LED off).

Here's a simple method showing how to do this, including one way to read the value of a single pin from the 32-bit value returned by IOPIN. This method will return '1' if the supplied pin is currently 'high', and '0' if it is currently low:

int getPinState(int pinNumber)
{
  // Read the current state of all pins in GPIO block 0
  int pinBlockState = GPIO0_IOPIN;

  // Read the value of 'pinNumber'
  int pinState = (pinBlockState & (1 << pinNumber)) ? 1 : 0;

  // Return the value of pinState
  return pinState;
}

Looking at this sample code, if the LED was currently turned on, "int state = getPinState(10);" should set 'state' to 0 since pin 10 is set to low; if the LED was currently turned off, the same 'state' variable would equal 1 since pin 10 is set to high.

Sidenote: Sometimes smaller really is better!

In general, most experienced software developers try to keep their code as 'concise' as possible (without sacrificing readability). The entire method above, for example, could be rewritten using one line that accomplishes exactly the same thing:

return (GPIO0_IOPIN & (1 << pinNumber)) ? 1 : 0;

Your compiler would almost certainly compile the code identically in these two cases, but in the latter case you end up with source code that isn't as sprawling and may be a bit quicker to analyse.

Another good example in the above code is the "?", called a conditional operator (sometimes called a 'ternary operator' as well). The conditional operator will return one of two values, depending on whether the code it is 'evaluating' -- (GPIO0_IOPIN & (1 << pinNumber)) in this case -- is true (1) or false (0). If it is true, the first value will be returned. If it is false, the second value (following the ":") will be returned. You could just as easily write this code as:

  if (GPIO0_IOPIN & (1 << pinNumber)) == "1"
  {
    return "1";
  }
  else
  {
    return 0;
  }

but that's a complete waste of space, and takes more effort to write as well as to read it. Again, the compiled code would almost certainly be identical in either case, but the aesthetic appeal and readability of your code can often be improved with a bit of experience and conscious effort on your part.

Exercise: Toggling LEDs with a Button

If you take all of the information presented above, you should be able to put together a simple program that will turn an LED on or off depending on whether a button is currently held down or released. Since there are two different LEDs and two different buttons on Olimex's LPC-P2148 board, we will try to turn LED1 on when Button1 is pressed, and LED2 on when Button2 is pressed. To help you out if you're not really comfortable reading schematics, here is the basic information you'll need to complete this exercise:

Device GPIO Pin 'On' State

LED1 0.10 Low / 0
LED2 0.11 Low / 0
Button1 0.15 Low / 0
Button2 0.16 Low / 0

I'm still lost ... what do I need to do?

Everything you need to know to do this exercise has already been explained in this article, and you'll probably get the most benefit out of it by trying to find a solution yourself first. If you've already made an effort, though, or are having a hard time, here are some basics steps that you could follow to
finish this exercise:

Set the direction of your buttons to input with IODIR

Set the direction of your LEDs to output with IODIR

Turn your LEDs off by setting them 'High'

In an infinite loop ("while (1) { ... }"), do the following:

Read the current state of Button1 and Button2 with IOPIN

If either button is pressed (remember pressed = 'Low' or '0'), ...
... turn the appropriate LED on by clearing it to 'Low'.

If a button is not pressed (released = 'High' or '1') ...
... turn the appropriate LED off by setting it to 'High'

I'm HOPELESSLY lost .... can you just give me the answer?

For the sake of completeness, and just to have something to compare your own efforts against, here is one possible solution for this exercise, building on

some of the code we have already introduced.  In this particular example, when Button1 is pressed, LED1 will be turned on, when Button1 is not pressed, LED1 will be turned off:

#include "lpc214x.h"

#define LED1    (1 << 10)
#define LED2    (1 << 11)
#define BUTTON1 (1 << 15)
#define BUTTON2 (1 << 16)

// Method prototypes

int getPinState(int pinNumber);

int main(void)
{
  // Set buttons as input
  GPIO0_IODIR &= ~(BUTTON1 | BUTTON2);

  // Set LEDs as output
  GPIO0_IODIR |= (LED1 | LED2);

  // Turn both LEDS off (set them 'high')
  GPIO0_IOSET |= (LED1 | LED2);

  while (1)
  {
    // Check if button 1 is pressed
    // Released = 'High' (1)
    // Pressed =  'Low'  (1)

    if (getPinState(15))
    {
      // Button1 is currently 'high'
      // Turn LED1 off
      GPIO0_IOSET |= LED1;
    }
    else
    {
      // Button1 is currently 'Low'
      // Turn LED1 on
      GPIO0_IOCLR |= LED1;
    }
  }
}

int getPinState(int pinNumber)
{
  // Read the current state of all pins in GPIO block 0
  int pinBlockState = GPIO0_IOPIN;

  // Read the value of 'pinNumber'
  int pinState = (pinBlockState & (1 << pinNumber)) ? 1 : 0;

  // Return the value of pinState
  return pinState;
}

Further Reading: GPIO is explained in detail in Chapter 8 of the LPC2148 User's Manual (UM10139).  It is also discussed in detail in Chapter 4 of the

Insider's Guide to the Philips ARM7 Based Microcontrollers.

Lesson Downloads

Lesson 2 - Exercise - Crossworks 2.0 Project

.END

No comments:

Post a Comment