在嵌入式系统开发中,按键是最常见的人机交互方式之一。本文将基于《嵌入式 – GD32开发实战指南(RISC-V版本)》第6章,深入探讨 GD32 RISC-V 平台上的按键驱动开发,重点分析中断处理、按键消抖以及状态机应用等关键技术点。我们将结合实际案例,分享在 GD32 上进行按键功能开发的实战经验,并提供一些避坑指南。
按键中断:实时响应的关键
中断配置
使用外部中断 (EXTI) 可以实现按键的实时响应。首先,需要配置 GPIO 引脚为中断输入模式,并使能对应的 EXTI 中断线。
void KEY_EXTI_Configuration(void)
{
/* Enable GPIOC clock */
rcu_periph_clock_enable(RCU_GPIOC);
/* Enable AFIO clock */
rcu_periph_clock_enable(RCU_AFIO);
/* Configure PC13 as external interrupt line */
gpio_init(GPIOC, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
/* Connect EXTI line to PC13 pin */
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOC, GPIO_PINSOURCE_13);
/* Enable EXTI line interrupt */
exti_init(EXTI_13, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_enable(EXTI_13);
/* Enable the EXTI13 interrupt */
nvic_irq_enable(EXTI10_15_IRQn, 0, 0);
}
这里,我们将 GPIOC 的 13 号引脚配置为下拉输入,并将其与 EXTI13 中断线关联。当按键按下时,电平由高变低,触发中断。
中断服务例程 (ISR)
接下来,编写中断服务例程来处理按键事件。注意:ISR 应该尽可能短小精悍,避免执行耗时操作。
void EXTI10_15_IRQHandler(void)
{
if(RESET != exti_interrupt_flag_get(EXTI_13)){
/* Delay 10ms for de-bounce */
delay_1ms(10);
if(RESET == gpio_input_bit_get(GPIOC, GPIO_PIN_13)){
/* Key pressed event */
// 处理按键事件,例如设置标志位
key_pressed_flag = 1;
}
/* Clear EXTI line flag */
exti_interrupt_flag_clear(EXTI_13);
}
}
可以看到,我们在 ISR 中进行了简单的消抖处理(delay_1ms)。更复杂的消抖逻辑可以在主循环中处理。
按键消抖:确保稳定输入
按键消抖是按键驱动开发中的关键步骤。机械按键在按下和释放时,会产生短暂的抖动,导致 MCU 误判。常见的消抖方法有:
- 硬件消抖:使用 RC 滤波器等硬件电路滤除抖动。
- 软件消抖:通过延时和多次采样来判断按键状态。
上面 ISR 中使用的延时是一种简单的软件消抖方法。更健壮的方法是使用定时器中断进行周期性采样,并根据连续多次采样的结果来判断按键状态。 这有点像 Nginx 的健康检查机制,需要多次请求都失败才判定服务不可用,避免网络抖动造成的误判。
基于状态机的消抖实现
enum KeyState {
KEY_RELEASED,
KEY_PRESSING,
KEY_PRESSED,
KEY_RELEASING
};
enum KeyState key_state = KEY_RELEASED;
uint32_t key_press_time = 0;
#define DEBOUNCE_TIME 20 // ms
void Key_Process(void) {
switch (key_state) {
case KEY_RELEASED:
if (gpio_input_bit_get(GPIOC, GPIO_PIN_13) == RESET) { // Key pressed
key_state = KEY_PRESSING;
key_press_time = HAL_GetTick();
}
break;
case KEY_PRESSING:
if (gpio_input_bit_get(GPIOC, GPIO_PIN_13) == SET) { // Key released during debounce
key_state = KEY_RELEASED;
} else if (HAL_GetTick() - key_press_time >= DEBOUNCE_TIME) {
key_state = KEY_PRESSED;
// Key pressed event
HandleKeyPress();
}
break;
case KEY_PRESSED:
if (gpio_input_bit_get(GPIOC, GPIO_PIN_13) == SET) {
key_state = KEY_RELEASING;
key_press_time = HAL_GetTick();
}
break;
case KEY_RELEASING:
if (gpio_input_bit_get(GPIOC, GPIO_PIN_13) == RESET) {
key_state = KEY_PRESSED;
} else if (HAL_GetTick() - key_press_time >= DEBOUNCE_TIME) {
key_state = KEY_RELEASED;
// Key released event
HandleKeyRelease();
}
break;
default:
key_state = KEY_RELEASED; // Reset to initial state
break;
}
}
Key_Process 函数应该周期性地被调用 (例如在 SysTick 中断中)。 状态机通过 KEY_RELEASED, KEY_PRESSING, KEY_PRESSED, 和 KEY_RELEASING 四个状态转换来判断按键的稳定状态。
实战避坑经验
- GPIO 配置:务必正确配置 GPIO 引脚的模式 (输入/输出、上拉/下拉),否则可能导致按键无法正常工作。
- 中断优先级:合理分配中断优先级,避免按键中断被其他中断抢占,影响响应速度。 如果系统使用了 FreeRTOS,还要注意任务的优先级设置,避免任务饥饿。
- 消抖时间:根据实际情况调整消抖时间,过短可能无法有效消抖,过长则会降低响应速度。
- 长按检测:可以通过记录按键按下的时间来判断是否为长按事件。
- 功耗优化:如果系统对功耗有要求,可以考虑使用低功耗模式,并在按键按下时唤醒系统。
通过以上实践和经验总结,相信读者能够更好地掌握 GD32 RISC-V 平台上的按键驱动开发,并能够在实际项目中灵活应用。
冠军资讯
键盘上的咸鱼