☰
事件驱动状态机例子
2026/10/14 16:05:30 网站建设 项目流程

核心思想:中断/定时器只投递事件,主循环或 RTOS 任务消费事件,状态机根据“当前状态 + 事件”决定转移和动作。

场景定义

  • 短按按键:切换 LED 开/关

  • 长按 2 秒:进入/退出 LED 闪烁

  • 按键需要 20ms 去抖

事件源:

  • 按键 GPIO 中断:产生EV_KEY_DOWN/EV_KEY_UP

  • 10ms 定时器中断:产生EV_TICK_10MS

事件和状态定义

typedef enum { EV_KEY_DOWN, EV_KEY_UP, EV_TICK_10MS } Event; typedef enum { ST_IDLE, // 空闲 ST_DEBOUNCE_DOWN, // 按下去抖 ST_PRESSED, // 已按下,等待短按/长按 ST_LONG_PRESS, // 长按已触发 ST_DEBOUNCE_UP // 松手去抖 } State;

事件队列:ISR 只投递事件

#define EVQ_SIZE 16 static Event evq[EVQ_SIZE]; static volatile uint8_t head = 0, tail = 0; bool event_post_from_isr(Event ev) { uint8_t next = (head + 1) % EVQ_SIZE; if (next == tail) { return false; // 队列满,可记录溢出 } evq[head] = ev; head = next; return true; } bool event_get(Event *ev) { if (tail == head) { return false; } *ev = evq[tail]; tail = (tail + 1) % EVQ_SIZE; return true; }

按键中断:

void key_isr(void) { if (gpio_read(KEY_PIN) == 0) { event_post_from_isr(EV_KEY_DOWN); } else { event_post_from_isr(EV_KEY_UP); } }

10ms 定时器中断:

void timer_10ms_isr(void) { event_post_from_isr(EV_TICK_10MS); }

状态机处理

static State state = ST_IDLE; static uint16_t press_ms = 0; void fsm_dispatch(Event ev) { switch (state) { case ST_IDLE: if (ev == EV_KEY_DOWN) { press_ms = 0; state = ST_DEBOUNCE_DOWN; } break; case ST_DEBOUNCE_DOWN: if (ev == EV_TICK_10MS) { press_ms += 10; if (press_ms >= 20) { // 去抖 20ms press_ms = 0; state = ST_PRESSED; } } else if (ev == EV_KEY_UP) { state = ST_IDLE; // 抖动,忽略 } break; case ST_PRESSED: if (ev == EV_TICK_10MS) { press_ms += 10; if (press_ms >= 2000) { // 长按 2s led_blink_toggle(); state = ST_LONG_PRESS; } } else if (ev == EV_KEY_UP) { led_toggle(); // 短按动作 press_ms = 0; state = ST_DEBOUNCE_UP; } break; case ST_LONG_PRESS: if (ev == EV_KEY_UP) { press_ms = 0; state = ST_DEBOUNCE_UP; } break; case ST_DEBOUNCE_UP: if (ev == EV_TICK_10MS) { press_ms += 10; if (press_ms >= 20) { state = ST_IDLE; } } break; default: state = ST_IDLE; break; } }

主循环消费事件

void main_loop(void) { Event ev; while (1) { if (event_get(&ev)) { fsm_dispatch(ev); } // 可以在这里进入低功耗,等待中断唤醒 // __WFI(); } }

如果是 RTOS,可以把fsm_dispatch放在一个任务里,事件队列换成xQueueSendFromISR()/xQueueReceive()。

  • 按键中断不直接切换 LED,也不做去抖计时,只投递EV_KEY_DOWN/EV_KEY_UP。

  • 定时器中断不判断长按,只投递EV_TICK_10MS。

  • 真正逻辑在fsm_dispatch()里,根据state + event决定:

    • 是否转移状态

    • 是否执行动作,比如led_toggle()、led_blink_toggle()

    • 是否累计时间、去抖、判断长按

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询