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

前置条件

硬件设置

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
    

Basic Button Reading

#include "hal/hal.h"

/* Button pin */
#define BTN_PORT    HAL_GPIO_PORT_A
#define BTN_PIN     0

/* LED pin */
#define LED_PORT    HAL_GPIO_PORT_D
#define LED_PIN     12

/**
 * \brief           Initialize button GPIO
 */
static hal_status_t button_init(void) {
    hal_gpio_config_t config = {
        .direction = HAL_GPIO_DIR_INPUT,
        .pull = HAL_GPIO_PULL_DOWN,  /* Pull-down for active-high button */
        .output_mode = HAL_GPIO_OUTPUT_PP,  /* Not used for input */
        .speed = HAL_GPIO_SPEED_LOW,
        .init_level = HAL_GPIO_LEVEL_LOW
    };

    return hal_gpio_init(BTN_PORT, BTN_PIN, &config);
}

/**
 * \brief           Initialize LED GPIO
 */
static hal_status_t led_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
    };

    return hal_gpio_init(LED_PORT, LED_PIN, &config);
}

int main(void) {
    hal_init();
    button_init();
    led_init();

    while (1) {
        /* Read button state */
        hal_gpio_level_t button_state = hal_gpio_read(BTN_PORT, BTN_PIN);

        /* Control LED based on button */
        if (button_state == HAL_GPIO_LEVEL_HIGH) {
            hal_gpio_write(LED_PORT, LED_PIN, HAL_GPIO_LEVEL_HIGH);
        } else {
            hal_gpio_write(LED_PORT, LED_PIN, HAL_GPIO_LEVEL_LOW);
        }
    }

    return 0;
}

Key Points:

  • Use HAL_GPIO_DIR_INPUT for input pins

  • Configure pull resistors based on button type (pull-down for active-high, pull-up for active-low)

  • hal_gpio_read() returns HAL_GPIO_LEVEL_HIGH or HAL_GPIO_LEVEL_LOW

Button Debouncing

Mechanical buttons can "bounce" when pressed, causing multiple transitions. Here's how to debounce:

#define DEBOUNCE_DELAY_MS  50

/**
 * \brief           Read button with debouncing
 * \return          true if button is pressed (debounced)
 */
static bool button_read_debounced(void) {
    static uint32_t last_change_time = 0;
    static bool last_stable_state = false;

    /* Read current state */
    bool current_state = (hal_gpio_read(BTN_PORT, BTN_PIN) == HAL_GPIO_LEVEL_HIGH);

    /* Check if state changed */
    if (current_state != last_stable_state) {
        uint32_t now = hal_get_tick();

        /* Check if enough time has passed since last change */
        if ((now - last_change_time) >= DEBOUNCE_DELAY_MS) {
            last_stable_state = current_state;
            last_change_time = now;
        }
    }

    return last_stable_state;
}

/* Usage in main loop */
while (1) {
    if (button_read_debounced()) {
        hal_gpio_write(LED_PORT, LED_PIN, HAL_GPIO_LEVEL_HIGH);
    } else {
        hal_gpio_write(LED_PORT, LED_PIN, HAL_GPIO_LEVEL_LOW);
    }

    hal_delay_ms(10);  /* Small delay to reduce CPU usage */
}

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 transition

  • HAL_GPIO_IRQ_TRIGGER_FALLING: Trigger on high-to-low transition

  • HAL_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:

  1. Press the button to cycle through modes

  2. Mode 0: All LEDs off

  3. Mode 1: Single LED blinks

  4. Mode 2: Knight Rider pattern

  5. Mode 3: Binary counter (0-15)

最佳实践

  1. Always Initialize: Call hal_init() before using GPIO functions

  2. Check Return Values: Always check return values from HAL functions

  3. Use Appropriate Pull Resistors: Configure pull-up/down based on your circuit

  4. Debounce Inputs: Always debounce mechanical switches

  5. Keep Interrupts Short: Minimize work in interrupt handlers

  6. Use Appropriate Speed: Don't use high-speed GPIO for slow signals (wastes power)

  7. 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.

下一步