杰理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_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)中断通信流程(典型)
  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 架构嵌入式开发实战指南
相关推荐
我是海飞16 天前
使用杰理 AC7925B 实现 Windows电脑 USB 副屏
windows·单片机·电脑·嵌入式·杰理
我是海飞16 天前
杰理 JL703N SDK 实现 AI 自动化调试:自动进入下载模式、下载、重启与日志抓取
人工智能·单片机·自动化·音视频·杰理
thesky1234562 个月前
27届大模型面试准备(六十二):大模型数值精度与混合精度工程——BF16/FP8、AMP、Loss Scale 与精度对齐
大模型·amp·混合精度·fp8·bf16·数值精度·梯度缩放
我是海飞3 个月前
杰理JL703N SSD1306 OLED(128x64) 点阵屏移植说明
单片机·算法·嵌入式·音频·杰理
一颗小树x3 个月前
《VLA 系列》AMP 代码复现 | 强化训练 | 行走 跌倒 恢复
机器人·强化学习·amp·vla·复现
要不枉此行6 个月前
杰理 AC791N vs AC792N:梧桐二代与三代的全方位对比与选型指南
杰理·ac792n
要不枉此行6 个月前
杰理AC792N笔记03: SDK 线程管理深度实战,os_xxx 与 thread_xxx API 用法、区别与场景选型
杰理·ac792n
要不枉此行6 个月前
杰理AC792N笔记05: SDK工程配置详解 | 配置文件+音频链路
杰理·ac792n
要不枉此行6 个月前
杰理AC792N笔记01: 四级初始化全流程详解(early/platform/module/late_initcall)
杰理·ac792n