找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2228

积分

0

好友

312

主题
发表于 7 天前 | 查看: 17| 回复: 0

在嵌入式系统开发中,你是否也遇到过这样的困扰:代码里满是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);
    }
}

通过以上设计,我们成功将一个充满阻塞和复杂判断的按键处理程序,重构为基于状态机和事件驱动的清晰模块。这个框架不仅解决了实时性和功耗问题,其模块化设计也使得添加新功能(如双击检测)变得非常简单——只需要定义新的状态和事件即可。希望这个案例能为你设计更优雅、更强大的嵌入式软件提供启发。欢迎在云栈社区分享你的实践与想法。




上一篇:Linux 6.12.32启动流程深度解析
下一篇:AI初创公司为何青睐PostgreSQL?技术、生态与商业优势深度解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:36 , Processed in 0.711487 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表