GPIO Control Tutorial¶
This tutorial teaches you advanced GPIO control techniques using the Nexus HAL. You’ll learn how to read inputs, control outputs, handle interrupts, and implement debouncing.
Learning Objectives¶
By the end of this tutorial, you will:
Configure GPIO pins as inputs and outputs
Read button states with proper debouncing
Use GPIO interrupts for event-driven programming
Control multiple LEDs with patterns
Understand GPIO electrical characteristics
Prerequisites¶
Completed Your First Nexus Application tutorial
STM32F4 Discovery board or compatible hardware
Basic understanding of digital I/O concepts
Hardware Setup¶
For this tutorial, you’ll use:
Outputs (LEDs on STM32F4 Discovery):
PD12: Green LED
PD13: Orange LED
PD14: Red LED
PD15: Blue LED
Input (User Button):
PA0: User button (active high)
No additional wiring is required for the STM32F4 Discovery board.
Part 1: Reading GPIO Inputs¶
Let’s start by reading the user button state.
GPIO Input Workflow¶
The following diagram shows the workflow for reading GPIO inputs and controlling outputs:
flowchart TD
START([Start]) --> INIT_HAL[Initialize HAL]
INIT_HAL --> INIT_BTN[Configure Button GPIO as Input]
INIT_BTN --> INIT_LED[Configure LED GPIO as Output]
INIT_LED --> LOOP{Main Loop}
LOOP --> READ[Read Button State]
READ --> CHECK{Button Pressed?}
CHECK -->|Yes| LED_ON[Turn LED On]
CHECK -->|No| LED_OFF[Turn LED Off]
LED_ON --> DELAY[Delay/Debounce]
LED_OFF --> DELAY
DELAY --> LOOP
style START fill:#e1f5ff
style INIT_HAL fill:#fff4e1
style INIT_BTN fill:#ffe1f5
style INIT_LED fill:#ffe1f5
style READ fill:#e1ffe1
style LED_ON fill:#ffe1e1
style LED_OFF fill:#ffe1e1
Edge Detection¶
Detect button press and release events:
/**
* \brief Detect button edges
* \param[out] pressed: Set to true on press event
* \param[out] released: Set to true on release event
*/
static void button_detect_edges(bool* pressed, bool* released) {
static bool last_state = false;
bool current_state = button_read_debounced();
*pressed = false;
*released = false;
if (current_state && !last_state) {
*pressed = true; /* Rising edge */
} else if (!current_state && last_state) {
*released = true; /* Falling edge */
}
last_state = current_state;
}
/* Usage */
while (1) {
bool pressed, released;
button_detect_edges(&pressed, &released);
if (pressed) {
/* Button was just pressed */
hal_gpio_toggle(LED_PORT, LED_PIN);
}
hal_delay_ms(10);
}
Part 2: Advanced Output Control¶
LED Patterns¶
Create complex LED patterns:
/* LED array for easy access */
typedef struct {
hal_gpio_port_t port;
hal_gpio_pin_t pin;
} led_t;
static const led_t leds[] = {
{HAL_GPIO_PORT_D, 12}, /* Green */
{HAL_GPIO_PORT_D, 13}, /* Orange */
{HAL_GPIO_PORT_D, 14}, /* Red */
{HAL_GPIO_PORT_D, 15} /* Blue */
};
#define NUM_LEDS (sizeof(leds) / sizeof(leds[0]))
/**
* \brief Initialize all LEDs
*/
static hal_status_t leds_init(void) {
hal_gpio_config_t config = {
.direction = HAL_GPIO_DIR_OUTPUT,
.pull = HAL_GPIO_PULL_NONE,
.output_mode = HAL_GPIO_OUTPUT_PP,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
for (size_t i = 0; i < NUM_LEDS; i++) {
if (hal_gpio_init(leds[i].port, leds[i].pin, &config) != HAL_OK) {
return HAL_ERR_FAIL;
}
}
return HAL_OK;
}
/**
* \brief Set LED state
*/
static void led_set(size_t index, bool on) {
if (index < NUM_LEDS) {
hal_gpio_write(leds[index].port, leds[index].pin,
on ? HAL_GPIO_LEVEL_HIGH : HAL_GPIO_LEVEL_LOW);
}
}
/**
* \brief Turn off all LEDs
*/
static void leds_all_off(void) {
for (size_t i = 0; i < NUM_LEDS; i++) {
led_set(i, false);
}
}
/**
* \brief Knight Rider pattern
*/
static void pattern_knight_rider(void) {
/* Forward */
for (size_t i = 0; i < NUM_LEDS; i++) {
led_set(i, true);
hal_delay_ms(100);
led_set(i, false);
}
/* Backward */
for (int i = NUM_LEDS - 1; i >= 0; i--) {
led_set(i, true);
hal_delay_ms(100);
led_set(i, false);
}
}
/**
* \brief Binary counter pattern
*/
static void pattern_binary_counter(void) {
for (uint8_t count = 0; count < 16; count++) {
for (size_t i = 0; i < NUM_LEDS; i++) {
led_set(i, (count & (1 << i)) != 0);
}
hal_delay_ms(500);
}
}
PWM-like LED Dimming¶
Create software PWM for LED dimming:
/**
* \brief Set LED brightness (0-100%)
* \param[in] index: LED index
* \param[in] brightness: Brightness percentage (0-100)
*/
static void led_set_brightness(size_t index, uint8_t brightness) {
if (index >= NUM_LEDS || brightness > 100) {
return;
}
/* Software PWM: on_time / period = brightness / 100 */
uint32_t period_us = 1000; /* 1ms period = 1kHz */
uint32_t on_time_us = (period_us * brightness) / 100;
uint32_t off_time_us = period_us - on_time_us;
if (on_time_us > 0) {
hal_gpio_write(leds[index].port, leds[index].pin, HAL_GPIO_LEVEL_HIGH);
/* Note: hal_delay_us() would be needed for microsecond delays */
/* For now, use millisecond approximation */
if (on_time_us >= 1000) {
hal_delay_ms(on_time_us / 1000);
}
}
if (off_time_us > 0) {
hal_gpio_write(leds[index].port, leds[index].pin, HAL_GPIO_LEVEL_LOW);
if (off_time_us >= 1000) {
hal_delay_ms(off_time_us / 1000);
}
}
}
/**
* \brief Fade LED in and out
*/
static void pattern_fade(size_t led_index) {
/* Fade in */
for (uint8_t brightness = 0; brightness <= 100; brightness += 5) {
for (int i = 0; i < 10; i++) { /* Repeat for smooth appearance */
led_set_brightness(led_index, brightness);
}
}
/* Fade out */
for (uint8_t brightness = 100; brightness > 0; brightness -= 5) {
for (int i = 0; i < 10; i++) {
led_set_brightness(led_index, brightness);
}
}
}
Part 3: GPIO Interrupts¶
GPIO interrupts allow event-driven programming without polling.
Note
GPIO interrupt support depends on the platform. Check your platform guide for availability.
Configuring GPIO Interrupts¶
#include "hal/hal.h"
static volatile bool button_pressed = false;
/**
* \brief GPIO interrupt callback
* \param[in] port: GPIO port
* \param[in] pin: GPIO pin
*/
static void gpio_interrupt_callback(hal_gpio_port_t port, hal_gpio_pin_t pin) {
if (port == BTN_PORT && pin == BTN_PIN) {
button_pressed = true;
}
}
/**
* \brief Initialize button with interrupt
*/
static hal_status_t button_init_interrupt(void) {
hal_gpio_config_t config = {
.direction = HAL_GPIO_DIR_INPUT,
.pull = HAL_GPIO_PULL_DOWN,
.output_mode = HAL_GPIO_OUTPUT_PP,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
/* Initialize GPIO */
if (hal_gpio_init(BTN_PORT, BTN_PIN, &config) != HAL_OK) {
return HAL_ERR_FAIL;
}
/* Configure interrupt on rising edge */
hal_gpio_irq_config_t irq_config = {
.trigger = HAL_GPIO_IRQ_TRIGGER_RISING,
.callback = gpio_interrupt_callback
};
return hal_gpio_irq_init(BTN_PORT, BTN_PIN, &irq_config);
}
int main(void) {
hal_init();
button_init_interrupt();
leds_init();
/* Enable GPIO interrupts */
hal_gpio_irq_enable(BTN_PORT, BTN_PIN);
while (1) {
/* Check flag set by interrupt */
if (button_pressed) {
button_pressed = false; /* Clear flag */
/* Toggle LED */
hal_gpio_toggle(LED_PORT, LED_PIN);
}
/* CPU can sleep here to save power */
hal_delay_ms(10);
}
return 0;
}
Interrupt Triggers:
HAL_GPIO_IRQ_TRIGGER_RISING: Trigger on low-to-high transitionHAL_GPIO_IRQ_TRIGGER_FALLING: Trigger on high-to-low transitionHAL_GPIO_IRQ_TRIGGER_BOTH: Trigger on any edge
Important Notes:
Keep interrupt handlers short and fast
Use volatile for variables shared between interrupt and main code
Avoid calling blocking functions in interrupt handlers
Consider using a flag to signal the main loop
Part 4: Complete Example¶
Here’s a complete example combining all concepts:
/**
* \file gpio_demo.c
* \brief GPIO Control Demo
*/
#include "hal/hal.h"
#include <stdbool.h>
/*-----------------------------------------------------------------------*/
/* Configuration */
/*-----------------------------------------------------------------------*/
#define BTN_PORT HAL_GPIO_PORT_A
#define BTN_PIN 0
#define DEBOUNCE_DELAY_MS 50
typedef struct {
hal_gpio_port_t port;
hal_gpio_pin_t pin;
} led_t;
static const led_t leds[] = {
{HAL_GPIO_PORT_D, 12}, /* Green */
{HAL_GPIO_PORT_D, 13}, /* Orange */
{HAL_GPIO_PORT_D, 14}, /* Red */
{HAL_GPIO_PORT_D, 15} /* Blue */
};
#define NUM_LEDS (sizeof(leds) / sizeof(leds[0]))
/*-----------------------------------------------------------------------*/
/* State Machine */
/*-----------------------------------------------------------------------*/
typedef enum {
MODE_OFF,
MODE_SINGLE_BLINK,
MODE_KNIGHT_RIDER,
MODE_BINARY_COUNTER,
MODE_MAX
} led_mode_t;
static led_mode_t current_mode = MODE_OFF;
/*-----------------------------------------------------------------------*/
/* Helper Functions */
/*-----------------------------------------------------------------------*/
static hal_status_t leds_init(void) {
hal_gpio_config_t config = {
.direction = HAL_GPIO_DIR_OUTPUT,
.pull = HAL_GPIO_PULL_NONE,
.output_mode = HAL_GPIO_OUTPUT_PP,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
for (size_t i = 0; i < NUM_LEDS; i++) {
if (hal_gpio_init(leds[i].port, leds[i].pin, &config) != HAL_OK) {
return HAL_ERR_FAIL;
}
}
return HAL_OK;
}
static hal_status_t button_init(void) {
hal_gpio_config_t config = {
.direction = HAL_GPIO_DIR_INPUT,
.pull = HAL_GPIO_PULL_DOWN,
.output_mode = HAL_GPIO_OUTPUT_PP,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
return hal_gpio_init(BTN_PORT, BTN_PIN, &config);
}
static void led_set(size_t index, bool on) {
if (index < NUM_LEDS) {
hal_gpio_write(leds[index].port, leds[index].pin,
on ? HAL_GPIO_LEVEL_HIGH : HAL_GPIO_LEVEL_LOW);
}
}
static void leds_all_off(void) {
for (size_t i = 0; i < NUM_LEDS; i++) {
led_set(i, false);
}
}
static bool button_read_debounced(void) {
static uint32_t last_change_time = 0;
static bool last_stable_state = false;
bool current_state = (hal_gpio_read(BTN_PORT, BTN_PIN) == HAL_GPIO_LEVEL_HIGH);
if (current_state != last_stable_state) {
uint32_t now = hal_get_tick();
if ((now - last_change_time) >= DEBOUNCE_DELAY_MS) {
last_stable_state = current_state;
last_change_time = now;
}
}
return last_stable_state;
}
static bool button_pressed_event(void) {
static bool last_state = false;
bool current_state = button_read_debounced();
bool pressed = current_state && !last_state;
last_state = current_state;
return pressed;
}
/*-----------------------------------------------------------------------*/
/* LED Patterns */
/*-----------------------------------------------------------------------*/
static void mode_single_blink(void) {
led_set(0, true);
hal_delay_ms(500);
led_set(0, false);
hal_delay_ms(500);
}
static void mode_knight_rider(void) {
for (size_t i = 0; i < NUM_LEDS; i++) {
led_set(i, true);
hal_delay_ms(100);
led_set(i, false);
}
for (int i = NUM_LEDS - 1; i >= 0; i--) {
led_set(i, true);
hal_delay_ms(100);
led_set(i, false);
}
}
static void mode_binary_counter(void) {
static uint8_t count = 0;
for (size_t i = 0; i < NUM_LEDS; i++) {
led_set(i, (count & (1 << i)) != 0);
}
count++;
hal_delay_ms(500);
}
/*-----------------------------------------------------------------------*/
/* Main Function */
/*-----------------------------------------------------------------------*/
int main(void) {
/* Initialize */
hal_init();
leds_init();
button_init();
/* Main loop */
while (1) {
/* Check for button press to change mode */
if (button_pressed_event()) {
current_mode = (current_mode + 1) % MODE_MAX;
leds_all_off();
}
/* Execute current mode */
switch (current_mode) {
case MODE_OFF:
leds_all_off();
hal_delay_ms(100);
break;
case MODE_SINGLE_BLINK:
mode_single_blink();
break;
case MODE_KNIGHT_RIDER:
mode_knight_rider();
break;
case MODE_BINARY_COUNTER:
mode_binary_counter();
break;
default:
current_mode = MODE_OFF;
break;
}
}
return 0;
}
How It Works:
Press the button to cycle through modes
Mode 0: All LEDs off
Mode 1: Single LED blinks
Mode 2: Knight Rider pattern
Mode 3: Binary counter (0-15)
Best Practices¶
Always Initialize: Call
hal_init()before using GPIO functionsCheck Return Values: Always check return values from HAL functions
Use Appropriate Pull Resistors: Configure pull-up/down based on your circuit
Debounce Inputs: Always debounce mechanical switches
Keep Interrupts Short: Minimize work in interrupt handlers
Use Appropriate Speed: Don’t use high-speed GPIO for slow signals (wastes power)
Document Pin Assignments: Clearly document which pins are used for what
Common Pitfalls¶
Forgetting Pull Resistors:
Floating inputs can cause erratic behavior. Always configure pull resistors for inputs.
No Debouncing:
Mechanical switches bounce. Always implement debouncing for reliable operation.
Blocking in Interrupts:
Never call hal_delay_ms() or other blocking functions in interrupt handlers.
Wrong Trigger Type:
Choose the correct interrupt trigger (rising, falling, or both) for your application.
Next Steps¶
UART Communication Tutorial - Add serial communication
Multi-Tasking with OSAL - Use OSAL for multi-tasking
Hardware Abstraction Layer (HAL) - Explore more HAL features
STM32F4 Platform Guide - Platform-specific GPIO details