Home Wiki Electricity & Electrons GPIO and Digital Signals: Controlling the Outside World
Electricity & Electrons

GPIO and Digital Signals: Controlling the Outside World

What Is GPIO? General-Purpose Input/Output

GPIO stands for General-Purpose Input/Output. These are the physical pins on a microcontroller that connect the digital world of firmware to the physical world of sensors, relays, motors, and indicators. Every embedded project starts with GPIO.

Each GPIO pin can be configured as either an input (reading external signals) or an output (driving external devices). On an STM32F407, there are over 80 GPIO pins organized into ports (GPIOA, GPIOB, GPIOC, etc.), each port containing 16 pins. In an industrial setting, GPIO pins control everything from indicator LEDs on a control panel to relay coils that switch high-power equipment.

A digital signal has only two states: HIGH (typically 3.3V on modern MCUs) and LOW (0V / ground). This binary nature makes digital I/O robust against electrical noise — the system only needs to distinguish between two voltage levels, not measure a precise analog value.

Configuring a Pin as Output: Driving LEDs and Relays

To use a GPIO pin as an output, you configure its mode, speed, and output type. The pin then drives the voltage on the physical line based on your firmware commands.

// STM32 HAL: Configure PA5 as push-pull output
__HAL_RCC_GPIOA_CLK_ENABLE();

GPIO_InitTypeDef gpio = {0};
gpio.Pin   = GPIO_PIN_5;
gpio.Mode  = GPIO_MODE_OUTPUT_PP;   // Push-pull: drives both HIGH and LOW
gpio.Pull  = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &gpio);

// Turn on
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
// Turn off
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);

In industrial applications, a GPIO output rarely drives a load directly. Instead, it controls a transistor or relay driver that switches the high-power circuit. A 3.3V GPIO pin cannot power a 24V industrial relay coil, but it can switch a MOSFET or optocoupler that does. Outputs can be push-pull (actively driving HIGH and LOW) or open-drain (only pulling LOW, requiring external pull-up) for buses like I2C.

Configuring a Pin as Input: Reading Buttons and Sensors

Input pins read the voltage level on the physical line and report it as a digital value in firmware. This is how the MCU detects button presses, limit switch activations, and binary sensor outputs.

// STM32 HAL: Configure PC13 as input (user button on many dev boards)
__HAL_RCC_GPIOC_CLK_ENABLE();

GPIO_InitTypeDef gpio = {0};
gpio.Pin  = GPIO_PIN_13;
gpio.Mode = GPIO_MODE_INPUT;
gpio.Pull = GPIO_PULLUP;   // Internal pull-up resistor
HAL_GPIO_Init(GPIOC, &gpio);

// Read button state
GPIO_PinState state = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13);
if (state == GPIO_PIN_RESET) {
    // Button is pressed (active-low with pull-up)
}

Rust with embedded-hal

The embedded-hal crate provides hardware abstraction traits that work across MCU families:

use embedded_hal::digital::InputPin;

fn check_limit_switch<P: InputPin>(pin: &P) -> bool {
    pin.is_low().unwrap_or(false)
}

Pull-Up and Pull-Down Resistors

When a GPIO input pin is not connected to a definite HIGH or LOW voltage, it floats — picking up random electrical noise and reporting unpredictable values. This is a critical problem in industrial environments full of electromagnetic interference from motors and inverters.

Pull-up resistors connect the pin to VCC through a high-value resistor (typically 10K ohms), ensuring the default state is HIGH. When a button or switch connects the pin to ground, it reads LOW. This is called active-low logic and is the standard in industrial controls.

Pull-down resistors do the opposite: connecting the pin to ground so the default state is LOW. The signal goes HIGH when activated.

Most modern MCUs have configurable internal pull-up and pull-down resistors, eliminating the need for external components in many cases. However, for long cable runs in a factory (over 30 cm), external pull-ups with lower resistance values are recommended for noise immunity.

The Bouncing Problem: Debouncing

Mechanical switches and buttons do not make clean transitions between states. When a button is pressed, the metal contacts physically bounce, creating multiple rapid transitions between HIGH and LOW before settling. A single button press might generate 5 to 50 false transitions within a few milliseconds.

Without debouncing, firmware sees multiple press events from a single physical press. The solution is a software filter:

#define DEBOUNCE_MS 50

static uint32_t last_press_time = 0;

bool is_button_pressed(void) {
    if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET) {
        uint32_t now = HAL_GetTick();
        if (now - last_press_time > DEBOUNCE_MS) {
            last_press_time = now;
            return true;
        }
    }
    return false;
}

The algorithm is simple: ignore any state change that occurs within 50 ms of the last accepted change. This filters out the mechanical bouncing while still detecting legitimate presses.

Practical Example: Driving a Cooling Fan With a Binary Temperature Sensor

This example uses a KSD9700 bimetallic thermostat (binary open/closed output) to control a cooling fan via relay. It combines input reading, debouncing, and output driving.

#include "stm32f4xx_hal.h"

#define TEMP_SENSOR_PIN  GPIO_PIN_0  // PA0: thermostat input
#define FAN_RELAY_PIN    GPIO_PIN_1  // PA1: relay output
#define DEBOUNCE_MS      200

static uint32_t last_change = 0;
static GPIO_PinState fan_state = GPIO_PIN_RESET;

void fan_control_loop(void) {
    GPIO_PinState sensor = HAL_GPIO_ReadPin(GPIOA, TEMP_SENSOR_PIN);
    uint32_t now = HAL_GetTick();
    if (sensor != fan_state && (now - last_change > DEBOUNCE_MS)) {
        fan_state = sensor;
        last_change = now;
        HAL_GPIO_WritePin(GPIOA, FAN_RELAY_PIN,
            fan_state == GPIO_PIN_SET ? GPIO_PIN_SET : GPIO_PIN_RESET);
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    // GPIO init omitted for brevity (same pattern as above)
    while (1) { fan_control_loop(); HAL_Delay(10); }
}

This pattern — read a binary sensor, debounce it, drive an output — is the foundation of countless industrial control systems.

Summary

GPIO is the fundamental interface between a microcontroller and the physical world. Pins configured as outputs drive LEDs, relays, and transistors, while input pins read buttons, switches, and binary sensors. Pull-up and pull-down resistors prevent floating inputs, and debouncing algorithms filter out mechanical switch noise. In industrial environments, GPIO rarely drives loads directly — transistors, optocouplers, and relay drivers bridge the gap between 3.3V logic and 24V industrial power. The next lesson covers analog-to-digital conversion, where we move beyond simple HIGH/LOW signals to measure precise voltage levels from industrial sensors.

GPIO digital-IO LED relay pull-up pull-down المداخل الرقمية المخارج الرقمية المرحل المقاومة الرافعة التحكم الأطراف