BES 芯片跨核通讯与共享内存设计原理

BES 芯片跨核通讯与共享内存设计原理

基于BES Best1600_SOC 项目代码实例分析

涉及核心:SensorHub、M33 (MCU)、M55 (DSP)

场景:陀螺仪头部追踪 → 空间音效渲染


目录

  1. 系统整体架构
  2. 跨核通讯原理设计
  3. 共享内存与缓冲区设计
  4. 启动与初始化流程
  5. 时序执行流程
  6. 核心代码路径索引
  7. 主频调度策略
  8. 方案设计优势分析
  9. 关键数据结构速查

1. 系统整体架构

1.1 三核职能划分

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                        BES   Best1600_SOC                           │
│                                                                     │
│  ┌──────────────┐    IPC/Core Bridge    ┌──────────────────────┐   │
│  │  SensorHub   │◄──────────────────────►│     M33 (MCU)        │   │
│  │              │   SENS_MSG / TLV      │   主控核 / 中枢       │   │
│  │ • IMU 采集   │                       │ • 协议栈 (BT/BLE)     │   │
│  │ • 陀螺仪     │                       │ • 业务逻辑            │   │
│  │ • 加速度计   │                       │ • IPC 路由            │   │
│  │ • MotionEng  │                       │ • HID 报告生成        │   │
│  │   四元数算法 │                       │ • spa_algo 配置管理   │   │
│  └──────────────┘                       └──────────┬───────────┘   │
│                                                     │               │
│                                         Core Bridge │ Task CMD      │
│                                         (MCU_DSP_M55_TASK_CMD_*)    │
│                                                     │               │
│                                         ┌───────────▼───────────┐   │
│                                         │     M55 (DSP)         │   │
│                                         │   高性能音频处理核     │   │
│                                         │ • spa_algo 空间音效引擎│   │
│                                         │ • HRTF 滤波器         │   │
│                                         │ • 实时四元数输入      │   │
│                                         │ • 208/260 MHz 运行(按需)│   │
│                                         └───────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

1.2 数据流向总览

复制代码
  [IMU 传感器]
       │
       │ SPI/I2C 硬件采样
       ▼
  [SensorHub]
   MotionEngine 算法
   四元数 / 陀螺仪计算
       │
       │ app_core_bridge_send_cmd()
       │ MCU_SENSOR_HUB_TASK_CMD_SEND_QUATERNION (0x027)
       │ MCU_SENSOR_HUB_TASK_CMD_SEND_GYROSCOPE  (0x028)
       ▼
  [M33 MCU]                          ─────────────────────────────────
   quaternion_send_receive_handler()            │
   gyroscope_send_receive_handler()             │ BT HID 报告
   │                                            │ app_bt_hid_process_sensor_report()
    │ spa_algo_audio_quat_relay()               ▼
   │ app_dsp_m55_bridge_send_cmd()     [手机 / 主机设备]
   │ MCU_DSP_M55_TASK_CMD_RS3D_QUAT (0x194)
   ▼
   [M55 DSP]
    spa_algo 空间音效引擎
   实时 HRTF 头部追踪渲染
       │
       │ 处理后音频数据
       ▼
  [DAC / 扬声器输出]

2. 跨核通讯原理设计

2.1 Core Bridge 机制

BES 平台的跨核通讯基于 Core Bridge 框架,封装了底层硬件邮箱(Mailbox)中断,向上提供两类命令接口:

类型 宏名 特征 使用场景
Task CMD CORE_BRIDGE_TASK_COMMAND_TO_ADD 有应答、有超时 需要确认的配置/数据
Instant CMD CORE_BRIDGE_INSTANT_COMMAND_TO_ADD 无应答、即发即走 频率调整、状态广播
Core Bridge 命令注册示例
c 复制代码
// 文件: services/audio_process/audio_process_spa_algo_m55_ipc.cpp

// 注册四元数转发命令(M33 → M55,无需响应)
CORE_BRIDGE_TASK_COMMAND_TO_ADD(
    MCU_DSP_M55_TASK_CMD_RS3D_QUAT,   // 命令码 0x194
    "MCU2DSP: quaternion receive",     // 调试字符串
    quat_relay_tx_handler,             // 发送侧 handler
    nullptr,                           // 接收侧 handler(M33 不接收此命令)
    0,                                 // 超时 0ms
    nullptr, nullptr, nullptr
);

// 注册控制命令(M33 ↔ M55 双向)
CORE_BRIDGE_TASK_COMMAND_TO_ADD(
    MCU_DSP_M55_TASK_CMD_RS3D_CTRL,   // 命令码 0x195
    "MCU2DSP: control",
    m55_ctrl_tx_handler,               // TX: 发送配置
    m55_ctrl_rx_handler,               // RX: 处理 CFG_REQ / CFG_CONFIRM
    0, nullptr, nullptr, nullptr
);

2.2 消息包格式

SensorHub ↔ M33 数据包
复制代码
┌────────────────────────────────────────────────────────┐
│  app_core_bridge_data_packet_t  (共 516 bytes)          │
├────────────┬────────────┬───────────────────────────────┤
│  cmdcode   │  cmdseq    │  content[512]                 │
│  uint16_t  │  uint16_t  │  payload (实际数据)            │
│  (2 bytes) │  (2 bytes) │  (最大 512 bytes)              │
└────────────┴────────────┴───────────────────────────────┘
M33 ↔ M55 数据包
复制代码
┌───────────────────────────────────────────────────────────┐
│  app_dsp_m55_bridge_data_packet_t  (共 520 bytes)          │
├────────────┬────────────┬────────────┬────────────────────┤
│  length    │  cmdcode   │  cmdseq    │  content[512]      │
│  uint32_t  │  uint16_t  │  uint16_t  │  payload           │
│  (4 bytes) │  (2 bytes) │  (2 bytes) │  (最大 512 bytes)  │
└────────────┴────────────┴────────────┴────────────────────┘

源码位置:

  • apps/sensorhub/app_sensor_hub.cpp 第 56-61 行
  • apps/dsp_m55/app_dsp_m55.cpp 第 53-59 行

2.2.1 执行流程与设计原理(M33 ↔ M55:app_dsp_m55_bridge_*)

这一节把 app_dsp_m55_bridge_send_cmd()app_dsp_m55_bridge_data_received() 的执行链路拆开说明。

关键 API 与运行上下文:

  1. 发送侧入口(线程上下文,通常是业务线程/M33 侧调用):
c 复制代码
// apps/dsp_m55/app_dsp_m55.h
int32_t app_dsp_m55_bridge_send_cmd(uint16_t cmd_code, uint8_t *p_buff, uint16_t length);
  1. 收包入口(IRQ 上下文,由底层 intersys/mailbox IRQ 回调进入):
c 复制代码
// apps/dsp_m55/app_dsp_m55.h
unsigned int app_dsp_m55_bridge_data_received(const void* data, unsigned int len);
  1. Instant vs Task 的处理差异(非常重要):

  2. Instant CMD :在 app_dsp_m55_bridge_data_received() 中直接调用命令表里的 cmdhandler(),因此 handler 运行在 IRQ 上下文,必须"短平快"(不要打印大量日志/不要阻塞/不要做复杂 memcpy)。

  3. Task CMD :在 app_dsp_m55_bridge_data_received() 中只负责入队,然后由 RX 线程把数据出队并调用 cmdhandler(),因此 handler 运行在线程上下文,允许做相对复杂处理。

A) 发送路径(app_dsp_m55_bridge_send_cmd → core_bridge_send → 远端)
复制代码
[Caller Thread]
  |
  | app_dsp_m55_bridge_send_cmd(cmd_code, payload, len)
  |  - 复制 payload 到 tx_mailbox_heap
  |  - 投递到 osMailQ: app_dsp_m55_bridge_tx_mailbox
  v
[core_bridge_tx_thread]
  |
  | osMailGet() 取出待发送消息
  | 查表:app_dsp_m55_bridge_get_task_cmd_entry()/get_instant_cmd_entry()
  | 调用:core_bridge_cmd_transmit_handler(payload, len)
  v
[Transmit Handler (per-cmd)]
  |
  | (常见实现是二选一)
  | app_dsp_m55_bridge_send_data_without_waiting_rsp()
  | app_dsp_m55_bridge_send_data_with_waiting_rsp()
  v
app_dsp_m55_bridge_transmit_data()
  |
  | 组包 app_dsp_m55_bridge_data_packet_t
  | core_bridge_send((uint8_t*)&pkt, pkt.length + sizeof(uint32_t), trycnt)
  |   -> 内部调用 dsp_m55_send(),失败重试+delay
  | 等待 tx_done 信号量(确保对端已取走/硬件发送完成)
  v
[Remote Core IRQ] 触发接收

补充说明:

  1. core_bridge_send()apps/dsp_m55/app_dsp_m55.cpp 内部静态封装,对 dsp_m55_send() 做重试与告警。
  2. app_dsp_m55_bridge_send_data_with_waiting_rsp() 会在发送后等待 app_dsp_m55_bridge_wait_cmd_rsp_id,由对端回 MCU_DSP_M55_TASK_CMD_RSP 并在本端 rsp handler 中释放信号量完成握手。
B) 接收路径(IRQ 入队/直调 → RX 线程分发 → cmdhandler)
复制代码
[M55 intersys/mailbox IRQ]
  |
  | dsp_m55_core_register_rx_irq_handler(..., app_dsp_m55_bridge_data_received)
  v
app_dsp_m55_bridge_data_received(data, len)     // IRQ 上下文
  |
  | 解析 app_dsp_m55_bridge_data_packet_t
  |
  | if Instant CMD:
  |   - 查表 app_dsp_m55_bridge_get_instant_cmd_entry(cmdcode)
  |   - 直接 pInstance->cmdhandler(content, payload_len)
  |
  | else Task CMD:
  |   - app_dsp_m55_bridge_queue_push(CQueue, data, len)
  |   - osSignalSet(rx_thread, DATA_RECEIVED)
  v
[core_bridge_rx_thread]
  |
  | while(queue not empty):
  |   - peek 长度
  |   - pop 到临时 buffer
  |   - app_dsp_m55_bridge_rx_handler(tmp, rcv_len)
  v
app_dsp_m55_bridge_rx_handler()                 // 线程上下文
  |
  | 查表 app_dsp_m55_bridge_get_task_cmd_entry(cmdcode)
  | 调用 pInstance->cmdhandler(content, payload_len)
  v
[Command Handler]

2.2.3 回包/等待应答(Task CMD with RSP)的独立时序图

下面的时序图对应 *_send_data_with_waiting_rsp() 这一类"发起请求并等待对端应答"的模式。

A) M33 ↔ M55(app_dsp_m55_bridge_*)
复制代码
M33 (Client/Caller)                M33 Core-Bridge                M55
      |                                   |                        |
      | app_dsp_m55_bridge_send_cmd()     |                        |
      |---------------------------------->|                        |
      |   (enqueue tx mailbox)            |                        |
      |                                   | core_bridge_tx_thread  |
      |                                   |  -> transmit_handler   |
      |                                   |  -> app_dsp_m55_bridge_
      |                                   |     send_data_with_
      |                                   |     waiting_rsp()      |
      |                                   |  -> transmit_data()    |
      |                                   |  -> core_bridge_send() |
      |                                   |----------------------->|
      |                                   |                        | app_dsp_m55_bridge_data_received()
      |                                   |                        |  (Task CMD -> enqueue)
      |                                   |                        | core_bridge_rx_thread
      |                                   |                        |  -> cmdhandler()
      |                                   |                        |  -> app_dsp_m55_bridge_send_rsp(rspcode,...)
      |                                   |<-----------------------|
      |                                   | app_dsp_m55_bridge_data_received()
      |                                   |  (RSP cmd -> enqueue)
      |                                   | core_bridge_rx_thread
      |                                   |  -> app_dsp_m55_bridge_cmd_rsp_handler()
      |                                   |     osSemaphoreRelease(wait_cmd_rsp)
      |<----------------------------------|                        |
      | (caller thread wait returns)      |                        |

关键点:

  1. 发送侧真正"等待"的位置在 app_dsp_m55_bridge_send_data_with_waiting_rsp(),它等待 app_dsp_m55_bridge_wait_cmd_rsp_id
  2. 对端回包统一用 MCU_DSP_M55_TASK_CMD_RSP 承载,payload 的开头会带上"被响应的原始 cmdcode",本端据此路由到对应 rsp_handle
B) SensorHub ↔ M33(app_core_bridge_*)
复制代码
M33 (Client/Caller)                 M33 Core-Bridge              SensorHub
      |                                    |                        |
      | app_core_bridge_send_cmd()         |                        |
      |----------------------------------->|                        |
      |   (enqueue tx mailbox)             |                        |
      |                                    | core_bridge_tx_thread  |
      |                                    |  -> transmit_handler   |
      |                                    |  -> app_core_bridge_
      |                                    |     send_data_with_
      |                                    |     waiting_rsp()      |
      |                                    |  -> transmit_data()    |
      |                                    |  -> sensor_hub_send()  |
      |                                    |----------------------->|
      |                                    |                        | app_core_bridge_data_received()
      |                                    |                        |  (Task CMD -> enqueue)
      |                                    |                        | core_bridge_rx_thread
      |                                    |                        |  -> cmdhandler()
      |                                    |                        |  -> app_core_bridge_send_rsp(rspcode,...)
      |                                    |<-----------------------|
      |                                    | app_core_bridge_data_received()
      |                                    |  (RSP cmd -> enqueue)
      |                                    | core_bridge_rx_thread
      |                                    |  -> app_core_bridge_cmd_rsp_handler()
      |                                    |     osSemaphoreRelease(wait_cmd_rsp)
      |<-----------------------------------|                        |
      | (caller thread wait returns)       |                        |

注意:SensorHub 侧 app_core_bridge_transmit_data() 内部对 sensor_hub_send() 有失败重试(最多 10 次)并延时 1ms,这是避免低层 IPC 暂时忙导致直接失败。

2.2.2 执行流程与设计原理(SensorHub ↔ M33:app_core_bridge_*)

SensorHub 通道的结构与 M55 通道类似,差别主要在于底层发送函数与 RTOS 实现(部分平台下使用 CMSIS-RTOS2 的 memory pool + message queue)。

关键 API 与运行上下文:

c 复制代码
// apps/sensorhub/app_sensor_hub.h
int32_t app_core_bridge_send_cmd(uint16_t cmd_code, uint8_t *p_buff, uint16_t length);
unsigned int app_core_bridge_data_received(const void* data, unsigned int len);
A) 发送路径(app_core_bridge_send_cmd → app_sensor_hub_send → sensor_hub_send)
复制代码
[Caller Thread]
  |
  | app_core_bridge_send_cmd(cmd_code, payload, len)
  |  - 复制 payload 到 tx_mailbox_heap
  |  - 投递到 message queue: app_core_bridge_tx_mailbox
  v
[core_bridge_tx_thread]
  |
  | osMessageQueueGet() 取出待发送消息
  | 查表:app_core_bridge_get_task_cmd_entry()/get_instant_cmd_entry()
  | 调用:core_bridge_cmd_transmit_handler(payload, len)
  v
app_core_bridge_transmit_data() / send_instant_cmd_data()
  |
  | 组包 app_core_bridge_data_packet_t
  | app_sensor_hub_send(...)
  |   -> sensor_hub_send(...)
  |   -> 失败重试(最多 10 次) + osDelay(1)
  | 等待 tx_done 信号量
  v
[Remote Core IRQ] 触发接收
B) 接收路径(IRQ 入队/直调 → RX 线程分发 → cmdhandler)
复制代码
[SensorHub intersys/mailbox IRQ]
  |
  | sensor_hub_core_register_rx_irq_handler(..., app_core_bridge_data_received)
  v
app_core_bridge_data_received(data, len)        // IRQ 上下文
  |
  | 解析 app_core_bridge_data_packet_t
  |
  | if Instant CMD:
  |   - 查表 app_core_bridge_get_instant_cmd_entry(cmdcode)
  |   - 直接 pInstance->cmdhandler(content, payload_len)
  |
  | else Task CMD:
  |   - app_core_bridge_queue_push(CQueue, data, len)
  |   - osThreadFlagsSet(rx_thread, DATA_RECEIVED)
  v
[core_bridge_rx_thread]
  |
  | while(queue not empty):
  |   - peek 长度
  |   - pop 到临时 buffer
  |   - app_core_bridge_rx_handler(tmp, rcv_len)
  v
app_core_bridge_rx_handler()                    // 线程上下文
  |
  | 查表 app_core_bridge_get_task_cmd_entry(cmdcode)
  | 调用 pInstance->cmdhandler(content, payload_len)
  v
[Command Handler]

Instant CMD 的注意事项同上:handler 运行在 IRQ 上下文,必须避免阻塞/耗时操作。

2.3 TLV 控制协议

SensorHub 头部追踪使能/禁用使用 TLV(Tag-Length-Value) 格式,便于协议扩展:

复制代码
┌─────────┬─────────┬─────────┐
│  TAG    │  LEN    │  VALUE  │
│  1 byte │  1 byte │  N byte │
└─────────┴─────────┴─────────┘

TAG::SENSOR (启停传感器):
  TAG=0x01  LEN=0x01  VALUE=0x00(关) / 0x01(开) / 0x02(校准)

TAG::ARC_MODE (自动重定心):
  TAG=0x02  LEN=0x01  VALUE=mode

TAG::GUID (传感器 GUID):
  TAG=0x04  LEN=0x02  VALUE=guid[2]
c 复制代码
// 文件: services/audio_process/audio_process_spa_algo_ipc.cpp
void spa_algo_audio_senhub_ipc_headtracking_enable(bool enable) {
    uint8_t tlv[3];
    tlv[0] = (uint8_t)IPC::TAG::SENSOR;    // TAG
    tlv[1] = IPC::SENSOR_LEN;              // LEN = 1
    tlv[2] = enable ? IPC::SENSOR_ON       // VALUE
                    : IPC::SENSOR_OFF;
    app_core_bridge_send_cmd(
        MCU_SENSOR_HUB_TASK_CMD_MOTIONENGINE, tlv, sizeof(tlv));
}

2.4 M33 ↔ M55 配置协议(TAG 枚举)

c 复制代码
// 文件: services/gaf_cc_stream/inc/gaf_cc_spa_algo_process_ipc_cmd.h
namespace M55_RS3D_IPC {
    enum class TAG : uint8_t {
        HEADTRACKING = 1,   // 头部追踪 开/关
        SPATIAL      = 2,   // 空间音效 开/关
        SPATIAL_EQ   = 3,   // 空间音效均衡器配置
        DRIVER_EQ    = 4,   // 驱动均衡器配置
        PRESET       = 5,   // 音效预设
        CFG_IND      = 6,   // 配置指示(M33 → M55,握手发起)
        CFG_REQ      = 7,   // 配置请求(M55 → M33,索取参数)
        CFG_SET      = 8,   // 配置下发(M33 → M55,参数写入)
        CFG_CONFIRM  = 9,   // 配置确认(M55 → M33,参数生效)
    };
}

2.5 四元数数据结构

c 复制代码
// 四元数报告包(SensorHub → M33)
namespace IPC {
    struct QuaternionRpt_s {
        struct {
            int16_t w, x, y, z;   // 16-bit 定点四元数
        } quat;
        uint32_t timestamp;        // 时间戳
        // 总计: 12 bytes
    };
}

// M55 RS3D 配置(M33 → M55)
namespace M55_RS3D_IPC {
    struct Config_s {
        bool    spatial_en;          // 空间音效总开关
        bool    headtracking_en;     // 头部追踪开关
        AUDIO_CONFIG_TYPE_T preset;  // 音效预设
        uint8_t hrtf[HRTF_ISB_LENGTH]; // HRTF 滤波器系数
    };

    struct ConfigReq_s {           // 12 bytes
        TAG      tag;              // 1 byte
        uint8_t  len;              // 1 byte
        uint16_t padding;          // 2 bytes (对齐)
        uint32_t sampleFreq;       // 4 bytes (采样率)
        Config_s *cfgPtr;          // 4 bytes (配置指针)
    };
}

3. 共享内存与缓冲区设计

本章先澄清两个容易混淆的概念:

  1. 跨核通讯(IPC) :用 Mailbox/中断把"命令/事件"从核 A 送到核 B。典型实现是 Core Bridge 的 TASK_CMD / INSTANT_CMD
  2. 内存共享(Shared Memory):两核访问同一段物理 RAM,用于搬运大数据(PCM/大块参数)。共享内存本身不"传输",需要配套的同步协议(写指针/读指针/doorbell)。

工程里通常是"IPC 负责敲门 + 共享内存负责装货":

复制代码
共享内存写入(Producer)  --->  IPC(doorbell/通知)  --->  共享内存读取(Consumer)

3.1 缓冲区层次结构

复制代码
  ┌─────────────────────────────────────────────────────────┐
  │                    M33 内存空间                          │
  │                                                         │
  │  ┌─────────────────────────────────────────────────┐   │
  │  │  SensorHub RX Ring Buffer (CQueue)               │   │
  │  │  大小: APP_CORE_BRIDGE_RX_BUFF_SIZE = 2048 bytes │   │
  │  │  类型: 环形队列 (循环覆盖,无阻塞)               │   │
  │  │  操作:                                            │   │
  │  │    写入: EnCQueue()  ← SensorHub 数据到达        │   │
  │  │    读取: DeCQueue()  → 上层 handler 消费         │   │
  │  │    预查: PeekCQueue() → 无破坏性检查长度         │   │
  │  └─────────────────────────────────────────────────┘   │
  │                                                         │
  │  ┌─────────────────────────────────────────────────┐   │
  │  │  四元数消息队列 (osMailQ)                         │   │
  │  │  容量: 4 元素                                    │   │
  │  │  元素类型: IPC::QuaternionRpt_s                  │   │
  │  │  内存池: 4 块预分配                              │   │
  │  └─────────────────────────────────────────────────┘   │
  │                                                         │
  │  ┌─────────────────────────────────────────────────┐   │
  │  │  M55 配置邮箱 (osMailQ)                           │   │
  │  │  容量: 4 元素                                    │   │
  │  │  元素类型: M55_RS3D_IPC::ConfigReq_s (12 bytes)  │   │
  │  │  用途: M55 配置请求的异步处理                    │   │
  │  └─────────────────────────────────────────────────┘   │
  │                                                         │
  │  ┌─────────────────────────────────────────────────┐   │
  │  │  Core Bridge TX Mailbox                          │   │
  │  │  M33 → SensorHub: 最大 8  条目                  │   │
  │  │  M33 → M55:       最大 30 条目                  │   │
  │  └─────────────────────────────────────────────────┘   │
  └─────────────────────────────────────────────────────────┘

3.2 环形队列操作流程

复制代码
SensorHub IPC 中断
       │
       ▼
app_core_bridge_receive_irq_handler()
       │
       ├─ [检查队列空间]
       │
       ▼
EnCQueue(app_core_bridge_receive_queue, data, len)
       │                     ↑
       │              环形缓冲区满时
       │              丢弃最旧数据
       ▼
RTOS Task 调度
app_core_bridge_task()
       │
       ├─ PeekCQueue() → 读取下一消息长度
       ├─ DeCQueue()   → 取出消息数据
       │
       ▼
app_core_bridge_receive_handler(cmdcode, data, len)
       │
       ├─ cmdcode == MCU_SENSOR_HUB_TASK_CMD_SEND_QUATERNION
       │       └─ quaternion_send_receive_handler()
       │
       ├─ cmdcode == MCU_SENSOR_HUB_TASK_CMD_SEND_GYROSCOPE
       │       └─ gyroscope_send_receive_handler()
       │
       └─ cmdcode == MCU_SENSOR_HUB_TASK_CMD_SEND_RAW_ACC
               └─ raw_acc_send_receive_handler()

3.3 传感器原始数据缓冲(滑动窗口平均)

c 复制代码
// 文件: services/audio_process/audio_process_spa_algo_ipc.cpp
#define GYRO_DATA_TOTAL_COUNT  5   // 滑动窗口大小

typedef struct {
    uint16_t xindex, yindex, zindex;     // 环形索引
    int16_t  x[GYRO_DATA_TOTAL_COUNT];   // X 轴历史采样
    int16_t  y[GYRO_DATA_TOTAL_COUNT];   // Y 轴历史采样
    int16_t  z[GYRO_DATA_TOTAL_COUNT];   // Z 轴历史采样
} RawGyroData_t;

// 写入:循环覆盖最旧采样
void spa_algo_gyro_data_set(int16_t gyro_data[3]) {
    gyro_data_buf.x[gyro_data_buf.xindex++ % GYRO_DATA_TOTAL_COUNT] = gyro_data[0];
    gyro_data_buf.y[gyro_data_buf.yindex++ % GYRO_DATA_TOTAL_COUNT] = gyro_data[1];
    gyro_data_buf.z[gyro_data_buf.zindex++ % GYRO_DATA_TOTAL_COUNT] = gyro_data[2];
}

// 读取:对 5 个历史采样求均值,降噪
void spa_algo_caculate_gyro_data(void) {
    for (uint8_t i = 0; i < GYRO_DATA_TOTAL_COUNT; i++) {
        total.x += gyro_data_buf.x[i];
        total.y += gyro_data_buf.y[i];
        total.z += gyro_data_buf.z[i];
    }
    avg.x = total.x / GYRO_DATA_TOTAL_COUNT;
    avg.y = total.y / GYRO_DATA_TOTAL_COUNT;
    avg.z = total.z / GYRO_DATA_TOTAL_COUNT;
}

4. 启动与初始化流程

4.1 系统启动时序(M33 视角)

复制代码
系统上电 / 复位
       │
       ▼
M33 主程序初始化
       │
       ├─────────────────────────────────────────────────────────┐
       │                                                         │
       ▼                                                         ▼
app_sensor_hub_init()                            app_dsp_m55_init(user)
       │                                                         │
       ├─ app_core_bridge_init()                 ├─ app_dsp_m55_bridge_init()
       │  └─ 初始化 RX 环形缓冲 (2048 bytes)     ├─ 注册 RX/TX handlers
       ├─ 注册 SensorHub 命令 handlers            ├─ dsp_m55_open()
       ├─ 创建接收任务 (RTOS Task)                │  └─ 启动 M55 核心
       └─ 等待 SensorHub ping                     ├─ osDelay(20ms)
                                                  │  (等待 M55 启动稳定)
                                                  ├─ app_dsp_m55_init_module()
                                                  └─ osDelay(80ms)
       │                                                         │
       └─────────────────┬───────────────────────────────────────┘
                         │
                         ▼
                spa_algo_audio_init()
                         │
          ┌──────────────┼────────────────┐
          │              │                │
          ▼              ▼                ▼
spa_algo_audio_   spa_algo_audio_   spa_algo_audio_
senhub_ipc_       m55_ipc_          tota_init()
init()            init()
  │                 │
  ├─ 创建四元数      ├─ 创建 mailQId
  │  消息队列(4)     │  (4 × ConfigReq_s)
  └─ 创建内存池(4)   └─ 创建 spa_algo_config
                       _processor_thread
                       (priority: AboveNormal)
                         │
                         ▼
                spa_algo_audio_m55_ipc_cfg_ind()
               (发送 CFG_IND,触发 M55 请求配置)
                         │
                         ▼
               ca_head_tracking_enable(true)
                         │
                          ├─ spa_algo_audio_m55_ipc_headtracking_enable(true)
                          └─ spa_algo_audio_senhub_ipc_headtracking_enable(true)
                              └─ 发送 TLV SENSOR_ON → SensorHub

4.2 M55 核心的 Ping-Pong 握手

复制代码
  M33                                M55
   │                                  │
   │──── dsp_m55_open() ─────────────►│ (M55 核启动)
   │                                  │
   │     osDelay(20ms)                │ (M55 内部初始化)
   │                                  │
   │◄─── MCU_DSP_M55_TASK_CMD_PING ───│ (M55 发送心跳)
   │                                  │
   │  app_dsp_m55_ping_received_       │
   │  handler()                       │
   │  g_m55_core_runing = true        │
   │                                  │
   │──── MCU_DSP_M55_TASK_CMD_RS3D ──►│ (M33 发送 CFG_IND)
   │     _CTRL / TAG::CFG_IND         │
   │                                  │
   │◄─── TAG::CFG_REQ ────────────────│ (M55 请求配置参数)
   │                                  │
   │  processCfgReq()                 │
   │  ca_cfg_push()                   │
   │                                  │
   │──── TAG::CFG_SET ───────────────►│ (M33 下发 HRTF + 参数)
   │                                  │
   │◄─── TAG::CFG_CONFIRM ────────────│ (M55 确认配置生效)
   │                                  │
   │  ca_cfg_confirm()                │
   │  [进入正常工作状态]               │ [开始渲染空间音效]

4.3 SensorHub 内部初始化

复制代码
SensorHub 上电
       │
       ▼
sensor_hub_core_app.c::main()
       │
       ├─ 初始化 RTOS / 调度器
       ├─ 初始化 IPC 通道
       ├─ 加载 MotionEngine 算法库
       │  └─ fme_Mobile_init()
       ├─ 注册传感器事件回调
       │  ├─ on_quaternion_ready()
       │  ├─ on_gyroscope_ready()
       │  └─ on_accelerometer_ready()
       └─ 启动传感器采样
              │
              ▼
         [等待 M33 的 SENSOR_ON TLV]
              │
              ▼
         开始上报四元数 / 陀螺仪数据

5. 时序执行流程

5.1 正常工作时序图(头部追踪 → 空间音效)

复制代码
  SensorHub          M33 (MCU)              M55 (DSP)
      │                  │                      │
      │  每 10ms 采样一次 │                      │
      │                  │                      │
  [IMU 硬件中断]          │                      │
      │                  │                      │
  MotionEngine 计算       │                      │
  四元数 Q(w,x,y,z)      │                      │
      │                  │                      │
      │──QUAT(0x027)────►│                      │
      │  (12 bytes)       │                      │
      │                  │ quaternion_send_       │
      │                  │ receive_handler()     │
      │                  │                      │
      │                  │ [条件检查]            │
      │                  │  sa_effect_side==     │
      │                  │  SA_ON_EAR &&         │
      │                  │  m55_is_running() &&  │
      │                  │  ca_cfg_is_confirmed()│
      │                  │                      │
      │                  │──RS3D_QUAT(0x194)───►│
      │                  │  (8 bytes 四元数)     │
      │                  │                      │ spa_algo
      │                  │                      │ 头部追踪计算
      │                  │                      │ HRTF 滤波渲染
      │                  │                      │
      │                  │──RS3D_QUAT(0x194)───►│ (下一帧)
      │  [连续 100Hz 更新] │                      │
      │                  │                      │
      │                  │ [同时生成 HID 报告]   │
      │                  │ → app_bt_hid_process  │
      │                  │   _sensor_report()   │
      │                  │ → 发送到手机          │

5.2 配置更新时序图

复制代码
  用户操作 / APP 命令
       │
       ▼
  M33: ca_spatial_enable(true)
       │
       ├─ audio_spatial_en = true
       ├─ hal_cmu_axi_freq_req(M55, 208MHz)  ← 请求提升 M55 频率(由 M33 落地)
        ├─ spa_algo_audio_m55_ipc_spatial_enable(true)
       │       └─ 压入 mailQId (TAG::SPATIAL)
       └─ pendVsEngineConfig = 2
                         │
              [RTOS 调度: spa_algo_config_processor_thread 唤醒]
                         │
              osMailGet(mailQId) → 取出配置项
                         │
              switch(TAG):
              case SPATIAL:
                  ca_cfg_push(cfgPtr, sampleFreq)
                  发送 CFG_SET → M55
                         │
                         ▼
              M55 接收 CFG_SET
               spa_algo 更新参数
              发送 CFG_CONFIRM → M33
                         │
                         ▼
              M33: ca_cfg_confirm()
              [配置生效,四元数转发开始]

5.3 音频处理 Tick 时序

复制代码
音频 DMA 中断 (每帧 ~5ms)
       │
       ▼
spa_algo_audio_process(buf, len)
       │
       ├─ [pendVsEngineConfig > 0 ?]
       │   ├─ --pendVsEngineConfig
       │   └─ [== 0] vsengineConfig()  ← 应用延迟配置
       │
        ├─ spa_algo_audio_senhub_ipc_check()
       │   └─ 读取 SensorHub 消息队列
       │       └─ [有数据] 调用对应 handler
       │
        ├─ spa_algo_audio_m55_ipc_check()
       │   └─ osMailGet(mailQId, 0)   ← 非阻塞轮询
       │       └─ 处理 CFG_REQ / CFG_CONFIRM
       │
        └─ spa_algo_audio_tota_check()
           └─ 处理 TOTA 调试通道消息

6. 核心代码路径索引

功能 文件路径 关键函数
空间音频主入口 services/audio_process/audio_process_spa_algo.cpp spa_algo_audio_init(), spa_algo_audio_process()
头部追踪使能 services/audio_process/audio_process_spa_algo.cpp:295 ca_head_tracking_enable()
空间音效使能 services/audio_process/audio_process_spa_algo.cpp:256 ca_spatial_enable()
SensorHub IPC services/audio_process/audio_process_spa_algo_ipc.cpp spa_algo_audio_senhub_ipc_init()
四元数接收 services/audio_process/audio_process_spa_algo_ipc.cpp:355 quaternion_send_receive_handler()
陀螺仪接收 services/audio_process/audio_process_spa_algo_ipc.cpp:455 gyroscope_send_receive_handler()
四元数转发到 M55 services/audio_process/audio_process_spa_algo_m55_ipc.cpp:46 spa_algo_audio_quat_relay()
M55 IPC 初始化 services/audio_process/audio_process_spa_algo_m55_ipc.cpp spa_algo_audio_m55_ipc_init()
M55 配置处理线程 services/audio_process/audio_process_spa_algo_m55_ipc.cpp:131 spa_algo_config_processor_thread()
SensorHub App apps/sensorhub/app_sensor_hub.cpp app_sensor_hub_init()
M55 App apps/dsp_m55/mcu_dsp_m55_app.cpp app_dsp_m55_init()
M55 命令定义 apps/dsp_m55/app_dsp_m55.h MCU_DSP_M55_TASK_CMD_*
SensorHub 命令定义 apps/sensorhub/app_sensor_hub.h MCU_SENSOR_HUB_TASK_CMD_*
M55 RS3D IPC 协议 services/gaf_cc_stream/inc/gaf_cc_spa_algo_process_ipc_cmd.h M55_RS3D_IPC::TAG

7. 主频调度策略

7.1 频率档位

档位常量 频率 使用场景
HAL_CMU_AXI_FREQ_208M 208 MHz 空间音效正常处理
HAL_CMU_AXI_FREQ_260M 260 MHz 复杂 HRTF 计算(按需)
默认低频 < 100 MHz 空闲 / 无音效

说明:本项目不支持 340M 主频档。芯片制程/规格不允许在量产配置下把 M55 主频设置到该档位。

7.2 频率调度逻辑

这里要强调一点:M55 的主频最终由 M33 侧控制

  1. M33 作为系统主控负责实际调用 hal_cmu_axi_freq_req()
  2. M55 侧如果需要提升/降低算力,通常通过 IPC 发起"频率请求"(instant cmd),由 M33 侧落地成真正的 CMU 配置。
  3. 若使能 SUBSYS_RMT_SYSFREQ_REQ,则 hal_sysfreq 会把"设置频率"动作通过回调转交给系统主控侧(用于多子系统/远端请求场景)。
c 复制代码
// 文件(示例): services/audio_process/audio_process_spa_algo.cpp
void ca_spatial_enable(bool enable) {
    if (enable) {
        // 空间音效开启 → 请求提升 M55 主频(由 M33 落地)
        hal_cmu_axi_freq_req(HAL_CMU_AXI_FREQ_USER_M55,
                             HAL_CMU_AXI_FREQ_208M);
    } else {
        // 空间音效关闭 → 释放频率请求,降回默认
        hal_cmu_axi_freq_req(HAL_CMU_AXI_FREQ_USER_M55,
                             HAL_CMU_AXI_FREQ_ANY);
    }
}

7.3 频率-功耗权衡

复制代码
  低功耗模式                          高性能模式
       │                                  │
  M55 off / sleep              M55 @ 208/260 MHz
  SensorHub 低频采样            spa_algo 全功率
  无空间音效                    100Hz 头部追踪
       │                                  │
  触发条件:                      触发条件:
  - 耳机摘下 (off-ear)           - 耳机戴上 (on-ear)
  - 用户关闭空间音效              - 用户开启空间音效
  - 无 BT 连接                   - BT 连接 + A2DP/LE Audio 播放

8. 方案设计优势分析

8.1 多核异构分工优势

核心 特点 分工理由
SensorHub 低功耗,专用传感器接口 持续运行,功耗敏感;避免唤醒主核
M33 通用控制,实时性强 协议栈、路由、HID、业务逻辑;不适合密集 DSP
M55 高性能 DSP,SIMD 指令 HRTF 卷积计算密集,专用 DSP 效率远高于 M33

8.2 Core Bridge 设计优势

  1. 解耦发送与接收 : Handler 注册机制,收发逻辑分离,新命令只需 CORE_BRIDGE_TASK_COMMAND_TO_ADD 一行注册
  2. 命令去重与序列化 : cmdseq 序列号防止重复处理,硬件邮箱保序
  3. 两类命令满足不同需求: Task CMD(有确认)用于配置,Instant CMD(无确认)用于高频数据
  4. 调试友好: 每个命令带字符串标识,日志追踪清晰

8.3 TLV 协议扩展优势

复制代码
传统方式(固定结构体):
  struct Cmd { uint8_t type; bool on_off; uint8_t mode; }
  → 新增字段必须修改结构体,破坏二进制兼容

TLV 方式:
  [TAG][LEN][VALUE][TAG][LEN][VALUE]...
  → 接收方只解析认识的 TAG,忽略未知 TAG
  → 协议向前兼容,双核固件可独立升级

8.4 邮箱 + RTOS 信号异步处理优势

复制代码
同步方式 (阻塞):
  M33 发送 CFG_SET → 等待 M55 响应 → 阻塞 1~5ms
  影响音频 DMA 中断响应,导致卡顿

异步方式 (邮箱 + 信号):
  M33 发送 CFG_SET → 立即返回
  spa_algo_config_processor_thread 独立处理响应
  音频 DMA 不受影响

8.5 环形缓冲区优势

复制代码
固定数组:
  - 读写必须同步,需要 mutex
  - 溢出处理复杂

CQueue 环形缓冲:
  - 单生产者单消费者天然无锁
  - 满时丢弃旧数据,保持实时性
  - 2KB 足够缓冲突发传感器数据

8.6 滑动窗口平均降噪

复制代码
原始陀螺仪数据存在高频抖动噪声:
  [128, 130, -5(噪声), 131, 129] → 直接取会产生跳变

滑动窗口平均(5点):
  avg = (128+130-5+131+129) / 5 = 102.6 → 有噪声仍影响

实际效果:
  多数时刻噪声点稀少,5点窗口足以平滑
  计算量极低(加法 + 移位),不引入延迟

9. 关键数据结构速查

9.1 命令码一览

c 复制代码
// SensorHub → M33
MCU_SENSOR_HUB_TASK_CMD_PING            = 0x037  // 心跳
MCU_SENSOR_HUB_TASK_CMD_SEND_QUATERNION = 0x027  // 四元数 (12 bytes)
MCU_SENSOR_HUB_TASK_CMD_SEND_GYROSCOPE  = 0x028  // 陀螺仪 (6 bytes)
MCU_SENSOR_HUB_TASK_CMD_SEND_RAW_ACC    = 0x029  // 原始加速度 (6 bytes)
MCU_SENSOR_HUB_TASK_CMD_SEND_RAW_GYRO   = 0x033  // 原始陀螺仪 (6 bytes)

// M33 → SensorHub
MCU_SENSOR_HUB_TASK_CMD_MOTIONENGINE    = 0x034  // 运动引擎控制 (TLV)

// M33 → M55
MCU_DSP_M55_TASK_CMD_RS3D_QUAT          = 0x194  // 四元数转发 (8 bytes)
MCU_DSP_M55_TASK_CMD_RS3D_CTRL          = 0x195  // 控制/配置 (TLV)

// M33 → M55 (无应答)
MCU_DSP_M55_INSTANT_CMD_AXI_SYSFREQ_REQ = 0x182 // 频率请求
MCU_DSP_M55_INSTANT_CMD_DISABLE_M55     = 0x183  // 禁用 M55

9.2 缓冲区尺寸汇总

缓冲区名称 大小 位置
SensorHub RX 环形缓冲 2048 bytes M33 内存
数据包最大 payload 512 bytes 每条消息
四元数消息队列 4 元素 × 12 bytes M33 内存
M55 配置邮箱 4 元素 × 12 bytes M33 内存
M33→M55 TX Mailbox 30 条目 Core Bridge
M33→SensorHub TX Mailbox 8 条目 Core Bridge
陀螺仪滑动窗口 5 采样 × 3轴 × 2 bytes = 30 bytes M33 内存

9.3 RTOS 任务优先级

任务 优先级 说明
spa_algo_config_processor_thread AboveNormal M55 配置处理,优先响应
app_core_bridge_task (SensorHub) Normal SensorHub 消息分发
app_dsp_m55_bridge_task Normal M55 消息分发

附录:完整数据流一图概览

复制代码
                        ┌─────────────────────────────────────────────────┐
                        │              BES Best1600_SOC 空间音频系统        │
                        └─────────────────────────────────────────────────┘

[IMU 传感器]                [SensorHub]                  [M33 MCU]
  加速度计 ──────SPI/I2C──► MotionEngine 算法  ─QUAT(0x027)──► quaternion_handler()
  陀螺仪   ──────SPI/I2C──► 四元数计算         ─GYRO(0x028)──► gyroscope_handler()
                            原始数据打包        ─ACC (0x029)──► raw_acc_handler()
                                                              │
                                          TLV SENSOR_ON ◄────┘
                                          (0x034 MOTIONENGINE)
                                                              │
                                                              ├── HID 报告 ──► 手机
                                                              │
                                                              │ RS3D_QUAT(0x194)
                                                              │ (条件: on-ear &&
                                                              │  m55_running &&
                                                              │  cfg_confirmed)
                                                              │
                                                              ▼
                                                         [M55 DSP]
                                                     spa_algo 引擎
                                                    HRTF 头部追踪渲染
                                                    208MHz 高速运行
                                                              │
                                                              ▼
                                                    [DAC 输出 → 耳机]
                                                    3D 空间音效

基于 Best1600_SOC 项目 develop 分支代码分析
涉及文件: services/audio_process/, apps/dsp_m55/, apps/sensorhub/, tests/dsp_m55/, tests/sensor_hub/


补充:共享内存的落地形态(@utils/cfifo / @services/mcpp / @services/gaf_cc_stream)

A. @utils/cfifo:共享内存环形 FIFO 的基本语义

@utils/cfifo 提供一个轻量 ring buffer:

  1. cfifo_put():写入连续字节流;空间不够返回 FIFO_ERR
  2. cfifo_pop():读出连续字节流;数据不够返回 FIFO_ERR
  3. cfifo_peek():不移动读指针,返回两段指针(buf1/len1 + buf2/len2),用于 wrap-around 场景减少一次 memcpy。

它内部用 write/read 单调递增做索引(用 % size 映射到环上),并使用内存屏障保证读写顺序。

跨核使用约束(必须遵守):

  1. 推荐单生产者/单消费者模型(SPSC):一核只写、一核只读,避免双写/双读引入锁。
  2. 共享 buffer 必须位于两核都可见的 RAM 映射区。
  3. 若启用 DCache:共享区应尽量放到 non-cacheable 区域;否则需要在"写完通知前 clean cache / 读前 invalidate cache",否则可能读到旧数据。

B. @services/mcpp:跨核 RPC + 共享缓冲区(大块参数/PCM)

@services/mcpp 的典型形态是"RPC 命令"走跨核消息通道,但大块内存通过共享缓冲区承载。

示例:框架提供 MCPP_FRAMEWORK_ALLOC_MEM_CTL 控制命令,让服务端分配一块共享 buffer,并把地址通过 ctl 参数返回给客户端使用。

要点:

  1. 这个"指针返回"只在两核共享同一地址空间/同一物理 RAM 映射时成立。
  2. 若两核 cache 属性不同(例如一侧 cacheable、一侧 non-cacheable),必须定义清晰的 cache 维护策略。
  3. 指针只是一种"共享内存句柄"概念,如果后续要支持不共享地址空间的平台,应抽象成 offset/handle 机制。

C. @services/gaf_cc_stream:音频流处理与算法服务的跨核组合

@services/gaf_cc_stream 更偏"流式数据路径":

  1. 控制类配置:用 IPC 下发(例如算法开关、参数更新)。
  2. 数据类 payload:尽量通过共享内存 FIFO/块缓冲承载,IPC 仅做 doorbell。
  3. 当开启算法处理(例如基于 @services/mcpp 的算法服务)时,GAF CC stream 侧会组织好帧长/采样率/通道映射等配置,再把数据送到算法侧。

补充:SUBSYS_RMT_SYSFREQ_REQ 与 M55 频率请求链路

当定义 SUBSYS_RMT_SYSFREQ_REQ 时,hal_sysfreq 会暴露注册接口 hal_sysfreq_subsys_set_freq_register(cb)

典型用法是让"系统主控侧"(通常是 M33)注册回调:

  1. 子系统(例如 M55 / BTH / SensorHub)触发频率变化需求。
  2. hal_sysfreq 不直接在子系统里落地 CMU 配置,而是通过回调把目标频率交给主控侧处理。
  3. 主控侧统一调用 hal_cmu_axi_freq_req() 等接口完成实际主频调整。

这样做的核心目标:避免多个子系统各自改 CMU 导致冲突,也符合"主控统一电源/时钟域管理"的系统设计。


补充:DSP_M55 驱动层(@platform/drivers/dsp_m55)与 Core-Bridge 的对接

本节从驱动视角解释 dsp_m55_open() / dsp_m55_send() / IRQ 回调如何支撑上层 Core-Bridge。

1) 驱动 API 概览

c 复制代码
// platform/drivers/dsp_m55/dsp_m55.h
typedef unsigned int (*DSP_M55_RX_IRQ_HANDLER)(const void *data, unsigned int len);
typedef void (*DSP_M55_TX_IRQ_HANDLER)(const void *data, unsigned int len);

int dsp_m55_open(DSP_M55_RX_IRQ_HANDLER rx_hdlr, DSP_M55_TX_IRQ_HANDLER tx_hdlr);
int dsp_m55_close(void);
int dsp_m55_send(const void *data, unsigned int len);
int dsp_m55_send_seq(const void *data, unsigned int len, unsigned int *seq);
int dsp_m55_tx_active(unsigned int seq);

对应关系:

  1. dsp_m55_send() 是 Core-Bridge 发送的最底层出口(例如 apps/dsp_m55/app_dsp_m55.cpp 里的 core_bridge_send() 最终调用它)。
  2. dsp_m55_open(rx, tx) 把上层的"收包回调/发送完成回调"注册到底层 IPC(hal_rmt_ipc_open)。

2) dsp_m55_open() 做了什么

platform/drivers/dsp_m55/dsp_m55.c::dsp_m55_open() 大体分三步:

  1. 电源/时钟/复位域准备hal_psc_sys_m55_enable()hal_cmu_m55_clock_enable()hal_cmu_m55_reset_clear()
  2. 加载 M55 镜像并设置入口 :通过 subsys_loader_load_image()/subsys_check_boot_struct() 得到 code_start,设置向量表(SP/ENTRY),最后 hal_cmu_m55_start_cpu() 拉起 M55。
  3. 建立 IPC 通道并启动接收
    • hal_rmt_ipc_open(HAL_RMT_IPC_CORE_SYS_M55C0, HAL_RMT_IPC_CHAN_0, rx_hdlr, tx_hdlr, ..., &rmt_ipc_ep)
    • hal_rmt_ipc_start_recv(rmt_ipc_ep)

这里的关键是第 3 步:驱动把 rx_hdlr/tx_hdlr 绑定到 remote IPC 的中断路径,从而让上层在收到对端消息时被"同步回调"。

3) RX/TX 回调如何上接 Core-Bridge

M33 侧通常在 apps/dsp_m55/mcu_dsp_m55_app.cpp 里调用 dsp_m55_open(dsp_m55_rx_handler, dsp_m55_tx_handler),其作用是把驱动回调再分发到 Core-Bridge:

  1. dsp_m55_rx_handler():根据优先级选择 core_rx_handler[prio],默认 low prio 时 core_rx_handler[LOW] = app_dsp_m55_bridge_data_received
  2. dsp_m55_tx_handler():默认 low prio 时 core_tx_handler[LOW] = app_dsp_m55_bridge_data_tx_done,用于释放发送侧等待的 tx_done 信号量。

因此链路是:

复制代码
hal_rmt_ipc IRQ -> dsp_m55_rx_handler() -> app_dsp_m55_bridge_data_received()
hal_rmt_ipc TX done -> dsp_m55_tx_handler() -> app_dsp_m55_bridge_data_tx_done()

4) send/tx_done 的设计点

  1. dsp_m55_send() 本质是 hal_rmt_ipc_send(rmt_ipc_ep, data, len)
  2. 上层 core_bridge_send()dsp_m55_send() 失败时做了重试与延迟退避,避免瞬时 busy 导致直接失败。
  3. 上层在成功调用 dsp_m55_send() 后仍会等待 tx_done 信号量(见 app_dsp_m55_bridge_transmit_data()),更准确地说是等待"对端已 rx_done 应答"(本端收到了 DATA_DONE IRQ 并在 tx IRQ handler 中回调 tx_hdlr),从而避免上层过早复用/释放发送缓冲。

补充:SensorHub 驱动层(@platform/drivers/sensor_hub)对照

SensorHub 驱动与 M55 驱动形态基本一致:

  1. sensor_hub_open(rx, tx) 内部同样使用 hal_rmt_ipc_open(..., rx_hdlr, tx_hdlr, ...) + hal_rmt_ipc_start_recv()
  2. 上层 apps/sensorhub/mcu_sensor_hub_app.cppsensor_hub_rx_handler() 会把数据交给 app_core_bridge_data_received()sensor_hub_tx_handler() 则调用 app_core_bridge_data_tx_done() 释放 tx_done。

补充:驱动层 IPC 的数据通道(@platform/hal/hal_rmt_ipc*)

这一节把"驱动层 IPC"再往下扒开一层,解释 hal_rmt_ipc 的真实工作方式:它并不是传统意义上的"硬件 FIFO 逐字节发送",而是 共享内存里的消息链表 + 两类 mailbox 中断(doorbell)

关键源码:

  • platform/hal/hal_rmt_ipc_common.c
  • platform/hal/hal_rmt_ipc_pri.h
  • platform/hal/best1600/hal_mcu2dsp_best1600.c(以 Best1600 的 MCU↔DSP 线路为例)

1) 术语对齐:Mailbox / 共享通道 / 两类 IRQ

hal_rmt_ipc 抽象了两个核心概念:

  1. 共享通道 (channel)HAL_RMT_IPC_CHAN_0..3 是同一对核之间并行的逻辑通道。每个通道都有独立的"发送记录池(send_record)"和"消息链表指针"。
  2. Mailbox/doorbell 中断 :每个通道至少有两类事件(在 hal_rmt_ipc_pri.h 里定义):
c 复制代码
enum HAL_RMT_IPC_IRQ_TYPE_T {
    HAL_RMT_IPC_IRQ_DATA_IND,   // 对端有新数据可取(doorbell: data indication)
    HAL_RMT_IPC_IRQ_DATA_DONE,  // 对端已处理完成并应答(doorbell: data done / ack)
};

在 Best1600 的实现里,这两类事件对应 CMU 里的不同 IRQ SET 位(见 hal_mcu2dsp_irq_active() 等函数)。

直观理解:

  1. DATA_IND:发送方敲对端门铃,让对端去共享内存里"取信"。
  2. DATA_DONE:接收方处理完后敲回门铃,告诉发送方可以释放发送记录/复用 buffer。

2) "Mailbox"到底装了什么:共享内存里的指针

hal_rmt_ipc 的"消息"本体定义在 hal_rmt_ipc_pri.h

c 复制代码
struct HAL_RMT_IPC_MSG_T {
    struct HAL_RMT_IPC_MSG_T *next;
    unsigned int len;
    const void *data;   // 注意:这里是指针,不是拷贝后的 payload
};

这意味着:

  1. hal_rmt_ipc_send() 不会把 data[len] 拷贝到硬件 FIFO,它只是把 {data 指针, len} 挂到一条"共享链表"上。
  2. 接收方 IRQ 里读取的是这个链表节点,然后把 data/len 原样交给上层 rx_irq_handler(data, len)
  3. 因为 data 是指针,发送缓冲必须对两核可见且在 tx_done 前保持有效 。这就是为什么 Core-Bridge 发送后要等 tx_done 信号量再复用 tx_mailbox_heap

这也解释了工程里常见的两条纪律:

  • IPC 负责敲门(doorbell),数据尽量放共享 RAM。
  • 发送侧 buffer 生命周期至少覆盖到"对端 rx_done"。

3) open 发生了什么:交换"配置块"地址

hal_rmt_ipc_open() 会把当前 core 的 HAL_RMT_IPC_CFG_T 地址写到一个双方约定的固定地址槽 里(不同链路对应不同 *_CFG_PTR_LOC)。

关键代码在 platform/hal/hal_rmt_ipc_common.c::hal_rmt_ipc_open()

  1. 初始化并清零 cfg->chan_cfg[chan]
  2. 给该通道分配发送记录池:
c 复制代码
cfg->chan_cfg[chan].send_record = cfg->send_record + chan * cfg->rec_num_per_chan;
cfg->chan_cfg[chan].send_rec_num = cfg->rec_num_per_chan;
  1. 把本端 cfg 地址发布出去,让对端可读:
c 复制代码
*cfg->local_cfg_pp = ADDR_CPU_TO_DEV(cfg);
__DSB();

对端在第一次收到 RX IRQ 时,会懒加载 recv_msg_list_pp

c 复制代码
all_chan_cfg[chan].recv_msg_list_pp =
    (const struct HAL_RMT_IPC_MSG_T **)&(*cfg->peer_cfg_pp)->chan_cfg[chan].send_msg_list_p;

这一步非常关键:它把"从哪里读对端的发送链表头指针"建立起来。

4) 发送路径:send_seq / seq 的真实语义

发送 API:

c 复制代码
int hal_rmt_ipc_send_seq(HAL_RMT_IPC_EP_T ep, const void *data, unsigned int len, unsigned int *seq);

它的实现(hal_rmt_ipc_common.c::hal_rmt_ipc_send_seq())核心逻辑是:

  1. send_record[] 里找一个 in_use == false 的槽位。
  2. {len, data 指针} 写进该槽位的 record->msg
  3. 如果当前 send_msg_list_p == NULL(通道空闲),直接把该 msg 作为"当前可见的发送链表头",然后触发对端 DATA_IND
c 复制代码
chan_cfg->send_msg_list_p = ADDR_CPU_TO_DEV(&record->msg);
__DSB();
cfg->peer_tx_irq_set(chan); // 对端 DATA_IND doorbell
  1. 如果通道当前已有正在发送/等待应答的链表,则把新 msg 挂到 send_pending_list_p(等待下一轮)。

因此:

  1. seq 就是 send_record 的索引*seq = i),用于后续查询该条发送是否已被对端应答。
  2. send_seq() 能否成功,取决于该通道的 send_record 池是否还有空位;池满会返回失败(上层通常会重试)。

5) RX 流控:manual_rx_done / 回调返回值的语义

接收侧 IRQ 入口是 hal_rmt_ipc_rx_irq_handler()platform/hal/hal_rmt_ipc_common.c),它支持两种"收包完成"模式:

  1. 自动 rx_done(默认)hal_rmt_ipc_open(..., manual_rx_done=false, ...)
  2. 手动 rx_donehal_rmt_ipc_open(..., manual_rx_done=true, ...),需要上层在合适时机调用 hal_rmt_ipc_rx_done(ep) 才会向对端发 DATA_DONE

对应到代码里的关键字段(hal_rmt_ipc_pri.h):chan_manual_rx_done / chan_rx_busy / recv_pending_head

更重要的是:rx 回调的返回值可以实现"分段消费"

  1. processed = rx_irq_handler(data, len)
  2. processed < len,驱动会把"剩余未处理的数据"挂到 recv_pending_head,并暂停继续推进消息链表,等后续被 rx_irq_resume() 重新触发。
  3. 当未完成处理时,如果是自动 rx_done 模式,驱动不会立刻 rx_done();如果是手动模式,则完全由上层调用 hal_rmt_ipc_rx_done() 来决定何时应答。

这套机制用于两类场景:

  1. IRQ handler 需要把数据搬到更安全的 buffer,但一次搬不完/资源不够(用 processed 做 backpressure)。
  2. 上层希望把"应答时机"延后到真正消费完成之后(manual rx_done)。

6) tx_active 的真实语义:是否已收到对端应答

驱动层 API:

c 复制代码
int hal_rmt_ipc_tx_active(HAL_RMT_IPC_EP_T ep, unsigned int seq);

其实现非常直接(hal_rmt_ipc_common.c::hal_rmt_ipc_tx_active()):

  1. seq < send_rec_num:返回 send_record[seq].in_use
  2. seq == HAL_RMT_IPC_ALL_SEND_SEQ:遍历所有 record,只要有任何 in_use 就认为 TX 仍 active。

所以 tx_active() 并不表示"硬件仍在移位发送",而是表示:

  1. 这条发送记录还没被回收。
  2. 换句话说:对端还没对这条消息完成 rx_done 应答,本端还不能复用它所引用的 buffer

7) tx_done 从哪里来:对端 rx_done → 本端 TX IRQ

hal_rmt_ipc_tx_irq_handler()hal_rmt_ipc_common.c)在收到 DATA_DONE IRQ 后会:

  1. 清除本端 TX IRQ 源(cfg->local_tx_irq_clear(chan))。
  2. 遍历 send_msg_list_p 链表,逐条调用上层注册的 tx_irq_handler(data, len),并把对应 send_record.in_use = false(回收槽位)。
  3. 如果存在 send_pending_list_p,则把 pending 提升为新的 send_msg_list_p,并再次触发对端 DATA_IND

因此从系统视角看,一条消息的"完成"是这样的握手:

复制代码
Sender:  写共享链表头 + doorbell(DATA_IND)  ───────────────►  Receiver
Receiver: 读取链表 -> 上抛 rx_handler
Receiver: 处理完 -> rx_done() -> doorbell(DATA_DONE)  ◄────── Sender
Sender:  TX IRQ 回收 record -> 回调 tx_handler -> tx_active 变 false

这也解释了为什么 Core-Bridge 里 tx_done 信号量能作为"发送完成"的判据:它等的是"对端已确认接收并处理完成"的语义。

8) 通道选择的工程习惯(CHAN_0/1/...)

工程里一般按"用途隔离"分配 channel:

  1. CHAN_0:主消息通道(例如 dsp_m55_open(..., HAL_RMT_IPC_CHAN_0, ...))。
  2. CHAN_1:trace / log 等旁路通道(例如 platform/drivers/dsp_m55/rx_dsp_m55_trc.cRMT_TRC_CHAN_ID)。

隔离的好处是:trace 的突发不会挤占业务消息的 send_record 池,也不会让业务通道的 doorbell 更频繁。

相关推荐
思为无线NiceRF2 小时前
高空线路安装智能安全帽全双工组网对讲系统(含优先级管控)应用方案
嵌入式硬件·物联网
独小乐4 小时前
012.整体框架适配SDRAM|千篇笔记实现嵌入式全栈/裸机篇
c语言·汇编·笔记·单片机·嵌入式硬件·arm·gnu
LCMICRO-133108477465 小时前
长芯微LPC556D1完全P2P替代DAC8830,是引脚兼容的16位数模转换器,该系列产品为单通道、低功耗、缓冲电压输出型DAC
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·电压输出型dac
forAllforMe5 小时前
如何用定时器PWM产生SPWM?--电机驱动控制
嵌入式硬件
charlie1145141915 小时前
嵌入式C++教程实战之Linux下的单片机编程(9):HAL时钟使能 —— 不开时钟,外设就是一坨睡死的硅
linux·开发语言·c++·单片机·嵌入式硬件·c
钿驰科技5 小时前
水泵无刷电机驱动板如何实现恒压控制?
单片机·嵌入式硬件
Vis-Lin5 小时前
BLE 协议栈:L2CAP 信道详解
网络·物联网·网络协议·蓝牙·iot·ble
xingzhemengyou15 小时前
STM32 DMA
stm32·单片机·嵌入式硬件
森利威尔电子-6 小时前
森利威尔 SL3160A 降压型 DC - DC 转换器:10V - 150V 宽输入,稳出 5V/2.5A
单片机·嵌入式硬件·集成电路·芯片·电源芯片