掌握GPIO基于GD32F407VE的天空星的输入输出控制
掌握GPIO基于GD32F407VE的天空星的输入输出控制
1. GPIO输出模式编程实践
1.1 LED控制基础
以GD32F407VE天空星为例,实现LED闪烁功能:
硬件连接:
- LED正极 → PB2(通过限流电阻)
- LED负极 → GND
代码实现步骤:
/*********************************************************** @brief 输出配置 * @param rcu 时钟端口 RCU_GPIOx(x = A,B,C,D,E,F,G,H,I)* @param port 引脚端口 GPIOx(x = A,B,C,D,E,F,G,H,I)* @param pin 引脚 GPIO_PIN_x(x=0..15)* @param otype 输出模式 GPIO_OTYPE_PP 推挽; GPIO_OTYPE_OD 开漏* @return **********************************************************/
void GPIO_output(rcu_periph_enum rcu, uint32_t port, uint32_t pin, uint8_t otype) {// 时钟初始化rcu_periph_clock_enable(rcu);// GPIO模式:输出gpio_mode_set(port, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, pin);// 输出选项设置gpio_output_options_set(port, otype, GPIO_OSPEED_MAX, pin);
}void GPIO_config() {// =============== PB2 推挽输出GPIO_output(RCU_GPIOB, GPIOB, GPIO_PIN_2, GPIO_OTYPE_PP);// ============== 底板 有别的引脚也要配置输出
}
1.2 输出模式选择技巧
推挽输出 vs 开漏输出:
模式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
推挽输出 | 驱动能力强、高低电平都有主动驱动 | 不能线与连接 | LED、继电器、电机控制 |
开漏输出 | 支持线与、电平转换灵活 | 需要外接上拉电阻、高电平驱动能力弱 | I²C总线、多设备总线 |
2. GPIO输入模式编程实践
2.1 按键检测基础
2.1.1 核心板按键(PA0)
电路特性:
- 按下时:高电平(连接VCC)
- 抬起时:低电平(外部下拉电阻)
/*********************************************************** @brief 输出配置 * @param rcu 时钟端口 RCU_GPIOx(x = A,B,C,D,E,F,G,H,I)* @param port 引脚端口 GPIOx(x = A,B,C,D,E,F,G,H,I)* @param pin 引脚 GPIO_PIN_x(x=0..15)* @param otype 输出模式 GPIO_OTYPE_PP 推挽; GPIO_OTYPE_OD 开漏* @return **********************************************************/
void GPIO_output(rcu_periph_enum rcu, uint32_t port, uint32_t pin, uint8_t otype) {// 时钟初始化rcu_periph_clock_enable(rcu);// GPIO模式:输出gpio_mode_set(port, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, pin);// 输出选项设置gpio_output_options_set(port, otype, GPIO_OSPEED_MAX, pin);
}/*********************************************************** @brief 输入配置 * @param rcu 时钟端口 RCU_GPIOx(x = A,B,C,D,E,F,G,H,I)* @param port 引脚端口 GPIOx(x = A,B,C,D,E,F,G,H,I)* @param pin 引脚 GPIO_PIN_x(x=0..15)* @param pull_up_down 上下拉模式 GPIO_PUPD_NONE 浮空GPIO_PUPD_PULLUP 上拉GPIO_PUPD_PULLDOWN 下拉* @return **********************************************************/
void GPIO_input(rcu_periph_enum rcu, uint32_t port, uint32_t pin, uint32_t pull_up_down) {// 时钟初始化rcu_periph_clock_enable(rcu);// GPIO模式:输入gpio_mode_set(port, GPIO_MODE_INPUT, pull_up_down, pin);
}void GPIO_config() {// =============== PB2 推挽输出GPIO_output(RCU_GPIOB, GPIOB, GPIO_PIN_2, GPIO_OTYPE_PP);// ============== PA0 浮空输入 外围电路已经下拉了,芯片内部浮空即可GPIO_input(RCU_GPIOA, GPIOA, GPIO_PIN_0, GPIO_PUPD_NONE);
}#define KEY0 GPIOA, GPIO_PIN_0
#define DOWN0 SET
#define UP0 RESET
#define PB2 GPIOB, GPIO_PIN_2// 上一步状态
FlagStatus last_state0 = UP0; // 有分号int main(void) {GPIO_config();// 系统滴答定时器初始化systick_config();while(1) {// ==================核心板 PA0=================FlagStatus cur_state0 = gpio_input_bit_get(KEY0);if (last_state0 == UP0 && cur_state0 == DOWN0) {// 上一次是抬起,当前是按下,按下才有效last_state0 = DOWN0; // 保存状态gpio_bit_set(PB2); // 高电平亮} else if (last_state0 == DOWN0 && cur_state0 == UP0) {// 上一次是按下,当前是抬起,抬起才有效last_state0 = UP0; // 保存状态gpio_bit_reset(PB2); // 低电平灭}delay_1ms(20);}}
2.1.2 扩展板按键(PC0)
电路特性:
- 按下时:低电平(接地)
- 抬起时:高电平(内部上拉)
// 扩展板按键初始化
void ext_key_init(void)
{// 使能GPIOC时钟rcu_periph_clock_enable(RCU_GPIOC);// 配置PC0为上拉输入gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN_0);
}// 扩展板按键检测
uint8_t ext_key_get_state(void)
{return gpio_input_bit_get(GPIOC, GPIO_PIN_0);
}
2.2 完整的按键控制LED实例
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
/*********************
任务目标: 核心板PA0 控制 PB2灯
PB2 高电平亮,PB2 低电平灭
PA0 按下是高,抬起是低需求:按下 灯亮,抬起灯灭
**********************//*********************************************************** @brief 输出配置 * @param rcu 时钟端口 RCU_GPIOx(x = A,B,C,D,E,F,G,H,I)* @param port 引脚端口 GPIOx(x = A,B,C,D,E,F,G,H,I)* @param pin 引脚 GPIO_PIN_x(x=0..15)* @param otype 输出模式 GPIO_OTYPE_PP 推挽; GPIO_OTYPE_OD 开漏* @return **********************************************************/
static inline void GPIO_output(rcu_periph_enum rcu, uint32_t port, uint32_t pin, uint8_t otype) {// 时钟初始化rcu_periph_clock_enable(rcu);// GPIO模式:输出gpio_mode_set(port, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, pin);// 输出选项设置gpio_output_options_set(port, otype, GPIO_OSPEED_MAX, pin);
}/*********************************************************** @brief 输入配置 * @param rcu 时钟端口 RCU_GPIOx(x = A,B,C,D,E,F,G,H,I)* @param port 引脚端口 GPIOx(x = A,B,C,D,E,F,G,H,I)* @param pin 引脚 GPIO_PIN_x(x=0..15)* @param pull_up_down 上下拉模式 GPIO_PUPD_NONE 浮空GPIO_PUPD_PULLUP 上拉GPIO_PUPD_PULLDOWN 下拉* @return **********************************************************/
static inline void GPIO_input(rcu_periph_enum rcu, uint32_t port, uint32_t pin, uint32_t pull_up_down) {// 时钟初始化rcu_periph_clock_enable(rcu);// GPIO模式:输入gpio_mode_set(port, GPIO_MODE_INPUT, pull_up_down, pin);
}void GPIO_config() {// =============== PB2 推挽输出GPIO_output(RCU_GPIOB, GPIOB, GPIO_PIN_2, GPIO_OTYPE_PP);// ============== PA0 浮空输入 外围电路已经下拉了,芯片内部浮空即可GPIO_input(RCU_GPIOA, GPIOA, GPIO_PIN_0, GPIO_PUPD_NONE);// ============= 独立按钮, 上拉输入, 外围电路没有做上下拉处理,交给芯片内部处理GPIO_input(RCU_GPIOC, GPIOC, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PUPD_PULLUP);}#define KEY0 GPIOA, GPIO_PIN_0
#define DOWN0 SET
#define UP0 RESET
#define PB2 GPIOB, GPIO_PIN_2// =================== 独立按键
#define KEY1 GPIOC, GPIO_PIN_0
#define KEY2 GPIOC, GPIO_PIN_1
#define KEY3 GPIOC, GPIO_PIN_2
#define KEY4 GPIOC, GPIO_PIN_3#define DOWN RESET
#define UP SET// 上一步状态
FlagStatus last_state0 = UP0; // 有分号
// 独立按键
FlagStatus last_state1 = UP;
FlagStatus last_state2 = UP;
FlagStatus last_state3 = UP;
FlagStatus last_state4 = UP;int main(void) {GPIO_config();// 系统滴答定时器初始化systick_config();while(1) {// ==================核心板 PA0=================FlagStatus cur_state0 = gpio_input_bit_get(KEY0);// 上一次 和 当前 不同if (last_state0 != cur_state0) {last_state0 = cur_state0; // 保存状态// 里面在单独判断按下和抬起if (cur_state0 == DOWN0) {gpio_bit_set(PB2); // 高电平亮} else {gpio_bit_reset(PB2); // 低电平灭}}// ==================独立按键1=================FlagStatus cur_state1 = gpio_input_bit_get(KEY1);// 上一次 和 当前 不同if (last_state1 != cur_state1) {last_state1 = cur_state1; // 保存状态// 里面在单独判断按下和抬起if (cur_state1 == DOWN) {gpio_bit_set(PB2); // 高电平亮} else {gpio_bit_reset(PB2); // 低电平灭}}delay_1ms(20);}}
4. 按键消抖
4.1 软件消抖实现
4.1.1 简单延时消抖
// 简单延时消抖
uint8_t key_scan_debounce_simple(GPIO_TypeDef* GPIOx, uint32_t pin)
{if(gpio_input_bit_get(GPIOx, pin) == SET) { // 检测到按键按下delay_ms(20); // 等待20msif(gpio_input_bit_get(GPIOx, pin) == SET) { // 再次确认return 1;}}return 0;
}
4.2.2 状态机消抖(推荐)
// 按键状态机消抖
typedef enum {KEY_STATE_RELEASED, // 按键释放状态KEY_STATE_DEBOUNCE, // 消抖状态KEY_STATE_PRESSED, // 按键按下状态KEY_STATE_LONG_PRESS // 长按状态
} key_state_t;typedef struct {GPIO_TypeDef* gpio;uint32_t pin;key_state_t state;uint32_t press_time;uint8_t last_phys_state;
} key_t;// 按键处理函数
key_state_t key_process(key_t* key)
{uint8_t current_phys_state = gpio_input_bit_get(key->gpio, key->pin);uint32_t current_time = get_system_tick();switch(key->state) {case KEY_STATE_RELEASED:if(current_phys_state != key->last_phys_state) {key->state = KEY_STATE_DEBOUNCE;key->press_time = current_time;}break;case KEY_STATE_DEBOUNCE:if(current_time - key->press_time > 20) { // 20ms消抖时间if(current_phys_state != key->last_phys_state) {if(current_phys_state == SET) { // 按键按下key->state = KEY_STATE_PRESSED;key->last_phys_state = current_phys_state;return KEY_STATE_PRESSED;}}key->state = KEY_STATE_RELEASED;}break;case KEY_STATE_PRESSED:if(current_phys_state != key->last_phys_state) {key->state = KEY_STATE_DEBOUNCE;key->press_time = current_time;} else if(current_time - key->press_time > 1000) { // 长按1秒key->state = KEY_STATE_LONG_PRESS;return KEY_STATE_LONG_PRESS;}break;case KEY_STATE_LONG_PRESS:if(current_phys_state != key->last_phys_state) {key->state = KEY_STATE_DEBOUNCE;key->press_time = current_time;}break;}key->last_phys_state = current_phys_state;return key->state;
}
4.2.3 使用状态机的完整示例
// 初始化按键结构
key_t core_key = {.gpio = GPIOA,.pin = GPIO_PIN_0,.state = KEY_STATE_RELEASED,.last_phys_state = 0
};key_t ext_key = {.gpio = GPIOC,.pin = GPIO_PIN_0,.state = KEY_STATE_RELEASED,.last_phys_state = 1 // 扩展板按键默认高电平
};// 使用状态机处理按键
void advanced_key_demo(void)
{led_init();key_init();ext_key_init();while(1) {key_state_t core_state = key_process(&core_key);key_state_t ext_state = key_process(&ext_key);// 处理核心板按键事件switch(core_state) {case KEY_STATE_PRESSED:led_toggle(); // 按下切换LED状态break;case KEY_STATE_LONG_PRESS:gpio_bit_set(GPIOB, GPIO_PIN_2); // 长按点亮LEDbreak;default:break;}// 处理扩展板按键事件switch(ext_state) {case KEY_STATE_PRESSED:led_toggle(); // 按下切换LED状态break;default:break;}delay_ms(5); // 5ms扫描周期}
}
5. 输入模式选择指南
5.1 不同输入模式的应用场景
输入模式 | 内部电路 | 适用场景 | 注意事项 |
---|---|---|---|
浮空输入 | 无上拉下拉 | 外部电路已处理电平 | 必须确保外部有确定电平 |
上拉输入 | 内部上拉电阻 | 按钮到地、开漏输出 | 默认高电平,按下为低 |
下拉输入 | 内部下拉电阻 | 按钮到VCC、推挽输出 | 默认低电平,按下为高 |
模拟输入 | 直连ADC | 模拟信号采集 | 不能用于数字信号 |
5.2 实际应用建议
-
按钮输入:
- 如果按钮连接VCC → 使用下拉输入
- 如果按钮连接GND → 使用上拉输入
- 如果外部已有电阻 → 使用浮空输入
-
传感器输入:
- 数字传感器 → 根据传感器输出特性选择
- 模拟传感器 → 必须使用模拟输入
-
通信接口:
- I²C → 开漏输出 + 上拉输入
- UART → 推挽输出 + 浮空输入
6. 函数注释翻译
/* 函数声明 */
/* 重置 GPIO端口 */
void gpio_deinit(uint32_t gpio_periph);
/* 设置GPIO模式 */
void gpio_mode_set(uint32_t gpio_periph, uint32_t mode, uint32_t pull_up_down, uint32_t pin);
/* 设置GPIO输出类型和速度 */
void gpio_output_options_set(uint32_t gpio_periph, uint8_t otype, uint32_t speed, uint32_t pin);/* 设置GPIO引脚 */
void gpio_bit_set(uint32_t gpio_periph, uint32_t pin);
/* 重置GPIO引脚 */
void gpio_bit_reset(uint32_t gpio_periph, uint32_t pin);
/* 向指定GPIO引脚写入数据 */
void gpio_bit_write(uint32_t gpio_periph, uint32_t pin, bit_status bit_value);
/* 向指定GPIO端口写入数据 */
void gpio_port_write(uint32_t gpio_periph, uint16_t data);/* 获取GPIO引脚输入状态 */
FlagStatus gpio_input_bit_get(uint32_t gpio_periph, uint32_t pin);
/* 获取GPIO端口输入状态 */
uint16_t gpio_input_port_get(uint32_t gpio_periph);
/* 获取GPIO引脚输出状态 */
FlagStatus gpio_output_bit_get(uint32_t gpio_periph, uint32_t pin);
/* 获取GPIO端口输出状态 */
uint16_t gpio_output_port_get(uint32_t gpio_periph);/* 设置GPIO复用功能 */
void gpio_af_set(uint32_t gpio_periph, uint32_t alt_func_num, uint32_t pin);
/* 锁定GPIO引脚 */
void gpio_pin_lock(uint32_t gpio_periph, uint32_t pin);/* 翻转GPIO引脚状态 */
void gpio_bit_toggle(uint32_t gpio_periph, uint32_t pin);
/* 翻转GPIO端口状态 */
void gpio_port_toggle(uint32_t gpio_periph);
函数作用解释
-
gpio_deinit
- 作用:将指定的GPIO端口恢复到复位状态(默认状态)
- 说明:用于初始化或重置整个GPIO端口的配置,清除之前的所有设置
-
gpio_mode_set
- 作用:配置GPIO引脚的工作模式
- 参数说明:
- mode:指定模式(输入/输出/复用功能/模拟模式等)
- pull_up_down:上拉/下拉电阻配置
- pin:指定要配置的引脚
- 说明:GPIO的核心配置函数,决定引脚的基本工作方式
-
gpio_output_options_set
- 作用:配置GPIO输出引脚的特性
- 参数说明:
- otype:输出类型(推挽/开漏)
- speed:输出速度(如低速/中速/高速)
- pin:指定要配置的引脚
- 说明:仅对配置为输出模式的引脚有效
-
gpio_bit_set
- 作用:将指定GPIO引脚设置为高电平
- 说明:快速将引脚置为高电平(1)
-
gpio_bit_reset
- 作用:将指定GPIO引脚设置为低电平
- 说明:快速将引脚置为低电平(0)
-
gpio_bit_write
- 作用:向指定GPIO引脚写入特定电平值
- 参数说明:
- bit_value:要写入的值(高/低电平)
- 说明:比set/reset更灵活,可以根据参数值决定写入高或低电平
-
gpio_port_write
- 作用:向整个GPIO端口写入16位数据
- 说明:一次性配置端口的所有引脚状态(16位对应16个引脚)
-
gpio_input_bit_get
- 作用:读取指定GPIO引脚的输入状态
- 返回值:引脚的当前输入电平(高/低)
- 说明:用于检测外部输入信号
-
gpio_input_port_get
- 作用:读取整个GPIO端口的输入状态
- 返回值:16位数据,每一位对应一个引脚的输入状态
- 说明:一次性获取所有引脚的输入状态
-
gpio_output_bit_get
- 作用:获取指定GPIO引脚的当前输出状态
- 返回值:引脚当前的输出电平(高/低)
- 说明:查询之前设置的输出状态
-
gpio_output_port_get
- 作用:获取整个GPIO端口的当前输出状态
- 返回值:16位数据,每一位对应一个引脚的输出状态
- 说明:查询整个端口的输出配置
-
gpio_af_set
- 作用:配置GPIO引脚的复用功能
- 参数说明:
- alt_func_num:复用功能编号(如UART、SPI等外设功能)
- 说明:用于将引脚分配给特定的外设功能,而不是作为普通GPIO使用
-
gpio_pin_lock
- 作用:锁定指定GPIO引脚的配置
- 说明:锁定后,引脚配置无法更改,直到下次复位,防止意外修改
-
gpio_bit_toggle
- 作用:翻转指定GPIO引脚的状态
- 说明:如果当前是高电平则变为低电平,反之亦然(常用于LED闪烁等场景)
-
gpio_port_toggle
- 作用:翻转整个GPIO端口所有引脚的状态
- 说明:一次性翻转端口所有引脚的电平状态