ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

AB1568蓝牙耳机ANC调试:从按键事件到NVR参数链路排查

AB1568蓝牙耳机ANC调试:从按键事件到NVR参数链路排查 简介面向Airoha洛达AB1568蓝牙耳机应用开发的PDF技术文档适合TWS耳机固件、嵌入式蓝牙开发及方案定制岗位的工程师与进阶学习者用于建立从硬件启动到应用层功能落地的完整知识框架。压缩包内仅含1个PDF文件大小约9.58MB内容按工程专题组织覆盖上电执行函数、mk文件宏定义、app活动行为、ANC模式切换、LED呼吸灯、充电参数监测、超时与事件管理、Airoha API、回调注册、自动关机、配对时间、开机提示电量、清除配对列表、链路重连、UI Shell、SideTone、音频路径、DUT测RF、硬件复位、PEQ及通话降噪等模块。已有374人学习可作为项目开发与调试时按问题检索的参考手册。其目录结构将芯片启动、事件驱动、降噪算法与低功耗配置串联起来便于读者对照接口文档和模块实现排查细节、补齐知识短板适合作为开发过程中的案头查询资料。1. 一个按键按下去为什么要在四个模块里绕一圈做 Airoha AB1568 蓝牙耳机开发最容易翻车的不是降噪算法而是一条按键事件到底走了几层才落到滤波器上。三连击 POWER 键提示音响了模式也切了耳朵里却一点变化都没有——这个现象背后压着三件事编译期的宏有没有把代码编进去、UI Shell 的活动有没有收到这条事件、NVR 里的参数有没有真的写进芯片。把这三层拆开看耳机固件的黑匣子就打开了一半剩下的都是细节。2. 上电第一行代码宏在哪个 mk 文件里决定哪段代码存在很多人调固件的第一反应是打开工程找main然后发现自己改的那段代码根本没有被编译。AB1568 的启动流程是两段式的上电后先跑apps目录下 bootloader 的main再由它把控制权交给apps下对应工程earbuds或headset的main最后由apps_inits把各种应用模块拉起来。这条流水线上每一段都可能被条件编译掐断而决定掐不掐的那个开关不在.c文件里在 GCC 目录下的某个 mk 文件里。理解这件事的价值在于它把你对“代码为什么没生效”的排查顺序从“翻代码”改成“先确认这段代码是否存在于最终镜像”。嵌入式里最贵的 bug 不是逻辑写错而是逻辑压根没进二进制。2.1 两段 main 的接力与条件编译的断点bootloader 的 main 负责的是很底层的事时钟、电源、存储介质自检然后判断要不要进 FOTA 流程。这个判断本身也是条件编译的——只有在 GCC 目录下的 mk 文件里定义了BL_FOTA_ENABLEbl_fota_process这个函数才会被编译成真正的代码。也就是说你把升级逻辑写得再漂亮宏没开那段函数体在预处理阶段就被整段抹掉了链接器连符号都看不见。常见的实现思路是mk 文件里的CFG_XXX y这类写法最终会被构建脚本翻译成编译命令行的-DXXX交给预处理器。所以“宏定义在哪里”这个问题的答案永远是“在本次编译实际引用的那个 mk 文件里”而不是“在工程目录里搜到的那个”。/* 这段代码是否存在于固件取决于 BL_FOTA_ENABLE 是否在本次编译的 mk 中被定义 */ #ifdef BL_FOTA_ENABLE /* 只有宏打开时这个函数体才会被编译器看到 */ bl_fota_process(); #endif上面这段没有参数需要解释重点在语义#ifdef之外是运行时判断#ifdef之内是编译期存在性判断。前者错了能在 log 里看到分支没走后者错了连 log 都不会有因为那段代码不存在。这也是为什么“加了打印却没输出”有时候根本不是串口问题。进到应用层之后apps_inits统一初始化各应用模块其中就包含所有活动行为的初始化。初始化顺序在这里是有讲究的谁先注册事件组谁就能更早收到开机阶段抛出的广播事件。顺序不对你会看到某些模块在开机头几百毫秒里“反应迟钝”而几百毫秒之后一切正常——这类现象很容易被当成玄学。2.2 build.sh list宏到底落在哪个 mk同一套 SDK 里通常并存多个板级工程和多个产品工程编译哪个工程、用哪个 mk由构建脚本的参数决定。先列出来看清楚比在目录里翻文件可靠得多。# 列出当前 SDK 支持的编译目标以及每个目标实际引用的 mk 文件 ./build.sh list # 输出里重点看两列工程名、对应的 feature_xx.mk # 当前头戴耳机基于 ab1565_ab1568_evk 开发板衍生 # 对应到 ab1568_evk 板级下的 headset_ref_desgin 工程 # 该工程编译时引用的是 feature_68_evk 这个 mk命令本身只有一句关键是读输出的方式不要只看工程名匹配要看板级前缀。ab1565_ab1568_evk是开发板名ab1568_evk是落到具体芯片型号的板级目录产品工程名headset_ref_desgin只是它的一个子项。三者对不上你改的 mk 就不是本次编译用的那个。确认之后打开feature_68_evk.mk能看到现成的宏定义也可以往里加新的。比如 ANC 相关的MTK_ANC_ENABLE在这个文件里已经是开启状态——这意味着 ANC 的按键处理分支是存在的问题不可能出在“没编进去”这一层。这个判断很省时间一次确认直接排除掉一整类可能性。2.3 增量编译与 mk 变更的冲突改 mk 之后不 clean 就 build是血泪经验级别的坑。构建系统对.c文件的依赖关系跟踪得很细但对 mk 文件的变更往往只做粗粒度判断甚至完全依赖时间戳。结果就是宏已经改了目标文件却没重新生成你烧进去的还是旧镜像。# 改过任何 mk 文件之后的标准动作 ./build.sh clean ./build.sh ab1568_evk headset_ref_desgin这两条命令的含义很直白但顺序不能省。clean清掉的是中间产物和最终镜像确保下一次编译完整展开所有 mk。省掉它的代价是几十分钟的迷惑你明明改了宏行为却一点没变于是开始怀疑芯片、怀疑工具链、怀疑人生。另外一个细节是宏的作用域。写在 mk 里的宏是全局的写在某个模块自己的 mk 片段里的宏只影响该模块。同一个宏名在两处出现且值不同最终生效的取决于链接顺序和包含顺序这种冲突不会有任何警告。所以新增宏之前先在feature_68_evk.mk里搜一遍名字比事后查行为异常便宜得多。3. UI Shell 事件框架一条事件为什么所有活动都能收到耳机固件里最像“操作系统”的部分就是这套 UI Shell。它把一个个界面抽象成活动activity每个活动有自己的事件组和处理函数。系统产生事件时不是点对点投递而是把事件抛出去所有活动都能监听到属于自己事件组的才处理。这个设计看着低效实际上解决了嵌入式 UI 里最麻烦的问题状态切换时的责任归属。3.1 活动栈ui_shell_start_activity 申请的那个 itemui_shell_start_activity做的事情是在活动栈里申请一个空闲 item把活动的描述结构挂上去。这个栈决定了“谁在前台”。app_preproc_activity_proc是主进程负责管理和调配其它子进程比如app_home_screen_idle_activity_proc这类具体界面。开机后系统会拉起很多不同的进程每个进程里有不同的事件组形成一张扁平的监听网。/* 活动的事件处理表事件组 ID 与处理函数的对应关系 */ static const ui_shell_event_handler_t home_screen_handlers[] { { KEY_EVENT_GROUP, app_home_screen_proc_key_event_group }, /* 按键类事件 */ { ANC_EVENT_GROUP, app_home_screen_proc_anc_event_group }, /* ANC 状态变化 */ { TIMER_EVENT_GROUP, app_home_screen_proc_timeout_event }, /* 超时类事件 */ }; /* 启动活动在活动堆里申请一个空闲 item并把处理表注册进去 */ ui_shell_start_activity(app_home_screen_idle_activity_proc, home_screen_handlers, ARRAY_SIZE(home_screen_handlers), false);上面两个调用的语义是先声明“我关心哪几类事件、分别交给谁处理”再声明“我现在进入活动栈”。第三个参数是处理表长度最后一个布尔量在不同 SDK 版本里可能是“是否独占前台”之类的开关入参顺序也常有调整理解成“注册范围 前台声明”这两件事就够了。真正要记住的是处理表在注册时就被固定下来活动运行期间不会再变。3.2 事件的三段式send、init_input_msg、remove事件的生命周期被拆成三个明确的动作。ui_shell_send_event负责投递携带事件组、事件 ID、payload 大小和优先级ui_shell_init_input_msg是接收侧的统一入口把底层消息队列里的事件取出来分派给活动ui_shell_remove_event则是后悔药——把还没被消费的事件从队列里摘掉。/* 投递一条按键事件不来自中断、优先级普通、属于按键事件组 */ ui_shell_send_event(false, /* 是否来自中断上下文 */ EVENT_PRIORITY_NORMAL, KEY_EVENT_GROUP, /* 事件组决定谁来处理 */ KEY_EVENT_ANC_SWITCH, /* 事件 ID具体是什么事 */ payload, /* 附带数据可为 NULL */ sizeof(payload), NULL, 0); /* 后悔药场景已经切换旧事件不该再被处理 */ ui_shell_remove_event(KEY_EVENT_GROUP, KEY_EVENT_ANC_SWITCH);参数里最关键的是事件组和事件 ID 的组合。事件组是路由依据事件 ID 是业务依据。同一个事件 ID 挂在不同事件组下会走到完全不同的处理函数这种“同名不同命”的情况在多人协作的工程里非常常见也是事件丢了的经典原因。false这个参数表示调用上下文不是中断如果你在中断里直接发事件并传true行为会更接近“延后到安全上下文处理”。传错的后果不是崩溃而是偶发的、难复现的事件丢失。3.3 广播式投递的代价与边界广播的好处是解耦新增一个活动不需要修改发送方。代价是每个活动都要对不属于自己的事件做一次快速丢弃而且“谁在前台谁处理”这条隐含规则没有被框架强制。当两个活动同时存在于栈上、又都注册了同一个事件组就会出现同一条按键被处理两次的现象——比如降噪模式连跳两格。ui_shell_remove_event的存在就是为了补这个洞在状态切换的瞬间把上一条还没被消费完的事件摘掉避免它在新的上下文里被执行。我一般会这么理解这套框架广播负责“送达”活动栈负责“当前谁该管”remove负责“别管过期的”。三者缺一个行为就会在某些边角场景里歪掉。判断一条事件有没有人处理最直接的办法是在ui_shell_init_input_msg的分派路径上加一行 log打印事件组和事件 ID。有投递记录但没有处理记录说明当前栈上没有活动注册该事件组两者都有但业务没反应问题就在处理函数内部跟框架无关。这条分界线划清楚排查效率会差好几倍。4. ANC 模式切换从 key_action 到 ANC 滤波器组ANC 是耳机固件里链路最长的一个功能它横跨按键、事件框架、服务层和声学参数四层。任何一层断掉用户听到的都是“没效果”但从代码上看每一层都“跑过了”。4.1 硬件通路FF、FB、FBC 和主麦各管什么音频通路分 Line-in 和 BT 两种输入。降噪侧用的是 FF 麦前馈朝外拾环境噪和 FB 麦反馈朝耳道拾残余噪FBC 是 FB 的补偿通路。整机上有 4 个麦克风用于降噪另有 1 个主麦负责说话拾音配 2 个振动喇叭和 2 个声音喇叭。分工很清楚ANC 降噪用 FF 和 FB通话降噪用 talk 麦加 FF。config tool 上有三个参数页ANC含 FF 与 FB、FBC、Passthrough通透。它们和用户感知的“模式”不是一一对应关系模式是给用户按的参数页是给声学工程师调的。一个 ANC 模式背后会同时用到 ANC 页和 FBC 页的参数通透模式背后是 Passthrough 页的参数。把这层关系理顺才看得懂后面滤波器编号的分配逻辑。4.2 key_action 的四种语义与映射文件按键事件进来后先进_proc_key_event_group处理。这条路径能不能走通前提是MTK_ANC_ENABLE已经打开——在feature_68_evk.mk里确认过是开的。所有 ANC 相关行为最终都收敛到app_home_screen_process_anc_and_pass_through这一个函数里靠key_action的值区分意图。/* key_action 的四种语义对应 config tool 里配置的按键行为 */ switch (key_action) { case KEY_ANC: /* 单纯 ANC 开 / 关不碰通透 */ target_anc_type (target_anc_type ANC_OFF) ? ANC_ON : ANC_OFF; break; case KEY_PASS_THROUGH: /* 单纯通透开 / 关 */ target_anc_type (target_anc_type PT_OFF) ? PT_ON : PT_OFF; break; case KEY_SWITCH_ANC_AND_PASSTHROUGH: /* 三段循环全关 - 通透 - ANC */ target_anc_type next_in_cycle(target_anc_type); break; case KEY_BETWEEN_ANC_PASSTHROUGH: /* 两段来回只在通透和 ANC 之间切 */ target_anc_type (target_anc_type ANC_ON) ? PT_ON : ANC_ON; break; } /* 模式和滤波器 ID 一起下发服务层据此选参数组 */ app_anc_service_enable(target_anc_type, target_filter_id);四个分支的差别不在代码量而在状态机的拓扑三段循环有三个稳定态两段切换只有两个。选错语义用户按三下会回到原点或者卡在通透出不来——这类“手感问题”最后都会被当成 bug 报上来。按键行为和key_action的绑定写在customerized_key_config.c里。这个文件是产品差异化的集中地不同客户的长按、双击、三连击语义都在这里分叉。改这里之前先确认MTK_ANC_ENABLE否则你会改了一个根本不会被编译进去的分支。4.3 app_anc_service_enable模式与滤波器枚举的对应服务层拿到target_anc_type和target_filter_id后去 NVR 里取对应的参数组下发给 DSP。滤波器用枚举表示AUDIO_ANC_CONTROL_ANC_FILTER_DEFAULT的值是 1AUDIO_ANC_CONTROL_PASS_THRU_FILTER_DEFAULT的值是 9。这两个数字不是随手写的它们对应 NVR 里参数块的编号。NVR 里的滤波器按四组组织每组包含左右耳各一套组内容说明Filter 1FF1(L/R) FB1(L/R) FBC1(L/R)第一组 ANC 参数Filter 2FF2(L/R) FB2(L/R) FBC2(L/R)第二组 ANC 参数Filter 3FF3(L/R) FB3(L/R) FBC3(L/R)第三组 ANC 参数Filter 4FF4(L/R) FB4(L/R) FBC4(L/R)第四组 ANC 参数把 tool 界面上的 L 和 R 叠在一起看四组结构的对称性就出来了。切换 ANC 模式时看 loganc_result里三个模式循环出现每次都会带上当前使用的 filter id。这个字段是排查“切了但没效果”的第一手证据id 变了说明链路通到了服务层没变说明卡在按键或状态机。4.4 NVR 参数从 config tool 到芯片的那一段三连击 POWER有提示音没有降噪效果——这是最典型的一类现象。原因往往极其朴素用 config tool 打开工程的download目录ANC 模式下的参数是空的。代码路径全对参数没写DSP 拿到的是一组零。修复流程是把声学调好的 ANC 参数nvr 文件导入工程或者直接在 tool 上调整然后先 Write NV 再 Reset把 FF、FB、FBC 和 AiroThru 参数写进耳机。之后循环三连击能听出不同模式的降噪差异才算真正打通。参数写进去之后还需要能读懂它。NVR 里的数值是高字节在前并且用有符号定点表示直接按十六进制读会得到离谱的结果。def parse_component_gain(word: str) - float: 解析 NVR 里的 component gain 字段返回 dB 值 # 芯片存储是高字节在前先按字节反转 swapped bytes.fromhex(word)[::-1].hex() raw int(swapped, 16) # 有符号 16 位超过 0x7FFF 说明是负数减 65536 还原 if raw 0x7FFF: raw - 0x10000 return raw / 100.0 # 单位是 0.1 dB换算成 dB # 以 0xE1E5 10FF 1400 D8DC D8DC 为例 print(parse_component_gain(10FF)) # -2.4 dB对应 component gain -240 print(parse_component_gain(1400)) # 0.2 dB对应 component gain 20关键在两步字节反转和有符号还原。10FF反转成FF10十进制是 65296减 65536 得 -240除以 100 就是 -2.4 dB。1400反转成0014十进制 20不用减直接是 0.2 dB。后面两段D8DC D8DC按同样的规则解读字段顺序照着 tool 里显示的顺序读就行。这套解析逻辑值得单独写成脚本的原因很实际调音阶段你会反复对比“tool 里显示的”和“芯片里存的”是否一致。手算一次两次还行改十几次参数之后必然出错而错的方向还挺隐蔽——正负号搞反降噪曲线就整条翻过来。5. 排查清单ANC 没效果、宏不生效、参数回滚的五个常见坑这一章的五条记录都来自实际调试现场共同点是代码看起来没问题现象却稳定复现。每条按现象、原因、解决三段写方便对号入座。5.1 有提示音、没降噪参数没写进芯片现象三连击 POWER 键提示音响了log 里anc_result也在三个模式之间循环但左右耳听感完全没变化。原因用 config tool 打开工程download目录ANC 模式下没有参数。代码路径通了服务层也执行了但下发给 DSP 的是一组全零系数。降噪算法在零系数下等价于直通所以“切了等于没切”。解决导入声学调好的 nvr 文件或者直接在 tool 上调整 ANC、FBC、Passthrough 三页参数然后 Write NV再 Reset。重置这一步不能省部分参数在初始化阶段才被读取不 Reset 就只能等下次上电。验证方式是循环三连击听感上能区分出不同模式的降噪强度差异。5.2 改了宏没生效改的不是本次编译的 mk现象在某个 mk 文件里加了宏定义重新编译烧录行为毫无变化或者链接时报某个符号未定义。原因工程里存在多个 mk 文件本次编译实际引用的是另一个。比如产品工程是headset_ref_desgin板级前缀是ab1568_evk最终落到feature_68_evk这个 mk而你改的是隔壁板级目录下的同名文件。解决先跑./build.sh list从输出里确认工程名与 mk 的对应关系再去改那个文件。改完必须 clean 再 build因为 mk 变更不一定触发目标文件重建。符号未定义这个报错反而是好事它至少告诉你“这段代码确实被条件编译切掉了”最怕的是行为静默改变连报错都没有。5.3 参数回滚只改了 download没改 XML 和 bin现象参数在 tool 里调好了也 Write NV 验证过效果。重新编译工程、烧录之后ANC 又回到旧参数。原因Write NV 写的是耳机里的 NVR 区不是工程代码。重新编译会用工程里的 XML 和filesystem.bin重新生成download目录把之前的改动覆盖掉。有一批 NV key 是存在filesystem.bin里的光改 XML 不换 bin参数只更新一半。解决用 Special_Export 导出变更到文件会得到 4 个文件用 Save 方式保存成 download 则是 3 个文件其中 syskey 和 bin 是一致的。把导出的syskey.xml里变更的 key 逐个更新到工程的几个nvkey.xml中——注意syskey.xml里有的 keyIDnvkey.xml里不一定有没有的要新增已有的要替换比如 ANC 参数涉及的0xEE00~0xEEFF段在nvkey.xml里就没有。同时替换filesystem.bin再 clean build。5.4 按键无响应事件发出去了没人接现象按键 log 有ui_shell_send_event也调了处理函数里的断点却从不命中。换个界面再按偶尔又能响应。原因事件是广播的但只有当前处于活动栈前台、且注册了对应事件组的活动才会处理。当前如果停在配对界面或产测界面home screen 的处理表根本没参与分派。另一种情况是事件组 ID 用错发到了另一个组名下。解决在ui_shell_init_input_msg的分派路径上打一行 log同时打印事件组和事件 ID对比活动注册表里的事件组。有投递无分派就是路由问题有分派无业务响应才是处理函数的问题。另外在状态切换的地方补ui_shell_remove_event把过期事件摘掉避免它在新上下文里被误执行。5.5 超时时间不准tick 单位和宏的换算现象自动关机、配对超时、链路丢失重连这些功能实际触发时间跟配置值对不上有时差好几倍。原因配置项的单位是毫秒底层定时器按 tick 计数中间有一次换算。有人在宏里直接填了毫秒数却没走换算宏或者在改 tick 频率之后忘了同步改配置值。解决把所有超时相关的宏集中看一遍确认单位是毫秒还是 tick换算系数跟当前 tick 频率是否匹配。验证方式是掐表设一个 30 秒的超时实际测一次触发点差一个数量级就是单位问题差几秒就是 tick 精度问题。自动关机还涉及关机前的提示音和 LED 状态收尾时间设置过短会在提示音播到一半时断电听感上像是“关机有杂音”。6. 参数固化还是 FOTA 下发NVKEY 的两条路怎么选ANC 参数最终要落到耳机里有两条路编译进固件或者通过 FOTA 更新 NV key。两者的取舍不在技术难度而在迭代节奏。维度固化到 XML/binFOTA 更新 NVKEY生效时机整包烧录后生效单独下发不用重烧固件产线适配量产程序直接编译一致性好需要额外的下发工具链迭代速度慢每次改参数都要 clean build快适合调音阶段反复试回滚成本重烧整包再下发一次旧参数调音阶段我倾向于走 FOTA/Write NV 这条路改一次参数听一次效果不用等编译。定版之后必须固化到 XML 和filesystem.bin让产线拿到的每一台耳机参数完全一致——参数固化到代码里的价值不是“省事”而是“可复现”。验证是否真的固化了方法很朴素clean build 生成新的download用 config tool 打开看 ANC 参数是不是刚才导入的那一版如果不是说明 XML 没更新全或者filesystem.bin没替换。再狠一点的验证是把耳机擦除后重新烧录读回 NV key 跟预期值逐字节比对比肉耳听降噪靠谱得多。踩过一次坑之后我就养成了习惯每次改 NV 参数先问自己一句“这是要验证还是要定版”验证走 Write NV定版走 Special_Export XML/bin 替换两条路绝不混用。混用的后果是某天发现仓库里的参数和产线耳机对不上而没人记得是哪次改的。从那以后我每次动 NV key 都强制走一遍“导出、更新 XML、替换 bin、clean build、tool 复核”这五步一步不跳。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表