GPIO Control 教程¶
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.
学习目标¶
完成本教程后,您将能够:
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
前置条件¶
Completed 您的第一个 Nexus 应用程序 tutorial
STM32F4 Discovery 板或兼容硬件
Basic understanding of digital I/O concepts
硬件设置¶
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.
备注
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)
最佳实践¶
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.
下一步¶
UART Communication 教程 - Add serial communication
使用 OSAL 的多任务 - Use OSAL for multi-tasking
硬件抽象层 (HAL) - Explore more HAL features
STM32F4 平台 Guide - Platform-specific GPIO details