嵌入式软件编程思想之状态机+事件驱动2

在上一篇文章中,事件驱动体现的不太明显,更像是一个状态机,本篇进行提升。
真正的“事件驱动”应该是:主循环什么都不用问,外部中断或硬件回调主动把事件“扔”进队列,状态机只负责被动消费。在嵌入式系统中,要体现“事件驱动”,必须满足以下三个特征:
生产者与消费者分离:硬件中断(生产者)只负责发信号,主循环(消费者)只负责处理逻辑。
异步性:事件可以在任何时刻发生(如按键按下),不需要等待主循环检查到它。
事件队列:这是核心!中断不能直接调用状态机(因为中断上下文不能做耗时操作,且可能重入),必须先把事件存起来。
将对之前的代码进行重构,加入 事件队列 和 中断模拟,改造成真正的状态机+事件驱动
1.定义事件队列

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>

// --- 1. 事件定义 ---
typedef enum {
    EVT_NONE = 0,
    EVT_BTN_POWER,
    EVT_BTN_MODE,
    EVT_TEMP_LOW,
    EVT_TEMP_HIGH,
    EVT_TEMP_ERROR,
    EVT_TICK_1S,
    EVT_MAX
} Event_t;

// --- 2. 简单的环形缓冲区 (事件队列) ---
#define QUEUE_SIZE 16
typedef struct {
    Event_t buffer[QUEUE_SIZE];
    uint8_t head;
    uint8_t tail;
    uint8_t count;
} EventQueue_t;

static EventQueue_t g_eventQueue = {0};

// 入队 (通常在中断中调用)
bool event_push(Event_t evt) {
    if (g_eventQueue.count >= QUEUE_SIZE) return false; // 队列满,丢弃或报错
    g_eventQueue.buffer[g_eventQueue.tail] = evt;
    g_eventQueue.tail = (g_eventQueue.tail + 1) % QUEUE_SIZE;
    g_eventQueue.count++;
    return true;
}

// 出队 (在主循环中调用)
bool event_pop(Event_t *evt) {
    if (g_eventQueue.count == 0) return false; // 队列空
    *evt = g_eventQueue.buffer[g_eventQueue.head];
    g_eventQueue.head = (g_eventQueue.head + 1) % QUEUE_SIZE;
    g_eventQueue.count--;
    return true;
}

2.模拟硬件中断(生产者)

// --- 模拟硬件中断服务程序 (ISR) ---

// 模拟:用户按下了电源键 (触发外部中断)
void HAL_GPIO_EXTI_Callback_PowerBtn() {
    // 【关键】中断只做一件事:把事件扔进队列,立刻退出!
    // 绝不在中断里执行风扇转动、延时等耗时操作
    event_push(EVT_BTN_POWER); 
}

// 模拟:用户按下了模式键
void HAL_GPIO_EXTI_Callback_ModeBtn() {
    event_push(EVT_BTN_MODE);
}

// 模拟:定时器溢出中断 (每秒一次)
void HAL_TIM_PeriodElapsedCallback() {
    // 定时器中断里也只负责发事件
    event_push(EVT_TICK_1S);
}

// 模拟:ADC 采样完成中断 (温度读取)
void HAL_ADC_ConvCpltCallback(float temp) {
    Event_t evt = EVT_NONE;
    if (temp > 100.0f) evt = EVT_TEMP_ERROR;
    else if (temp > 35.0f) evt = EVT_TEMP_HIGH;
    else if (temp < 25.0f) evt = EVT_TEMP_LOW;
    
    // 只有当温度跨越阈值产生新事件时才推送,避免队列溢出
    // 实际项目中可能需要更复杂的去抖逻辑
    if (evt != EVT_NONE) {
        event_push(evt);
    }
}

3.状态机现在变得非常纯粹,它不关心事件从哪来,只关心从队列里取出的事件是什么

// --- 状态机定义 (简化版,沿用之前的逻辑) ---
typedef enum { STATE_OFF, STATE_AUTO, STATE_FAULT, STATE_MAX } State_t;
typedef void (*Action)(void);

typedef struct {
    State_t cur;
    Event_t evt;
    State_t next;
    Action act;
} Transition;

// ... (此处省略具体的 stateTable 数组定义,逻辑同前) ...
// 假设 stateTable 已定义好

static State_t g_currentState = STATE_OFF;

void run_state_machine(Event_t evt) {
    // 查找表逻辑...
    if (g_currentState == STATE_OFF && evt == EVT_BTN_POWER) {
        printf("开启风扇 (自动模式)\n");
        g_currentState = STATE_AUTO;
    } else if (g_currentState == STATE_AUTO && evt == EVT_TEMP_HIGH) {
        printf("风速调至最大\n");
        // 状态不变,但执行动作
    } else if (g_currentState == STATE_AUTO && evt == EVT_TEMP_ERROR) {
        printf("进入故障保护,关闭风扇\n");
        g_currentState = STATE_FAULT;
    } else {
        // printf("状态 %d 忽略事件 %d\n", g_currentState, evt);
    }
}

4.main(调度器)
这才是事件驱动的精髓:主循环不再轮询硬件,它只轮询队列。如果没有事件,它就休眠或做其他低优先级任务

int main(void) {
    printf("------------------------------------------------\n");

    while (1) {
        Event_t currentEvt;

        // 与上一篇的核心区别
        // 之前:主动问硬件 "你有事吗?" 
        // 现在:问队列 "有没人塞进来的事件?" 
        
        if (event_pop(&currentEvt)) {
            // 如果有事件,立即处理
            // printf("从队列取出事件: %d\n", currentEvt);
            run_state_machine(currentEvt);
        } else {
            // 如果队列是空的,说明没有突发事件
            // CPU 可以进入低功耗模式 (WFI),或者处理背景任务 (如日志上传)
            // 这里为了演示,我们手动模拟一些中断触发
            static int tick = 0;
            
            // 模拟:第 1 秒,触发电源键中断
            if (tick == 10) { 
                HAL_GPIO_EXTI_Callback_PowerBtn(); // 模拟中断发生
            }
            // 模拟:第 3 秒,温度升高触发中断
            if (tick == 30) {
                HAL_ADC_ConvCpltCallback(40.5f); 
            }
            // 模拟:第 5 秒,温度异常触发中断
            if (tick == 50) {
                HAL_ADC_ConvCpltCallback(120.0f);
            }
            
            tick++;
            // 模拟延时,代表系统空闲时间
            // 在真实系统中,这里可以是 __WFI() (Wait For Interrupt)
        }
    }
    return 0;
}

在这个改进版中:
事件驱动体现在“推模型” :硬件中断(生产者)在事件发生时,主动将事件 push 到队列。
状态机体现在“消费模型”:状态机作为消费者,从队列 pop 事件并驱动状态流转。
解耦:main 函数不再包含任何 if (button_pressed) 这样的硬件检测代码。这就是架构上的解耦。
这种架构是 RTOS(如 FreeRTOS, Zephyr)中任务间通信的标准模式(Queue + Task),也是现代嵌入式软件处理复杂交互的核心思想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值