一、文章导读
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 执行:
- 复位启动:芯片复位后,仅 CPU0 从 Flash 启动,执行 ROM 代码初始化硬件
- 四级初始化 :CPU0 在
app_core任务中按early→platform→module→late顺序执行initcall,完成时钟、中断、外设、驱动初始化 - CPU1 唤醒 :CPU0 初始化完成后,通过硬件寄存器(如
CPU1_BOOT_ADDR)设置 CPU1 启动地址,发送启动信号唤醒 CPU1 - 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_core、sys_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)中断通信流程(典型)
- CPU0 准备数据→加锁写共享内存→发送
IRQ_CPU1_NOTIFY中断 - CPU1 响应中断→加锁读共享内存→处理数据→发送
IRQ_CPU0_ACK应答 - 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 关键开发注意事项
- 共享内存同步:所有共享内存访问必须加自旋锁,避免数据竞态
- 中断优先级:CPU1 实时中断(如音频 DMA)优先级需高于 CPU0 任务,保障实时性
- 栈空间分配:CPU1 无 OS 栈管理,需手动分配足够栈空间(避免溢出)
- Cache 一致性 :双核共享内存需禁用 Cache 或手动刷新(
DCache_Invalidate/DCache_Clean),防止数据不一致 - 调试手段:使用 UART 打印、J-Link 调试、核间状态消息,定位双核协同问题
六、总结与进阶方向
6.1 核心要点回顾
- 架构:AC792N 采用 AMP 双核架构,CPU0 主系统 + CPU1 实时核心,分工明确
- 调度:静态任务绑定 + 优先级抢占,支持单核 / 双核灵活切换
- 通信:共享内存(高效)、核间中断(同步)、消息队列(易用)三层机制,适配不同场景
- 实战:双核协同开发需关注同步、中断、Cache 一致性,保障系统稳定
6.2 进阶开发方向
- 低功耗优化:CPU1 空闲时进入休眠,通过核间中断唤醒,降低整体功耗
- 负载均衡:动态调整任务绑定,实现双核负载均衡(需扩展调度逻辑)
- 安全加固:共享内存加校验和,核间消息加密,提升通信安全性
- 复杂应用:双核协同实现视频编码 + AI 推理 + WiFi 传输,打造高端智能终端
七、参考资料
- 杰理 AC792N SDK 开发手册(V1.5)
- 杰理梧桐三代 SoC 硬件 datasheet
- FreeRTOS 官方文档(V10.4.6)
- AMP 架构嵌入式开发实战指南