杰理AC792N笔记02: 双核调度与通信机制深度解析

一、文章导读

1.1 适用人群

  • 杰理 AC792N/AC695N 系列 SoC 嵌入式开发者
  • 从事 WiFi / 蓝牙音视频、AIoT 终端开发的工程师
  • 需掌握双核 AMP 架构调度与通信的技术人员

1.2 本文解决的问题

  • 清晰理解 AC792N 双核 AMP 架构与硬件基础
  • 掌握双核任务绑定、优先级调度与单核 / 双核切换
  • 精通共享内存、核间中断、消息队列三种通信机制
  • 提供可直接复用的双核音频解码实战工程框架

1.3 前置知识

  • 嵌入式 C 语言开发基础
  • FreeRTOS 实时操作系统原理
  • 杰理 SDK 开发环境搭建

二、AC792N 双核硬件架构与 AMP 模式

2.1 核心硬件规格

AC792N 采用双 DSP 核心(CPU0/CPU1),硬件对等、资源共享,是梧桐三代高集成 WiFi + 蓝牙 + 音视频 SoC 的核心算力单元:

  • 主频:最高 360MHz,支持单精度浮点与 DSP 加速指令(FFT/IFFT/ 矩阵运算)
  • 缓存:独立 I-Cache、D-Cache,提升指令 / 数据访问效率
  • 内存:片上 384KB SRAM,支持外挂 8/16MB DDR1,双核共享物理地址空间
  • 中断:独立中断控制器(INTC),支持中断核间路由与优先级配置
  • 外设:WiFi / 蓝牙、音频、视频、GPIO 等外设双核共享,硬件仲裁访问

2.2 AMP 架构核心设计

AC792N 默认采用 **AMP(非对称多处理)** 架构,区别于 SMP(对称多处理),核心特点:

  • 独立 OS / 固件:CPU0 运行 FreeRTOS 主系统,负责初始化、外设驱动、协议栈与业务调度;CPU1 运行独立裸机 / 轻量固件,专注实时计算(音频解码、视频处理、AI 推理)
  • 无全局调度器:双核各自管理任务,无统一内核调度,通过通信机制协同
  • 资源隔离与共享:代码 / 栈空间独立,共享内存、外设、中断资源,需软件同步

2.3 双核启动流程(关键时序)

AC792N 启动严格遵循 "CPU0 主导,CPU1 受控启动" 逻辑,所有初始化仅在 CPU0 执行:

  1. 复位启动:芯片复位后,仅 CPU0 从 Flash 启动,执行 ROM 代码初始化硬件
  2. 四级初始化 :CPU0 在app_core任务中按early→platform→module→late顺序执行initcall,完成时钟、中断、外设、驱动初始化
  3. CPU1 唤醒 :CPU0 初始化完成后,通过硬件寄存器(如CPU1_BOOT_ADDR)设置 CPU1 启动地址,发送启动信号唤醒 CPU1
  4. CPU1 运行 :CPU1 从指定地址启动,进入cpu1_main入口,执行用户自定义逻辑(无initcall,需手动初始化局部资源)

三、双核调度机制:任务绑定与优先级管理

3.1 任务绑定 API 与配置

SDK 通过task_info_table结构体实现任务静态绑定,指定任务运行核心(CPU0/CPU1),核心字段:

c

运行

复制代码
// 任务信息结构体(SDK核心定义)
struct task_info {
    const char *name;       // 任务名
    u8 priority;            // 优先级(数值越大越高,0~15)
    u8 cpu_id;              // 绑定核心:0=CPU0,1=CPU1,2=自动调度(默认)
    u16 stack_size;         // 栈大小(字节)
    u16 msg_size;           // 消息队列大小(字节)
    void (*entry)(void *arg);// 任务入口函数
};

// 示例:任务绑定配置(app_main.c)
const struct task_info task_info_table[] = {
    // CPU0绑定:系统核心、协议栈、业务任务
    {"app_core",    1, 0, 640, 128, app_core_task},  // 主任务(CPU0独占)
    {"btctrler",    4, 0, 512, 256, bt_controller},  // 蓝牙控制器(CPU0)
    {"wifi_task",   5, 0, 768, 256, wifi_main},      // WiFi任务(CPU0)
    // CPU1绑定:实时计算、音视频处理任务
    {"audio_decode",3, 1, 768, 128, audio_decode},  // 音频解码(CPU1)
    {"video_proc",  6, 1, 1024, 256, video_process}, // 视频处理(CPU1)
    {NULL, 0, 0, 0, 0, NULL} // 结束标记
};
  • 绑定规则cpu_id=0/1强制绑定对应核心;cpu_id=2由系统自动调度(默认,多核负载均衡)
  • 优先级范围 :0(最低)~15(最高),系统任务(如systimer)优先级通常≥7,业务任务≤5

3.2 双核调度策略详解

(1)CPU0 调度:主系统核心

CPU0 运行 FreeRTOS,负责全局调度、外设管理、中断处理、协议栈运行,调度规则:

  • 抢占式调度:高优先级任务就绪时,立即抢占低优先级任务
  • 时间片轮转:同优先级任务按时间片(默认 10ms)轮转执行
  • 核心独占任务app_coresys_event等系统任务强制绑定 CPU0,保障系统稳定
(2)CPU1 调度:实时计算核心

CPU1 无完整 OS,采用 **"裸机轮询 + 中断驱动"** 调度,适合高实时性、高算力场景:

  • 无任务调度器 :用户在cpu1_main中实现主循环,手动管理任务执行
  • 中断优先:绑定到 CPU1 的中断(如音频 DMA、定时器)可抢占主循环,保障实时性
  • 算力独占:CPU1 不执行系统初始化与协议栈,100% 算力用于用户自定义计算(如音频 EQ、视频编码)
(3)单核 / 双核切换配置

SDK 支持灵活切换单核 / 双核模式,适配不同资源需求:

c

运行

复制代码
// 1. 双核模式(默认,CPU_CORE_NUM=2)
#define CPU_CORE_NUM 2 // 使能双核,任务可绑定CPU0/CPU1

// 2. 单核模式(CPU1禁用,仅CPU0运行)
#define CPU_CORE_NUM 1 // 需替换单核专用system.a库,CPU1不启动

// 3. 中断核间路由配置(irq_info_table)
const struct irq_info irq_info_table[] = {
    {TIMER0_IRQn, 5, 0}, // 定时器0中断,优先级5,绑定CPU0
    {AUDIO_DMA_IRQn, 7, 1}, // 音频DMA中断,优先级7,绑定CPU1(实时)
    {NULL, 0, 0} // 结束标记
};
  • 应用场景:双核模式用于音视频、AI 等高性能场景;单核模式用于低功耗、简单控制场景

3.3 调度实战:典型任务分配方案

结合 AC792N 应用场景,推荐以下任务绑定策略:

表格

核心 任务类型 典型任务 优先级 核心价值
CPU0 系统核心 app_core、sys_event、systimer 1~7 系统初始化、全局管理、看门狗
CPU0 协议栈 btctrler、btstack、wifi_task 3~5 蓝牙 / WiFi 协议处理、连接管理
CPU0 业务逻辑 app_main、ui_task、cmd_task 1~3 用户交互、命令处理、业务调度
CPU1 实时计算 audio_decode、video_proc、ai_infer 3~6 音频解码、视频处理、AI 推理
CPU1 硬件控制 motor_ctrl、sensor_read 2~4 外设实时控制、传感器数据采集

四、双核通信机制:共享内存、中断、消息队列

AC792N 双核通信基于 **"共享内存为基础、中断为同步、消息队列为封装"** 的三层架构,兼顾效率与易用性,SDK 提供完整 API 支持。

4.1 共享内存通信(最底层、最高效)

共享内存是双核通信的基础通道,利用双核物理地址空间共享特性,实现零拷贝数据交互,适合大数据量、高实时性场景。

(1)共享内存区域定义

AC792N 片上 SRAM 中划分专用共享内存区(通常 64KB~128KB),通过链接脚本固定地址,双核均可直接访问:

c

运行

复制代码
// 链接脚本(linker.ld)定义共享内存段
SHARE_MEM : ORIGIN = 0x20000000, LENGTH = 0x20000 // 128KB共享内存

// 代码中声明共享内存变量(__attribute__指定段)
#define SHARE_MEM_BASE 0x20000000
typedef struct {
    u32 cmd;                // 命令码(0=空闲,1=开始,2=结束)
    u32 data_len;           // 数据长度
    u8 data[1024*64];       // 数据缓冲区(64KB)
    volatile u32 lock;      // 自旋锁(同步访问)
} share_mem_t;

// 绑定到共享内存段
share_mem_t *share_mem = (share_mem_t *)SHARE_MEM_BASE;
(2)自旋锁同步(避免竞态)

共享内存访问需 ** 自旋锁(Spinlock)** 同步,防止双核同时读写导致数据错乱:

c

运行

复制代码
// 自旋锁操作(原子操作,无OS依赖)
#define SHARE_MEM_LOCK() while(__sync_lock_test_and_set(&share_mem->lock, 1)) {}
#define SHARE_MEM_UNLOCK() __sync_lock_release(&share_mem->lock)

// 示例:CPU0向CPU1发送数据
void cpu0_send_data(u8 *data, u32 len) {
    SHARE_MEM_LOCK(); // 加锁
    share_mem->cmd = 1; // 命令:开始传输
    share_mem->data_len = len;
    memcpy(share_mem->data, data, len); // 拷贝数据
    SHARE_MEM_UNLOCK(); // 解锁
    cpu1_send_irq(IRQ_SHARE_MEM); // 发送中断通知CPU1
}

// 示例:CPU1接收数据
void cpu1_recv_data(void) {
    if (share_mem->cmd == 1) {
        SHARE_MEM_LOCK();
        u32 len = share_mem->data_len;
        u8 *data = share_mem->data;
        // 处理数据(如音频解码)
        share_mem->cmd = 0; // 清空命令
        SHARE_MEM_UNLOCK();
    }
}
  • 优势 :零拷贝、延迟低(<1μs)、带宽高;劣势:需手动同步,易出错

4.2 核间中断(IRQ):同步与通知机制

核间中断是双核事件同步的核心手段,CPU0/CPU1 可通过硬件寄存器向对方发送中断,实现 "事件触发式" 通信,适合小数据量、实时通知场景。

(1)核间中断 API(SDK 封装)

c

运行

复制代码
// 核间中断定义(AC792N硬件支持8个核间中断:IRQ_CPU0_TO_CPU1_0~7)
#define IRQ_CPU1_NOTIFY  IRQ_CPU0_TO_CPU1_0 // CPU0→CPU1中断
#define IRQ_CPU0_ACK     IRQ_CPU1_TO_CPU0_0 // CPU1→CPU0中断

// 1. 注册核间中断回调函数
void cpu1_irq_callback(void) {
    // CPU1中断处理:读取共享内存数据
    cpu1_recv_data();
    // 发送应答中断给CPU0
    cpu0_send_irq(IRQ_CPU0_ACK);
}

// 2. 初始化核间中断(CPU0中执行)
void share_irq_init(void) {
    // 注册CPU1中断回调
    irq_register(IRQ_CPU1_NOTIFY, 5, cpu1_irq_callback);
    irq_enable(IRQ_CPU1_NOTIFY);
    // 注册CPU0应答中断回调
    irq_register(IRQ_CPU0_ACK, 4, cpu0_ack_callback);
    irq_enable(IRQ_CPU0_ACK);
}

// 3. 发送核间中断(通用API)
void cpu1_send_irq(u32 irq_num) {
    // 写硬件寄存器触发CPU1中断
    *(volatile u32 *)0x40000000 = (1 << irq_num);
}
void cpu0_send_irq(u32 irq_num) {
    *(volatile u32 *)0x40000004 = (1 << irq_num);
}
(2)中断通信流程(典型)
  1. CPU0 准备数据→加锁写共享内存→发送IRQ_CPU1_NOTIFY中断
  2. CPU1 响应中断→加锁读共享内存→处理数据→发送IRQ_CPU0_ACK应答
  3. CPU0 响应应答中断→解锁共享内存→完成通信

4.3 消息队列(Message Queue):高层封装,易用高效

SDK 基于共享内存 + 核间中断 封装核间消息队列,提供类似 POSIX 的 API,屏蔽底层同步细节,适合结构化数据、命令交互场景。

(1)核间消息队列 API(SDK 核心)

c

运行

复制代码
// 消息队列结构体(SDK定义)
typedef struct {
    u32 msg_id;         // 消息ID(命令码)
    u32 param1;         // 参数1
    u32 param2;         // 参数2
    u8 data[64];        // 附加数据(最大64字节)
} cpu_msg_t;

// 1. 创建核间消息队列(CPU0初始化)
msg_queue_t cpu0_to_cpu1_queue;
msg_queue_t cpu1_to_cpu0_queue;

void msg_queue_init(void) {
    // 初始化队列(共享内存地址,队列深度)
    msg_queue_create(&cpu0_to_cpu1_queue, SHARE_MEM_BASE + 0x1000, 16);
    msg_queue_create(&cpu1_to_cpu0_queue, SHARE_MEM_BASE + 0x2000, 16);
}

// 2. CPU0发送消息到CPU1
int cpu0_send_msg(u32 msg_id, u32 p1, u32 p2, u8 *data, u32 len) {
    cpu_msg_t msg;
    msg.msg_id = msg_id;
    msg.param1 = p1;
    msg.param2 = p2;
    if (data && len <= 64) memcpy(msg.data, data, len);
    // 发送消息(阻塞/非阻塞可选)
    return msg_queue_send(&cpu0_to_cpu1_queue, &msg, 0); // 0=非阻塞
}

// 3. CPU1接收消息(主循环中轮询/中断触发)
int cpu1_recv_msg(cpu_msg_t *msg) {
    return msg_queue_recv(&cpu0_to_cpu1_queue, msg, 0); // 0=非阻塞
}
(2)消息队列通信优势
  • 易用性:API 简洁,无需手动管理锁与中断
  • 结构化:支持固定格式消息,适合命令 / 参数传递
  • 可靠性:队列缓存消息,避免丢包(深度可配)
  • 适用场景:命令交互、参数配置、状态同步(如 WiFi 连接状态、音频播放控制)

4.4 三种通信方式对比与选型

表格

通信方式 实现原理 数据量 延迟 易用性 适用场景
共享内存 直接访问物理内存 大(KB~MB) 极低(<1μs) 低(需手动同步) 音视频大数据、高速采样
核间中断 硬件中断触发 小(仅通知) 低(<5μs) 中(需注册回调) 事件通知、同步信号
消息队列 共享内存 + 中断封装 中(<64B / 消息) 中(<10μs) 高(API 封装) 命令交互、状态同步

选型建议

  • 大数据量、高实时:优先共享内存 + 核间中断
  • 命令交互、状态同步:优先核间消息队列
  • 简单事件通知:直接使用核间中断

五、实战开发:双核音频解码工程示例

结合 AC792N 典型应用(WiFi 蓝牙音箱),实现CPU0 负责 WiFi / 蓝牙连接 + UI 控制,CPU1 负责音频解码 + 播放的双核协同方案,附完整代码框架。

5.1 工程架构设计

plaintext

复制代码
AC792N_DualCore_Audio/
├── app_main.c        // CPU0主程序:系统初始化、任务创建、WiFi/蓝牙管理
├── cpu1_main.c       // CPU1主程序:音频解码、DAC播放、中断处理
├── share_mem.h/c     // 共享内存与核间中断定义
├── msg_queue.h/c     // 核间消息队列API
├── audio_decode.h/c  // CPU1音频解码库(MP3/AAC)
└── linker.ld         // 链接脚本:定义共享内存区域

5.2 CPU0 代码(主系统)

c

运行

复制代码
// app_main.c(CPU0)
#include "share_mem.h"
#include "msg_queue.h"
#include "wifi_api.h"
#include "bt_api.h"

// 任务入口
void app_core_task(void *arg) {
    // 1. 初始化共享内存与消息队列
    share_mem_init();
    msg_queue_init();
    share_irq_init(); // 核间中断初始化

    // 2. 启动WiFi/蓝牙
    wifi_init();
    bt_init();

    // 3. 主循环:处理连接与发送命令
    while(1) {
        // 处理WiFi/蓝牙事件
        wifi_event_process();
        bt_event_process();

        // 示例:WiFi连接成功后,发送播放命令到CPU1
        if (wifi_is_connected()) {
            cpu0_send_msg(MSG_AUDIO_PLAY, (u32)"test.mp3", 0, NULL, 0);
        }

        // 接收CPU1状态消息
        cpu_msg_t msg;
        if (cpu0_recv_msg(&msg) == 0) {
            if (msg.msg_id == MSG_AUDIO_STATUS) {
                printf("CPU1 Audio Status: %d\n", msg.param1);
            }
        }
        os_task_delay(10); // 10ms调度
    }
}

// 主函数(CPU0唯一入口)
int main(void) {
    system_init(); // 四级初始化(early/platform/module/late)
    task_create(task_info_table); // 创建并绑定任务
    os_start_scheduler(); // 启动FreeRTOS调度
    return 0;
}

5.3 CPU1 代码(音频核心)

c

运行

复制代码
// cpu1_main.c(CPU1,无OS)
#include "share_mem.h"
#include "msg_queue.h"
#include "audio_decode.h"
#include "dac_api.h"

// 音频播放状态
typedef enum {
    AUDIO_IDLE = 0,
    AUDIO_PLAYING,
    AUDIO_PAUSE,
    AUDIO_STOP
} audio_status_t;

audio_status_t g_audio_status = AUDIO_IDLE;

// CPU1主循环
void cpu1_main(void) {
    // 1. 初始化局部资源(无initcall,手动初始化)
    audio_decode_init();
    dac_init(44100, 16, 2); // 44.1kHz,16位,双声道

    // 2. 主循环:处理消息与音频解码
    while(1) {
        cpu_msg_t msg;
        // 接收CPU0命令
        if (cpu1_recv_msg(&msg) == 0) {
            switch(msg.msg_id) {
                case MSG_AUDIO_PLAY:
                    g_audio_status = AUDIO_PLAYING;
                    audio_decode_start((char *)msg.param1); // 解码指定文件
                    break;
                case MSG_AUDIO_PAUSE:
                    g_audio_status = AUDIO_PAUSE;
                    audio_decode_pause();
                    break;
                case MSG_AUDIO_STOP:
                    g_audio_status = AUDIO_STOP;
                    audio_decode_stop();
                    break;
                default:
                    break;
            }
        }

        // 音频解码与播放(实时处理)
        if (g_audio_status == AUDIO_PLAYING) {
            u8 *pcm_data = audio_decode_get_pcm(); // 从共享内存读PCM
            if (pcm_data) {
                dac_play(pcm_data, 1024); // DAC播放
            }
        }

        // 发送状态到CPU0
        cpu1_send_msg(MSG_AUDIO_STATUS, g_audio_status, 0, NULL, 0);
    }
}

// 核间中断回调(音频DMA完成)
void audio_dma_irq(void) {
    // 触发下一轮解码(实时同步)
    audio_decode_trigger();
}

5.4 关键开发注意事项

  1. 共享内存同步:所有共享内存访问必须加自旋锁,避免数据竞态
  2. 中断优先级:CPU1 实时中断(如音频 DMA)优先级需高于 CPU0 任务,保障实时性
  3. 栈空间分配:CPU1 无 OS 栈管理,需手动分配足够栈空间(避免溢出)
  4. Cache 一致性 :双核共享内存需禁用 Cache 或手动刷新(DCache_Invalidate/DCache_Clean),防止数据不一致
  5. 调试手段:使用 UART 打印、J-Link 调试、核间状态消息,定位双核协同问题

六、总结与进阶方向

6.1 核心要点回顾

  1. 架构:AC792N 采用 AMP 双核架构,CPU0 主系统 + CPU1 实时核心,分工明确
  2. 调度:静态任务绑定 + 优先级抢占,支持单核 / 双核灵活切换
  3. 通信:共享内存(高效)、核间中断(同步)、消息队列(易用)三层机制,适配不同场景
  4. 实战:双核协同开发需关注同步、中断、Cache 一致性,保障系统稳定

6.2 进阶开发方向

  1. 低功耗优化:CPU1 空闲时进入休眠,通过核间中断唤醒,降低整体功耗
  2. 负载均衡:动态调整任务绑定,实现双核负载均衡(需扩展调度逻辑)
  3. 安全加固:共享内存加校验和,核间消息加密,提升通信安全性
  4. 复杂应用:双核协同实现视频编码 + AI 推理 + WiFi 传输,打造高端智能终端

七、参考资料

  1. 杰理 AC792N SDK 开发手册(V1.5)
  2. 杰理梧桐三代 SoC 硬件 datasheet
  3. FreeRTOS 官方文档(V10.4.6)
  4. AMP 架构嵌入式开发实战指南
相关推荐
要不枉此行5 小时前
杰理AC792N笔记03: SDK 线程管理深度实战,os_xxx 与 thread_xxx API 用法、区别与场景选型
杰理·ac792n
要不枉此行12 小时前
杰理AC792N笔记05: SDK工程配置详解 | 配置文件+音频链路
杰理·ac792n
要不枉此行1 天前
杰理AC792N笔记01: 四级初始化全流程详解(early/platform/module/late_initcall)
杰理·ac792n
*JOKER1 个月前
混合精度训练AMP&master-sweight&Loss Scaling
人工智能·深度学习·机器学习·混合精度训练·amp
Jia ming3 个月前
HMP vs AMP:异构多处理的两种范式详解
amp·hmp
我是海飞3 个月前
杰理 AC792N 使用 WebSocket 连接百度语音大模型,实现 AI 对话
c语言·单片机·嵌入式·ai对话·杰理·websockey
我是海飞3 个月前
杰理 AC792N WebSocket 客户端例程使用测试教程
c语言·python·单片机·websocket·网络协议·嵌入式·杰理
小溪彼岸4 个月前
初识Amp Code
aigc·amp
Jim天河7 个月前
杰理AC632N---RTC应用问题
杰理·杰理ble·杰理蓝牙