欢迎光临
我们一直在努力

事件驱动架构:用消息队列代替全局变量

摘要:按键模块调用显示模块,显示模块调用存储模块,形成蜘蛛网一样的依赖?这是强耦合的恶果。本文将演示如何通过 消息队列(Message Queue)​ 实现 事件驱动架构(EDA),让模块间彻底解耦。


一、反面教材(蜘蛛网写法)

场景:按键按下,屏幕显示数字,同时保存到 Flash。

// key.c
#include "display.h"
#include "storage.h"

void Key_Pressed_Callback(void)
{
Display_ShowNumber(123); // 直接调用显示模块
Storage_SaveData(123); // 直接调用存储模块
}

痛点:

  • 依赖爆炸:key.c必须知道 display和 storage的存在。

  • 无法复用:把按键模块移植到新项目,必须把显示和存储代码也搬过去。

  • 死锁风险:如果显示模块反过来调用按键模块,系统直接死锁。


二、进阶写法(事件驱动)

核心思想:模块只发消息,不关心谁处理。

1. 定义事件(Event)

创建一个 event.h,定义系统中所有的事件。

// event.h
typedef enum {
EVENT_KEY_PRESSED = 0x01,
EVENT_TEMP_READY = 0x02,
EVENT_OTA_START = 0x03,
} SystemEvent_t;

2. 建立消息队列(Broker)

在 main.c中创建队列,所有模块只对这个队列说话。

// main.c
QueueHandle_t g_event_queue;

void System_Init(void)
{
g_event_queue = xQueueCreate(10, sizeof(SystemEvent_t));
}

3. 模块只负责“发消息”

按键模块不再关心后续动作。

// key.c
#include "event.h"

void Key_Pressed_Callback(void)
{
SystemEvent_t event = EVENT_KEY_PRESSED;
xQueueSend(g_event_queue, &event, 0); // 只发消息
}

4. 模块只负责“收消息”

显示模块和存储模块监听队列。

// display_task.c
void Display_Task(void *arg)
{
SystemEvent_t event;
while (1) {
if (xQueueReceive(g_event_queue, &event, portMAX_DELAY)) {
if (event == EVENT_KEY_PRESSED) {
Display_ShowNumber(123); // 处理事件
}
}
}
}

// storage_task.c
void Storage_Task(void *arg)
{
SystemEvent_t event;
while (1) {
if (xQueueReceive(g_event_queue, &event, portMAX_DELAY)) {
if (event == EVENT_KEY_PRESSED) {
Storage_SaveData(123); // 处理事件
}
}
}
}


三、进阶收益

维度

强耦合写法

事件驱动写法

依赖关系​

按键依赖显示、存储

按键依赖队列

新增功能​

修改按键源码

新增一个监听者

可测试性​

必须联调

可单独模拟事件

架构图​

蜘蛛网

星型结构


四、工程级最佳实践

1. 事件携带参数(Payload)

如果事件需要带数据,定义结构体。

typedef struct {
SystemEvent_t type;
uint32_t param1;
void *data;
} EventMsg_t;

2. 严禁在中断里处理业务

ISR 里只发消息,不做任何业务逻辑。

// 正确:ISR 只发消息
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
SystemEvent_t event = EVENT_KEY_PRESSED;
xQueueSendFromISR(g_event_queue, &event, &xHigherPriorityTaskWoken);
}

3. 单一职责

一个模块只处理一种事件,不要在一个任务里处理所有事件。


五、总结 Checklist

  • [ ] 是否消除了模块间的直接函数调用?

  • [ ] 是否定义了统一的事件枚举(Event Enum)?

  • [ ] 是否所有模块都只通过队列通信?

  • [ ] 是否严禁在 ISR 里执行业务逻辑?


六、写在最后(关注我,少走弯路)

我是 gqqsherry666,一个拒绝调包、专注工程落地的嵌入式架构师。

强耦合是代码的癌症,它会随着项目变大吞噬你的维护信心。

学会用消息队列解耦,你的代码寿命将从 Demo 级​ 跃升为 产品级。

关注我的专栏《嵌入式系统架构实战》,下一篇我们将深入解析 《如何设计一个通用的按键模块?》。

👉 下一篇预告:《如何设计一个通用的按键模块?》


原创文章,转载请注明出处。

赞(0)
未经允许不得转载:171主机测评 » 事件驱动架构:用消息队列代替全局变量
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址