示例代码库
本页汇总 AC792N SDK(sdk/apps/common/example/ 目录)中面向应用开发的全部示例代码,涵盖音频、DMA2D 图形加速、网络协议(MQTT/HTTP/CoAP/FTP/NTP/WebSocket/Socket)、外设(按键/重力传感器/DVB)以及 LVGL 图形库等场景,并说明每个示例的启用方式、控制流与关键 API。
Purpose and Scope
本页是 SDK 示例代码的入口文档,目标读者是希望快速上手 AC792N 芯片外设与协议栈的应用开发者。文档覆盖:
- 示例代码的目录组织与分类(音频、DMA2D、网络协议、外设、OTA、蓝牙、LVGL);
- 每个示例的启用开关(
app_config.h中的配置宏)与编译方式; - 代表性示例的完整控制流与核心 API 用法(MQTT、按键事件、音频 buffer 播放);
- 示例中反复出现的 SDK 基础设施(application 注册、事件系统、server 请求模型)。
以下主题不在本页范围,由各自的目录页单独讲解:Wi-Fi 连接细节(见网络协议相关页面)、音频编解码器内部实现(见音频相关页面)、LVGL 组件的每个示例(见 LVGL 页面)、OTA 升级协议(见升级相关页面)。本页只提供入口、结构与通用模式。
Overview
fw-AC792_SDK 是一套面向 AC792N 系列蓝牙音频 SoC 的嵌入式 SDK,采用"芯片基础库 + 通用 app 层 + 示例"的分层结构。示例代码统一放在 sdk/apps/common/example/ 下,每个示例是一个独立的 main.c(或少量辅助文件),通过配置宏开关编入工程,不随默认固件编译,从而保证示例之间互不干扰、可按需裁剪。
示例按功能域划分:
| 功能域 | 目录 | 代表示例 |
|---|---|---|
| 音频 | example/audio/ | JLA 解码、变速变调(pitch_speed)、buffer 播放、虚拟 DAC、虚拟编码 |
| 图形加速 | example/dma2d/ | 填充/拷贝/混合测试、RLE 解码、DMA2D 快速排序 |
| 网络协议 | example/network_protocols/ | MQTT/MQTTS、HTTP(下载/上传/服务器)、CoAP、FTP、NTP、WebSocket、IPv4/IPv6 Socket |
| 外设 | example/peripheral/ | 按键(key)、重力传感器(gsensor)、TSI DVB |
| 计量 | example/scale/ | 电子秤示例(scale_main) |
| OTA | example/update/ | 外置 Flash 扩展 OTA(ext_ota_example) |
| 蓝牙扩展 | ext_spi_bt/、ext_uart_bt/ | SPI/UART 外挂蓝牙模块测试 |
| 图形库 | lvgl_v8/examples/ | LVGL v8 动画、事件等官方示例移植 |
每个示例都遵循相同的"三步走"模式:① 通过 #ifdef USE_XXX_TEST 编译开关使能 → ② 在 app_config.h 中定义宏并选择运行哪个示例 → ③ 示例内部通过 late_initcall 或应用注册机制挂载到系统。开发者拿到示例后只需改配置宏即可切换测试对象,无需修改启动代码。
Architecture
示例代码在 SDK 中的位置及其与系统组件的依赖关系如下图所示:
flowchart TD
subgraph sg_Examples["sdk/apps/common/example/(示例层)"]
MQTT["network_protocols/mqtt/main.c"]
HTTP["network_protocols/http/*/main.c"]
SOCK["network_protocols/sockets/*/*_example.c"]
AUDIO["audio/play_audio_buffer/main.c"]
KEY["peripheral/key/main.c"]
DMA2D["dma2d/*_test.c"]
LVGL["lvgl_v8/examples/*/*.c"]
end
subgraph sg_Sys["系统服务层(SDK 基础设施)"]
APP["application 框架<br/>REGISTER_APPLICATION / start_app"]
EVENT["事件系统<br/>sys_event / key_event / device_event"]
SERVER["server 框架<br/>server_request / audio_server"]
NET["网络协议栈<br/>MQTTClient / socket API"]
WIFI["wifi/wifi_connect.h"]
end
subgraph sg_HW["硬件层"]
HW["AC792N 外设:DMA2D / I2S / Key / SPI / UART"]
end
APP_CONFIG["app_config.h(USE_XXX_TEST 开关)"] -->|"编译期使能"| sg_Examples
MQTT --> NET
MQTT --> WIFI
HTTP --> NET
SOCK --> NET
AUDIO --> SERVER
KEY --> APP
KEY --> EVENT
DMA2D --> HW
LVGL --> HW
NET --> HW
SERVER --> HW
EVENT --> HW
架构要点:
- 示例层只依赖系统服务层,不反向依赖。所有示例都通过
system/includes.h、app_config.h引入 SDK 头文件,自身不实现硬件驱动,便于复用与裁剪。 - 编译开关是唯一的"接线"方式。示例文件头部用
#ifdef USE_MQTT_TEST(或USE_AUDIO_BUFFER_TEST等)包裹全部代码,未定义宏时文件编译为空,不占用代码空间。 - 应用框架(application)是交互类示例的入口。按键示例通过
REGISTER_APPLICATION(app1)注册名为APP1的应用,并用late_initcall(c_main)创建测试任务;网络类示例则直接在main.c中同步执行协议流程。
示例目录详解
音频示例(example/audio/)
| 示例 | 说明 |
|---|---|
jla/main.c | JLA 私有音频格式解码播放 |
pitch_speed/main.c | 变速变调播放,演示 pitch_speed 音频处理链 |
play_audio_buffer/main.c | 从文件/缓存读取音频数据送入解码 server 播放,演示 audio server 事件回调与读写信号量同步 |
virtual_dac/main.c | 虚拟 DAC,演示把解码输出重定向(不真实播放) |
virtual_enc/main.c | 虚拟编码器,演示解码后重新编码的链路 |
DMA2D 图形加速示例(example/dma2d/)
| 示例 | 说明 |
|---|---|
fill_test.c | 2D 填充(fill)性能与正确性测试 |
copy_test.c | 2D 拷贝(copy/blit)测试 |
blend_test.c | 2D 混合(blend/alpha 合成)测试 |
dma2d_quick_sort.c/.h | 利用 DMA2D 并行计算能力实现快速排序的演示 |
dma2d_verify.c | DMA2D 结果校验工具 |
rle_test/rle_test.c | RLE(游程编码)解码测试,用于压缩位图资源 |
网络协议示例(example/network_protocols/)
| 示例 | 说明 |
|---|---|
mqtt/main.c | MQTT 客户端连接阿里云物联网平台,订阅/发布温湿度消息(QoS1) |
mqtts/main.c | MQTT over TLS(加密连接)版本 |
http/http_download/main.c | HTTP 文件下载 |
http/http_post_get_put/main.c | HTTP POST/GET/PUT 请求示例 |
http/http_server/main.c | 设备侧 HTTP 服务器 |
http/user_http_head/post_flash/main.c、post_sd/main.c | HTTP 上报数据到 Flash / SD 卡 |
| `coap/coap_client | server/main.c` |
ftp_client/main.c | FTP 客户端 |
ntp/main.c | NTP 网络校时 |
websocket/main.c | WebSocket 客户端 |
sockets/ipv4/*_example.c、sockets/ipv6/*_example.c | 原生 Socket 编程示例:IPv4/IPv6 下的 TCP 客户端、TCP 服务器、UDP 客户端、UDP 服务器 |
外设与其它示例
peripheral/key/main.c:按键事件示例,演示 application 注册、SYS_KEY_EVENT事件处理与key_event_enable()开关;peripheral/gsensor/main.c:重力传感器驱动与数据读取;peripheral/TSI_DVB/DVB/dvb0_example.c、dvb1_example.c:TSI DVB 调谐器双路示例;scale/scale_main.c:电子秤应用主流程;update/http_upgrade/example3/ext_ota_example.c:外置 Flash 扩展 OTA 升级示例;ext_spi_bt/bt_test_example.c、ext_uart_bt/bt_test_example.c:SPI/UART 外挂蓝牙模块的 AT 交互测试;lvgl_v8/examples/:LVGL v8 官方示例移植(anim 动画、event 事件等),用于验证图形栈。
示例的启用与控制流
所有示例共享同一条"配置宏 → 初始化挂载 → 任务运行"的路径:
flowchart TD
Start([修改 app_config.h]) --> Define{"定义 USE_XXX_TEST 宏?"}
Define -->|"否"| Skip["该示例 main.c 整体为空<br/>(#ifdef 未命中)"]
Define -->|"是"| Compile["示例代码编入固件"]
Compile --> Entry{"示例入口类型"}
Entry -->|"网络类(MQTT/HTTP…)"| Sync["main.c 中同步执行协议流程<br/>(NewNetwork→Connect→Subscribe→Publish)"]
Entry -->|"交互类(key…)"| App["late_initcall(c_main)<br/>→ os_task_create 创建任务"]
App --> Reg["REGISTER_APPLICATION 注册应用<br/>event_handler 接收系统事件"]
Sync --> Run["示例运行,打印 log"]
Reg --> Run
启用步骤
- 打开
sdk/apps/common/include/app_config.h(各工程亦可使用自己的app_config.h); - 定义对应宏,例如
#define USE_MQTT_TEST或#define USE_AUDIO_BUFFER_TEST; - 同一时间只启用一个示例,避免多个示例的
main.c同时编入造成入口冲突; - 重新编译烧录,观察串口 log。
注意:宏名与示例文件一一对应(
USE_MQTT_TEST对应mqtt/main.c,USE_AUDIO_BUFFER_TEST对应audio/play_audio_buffer/main.c),这是示例代码中#ifdef守卫使用的约定。
为什么使用"守卫宏 + 独立 main.c"的设计?
这是嵌入式 SDK 常见的示例组织方式,设计意图有三点:
- 零成本裁剪:未使能的示例编译为空文件,不占用 Flash/RAM,避免链接器因未使用函数报错;
- 隔离命名空间:每个示例独占一个
main.c,静态函数(如mqtt_start、app1_event_handler)不会互相冲突; - 降低上手门槛:开发者不需要理解整个 SDK 的构建体系,改一个宏即可切换测试对象,把"示例"与"产品代码"的边界固定下来。
核心流程:MQTT 示例(连接阿里云物联网平台)
network_protocols/mqtt/main.c 是网络类示例的典型代表:通过 MQTT 协议连接阿里云,订阅下行主题并周期性发布温湿度属性。其控制流如下:
sequenceDiagram
participant App as mqtt_start()
participant Net as Network(TCP)
participant MQ as MQTTClient
participant Ali as 阿里云 IoT 平台
App->>Net: NewNetwork(&network)
App->>Net: SetNetworkRecvTimeout(1000ms)
App->>MQ: MQTTClient(&client, &network, ...)
App->>Net: ConnectNetwork(address, 1883)
Net-->>App: err == 0
App->>MQ: MQTTConnect(&client, &connectData)
MQ->>Ali: CONNECT(用户名/密码/心跳30s)
Ali-->>MQ: CONNACK
App->>MQ: MQTTSubscribe(topic, QOS1, messageArrived)
MQ->>Ali: SUBSCRIBE /user/get
Ali-->>MQ: SUBACK
loop 每 2 次循环发布一次
App->>MQ: MQTTPublish(publishTopic, &message)
MQ->>Ali: PUBLISH(温湿度 JSON)
Ali-->>MQ: PUBACK(QoS1)
end
Ali-->>App: 下行消息 → messageArrived 回调打印
示例代码中的关键配置常量(见 main.c):
| 常量 | 值 | 含义 |
|---|---|---|
COMMAND_TIMEOUT_MS | 30000 | 命令超时时间(ms) |
MQTT_TIMEOUT_MS | 10000 | 接收阻塞时间(ms) |
MQTT_KEEPALIVE_TIME | 30000 | MQTT 心跳保活时间(ms) |
SEND_BUF_SIZE / READ_BUF_SIZE | 1024 | 发送/接收缓冲区大小 |
核心流程:音频 Buffer 播放示例
audio/play_audio_buffer/main.c 演示了 audio server 的事件驱动模型:解码 server 通过回调上报事件,示例通过读写信号量(r_sem/w_sem)与音频线程同步缓存数据。回调中处理 AUDIO_SERVER_EVENT_ERR、AUDIO_SERVER_EVENT_END(停止解码并 audio_uninit())和 AUDIO_SERVER_EVENT_CURR_TIME(刷新播放进度),自定义的 audio_vfs_fread 作为 VFS 读函数从 cbuffer_t 环形缓冲取数据,缓冲不足时挂起等待写信号量。这种"生产者-消费者 + 事件回调"结构是 SDK 音频链路的通用模式,示例可作为自定义音频源的模板。
核心流程:按键事件示例
peripheral/key/main.c 演示了 application 事件模型:先通过 REGISTER_APPLICATION(app1) 注册名为 APP1 的应用并绑定 event_handler,再用 late_initcall(c_main) 创建测试任务;任务中 start_app(&it) 启动应用、key_event_enable() 打开按键事件开关,此后所有 SYS_KEY_EVENT 都会递交给 app1_event_handler。event_handler 返回 true 表示已消费事件,不再传递给 app_default_event_handler——这是 SDK 事件分发的一个重要约定。
使用示例
示例 1:MQTT 订阅与发布(网络类示例模板)
以下代码摘自 MQTT 示例的初始化与订阅发布流程,展示了 SDK 网络协议栈的标准调用序列:NewNetwork → MQTTClient → ConnectNetwork → MQTTConnect → MQTTSubscribe → MQTTPublish:
//接收回调,当订阅的主题有信息下发时,在这里接收
static void messageArrived(MessageData *data)
{
char temp[128] = {0};
strncpy(temp, data->topicName->lenstring.data, data->topicName->lenstring.len);
temp[data->topicName->lenstring.len] = '\0';
printf("Message arrived on topic (len : %d, topic : %s)\n", data->topicName->lenstring.len, temp);
memset(temp, 0, sizeof(temp));
strncpy(temp, data->message->payload, data->message->payloadlen);
temp[data->message->payloadlen] = '\0';
printf("message (len : %d, payload : %s)\n", data->message->payloadlen, temp);
}
static int mqtt_start(void)
{
Client client;
Network network;
MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer;
MQTTMessage message;
int err;
int loop_cnt = 0;
int temperature = 0;
int humidity = 0;
char sendbuf[256];
char payload[256] = "{\"id\":\"161848123\",\"version\":\"1.0\",\"params\":{\"temperature\":%d,\"Humidity\":%d},\"method\":\"thing.event.property.post\"}";
char *address = "a1mhUx5m6Uq.iot-as-mqtt.cn-shanghai.aliyuncs.com";
char *username = "iot-001&a1mhUx5m6Uq";
char *password = "083dc23c92b1aad822c7c1f1a3c687dda022089b";
char *subscribeTopic = "/a1mhUx5m6Uq/iot-001/user/get"; //订阅的主题
char *publishTopic = "/sys/a1mhUx5m6Uq/iot-001/thing/event/property/post"; //发布消息的主题
char *clientID = "12345|securemode=3,signmethod=hmacsha1|";
_reconnect:
//初始化网络接口
NewNetwork(&network);
SetNetworkRecvTimeout(&network, 1000);
//初始化客户端
MQTTClient(&client, &network, COMMAND_TIMEOUT_MS, send_buf, sizeof(send_buf), read_buf, sizeof(read_buf));
//tcp层连接服务器
err = ConnectNetwork(&network, address, 1883);
if (err != 0) {
printf("ConnectNetwork fail\n");
return -1;
}
connectData.willFlag = 0;
connectData.MQTTVersion = 3; //mqtt版本号
connectData.clientID.cstring = clientID; //客户端id
connectData.username.cstring = username; //连接时的用户名
connectData.password.cstring = password; //连接时的密码
connectData.keepAliveInterval = MQTT_KEEPALIVE_TIME / 1000; //心跳时间
connectData.cleansession = 1; //是否使能服务器的cleansession,0:禁止, 1:使能
//mqtt层连接,向服务器发送连接请求
err = MQTTConnect(&client, &connectData);
if (err != 0) {
network.disconnect(&network);
printf("MQTTConnect fail, err : 0x%x\n", err);
return -1;
}
//订阅主题
err = MQTTSubscribe(&client, subscribeTopic, QOS1, messageArrived);
if (err != 0) {
MQTTDisconnect(&client);
network.disconnect(&network);
printf("MQTTSubscribe fail, err : 0x%x\n", err);
return -1;
}
message.qos = QOS1;
message.retained = 0;
while (1) {
if (0 == loop_cnt % 2) {
sprintf(sendbuf, payload, temperature, humidity);
message.payload = sendbuf;
message.payloadlen = strlen(sendbuf) + 1;
temperature += 1;
humidity += 2;
if (temperature > 100) {
temperature = 0;
}
if (humidity > 100) {
humidity = 0;
}
//发布消息
err = MQTTPublish(&client, publishTopic, &message);
...
Source: sdk/apps/common/example/network_protocols/mqtt/main.c
要点解读:
messageArrived是订阅消息回调,MessageData中topicName与message都是"长度 + 指针"结构(lenstring/payloadlen),因此示例先用strncpy拷贝再加'\0',避免越界打印;- 使用
_reconnect:标号支持失败后跳回重连,配合network.disconnect()做资源清理; - 发布使用 QoS1,
payload模板为阿里云物模型属性上报 JSON,温湿度每两轮循环自增一次并做 0~100 回绕。
示例 2:按键事件与 application 注册(交互类示例模板)
#include "app_config.h"
#include "system/includes.h"
#include "event/device_event.h"
#include "event/key_event.h"
#include "key/key_driver.h"
/* 一个事件递交到当前APP处理,实际上最常用*/
static int app1_event_handler(struct application *app, struct sys_event *event)
{
if (event->type == SYS_KEY_EVENT) {
struct key_event *key_e = (struct key_event *)event->payload;
printf("app1_event_handler[in %s task] SYS_KEY_EVENT %d \r\n", os_current_task(), key_e->value);
}
return true; //如果返回true则不再调用 app_default_event_handler
//return false;
}
static void app_event_test_task(void *p)
{
os_time_dly(100); //app_core_init初始化完成后才可以测试
struct intent it;
init_intent(&it);
it.name = "APP1";
it.action = ACTION_DO_NOTHING;
start_app(&it);
key_event_enable(); //针对按键事件,需要先使能开关
while (1) {
os_time_dly(200);
puts("\n\n\n\n");
os_time_dly(200);
puts("\n\n\n\n");
}
}
static int app1_state_machine(struct application *app, enum app_state state, struct intent *it)
{
return 0;
}
static const struct application_operation app1_ops = {
.state_machine = app1_state_machine,
.event_handler = app1_event_handler,
};
REGISTER_APPLICATION(app1) = {
.name = "APP1",
.ops = &app1_ops,
.state = APP_STA_DESTROY,
};
static int c_main(void)
{
os_task_create(app_event_test_task, NULL, 10, 1000, 128, "app_event_test_task");
return 0;
}
late_initcall(c_main);
要点解读:
REGISTER_APPLICATION(app1)是编译期注册宏,把应用描述符链接进系统应用表,start_app(&it)按intent.name查找并启动;late_initcall(c_main)让测试任务在系统初始化晚期自动创建,任务内先os_time_dly(100)等待app_core_init完成,这是示例编写时的时序约定;key_event_enable()是按键事件的全局开关,未使能时按键事件不会投递,这是 SDK 事件系统的节能设计。
示例 3:音频解码 server 事件回调(audio server 模型)
static int audio_buffer_dec_stop(void)
{
int err = 0;
union audio_req req = {0};
if (!__this->file) {
return 0;
}
req.dec.cmd = AUDIO_DEC_STOP;
server_request(__this->dec_server, AUDIO_REQ_DEC, &req);
int argv[2];
argv[0] = AUDIO_SERVER_EVENT_END;
argv[1] = (int)__this->file;
server_event_handler_del(__this->dec_server, 2, argv);
fclose(__this->file);
__this->file = NULL;
return 0;
}
extern void audio_uninit();
static void dec_server_event_handler(void *priv, int argc, int *argv)
{
switch (argv[0]) {
case AUDIO_SERVER_EVENT_ERR:
log_i("audio_buffer: AUDIO_SERVER_EVENT_ERR\n");
case AUDIO_SERVER_EVENT_END:
log_i("audio_buffer: AUDIO_SERVER_EVENT_END\n");
audio_buffer_dec_stop();
audio_uninit();
break;
case AUDIO_SERVER_EVENT_CURR_TIME:
log_d("play_time: %d\n", argv[1]);
__this->play_time = argv[1];
break;
}
}
static int audio_vfs_fread(void *file, void *data, u32 len)
{
cbuffer_t *cbuf = (cbuffer_t *)file;
u32 rlen;
do {
rlen = cbuf_get_data_size(cbuf);
if (rlen < len) {
if (audio_get_read_file_is_end()) {
return cbuf_read(cbuf, data, rlen);
}
audio_post_write_semaphore();
audio_pend_read_semaphore();
continue;
}
if (cbuf_read(cbuf, data, len) > 0) {
return len;
}
} while (__this->run_flag);
...
Source: sdk/apps/common/example/audio/play_audio_buffer/main.c
要点解读:
- 与解码 server 的交互统一走
server_request(server, AUDIO_REQ_DEC, &req)命令通道,req.dec.cmd决定具体动作(如AUDIO_DEC_STOP); - 解码结束/出错通过
AUDIO_SERVER_EVENT_END/ERR事件回调上报,示例在此处做资源释放(fclose、server_event_handler_del、audio_uninit),形成"停止-注销回调-释放"的清理序列; audio_vfs_fread把环形缓冲cbuffer_t包装成 VFS 读接口,数据不足时用audio_pend_read_semaphore()/audio_post_write_semaphore()阻塞等待生产者填充,避免忙等。
配置选项
示例代码的配置集中在两个层面:编译期开关(app_config.h 宏) 与 示例内部常量。
编译期开关(app_config.h)
| 宏 | 对应示例 | 说明 |
|---|---|---|
USE_MQTT_TEST | network_protocols/mqtt/main.c | 使能 MQTT 客户端示例 |
USE_AUDIO_BUFFER_TEST | audio/play_audio_buffer/main.c | 使能音频 buffer 播放示例 |
| (其它示例同理) | 各 main.c 头部 #ifdef | 按示例文件名约定命名 |
开关未定义时,示例文件整体为空。同一时刻建议只启用一个示例,避免多个
late_initcall/ 注册表项同时生效。
MQTT 示例内部常量
| 常量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
COMMAND_TIMEOUT_MS | int | 30000 | 命令超时时间(ms) |
MQTT_TIMEOUT_MS | int | 10000 | 接收阻塞时间(ms) |
MQTT_KEEPALIVE_TIME | int | 30000 | 心跳保活时间(ms) |
SEND_BUF_SIZE | int | 1024 | 发送 buf 大小(字节) |
READ_BUF_SIZE | int | 1024 | 接收 buf 大小(字节) |
send_buf / read_buf | static char[] | — | 全局发送/接收缓冲区 |
来源:mqtt/main.c
音频 Buffer 示例内部常量
| 常量 | 值 | 说明 |
|---|---|---|
FIRST_INFO_SECTOR | 512 | 首个信息扇区偏移 |
READ_DATA_SIZE | 512 | 每次读取数据大小(字节) |
API 参考(示例中使用的 SDK 核心接口)
以下接口并非示例自身定义,而是 SDK 提供的公共 API,示例代码展示了它们的标准用法。
REGISTER_APPLICATION(name)
- 说明:编译期宏,把
struct application描述符链接进系统应用注册表,使start_app()可按名字查找。 - 用法:
REGISTER_APPLICATION(app1) = { .name = "APP1", .ops = &app1_ops, .state = APP_STA_DESTROY }; - 关联结构:
struct application_operation(含state_machine、event_handler回调)。 - 来源:peripheral/key/main.c
start_app(struct intent *it)
- 说明:按
intent.name启动已注册的应用;it->action指定启动动作。 - 参数:
it(struct intent *):包含name、action等字段;需先init_intent(&it)。 - 来源:peripheral/key/main.c
late_initcall(fn)
- 说明:把函数注册为系统"晚期初始化"回调,系统启动流程接近完成时自动调用,适合创建示例测试任务。
- 来源:peripheral/key/main.c
key_event_enable()
- 说明:使能按键事件投递;未调用时按键事件不会进入事件分发。
- 来源:peripheral/key/main.c
MQTT 客户端接口(mqtt/MQTTClient.h)
| 函数 | 说明 |
|---|---|
NewNetwork(Network *n) | 初始化网络接口对象 |
SetNetworkRecvTimeout(Network *n, int ms) | 设置接收超时 |
MQTTClient(Client *c, Network *n, int timeout, ...) | 初始化 MQTT 客户端(绑定 buf) |
ConnectNetwork(Network *n, char *addr, int port) | TCP 层连接服务器,返回 0 成功 |
MQTTConnect(Client *c, MQTTPacket_connectData *d) | MQTT 层连接(CONNECT/CONNACK) |
MQTTSubscribe(Client *c, char *topic, int qos, callback) | 订阅主题并注册消息回调 |
MQTTPublish(Client *c, char *topic, MQTTMessage *msg) | 发布消息 |
MQTTDisconnect(Client *c) | 断开 MQTT 连接 |
- 失败返回值:
MQTTConnect/MQTTSubscribe/MQTTPublish非 0 表示失败,示例打印err : 0x%x并清理资源。 - 来源:network_protocols/mqtt/main.c
Audio server 接口(server/audio_server.h)
| 接口 | 说明 |
|---|---|
server_request(server, AUDIO_REQ_DEC, &req) | 向解码 server 发送命令(req.dec.cmd 取值如 AUDIO_DEC_STOP) |
server_event_handler_del(server, argc, argv) | 注销指定事件回调 |
事件 AUDIO_SERVER_EVENT_ERR/END/CURR_TIME | 解码出错/结束/播放进度上报 |
故障模式、边界情况与并发注意
- 网络连接失败:MQTT 示例对
ConnectNetwork、MQTTConnect、MQTTSubscribe分别判错,失败路径依次执行network.disconnect()与MQTTDisconnect()防止资源泄漏;_reconnect:标号预留了重连入口,但示例本身未实现退避重试,量产代码需补充指数退避与状态机。 - 缓冲区不足:
SEND_BUF_SIZE/READ_BUF_SIZE固定为 1024 字节,若主题名、JSON 报文或证书字段超过该长度会导致截断或错误,扩展示例时应同步加大 buf。 - 消息回调的数据生命周期:
MessageData中的 payload 指针指向接收缓冲,回调返回后即失效,示例先拷贝到局部数组再打印,这是嵌入式 MQTT 回调的通用安全写法。 - 音频环形缓冲并发:
audio_vfs_fread与生产者线程共享cbuffer_t,通过r_sem/w_sem信号量互斥;若生产者提前结束而读端仍阻塞,可能造成永久挂起——示例通过audio_get_read_file_is_end()与run_flag双重判定退出,移植时务必保留这两个退出条件。 - 事件消费语义:
event_handler返回true会短路app_default_event_handler;多个应用同时注册时,事件只递交给当前活跃应用(intent启动的APP1),理解这一点才能正确设计多应用事件路由。 - 示例互斥:多个示例同时使能时,各自的
late_initcall任务与注册表项会同时运行,可能争抢外设或打印混乱;SDK 约定一次只启用一个示例。
性能与运维提示
- MQTT 示例每两轮循环发布一次温湿度,实际产品应根据业务频率调整发布节奏,避免无效流量消耗网络与功耗;心跳
MQTT_KEEPALIVE_TIME(30s)需大于运营商 NAT 超时,否则长连接会被静默断开。 play_audio_buffer使用 512 字节分片读取,配合双信号量实现流水线式填充;若解码跟不上,会表现为CURR_TIME停滞,可优先增大cache_buf而非降低采样率。- 示例代码默认开启
printf/log_i/log_d调试输出,会占用串口带宽;量产时应通过日志等级宏关闭,避免影响实时任务。
扩展点
- 新增协议示例:复制
network_protocols/下任一main.c,按"守卫宏 + 协议初始化 + 收发循环"三段式编写,并在app_config.h增加对应USE_XXX_TEST宏。 - 自定义音频源:以
play_audio_buffer为模板,替换audio_vfs_fread的数据来源(Flash、网络流、录音 buffer),即可接入 SDK 音频解码链路。 - 多应用事件路由:参考
REGISTER_APPLICATION+intent机制注册多个应用,用event_handler的返回值控制事件分发优先级。 - LVGL 示例:
lvgl_v8/examples/下的官方示例可直接移植为 UI 原型,验证图形栈性能后再替换为产品界面。
相关链接
- 示例代码根目录:sdk/apps/common/example
- MQTT 示例源码:network_protocols/mqtt/main.c
- 按键示例源码:peripheral/key/main.c
- 音频 Buffer 播放示例:audio/play_audio_buffer/main.c
- 相关目录页:网络协议(MQTT/HTTP/CoAP)、音频服务、DMA2D 图形加速、LVGL 图形库、OTA 升级、外设(按键/传感器/DVB)。