Introduction to GPIO — Tiva C
- Eslam El Hefny
- Tutorials, Tiva c
- April 4, 2025
Overview
GPIO (General-Purpose Input/Output) is the most fundamental peripheral in any microcontroller. On the TM4C123GH6PM, six GPIO ports (A through F) provide up to 43 individually configurable pins that can serve as digital inputs, digital outputs, or alternate-function pins for peripherals such as UART, SPI, and I2C.
Beginner Level — What & Why
What is GPIO?
GPIO pins are the microcontroller’s “hands” — they let the chip sense the outside world (input) or control it (output). An input pin reads a HIGH (3.3 V) or LOW (0 V) signal. An output pin drives one of those two voltages.
Real-World Analogy
Imagine a row of light switches on a wall. Each switch can be flipped either to “sense” a push-button press (input mode) or to “drive” a lamp (output mode). The microcontroller is the electrician deciding which switches do what and when.
What Problem Does GPIO Solve?
Without GPIO, a microcontroller could not interact with LEDs, buttons, relays, sensors with digital outputs, or any simple on/off device. Every embedded project starts with GPIO.
Key Terms
| Term | Meaning |
|---|---|
| Port | A group of up to 8 GPIO pins (Port A = PA0-PA7) |
| GPIODIR | Direction register — 1 = output, 0 = input |
| GPIODEN | Digital Enable register — must be set for digital I/O |
| GPIOAFSEL | Alternate Function Select — routes pin to a peripheral |
| GPIOPUR | Pull-Up Resistor register |
| GPIOPDR | Pull-Down Resistor register |
| Commit register | Protects NMI and JTAG pins from accidental reconfiguration |
| APB | Advanced Peripheral Bus (default GPIO bus on TM4C123) |
Intermediate Level — How It Works
Port Addresses
| Port | APB Base Address | AHB Base Address |
|---|---|---|
| Port A | 0x40004000 |
0x40058000 |
| Port B | 0x40005000 |
0x40059000 |
| Port C | 0x40006000 |
0x4005A000 |
| Port D | 0x40007000 |
0x4005B000 |
| Port E | 0x40024000 |
0x4005C000 |
| Port F | 0x40025000 |
0x4005D000 |
TM4C123GXL LaunchPad LEDs and buttons:
- PF1 = Red LED
- PF2 = Blue LED
- PF3 = Green LED
- PF0 = SW2 (requires unlock — shared with NMI)
- PF4 = SW1
Key Registers
| Register | Offset | Description |
|---|---|---|
| GPIODATA | 0x000 |
Data register (APB address trick applies) |
| GPIODIR | 0x400 |
Direction: 1 = output, 0 = input |
| GPIOIS | 0x404 |
Interrupt Sense (0 = edge, 1 = level) |
| GPIOIBE | 0x408 |
Interrupt Both Edges |
| GPIOIEV | 0x40C |
Interrupt Event (0 = falling/low, 1 = rising/high) |
| GPIOIM | 0x410 |
Interrupt Mask |
| GPIORIS | 0x414 |
Raw Interrupt Status |
| GPIOMIS | 0x418 |
Masked Interrupt Status |
| GPIOICR | 0x41C |
Interrupt Clear |
| GPIOAFSEL | 0x420 |
Alternate Function Select |
| GPIOPUR | 0x510 |
Pull-Up Resistor Enable |
| GPIOPDR | 0x514 |
Pull-Down Resistor Enable |
| GPIODEN | 0x51C |
Digital Enable |
| GPIOLOCK | 0x520 |
Lock register (write 0x4C4F434B to unlock) |
| GPIOCR | 0x524 |
Commit register |
| GPIOAMSEL | 0x528 |
Analog Mode Select |
GPIODATA Address Trick (APB)
The GPIODATA register uses bits [9:2] of the bus address as a write mask. This allows atomic read/modify/write without disabling interrupts:
/* Write only pin 1 (bit mask = 0b00000010 → address offset = 0x008) */
HWREG(GPIO_PORTF_BASE + (GPIO_PIN_1 << 2)) = GPIO_PIN_1;
/* Write pins 1 and 3 simultaneously */
HWREG(GPIO_PORTF_BASE + ((GPIO_PIN_1 | GPIO_PIN_3) << 2)) =
GPIO_PIN_1 | GPIO_PIN_3;
When reading, the address mask ANDs the result, so you only see the bits you addressed.
TivaWare DriverLib API
/* Enable clock and configure output */
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF));
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
/* Configure input with pull-up (SW1 = PF4) */
GPIOPinTypeGPIOInput(GPIO_PORTF_BASE, GPIO_PIN_4);
GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_4,
GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
/* Read and write */
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_PIN_1); // PF1 HIGH
int state = GPIOPinRead(GPIO_PORTF_BASE, GPIO_PIN_4); // read PF4
/* GPIO interrupt configuration */
GPIOIntTypeSet(GPIO_PORTF_BASE, GPIO_PIN_4, GPIO_FALLING_EDGE);
GPIOIntEnable(GPIO_PORTF_BASE, GPIO_INT_PIN_4);
IntEnable(INT_GPIOF);
IntMasterEnable();
Advanced Level — Deep Dive
Bare-Metal Register Configuration
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_gpio.h"
/* Step 1: Enable Port F clock via SYSCTL RCGCGPIO register */
HWREG(SYSCTL_RCGCGPIO) |= (1 << 5); // bit 5 = Port F
/* Wait for clock to stabilize */
while (!(HWREG(SYSCTL_PRGPIO) & (1 << 5)));
/* Step 2: Set PF1, PF2, PF3 as outputs (GPIODIR) */
HWREG(GPIO_PORTF_BASE + GPIO_O_DIR) |= 0x0E; // 0b00001110
/* Step 3: Enable digital function on PF1, PF2, PF3 (GPIODEN) */
HWREG(GPIO_PORTF_BASE + GPIO_O_DEN) |= 0x0E;
/* Step 4: Drive PF1 HIGH (Red LED ON) using address trick */
HWREG(GPIO_PORTF_BASE + (0x02 << 2)) = 0x02; // only PF1 affected
/* Step 5: Drive PF1 LOW */
HWREG(GPIO_PORTF_BASE + (0x02 << 2)) = 0x00;
Unlocking PF0 (SW2 / NMI pin)
PF0 is multiplexed with the NMI signal and is write-protected by the commit register:
/* Unlock GPIOLOCK with the magic key */
HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = 0x4C4F434B; // "LOCK"
/* Allow commit on PF0 */
HWREG(GPIO_PORTF_BASE + GPIO_O_CR) |= GPIO_PIN_0;
/* Now configure PF0 as input with pull-up */
HWREG(GPIO_PORTF_BASE + GPIO_O_DIR) &= ~GPIO_PIN_0;
HWREG(GPIO_PORTF_BASE + GPIO_O_PUR) |= GPIO_PIN_0;
HWREG(GPIO_PORTF_BASE + GPIO_O_DEN) |= GPIO_PIN_0;
/* Re-lock */
HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = 0;
GPIO Interrupt ISR
The ISR must clear the interrupt flag before returning, otherwise it re-fires immediately:
void GPIOF_Handler(void)
{
uint32_t ui32Status = GPIOIntStatus(GPIO_PORTF_BASE, true);
GPIOIntClear(GPIO_PORTF_BASE, ui32Status); // MUST clear first
if (ui32Status & GPIO_INT_PIN_4)
{
/* SW1 was pressed — toggle green LED */
uint8_t current = GPIOPinRead(GPIO_PORTF_BASE, GPIO_PIN_3);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3,
current ^ GPIO_PIN_3);
}
}
Edge Cases and Gotchas
- Clock must be enabled before any register access — reading GPIODIR before enabling the clock returns garbage.
- GPIODEN is 0 by default — forgetting to set it means digital I/O silently does nothing.
- Alternate function requires both
GPIOAFSELandGPIOPinConfigure()— setting AFSEL alone does not select which peripheral. - Open-drain mode (GPIOODR register) is needed for I2C lines.
- Drive strength options: 2 mA (default), 4 mA, 8 mA. High-current mode required for driving transistors.
Step-by-Step Example
/*
* gpio_led_button.c
* Press SW1 (PF4) to cycle through RGB LEDs on TM4C123GXL
* Board : TM4C123GXL EK LaunchPad
* SDK : TivaWare_C_Series-2.2.x
*/
#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_gpio.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "inc/tm4c123gh6pm.h" // IRQ numbers
/* LED pin masks */
#define LED_RED GPIO_PIN_1
#define LED_BLUE GPIO_PIN_2
#define LED_GREEN GPIO_PIN_3
#define SW1 GPIO_PIN_4
static volatile uint8_t g_ui8LedState = 0; // 0=Red, 1=Blue, 2=Green
void GPIOF_Handler(void);
int main(void)
{
/* 80 MHz system clock */
SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL |
SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);
/* Enable Port F clock */
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF));
/* Configure RGB LED pins as outputs */
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE,
LED_RED | LED_BLUE | LED_GREEN);
/* Configure SW1 (PF4) as input with internal pull-up */
GPIOPinTypeGPIOInput(GPIO_PORTF_BASE, SW1);
GPIOPadConfigSet(GPIO_PORTF_BASE, SW1,
GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
/* Configure SW1 to generate interrupt on falling edge */
GPIOIntTypeSet(GPIO_PORTF_BASE, SW1, GPIO_FALLING_EDGE);
GPIOIntEnable(GPIO_PORTF_BASE, GPIO_INT_PIN_4);
/* Enable Port F interrupt in NVIC */
IntEnable(INT_GPIOF);
IntMasterEnable();
/* Turn on Red LED to start */
GPIOPinWrite(GPIO_PORTF_BASE,
LED_RED | LED_BLUE | LED_GREEN, LED_RED);
while (1)
{
/* All work done in ISR; main just idles */
}
}
/* GPIO Port F interrupt service routine */
void GPIOF_Handler(void)
{
/* Read and clear the interrupt status */
uint32_t ui32Status = GPIOIntStatus(GPIO_PORTF_BASE, true);
GPIOIntClear(GPIO_PORTF_BASE, ui32Status);
if (ui32Status & GPIO_INT_PIN_4)
{
/* Advance to next LED colour */
g_ui8LedState = (g_ui8LedState + 1) % 3;
uint8_t ui8Led;
switch (g_ui8LedState)
{
case 0: ui8Led = LED_RED; break;
case 1: ui8Led = LED_BLUE; break;
default: ui8Led = LED_GREEN; break;
}
GPIOPinWrite(GPIO_PORTF_BASE,
LED_RED | LED_BLUE | LED_GREEN, ui8Led);
}
}
Summary
| Key Point | Details |
|---|---|
| GPIO ports | A, B, C, D, E, F (up to 43 pins) |
| RGB LEDs | PF1 = Red, PF2 = Blue, PF3 = Green |
| Buttons | PF4 = SW1, PF0 = SW2 (needs unlock) |
| Direction | GPIODIR: 1 = output, 0 = input |
| Digital enable | GPIODEN must be set for digital I/O |
| APB data trick | Address bits [9:2] act as write mask |
| Interrupt clear | GPIOICR must be written before returning from ISR |
| Pull resistors | GPIOPUR (pull-up), GPIOPDR (pull-down) |