开发板示例工程
本页介绍 AC79 系列 AIoT SDK 中位于 apps/demo/ 的开发板示例工程(demo projects)以及 apps/common/example/ 下的外设例程库,说明每个工程的定位、通用工程结构、启动流程、配置方式与构建方法。
Purpose and Scope
本页面向需要在真实开发板上快速上手或作为二次开发起点的开发者,覆盖以下内容:
apps/demo/下 11 个示例工程(demo_hello、demo_DevKitBoard、demo_audio、demo_ble、demo_edr、demo_wifi、demo_wifi_ext、demo_video、demo_uvc、demo_ui、demo_matter)的功能定位与源码组织;- 每个工程共有的结构:
app_main.c入口、board/<板型>/板级目录、include/app_config.h配置头文件; - SDK 的启动机制:
irq_info_table中断注册表与task_info_table任务注册表如何被内核消费,app_main()如何被调度; apps/common/example/readme.md例程总览所罗列的外设例程(GPIO、TIMER、RTC、ADC、PWM、SPI、IIC、UART 等)。
不纳入本页范围、由兄弟页面承载的内容:各外设例程的逐项 API 细节(见各例程子目录 readme,如 uart/readme.md)、ASR/音频算法等公共组件内部实现(属于 apps/common 组件文档)、SDK 烧录与量产工具链说明。
Overview
AC79 SDK 采用「示例工程 + 公共组件 + 外设例程」三层的组织方式:
- 示例工程层(apps/demo):每个目录是一个完整可编译、可烧录到开发板的独立应用,覆盖音频、蓝牙、WiFi、视频、UI、Matter 等不同产品形态,是二次开发最直接的起点。
- 外设例程层(apps/common/example):以
readme.md为总览索引,按外设/功能模块组织大量可复制的最小例程(如 GPIO 输入输出与中断、定时器捕获、RTC 闹钟、ADC 采压、PWM 呼吸灯、SPI 外挂 Flash、IIC 外接 EEPROM、UART 打印/通信等),并给出「常见问题」条目,帮助开发者绕开硬件陷阱。 - 公共组件层(apps/common):ASR(aisp/jlkws/roobo/wanson)、音频处理、网络协议栈等公共能力,供示例工程调用。
示例工程的启动不是传统的裸机 main 顺序执行,而是由内核依据两张静态表驱动:irq_info_table 决定中断注册到哪个 CPU 核与优先级,task_info_table 决定创建哪些任务、各自优先级与栈大小。开发者修改这两张表即可裁剪/增加系统能力,这是理解整个 SDK 工程的关键入口。
Architecture
flowchart TD
subgraph sg_demo["apps/demo 示例工程层"]
DemoHello["demo_hello<br/>最小系统"]
DemoDevKit["demo_DevKitBoard<br/>开发板综合工程"]
DemoAudio["demo_audio<br/>音频/智能音箱"]
DemoBle["demo_ble<br/>BLE 蓝牙"]
DemoEdr["demo_edr<br/>经典蓝牙 EDR"]
DemoWifi["demo_wifi<br/>WiFi 联网"]
DemoWifiExt["demo_wifi_ext<br/>WiFi 扩展"]
DemoVideo["demo_video<br/>视频采集"]
DemoUvc["demo_uvc<br/>UVC 摄像头"]
DemoUi["demo_ui<br/>LCD UI 显示"]
DemoMatter["demo_matter<br/>Matter 协议"]
end
subgraph sg_example["apps/common/example 例程库"]
ExCpu["CPU 外设例程<br/>GPIO/TIMER/RTC/ADC/PWM/SPI/IIC/UART"]
ExOther["音频/视频/网络等例程"]
end
subgraph sg_common["apps/common 公共组件"]
CommonLib["公共组件<br/>ASR/音频/网络/存储/蓝牙"]
Kernel["系统内核<br/>irq_info_table/task_info_table"]
end
DemoHello --> CommonLib
DemoDevKit --> CommonLib
DemoAudio --> CommonLib
DemoBle --> CommonLib
DemoEdr --> CommonLib
DemoWifi --> CommonLib
DemoWifiExt --> CommonLib
DemoVideo --> CommonLib
DemoUvc --> CommonLib
DemoUi --> CommonLib
DemoMatter --> CommonLib
ExCpu --> CommonLib
ExOther --> CommonLib
DemoHello --> Kernel
DemoDevKit --> Kernel
上图展示三层结构:上层每个示例工程都通过 app_main.c 接入系统内核并复用 apps/common 公共组件;apps/common/example 例程库则为外设操作提供可直接摘抄的参考实现。示例工程之间通过 app_config.h 中的编译宏(如 CONFIG_BT_ENABLE、CONFIG_WIFI_ENABLE、CONFIG_UI_ENABLE)来开启或裁剪对应组件,因此一个工程目录往往同时包含多套能力的代码,最终产物由宏开关决定。
示例工程清单
apps/demo/ 下共 11 个工程,每个工程都以 app_main.c 作为应用入口。下表依据仓库实际目录整理:
| 工程目录 | 功能定位 | 关键源码文件 |
|---|---|---|
| demo_hello | 最小系统示例,外设裁剪最少,适合验证工具链与烧录流程 | app_main.c |
| demo_DevKitBoard | 开发板综合工程,集成音频、WiFi、蓝牙、视频、UI 等全能力 | app_main.c |
| demo_audio | 音频应用工程:AI 音箱、本地音乐、网络音乐、录音,带模式切换 | app_main.c、demo/ai_speaker.c、demo/local_music.c、demo/net_music.c、demo/recorder.c、demo/mode.c、wifi_app_task.c |
| demo_ble | BLE 低功耗蓝牙工程 | app_main.c、bt_ble/ble.c |
| demo_edr | 经典蓝牙(EDR)工程 | app_main.c |
| demo_wifi | WiFi 联网基础工程 | app_main.c |
| demo_wifi_ext | WiFi 扩展应用工程 | app_main.c |
| demo_video | 视频采集/处理工程 | app_main.c |
| demo_uvc | UVC 摄像头(USB Video Class)工程 | app_main.c |
| demo_ui | LCD/UI 显示工程 | app_main.c |
| demo_matter | Matter 智能家居互联协议工程 | app_main.c |
各工程均包含 board/<板型>/ 板级目录(例如 demo_audio 与 demo_ble 都带有 board/wl82/,内含 board.c、Makefile 以及 CodeBlocks 工程文件 AC791N_DEMO_DEMO_AUDIO.cbp / AC791N_DEMO_DEMO_BLE.cbp)和 include/app_config.h 功能配置头文件。
工程通用结构
一个典型示例工程的目录布局如下(以 demo_audio 为例):
app_main.c:应用主入口,包含irq_info_table、task_info_table与应用逻辑;board/wl82/:板级适配,board.c做引脚/外设初始化,Makefile定义编译目标,.cbp为 CodeBlocks IDE 工程;demo/:业务模块源码(如 ai_speaker、local_music、net_music、recorder、mode 模式切换);include/app_config.h:宏开关集中地,控制外设与协议栈的裁剪;- 顶层
wifi_app_task.c等:工程特有任务(如 WiFi 应用任务)。
这种「入口表驱动 + 板级目录 + 宏配置」的结构设计意图是:换板不换工程、换功能不改入口。适配新开发板只需替换 board/ 目录,裁剪功能只需改 app_config.h 与两张注册表,业务代码保持稳定。
启动流程
sequenceDiagram
participant ROM as Boot ROM
participant OS as 系统内核
participant Irq as 中断控制器
participant Init as init 任务
participant App as app_core 任务
ROM->>OS: 上电/复位,加载固件
OS->>OS: 读取 irq_info_table 注册中断到 CPU0/1
OS->>Irq: 设置 IRQ 优先级(0-7)与归属核
OS->>OS: 读取 task_info_table 创建系统任务
OS->>Init: 调度 init 任务(优先级30,栈512)
Init->>Init: 板级初始化(board.c)
Init->>App: 创建并切换 app_core 任务
App->>App: 执行 app_main() 应用主流程
App->>App: 事件循环/模式分发
启动链路可以概括为四步:
- 中断注册:内核按
irq_info_table中每一项的「中断号、优先级(0-7)、注册 CPU 核」配置中断控制器,支持把特定中断强制绑定到 CPU0 或 CPU1(例如IRQ_SOFT5_IDX绑 CPU0、IRQ_SOFT4_IDX绑 CPU1 用于不可屏蔽中断场景)。 - 任务创建:内核按
task_info_table创建任务,每个条目给出任务名、优先级、栈大小与配额(quota)。任务名前缀#C0表示强制调度到 CPU0(如双核下的#C0usb_msd0、#C0btctrler)。 - 板级初始化:最高优先级的
init任务完成时钟、引脚、存储等板级外设初始化。 - 业务启动:
app_core任务进入app_main(),示例工程在此注册业务回调、启动模式分发与事件循环。
核心代码走读:以 demo_DevKitBoard 为例
demo_DevKitBoard 是开发板综合工程,其 app_main.c 完整展示了「中断表 + 任务表 + 应用入口」的 SDK 标准写法。
中断注册表 irq_info_table
/*中断列表 */
const struct irq_info irq_info_table[] = {
//中断号 //优先级0-7 //注册的cpu(0或1)
#ifdef CONFIG_IPMASK_ENABLE
//不可屏蔽中断方法:支持写flash,但中断函数和调用函数和const要全部放在内部ram
{ IRQ_SOFT5_IDX, 6, 0 }, //此中断强制注册到cpu0
{ IRQ_SOFT4_IDX, 6, 1 }, //此中断强制注册到cpu1
#if 0 //如下,SPI1使用不可屏蔽中断设置,优先级固定7
{ IRQ_SPI1_IDX, 7, 1 },//中断强制注册到cpu0/1
#endif
#endif
#if CPU_CORE_NUM == 1
{ IRQ_SOFT5_IDX, 7, 0 }, //此中断强制注册到cpu0
{ IRQ_SOFT4_IDX, 7, 1 }, //此中断强制注册到cpu1
{ -2, \t\t\t-2, -2 },//如果加入了该行, 那么只有该行之前的中断注册到对应核, 其他所有中断强制注册到CPU0
#endif
{ -1, -1, -1 },
};
Source: app_main.c
设计要点:
- 表以
{ -1, -1, -1 }结尾作为终止标记; - 单核(
CPU_CORE_NUM == 1)下可通过插入{ -2, -2, -2 }行,把该行之后所有中断强制注册到 CPU0,用于满足某些外设驱动对中断亲和性的要求; CONFIG_IPMASK_ENABLE开启不可屏蔽中断模式,此时中断服务函数及其调用的函数、常量必须全部放在内部 RAM,才能保证写 Flash 等临界操作不被中断打断。
任务注册表 task_info_table
/*任务列表 */
const struct task_info task_info_table[] = {
{"init", 30, 512, 256 },
{"app_core", 15, 1024, 256 },
{"sys_event", 29, 512, 0 },
{"systimer", 14, 256, 0 },
{"sys_timer", 9, 512, 64 },
{"mp3_encoder", 13, 768, 0 },
{"jla_encoder", 13, 768, 0 },
{"audio_server", 16, 512, 64 },
{"audio_mix", 28, 512, 0 },
{"audio_decoder", 30, 1024, 64 },
{"audio_encoder", 12, 384, 64 },
{"speex_encoder", 10, 1024, 0 },
{"opus_encoder", 10, 1536, 0 },
{"ogg_encoder", 10, 1536, 0 },
{"vir_dev_task", 13, 256, 0 },
{"wechat_task", 18, 2048, 64 },
...
{"usb_server", 20, 1024, 64 },
#if CPU_CORE_NUM > 1
{"#C0usb_msd0", 1, 512, 128 },
#else
{"usb_msd0", 1, 512, 128 },
#endif
...
#ifdef CONFIG_BT_ENABLE
#if CPU_CORE_NUM > 1
{"#C0btencry", 16, 512, 128 },
{"#C0btctrler", 19, 512, 384 },
{"#C0btstack", 18, 1024, 384 },
#else
{"btencry", 14, 512, 128 },
{"btctrler", 19, 512, 384 },
{"btstack", 18, 768, 384 },
#endif
#endif
{"tcpip_thread", 16, 800, 0},
#ifdef CONFIG_WIFI_ENABLE
{"tasklet", 10, 1400, 0},//通过调节任务优先级平衡WIFI收发占据总CPU的比重
{"RtmpMlmeTask", 17, 700, 0},
{"RtmpCmdQTask", 17, 300, 0},
{"wl_rx_irq_thread", 5, 256, 0},
#endif
#ifdef CONFIG_UI_ENABLE
{"ui", 21, 768, 256 },
{"lcd_task_0", 9, 1024, 32 },
{"lcd_task_1", 9, 1024, 32 },
{"te_task", 10, 1024, 32 },
#endif
...
{"ai_server", 15, 1024, 64 },
{"asr_server", 15, 1024, 64 },
{"wake-on-voice", 7, 1024, 0 },
{"resample_task", 8, 1024, 0 },
{"video_server", 26, 800, 1024 },
...
};
Source: app_main.c
每个任务条目依次为 {名称, 优先级, 栈大小, 配额}。任务名中的 #C0 前缀表示强制绑定 CPU0(双核场景下用于把蓝牙控制器、USB 等实时性敏感的任务与主核隔离)。SDK 通过 CONFIG_BT_ENABLE、CONFIG_WIFI_ENABLE、CONFIG_UI_ENABLE、CPU_CORE_NUM 等宏对任务表做条件编译,实现「一份工程、按宏裁剪能力」。
外设例程库 apps/common/example
apps/common/example/readme.md 是全 SDK 外设例程的总索引(EXAMPLE 例程总览),按模块列出例程主题与常见问题。已确认覆盖的 CPU 外设部分包括:
| 模块 | 例程覆盖内容 |
|---|---|
| GPIO | 输入/上下拉配置、输出/强驱配置、中断模式、USB 等特殊引脚复用、双重绑定 IO、无互斥高速 GPIO 操作方法 |
| TIMER | 可用定时器选择、中断/轮询、开始/暂停/继续/停止、定时器捕获 |
| RTC | 软硬件配置、走时/设置/读取时间、闹钟、系统运行时间读取 |
| ADC | 引脚配置、测量电压范围与精度、读取方法、VBAT 电源电压测量 |
| PWM | 引脚配置、频率/占空比范围、普通与正反转 PWM、动态配置 |
| SPI | 引脚配置、时钟范围、软/硬件 SPI、SPI1 外挂 FLASH、硬件从机 |
| IIC | 引脚配置、软/硬件 IIC、外接 EEPROM |
| UART | 打印口与通信串口的引脚/波特率配置、读写说明(详见 uart/readme.md) |
| INPUT_CHANNEL / OUTPUT_CHANNEL | 硬件模块占用通道数量与系统总量注意事项 |
| PWM_LED | 呼吸灯引脚与模式配置 |
| PAP / EMI 推屏接口 | 引脚配置、时序控制、读写与中断使用方法 |
Source: readme.md
总览文件还包含音频、视频、网络等更多例程条目(文件全长数百行),每个条目的典型格式为「功能点列表 + 常见问题」,开发者可直接按模块名跳到对应例程目录复用代码。这种「总览索引 + 子目录 readme + 可复制例程」的组织方式,把外设驱动从业务工程中解耦出来,保证任何示例工程都能独立引用同一套经过验证的底层用法。
配置选项
示例工程的配置主要分布在三个位置:include/app_config.h(功能宏)、board/<板型>/board.c(引脚/外设)、board/<板型>/Makefile 与 .cbp(构建目标)。下表汇总了在 app_main.c 中实际出现、可直接观察到的宏开关及其作用:
| 宏/符号 | 类型 | 默认行为 | 作用 |
|---|---|---|---|
CPU_CORE_NUM | 宏 | 1 或 2(按芯片) | 控制单/双核编译路径,影响任务表 #C0 前缀与中断注册方式 |
CONFIG_IPMASK_ENABLE | 宏 | 未定义 | 开启不可屏蔽中断方法,支持中断内写 Flash,要求相关代码与常量放内部 RAM |
CONFIG_BT_ENABLE | 宏 | 按工程 | 是否编译蓝牙协议栈任务(btencry/btctrler/btstack) |
CONFIG_WIFI_ENABLE | 宏 | 按工程 | 是否编译 WiFi 协议栈任务(tasklet/RtmpMlmeTask/RtmpCmdQTask/wl_rx_irq_thread) |
CONFIG_UI_ENABLE | 宏 | 按工程 | 是否编译 UI/LCD 任务(ui/lcd_task_0/lcd_task_1/te_task) |
CONFIG_IPMASK_ENABLE | 宏 | 未定义 | 参见上表(不可屏蔽中断) |
任务表条目参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| 任务名 | string | 名称;#C0 前缀表示强制调度到 CPU0 |
| 优先级 | int | 数值越小优先级越高(如 init 为 30 最高,usb_msd0 为 1 较低) |
| 栈大小 | int | 任务栈字节数,音频编解码任务通常较大(1536 及以上) |
| 配额 | int | 时间片/配额参数,0 表示不参与轮转或按系统默认 |
构建与移植
- 构建方式:每个
board/<板型>/下提供Makefile与 CodeBlocks 工程文件(如AC791N_DEMO_DEMO_AUDIO.cbp、AC791N_DEMO_DEMO_BLE.cbp),开发者可直接用 CodeBlocks 打开.cbp编译,或按 Makefile 走命令行构建。 - 换板:复制
board/<板型>/目录并修改board.c的引脚/外设配置即可,工程源码无需改动——这是板级目录独立成目录的设计意图。 - 裁剪功能:修改
include/app_config.h的CONFIG_*宏并同步调整task_info_table(删除对应任务条目)与irq_info_table,可显著降低内存占用与启动时间。
失败模式与边界情况
- 任务栈溢出:音频编解码(opus/ogg 1536)、AI 服务(ai_server 1024)、视频服务(video_server 1024 栈 + 1024 配额)等重任务栈较大;自行新增任务时栈大小不足会导致运行期随机崩溃,应参照同类任务取值。
- 中断亲和性错误:双核下外设中断默认归属可能不符合驱动要求,需在
irq_info_table中显式指定 CPU;插入{ -2, -2, -2 }行后其后的中断全部强制到 CPU0,可能引发 CPU0 负载不均。 - 不可屏蔽中断约束:
CONFIG_IPMASK_ENABLE下中断函数、其调用链与常量必须全部位于内部 RAM,否则写 Flash 期间访问外部存储会失败或死锁。 - WiFi 任务优先级调优:
tasklet任务注释明确指出「通过调节任务优先级平衡 WIFI 收发占据总 CPU 的比重」——在强实时音频场景下应适当降低 tasklet 优先级,避免 WiFi 抢占音频解码导致卡顿。 - 单核/双核差异:
CPU_CORE_NUM == 1时蓝牙、USB 任务不带#C0前缀且栈需求更小;从双核工程裁剪到单核芯片时必须同步调整任务表,否则任务无法创建。
扩展点
- 新增示例工程:复制 demo_hello 目录,改写
app_main.c的任务表与app_main(),保持board/、include/结构不变即可。 - 新增板级支持:在
board/下新增板型目录,提供board.c、Makefile、.cbp,即可让同一工程跑在不同硬件上。 - 复用外设例程:从
apps/common/example对应模块摘抄初始化与读写代码到工程业务模块,注意其「常见问题」条目列出的引脚互斥与通道总数限制。
相关链接
- EXAMPLE 例程总览 — 外设例程索引(GPIO/TIMER/RTC/ADC/PWM/SPI/IIC/UART 等)
- UART 例程说明 — 打印口/通信串口配置与读写示例
- demo_DevKitBoard/app_main.c — 开发板综合工程入口(中断表/任务表/应用主流程)
- demo_audio 工程 — 音频/AI 音箱示例,含 ai_speaker、local_music、net_music、recorder 等业务模块
- demo_ble 工程 — BLE 蓝牙示例