
MCU软件架构和开饭店是一样的
-我说的

关注我,不迷路



MCU开发 = 开饭店?
你想象一下,你在一家餐厅当大厨(主循环)。
如果客人点菜(中断/外设触发),你得放下手里的锅铲,跑到前厅去听客人要啥,然后再跑回厨房做菜。要是饭点儿人多,客人疯狂喊你,你光跑腿就累死了,哪还有空炒菜?搞不好还会把菜烧糊了(系统死机)。
这时候,老板给你配了一个传菜员(事件队列)。
这就好办了:
前台接单(入队): 客人一喊,传菜员立马记下菜单(往队列里塞个事件),然后扭头就走,一秒钟都不耽误。传菜员动作贼快,因为他只负责记,不负责做。
后厨做菜(出队+处理): 你(主循环),就站在灶台前不用动了。看一眼传菜员的单子,拿一张,炒一个菜。做完这张再看下一张。
开饭店是不是跟嵌入式很像?

事件驱动架构
在MCU裸机开发中,事件队列(Event Queue)是实现“生产者-消费者”模型的核心机制。它的本质是将突发的、无序的外部消息转化为有序的工单,让中断只负责“登记”,主循环负责“干活”,从而实现高内聚、低耦合1。以下是实现该架构的详情与核心要点:
01
整体数据流转架构
一个完整的事件驱动系统通常包含以下四个环节:
事件产生:如按键中断触发、UART接收一帧数据或定时器溢出。
入队操作:中断服务程序(ISR)调用 post_event 将事件塞入队列尾部。
出队分发:主循环轮询队列头部,通过 get_event 取出事件并交给状态机处理。
业务执行:当前状态的处理函数决定如何响应事件,可能伴随状态切换和硬件动作(如启动加热片、点亮LED等)。
02
事件队列
为了节省内存且高效,工程上强烈推荐采用环形缓冲区(Ring Buffer)来实现事件队列。
结构体定义:队列通常由一个固定大小的数组(如长度为16或32)、头指针(head/tail)、以及元素计数(count)组成。
容量规划:队列长度建议设为8~32之间,需根据业务的峰值进行调整。经验法则是:如果设备平均每秒最多产生5个事件,每次处理耗时不超过100ms,深度为8基本够用;留有余量是为了防止“事件风暴”导致丢包。

03
并发安全与临界区保护
由于中断随时可能发生,直接操作共享的队列极易引发数据竞争甚至死机。因此必须做好同步保护:
关中断保护:在入队和出队的关键代码段前后,必须关闭全局中断(如ARM Cortex-M下的 __disable_irq() ),操作完成后再恢复。
无锁优化(进阶):在多中断源场景下,为了降低关中断带来的延迟,可以使用原子操作指令(如ARM的 LDREX/STREX)实现无锁队列,或者使用位掩码替代取模运算来提高索引效率。
04
内存分配策略
在资源受限的MCU中,频繁的 malloc/free 会产生严重的内存碎片。
静态内存池:推荐在编译期一次性分配所有内存,运行时只做节点复用。这能彻底消灭碎片化隐患。
固定大小消息:嵌入式场景下消息体通常大小相近,采用固定大小的消息格式不仅实现简单,还能保证时间复杂度为O(1)。
04
状态机的无缝对接
事件从队列中取出后,需要精准分发给对应的业务模块。可以通过构建状态跳转表(State Transition Table)来实现:
定义一个包含函数指针的数组,将不同的系统状态(如空闲、连接、错误)映射到具体的处理函数(Handler)。
主分发入口只需一句 current_handler(evt),即可让当前状态自己决定怎么应对新事件,避免在主循环中出现冗长的 if-else 或 switch-case。


求点赞,求关注

夜雨聆风