Skip to content

05. GPIO Control - Controlling Hardware Pins

GPIO (General Purpose Input/Output) pins are the Raspberry Pi's interface to the physical world. Let's learn how to control LEDs, read buttons, and interact with external hardware.

What is GPIO?

The Raspberry Pi 4B has 40 GPIO pins on its header. These pins can be configured as: - Output: Control LEDs, relays, motors - Input: Read buttons, sensors - Alternate Functions: UART, SPI, I2C, PWM

GPIO Pin Layout (Raspberry Pi 4B)

1
2
3
4
5
6
7
3.3V  (1) (2)  5V
GPIO2 (3) (4)  5V
GPIO3 (5) (6)  GND
GPIO4 (7) (8)  GPIO14
GND   (9) (10) GPIO15
...
GPIO21 (40)

Voltage Levels (Water Pressure)

Raspberry Pi GPIO operates at 3.3V.

Think of Voltage as Water Pressure: - 5V: High pressure fire hose (Arduino/USB). - 3.3V: Garden hose (Raspberry Pi GPIO).

If you connect a 5V device directly to a 3.3V pin, you burst the pipe (fry the chip). Always check voltages!

GPIO Register Overview

BCM2711 GPIO Registers

Register Offset Purpose
GPFSEL0-5 0x00-0x14 Function Select (input/output/alt)
GPSET0-1 0x1C-0x20 Set Pin High
GPCLR0-1 0x28-0x2C Clear Pin Low
GPLEV0-1 0x34-0x38 Read Pin Level
GPIO_PUP_PDN_CNTRL 0xE4+ Pull-up/Pull-down Control (RPi 4)

Base address: 0xFE200000 (RPi 4)

Function Select Register (GPFSEL)

Each pin uses 3 bits to select its function:

Value Function
000 Input
001 Output
100 Alt Function 0
101 Alt Function 1
... ...

Example: To set GPIO 21 as output: - GPIO 21 is in GPFSEL2 (pins 20-29) - Bit offset: (pin % 10) * 3 -> (21 % 10) * 3 = 1 * 3 = 3

We need to treat the 32-bit register like a row of mailboxes, where every 3 mailboxes belong to one pin.

block-beta
    columns 10
    block:Bits
        bits["..."]
        b29["Pin 29"]
        b28["Pin 28"]
        b27["Pin 27"]
        b26["Pin 26"]
        b25["Pin 25"]
        b24["Pin 24"]
        b23["Pin 23"]
        b22["Pin 22"]
        b21["Pin 21<br>(Bits 5-3)"]
        b20["Pin 20"]
    end

To configure GPIO 21, we only touch bits 3, 4, and 5.

  1. Clear: Set bits 3-5 to 000 (wipe previous setting).
  2. Set: Write 001 (Output) to these bits.

Implementing the GPIO Driver

Header File: include/gpio.h

typedef enum {
    GPIO_INPUT  = 0,
    GPIO_OUTPUT = 1,
    GPIO_ALT0   = 4,
    GPIO_ALT1   = 5,
    // ...
} gpio_function_t;

void gpio_set_function(uint32_t pin, gpio_function_t func);
void gpio_set(uint32_t pin);       // Set pin HIGH
void gpio_clear(uint32_t pin);     // Set pin LOW
uint32_t gpio_read(uint32_t pin);  // Read pin level

Driver Implementation: drivers/gpio.c

1. Setting Pin Function

void gpio_set_function(uint32_t pin, gpio_function_t func) {
    if (pin > 53) return;

    uint32_t reg_index = pin / 10;      // Which GPFSEL register
    uint32_t bit_offset = (pin % 10) * 3; // Bit position

    volatile uint32_t* gpfsel = GPFSEL0 + reg_index;

    uint32_t value = *gpfsel;
    value &= ~(7 << bit_offset);   // Clear 3 bits
    value |= (func << bit_offset);  // Set function
    *gpfsel = value;
}

2. Setting Pin High/Low

void gpio_set(uint32_t pin) {
    if (pin < 32) {
        *GPSET0 = (1 << pin);
    } else {
        *GPSET1 = (1 << (pin - 32));
    }
}

void gpio_clear(uint32_t pin) {
    if (pin < 32) {
        *GPCLR0 = (1 << pin);
    } else {
        *GPCLR1 = (1 << (pin - 32));
    }
}

Write-Only Registers

GPSET and GPCLR are write-only. Write a 1 to the bit position to set/clear that pin.

3. Reading Pin State

1
2
3
4
5
6
7
uint32_t gpio_read(uint32_t pin) {
    if (pin < 32) {
        return (*GPLEV0 >> pin) & 1;
    } else {
        return (*GPLEV1 >> (pin - 32)) & 1;
    }
}

LED Blinking Demo

Hardware Setup

Connect an LED to GPIO 21 (Pin 40):

graph LR
    GPIO["GPIO 21 (Pin 40)"] -- "+" --> R["Result (220Ω)"]
    R --> LED["LED Anode (+)"]
    LED -- "-" --> GND["GND (Pin 39)"]

Software: kernel/main.c

#include "gpio.h"
#include "timer.h"

#define LED_PIN 21

void kernel_main(void) {
    gpio_init();
    timer_init();

    gpio_set_function(LED_PIN, GPIO_OUTPUT);

    int state = 0;
    while(1) {
        if (state) {
            gpio_set(LED_PIN);   // LED ON
        } else {
            gpio_clear(LED_PIN); // LED OFF
        }

        state = !state;
        timer_sleep(1000);  // 1 second
    }
}

Building and Testing

1. Build

cd simpian-os/build
make

2. Deploy

Copy kernel8.img to SD card (with firmware files).

3. Connect Hardware

  • LED on GPIO 21 (Pin 40)
  • Resistor (220Ω - 1kΩ)
  • Ground (Pin 39)

4. Power On

The LED should blink every second!

Expected Serial Output

1
2
3
4
5
6
7
8
9
Raspberry Pi OS - GPIO Demo
Blinking LED on GPIO 21 (Pin 40)
Connect LED: Pin 40 (+) -> Resistor -> Pin 39 (GND)

LED ON
LED OFF
LED ON
LED OFF
...

Reading Button Input (Bonus)

Here's how to read a button on GPIO 16:

#define BUTTON_PIN 16

gpio_set_function(BUTTON_PIN, GPIO_INPUT);
gpio_set_pull(BUTTON_PIN, GPIO_PULL_UP);

while(1) {
    if (gpio_read(BUTTON_PIN) == 0) {  // Button pressed (active low)
        uart_puts("Button pressed!\n");
        timer_sleep(200);  // Debounce delay
    }
}

Why Pull-up? (The Floating Problem)

Imagine a switch connected to a pin. - Pressed: Connected to GND (0V). - Released: Connected to... nothing?

If connected to "nothing" (floating), the pin acts like an antenna, picking up random noise (0, 1, 0, 1...).

The Solution: Pull-up Resistor (The Spring)

Think of the value 1 (High) as "Up" and 0 (Low) as "Down". A Pull-up Resistor is like a weak spring pulling the pin Up (1).

  • Button Released: The spring pulls the pin Up. Read: 1.
  • Button Pressed: You use your strength to pull it Down to GND. Read: 0.
graph TD
    VCC["3.3V (VCC)"] -- "Weak Spring (Resistor)" --> Pin["GPIO Pin"]
    Pin -- "Button (Open)" --> GND["GND"]

    style Pin fill:#f9f,stroke:#333
    style VCC fill:#dfd,stroke:#333

Complete Source Code

Troubleshooting

Problem Solution
LED doesn't blink Check wiring (anode/cathode)
LED always on/off Verify pin number (GPIO 21 = Pin 40)
No serial output UART still working? Check baud rate
Dim LED Resistor too large, try 220Ω

GPIO Safety Tips

  1. Never exceed 3.3V on any GPIO pin
  2. Current limit: Max ~16mA per pin, ~50mA total
  3. Use resistors with LEDs (recommended: 220Ω-1kΩ)
  4. Short circuit protection: None! Be careful

What's Next?

Now that we can control GPIO, the next article covers Interrupts and Exception Handling, which will allow us to: - Respond to button presses instantly (no polling) - Handle timer interrupts for multitasking - Catch and handle CPU exceptions (e.g., data aborts)

Stay tuned!