GPIO Pins

Overview

One of the primary features of a microprocessor is to interact with and control external devices. These devices may be something as simple as an LED or a push button.  In order to interface with such devices, almost all microprocessors have dedicated pins that can be controlled by software.  We are going to examine the most basic digital interface: the Input/Output port or IO port.

IO Port Basics

A IO port is a collection of external pins on a microprocessor that can be configured to act as either an input or an output.  Voltage levels present on the physical pins are converted into corresponding logic value.    The supply voltage of the microprocessor, commonly called VCC, determines what voltage level equates to logic 1.  If the supply voltage is 3.3V, an input voltage of 3.3V will be interpreted as logic 1.  Similarly, 0.0V will be interpreted as logic 0.

In the example below, we see a 4-bit IO port configured as an input.  The switches on the lower 3 bits are open , so PA.0, PA.1 and PA.2 are connected to 3.3V.  The 4th bit has the switch pressed which connects PA.3 to 0.0V, or logic 0.   The IO port converts the analog voltage levels (3.3V & 0V) into a binary representation of 0111.

PortA-Input

We could alternatively configure the IO port to act as an output. In this situation, software writes a logic value to the IO port which in turn places corresponding voltage on the pins.  The example below illustrates how a GPIO port configured as an output can be used to turn LEDs on.  If binary 0011 is written to the GPIO port, LEDs D2 and D3 will turn on.

The processor places a voltage of 3.3V on PA.0 and PA.1.  Since there is no forward voltage drop, the LEDs D0 and D1 do not turn on.  The processor places 0.0V on PA.2 and PA.3, creating a forward voltage drop on D2 and D3 turning both LEDs on.

PortA-Output



Pull Up/Down resistors

In some situation it might be necessary for a pull-up or pull-down resistor to be enabled on a IO pin.  A pull-up/down resistor is often times used to set the state of a IO pin in the absence of a valid signal.  An example of this would be a push button connected to an input pin.  In the figure below, the push button on PA.0 is not being pressed.  When a IO pin is configured as an input, the microprocessor does not drive any value on the pin.  The pin is essentially in a Hi-Z state, meaning that it is neither 0 nor 1.  An input pins relies on some external circuit to set the value.  If software were to read the value of PA.0, the value is essentially unknown (It could be a 0 or a 1).

NoPullup

In order to solve this problem, we want to add a pull-up resistor that sets the value on PA.0 to a know value when the push button is not being pressed.

Pullup

When pressed, the push button connects PA.0 to GND and software would read a logic value of 0.  When the push button is not pressed, the pull-up resistor pulls the PA.0 to 3.3V and software would read a logic value of 1.

The TM4C123 has internal pull up/down resistors that can be enabled on any GPIO pin. The resistors can be enabled by writing a ‘1’ to the corresponding pin in the PUR (Pull Up Register) or PDR (Pull Down Register).

Many designs use external pull-up/down resistors.  This is what is shown in the figure above.  As a designer you get to choose if you want to use the internal or external pull-up resistors.  The benefit of internal resistors is that they are already included in the processor, consume no additional space on your printed circuit board, and have added flexibility by enabling them in software.

The down side is that it takes some finite amount of time during board initialization before internal resistors are active.  Prior to the IO initialization code being executed by the application, the IO lines will float to an unknown value and could cause a system to malfunction.  For this reason, I almost always use external pull-up/down resistors.  I would prefer to spend a few pennies on external resistors and ensure that floating GPIO pins do not cause a system to malfunction.

De-bouncing a Push Button

A common problem encountered when using a push button is that the mechanical contacts of some push buttons will intermittently make contact, or bounce, when first pressed.   The image below demonstrates this phenomenon.

switchbounce

If an application were to examine the IO pin in rapid succession, the application might treat a single button press as multiple button presses.  In most situation, this is not the behavior that the user would expect.

In order for the application to detect only a single push button event, we need to de-bounce the button.  De-bouncing a push button can be accomplished in hardware using a few different techniques such as a Schmitt triggered input or by adding an resistor-capacitor combination to the input pin.

debounce-rc-2

The voltage on the IO pin cannot change instantaneously due the presence of the capacitor.  Choosing the correct R and C values will act to filter out the temporary  spikes in voltage due to the bouncing of the button. The other approach to take is to de-bounce the button in software.  De-bouncing in software does not require an external resistor-capacitor filter but does require software to periodically sample the IO pin. If you look at the image of the bouncing push button, we see that the button stops bouncing after 228uS.  The largest gap in-between when the signal bounces is roughly 125us.

If we choose to sample at a rate of 150us, we can expect the signal to stop bouncing once the application reads a 1 followed by two readings of 0.  The arrow furthest to the right in the image below represents when the application would detect that the button has been pressed. There are several different ways to de-bounce a button in software.  The method that follows uses a Finite State Machine to de-bounce the button.    A button is considered pressed once it enters the state for the 2nd Zero detected.  This FSM will detect a single button press regardless of how long the user presses the button.

.debouncing-sampling-2

 

 

 

 

 

 

 

 

fsm

 

 

 

 

 

 

 

The code that follows implements this FSM using an enumerated type.  An enumerated type allows you to name the valid values that can be assigned to an instance of the enumerated type.  Enumerated types are not necessary to implement an FSM, but it does make the C implementation less error prone and more readable.

typedef enum 
{
  DEBOUNCE_ONE,
  DEBOUNCE_1ST_ZERO,
  DEBOUNCE_2ND_ZERO,
  DEBOUNCE_PRESSED
} DEBOUNCE_STATES;

//*****************************************************************************
// 
//*****************************************************************************
bool sw1_debounce_fsm(void)
{
  static DEBOUNCE_STATES state = DEBOUNCE_ONE;
  bool pin_logic_level;

  if( (P1->IN & 0x10) == 0)
     pin_logic_level = false;
  else
     pin_logic_level = true;

  switch (state)
  {
    case DEBOUNCE_ONE:
    {
      if(pin_logic_level)
        state = DEBOUNCE_ONE;
      else
        state = DEBOUNCE_1ST_ZERO;
      break;
    }
    case DEBOUNCE_1ST_ZERO:
    {
      if(pin_logic_level)
        state = DEBOUNCE_ONE;
      else
        state = DEBOUNCE_2ND_ZERO;
      break;
    }
    case DEBOUNCE_2ND_ZERO:
    {
      if(pin_logic_level)
        state = DEBOUNCE_ONE;
      else
        state = DEBOUNCE_PRESSED;
      break;
    }
    case DEBOUNCE_PRESSED:
    {
      if(pin_logic_level)
        state = DEBOUNCE_ONE;
      else
        state = DEBOUNCE_PRESSED;
      break;
    }
    default:
    {
      while(1){};
    }
  }

  if(state == DEBOUNCE_2ND_ZERO )
    return true;
  else
    return false;
}

This function would be called once every 150uS by another function.  Normally this delay would be triggered by a timer interrupt set at 150us.

Leave a Reply