杰理 SDK 文档中心
首页
首页
  • 概述与快速入门

    • 芯片平台与 SDK 概述
    • 环境搭建与编译工具链
    • 快速开始:选型、编译与烧录
    • 烧录与量产工具
  • 构建系统与板级工程

    • 顶层 Makefile 与编译目标
    • 板级工程与配置
    • 后处理与配置工具
  • HID 人机交互应用

    • HID 应用架构总览
    • 键盘、翻页器与遥控应用
    • 鼠标应用:单模、双模与低延迟
    • 空闲应用与初始化流程
  • BLE 透传与数传应用

    • 透传应用总览
    • 多连接与无连接传输
    • AT 命令模组应用
    • Dongle 适配器应用
  • BSP 公共模块

    • 蓝牙公共处理
    • 按键、LED 与红外
    • 传感器与编码器
    • 存储、VM 与文件系统
    • 电源管理与低功耗
    • 消息调度与通信外设
  • 协议栈与预编译库

    • 蓝牙协议栈库
    • 设备驱动与文件系统库
    • 音频、升级与其他库
  • 开发资料与补丁发布

    • 文档资料中心
    • 版本补丁与兼容性修复

消息调度与通信外设

消息调度与通信外设是 AW31N BLE SDK 中负责系统级消息(Message)与事件(Event)投递、排队、调度和消费的公共基础模块,核心实现在 msg.c / msg.h,并通过事件池(sys_event.c)与低功耗框架(REGISTER_LP_TARGET / DEEPSLEEP_TARGET_REGISTER)深度集成。

Purpose and Scope(目的与范围)

本文档完整覆盖该模块的端到端机制:

  • 消息编码格式:16 位消息头(12 位类型 + 4 位参数个数)与消息 ID 分区设计;
  • 消息队列:基于环形缓冲区(msg_cbuf)的消息池,生产者/消费者模型;
  • 事件位图调度:event_buf 位图 + CLZ 指令实现的静态优先级事件调度;
  • 投递与消费 API:post_msg / post_event / get_msg / get_msg_phy 的完整实现与错误码语义;
  • 特殊消息路径:MSG_TYPE_EVENT 携带 sys_event* 与 dev->ops 指针的双指针扩展;
  • 生命周期与低功耗集成:message_init 初始化、低功耗 idle 查询、深睡唤醒复位;
  • 与通信外设的消息通路:UART 测试盒升级(MSG_UART_TESTBOX_UPDATE_START)、BLE 连接/断开事件(0x900 段)等外设事件如何进入统一调度。

以下内容属于兄弟页面,不在本文展开:UART/SPI/I2C 底层驱动的寄存器级实现、BLE 协议栈内部状态机、具体 APP 业务逻辑(如音乐播放器的 MSG_PP 处理细节)。本文只覆盖"消息调度"这一公共机制及其与外设事件接入的边界。

Overview(概述)

在嵌入式 BLE SoC 固件中,按键任务、解码器、设备热插拔检测、BLE 协议栈、升级模块等众多异步生产者(可运行在中断上下文或不同任务中)都需要向 APP 主逻辑投递事件。该模块提供了一种单消费者、无锁(关中断临界区)、静态优先级的消息/事件调度机制:

  • 普通消息(post_msg)走环形缓冲区,按 FIFO 顺序消费,支持最多 15 个 int 参数;
  • 系统事件(post_event)走位图,用 CLZ(Count Leading Zeros)指令在常数时间内取出优先级最高的挂起事件,再通过 event2msg[] 静态表映射为消息 ID;
  • 两种路径共享同一个忙碌状态字 msg_busy_state,它同时是低功耗框架判断"消息子系统是否可休眠"的依据。

设计意图(WHY):

  1. 中断安全:所有队列/位图操作都包在 OS_ENTER_CRITICAL() / OS_EXIT_CRITICAL() 中,中断服务程序可以直接调用 post_msg / post_event,无需等待互斥锁;
  2. 优先级确定性:事件位图索引即优先级(索引越小优先级越高),CLZ 保证 get_msg 每次都先取到最高优先级事件,避免事件饿死;
  3. 零动态内存:消息池是编译期分配的 u32 msg_pool[MAX_POOL],事件位图是静态 u32 event_buf[],杜绝堆碎片;
  4. 功耗门控:msg_busy_state 非零表示还有待处理消息,低功耗控制器据此阻止进入睡眠;深睡唤醒时调用 message_init() 丢弃陈旧消息,保证唤醒后状态一致。

Architecture(架构)

flowchart TD
    subgraph sg_Producer["消息生产者(多上下文)"]
        KEY["按键/编码器任务"]
        BLE["BLE 协议栈"]
        DEC["解码器 F1A1/MP3/WAV/OPUS"]
        DEV["设备检测 USB/SD/AUX/OTG"]
        UART["UART 测试盒升级"]
    end

    subgraph sg_Core["消息调度核心 msg.c"]
        POST["post_msg / post_event"]
        CBUF["环形缓冲区 msg_cbuf<br/>msg_pool[32]"]
        EVBUF["事件位图 event_buf[]"]
        BUSY["忙碌状态 msg_busy_state<br/>BIT0=缓冲忙 BIT1=事件忙"]
        EVMAP["event2msg[] 静态映射表"]
    end

    subgraph sg_Consumer["消息消费者"]
        GET["get_msg / get_msg_sub / get_msg_phy"]
        APP["APP 主循环/任务"]
    end

    subgraph sg_Power["低功耗框架"]
        LP["REGISTER_LP_TARGET 查询 is_idle"]
        DS["DEEPSLEEP_TARGET_REGISTER 唤醒复位"]
    end

    KEY --> POST
    BLE --> POST
    DEC --> POST
    DEV --> POST
    UART --> POST
    POST --> CBUF
    POST --> EVBUF
    POST --> BUSY
    EVBUF --> EVMAP
    CBUF --> GET
    EVMAP --> GET
    GET --> APP
    BUSY --> LP
    DS --> POST
    DS --> EVBUF

架构说明:左侧是各类异步生产者,它们只依赖 post_msg / post_event 两个入口,因此与调度核心解耦;中间是核心,普通消息与事件分别进入环形缓冲和位图,msg_busy_state 统一反映两条路径的待处理状态;右侧 get_msg 是唯一的消费出口(单消费者模型),APP 主循环轮询调用;底部低功耗框架通过 is_idle 回调读取忙碌状态门控睡眠,深睡唤醒通过 .exit 回调调用 message_init() 复位整个子系统。

核心实现分析

消息 ID 定义与分区

所有消息 ID 在 msg.h 中以枚举定义,按功能分区,数值区间承载了语义:

数值区间用途典型消息
0x0000–0x000F系统通用MSG_0~MSG_9、MSG_PP_2、MSG_RECODE_START/END
0x0010 起APP 级MSG_500MS、MSG_APP_SWITCH_ACTIVE、MSG_LOW_POWER、MSG_ENTER_IDLE、MSG_POWER_OFF
—音乐/录音/MIDI/RFMSG_PP、MSG_NEXT_FILE、MSG_REC_MODE_SWITCH、MSG_MIDICTRL_*、MSG_SENDER_START 等
0x0600 起mbox 音乐控制MSG_CHANGE_WORK_MODE、MSG_MUSIC_PLAY_NEW_FILE、MSG_NEXT_WORKMODE
0x0610 起均衡器/播放模式MSG_MUSIC_PREV_EQ、MSG_NEXT_PLAYMODE
0x0620 起FM 收音机MSG_FM_NEXT_STEP、MSG_FM_SCAN_ALL_UP、MSG_CH_SET
0x0630 起系统/时钟MSG_REQUEST_Y/N、MSG_INPUT_TIMEOUT、MSG_READ_CLOCK、MSG_ALARM
0x0640 起设备热插拔MSG_USB_DISK_IN/OUT、MSG_SDMMCA_IN/OUT、MSG_AUX_IN/OUT、MSG_PC_MUTE
0x0800 起解码器回执MSG_F1A1_FILE_END/ERR/LOOP、MSG_MP3_FILE_END、MSG_WAV_LOOP、MSG_OPUS_FILE_ERR
0x0900 起BLE/升级MSG_BLE_CONNECT_COMPLETE、MSG_BLE_APP_UPDATE_START、MSG_UART_TESTBOX_UPDATE_START
MSG_TYPE_EVENT事件消息携带 sys_event* 与 dev->ops 两个指针
NO_MSG = 0x0fff空哨兵无消息时的填充值

关键设计:解码器回执(0x800 段)和 BLE 事件(0x900 段)的注释明确标注"库会使用到,不能更改",说明数值稳定性是 ABI 约束;设备插入/拔出段(0x640 起)注释"以下顺序不可随意调整",因为事件位图索引 EVENT_* 宏与其顺序一一对应。

消息头编码与缓冲池布局

消息头为 16 位定长,位域由 msg.h 的宏定义:

#define MSG_HEADER_BYTE_LEN     2
#define MSG_HEADER_BIT_LEN     (MSG_HEADER_BYTE_LEN*8)
#define MSG_HEADER_ALL_BIT     ((1L<<MSG_HEADER_BIT_LEN) - 1)

#define MSG_TYPE_BIT_LEN        12
#define MSG_PARAM_BIT_LEN       (MSG_HEADER_BYTE_LEN*8-MSG_TYPE_BIT_LEN)

#define MAX_POOL		    32//128

#define NO_EVENT			0xffff

来源:msg.h

头布局为:bit[15:4] 消息类型(12 位),bit[3:0] 参数个数(4 位,最多 15 个参数)。MAX_POOL 当前为 32(注释保留历史值 128),即消息池为 32 个 u32(128 字节)。

存储结构在 msg.c 中静态分配:

NOT_KEEP_RAM
static cbuffer_t msg_cbuf;

NOT_KEEP_RAM
static u32 event_buf[EVENT_TOTAL];

NOT_KEEP_RAM
static u32 msg_pool[MAX_POOL];

#define  MSG_BUF_BUSY_BIT    BIT(0)
#define  MSG_EVENT_BUSY_BIT  BIT(1)
static   u8 msg_busy_state; //记录msg的状态

来源:msg.c

  • msg_cbuf:cbuffer_t 环形缓冲区,底层存储为 msg_pool,承载普通消息的头部与参数;
  • event_buf[EVENT_TOTAL]:事件位图,EVENT_TOTAL = (1+(sizeof(event2msg)/2 -1)/32),即按 event2msg[] 表项数向上取整到 32 位字的个数;
  • msg_busy_state:两个忙位——MSG_BUF_BUSY_BIT 表示缓冲中还有未消费消息,MSG_EVENT_BUSY_BIT 表示还有挂起事件;两者都清零时子系统才允许休眠。

文件顶部通过 #pragma bss_seg(".msg.data.bss")、#pragma code_seg(".msg.text") 等指令把消息模块的变量与代码放入专用内存段,便于链接器布局与低功耗管理(NOT_KEEP_RAM 表示该段在深睡时可被丢弃/复位)。

事件位图调度算法

event2msg[] 静态表(msg.c)把"事件索引"映射为"消息 ID",例如索引 0→MSG_F1A1_FILE_END、36→MSG_BLE_APP_UPDATE_START。取出事件时用 CLZ 指令直接定位最高优先级的置位比特(索引最小即优先级最高):

_NOINLINE_
static u32 get_event(void)
{
    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();
            /* log_info(" has event 0x%x\n",i*32 + (31 - event_cls)); */
            return i * 32 + (31 - event_cls);
        }
    }
    OS_EXIT_CRITICAL();

    return NO_EVENT;
}

来源:msg.c

该实现是典型的静态优先级调度:get_event 从低到高扫描位图字,CLZ 返回最高位位置(31 - clz 即事件索引),因此 event2msg[] 中排在前面的事件(如解码器文件结束)永远优先于排在后面的事件被消费。整个查找在最坏情况下只需扫描 EVENT_TOTAL 个字,且单字查找是常数时间。

Core Flow(核心流程)

sequenceDiagram
    participant P as 生产者(中断/任务)
    participant POST as post_msg / post_event
    participant Q as 环形缓冲 msg_cbuf
    participant E as 事件位图 event_buf
    participant G as get_msg / get_msg_sub
    participant A as APP 主循环

    P->>POST: post_msg(2, MSG_PP, param)
    POST->>POST: 关中断,组装 16bit 头
    POST->>Q: 写入头部 + 参数
    POST->>POST: 置 MSG_BUF_BUSY_BIT
    POST-->>P: MSG_NO_ERROR

    P->>POST: post_event(EVENT_MP3_END)
    POST->>E: event_buf[event/32] |= BIT(event%32)
    POST->>POST: 置 MSG_EVENT_BUSY_BIT
    POST-->>P: MSG_NO_ERROR

    A->>G: get_msg(len, msg)
    G->>E: get_event() 用 CLZ 找最高优先级置位
    E-->>G: 事件索引
    G->>G: event2msg[索引] -> msg[0],清位
    G->>Q: 读 2 字节头 + 参数
    Q-->>G: 消息内容
    G-->>A: MSG_NO_ERROR

消费侧两条路径的汇合点在 get_msg_sub(msg.c):事件优先于普通消息——先查 get_event(),有挂起事件则立即清位并映射返回;无事件时才调用 get_msg_phy 从环形缓冲取普通消息。同时 get_msg 入口(msg.c)先检查 msg_busy_state,若两条路径都空闲则直接返回 MSG_NO_MSG,避免无谓的临界区开销——这是"忙碌状态字同时充当快速路径开关"的设计。

投递路径实现

post_msg:变参消息写入

post_msg 是变参函数,签名 int post_msg(int argc, ...):第一个参数是"参数个数 + 1"(含消息 ID 本身),随后依次是消息 ID 与各参数。实现位于 msg.c:

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);

    int msg = va_arg(argptr, int);
    size_t additional_size = 0;
    if (msg == MSG_TYPE_EVENT) {
        additional_size += 2 * sizeof(void *); // for sys_event* and dev->ops
    }
    if (!cbuf_is_write_able(&msg_cbuf, (argc - 1) * sizeof(int) + additional_size + MSG_HEADER_BYTE_LEN)) {
        OS_EXIT_CRITICAL();
        va_end(argptr);
        putchar('f');
        return MSG_BUF_NOT_ENOUGH;
    }

    t_msg[0] = (MSG_HEADER_ALL_BIT >> MSG_PARAM_BIT_LEN) & msg;
    t_msg[0] = ((argc - 1) << (MSG_TYPE_BIT_LEN)) | t_msg[0];
    cbuf_write(&msg_cbuf, (void *)&t_msg[0], MSG_HEADER_BYTE_LEN);

    for (u32 i = 0; i < argc - 1; i++) {
        param = va_arg(argptr, int);
        cbuf_write(&msg_cbuf, (void *)&param, sizeof(int));
    }

    if (msg == MSG_TYPE_EVENT) {
        void *event_ptr = va_arg(argptr, void *);
        void *ops_ptr = va_arg(argptr, void *);
        cbuf_write(&msg_cbuf, &event_ptr, sizeof(void *));
        cbuf_write(&msg_cbuf, &ops_ptr, sizeof(void *));
    }
    va_end(argptr);
#ifdef CONFIG_DEBUG_ENABLE
    if (msg_remain_min > msg_cbuf_remain()) {
        msg_remain_min = msg_cbuf_remain();
    }
#endif
    msg_busy_state |= MSG_BUF_BUSY_BIT;
    OS_EXIT_CRITICAL();
    return MSG_NO_ERROR;
}

来源:msg.c

要点:

  • 头编码:t_msg[0] = (MSG_HEADER_ALL_BIT >> MSG_PARAM_BIT_LEN) & msg; 取消息 ID 的低 12 位作为类型,再 ((argc-1) << MSG_TYPE_BIT_LEN) 把参数个数放进高 4 位(注意端序上参数个数落在 bit[15:12] 语义位);
  • 容量预检:cbuf_is_write_able 提前计算"参数 + 可能的双指针 + 2 字节头"是否放得下,放不下时打印字符 'f'(fail)并返回 MSG_BUF_NOT_ENOUGH,全程在临界区内,保证不出现半写状态;
  • MSG_TYPE_EVENT 扩展:当消息 ID 为 MSG_TYPE_EVENT 时,参数区之后还会写入 sys_event* 与 dev->ops 两个指针,这是事件消息与普通 int 参数消息的结构性区别;
  • 调试统计:CONFIG_DEBUG_ENABLE 下维护 msg_remain_min 记录缓冲剩余空间的历史最小值,用于运行时定位消息积压。

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);
    msg_busy_state |= MSG_EVENT_BUSY_BIT;
    OS_EXIT_CRITICAL();
    return MSG_NO_ERROR;
}

来源:msg.c

post_event 的事件参数是 event2msg[] 的表索引(即 EVENT_* 宏),而非消息 ID。越界索引返回 MSG_EVENT_PARAM_ERROR。与 post_msg 不同,事件不占环形缓冲,天然去重(同一事件重复置位只保留一个位),适合"解码器文件结束"这类可能高频触发的信号。

MSG_TYPE_EVENT 双指针消费路径

get_msg_phy(msg.c)在读完头部和参数后,若 msg[0] == MSG_TYPE_EVENT,会继续从缓冲读出两个指针并分别放入 msg[param_len+1] 与 msg[param_len+2],把"消息 + 事件对象 + 设备操作表"打包成一次取出的完整上下文。这样 APP 侧拿到事件消息后无需再做二次查找,即可同时获得 sys_event 实例与对应设备的 ops 函数表——这是"通信外设"接入的核心通道:外设事件(如 UART 升级请求)以 sys_event 形式投递,dev->ops 指向该外设驱动的操作接口。

flowchart LR
    subgraph sg_Header["消息头 16bit"]
        T["bit[15:4] 消息类型 12bit"]
        N["bit[3:0] 参数个数 4bit"]
    end
    subgraph sg_Event["MSG_TYPE_EVENT 扩展"]
        P1["参数区 param[0..n-1]"]
        P2["sys_event* 指针"]
        P3["dev->ops* 指针"]
    end
    T --- N
    N --- P1
    P1 --> P2
    P2 --> P3

生命周期与低功耗集成

stateDiagram-v2
    [*] --> IDLE
    IDLE --> BUF_BUSY: post_msg 写入缓冲
    IDLE --> EVENT_BUSY: post_event 置位事件
    BUF_BUSY --> IDLE: get_msg 消费完毕
    EVENT_BUSY --> IDLE: get_msg 消费完毕
    IDLE --> SLEEP: 低功耗查询 is_idle==true
    SLEEP --> IDLE: 深睡唤醒 exit 回调<br/>message_init() 复位
  • 初始化:message_init()(msg.c)清零事件位图、以 msg_pool 为底初始化环形缓冲、复位忙碌状态字,全程关中断;系统启动与深睡唤醒都会调用它。
  • 低功耗门控:REGISTER_LP_TARGET(msg_lowpower_target)(msg.c)注册名为 "msg_lowpwer_deal" 的低功耗目标,is_idle 回调返回 !msg_busy_state——只要还有未消费消息或事件,低功耗控制器就不允许进入睡眠,保证待处理消息不会丢失。
  • 深睡恢复:DEEPSLEEP_TARGET_REGISTER(sys_msg_sleep)(msg.c)注册 .exit = msg_exit_leep,唤醒时调用 message_init() 丢弃唤醒前的陈旧消息并复位状态,避免深睡期间残留的中断消息污染唤醒后的业务逻辑。注意 msg_exit_leep 的拼写是源码中的原样(leep),属历史命名。

使用示例

示例 1:消费侧主循环取消息

APP 主循环典型地循环调用 get_msg,收到 MSG_NO_MSG 即空闲(源码中 get_msg 的忙碌状态快速路径):

int get_msg(int len, int *msg)
{
    if (!msg_busy_state) {
        msg[0] = NO_MSG;
        return MSG_NO_MSG;
    }
    return get_msg_sub(len, msg);
}

来源:msg.c

示例 2:投递普通消息

生产者(按键/解码器/外设驱动)投递"播放/暂停"类消息,第一个参数为消息 ID,其后携带业务参数:

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);

    int msg = va_arg(argptr, int);
    size_t additional_size = 0;
    if (msg == MSG_TYPE_EVENT) {
        additional_size += 2 * sizeof(void *); // for sys_event* and dev->ops
    }
    if (!cbuf_is_write_able(&msg_cbuf, (argc - 1) * sizeof(int) + additional_size + MSG_HEADER_BYTE_LEN)) {
        OS_EXIT_CRITICAL();
        va_end(argptr);
        putchar('f');
        return MSG_BUF_NOT_ENOUGH;
    }

    t_msg[0] = (MSG_HEADER_ALL_BIT >> MSG_PARAM_BIT_LEN) & msg;
    t_msg[0] = ((argc - 1) << (MSG_TYPE_BIT_LEN)) | t_msg[0];
    cbuf_write(&msg_cbuf, (void *)&t_msg[0], MSG_HEADER_BYTE_LEN);
    ...
}

来源:msg.c

调用形态如 post_msg(2, MSG_PP, 0):argc=2 表示"消息 ID + 1 个参数",MSG_PP 写入 16 位头,参数 0 紧随其后;中断服务程序中同样可以安全调用。

示例 3:消费侧解析消息头与参数

get_msg_phy 展示了完整解析:先读 2 字节头,拆出类型与参数个数,再按个数读参数,最后处理 MSG_TYPE_EVENT 的双指针:

    u32 tlen = cbuf_read(&msg_cbuf, (void *)t_msg, MSG_HEADER_BYTE_LEN);

    if (MSG_HEADER_BYTE_LEN != tlen) {
        msg_busy_state &= (~MSG_BUF_BUSY_BIT);
        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;
    }

来源:msg.c

注意:当缓冲为空时,get_msg_phy 会清除 MSG_BUF_BUSY_BIT 并返回 MSG_NO_MSG,msg[0] 置为 NO_MSG(0x0fff)——这是忙碌状态字自动复位的关键路径。

配置选项

选项类型默认值说明
MAX_POOL宏32(注释保留历史值 128)消息缓冲池大小(u32 个数),决定环形缓冲容量与可排队的普通消息数
MSG_HEADER_BYTE_LEN宏2消息头字节数,头编码的基础
MSG_TYPE_BIT_LEN宏12消息类型位宽,MSG_PARAM_BIT_LEN 由 16-12=4 推导,决定最大参数个数 15
EVENT_TOTAL宏(1+(sizeof(event2msg)/2-1)/32)事件位图所需 u32 字数,随 event2msg[] 表自动推导
NO_EVENT宏0xffffget_event 无事件时的返回值哨兵
CONFIG_DEBUG_ENABLE编译宏关闭开启后维护 msg_remain_min 历史最低剩余容量,并启用 msg_debug_info() 输出
LOG_TAG_CONST宏OFF消息模块日志开关,置 NORM 可打开 [msg] 日志

API Reference

函数签名说明
post_msgint post_msg(int argc, ...)投递普通消息;argc 为参数个数 +1(含消息 ID);返回 MSG_NO_ERROR 或 MSG_BUF_NOT_ENOUGH
post_eventint post_event(int event)投递系统事件;event 为 event2msg[] 表索引(EVENT_* 宏);越界返回 MSG_EVENT_PARAM_ERROR
get_msgint get_msg(int len, int *msg)消费消息;空闲时直接返回 MSG_NO_MSG 且 msg[0]=NO_MSG
get_msg_phyint get_msg_phy(int len, int *msg, bool idle)物理读取环形缓冲;len 为调用方缓冲容量(int 个数),防止参数溢出
get_event_statusbool get_event_status(u32 event)查询指定事件是否挂起(关中断读位图)
clear_one_eventvoid clear_one_event(u32 event)清除指定事件的挂起位
has_sys_eventbool has_sys_event(void)是否有任一事件挂起
clear_all_messagevoid clear_all_message(void)清空环形缓冲(不清事件位图)
message_initvoid message_init()复位整个消息子系统:清位图、初始化缓冲、清忙碌状态;启动与深睡唤醒调用
msg_debug_infovoid msg_debug_info(void)打印 msg_remain_min(仅 CONFIG_DEBUG_ENABLE)
event_pool_allocstruct sys_event *event_pool_alloc()从事件池分配 sys_event(实现在 sys_event.c)
event_pool_freevoid event_pool_free(struct sys_event *event_ptr)归还事件对象到事件池
event_pool_initvoid event_pool_init()初始化事件池

错误码

定义于 msg.h:

错误码值触发场景
MSG_NO_ERROR / MSG_NO_MSG0成功 / 无消息可消费
MSG_EVENT_EXIST-1事件已存在(预留)
MSG_NOT_EVENT-2非事件(预留)
MSG_EVENT_PARAM_ERROR-3post_event 索引越界
MSG_BUF_NOT_ENOUGH-4环形缓冲空间不足(post_msg)或调用方缓冲过小(get_msg_phy)
MSG_CBUF_ERROR-5环形缓冲读取长度不一致(数据损坏)

故障模式与边界情况

场景行为源码依据
环形缓冲满(post_msg)返回 MSG_BUF_NOT_ENOUGH 并打印 'f',消息丢弃,调用方需自行重试或忽略msg.c L277-L282
调用方缓冲过小(get_msg_phy)返回 MSG_BUF_NOT_ENOUGH,已读的头不退还,下一次 get_msg 会读到同一个头——要求调用方保证 len 足够大msg.c L180-L184
缓冲数据不一致cbuf_read 实际读长度与期望不符,返回 MSG_CBUF_ERROR(数据损坏或并发写穿)msg.c L185-L189
事件索引越界post_event 返回 MSG_EVENT_PARAM_ERROR,位图不写入msg.c L251-L253
队列与事件都为空get_msg 快速路径返回 MSG_NO_MSG,msg[0]=NO_MSGmsg.c L242-L245
深睡唤醒.exit 回调 message_init() 清空全部缓冲与位图,唤醒后旧消息不再可见msg.c L344-L355
待处理消息时请求休眠is_idle = !msg_busy_state 返回 false,低功耗控制器推迟睡眠msg.c L334-L342

并发与一致性

  • 原子性:post_msg、post_event、get_msg*、clear_one_event、get_event_status、message_init 全部以 OS_ENTER_CRITICAL() / OS_EXIT_CRITICAL() 包裹临界区,因此中断上下文与任务上下文都能安全调用,无需互斥锁或信号量;
  • 单消费者:get_msg 是唯一消费出口(设计上 APP 侧单任务轮询),天然规避多读者竞争;若未来引入多消费者,需要新增消费端互斥;
  • 事件去重:位图置位天然合并重复事件,高频信号(如文件结束)不会撑爆缓冲,但会丢失中间计数——语义上属于"最新状态"而非"事件计数";
  • 事件优先级:低索引事件优先,属于静态优先级;普通消息之间是严格 FIFO,事件与消息之间事件优先(get_msg_sub 先查位图);
  • 无锁数据结构风险:临界区只保护单次操作,若调用方在 get_msg 与下一次 get_msg 之间长期不消费,生产者的 cbuf_is_write_able 会持续失败——缓冲满时的消息丢弃是显式策略(打印 'f'),上层需要据此实现重试或告警。

性能与运维

  • 取事件 O(1):CLZ 单指令定位最高优先级事件,位图扫描最坏为 EVENT_TOTAL 次循环(当前约 2 个 u32),极低成本;
  • 临界区短:post_msg 的临界区仅含容量判断与 cbuf_write 批量拷贝,不触发任何回调,适合在中断服务程序中直接调用;
  • 零动态分配:缓冲池、位图、忙碌状态均为编译期静态内存,运行期无 malloc/free,无堆碎片风险;
  • 调试手段:CONFIG_DEBUG_ENABLE 下 msg_remain_min 记录历史最低剩余容量,msg_debug_info() 打印该值——若持续接近 0 说明消息积压,应排查生产者频率或增加 MAX_POOL(注意 MAX_POOL 从 128 被调小到 32,属于内存与吞吐的权衡点);
  • 内存段约束:模块数据置于 .msg.data / .msg.data.bss 专用段,NOT_KEEP_RAM 变量在深睡时可不保留,链接时需保证该段与低功耗唤醒复位流程匹配。

扩展点

  1. 新增普通消息:在 msg.h 枚举中追加 ID(避开已占用区间,且勿改动 0x800/0x900 段"库会使用"的既有值),即可被 post_msg 投递;
  2. 新增系统事件:在 event2msg[] 表尾追加"事件索引 → 消息 ID"映射,同时在 msg.h 定义对应的 EVENT_XXX 宏;EVENT_TOTAL 自动推导,无需手工修改位图大小;
  3. 接入新外设事件:复用 MSG_TYPE_EVENT 路径——投递时附带 sys_event* 与 dev->ops*,消费侧从 msg[param_len+1] / msg[param_len+2] 直接取得事件对象与设备操作表;
  4. 事件对象生命周期:event_pool_alloc / event_pool_free / event_pool_init 由 sys_event.c 实现,分配与归还配对使用,避免 MSG_TYPE_EVENT 消息中的指针悬挂;
  5. 低功耗协同:可通过 REGISTER_LP_TARGET 注册其他模块的 idle 查询,与 msg_busy_state 一起参与睡眠裁决;深睡目标可通过 DEEPSLEEP_TARGET_REGISTER 注册各自的 .enter / .exit 回调。

测试情况

在本次探索预算内未发现针对 msg.c 的独立单元测试文件。模块的运行时正确性主要依赖:CONFIG_DEBUG_ENABLE 统计(msg_remain_min)、低功耗框架的 is_idle 断言(忙碌状态驱动睡眠裁决),以及 get_msg / get_msg_phy 对 MSG_CBUF_ERROR 等错误码的显式返回——这些错误码设计本身即为可测试契约。建议的验证方式:在 post_msg 满队列、事件高优先级抢占、深睡唤醒丢弃等边界场景下做压力与状态机验证。

相关链接

  • msg.c(核心实现)
  • msg.h(消息定义与 API 声明)
  • sys_event.c(事件池实现,与本文档 event_pool_* API 对应)
  • 通信外设底层细节(UART/SPI 寄存器级驱动)与 BLE 协议栈状态机请参见对应外设/协议栈目录的独立页面;
  • 低功耗框架的 REGISTER_LP_TARGET / DEEPSLEEP_TARGET_REGISTER 机制请参见低功耗管理相关页面。
Prev
电源管理与低功耗