在嵌入式系统开发中,你是否也遇到过这样的困扰:代码里满是if-else分支和delay()阻塞延时,导致程序响应迟钝、难以维护,更别提支持复杂的交互逻辑了。本文将介绍一种结合有限状态机与事件驱动的轻量级软件框架设计,并以智能按键处理为例,展示如何用清晰的逻辑替代冗杂的条件判断,实现高效、可扩展的嵌入式应用。
传统方案问题
按键处理问题示例
下面是一段典型的传统按键处理代码,它暴露了许多常见问题:
// ❌ 问题代码:传统按键处理方式
void key_scan(void) {
static uint32_t last_time = 0;
uint32_t now = HAL_GetTick();
if (now - last_time < 50) { // 50ms扫描一次
return;
}
last_time = now;
if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) {
delay_ms(20); // 阻塞延时20ms
if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) {
// 短按检测
uint32_t press_time = HAL_GetTick();
while (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) {
delay_ms(10); // 继续阻塞等待
}
uint32_t release_time = HAL_GetTick();
uint32_t duration = release_time - press_time;
if (duration < 500) {
// 短按处理
printf("Short press\r\n");
} else if (duration < 3000) {
// 长按处理
printf("Long press\r\n");
} else {
// 超长按处理
printf("Extra long press\r\n");
}
}
}
}
// 问题分析:
// 1. delay_ms()阻塞CPU,无法响应其他事件
// 2. while循环占用CPU,功耗高
// 3. 无法处理多个按键同时按下
// 4. 代码逻辑混乱,难以扩展
这段代码的核心问题在于同步阻塞。delay_ms()函数让CPU空转等待,期间系统无法处理任何其他任务(如串口数据、传感器采样),严重影响了实时性。同时,整个逻辑交织在一起,想要增加“双击”或“连按”功能几乎需要重写整个函数。
状态机与事件驱动
有限状态机基础
有限状态机(FSM)是一种数学模型,它将系统行为抽象为有限数量的状态,以及触发状态间转换的事件。在任何时刻,系统都处于且仅处于一个确定的状态中。
状态机的优势:
- 清晰的状态表示:系统在任何时刻都处于明确的状态
- 可预测的行为:相同状态和事件总是产生相同的转换
- 易于测试:可以系统性地测试所有状态转换
- 易于扩展:添加新状态和转换相对简单
事件驱动基础
事件驱动架构的核心是异步通信。当某个动作发生时(如按键按下、定时器超时),产生一个事件对象并放入队列。系统的主循环或调度器从队列中取出事件,分发给对应的事件处理函数进行处理。
事件驱动的优势:
- 非阻塞:事件处理函数快速返回,不阻塞系统
- 实时响应:事件可以立即得到处理
- 解耦:事件源和事件处理解耦
- 可扩展:易于添加新的事件类型和处理者
状态机与事件驱动的结合
将两者结合,状态机作为事件的处理者:系统处于某个状态,当特定事件到来时,触发状态转移并执行相应动作。这种模式完美解决了传统方案的痛点——逻辑清晰、响应及时、高度解耦。
轻量级事件-状态机框架设计
核心数据结构设计
框架的核心是几个关键的数据结构,它们使用标准的C/C++语法定义,确保了可移植性和效率。
// event_state_machine.h
#ifndef EVENT_STATE_MACHINE_H
#define EVENT_STATE_MACHINE_H
#include <stdint.h>
#include <stdbool.h>
// 事件类型定义
typedef enum {
EVENT_NONE = 0,
EVENT_KEY_PRESS,
EVENT_KEY_RELEASE,
EVENT_KEY_LONG_PRESS,
EVENT_TIMER_TIMEOUT,
EVENT_UART_RX,
EVENT_UART_TX_COMPLETE,
EVENT_USER_DEFINED_START = 100, // 用户自定义事件从此开始
} event_type_t;
// 事件结构
typedef struct {
event_type_t type; // 事件类型
uint32_t data; // 事件数据(可扩展为联合体)
uint32_t timestamp; // 时间戳
} event_t;
// 状态机状态类型(用户定义)
typedef uint8_t state_t;
// 状态处理函数类型
typedef state_t (*state_handler_t)(event_t *event);
// 状态转换回调函数类型
typedef void (*state_transition_cb_t)(state_t old_state, state_t new_state);
// 状态机结构
typedef struct {
state_t current_state; // 当前状态
state_t initial_state; // 初始状态
state_handler_t *state_table; // 状态处理函数表
state_transition_cb_t on_transition;// 状态转换回调
uint8_t max_states; // 最大状态数
} state_machine_t;
// 事件队列结构
#define EVENT_QUEUE_SIZE 32
typedef struct {
event_t events[EVENT_QUEUE_SIZE];
uint8_t head;
uint8_t tail;
uint8_t count;
} event_queue_t;
// API函数声明
void event_queue_init(event_queue_t *queue);
bool event_queue_push(event_queue_t *queue, event_t *event);
bool event_queue_pop(event_queue_t *queue, event_t *event);
bool event_queue_is_empty(event_queue_t *queue);
bool event_queue_is_full(event_queue_t *queue);
void state_machine_init(state_machine_t *sm, state_t initial_state,
state_handler_t *state_table, uint8_t max_states,
state_transition_cb_t on_transition);
void state_machine_process(state_machine_t *sm, event_t *event);
void state_machine_run(state_machine_t *sm, event_queue_t *queue);
// 辅助宏:获取当前时间(用户需要实现HAL_GetTick或类似函数)
#ifndef GET_TICK
#define GET_TICK() HAL_GetTick()
#endif
#endif // EVENT_STATE_MACHINE_H
事件队列实现(环形缓冲区)
事件队列采用环形缓冲区实现,确保在固定内存下的高效入队和出队操作。
// event_state_machine.c
#include "event_state_machine.h"
#include <string.h>
// 事件队列初始化
void event_queue_init(event_queue_t *queue) {
if (queue == NULL) return;
memset(queue, 0, sizeof(event_queue_t));
queue->head = 0;
queue->tail = 0;
queue->count = 0;
}
// 事件入队
bool event_queue_push(event_queue_t *queue, event_t *event) {
if (queue == NULL || event == NULL) return false;
// 队列满检查
if (queue->count >= EVENT_QUEUE_SIZE) {
return false; // 队列满,丢弃最旧的事件或返回错误
}
// 添加时间戳
event->timestamp = GET_TICK();
// 入队
queue->events[queue->tail] = *event;
queue->tail = (queue->tail + 1) % EVENT_QUEUE_SIZE;
queue->count++;
return true;
}
// 事件出队
bool event_queue_pop(event_queue_t *queue, event_t *event) {
if (queue == NULL || event == NULL) return false;
// 队列空检查
if (queue->count == 0) {
return false;
}
// 出队
*event = queue->events[queue->head];
queue->head = (queue->head + 1) % EVENT_QUEUE_SIZE;
queue->count--;
return true;
}
// 检查队列是否为空
bool event_queue_is_empty(event_queue_t *queue) {
if (queue == NULL) return true;
return (queue->count == 0);
}
// 检查队列是否满
bool event_queue_is_full(event_queue_t *queue) {
if (queue == NULL) return true;
return (queue->count >= EVENT_QUEUE_SIZE);
}
状态机核心实现
状态机引擎负责驱动状态转换,它是整个框架的“大脑”。
// 状态机初始化
void state_machine_init(state_machine_t *sm, state_t initial_state,
state_handler_t *state_table, uint8_t max_states,
state_transition_cb_t on_transition) {
if (sm == NULL || state_table == NULL) return;
sm->current_state = initial_state;
sm->initial_state = initial_state;
sm->state_table = state_table;
sm->on_transition = on_transition;
sm->max_states = max_states;
}
// 状态机处理单个事件
void state_machine_process(state_machine_t *sm, event_t *event) {
if (sm == NULL || event == NULL) return;
if (sm->state_table == NULL) return;
// 获取当前状态的处理函数
state_t current = sm->current_state;
if (current >= sm->max_states) return;
state_handler_t handler = sm->state_table[current];
if (handler == NULL) return;
// 调用状态处理函数
state_t new_state = handler(event);
// 检查状态是否发生变化
if (new_state != current && new_state < sm->max_states) {
// 状态转换
state_t old_state = sm->current_state;
sm->current_state = new_state;
// 调用转换回调
if (sm->on_transition != NULL) {
sm->on_transition(old_state, new_state);
}
}
}
// 状态机主循环(从事件队列处理事件)
void state_machine_run(state_machine_t *sm, event_queue_t *queue) {
if (sm == NULL || queue == NULL) return;
event_t event;
while (!event_queue_is_empty(queue)) {
if (event_queue_pop(queue, &event)) {
state_machine_process(sm, &event);
}
}
}
智能按键处理应用
按键状态定义
我们为按键设计一个独立的状态机,明确其所有可能的状态。
// key_state_machine.h
#ifndef KEY_STATE_MACHINE_H
#define KEY_STATE_MACHINE_H
#include "event_state_machine.h"
// 按键状态定义
typedef enum {
KEY_STATE_IDLE = 0, // 空闲状态
KEY_STATE_PRESSED, // 按下(消抖中)
KEY_STATE_DEBOUNCED, // 消抖完成
KEY_STATE_HELD, // 持续按下
KEY_STATE_SHORT_PRESS, // 短按
KEY_STATE_LONG_PRESS, // 长按
KEY_STATE_RELEASED, // 释放
KEY_STATE_MAX
} key_state_t;
// 按键事件定义
#define KEY_EVENT_PRESS EVENT_KEY_PRESS
#define KEY_EVENT_RELEASE EVENT_KEY_RELEASE
#define KEY_EVENT_TIMEOUT EVENT_TIMER_TIMEOUT
// 按键配置参数
#define KEY_DEBOUNCE_TIME 20 // 消抖时间 20ms
#define KEY_LONG_PRESS_TIME 500 // 长按时间 500ms
// 按键处理结构
typedef struct {
state_machine_t sm; // 状态机
event_queue_t queue; // 事件队列
state_handler_t handlers[KEY_STATE_MAX]; // 状态处理函数表
uint32_t press_time; // 按下时间戳
} key_handler_t;
// API
void key_handler_init(key_handler_t *key);
void key_handler_process(key_handler_t *key);
void key_on_press(key_handler_t *key);
void key_on_release(key_handler_t *key);
// 回调函数类型(用户实现)
typedef void (*key_short_press_cb_t)(void);
typedef void (*key_long_press_cb_t)(void);
extern key_short_press_cb_t key_short_press_callback;
extern key_long_press_cb_t key_long_press_callback;
#endif // KEY_STATE_MACHINE_H
按键状态处理函数实现
每个状态都有对应的处理函数,它们根据接收到的事件决定下一步动作。
// key_state_machine.c
#include "key_state_machine.h"
#include "stm32l4xx_hal.h"
// 用户回调函数(外部定义)
key_short_press_cb_t key_short_press_callback = NULL;
key_long_press_cb_t key_long_press_callback = NULL;
// 外部按键GPIO定义(根据实际硬件修改)
extern GPIO_TypeDef* KEY_GPIO_Port;
extern uint16_t KEY_Pin;
// 状态处理函数:IDLE状态
static state_t key_state_idle_handler(event_t *event) {
if (event->type == KEY_EVENT_PRESS) {
return KEY_STATE_PRESSED; // 转换到按下状态
}
return KEY_STATE_IDLE; // 保持当前状态
}
// 状态处理函数:PRESSED状态(消抖)
static state_t key_state_pressed_handler(event_t *event) {
switch (event->type) {
case KEY_EVENT_TIMEOUT:
// 消抖时间到,检查按键是否仍按下
if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) {
return KEY_STATE_DEBOUNCED; // 确认按下
} else {
return KEY_STATE_IDLE; // 抖动,返回空闲
}
case KEY_EVENT_RELEASE:
// 在消抖期间释放,认为是抖动
return KEY_STATE_IDLE;
default:
return KEY_STATE_PRESSED;
}
}
// 状态处理函数:DEBOUNCED状态
static state_t key_state_debounced_handler(event_t *event) {
switch (event->type) {
case KEY_EVENT_RELEASE:
// 释放,判定为短按
return KEY_STATE_SHORT_PRESS;
case KEY_EVENT_TIMEOUT:
// 长按超时
return KEY_STATE_LONG_PRESS;
default:
return KEY_STATE_DEBOUNCED;
}
}
// 状态处理函数:SHORT_PRESS状态
static state_t key_state_short_press_handler(event_t *event) {
// 触发短按回调
if (key_short_press_callback != NULL) {
key_short_press_callback();
}
return KEY_STATE_IDLE; // 返回空闲状态
}
// 状态处理函数:LONG_PRESS状态
static state_t key_state_long_press_handler(event_t *event) {
// 触发长按回调
if (key_long_press_callback != NULL) {
key_long_press_callback();
}
if (event->type == KEY_EVENT_RELEASE) {
return KEY_STATE_RELEASED;
}
return KEY_STATE_HELD; // 继续按住
}
// 状态处理函数:HELD状态
static state_t key_state_held_handler(event_t *event) {
if (event->type == KEY_EVENT_RELEASE) {
return KEY_STATE_RELEASED;
}
return KEY_STATE_HELD;
}
// 状态处理函数:RELEASED状态
static state_t key_state_released_handler(event_t *event) {
// 释放处理完成,返回空闲
return KEY_STATE_IDLE;
}
// 状态转换回调
static void key_state_transition_cb(state_t old_state, state_t new_state) {
// 记录状态转换,可用于调试
// printf("Key state: %d -> %d\r\n", old_state, new_state);
}
// 按键处理初始化
void key_handler_init(key_handler_t *key) {
if (key == NULL) return;
// 初始化状态处理函数表
key->handlers[KEY_STATE_IDLE] = key_state_idle_handler;
key->handlers[KEY_STATE_PRESSED] = key_state_pressed_handler;
key->handlers[KEY_STATE_DEBOUNCED] = key_state_debounced_handler;
key->handlers[KEY_STATE_SHORT_PRESS] = key_state_short_press_handler;
key->handlers[KEY_STATE_LONG_PRESS] = key_state_long_press_handler;
key->handlers[KEY_STATE_HELD] = key_state_held_handler;
key->handlers[KEY_STATE_RELEASED] = key_state_released_handler;
// 初始化事件队列
event_queue_init(&key->queue);
// 初始化状态机
state_machine_init(&key->sm, KEY_STATE_IDLE, key->handlers,
KEY_STATE_MAX, key_state_transition_cb);
}
// 按键处理主循环(在主循环中定期调用)
void key_handler_process(key_handler_t *key) {
if (key == NULL) return;
// 处理事件队列中的所有事件
state_machine_run(&key->sm, &key->queue);
}
// 按键按下事件(在GPIO中断中调用)
void key_on_press(key_handler_t *key) {
if (key == NULL) return;
event_t event = {
.type = KEY_EVENT_PRESS,
.data = 0,
.timestamp = GET_TICK()
};
event_queue_push(&key->queue, &event);
// 启动消抖定时器(使用软件定时器或硬件定时器)
// 这里需要在20ms后发送KEY_EVENT_TIMEOUT事件
}
// 按键释放事件(在GPIO中断中调用)
void key_on_release(key_handler_t *key) {
if (key == NULL) return;
event_t event = {
.type = KEY_EVENT_RELEASE,
.data = 0,
.timestamp = GET_TICK()
};
event_queue_push(&key->queue, &event);
}
按键使用示例
最后,我们看看如何在主程序中集成这个智能按键处理框架。这种清晰的架构分离了硬件操作、逻辑处理和业务回调,使得代码极易维护和扩展。
// main.c
#include "key_state_machine.h"
#include "stm32l4xx_hal.h"
key_handler_t g_key_handler;
// 短按回调函数
void on_key_short_press(void) {
printf("Short press detected\r\n");
// 执行短按业务逻辑,例如:切换LED状态
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
// 长按回调函数
void on_key_long_press(void) {
printf("Long press detected\r\n");
// 执行长按业务逻辑,例如:进入配置模式
enter_config_mode();
}
// GPIO中断回调
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == KEY_Pin) {
if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) {
key_on_press(&g_key_handler);
} else {
key_on_release(&g_key_handler);
}
}
}
// 软件定时器回调(1ms调用一次)
void systick_callback(void) {
static uint32_t debounce_timer = 0;
static uint32_t long_press_timer = 0;
// 消抖定时器
if (debounce_timer > 0) {
debounce_timer--;
if (debounce_timer == 0) {
event_t event = {
.type = KEY_EVENT_TIMEOUT,
.data = KEY_DEBOUNCE_TIME,
.timestamp = GET_TICK()
};
event_queue_push(&g_key_handler.queue, &event);
}
}
// 长按定时器(在DEBOUNCED状态时启动)
// ... 类似处理
}
int main(void) {
// 硬件初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
// 设置回调函数
key_short_press_callback = on_key_short_press;
key_long_press_callback = on_key_long_press;
// 初始化按键处理
key_handler_init(&g_key_handler);
// 主循环
while (1) {
// 处理按键事件(非阻塞)
key_handler_process(&g_key_handler);
// 其他任务
// ...
// 进入低功耗模式(可选)
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
}
}
通过以上设计,我们成功将一个充满阻塞和复杂判断的按键处理程序,重构为基于状态机和事件驱动的清晰模块。这个框架不仅解决了实时性和功耗问题,其模块化设计也使得添加新功能(如双击检测)变得非常简单——只需要定义新的状态和事件即可。希望这个案例能为你设计更优雅、更强大的嵌入式软件提供启发。欢迎在云栈社区分享你的实践与想法。