杰理 SDK 文档中心
首页
首页
  • 项目概览

    • SDK 简介与核心特性
    • 芯片平台与硬件资料
    • SDK 版本与发布信息
  • 快速开始

    • 环境搭建与工具链
    • 编译工程
    • 烧录与量产工具
  • 工程结构与构建系统

    • 工程目录布局
    • 构建与链接配置
  • 应用层开发

    • mbox_flash 应用框架
    • 板级支持包 (BSP)
    • 公共应用模块
    • UI 显示子系统
  • 蓝牙子系统

    • BLE 控制器、链路层与 HCI 传输
    • GATT 服务框架
    • BLE 应用示例:遥控器 / Dongle / 对讲机
    • 经典蓝牙支持
  • 音频子系统

    • 音频编解码器
    • 音频设备接口 (DAC / ADC / APA)
    • 音效处理与 EQ
    • 播放、录音与 MIO 工作流
  • 设备与文件系统

    • 存储设备驱动 (NorFlash / SDMMC / USB)
    • 文件系统 (FAT / nor_fs / SYDF)
    • 设备管理框架 (dev_mg)
  • 系统服务与电源管理

    • 消息机制 (msg / hot_msg)
    • 配置与参数存储 (app_config / VM)
    • 电源管理 (SOFT OFF / POWER DOWN)
  • 固件升级

    • 升级框架总览 (code_v1 / code_v2)
    • 双 Bank 升级机制
    • 升级通道:UART / 测试盒 / BLE OTA / USB / SD
  • 补丁包与版本维护

    • 版本升级补丁链 (v1.1.0 → v1.4.0)
    • 问题修复补丁
    • 固件裁剪与资源优化
  • 开发工具与支持

    • 辅助工具与脚本
    • 文档、配置说明与常见问题

消息机制 (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/存储热插拔、蓝牙、定时器)运行在不同中断/上下文环境中,彼此不能直接调用对方的业务函数。消息机制提供了一套异步、无锁(关中断临界区)、单一消费点的通信方式:

  1. 生产者:任意上下文(中断、任务、库回调)调用 post_msg(argc, ...) 或 post_event(event) 投递消息;
  2. 队列:普通消息写入环形缓冲 msg_cbuf(存储于 msg_pool),事件消息写入位图 event_buf;
  3. 消费者:应用主循环通过 get_msg(len, msg) 取出消息,其中事件优先级高于普通消息;
  4. 分发:取出的消息交给 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 = 0MSG_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 = 0x600MSG_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 = 0x800MSG_MP3_FILE_END、MSG_WAV_FILE_ERR、MSG_WFILE_FULL 等库会使用,不能更改

msg.h 中有两条不可破坏的顺序约束,直接对应 hot_msg.c 的算法:

  1. MSG_USB_DISK_IN 与 MSG_SDMMCA_IN 必须相邻且中间不可插入其他消息——hot_msg.c 用 key - MSG_USB_DISK_IN 计算升级设备号(见 msg.h);
  2. 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 *)&param;
    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 *)&param;
    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 *)&param;
    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_500MSapp_power_scan()、wdt_clear()、audio_lookup()、charge_set_vbat_voltage()、music_vol_update()500ms 系统节拍:电源扫描、喂狗、音频查表、充电电压跟随、音量回写 sysmem
MSG_VOL_UP/DOWNdac_vol('+/-', 255) → UI_menu(MENU_MAIN_VOL, 0)音量调节走 DAC API 并刷新 UI(用 goto 合并公共尾部)
MSG_OTG_IN/OUTusb_host_mount(0, 3, 20, 200) / usb_host_unmount(0),随后 post_event(EVENT_UDISK_IN/OUT)USB 主机挂载,成功后再投递事件驱动上层状态机
MSG_PC_INwork_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_9IR 数字输入累加 Input_Number = Input_Number * 10 + key,UI_menu(MENU_INPUT_NUMBER, 0)KEY_IR_EN 使能时复用系统保留消息做遥控器数字选台

关键模式:

  1. 重投递(re-post):MSG_PC_IN、MSG_NEXT_WORKMODE、MSG_POWER_OFF 都不直接执行模式切换逻辑,而是设置 work_mode 后重新 post_msg(1, MSG_CHANGE_WORK_MODE)——把"变更请求"统一收敛为"模式切换"这一条消息,让模式状态机只有单一入口;
  2. 级联事件:MSG_OTG_IN 处理后 post_event(EVENT_UDISK_IN),把"硬件层面事件"翻译成"业务层面事件",由 event2msg 之外的业务位图继续驱动;
  3. 上电保护:maskrom_get_jiffies() < 150 的 1.5s 窗口内忽略存储设备上线消息,避免上电瞬间误触发设备升级;
  4. 音量持久化: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

时序要点:

  1. 生产端:无论中断还是普通任务,post_msg / post_event 都只在临界区内做"拷贝/置位",不触碰业务;
  2. 消费端:主循环调用 get_msg,事件优先(clz 查位图),无事件时读环形缓冲;空队列且 idle=true 时执行 idle 指令省电;
  3. 分发端: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-1
  • MSG_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

  1. 新增普通消息:在 msg.h 的枚举中追加 MSG_* 常量(避开 0x600 mbox 段与 0x800 库保留段的既有槽位),然后在 ap_handle_hotkey 中新增 case;若消息带参数,用 post_msg(argc, ...) 投递,在 case 内通过共享参数区/全局变量读取。
  2. 新增事件(去重通知):在 event2msg[] 表(msg.c)尾部追加事件对应的消息 ID,用 post_event(event) 投递、get_msg 自动消费;注意 EVENT_TOTAL 由宏自动推导,无需手工改。
  3. 复用系统保留槽位:MSG_0~MSG_9 在 KEY_IR_EN 下被复用为 IR 数字键——这种"功能复用"模式可推广,但必须保证同一消息 ID 在任意时刻只有一种语义。
  4. 业务级联:在 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 映射的兄弟页面)
Next
配置与参数存储 (app_config / VM)