简介面向 Airoha 洛达 AB1568 蓝牙耳机芯片的应用开发学习者这份文档型资料聚焦耳机工程从启动到功能落地的完整实现路径适合已具备嵌入式与蓝牙基础、希望深入掌握量产级耳机软件架构的开发者。压缩包内为 1 个 PDF 文件大小约 9.58MB按工程分析目录组织内容便于系统查阅与对照实践。内容围绕上电初始化函数、代码宏与 mk 文件定义、App 活动行为开启展开并延伸到 ANC 主动降噪模式切换、LED 呼吸灯样式修改、充电参数监测、超时时间设置及事件收发删除等机制同时覆盖 Airoha API 使用、回调函数注册调用、自动关机与配对时间配置、开机提示电量、清除配对列表、链路丢失重连、UI Shell 框架、SideTone、音频路径、DUT 模式测 RF、硬件复位与 PEQ 设定、通话降噪及 iGO 算法等模块。已有 374 人学习可作为耳机固件开发的功能索引与实现参考帮助读者快速定位关键流程、理解模块间协作关系并减少调试摸索成本。1. 三连击 Power 键没反应AB1568 蓝牙耳机开发最先撞上的那道墙某开发者拿到 AB1568 平台的 headset 参考工程第一件事就是改按键。他在customerized_key_config.c里把三连击 Power 键绑到KEY_SWITCH_ANC_AND_PASSTHROUGH编译、下载、戴上耳机三连击耳机“滴”了一声提示音然后——什么也没发生降噪没有通透也没有。事件链路是通的app_anc_service_enable也确实被调用了问题出在更底层Config Tool 打开download目录一看ANC 那几组滤波器的 NVKEY 是空的服务层拿着空气在切模式。这类“代码没错、参数没落地”的坑是 AB1568 蓝牙耳机开发里最典型的入门墙。这份 headset 工程分析资料从 bootloader 的main一直讲到NVKEY_Setting.c横跨启动链路、UI Shell 活动框架、ANC 模式切换、LED 呼吸灯、充电监测、FOTA 与 NVDM、RACE 产测指令三十多个主题。它适合已经拿到 SDK 但不知道从哪个文件下手的嵌入式音频开发者也适合能把功能跑起来、但说不清事件怎么在 activity 之间流转的熟手。2. 启动链路与编译入口从 bootloader 的 main 到 feature_68_evk.mk2.1 上电后的两段 main谁先跑谁决定跑不跑AB1568 的固件不是从头到尾一个main。上电后先跑的是apps目录下 bootloader 的那份main它负责判断“这次上电是要升级固件还是正常启动应用”确认没有升级任务之后才跳转到apps下对应的工程earbuds或headset的main再进apps_inits把所有应用模块拉起来。/* ---------- 阶段一apps 下 bootloader 工程的 main示意 ---------- */ int main(void) { /* bl_fota_process 只有在 BL_FOTA_ENABLE 打开时才会被编进固件。 宏没开这个函数在预处理阶段就被整段删掉了不是运行时才跳过。 */ #ifdef BL_FOTA_ENABLE bl_fota_process(); /* 检查是否有待升级镜像有则停在 bootloader 收包 */ #endif /* 没有升级任务校验应用区镜像后跳过去 */ bootloader_jump_to_app(); return 0; } /* ---------- 阶段二apps/headset 工程的 main示意 ---------- */ int main(void) { /* 芯片时钟、电源域、引脚复用等底层初始化SDK 已封装 */ hal_init(); /* 各应用模块一次性拉起UI Shell、音频通路、按键、LED、充电检测 */ apps_inits(); /* 进入消息循环后续所有 activity 的处理都发生在这里面 */ while (1) { ui_shell_run(); /* 取事件 → 找目标 activity → 回调处理 */ } }这里有两个容易读漏的点。第一BL_FOTA_ENABLE是条件编译不是运行开关define 写在 mk 文件里而不是某个config.h所以你在源码里 grep 这个宏名只能看到#ifdef定义要去 GCC 目录下的 mk 里翻。第二apps_inits是一个大杂烩入口UI Shell 的活动注册、按键服务注册、音频服务初始化都挂在这一串调用里排查“某个模块没起来”时先在这里打断点确认它到底有没有被调。函数说明bl_fota_process是 FOTA 的入口点负责识别升级标志位apps_inits是应用层模块初始化总入口ui_shell_run是 UI Shell 的主循环常见做法是“阻塞取事件 → 分发 → 回到取事件”SDK 里的实际函数名以你手上的版本为准。2.2 用 build.sh list 定位宏写在哪个 mk 文件SDK 的 GCC 目录下有一堆 mk 文件feature_68_evk.mk、ab1568_evk.mk、各种feature_*.mk同一批宏在不同工程里取值可能完全不同。改之前先确认三件事当前编译的是哪个工程、这个工程用的是哪块板子的配置、对应哪个 mk。# 进入 SDK 根目录 cd sdk_root # 列出所有可编译工程以及每个工程调用的 mk 文件 ./build.sh list # 编译 headset 参考设计工程板级配置是 ab1565_ab1568_evk ./build.sh ab1568_evk headset_ref_designlist的输出会明确告诉你ab1568_evk的headset_ref_design工程调的是feature_68_evk这个 mk。当前头戴耳机项目是基于ab1565_ab1568_evk这块开发板派生出来的所以板级相关的宏都在feature_68_evk.mk里这一步搞错后面所有“我明明改了宏”的争论都没有意义。2.3 在 feature_68_evk.mk 里加宏的正确姿势宏定义直接写在这个 mk 里格式是名字 y。加自定义开关的时候我习惯统一前缀方便半年后回来 grep。# feature_68_evk.mk 片段 # ANC 总开关按键处理里 #ifdef MTK_ANC_ENABLE 靠它生效 MTK_ANC_ENABLE y # FBC反馈麦补偿开关关掉后 ANC 只剩 FFFB 两路 MTK_ANC_ENABLE_FBC y # bootloader 的 FOTA 能力不开则 bl_fota_process 不参与编译 BL_FOTA_ENABLE y # 自定义业务开关建议统一加项目前缀 MYAPP_LED_BREATH_ENABLE y MYAPP_ANC_EXTRA_LEVEL y参数说明值写y还是Y取决于 mk 里的判断写法大部分 SDK 用ifeq ($(XXX),y)大小写敏感照抄同文件里已有条目的写法最稳。新增宏之后必须 clean 再 build因为增量编译不会重新跑预处理宏改了但目标文件没重编现象就是“改了跟没改一样”。2.4 clean 之后 build你才拿到真正可用的 download 目录这条单独拎出来说是因为后面 ANC 参数固化会反复踩。工程编译产物里有一个download目录Config Tool 打开的就是它里面装着filesystem.bin、若干nvkey.xml和固件 bin。不清干净直接 builddownload里可能还混着上一次的旧 bin只有 clean 后完整 builddownload才是这次代码对应的那一份。养成“改宏 → clean → build → Config Tool 打开 download 确认”的固定动作能省掉大量“参数怎么又回去了”的玄学排查。3. UI Shell 事件框架Activity、事件投递与 LED 呼吸灯联动3.1 ui_shell_start_activity 与 app_preproc_activity_proc 的分工UI Shell 用一套“活动activity 事件”的模型来组织应用。每个 activity 相当于一个 UI 界面或者一个功能状态比如 home screen、通话中、配对中各自有独立的事件组。系统里产生一个事件后是广播出去的所有 activity 都能收到谁的事件组和事件 ID 匹配谁处理。/* 应用初始化阶段把 activity 注册进活动堆示意 */ void app_init_activities(void) { /* 主进程负责调度、分发不直接管具体界面 */ ui_shell_start_activity(app_preproc_activity_proc, NULL, 0, NULL); /* 子界面各自注册idle 界面负责按键、ANC、LED 这类常驻交互 */ ui_shell_start_activity(app_home_screen_idle_activity_proc, NULL, 0, NULL); }ui_shell_start_activity的作用是在活动堆里申请一个空闲 item 并挂上去参数依次是活动处理结构体指针、初始化参数、参数长度、启动后要投递的初始事件。app_preproc_activity_proc是主进程管理其他子进程app_home_screen_idle_activity_proc是空闲界面进程开机后大部分按键事件、ANC 切换、LED 指示都落在它身上。理解这层关系以后看到一个事件没人处理你就知道该去查它的group是不是挂在了当前没启动的 activity 上。3.2 三个事件函数send、remove、init_input_msg事件机制对外就三个函数掌握它们基本够用。/* 投递事件把自己塞进目标 activity 的事件队列 */ bool ui_shell_send_event(bool is_sync, /* true 同步等待处理完false 异步 */ uint32_t group, /* 事件组决定谁能收到 */ uint32_t event_id, /* 组内事件号 */ void *data, /* 附带数据可为 NULL */ uint32_t data_size, /* 数据长度 */ uint32_t timeout, /* 超时毫秒数超时后事件作废 */ uint32_t handler_id); /* 目标 activity 的 id */ /* 撤销事件超时或状态切换时把还没处理的事件捞回来 */ bool ui_shell_remove_event(uint32_t group, uint32_t event_id); /* 外部消息入口按键、UART、协议栈投进来的消息从这里进 UI Shell */ bool ui_shell_init_input_msg(void);is_sync传 true 时调用方会阻塞到目标 activity 处理完按键这种需要立刻响应的场景一般用异步避免在中断上下文里卡住。timeout是个容易被忽略的参数设 0 表示不超时一直等设具体毫秒数则超时后事件自动作废适合“3 秒内没确认配对就取消”这类逻辑。data和data_size指向的内存所有权要明确如果发送方在栈上开数组传进去又立刻返回接收方拿到的就是野指针这类问题在 log 里表现为随机崩溃很难查。ui_shell_remove_event常用在状态切换点比如退出配对界面时把还在队列里的“配对超时”事件撤掉否则用户已经回到主界面了几秒后突然弹出配对失败提示。ui_shell_init_input_msg是 UI Shell 的输入消息接收初始化按键、外部调试口进来的指令最终都会汇到这条链上回调函数的注册也在这套机制里完成注册和调用的对应关系没对上表现就是“函数写了但从来没被调”。3.3 超时时间、自动关机与配对时间这些“时间类”配置散落在好几个地方集中记一下比每次翻代码快。配置项常见取值修改位置自动关机超时5 分钟无连接电源管理模块的定时器注册处配对超时180 秒配对流程的ui_shell_send_event超时参数开机提示电量延时300 至 800 毫秒开机流程 activity 的初始事件链路丢失重连窗口视产品定义蓝牙连接管理回调/* 注册一个自动关机定时事件超时未触发就投递关机事件 */ ui_shell_send_event(false, /* 异步投递 */ EVENT_GROUP_UI_SHELL_APP, EVENT_ID_AUTO_POWER_OFF, NULL, 0, AUTO_POWER_OFF_TIMEOUT_MS, /* 例如 5 * 60 * 1000 */ app_preproc_activity_proc.id);这段的关键在于超时参数就是“多久没动作就关机”的时间源改产品需求时改这个宏就够了不用去动定时器底层。清空配对列表、链路丢失重连也走同一套事件清列表是往蓝牙管理模块发一个删除绑定信息的请求重连是在断链回调里重新发起连接并附带重试次数上限两个功能看着不相关本质都是“事件 超时”的组合。3.4 LED 呼吸灯改的是曲线不是寄存器LED 呼吸灯在 SDK 里通常被抽象成一张 step 表每一步给一个亮度和一个持续时长硬件定时器按表推进。所以“改呼吸灯样式”实际是改表不是去改 PWM 寄存器。/* 呼吸灯曲线表示意字段名以 SDK 为准 */ static const led_breath_step_t g_breath_table[] { /* 亮度(0-255), 持续时间(ms) */ { 0, 200 }, /* 全灭停顿形成呼吸的屏息感 */ { 40, 300 }, { 120, 400 }, { 220, 500 }, /* 峰值 */ { 120, 400 }, { 40, 300 }, }; /* 把表注册给 LED 服务并指定这条曲线绑定的场景开机 / 配对 / 低电 */ led_service_set_breath_pattern(LED_SCENE_PAIRING, g_breath_table, sizeof(g_breath_table) / sizeof(g_breath_table[0]));参数说明亮度值受硬件限流影响写到 255 不一定真的最亮实际观感要在板子上调总周期是把所有 duration 加起来想加快呼吸频率就等比缩短 duration而不是把亮度调高。绑定场景这一步别漏很多“灯不亮”的问题不是曲线错是场景没绑定或者当前 activity 压根没切到那个场景。3.5 充电参数监测与开机提示电量充电参数监测一般分两条线一条是充电状态插入、充电中、充满、异常走中断回调另一条是电池电压走 ADC 周期采样。开机提示电量就是把当前电压换算成百分比再用提示音或者语音播报出来。/* 电压转电量百分比分段线性插值比单一斜率准得多 */ static uint8_t battery_voltage_to_percent(uint16_t mv) { /* 锂电池典型放电曲线锚点按实际电芯调整 */ static const struct { uint16_t mv; uint8_t pct; } table[] { { 4200, 100 }, { 4000, 80 }, { 3800, 55 }, { 3700, 30 }, { 3600, 10 }, { 3400, 0 }, }; /* 高于最高锚点直接满电低于最低锚点直接 0 */ if (mv table[0].mv) return 100; for (int i 1; i ARRAY_SIZE(table); i) { if (mv table[i].mv) { /* 在相邻两个锚点之间做线性插值 */ uint16_t dv table[i - 1].mv - table[i].mv; uint8_t dp table[i - 1].pct - table[i].pct; return table[i].pct (mv - table[i].mv) * dp / dv; } } return 0; }参数说明锚点电压取自分压电阻后的实测值电阻精度直接影响百分比误差1% 精度的分压电阻比 5% 的贵不了多少但省很多事。开机提示电量要加延时等音频通路初始化完再播否则前几百毫秒的提示音会被吞掉这也是为什么很多产品开机提示总是“只响半声”。3.6 打 log 与 DUT 模式确认改动真的生效改完功能不验证等于没改。AB1568 的调试通道主要有两条USB 口打 log以及 DUT 模式测 RF。USB 口通信要先在配置里把对应引脚切到调试功能PC 端用串口工具接上波特率对齐DUT 模式则是进入一个专门的测试固件状态配合仪器做射频指标测量。产测相关的指令体系分三层ATCMD是给人看的可读命令RACE 是二进制指令通道RAW 是更底层的裸指令。三者之间有转换关系——AT 命令最终会被转成 RACE 格式下发所以抓包看到的是二进制并不代表 AT 没生效。设置设备信息、读 NVKEY 也都有对应的 AT 命令产线夹具就是靠这套指令批量写参数的。排查“指令没反应”时先确认串口通不通再确认指令前缀和结束符对不对最后才怀疑固件里对应模块没编进去。4. ANC 模式切换从 key_action 到 NVKEY 滤波器落地4.1 按键怎么变成 key_action音频通路分 Line-in 和 BT 两种ANC 靠 FF 麦前馈和 FB 麦反馈实现FBC 是 FB 的补偿通话降噪则用说话主麦加 FF。硬件上典型配置是 4 个降噪麦、1 个主麦、2 个振动喇叭加 2 个声音喇叭。按键进_proc_key_event_group之后要先查行为映射表看这个物理动作被赋予了哪个key_action。/* customerized_key_config.c 片段示意 */ static const key_action_map_t g_key_action_map[] { /* 按键 ID 触发方式 行为 */ { KEY_ID_POWER, KEY_TRIPLE_CLICK, KEY_SWITCH_ANC_AND_PASSTHROUGH }, { KEY_ID_POWER, KEY_LONG_PRESS, KEY_ANC }, { KEY_ID_ANC, KEY_SINGLE_CLICK, KEY_BETWEEN_ANC_PASSTHROUGH }, };参数说明KEY_PASS_THROUGH只管通透模式开关KEY_ANC只管 ANC 开关KEY_SWITCH_ANC_AND_PASSTHROUGH是“全关 → 通透 → ANC”的三态循环KEY_BETWEEN_ANC_PASSTHROUGH只在通透和 ANC 之间来回切。这几个行为在 Config Tool 的 UI 里都有对应项改代码和改工具要一致否则用户按出来的效果和产品定义对不上。4.2 app_home_screen_process_anc_and_pass_through 的执行链路所有 ANC 相关按键行为最终都汇到一个函数里这个函数的职责很清晰算出目标模式和目标滤波器 ID然后调服务层使能。/* 所有 ANC / 通透按键行为的统一入口示意 */ static void app_home_screen_process_anc_and_pass_through(key_action_t action) { switch (action) { case KEY_SWITCH_ANC_AND_PASSTHROUGH: /* 三态循环全关 → 通透 → ANC → 全关 */ target_anc_type next_anc_type(target_anc_type); target_filter_id AUDIO_ANC_CONTROL_ANC_FILTER_DEFAULT; /* 枚举值 1 */ break; case KEY_PASS_THROUGH: target_anc_type ANC_TYPE_PASS_THROUGH; target_filter_id AUDIO_ANC_CONTROL_PASS_THRU_FILTER_DEFAULT; /* 枚举值 9 */ break; default: break; } /* 真正下发给音频服务参数是模式和滤波器 ID */ app_anc_service_enable(target_anc_type, target_filter_id); }参数说明target_anc_type决定 FF/FB 怎么组合target_filter_id决定用哪一组调好的参数。ANC 默认滤波器是值为 1 的枚举AUDIO_ANC_CONTROL_ANC_FILTER_DEFAULT通透默认滤波器是值为 9 的AUDIO_ANC_CONTROL_PASS_THRU_FILTER_DEFAULT这两个数字在抓 log 过滤anc_result时会反复出现记住能省不少时间。切模式时打 log 会看到三个模式循环切换如果 log 里模式和 ID 都对、实际听感没变化问题一定在 NVKEY 里没有对应参数而不是这段代码。4.3 滤波器与 NVKEYFilter1 到 Filter4 存了什么Config Tool 里有三类参数页ANC、FBC、Passthrough其中 ANC 页包含 FF 和 FB。落到 NVKEY 上是四组滤波器滤波器组内容说明Filter 1FF1(L/R) FB1(L/R) FBC1(L/R)第 1 套降噪参数Filter 2FF2(L/R) FB2(L/R) FBC2(L/R)第 2 套可按场景区分Filter 3FF3(L/R) FB3(L/R) FBC3(L/R)第 3 套Filter 4FF4(L/R) FB4(L/R) FBC4(L/R)第 4 套左右声道在工具里是上下重叠显示的两条曲线R 和 L 叠在一起看就是“同一组滤波器在两侧的响应”。ANC 参数的 keyID 集中在0xEE00到0xEEFF这一段这一段在工程自带的nvkey.xml里通常是没有的需要手动添加这也是后面固化参数时最容易漏的地方。4.4 Config Tool 实操Import NVR、Write NV、Reset声学工程师调完参数会给你一个 nvr 文件把它灌进耳机的流程是固定的打开 Config Tool连上工程download目录用Import NVR导入声学调好的 nvr 文件或直接在工具里调曲线点Write NV把 FF、FB、FBC、AiroThru 参数写进耳机点Reset复位耳机让参数生效三连击 Power 键循环切模式听降噪效果是否随模式变化确认无误后点保存参数落到download目录。第 5 步是判断“ANC 使能和模式切换是否成功”的唯一标准光看 log 不够必须耳朵听到差异。这一步过了才说明代码链路和参数链路都通了。4.5 把参数固化进代码Special_Export 与 nvkey.xml 合并工具里点 Save 只是把参数存进download目录重新编译工程时download会被覆盖参数就没了。要固化到代码需要用Special_Export把变更导出成文件。Save 方式生成 3 个文件Special_Export 方式生成 4 个文件两种方式里的 syskey 和 bin 内容是一致的区别在于导出粒度。拿 Special_Export 出来的syskey.xml把它里面变更的 key 逐条更新到工程里的几个nvkey.xml中——以ab1568_evk为例工程里通常有三个nvkey.xml分担不同 KeyID 段一个管 7 个 key一个管 4 个一个管剩下的全部。!-- 合并进 nvkey_anc.xml 的片段结构与字段以实际工程为准 -- Key ID0xE1E5 !-- 注意这里存的是原始字节不是直观的 dB 值 -- Value10FF 1400 D8DC D8DC/Value /Key Key ID0xEE10 !-- ANC Filter1 的 FF 参数0xEE00~0xEEFF 段在原始 xml 里往往不存在 -- Value.../Value /Key规则是syskey.xml里有的 keyIDnvkey.xml里存在就替换不存在就新增同时把filesystem.bin一并替换掉因为有一部分 NVKEY 是存在文件系统镜像里的不在 xml 里。改完 clean 再 build重新生成download用 Config Tool 打开确认 ANC 参数就是刚才那套 nvr 的配置才算固化成功。这套流程没有捷径漏一步就会回到“有提示音没降噪”的起点。4.6 NVKEY 数值怎么读字节序反转加补码工具里看到的增益是 dBxml 里存的是一串十六进制中间隔着一层解析。以0xE1E5 10FF 1400 D8DC D8DC为例原始字节反转后十进制处理含义10FFFF1065296 − 65536 −240−2.4 dBComponent Gain14000014200.2 dBD8DCDCD856536 − 65536 −9000另一路补偿参数两个动作先前后字节反转高字节在前再按有符号 16 位处理——数值大于 32767 时减去 65536 得到负数。0.1 dB 为一个单位所以 −240 对应 −2.4 dB。这一步看着枯燥但调参调出“声音发闷”“底噪变大”时十有八九是某一路增益的符号位搞错了手工验算一遍比反复烧录快得多。4.7 FOTA 更新 NVKEY 与 NVDM产品上市后要改 ANC 参数不能靠返厂烧录走 FOTA。FOTA 更新 NVKEY 的做法是用Special_Export导出变更文件在工程里生成对应的NVKEY_Setting.c这个文件里的函数负责把该文件中的 NVKEY 写进 NVDM 分区。升级包下发后设备重启进入 bootloaderbl_fota_process收包写入重启后新参数生效。注意 NVDM 是有版本和分区概念的升级包里带的 NVKEY 布局必须和当前固件匹配跨大版本直接刷会写花表现是开机后一堆配置变成默认值。做 FOTA 参数更新时我一般会先在样机上走一遍完整流程再用 AT 命令读回 NVKEY 逐条比对确认写进去的和预期一致才敢推批量。5. 避坑排查ANC 不生效、参数不落地的五个现场5.1 三连击有提示音但完全听不到降噪变化现象按键提示音响了log 里anc_result显示模式在循环切换耳机里一点降噪感都没有。原因ANC 服务被正常调用但 NVKEY 里没有滤波器参数服务层拿到的是全零或者默认值等于用一组空参数去做降噪。解决Config Tool 打开download目录看 ANC 页的 FF/FB/FBC 曲线是不是空的。空的话用Import NVR导入声学参数Write NV写入后Reset再按键试听。确认有效后再按 4.5 的流程固化进代码别只停在工具里。5.2 改了 mk 里的宏功能毫无变化现象MTK_ANC_ENABLE从 n 改成 y编译通过下载后按键还是没反应。原因三种可能——改错了 mk 文件工程实际用的是另一个feature_*.mk、没有 clean 导致增量编译没重编、或者宏名拼错MTK_ANC_ENABLE写成MTK_ANC_ENABLE_FBC之类。解决先跑./build.sh list确认当前工程对应的 mk 文件名再 grep 宏名确认拼写最后 clean 后完整 build。三步都过了还没生效就在#ifdef分支里加一句 log看这段代码到底有没有被编进去。5.3 工具里参数正常重新编译后又变回默认现象Config Tool 里调好的 ANC 参数clean build 之后打开download参数全没了。原因参数只存在download目录而这个目录是编译产物每次完整编译都会重新生成。工具里点 Save 不等于写进代码。解决必须走Special_Export→ 合并syskey.xml到工程的nvkey.xml→ 替换filesystem.bin→ clean build 这条链路。判断是否成功的方法很简单重新编译后打开download参数还在才算固化。5.4 参数固化了但降噪效果变差甚至啸叫现象参数确实写进去了切到 ANC 模式后声音发闷、底噪明显严重时啸叫。原因多数是增益解析错。手工改 xml 时把10FF当成正数 4351 写进去实际应该是 −2.4 dB或者只改了 FF 忘了改 FB/FBC 的对应项三路增益不匹配。解决用 4.6 的方法逐条验算大于 32767 的减 65536单位是 0.1 dB正负号别丢。改完先在样机上听再用仪器测一遍降噪深度曲线别只信工具里的数字。5.5 DUT 模式连不上、log 抓不到现象进了 DUT 模式PC 端串口工具没输出或者 AT 命令发下去没回应。原因USB 口的功能没切到调试通道、波特率不对、指令前缀或结束符不对也可能是固件里对应模块没编进去比如产测功能被宏关掉了。解决先确认 USB 引脚配置和调试功能是否使能再对齐波特率然后确认 AT 命令和 RACE 指令的区别——AT 命令最终会转成 RACE 二进制格式抓包看到二进制是正常的。最后确认产测相关宏在 mk 里是打开的ATCMD和 RAW 指令走的是不同解析分支发错了通道自然没回应。6. 二次开发加一档自定义 ANC 并让状态灯跟着说话改到这一步最实用的扩展是“多一档 ANC 模式 LED 状态指示”。产品经理常见的需求是不要只有开和关要“强降噪 / 轻降噪 / 通透”三档每档亮不同颜色。代码上不用动服务层只改两处循环逻辑和指示灯联动。/* 扩展 ANC 档位循环在原有三态基础上插入一个轻降噪档 */ static anc_type_t next_anc_type(anc_type_t cur) { switch (cur) { case ANC_TYPE_OFF: return ANC_TYPE_ANC_LIGHT; /* 新增的轻降噪 */ case ANC_TYPE_ANC_LIGHT: return ANC_TYPE_ANC_STRONG; case ANC_TYPE_ANC_STRONG: return ANC_TYPE_PASS_THROUGH; case ANC_TYPE_PASS_THROUGH: return ANC_TYPE_OFF; default: return ANC_TYPE_OFF; } } /* 按键处理里模式切换后同步刷新指示灯 */ target_anc_type next_anc_type(target_anc_type); target_filter_id (target_anc_type ANC_TYPE_PASS_THROUGH) ? AUDIO_ANC_CONTROL_PASS_THRU_FILTER_DEFAULT /* 9 */ : AUDIO_ANC_CONTROL_ANC_FILTER_DEFAULT; /* 1 */ app_anc_service_enable(target_anc_type, target_filter_id); /* 灯效跟随模式不同模式绑不同呼吸曲线 */ led_service_set_breath_pattern(led_scene_of(target_anc_type), g_breath_table, ARRAY_SIZE(g_breath_table));参数说明ANC_TYPE_ANC_LIGHT是新加的枚举需要同步在 ANC 服务层注册对应的滤波器组合否则服务层不认识这个类型会直接返回失败。滤波器 ID 先复用默认值 1等声学同事调出第二套参数后改成 Filter 2 对应的 ID同时把0xEE00段的 NVKEY 补进 xml。灯效场景枚举要和 LED 表一一对应漏一个就是某个档位亮错颜色。验证按这个顺序走先在 log 里过滤anc_result确认四次按键依次输出四个模式和对应的滤波器 ID再用 Config Tool 读回 NVKEY确认新档位参数真的在最后戴上去听四个档位听感要能区分开。三步有任何一步对不上就回到对应章节查——模式对但没声音是参数问题模式不对是 key_action 映射问题。还有一个常被忽略的点改了 ANC 档位数量KEY_SWITCH_ANC_AND_PASSTHROUGH这个行为名就有点名不副实了如果产线测试脚本或上位机工具里硬编码了“三态循环”的假设升级后测试项会误判。我一般会把档位数量和模式枚举抽成一张表按键、LED、产测、AT 命令都从这张表取改一处全链路生效。从那以后我每次改 NVKEY都强制走一遍Special_Export→ 合并 xml → 替换filesystem.bin→ clean → build → 工具读回 的完整流程再不敢点个 Save 就当完事。这套流程慢但它是唯一能让你在两周后回头改代码时不用重新推理参数去哪了的办法。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?