消息机制 (msg / hot_msg)
AW30N BLE SDK 的片上消息机制:msg 模块提供基于环形缓冲区与事件位图的消息投递/取出原语(post_msg / post_event / get_msg),hot_msg 模块则在应用层集中处理"热消息"(ap_handle_hotkey),是按键、解码器、USB/存储设备、BLE 升级等事件源与应用逻辑之间的统一通道。
Purpose and Scope
本页面向工程师完整解释 AW30N SDK 的消息机制,覆盖:
- 消息 ID 的全局枚举组织方式(msg.h 中的
MSG_*宏定义); - 内核消息模块(msg.c)的实现:环形缓冲
msg_cbuf、事件位图event_buf、event2msg映射表、以及post_msg/get_msg/post_event等原语; - 应用层"热消息"集中处理器(hot_msg.c)中
ap_handle_hotkey的分发逻辑。
以下主题属于兄弟页面,不在本页展开:解码器内部的消息路由表(decoder_msg_tab.c)、RCSP 蓝牙协议消息(rcsp_msg.c)、按键扫描与键值映射(key_msg.lua)、以及具体工作模式(音乐/FM/录音)的业务实现。本页聚焦"消息如何被产生、排队、取出与分发"这一通用机制本身。
Overview
在资源受限的嵌入式 BLE SoC 上,各模块(按键扫描、解码器、USB/存储热插拔、蓝牙、定时器)运行在不同中断/上下文环境中,彼此不能直接调用对方的业务函数。消息机制提供了一套异步、无锁(关中断临界区)、单一消费点的通信方式:
- 生产者:任意上下文(中断、任务、库回调)调用
post_msg(argc, ...)或post_event(event)投递消息; - 队列:普通消息写入环形缓冲
msg_cbuf(存储于msg_pool),事件消息写入位图event_buf; - 消费者:应用主循环通过
get_msg(len, msg)取出消息,其中事件优先级高于普通消息; - 分发:取出的消息交给
ap_handle_hotkey()(hot_msg)等处理函数集中分发。
该设计的关键动机:
- 解耦生产与消费:中断里只做"投递"这一件极短的事,业务逻辑统一在主循环中执行,避免在中断上下文做耗时操作;
- 确定性:所有队列操作都在
OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL()临界区内完成,保证单核 MCU 上的原子性; - 低功耗友好:无消息时
get_msg_phy会执行idle指令进入低功耗等待,配合wdt_clear()保持系统稳定; - 位图压缩事件:解码器"文件结束/出错/循环"这类高频事件用 1 bit 表达,既省内存又可用
clz指令快速查找。
Architecture
flowchart TD
subgraph sg_Producers["消息生产者(中断 / 任务 / 库回调)"]
Key["按键 / IR 扫描"]
Decoder["解码器回调"]
USB["USB / 存储热插拔"]
BLE["BLE 升级 / 协议栈"]
Timer["系统定时器"]
end
subgraph sg_Core["msg 核心模块 (msg.c)"]
PostMsg["post_msg(argc, ...)<br/>变长参数入队"]
PostEvent["post_event(event)<br/>位图置位"]
CBuf["msg_cbuf 环形缓冲<br/>(msg_pool)"]
EventBuf["event_buf 事件位图<br/>+ event2msg 映射表"]
GetMsg["get_msg(len, msg)<br/>事件优先,其次环形缓冲"]
end
subgraph sg_Consumer["应用层消费与分发"]
MainLoop["主循环 / 任务"]
HotMsg["ap_handle_hotkey(key)<br/>hot_msg.c 集中分发"]
AppLogic["应用业务逻辑<br/>(电源 / 音量 / 模式 / 升级)"]
end
Key --> PostMsg
Key --> PostEvent
Decoder --> PostEvent
USB --> PostMsg
BLE --> PostMsg
Timer --> PostMsg
PostMsg --> CBuf
PostEvent --> EventBuf
EventBuf --> GetMsg
CBuf --> GetMsg
GetMsg --> MainLoop
MainLoop --> HotMsg
HotMsg --> AppLogic
上图中,所有生产者只与 msg 核心模块交互,核心模块不感知业务;消费侧只有一个出口 get_msg,再由 hot_msg 模块的 ap_handle_hotkey 按消息 ID 分发到具体业务。post_event 走位图(去重、省内存),post_msg 走环形缓冲(保留参数、可重复),二者在 get_msg 中汇合时事件优先。
消息 ID 的全局枚举
消息 ID 是整套机制的唯一"语言"。sdk/apps/include_lib/msg/msg.h 用一个巨型 enum 定义了全部 MSG_* 常量,按功能分区排列:
| 分区 | 起始 | 典型消息 | 说明 |
|---|---|---|---|
| 系统保留 | MSG_0 = 0 | MSG_0 ~ MSG_9、MSG_PP_2、MSG_RECODE_START/END | 基础编号,MSG_0~MSG_9 在 IR 输入场景被复用 |
| APP 级 | 紧跟其后 | MSG_500MS、MSG_APP_SWITCH_ACTIVE、MSG_LOW_POWER、MSG_ENTER_IDLE、MSG_POWER_OFF | 应用主循环节拍与电源状态 |
| 音乐操作 | — | MSG_PP、MSG_NEXT_FILE、MSG_PREV_FILE、MSG_VOL_UP/DOWN、MSG_A_PLAY | 播放/切歌/音量 |
| 录音 / MIDI / RF | — | MSG_REC_MODE_SWITCH、MSG_MIDICTRL_NOTE_ON_*、MSG_SENDER_START 等 | 外设功能组 |
| mbox 应用消息 | MSG_CHANGE_WORK_MODE = 0x600 | MSG_MUSIC_*、MSG_FM_*、MSG_CH_SET、MSG_NEXT_WORKMODE 等 | 工作模式与音乐/FM 业务 |
| 定时/解码器 | — | MSG_10MS、MSG_100MS、MSG_200MS、MSG_DECODE_USER_END、MSG_DECODE_FILE_END | 周期消息与解码结束通知 |
| RTC | — | MSG_READ_CLOCK、MSG_WRITE_ALARM、MSG_ALARM、MSG_POWER_DOWN | 时钟/闹钟 |
| 设备热插拔 | — | MSG_USB_DISK_IN、MSG_SDMMCA_IN、MSG_EXTFLSH_IN、MSG_OTG_IN、MSG_PC_IN | 存储/PC 接入断开 |
| 库系统消息 | MSG_F1A1_FILE_END = 0x800 | MSG_MP3_FILE_END、MSG_WAV_FILE_ERR、MSG_WFILE_FULL 等 | 库会使用,不能更改 |
msg.h 中有两条不可破坏的顺序约束,直接对应 hot_msg.c 的算法:
MSG_USB_DISK_IN与MSG_SDMMCA_IN必须相邻且中间不可插入其他消息——hot_msg.c用key - MSG_USB_DISK_IN计算升级设备号(见 msg.h);0x800之后为库保留段,"系统相关消息,库会使用到,不能更改"(见 msg.h)。
设计意图:采用单一全局枚举而非每模块独立编号,使任何模块都能用同一套 switch 集中处理,并让 event2msg 位图索引与消息 ID 的解耦映射成为可能;同时用显式注释标出"不可调整"的槽位,防止裁剪/升级时破坏二进制兼容。
msg 核心模块实现剖析
msg.c 位于 sdk/apps/app/bsp/common/msg/msg.c,是整个机制的"内核"。其实现有四个关键点:段定位、环形缓冲、事件位图、优先级取出逻辑。
段定位(Section Placement)
文件头部用 #pragma 将全部代码与数据放入专用段:
#pragma bss_seg(".msg.data.bss")
#pragma data_seg(".msg.data")
#pragma const_seg(".msg.text.const")
#pragma code_seg(".msg.text")
#pragma str_literal_override(".msg.text.const")
Source: msg.c
这是杰理平台 SDK 的典型做法:把消息模块的代码/常量/变量集中映射到固定 RAM/ROM 区域,便于链接脚本做内存规划(例如放在不随补丁包覆盖的区域),也保证该模块在代码裁剪(256KB 补丁包)时行为一致。
全局状态与事件映射表
#define EVENT_TOTAL (1+(sizeof(event2msg)/2 -1)/32)
static const u16 event2msg[] = {
MSG_F1A1_FILE_END, /* 0 */
MSG_F1A1_FILE_ERR, /* 1 */
MSG_F1A1_LOOP,
...
MSG_UART_TESTBOX_UPDATE_START, /* 37 */
NO_MSG,
};
static cbuffer_t msg_cbuf;
static u32 event_buf[EVENT_TOTAL];
static u32 msg_pool[MAX_POOL];
Source: msg.c
event2msg[]是事件索引 → 消息 ID 的静态映射:解码器各类格式的FILE_END / FILE_ERR / LOOP、设备接入断开(MSG_OTG_IN、MSG_USB_DISK_IN、MSG_PC_IN…)、三类升级启动消息(MSG_BLE_APP_UPDATE_START等)都通过这张表转为消息。EVENT_TOTAL按 32 位一组计算位图需要的字数量,避免硬编码。msg_cbuf是环形缓冲控制块,数据实体存放在msg_pool[MAX_POOL]中;event_buf[]是事件位图。
设计意图:解码器/设备事件语义上是"去重的状态通知"(同一时刻某事件只关心"有没有"),用位图表达比在环形缓冲里塞重复消息更省内存、更快;而普通消息(带参数、可重复)则必须走环形缓冲。
事件位图操作
static void event_init(void)
{
CPU_SR_ALLOC();
OS_ENTER_CRITICAL();
for (u32 i = 0; i < EVENT_TOTAL; i++) {
event_buf[i] = 0;
}
OS_EXIT_CRITICAL();
}
void clear_one_event(u32 event)
{
if (event >= ARRAY_SIZE(event2msg)) {
return;
}
CPU_SR_ALLOC();
OS_ENTER_CRITICAL();
event_buf[event / 32] &= ~BIT(event % 32);
OS_EXIT_CRITICAL();
}
static u32 get_event(void)
{
u32 i;
CPU_SR_ALLOC();
OS_ENTER_CRITICAL();
u32 event_cls;
for (u32 i = 0; i < EVENT_TOTAL; i++) {
__asm__ volatile("%0 = clz(%1)":"=r"(event_cls):"r"(event_buf[i]));
if (event_cls != 32) {
OS_EXIT_CRITICAL();
return i * 32 + (31 - event_cls);
}
}
OS_EXIT_CRITICAL();
return NO_EVENT;
}
Source: msg.c
- 置位/清位/查询都包在
OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL()中,保证与中断上下文的原子性; get_event()用 DSP 指令clz(count leading zeros)在单个周期内找到最高置位 bit,将event转换为i*32 + (31 - event_cls)的事件索引——这是嵌入式场景下"用指令换循环"的经典优化;- 事件位被取出后由调用方
clear_one_event()清除(见get_msg),保证"只消费一次"。
消息的打包与取出(核心路径)
post_msg 是变长参数投递入口:
int post_msg(int argc, ...)
{
u32 param;
u16 *t_msg = (u16 *)¶m;
CPU_SR_ALLOC();
va_list argptr;
OS_ENTER_CRITICAL();
va_start(argptr, argc);
if (!cbuf_is_write_able(&msg_cbuf, (argc - 1)*sizeof(int) + MSG_HEADER_BYTE_LEN)) {
OS_EXIT_CRITICAL();
return MSG_BUF_NOT_ENOUGH;
}
...
}
Source: msg.c
而 get_msg_phy 展示了消息头的解码格式:
int get_msg_phy(int len, int *msg, bool idle)
{
u32 param = 0;
u16 *t_msg = (u16 *)¶m;
CPU_SR_ALLOC();
OS_ENTER_CRITICAL();
u32 tlen = cbuf_read(&msg_cbuf, (void *)t_msg, MSG_HEADER_BYTE_LEN);
if (MSG_HEADER_BYTE_LEN != tlen) {
OS_EXIT_CRITICAL();
if (idle) {
__asm__ volatile("idle");
}
msg[0] = NO_MSG;
return MSG_NO_MSG;
}
msg[0] = t_msg[0] & (MSG_HEADER_ALL_BIT >> MSG_PARAM_BIT_LEN);
u32 param_len = param >> MSG_TYPE_BIT_LEN;
if (param_len > (len - 1)) {
OS_EXIT_CRITICAL();
return MSG_BUF_NOT_ENOUGH;
}
u32 rlen = cbuf_read(&msg_cbuf, (void *)(msg + 1), param_len * sizeof(int));
if ((param_len * sizeof(int)) != rlen) {
OS_EXIT_CRITICAL();
return MSG_CBUF_ERROR;
}
OS_EXIT_CRITICAL();
return MSG_NO_ERROR;
}
Source: msg.c
可以还原出消息的帧格式:
- 头部固定
MSG_HEADER_BYTE_LEN字节(一个u32 param,按u16视图切分); - 低位
MSG_TYPE_BIT_LEN位为消息类型(t_msg[0] & (MSG_HEADER_ALL_BIT >> MSG_PARAM_BIT_LEN)); - 高位
MSG_PARAM_BIT_LEN位记录参数个数param_len = param >> MSG_TYPE_BIT_LEN; - 头部之后紧跟
param_len个int型参数,读出后依次放入调用者提供的msg[1..]; - 无消息时若
idle == true执行idle汇编指令让 CPU 进入低功耗等待(这正是主循环取消息时的节电路径)。
get_msg 把两条取数路径合并,并规定事件优先:
int get_msg(int len, int *msg)
{
u32 param = 0;
u16 *t_msg = (u16 *)¶m;
CPU_SR_ALLOC();
OS_ENTER_CRITICAL();
u32 event = get_event();
if (event != NO_EVENT) {
clear_one_event(event);
msg[0] = event2msg[event];
OS_EXIT_CRITICAL();
return MSG_NO_ERROR;
}
OS_EXIT_CRITICAL();
return get_msg_phy(len, msg, 1);
}
Source: msg.c
设计意图:get_msg 先查事件位图、再查环形缓冲。因为事件(如"某首歌播完")代表状态跳变,必须尽早通知应用;而普通消息(如音量键)允许在队列中排队。get_msg 单次只返回一条消息,由主循环反复调用,天然形成"一次一消息"的串行消费模型,杜绝了多消费者竞争。
事件投递与状态查询
int post_event(int event)
{
if (event >= ARRAY_SIZE(event2msg)) {
return MSG_EVENT_PARAM_ERROR;
}
CPU_SR_ALLOC();
OS_ENTER_CRITICAL();
event_buf[event / 32] |= BIT(event % 32);
OS_EXIT_CRITICAL();
return MSG_NO_ERROR;
}
bool get_event_status(u32 event)
{
CPU_SR_ALLOC();
OS_ENTER_CRITICAL();
if (event_buf[event / 32] & BIT(event % 32)) {
OS_EXIT_CRITICAL();
return TRUE;
}
OS_EXIT_CRITICAL();
return FALSE;
}
bool has_sys_event(void)
{
for (u32 i = 0; i < EVENT_TOTAL; i++) {
if (0 != event_buf[i]) {
return true;
}
}
return FALSE;
}
Source: [msg.c](https://gitee.com/Jieli-Tech/AW30N/blob/main/sdk/apps/app/bsp/common/msg/msg.c#L128-L151, msg.c
post_event对越界事件返回MSG_EVENT_PARAM_ERROR,防止位图越界写;get_event_status供其他模块查询事件状态(不消费);has_sys_event是快速判空,供主循环决定是否进入取消息流程。
hot_msg:应用层热消息处理器
hot_msg(sdk/apps/app/src/mbox_flash/common/hot_msg.c / hot_msg.h)是消息机制在应用层的"落地端"。它只暴露一个接口:
#ifndef __HOT_MSG_H__
#define __HOT_MSG_H__
#include "typedef.h"
#include "bsp_loop.h"
void ap_handle_hotkey(u16 key);
#endif
Source: hot_msg.h
所谓"热消息"(hot key)指主循环热路径上高频出现、必须快速响应的消息。ap_handle_hotkey(u16 key) 用一个巨型 switch 按消息 ID 集中分发:
void ap_handle_hotkey(u16 key)
{
u8 vol = 0;
int err = 0;
switch (key) {
case MSG_500MS:
app_power_scan();
wdt_clear();
/* 模拟量的调节,电压不要急升 */
audio_lookup();
#if TCFG_CHARGE_ENABLE
charge_set_vbat_voltage(app_power_get_vbat());
#endif
music_vol_update();
break;
//-------------音量调节
case MSG_VOL_UP:
vol = dac_vol('+', 255);
goto __app_vol_deal;
case MSG_VOL_DOWN:
vol = dac_vol('-', 255);
__app_vol_deal:
log_info("VOL:%d \n", vol);
UI_menu(MENU_MAIN_VOL, 0);
break;
...
case MSG_USB_DISK_IN: //应用为了节省代码将插U盘和插卡消息分支写在一起,中间不可插入其他消息,否则影响设备升级
log_info("udisk in\n");
case MSG_SDMMCA_IN:
if (time_before(maskrom_get_jiffies(), 150)) {
break;//上电1.5s内不响应设备上线消息
}
#if TFG_DEV_UPGRADE_SUPPORT
u8 update_dev = key - MSG_USB_DISK_IN;
device_update(update_dev);
#endif
break;
...
case MSG_NEXT_WORKMODE:
log_info("MSG_NEXT_WORKMODE\n");
work_mode++;
post_msg(1, MSG_CHANGE_WORK_MODE);
break;
case MSG_POWER_OFF:
log_info("MSG_POWER_OFF\n");
work_mode = SOFTOFF_MODE;
post_msg(1, MSG_CHANGE_WORK_MODE);
break;
}
}
Source: hot_msg.c
该处理器承担几类典型职责,每个 case 都体现了"消息机制 + 业务"的协作模式:
| 消息 | 处理动作 | 说明 |
|---|---|---|
MSG_500MS | app_power_scan()、wdt_clear()、audio_lookup()、charge_set_vbat_voltage()、music_vol_update() | 500ms 系统节拍:电源扫描、喂狗、音频查表、充电电压跟随、音量回写 sysmem |
MSG_VOL_UP/DOWN | dac_vol('+/-', 255) → UI_menu(MENU_MAIN_VOL, 0) | 音量调节走 DAC API 并刷新 UI(用 goto 合并公共尾部) |
MSG_OTG_IN/OUT | usb_host_mount(0, 3, 20, 200) / usb_host_unmount(0),随后 post_event(EVENT_UDISK_IN/OUT) | USB 主机挂载,成功后再投递事件驱动上层状态机 |
MSG_PC_IN | work_mode = USB_SLAVE_MODE,post_msg(1, MSG_CHANGE_WORK_MODE) | 切到 PC 从机模式并重投递模式切换消息 |
MSG_USB_DISK_IN / MSG_SDMMCA_IN | 上电 1.5s 内忽略;否则 device_update(key - MSG_USB_DISK_IN) | 依赖 msg.h 中两个消息必须相邻的顺序约束 |
MSG_BLE_APP_UPDATE_START 等 | app_update_handle(key)(UPDATE_V2_EN 使能时) | BLE/串口测试盒升级入口 |
MSG_NEXT_WORKMODE / MSG_POWER_OFF | 修改 work_mode 后 post_msg(1, MSG_CHANGE_WORK_MODE) | 消息→状态变更→再投递,形成状态机迁移链 |
MSG_0 ~ MSG_9 | IR 数字输入累加 Input_Number = Input_Number * 10 + key,UI_menu(MENU_INPUT_NUMBER, 0) | KEY_IR_EN 使能时复用系统保留消息做遥控器数字选台 |
关键模式:
- 重投递(re-post):
MSG_PC_IN、MSG_NEXT_WORKMODE、MSG_POWER_OFF都不直接执行模式切换逻辑,而是设置work_mode后重新post_msg(1, MSG_CHANGE_WORK_MODE)——把"变更请求"统一收敛为"模式切换"这一条消息,让模式状态机只有单一入口; - 级联事件:
MSG_OTG_IN处理后post_event(EVENT_UDISK_IN),把"硬件层面事件"翻译成"业务层面事件",由event2msg之外的业务位图继续驱动; - 上电保护:
maskrom_get_jiffies() < 150的 1.5s 窗口内忽略存储设备上线消息,避免上电瞬间误触发设备升级; - 音量持久化:
music_vol_update()在音量与SYSMEM_INDEX_VOL不一致时回写 sysmem,保证音量掉电不丢失(见 hot_msg.c)。
Core Flow:消息从产生到分发的完整时序
sequenceDiagram
participant P as 生产者(中断/库回调)
participant M as msg.c 核心
participant B as msg_cbuf / event_buf
participant L as 主循环
participant H as ap_handle_hotkey
participant A as 应用业务
alt 带参数消息(如音量键)
P->>M: post_msg(argc, ...)
M->>M: OS_ENTER_CRITICAL()
M->>B: cbuf 写头部(类型+参数个数)+参数
M->>M: OS_EXIT_CRITICAL()
else 去重事件(如解码器文件结束)
P->>M: post_event(event)
M->>M: OS_ENTER_CRITICAL()
M->>B: event_buf[event/32] \|= BIT(event%32)
M->>M: OS_EXIT_CRITICAL()
end
loop 主循环反复取消息
L->>M: get_msg(len, msg)
M->>B: get_event() 查位图(clz)
alt 有事件
M->>B: clear_one_event(event)
B-->>L: msg[0] = event2msg[event]
else 无事件
M->>B: cbuf 读头部与参数
B-->>L: msg[0]=类型, msg[1..]=参数
end
L->>H: ap_handle_hotkey(msg[0])
H->>A: switch 分发到业务
A-->>H: 可能 post_msg/post_event 再次投递
end
时序要点:
- 生产端:无论中断还是普通任务,
post_msg/post_event都只在临界区内做"拷贝/置位",不触碰业务; - 消费端:主循环调用
get_msg,事件优先(clz查位图),无事件时读环形缓冲;空队列且idle=true时执行idle指令省电; - 分发端:
ap_handle_hotkey一个switch覆盖所有"热消息",业务可以反向再投递(重投递模式切换、级联事件),从而形成下一轮消息循环。
Usage Examples
示例 1:投递带参数的模式切换消息
应用需要切换工作模式时,只投递消息、不直接执行切换:
case MSG_NEXT_WORKMODE:
log_info("MSG_NEXT_WORKMODE\n");
work_mode++;
post_msg(1, MSG_CHANGE_WORK_MODE);
break;
case MSG_POWER_OFF:
log_info("MSG_POWER_OFF\n");
work_mode = SOFTOFF_MODE;
post_msg(1, MSG_CHANGE_WORK_MODE);
break;
Source: hot_msg.c
示例 2:硬件事件 → 业务事件级联
USB 设备热插拔经 post_event 翻译为业务事件,供上层状态机消费:
case MSG_OTG_IN:
if (0 != usb_host_mount(0, 3, 20, 200)) {
log_info("mount err!\n");
break;
}
post_event(EVENT_UDISK_IN);
break;
case MSG_OTG_OUT:
usb_host_unmount(0);
post_event(EVENT_UDISK_OUT);
break;
Source: hot_msg.c
示例 3:500ms 节拍中的聚合任务
一条 MSG_500MS 里串行完成电源扫描、喂狗、音频查表、充电电压、音量持久化,体现"定时器只投递节拍、业务集中执行":
case MSG_500MS:
app_power_scan();
wdt_clear();
/* 模拟量的调节,电压不要急升 */
audio_lookup();
#if TCFG_CHARGE_ENABLE
charge_set_vbat_voltage(app_power_get_vbat());
#endif
music_vol_update();
break;
Source: hot_msg.c
示例 4:事件位图的原语用法(核心模块内部)
事件投递与消费是 get_msg / post_event 的对称操作,均受临界区保护:
int post_event(int event)
{
if (event >= ARRAY_SIZE(event2msg)) {
return MSG_EVENT_PARAM_ERROR;
}
CPU_SR_ALLOC();
OS_ENTER_CRITICAL();
event_buf[event / 32] |= BIT(event % 32);
OS_EXIT_CRITICAL();
return MSG_NO_ERROR;
}
Source: msg.c
Configuration Options
消息机制的行为由一组编译期宏与消息枚举共同控制(均为 #if/#define 静态配置,无运行时配置项):
| 配置项 | 类型 | 默认/取值 | 作用位置 | 说明 |
|---|---|---|---|---|
MAX_POOL | 宏 | 由工程配置 | msg.c 的 msg_pool[MAX_POOL] | 环形缓冲数据池大小(u32 为单位),决定可排队消息总量 |
MSG_HEADER_BYTE_LEN / MSG_TYPE_BIT_LEN / MSG_PARAM_BIT_LEN / MSG_HEADER_ALL_BIT | 宏 | 库头文件定义 | msg.c 打包/解包 | 消息头帧格式:类型位宽与参数个数位宽 |
NO_MSG / NO_EVENT | 枚举 | 特殊值 | msg.c | 空队列/空事件标记 |
TCFG_CHARGE_ENABLE | 宏 | 工程配置 | hot_msg.c | 使能充电电压跟随逻辑 |
TCFG_UDISK_ENABLE | 宏 | 工程配置 | hot_msg.c | 使能 U 盘(OTG)分支 |
TCFG_PC_ENABLE | 宏 | 工程配置 | hot_msg.c | 使能 PC 从机模式分支 |
TFG_DEV_UPGRADE_SUPPORT | 宏 | 工程配置 | hot_msg.c | 使能 U 盘/卡设备升级(依赖 MSG_USB_DISK_IN/MSG_SDMMCA_IN 相邻) |
UPDATE_V2_EN | 宏 | 工程配置 | hot_msg.c | 使能 V2 升级(BLE APP / 测试盒)消息处理 |
KEY_IR_EN | 宏 | 工程配置 | hot_msg.c | 使能 IR 遥控器数字键(MSG_0~MSG_9)分支 |
说明:
MSG_HEADER_*系列宏定义于消息库头文件(msg.h),MAX_POOL由板级/工程配置给出;本仓库 msg.c 中直接引用,具体数值以实际工程链接配置为准。
API Reference
int post_msg(int argc, ...)
向环形缓冲投递一条带参数消息。
Parameters:
argc(int): 参数个数 + 1(argc=1表示无参数,见post_msg(1, MSG_CHANGE_WORK_MODE))...(int...): 至多argc-1个int型消息参数
Returns:
MSG_NO_ERROR: 投递成功MSG_BUF_NOT_ENOUGH: 环形缓冲剩余空间不足(argc-1)*sizeof(int) + MSG_HEADER_BYTE_LEN
说明: 整个入队过程在临界区内完成,可在中断上下文调用。
int get_msg(int len, int *msg)
从消息机制取出一条消息(事件优先,其次环形缓冲)。
Parameters:
len(int): 调用者缓冲可容纳的int个数msg(int*): 输出缓冲,msg[0]为消息类型,msg[1..]为参数
Returns:
MSG_NO_ERROR: 成功取到消息MSG_NO_MSG: 队列为空(此时msg[0] = NO_MSG)MSG_BUF_NOT_ENOUGH: 消息参数个数超过len-1MSG_CBUF_ERROR: 环形缓冲读长度不一致(内部状态异常)
int post_event(int event)
将事件索引置入事件位图(去重,同事件重复投递只保留一个位)。
Parameters:
event(int): 事件索引,必须< ARRAY_SIZE(event2msg)
Returns:
MSG_NO_ERROR: 置位成功MSG_EVENT_PARAM_ERROR:event越界
bool get_event_status(u32 event)
查询事件位(不消费)。
Parameters:
event(u32): 事件索引
Returns: TRUE 表示事件已置位,FALSE 表示未置位。
bool has_sys_event(void)
快速判断是否存在任何系统事件。
Returns: true 表示位图非空。
void clear_one_event(u32 event)
清除指定事件位(消费后调用,越界则直接返回)。
Parameters:
event(u32): 事件索引
int get_msg_phy(int len, int *msg, bool idle)
直接从环形缓冲读取物理消息(不检查事件位图)。
Parameters:
len(int): 输出缓冲容量msg(int*): 输出缓冲idle(bool): 队列为空时是否执行idle指令进入低功耗等待
Returns: 同 get_msg。
void ap_handle_hotkey(u16 key)
应用层"热消息"集中分发函数(hot_msg 模块唯一对外接口)。
Parameters:
key(u16): 消息 ID(MSG_*枚举值)
Returns: void
说明: 内部以 switch 分发到电源、音量、USB/存储、升级、工作模式、IR 输入等业务分支;未匹配的消息静默忽略。
Failure Modes, Edge Cases & Concurrency
并发与原子性
- 所有队列/位图操作(
post_msg、post_event、get_msg、get_msg_phy、clear_one_event、get_event_status)均在OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL()内执行(见 msg.c)。这是单核关中断模型:中断上下文与主循环之间天然互斥,不存在自旋锁/信号量开销。 event_init()在临界区内批量清零位图,避免启动阶段被中断打断产生脏位。
缓冲溢出
post_msg在写入前用cbuf_is_write_able检查剩余空间,不足时返回MSG_BUF_NOT_ENOUGH并丢弃该消息(不阻塞、不覆盖旧数据)。生产者在高频投递时必须自行处理该返回值,否则消息静默丢失。get_msg_phy在参数个数超过调用者缓冲(param_len > len - 1)时返回MSG_BUF_NOT_ENOUGH,此时头部已被读出但参数未读——调用者需要提供足够大的缓冲(建议msg[MSG_MAX_PARAM+1]级)。
环形缓冲读一致性
get_msg_phy校验cbuf_read实际读到的字节数:头部不足返回MSG_NO_MSG,参数不足返回MSG_CBUF_ERROR。MSG_CBUF_ERROR属于异常路径,表明缓冲控制块被破坏(如越界写),应作为故障排查线索。
事件越界保护
post_event与clear_one_event均对event >= ARRAY_SIZE(event2msg)做边界检查,防止位图越界读写;event2msg以NO_MSG结尾,天然限定了合法事件范围。
业务层的边界处理(hot_msg.c)
- 上电 1.5s 保护:
time_before(maskrom_get_jiffies(), 150)窗口内忽略MSG_USB_DISK_IN/MSG_SDMMCA_IN,防止上电瞬间误触发device_update设备升级(见 hot_msg.c); - 消息顺序强约束:
MSG_USB_DISK_IN与MSG_SDMMCA_IN相邻,升级设备号由key - MSG_USB_DISK_IN推导;任何人向二者之间插入新消息都会破坏升级逻辑(源码注释明确警告,见 msg.h); - 0x800 段不可动:库系统消息(解码器文件结束/出错等)编号被库引用,裁剪或重排会破坏二进制兼容;
- IR 输入防溢出:
Input_Number > 999时清零重计,避免遥控器连按溢出; - 未匹配消息:
ap_handle_hotkey对switch未覆盖的消息静默丢弃——新加消息必须同时加入处理分支,否则会被悄悄吞掉。
Performance & Operational Considerations
- 取消息空转省电:
get_msg_phy在空队列时执行idle汇编指令,CPU 进入低功耗等待直至中断唤醒,是低功耗设计的关键一环(见 msg.c); - 事件查找 O(1) 化:
clz指令一次找到最高置位 bit,事件位图扫描无需逐位循环;EVENT_TOTAL位图仅几字(38 个事件约 2 个 u32),has_sys_event只需扫描 2 个字; - 喂狗与节拍:
MSG_500MS中调用wdt_clear(),把看门狗喂狗动作挂到消息节拍上,保证主循环活着时系统不被复位(见 hot_msg.c); - 内存静态分配:
msg_pool、event_buf均为静态数组,无运行时 malloc,内存占用可静态预算;msg.c整体放入专用.msg.*段,便于链接器统一规划; - 中断上下文约束:
post_msg/post_event允许中断调用(临界区保护),但处理函数(ap_handle_hotkey)只能在主循环上下文运行,中断里绝不能直接调用业务处理。
Extension Points
- 新增普通消息:在 msg.h 的枚举中追加
MSG_*常量(避开0x600mbox 段与0x800库保留段的既有槽位),然后在ap_handle_hotkey中新增case;若消息带参数,用post_msg(argc, ...)投递,在case内通过共享参数区/全局变量读取。 - 新增事件(去重通知):在
event2msg[]表(msg.c)尾部追加事件对应的消息 ID,用post_event(event)投递、get_msg自动消费;注意EVENT_TOTAL由宏自动推导,无需手工改。 - 复用系统保留槽位:
MSG_0~MSG_9在KEY_IR_EN下被复用为 IR 数字键——这种"功能复用"模式可推广,但必须保证同一消息 ID 在任意时刻只有一种语义。 - 业务级联:在
ap_handle_hotkey分支内再次post_msg/post_event(如MSG_PC_IN→MSG_CHANGE_WORK_MODE,MSG_OTG_IN→EVENT_UDISK_IN),形成"硬件事件 → 消息 → 业务事件"的分层通知链。
Related Links
- 消息 ID 枚举定义 msg.h
- 消息核心实现 msg.c
- 热消息处理器 hot_msg.c
- 热消息接口 hot_msg.h
- 解码器消息路由表 decoder_msg_tab.c(解码器侧消息分发的兄弟页面)
- RCSP 协议消息 rcsp_msg.c(蓝牙协议侧消息的兄弟页面)
- 按键消息映射 key_msg.lua(按键 → 消息 ID 映射的兄弟页面)