消息系统
AC63 系列 MCU 固件中的轻量级事件消息队列,基于静态环形缓冲区和关中断临界区实现,为按键驱动、双 Bank 升级流程与应用主循环之间提供解耦的异步通信机制,支持 FIFO(先进先出)与 LIFO(后进先出)两种入队策略。
目的与范围
本页完整覆盖 BSP 消息系统的实现与集成方式,包括:
- 消息表与哨兵值
NO_MSG的定义(sdk/bsp/include/msg.h) - 环形消息池
msg_pool[]、读写游标与剩余容量管理(sdk/bsp/msg.c) - 临界区保护(
local_irq_disable()/local_irq_enable())与并发语义 - FIFO / LIFO 入队、出队、清空、初始化的完整控制流
- 消息生产者/消费者的集成点(按键驱动、升级流程、应用主循环)
本页不涉及上层具体业务逻辑,例如按键动作如何映射到具体功能、双 Bank 升级协议本身、应用层的消息分发处理细节——这些属于各自能力的独立目录页(如按键系统、双 Bank 升级、应用框架)。
概述
在裸机/轻量 RTOS 风格的 MCU 固件中,硬件事件(按键按下/长按、升级流程状态变化)通常产生于中断、定时器扫描或驱动上下文,而业务处理集中在主循环中。若让驱动直接调用业务代码,会造成强耦合且难以在中断上下文中安全执行。消息系统正是为这种场景设计的异步事件通道:
- 生产者(按键扫描、升级流程、应用事件)调用
msg_put_fifo()/msg_put_lifo()将事件 ID 写入消息池; - 消费者(应用主循环)调用
get_msg()取出消息并按 ID 分发处理; - 生产者与消费者之间通过环形缓冲解耦,任意一方阻塞或抖动都不会直接阻塞另一方。
设计上有几个关键决策:
- 静态内存、零动态分配:消息池是编译期固定大小的
u16数组(默认 10 条),适合内存受限的嵌入式环境,也避免了 malloc 带来的碎片与不确定性。 - 临界区采用关中断:所有 API 内部用
local_irq_disable()/local_irq_enable()包裹,保证 ISR 与主循环之间的原子性;临界区只包含几条赋值指令,关中断时间极短。 - 哨兵值
NO_MSG = 0xffff:消息 ID 从 0 递增且MSG_MAX被显式赋值为NO_MSG,因此0xffff永远不会是合法消息,天然可作为"无消息"返回值。 - FIFO 与 LIFO 双策略:普通事件按顺序处理(FIFO),紧急事件可用 LIFO"插队"到队头优先被消费。
架构
flowchart TD
subgraph sg_Producer["消息生产者(#include \"msg.h\" 的模块)"]
KeyDriver["key_driver.c 按键扫描"]
Boot["boot.c / dual_bank_update.c 升级流程"]
AppProducer["main.c 应用事件"]
TestBoard["board/test.c 板级测试"]
end
subgraph sg_API["消息系统 API(sdk/bsp/msg.c)"]
Init["msg_init()"]
Flush["flush_all_msg()"]
PutFIFO["msg_put_fifo(u16 msg)"]
PutLIFO["msg_put_lifo(u16 msg)"]
GetMsg["get_msg()"]
end
subgraph sg_Pool["消息池(临界区保护)"]
Pool[("msg_pool[10] 环形缓冲区<br/>msg_read / msg_write / msg_pool_residue")]
end
subgraph sg_Consumer["消息消费者"]
MainLoop["应用主循环 get_msg() 分发"]
end
Init --> Flush
Flush --> Pool
KeyDriver -->|"按键事件"| PutFIFO
AppProducer --> PutFIFO
TestBoard --> PutFIFO
Boot -->|"紧急状态"| PutLIFO
PutFIFO -->|"写尾指针"| Pool
PutLIFO -->|"写头指针(插队)"| Pool
Pool -->|"读头指针"| GetMsg
GetMsg --> MainLoop
架构说明:
- 消息池是唯一共享状态,由
msg_pool[MAX_POOL]数组加三个游标构成:msg_read(读位置)、msg_write(写位置)、msg_pool_residue(剩余空位)。msg_pool_residue的存在避免了传统环形缓冲区在"空"与"满"两种状态下读写指针重合的歧义:池空时residue == MAX_POOL,池满时residue == 0。 - 所有 API 都自包含临界区,调用方无需自行加锁;这是嵌入式消息队列的常见设计——把同步开销收敛到队列内部,简化调用方。
- 生产者多于消费者:按键驱动、升级流程、板级测试都可投递消息;消费者语义上是单消费者(
get_msg()每调用消费一条),多消费者场景需要外部额外互斥(源码未提供,见「故障模式、边界情况与并发」)。
核心实现
消息定义与哨兵值
msg.h 定义了空消息哨兵和消息表。消息表是一个匿名 enum,所有消息 ID 从 0 开始连续递增,最后一个成员 MSG_MAX 被显式赋值为 NO_MSG(即 0xffff):
//定义空消息
#define NO_MSG 0xffff
//消息表
enum {
MSG_TEST_IO_KEY1_SHORT,
MSG_TEST_IO_KEY1_LONG,
MSG_TEST_IO_KEY1_HOLD,
MSG_TEST_IO_KEY1_LONG_HOLD_UP,
MSG_TEST_IO_KEY2_SHORT,
MSG_TEST_IO_KEY2_LONG,
MSG_TEST_IO_KEY2_HOLD,
MSG_TEST_IO_KEY2_LONG_HOLD_UP,
MSG_MAX = NO_MSG,
};
Source: msg.h
设计意图:MSG_MAX = NO_MSG 这一赋值是编译期保证——所有合法消息 ID 必然小于 0xffff,因此 NO_MSG 可以安全地作为 get_msg() 的"无消息"哨兵返回值,无需额外标志位。当前示例消息表对应测试 IO 按键的短按/长按/持续按住/长按抬起事件;新增业务消息时,在 MSG_MAX 之前插入枚举成员即可,编译器会自动分配递增 ID。
消息池数据结构与临界区
msg.c 顶部定义了消息池容量、临界区宏和全局状态:
//消息池大小
#define MAX_POOL 10
//临界区管理
#define MSG_ENTER_CRITICAL() local_irq_disable()
#define MSG_EXIT_CRITICAL() local_irq_enable()
u16 msg_pool[MAX_POOL]; //消息池
u16 msg_read = 0; //消息读位置
u16 msg_write = 0; //消息写位置
u16 msg_pool_residue = MAX_POOL; //消息池剩余大小
Source: msg.c
要点分析:
MAX_POOL是编译期常量(默认 10),消息池共占用10 × 2 = 20字节 RAM,零动态内存。- 临界区宏可替换:
MSG_ENTER_CRITICAL()/MSG_EXIT_CRITICAL()封装了 BSP 的关中断/开中断 API。若移植到不同平台或改用 RTOS 的调度器锁,只需修改这两个宏,消息系统内部逻辑无需改动——这是本文件最重要的扩展点。 msg_pool_residue从MAX_POOL递减:入队减一、出队加一,通过它与 0 或MAX_POOL的比较即可判定池满/池空,避免读写指针相等时的二义性。
msg_put_fifo() —— 先进先出入队
u8 msg_put_fifo(u16 msg)
{
MSG_ENTER_CRITICAL();
if (msg_pool_residue == 0) {
MSG_EXIT_CRITICAL();
puts("err:msg pool is full!\n");
return -1;
}
msg_pool[msg_write] = msg;
msg_write++;
if (msg_write == MAX_POOL) {
msg_write = 0;
}
msg_pool_residue--;
MSG_EXIT_CRITICAL();
return 0;
}
Source: msg.c
控制流:
- 进入临界区(关中断);
- 若
msg_pool_residue == 0(池满),先退出临界区、打印err:msg pool is full!并返回-1; - 否则将消息写入
msg_pool[msg_write](队尾),msg_write自增; - 写指针到达
MAX_POOL时回绕到 0——环形缓冲区的经典回绕处理; msg_pool_residue--,退出临界区,返回 0。
注意满池时先退出临界区再打印,避免在关中断状态下执行耗时的串口输出,这是对中断实时性的细心处理。
msg_put_lifo() —— 后进先出"插队"入队
u8 msg_put_lifo(u16 msg)
{
MSG_ENTER_CRITICAL();
if (msg_pool_residue < 2) {
MSG_EXIT_CRITICAL();
puts("err:msg pool less than two\n");
return -1;
}
msg_read--;
if (msg_read == 0xffff) {
msg_read = MAX_POOL - 1;
}
msg_pool[msg_read] = msg;
msg_pool_residue--;
MSG_EXIT_CRITICAL();
return 0;
}
Source: msg.c
与 FIFO 的关键区别:
- 入队位置不同:FIFO 写在
msg_write(尾),LIFO 写在msg_read - 1(头),即新消息插入到队头,下一次get_msg()会优先读到这条消息——这正是"插队"语义,适合紧急事件(例如升级流程的状态切换)抢占普通按键事件。 - 下溢回绕:
msg_read--后若等于0xffff,说明跨越了数组下界,回绕到MAX_POOL - 1。 - 容量门槛更严:要求
msg_pool_residue >= 2才允许插入,比 FIFO 的residue >= 1多留一个空位。实现注释未说明原因,合理的推断是:LIFO 消息会抢占队头,若把池彻底占满(residue == 0),后续 FIFO 普通消息将完全无法入队,导致普通事件饿死;预留一个空位保证系统仍有接收普通事件的余地。
get_msg() —— 出队
u16 get_msg(void)
{
u16 msg = NO_MSG;
MSG_ENTER_CRITICAL();
if (msg_pool_residue < MAX_POOL) {
msg = msg_pool[msg_read];
msg_read++;
if (msg_read == MAX_POOL) {
msg_read = 0;
}
msg_pool_residue++;
}
MSG_EXIT_CRITICAL();
return msg;
}
Source: msg.c
控制流:
- 局部变量
msg初始化为NO_MSG哨兵; - 进入临界区;
- 若
msg_pool_residue < MAX_POOL说明池非空(有消息),从msg_pool[msg_read]读取队头消息,msg_read自增并回绕,msg_pool_residue++; - 若池空则保持
NO_MSG; - 退出临界区,返回消息值。
空池不报错,而是返回 NO_MSG,因此主循环可以无阻塞地轮询:msg = get_msg(); if (msg != NO_MSG) { ... }。由于 LIFO 会把消息写到 msg_read 之前,get_msg() 无需区分消息类型,天然先消费 LIFO 插队的消息。
flush_all_msg() 与 msg_init() —— 清空与初始化
void flush_all_msg(void)
{
MSG_ENTER_CRITICAL();
msg_read = 0;
msg_write = 0;
msg_pool_residue = MAX_POOL;
memset(msg_pool, 0xff, sizeof(msg_pool));
MSG_EXIT_CRITICAL();
}
void msg_init(void)
{
if (MAX_POOL < 3) {
while (1) {
puts("err:max pool less than 3\n");
}
}
flush_all_msg();
}
Source: msg.c
flush_all_msg()将三个游标复位并把整个池填充为0xff(即NO_MSG哨兵值),使池中残留数据不会在调试时被误读为合法消息。由于复位操作在临界区内完成,即使清空过程中发生中断,也不会观察到中间状态。msg_init()首先做编译期配置校验:若MAX_POOL < 3则进入死循环并反复打印err:max pool less than 3。这是嵌入式固件常见的"配置错误就地暴露"手法——MAX_POOL是编译期常量,若配置过小(连 FIFO 入队 + LIFO 插队 + 空位预留都满足不了)说明工程配置有误,与其带着隐患运行,不如在启动时死循环提示。初始化应在系统启动早期调用(boot.c已包含msg.h,属于消息系统的初始化入口之一)。
核心流程
典型 FIFO 消息生命周期
sequenceDiagram
participant P as 生产者(按键/升级/应用)
participant C as 消息系统 msg.c
participant Pool as msg_pool[10]
participant M as 主循环消费者
P->>C: msg_put_fifo(msg)
activate C
C->>C: local_irq_disable() 进入临界区
C->>C: msg_pool_residue == 0 ?
alt 池满
C-->>P: 返回 -1,打印 "err:msg pool is full!"
else 池未满
C->>Pool: msg_pool[msg_write] = msg
C->>C: msg_write++,== MAX_POOL 时回绕为 0
C->>C: msg_pool_residue--
C-->>P: 返回 0
end
C->>C: local_irq_enable() 退出临界区
deactivate C
M->>C: get_msg()
activate C
C->>C: local_irq_disable()
alt 池非空(msg_pool_residue < MAX_POOL)
C->>Pool: 读取 msg_pool[msg_read]
C->>C: msg_read++,== MAX_POOL 时回绕为 0
C->>C: msg_pool_residue++
C-->>M: 返回消息值
else 池空
C-->>M: 返回 NO_MSG(0xffff)
end
C->>C: local_irq_enable()
deactivate C
LIFO 插队判定流程
flowchart TD
Start(["msg_put_lifo(msg) 被调用"]) --> Enter["进入临界区<br/>local_irq_disable()"]
Enter --> Check{"msg_pool_residue < 2 ?"}
Check -->|"是(空位不足)"| Err["退出临界区<br/>返回 -1<br/>打印 err:msg pool less than two"]
Check -->|"否"| Dec["msg_read--"]
Dec --> Wrap{"msg_read == 0xffff ?"}
Wrap -->|"是(跨过数组下界)"| WrapBack["msg_read = MAX_POOL - 1"]
Wrap -->|"否"| Store["msg_pool[msg_read] = msg"]
WrapBack --> Store
Store --> Dec2["msg_pool_residue--"]
Dec2 --> Exit["退出临界区 local_irq_enable()<br/>返回 0"]
Err --> End([结束])
Exit --> End
系统集成点
通过 #include "msg.h" 的模块可以确认消息系统的实际参与者(本页基于源文件中的引用关系整理):
| 模块 | 角色 | 说明 |
|---|---|---|
sdk/apps/main.c | 生产者/消费者 | 应用入口,主循环中轮询 get_msg() 分发消息 |
sdk/bsp/AC632N/src/key_driver.c(及 AC635N/AC636N 对应文件) | 生产者 | 按键扫描产生 MSG_TEST_IO_KEY* 系列消息 |
sdk/bsp/AC632N/src/boot.c(及对应系列文件) | 初始化 | 启动阶段初始化消息系统 |
sdk/bsp/dual_bank_update.c | 生产者 | 双 Bank 升级流程状态上报 |
sdk/bsp/AC632N/board/test.c(及对应系列文件) | 生产者 | 板级测试程序验证消息通路 |
典型运行时序:系统上电 → boot.c 完成 msg_init() → 应用主循环启动 → 按键驱动在扫描上下文调用 msg_put_fifo() 投递按键事件 → 主循环 get_msg() 取出消息,若为 NO_MSG 则继续轮询,否则按消息 ID 分发处理。升级流程的紧急状态则通过 msg_put_lifo() 插队,确保被优先消费。
使用示例
示例 1:新增业务消息
在消息表中、MSG_MAX = NO_MSG 之前追加枚举成员,编译器自动分配 ID(始终小于 0xffff):
enum {
MSG_TEST_IO_KEY1_SHORT,
MSG_TEST_IO_KEY1_LONG,
MSG_TEST_IO_KEY1_HOLD,
MSG_TEST_IO_KEY1_LONG_HOLD_UP,
MSG_TEST_IO_KEY2_SHORT,
MSG_TEST_IO_KEY2_LONG,
MSG_TEST_IO_KEY2_HOLD,
MSG_TEST_IO_KEY2_LONG_HOLD_UP,
// 在此处新增业务消息,例如:
// MSG_UPGRADE_START,
// MSG_UPGRADE_DONE,
MSG_MAX = NO_MSG,
};
Source: msg.h
示例 2:生产者投递普通事件(FIFO)
按键驱动或应用模块在事件发生时调用 msg_put_fifo()。返回 0 表示投递成功,-1 表示消息池已满(此时事件被丢弃,调用方应自行决定是否重试或忽略):
u8 msg_put_fifo(u16 msg)
{
MSG_ENTER_CRITICAL();
if (msg_pool_residue == 0) {
MSG_EXIT_CRITICAL();
puts("err:msg pool is full!\n");
return -1;
}
msg_pool[msg_write] = msg;
msg_write++;
if (msg_write == MAX_POOL) {
msg_write = 0;
}
msg_pool_residue--;
MSG_EXIT_CRITICAL();
return 0;
}
Source: msg.c
示例 3:消费者主循环轮询消息
主循环以非阻塞方式轮询 get_msg():返回 NO_MSG 表示当前无消息,继续执行其他任务;否则按消息 ID 分发:
u16 get_msg(void)
{
u16 msg = NO_MSG;
MSG_ENTER_CRITICAL();
if (msg_pool_residue < MAX_POOL) {
msg = msg_pool[msg_read];
msg_read++;
if (msg_read == MAX_POOL) {
msg_read = 0;
}
msg_pool_residue++;
}
MSG_EXIT_CRITICAL();
return msg;
}
Source: msg.c
典型消费模式(伪代码,基于上述 API 语义):
for (;;) {
u16 msg = get_msg();
if (msg != NO_MSG) {
switch (msg) {
case MSG_TEST_IO_KEY1_SHORT: /* 处理按键1短按 */ break;
case MSG_TEST_IO_KEY2_LONG: /* 处理按键2长按 */ break;
default: break;
}
}
/* 其他主循环任务 */
}
示例 4:紧急事件插队(LIFO)
升级流程等需要抢占队头的场景使用 msg_put_lifo();注意其容量门槛为 msg_pool_residue >= 2,比 FIFO 更严格:
u8 msg_put_lifo(u16 msg)
{
MSG_ENTER_CRITICAL();
if (msg_pool_residue < 2) {
MSG_EXIT_CRITICAL();
puts("err:msg pool less than two\n");
return -1;
}
msg_read--;
if (msg_read == 0xffff) {
msg_read = MAX_POOL - 1;
}
msg_pool[msg_read] = msg;
msg_pool_residue--;
MSG_EXIT_CRITICAL();
return 0;
}
Source: msg.c
示例 5:系统启动初始化
启动阶段调用 msg_init() 完成游标复位与池内容填充(0xff),若 MAX_POOL < 3 则死循环报错:
void msg_init(void)
{
if (MAX_POOL < 3) {
while (1) {
puts("err:max pool less than 3\n");
}
}
flush_all_msg();
}
Source: msg.c
配置选项
消息系统没有运行时配置,全部为编译期宏/常量,定义于 sdk/bsp/msg.c 与 sdk/bsp/include/msg.h:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
MAX_POOL | 宏(msg.c) | 10 | 消息池容量,即环形缓冲区最多容纳的消息条数;要求 ≥ 3,否则 msg_init() 死循环报错 |
NO_MSG | 宏(msg.h) | 0xffff | 空消息哨兵,既是 MSG_MAX 的取值,也是 get_msg() 空池返回值 |
MSG_ENTER_CRITICAL() | 宏(msg.c) | local_irq_disable() | 进入临界区操作,可替换为平台相关的关中断/调度器锁实现 |
MSG_EXIT_CRITICAL() | 宏(msg.c) | local_irq_enable() | 退出临界区操作,与 MSG_ENTER_CRITICAL() 成对替换 |
调整容量时只需修改 MAX_POOL,消息池数组 msg_pool[MAX_POOL] 会自动随之伸缩(RAM 占用为 MAX_POOL × 2 字节)。注意:增大容量会延长极端情况下满池打印前可缓存的消息数,但不会影响各 API 的 O(1) 时间复杂度。
API 参考
u8 msg_put_fifo(u16 msg)
以先进先出方式将消息放入队尾。
- 参数:
msg(u16)——消息 ID,应来自消息表枚举(必须小于NO_MSG)。 - 返回:
0表示入队成功;-1表示消息池已满(msg_pool_residue == 0),消息被丢弃并打印err:msg pool is full!。 - 并发:内部自含临界区,可在中断上下文调用。
u8 msg_put_lifo(u16 msg)
以后进先出方式将消息插入队头(插队),使下一次 get_msg() 优先返回该消息。
- 参数:
msg(u16)——消息 ID。 - 返回:
0表示入队成功;-1表示剩余空位不足 2(msg_pool_residue < 2),打印err:msg pool less than two。 - 说明:容量门槛比 FIFO 更严(要求至少 2 个空位),为普通 FIFO 消息保留入队空间。
u16 get_msg(void)
从队头取出一个消息。
- 返回:消息值;若消息池为空则返回
NO_MSG(0xffff)。每调用一次消费一条消息。 - 并发:内部自含临界区;单消费者语义,多个消费者并发调用需要外部额外互斥。
void flush_all_msg(void)
清空消息池:复位 msg_read、msg_write、msg_pool_residue,并将整个池填充为 0xff(NO_MSG)。
- 参数/返回:无。
- 用途:系统复位、模式切换(如进入低功耗/升级模式前丢弃积压事件)。
void msg_init(void)
初始化消息系统:校验 MAX_POOL >= 3(否则死循环打印 err:max pool less than 3),随后调用 flush_all_msg()。
- 参数/返回:无。
- 调用时机:系统启动早期,
boot.c等启动流程中。
故障模式、边界情况与并发
消息池满(FIFO)
当 msg_pool_residue == 0 时 msg_put_fifo() 返回 -1 并打印 err:msg pool is full!。消息被静默丢弃,调用方只能根据返回值感知。对于不可丢失的关键事件(如升级完成通知),调用方应实现重试或等待策略;对于高频按键事件,丢弃通常是可接受的(用户会再次按键)。
空位不足(LIFO)
msg_put_lifo() 要求 msg_pool_residue >= 2,否则返回 -1 并打印 err:msg pool less than two。该设计预留一个空位,避免 LIFO 插队把池占满导致普通 FIFO 事件完全无法入队。
环形回绕与下溢
- FIFO 写指针
msg_write与出队读指针msg_read在递增到MAX_POOL时回绕为 0; - LIFO 将
msg_read递减,当跨过数组下界(msg_read == 0xffff)时回绕到MAX_POOL - 1。 - 回绕逻辑均位于临界区内,配合
msg_pool_residue判断,读写指针相遇不会引发数据损坏。
空池读取
get_msg() 在池空时返回 NO_MSG 而非错误码,使主循环可以无阻塞轮询;哨兵值由 MSG_MAX = NO_MSG 编译期保证不会与合法消息冲突。
中断与主循环并发
所有 API 通过 local_irq_disable() / local_irq_enable() 实现原子性,因此可在中断上下文安全投递消息(典型场景:按键扫描在定时器中断中入队,主循环出队)。代价是临界区内会短暂关闭全局中断;由于临界区仅包含数组读写与游标自增等数条指令,对系统实时性影响极小。若系统中存在高实时性外设(如音频 DMA),应评估关中断时间是否可接受,或考虑将临界区宏替换为基于优先级的中断屏蔽方案。
单消费者语义
get_msg() 每调用消费一条,且没有任务级互斥。若多个任务/上下文同时调用 get_msg(),可能出现消息竞争(例如 A 任务读到 NO_MSG 的同时 B 任务消费了实际消息)。源码假定单消费者(应用主循环);引入多消费者时需要外部加锁或在消息分发层串行化。
初始化配置错误
msg_init() 在 MAX_POOL < 3 时进入死循环并打印错误。这是刻意的"失败即停"策略:MAX_POOL 是编译期常量,若配置过小说明工程配置有误,继续运行会在运行时反复丢消息,死循环能第一时间暴露问题。
内存占用与溢出风险
消息池是静态数组,不存在堆溢出风险;但 msg_pool_residue、msg_read、msg_write 均为 u16,在 MAX_POOL 不超过 65535 的前提下不会溢出。当前默认值 10 远小于该上限。
性能与运维
- 时间复杂度:
msg_put_fifo、msg_put_lifo、get_msg、flush_all_msg均为 O(1);msg_init额外执行一次 O(MAX_POOL) 的memset(仅启动时)。 - 内存占用:消息池固定
MAX_POOL × 2字节(默认 20 字节),加 3 个u16游标,总计约 26 字节 RAM,无动态内存、无碎片。 - 临界区开销:每次 API 调用执行一次关中断/开中断,临界区仅数条指令。投递/消费频率受关中断时长限制,对典型按键事件(毫秒级)完全够用;若消息频率极高(如每微秒级),应评估关中断累积开销。
- 可观测性:满池与容量不足时会通过
puts()输出错误串(err:msg pool is full!/err:msg pool less than two),配合串口即可定位"事件丢失"类问题。调试时可用flush_all_msg()丢弃积压消息后重新观察。 - 复位语义:
msg_init()应在任何生产者开始投递之前完成;模式切换(升级、低功耗)前调用flush_all_msg()可避免旧事件在新模式下被误处理。
扩展点
- 新增消息类型:在
msg.h的枚举中、MSG_MAX = NO_MSG之前插入新成员,编译器自动分配 ID。消息系统对消息内容零感知——消息就是u16ID,语义完全由生产者和消费者约定,因此新增消息不需要改动msg.c。 - 调整消息池容量:修改
MAX_POOL即可(RAM 按MAX_POOL × 2增长);注意保持 ≥ 3,否则msg_init()死循环。 - 新增生产者:任何模块
#include "msg.h"后调用msg_put_fifo()/msg_put_lifo()即可,无需注册、无需修改消息系统。当前已知生产者包括按键驱动(key_driver.c)、双 Bank 升级(dual_bank_update.c)、板级测试(board/test.c)与应用(main.c)。 - 替换临界区实现:修改
MSG_ENTER_CRITICAL()/MSG_EXIT_CRITICAL()宏,例如适配 RTOS 的调度器锁或不同架构的关中断指令。这是将消息系统移植到新平台的主要改动点。 - 扩展消费者:
get_msg()语义固定为单消费者;若需多消费者/优先级消费,可在主循环分发层之上包装(例如按消息 ID 路由到不同任务),而不修改队列本身。
测试
仓库中未发现针对 msg.c 的独立单元测试文件。消息系统的验证主要依赖两类途径:
- 板级测试程序:
sdk/bsp/AC632N/board/test.c、sdk/bsp/AC635N/board/test.c、sdk/bsp/AC636N/board/test.c均包含msg.h,可在生产板上通过按键/串口交互验证消息投递与消费通路。 - 集成验证:消息满池/空位不足时的
puts()错误输出,可在系统测试中作为"事件丢失"的判定依据。
如需补充自动化测试,建议针对四个关键边界编写用例:FIFO 满池返回 -1、LIFO 空位不足返回 -1、读写指针回绕(MAX_POOL 处与 0xffff 下溢)、空池 get_msg() 返回 NO_MSG。