公共示例工程
本文介绍 fw-AC79_AIoT_SDK 中位于 apps/common/example 目录下的公共示例工程集合,涵盖音频、蓝牙、网络协议、开发板测试以及 BLE Mesh 等各类示例,重点说明其组织方式、编译使能方法、事件驱动机制和核心运行流程,帮助开发者快速定位、理解并复用这些示例代码。
Purpose and Scope
本页面向希望在 AC791N 系列 AIoT SoC 上快速开始开发的工程师,系统梳理 SDK 中"开箱即用"的公共示例工程:
- 说明示例工程的目录结构与分类(音频、蓝牙、网络协议、开发板测试、BLE Mesh);
- 讲解示例应用的通用组织模式:
main.c入口、事件处理器注册、intent+start_app应用切换机制; - 给出示例工程的编译使能与配置宏(如
USE_AUDIO_DEMO、CONFIG_LOCAL_MUSIC_MODE_ENABLE等); - 通过真实代码摘录演示如何编写按键事件、设备事件、网络事件处理器以及音频服务器回调。
不包含的内容:本文只覆盖"公共示例"这一主题边界。各示例具体业务(如本地音乐、网络音乐、录音、ASR、CoAP/HTTP/MQTT 协议细节、BLE Mesh 模型实现等)属于各自独立的功能主题,请参阅对应的功能页;SDK 整体环境搭建与编译烧录流程参见《快速开始》等姊妹页面。
Overview
fw-AC79_AIoT_SDK 是杰理科技面向 AC791N 系列(wl82 平台)WiFi 802.11b/g/n + 双模蓝牙(BR/EDR + BLE)音视频多媒体 SoC 的通用固件开发包,芯片内置 320MHz 双核浮点 DSP,集成了音频(ADC/DAC/IIS/PDM)、视频(摄像头 ISC)、显示(RGB 推屏)及丰富外设资源。SDK 的 Release 代码中附带大量可直接编译运行的示例工程,全部集中在 apps/common/example(以及 BLE Mesh 示例所在的 apps/common/ble/mesh/examples),覆盖了 SDK 主要的应用场景:
| 示例类别 | 目录 | 典型示例 | 覆盖能力 |
|---|---|---|---|
| 音频 | apps/common/example/audio/* | audio_demo(本地音乐/网络音乐/录音/AI 音箱四合一)、asr、lc3、pitch_speed、play_audio_buffer、src_test、virtual_dac、virtual_enc、jla | 音频服务器框架、编解码、变速变调、SRC、虚拟 DAC/编码、ASR 语音 |
| 蓝牙经典 | apps/common/example/bluetooth/edr/bt_music | bt_music | 经典蓝牙(BR/EDR)音乐播放 |
| 网络协议 | apps/common/example/network_protocols/* | coap_client/server、http_download、http_post_get_put、http_server、user_http_head/post_flash、user_http_head/post_sd、mqtt | WiFi 联网、CoAP/HTTP/MQTT 协议栈、HTTP 上传下载 |
| 开发板测试 | apps/common/example/Devkitboard_test | main.c + ui_main.c | 开发板硬件外设全功能自检 |
| BLE Mesh | apps/common/ble/mesh/examples/* | generic_onoff_client/server、light_lightness_server、provisioner、onoff_tobe_provision、vendor_client/server、AliGenie_*、TUYA_light | BLE Mesh 配网、Generic OnOff、Lightness、Vendor Model、天猫精灵/涂鸦平台对接 |
这些示例共用一个统一的"应用框架 + 事件驱动"运行模型:每个示例以 main.c 为入口,通过注册按键/设备/网络事件处理器响应外部输入,借助 server 框架(如 audio_server)完成媒体等耗时任务,最终以 intent 结构体 + start_app() 的方式在多个子应用间切换。理解这个模型是读懂任何示例工程的前提。
Architecture
公共示例工程在 SDK 中的位置及其与基础组件的关系如下图所示:
flowchart TD
subgraph sg_Examples["apps/common/example 公共示例工程"]
Audio["音频示例<br/>audio/* (audio_demo 等)"]
BT["经典蓝牙示例<br/>bluetooth/edr/bt_music"]
Net["网络协议示例<br/>network_protocols/*"]
Devkit["开发板测试<br/>Devkitboard_test"]
Mesh["BLE Mesh 示例<br/>ble/mesh/examples/*"]
end
subgraph sg_Framework["SDK 公共框架层"]
AppCore["app_core 应用框架<br/>(intent / start_app)"]
Server["server 服务器框架<br/>audio_server / server_core"]
EventSys["事件系统<br/>key_event / device_event / net_event"]
end
subgraph sg_Config["配置与平台层"]
AppCfg["app_config.h<br/>使能宏定义"]
Platform["wl82 平台库 (*.a)<br/>FreeRTOS / lwIP / mbedTLS"]
end
Audio --> AppCore
BT --> AppCore
Net --> AppCore
Devkit --> AppCore
Mesh --> AppCore
Audio --> Server
Audio --> EventSys
BT --> EventSys
Net --> EventSys
Devkit --> EventSys
Audio --> AppCfg
Net --> AppCfg
AppCfg --> Platform
架构解读:
- 示例层(上层):各类示例工程位于
apps/common/example下,按功能划分子目录。它们不直接操作寄存器,而是调用公共框架层的 API 完成业务。 - 框架层(中层):
app_core提供应用生命周期管理(start_app/intent),server框架(server/audio_server.h、server/server_core.h)以"请求-应答"模式封装音频解码等异步任务,event系统(event/key_event.h、event/device_event.h、event/net_event.h)负责将按键、设备插拔、网络状态等外部变化派发给示例注册的处理器。 - 配置与平台层(底层):每个示例通过
app_config.h中的使能宏(如USE_AUDIO_DEMO)决定是否参与编译;最终链接平台库(*.a)以及 FreeRTOS、lwIP、mbedTLS 等开源组件(见 README.md)。
这种"示例 → 框架 → 配置/平台"的三层结构使示例代码保持简洁:示例只关心业务逻辑,框架负责资源管理与事件分发,平台层屏蔽芯片差异,从而让同一套示例可以复用于不同方案(wifi_camera、wifi_story_machine 等,见 README.md)。
示例工程目录结构
公共示例按功能域组织为 apps/common/example/<类别>/<子功能>/main.c 的形式,每个可独立编译的子应用都以 main.c 作为程序入口。仓库中实际存在的示例清单如下:
| 类别目录 | 子示例 | 说明 |
|---|---|---|
audio/asr | main.c | 语音打断唤醒(ASR)示例 |
audio/audio_demo | main.c + local_music.c / net_music.c / recorder.c / ai_speaker.c | 四合一音频综合示例:本地音乐、网络音乐、录音、AI 音箱,通过按键循环切换模式,是理解示例工程组织方式的最佳范本 |
audio/jla、audio/lc3 | main.c | JLA / LC3 编解码应用示例 |
audio/pitch_speed | main.c | 变声变速(pitch/speed)效果示例 |
audio/play_audio_buffer | main.c | 内存音频缓冲播放示例 |
audio/src_test | main.c | 采样率转换(SRC)测试示例 |
audio/virtual_dac、audio/virtual_enc | main.c | 虚拟 DAC、虚拟编码器示例 |
bluetooth/edr/bt_music | main.c | 经典蓝牙音乐示例 |
Devkitboard_test | main.c + ui_main.c | 开发板外设自检示例(带 UI) |
network_protocols/coap | coap_client/main.c、coap_server/main.c | CoAP 客户端/服务端示例 |
network_protocols/http | http_download、http_post_get_put、http_server、user_http_head/post_flash、user_http_head/post_sd | HTTP 下载、POST/GET/PUT、HTTP 服务器、自定义 HTTP 头上传 Flash/SD 示例 |
network_protocols/mqtt | main.c | MQTT 客户端示例 |
apps/common/ble/mesh/examples | generic_onoff_client、generic_onoff_server、light_lightness_server、provisioner、onoff_tobe_provision、vendor_client、vendor_server、AliGenie_fan/light/socket、TUYA_light | BLE Mesh 模型与平台对接示例(含 provisioner_config.h) |
示例的编译与使能
示例并非全部默认编译,而是通过 app_config.h 中的使能宏"按需参与构建"。以 audio_demo 为例,整个文件被 USE_AUDIO_DEMO 宏包裹,只有定义该宏时示例代码才进入编译单元:
Source: main.c
#ifdef USE_AUDIO_DEMO
static int main_key_event_handler(struct key_event *key)
同理,示例内部各功能模式也由独立宏控制。audio_demo 的应用模式表通过条件编译动态组装——启用哪个宏,表中就包含哪个模式,未启用的模式不会占用 Flash 与 RAM:
Source: main.c
struct audio_app_t {
const char *tone_file_name;
const char *app_name;
};
static const struct audio_app_t audio_app_table[] = {
#ifdef CONFIG_LOCAL_MUSIC_MODE_ENABLE
{"FlashMusic.mp3", "local_music"},
#endif
#ifdef CONFIG_NET_MUSIC_MODE_ENABLE
{"NetMusic.mp3", "net_music" },
#endif
#ifdef CONFIG_RECORDER_MODE_ENABLE
{"Recorder.mp3", "recorder"},
#endif
#ifdef CONFIG_ASR_ALGORITHM_ENABLE
{"AiSpeaker.mp3", "ai_speaker"},
#endif
};
设计意图:这张表是"公共示例"最典型的组织模式——每个子应用由 app_name 标识(与 start_app() 的应用注册名对应),每个模式切换前先播放一段提示音 tone_file_name,提示音结束后才真正拉起目标应用。这种"先播提示音、再切模式"的顺序由 dec_server_event_handler 中的事件回调保证(见下文核心流程)。
示例的统一运行模型
所有公共示例共享同一套"事件驱动 + 应用切换"的运行模型,audio_demo/main.c 是这一模型的完整样本,包含四类标准组件:
1. 按键事件处理器
示例通过注册 main_key_event_handler 响应用户按键,处理 KEY_EVENT_CLICK(单击)与 KEY_EVENT_LONG(长按)两类动作;未消费的事件返回 false 交给上层继续处理:
Source: main.c
static int main_key_event_handler(struct key_event *key)
{
switch (key->action) {
case KEY_EVENT_CLICK:
switch (key->value) {
case KEY_MODE:
audio_demo_mode_switch();
break;
default:
return false;
}
break;
case KEY_EVENT_LONG:
break;
default:
return false;
}
return true;
}
2. 设备事件与网络事件处理器
示例还需处理设备插拔(如 Flash/SD 卡)与网络状态变化。设备事件区分 DEVICE_EVENT_IN(插入)、DEVICE_EVENT_OUT(拔出)、DEVICE_EVENT_CHANGE(变更);网络事件区分 NET_EVENT_CMD 与 NET_EVENT_DATA,且仅在 CONFIG_NET_ENABLE 开启时编译:
Source: main.c
static int main_dev_event_handler(struct device_event *event)
{
switch (event->event) {
case DEVICE_EVENT_IN:
break;
case DEVICE_EVENT_OUT:
break;
case DEVICE_EVENT_CHANGE:
break;
}
return 0;
}
#ifdef CONFIG_NET_ENABLE
static int main_net_event_hander(struct net_event *event)
{
switch (event->event) {
case NET_EVENT_CMD:
break;
case NET_EVENT_DATA:
break;
}
return false;
}
#endif
3. 音频服务器事件回调
耗时媒体任务通过 server 框架执行,示例以回调 dec_server_event_handler 接收解码服务器(audio_server)的事件。当提示音解码结束(AUDIO_SERVER_EVENT_END)或出错(AUDIO_SERVER_EVENT_ERR)时,回调负责:停止解码请求、关闭服务器连接、关闭文件句柄、重新使能按键,最后通过 start_app 拉起下一个应用模式:
Source: main.c
static void dec_server_event_handler(void *priv, int argc, int *argv)
{
union audio_req r = {0};
switch (argv[0]) {
case AUDIO_SERVER_EVENT_ERR:
log_i("tone: AUDIO_SERVER_EVENT_ERR\n");
case AUDIO_SERVER_EVENT_END:
log_i("tone: AUDIO_SERVER_EVENT_END\n");
r.dec.cmd = AUDIO_DEC_STOP;
server_request(priv, AUDIO_REQ_DEC, &r);
server_close(priv); //priv是server_register_event_handler_to_task的priv参数
fclose((FILE *)argv[1]); //argv[1]是解码开始时传递进去的文件句柄
key_event_enable();
//等提示音播完了再切换模式
struct intent it;
init_intent(&it);
it.name = audio_app_table[mode_index++].app_name;
start_app(&it);
break;
case AUDIO_SERVER_EVENT_CURR_TIME:
log_i("play_time: %d\n", argv[1]);
break;
default:
break;
}
}
设计意图:这里体现了两个关键约定——(1) 事件回调通过 argv[0] 区分事件类型,argv[1] 传递解码开始时传入的文件句柄,priv 是 server_register_event_handler_to_task 注册时传入的服务器句柄;(2) 模式切换必须等提示音播完(AUDIO_SERVER_EVENT_END 回调内)才执行 start_app,避免在音频服务器仍占用资源时切换应用导致冲突。
核心流程:示例应用的启动与模式切换
以 audio_demo 为例,公共示例工程的完整运行流程遵循"按键事件 → 播放提示音 → 事件回调 → 应用切换"的时序。图中 audio_app_table 的下标 mode_index 每切换一次递增,循环遍历已使能的模式:
sequenceDiagram
participant U as 用户按键
participant KE as main_key_event_handler
participant AM as audio_demo_mode_switch
participant PS as app_play_tone_file
participant SRV as audio_server
participant DSE as dec_server_event_handler
participant AC as app_core (start_app)
U->>KE: KEY_EVENT_CLICK / KEY_MODE
KE->>AM: audio_demo_mode_switch()
AM->>PS: app_play_tone_file(tone_file_name)
PS->>SRV: server_request(AUDIO_REQ_DEC, ...)
SRV-->>DSE: AUDIO_SERVER_EVENT_END (argv[1]=FILE*)
DSE->>SRV: server_request(AUDIO_DEC_STOP) + server_close
DSE->>DSE: fclose(file) + key_event_enable()
DSE->>AC: init_intent + start_app(audio_app_table[mode_index++].app_name)
AC-->>U: 进入下一应用模式(local_music → net_music → recorder → ai_speaker)
提示音的发起由 app_play_tone_file 完成——它打开音频文件、构造 audio_req 请求并通过 server_request 提交给音频解码服务器,同时把文件句柄作为参数随请求传递,供回调结束时释放:
Source: main.c
//播放提示音
static int app_play_tone_file(const char *path)
{
int err = 0;
union audio_req req = {0};
log_d("play tone file : %s\n", path);
FILE *file = fopen(path, "r");
...
}
示例工程的生命周期总览
从编译到运行,公共示例经历以下阶段:
flowchart TD
Start([工程配置]) --> Build{"app_config.h 使能宏"}
Build -->|"USE_AUDIO_DEMO 等已定义"| Compile["示例代码参与编译"]
Build -->|"未定义"| Skip["示例被排除出固件"]
Compile --> Boot["系统启动,app_core 初始化"]
Boot --> Reg["示例注册事件处理器"]
Reg --> Wait["进入事件驱动主循环"]
Wait --> Ev{"事件到达?"}
Ev -->|"按键事件"| Key["main_key_event_handler"]
Ev -->|"设备事件"| Dev["main_dev_event_handler"]
Ev -->|"网络事件"| Net["main_net_event_hander"]
Key --> Action["执行业务动作<br/>如 audio_demo_mode_switch"]
Dev --> Action
Net --> Action
Action --> Wait
配置选项
公共示例的编译与行为主要由 app_config.h 中的宏控制。下表汇总了 audio_demo 中可直接观察到的配置项(其他示例遵循相同模式,具体宏名以各示例头文件为准):
| 配置宏 | 作用范围 | 默认状态 | 说明 |
|---|---|---|---|
USE_AUDIO_DEMO | 编译期 | 关闭 | 使能 audio_demo 综合示例,未定义时 main.c 整体不参与编译 |
CONFIG_NET_ENABLE | 编译期 | 关闭 | 使能网络功能,决定 main_net_event_hander 等网络代码是否编译 |
CONFIG_LOCAL_MUSIC_MODE_ENABLE | 编译期 | 关闭 | 在 audio_app_table 中启用 local_music(本地音乐)模式,提示音 FlashMusic.mp3 |
CONFIG_NET_MUSIC_MODE_ENABLE | 编译期 | 关闭 | 启用 net_music(网络音乐)模式,提示音 NetMusic.mp3 |
CONFIG_RECORDER_MODE_ENABLE | 编译期 | 关闭 | 启用 recorder(录音)模式,提示音 Recorder.mp3 |
CONFIG_ASR_ALGORITHM_ENABLE | 编译期 | 关闭 | 启用 ai_speaker(AI 音箱/ASR)模式,提示音 AiSpeaker.mp3 |
配置要点:模式表采用条件编译组装,因此"未使能模式对应的代码模块"同样不会被链接,固件体积随使能模式数量线性增长。新增一个模式时,开发者只需在表中追加一条 {提示音, app_name} 条目,并提供同名 .c 应用文件即可。
API 参考(来自 audio_demo 示例)
以下签名均提取自 apps/common/example/audio/audio_demo/main.c 实际代码,是编写自定义示例时最常复用的接口模式:
main_key_event_handler(struct key_event *key): int
按键事件回调入口。返回 true 表示事件已消费,false 表示未处理、交由框架继续分发。
- 参数:
key— 按键事件对象,含action(KEY_EVENT_CLICK/KEY_EVENT_LONG)与value(如KEY_MODE)。 - 返回:
int(true/false)。
dec_server_event_handler(void *priv, int argc, int *argv): void
音频服务器事件回调,通过 argv[0] 区分事件类型。
- 参数:
priv— 服务器句柄(注册时传入);argc— 参数个数;argv— 参数数组,argv[0]为事件类型(AUDIO_SERVER_EVENT_END/AUDIO_SERVER_EVENT_ERR/AUDIO_SERVER_EVENT_CURR_TIME),argv[1]为解码文件句柄或播放时间。 - 典型动作:
server_request(priv, AUDIO_REQ_DEC, &r)(停止解码)、server_close(priv)、fclose((FILE *)argv[1])、key_event_enable()、init_intent(&it)+start_app(&it)。
app_play_tone_file(const char *path): int
播放提示音文件。打开 path 指向的音频文件,构造 union audio_req 并通过 server_request 提交解码。
- 参数:
path— 音频文件路径(如"FlashMusic.mp3")。 - 返回:
int(错误码)。
init_intent(struct intent *it) / start_app(struct intent *it)
应用切换原语。init_intent 初始化意图结构体,设置 it.name 为目标应用名后调用 start_app 拉起新应用。audio_app_table[mode_index++].app_name 即通过此机制实现模式轮转。
失败模式与边界情况
根据 audio_demo/main.c 的实现可以归纳出公共示例工程中常见且必须处理的边界与失败场景:
- 提示音播放失败/中断:解码出错时服务器会回调
AUDIO_SERVER_EVENT_ERR。代码将AUDIO_SERVER_EVENT_ERR与AUDIO_SERVER_EVENT_END合并处理(fall-through),保证无论提示音正常结束还是出错,都会执行server_close、fclose与key_event_enable,避免资源泄漏和按键被永久禁用。这一"错误与正常路径共用清理代码"的模式值得在自定义示例中沿用。 - 模式表越界:
mode_index++直接作为audio_app_table下标使用,因此audio_app_table[]中未初始化的数组元素(如仅使能一个模式时)不会导致越界——但表元素数量必须与使能宏严格一致,新增模式而忘记配置宏会导致数组长度不匹配的隐患。 - 资源双重释放风险:文件句柄
argv[1]由事件回调负责fclose,而解码请求本身通过server_request(AUDIO_DEC_STOP)停止——两者必须按"先停请求、再关句柄"的顺序执行,先fclose后停止解码可能造成服务器在已关闭的句柄上继续读写。 - 按键与应用的竞态:播放提示音期间按键被禁用(
key_event_enable前的状态),提示音结束后才重新使能,防止用户在模式切换过程中再次触发切换导致状态错乱。自定义示例若需快速连续切换,需自行设计防抖或队列机制。 - 网络代码的条件编译:
main_net_event_hander仅在CONFIG_NET_ENABLE下编译。若在未使能网络的固件中引用了NET_EVENT_*相关符号,会直接编译失败;反之在使能网络的工程中遗漏CONFIG_NET_ENABLE则不会有任何网络事件被处理。
性能与运维注意事项
- Flash 占用:每个模式的提示音文件(
FlashMusic.mp3、NetMusic.mp3等)需随固件烧录到 Flash。示例中提示音文件名以字符串字面量写死在表中,实际方案中建议通过资源打包工具统一管理,避免文件名不一致导致fopen失败。 - 音频服务器资源:
server_request/server_close是异步请求,解码任务运行在独立服务器任务上下文中,示例回调中不应执行耗时操作(如长时间阻塞 I/O),以免阻塞服务器任务影响其他音频请求。 - 事件回调执行上下文:事件处理器运行在系统事件分发上下文中,代码应保持轻量——
audio_demo中各处理器仅做分发,实际业务(如模式切换、文件操作)落在后续调用中,这种设计避免了在事件上下文中做重活导致的系统卡顿。
扩展点
公共示例工程的设计为开发者预留了清晰的扩展路径:
- 新增子应用模式:在
audio_app_table中追加{"提示音文件", "app_name"}条目,并以该app_name编写独立的xxx.c应用文件(参考local_music.c、recorder.c等),同时通过start_app注册应用名。 - 新增整个示例工程:在
apps/common/example/<类别>/下新建目录并编写main.c,在app_config.h中定义对应的使能宏,参照USE_AUDIO_DEMO的#ifdef包裹方式接入构建系统。 - 接入新的网络协议:
network_protocols/下的coap、http、mqtt示例展示了统一的协议接入模式——在CONFIG_NET_ENABLE基础上注册网络事件,示例可作为协议栈移植的起点。 - BLE Mesh 平台对接:
apps/common/ble/mesh/examples中的AliGenie_*、TUYA_light示例展示了如何基于provisioner.c与provisioner_config.h对接天猫精灵、涂鸦等云平台,新增平台时可在同目录复制改造。
测试情况说明
公共示例工程面向"开箱演示"与"二次开发起点"两个目标,仓库中示例代码以演示与移植为主(如 Devkitboard_test 即开发板硬件自检程序)。示例目录下未发现独立的单元测试文件——功能验证主要依赖目标板上运行的集成验证(如按键切换模式、播放提示音、网络连接等手工验证路径)。audio_demo 的事件回调中大量 log_i/log_d 日志(如 "tone: AUDIO_SERVER_EVENT_END"、"play_time: %d")即为板端调试定位的主要手段,开发者可通过串口日志观察运行轨迹。
Related Links
- SDK 概述与快速开始(README)
- audio_demo 综合示例源码
- BLE Mesh 示例目录
- 文档中心(SDK 在线开发文档):https://doc.zh-jieli.com/AC79/zh-cn/release_v1.2.0/index.html
- 姊妹页面:音频服务器框架(server/audio_server)、网络协议栈(lwIP/mbedTLS 集成)、BLE Mesh 协议栈