简介:围绕Airoha洛达AB1568蓝牙耳机芯片的工程开发与定制场景,这份PDF技术资料面向嵌入式开发工程师,系统覆盖从底层硬件启动到应用层事件处理的完整链路,内容包括上电后执行的初始化函数、代码宏定义所处的mk文件、应用活动行为管理、ANC主动降噪模式切换、LED呼吸灯样式修改、充电参数监测、超时时间设置、Airoha API函数使用参考、回调函数注册与调用等二十余个专题。包体为单个PDF文档,容量9.58MB,以耳机工程分析为主线,目录结构清晰,每个专题均针对实际开发中的常见问题展开,可直接用于AB1568功能定制、音频路径切换、DUT射频测试、PEQ音效调整与通话降噪算法匹配等场景。目前该资源已有372人学习,适合具备一定嵌入式开发基础的蓝牙耳机方案设计、测试与维护人员作为技术手册参考。
1. AB1568 蓝牙耳机应用开发到底在做什么:从一颗芯片到能出货的耳机
拿到一块络达(Airoha)AB1568 的开发板,很多人的第一反应是“这不就是个蓝牙耳机主控吗,接上喇叭和麦克风不就能出声了”。真上手才发现,AB1568 不是那种上电就能跑的模组,而是一颗需要你自己写应用层的蓝牙音频 SoC。你要处理的不只是“初始化完放音乐”,还包括按键事件怎么分发、左右耳 TWS 怎么配对同步、睡眠唤醒怎么不掉蓝牙、音频参数怎么配才能过产线测试。这套开发工作和写单片机固件很像,但多了一层蓝牙协议栈和音频链路的约束。
这篇文章想解决的,就是“从裸板到能稳定出货”这条路上最常被问的问题:开发环境怎么搭、应用代码从哪写起、TWS 和音频参数怎么调、哪些坑会让你的板子“白天好好的,晚上就失联”。适合正在做 TWS 耳机、蓝牙音频模块,或者从硬件转软件、第一次接触络达 SDK 的开发者。我用过的 SDK 版本不同,具体 API 以你拿到的版本为准,但思路和排查路径是通用的。
2. 搭建 AB1568 最小可烧录环境:工具链、工程骨架与第一次跑通
AB1568 的开发不是打开一个 IDE 点编译那么简单的,但也没有想象中那么玄。它本质上是 ARM 内核 + 蓝牙协议栈 + 音频 DSP 的集成方案,厂商交付的 SDK 里已经帮你把协议栈和驱动封装好了,你的工作集中在应用层。这一章先把环境讲清楚,因为后面所有调试都建立在一个“能编译、能烧录、能看日志”的基础上。
2.1 开发三板斧:编译器、烧录器与 SDK 怎么配
常见做法是,SDK 解压后自带一套工具链配置脚本,你只需要装好 ARM 交叉编译器和烧录工具。AB1568 的烧录通常走 SWD 或者串口,板子上一般预留了烧录排针。我一般会先确认三件事:编译工具链的版本对不对、烧录器驱动有没有装、SDK 里的芯片型号宏有没有选对。
# 1. 解压 SDK 并设置环境变量 tar -xzf ab1568_sdk_vX.X.tar.gz cd ab1568_sdk_vX.X export AB1568_SDK_ROOT=$(pwd) # 2. 确认交叉编译器路径 arm-none-eabi-gcc --version # 3. 编译一个最小的 hello 工程,验证工具链可用 cd project/hello_world make clean && make编译完成之后,你会得到一个.bin或者.elf文件。如果make报找不到arm-none-eabi-gcc,说明交叉编译器没进 PATH,最常见的原因是装了新版 GCC 但环境变量没刷新,或者 SDK 里写死了旧的编译器路径。另一个高频报错是链接时 Flash 溢出,AB1568 的可用 Flash 是固定的,SDK 默认开了很多你用不到的功能,第一次编译就爆容量很正常,后面关功能就好。
烧录这一步,不同公版方案差异比较大。有的板子用 J-Link 连 SWD,有的用串口工具配合烧录脚本。我习惯先用厂商自带的烧录脚本烧一次出厂固件,确认硬件基本正常,再烧自己的固件,这样出了问题能分清是硬件还是软件。
2.2 最小工程骨架:main、事件循环与 profile 注册
AB1568 的应用层跑在一个实时操作系统上,所以程序不是简单的while(1)轮询,而是创建任务、注册消息处理器、等待事件到达。最小工程骨架通常长这样:硬件初始化 → 协议栈初始化 → 注册蓝牙服务(HFP/A2DP/AVRCP)→ 创建应用任务 → 进入事件循环。
#include "ab1568_app.h" #include "bt_service.h" #include "audio_path.h" /* 硬件初始化:GPIO、电源、时钟 */ static void board_init(void) { gpio_init(); i2c_init(400000); /* I2C 速率,实际以板载 codec 为准 */ power_manage_init(); } /* 蓝牙事件回调:连接、断开、配对请求都会走到这里 */ static void bt_event_handler(uint32_t event, void *param) { switch (event) { case BT_EV_CONNECTED: audio_path_open(AUDIO_OUT_SPEAKER); break; case BT_EV_DISCONNECTED: audio_path_close(); break; case BT_EV_PAIRING: enter_pairing_mode(120); /* 进入配对模式,120 秒超时 */ break; default: break; } } /* 应用主函数入口 */ int app_main(void) { board_init(); bt_service_init(bt_event_handler); bt_service_register_profile(PROFILE_HFP | PROFILE_A2DP | PROFILE_AVRCP); app_task_create(); return 0; }这段代码是典型的络达应用框架。注意bt_event_handler是回调函数,不是主动调用,蓝牙协议栈在收到空中事件后会帮你把它投递到这个回调里。bt_service_init的参数是函数指针,注册时不要传局部函数或者有依赖关系的对象。
bt_service_register_profile决定了这台耳机能做哪些事情:HFP 是打电话,A2DP 是放音乐,AVRCP 是音量控制和歌曲切换。第一次开发建议全开,跑通了再按产品需求裁剪。注意,裁剪 profile 不只是省 Flash,还会影响配对流程,比如只保留 A2DP 的耳机会在来电时没有铃声通路。
2.3 第一次编译烧录:命令与常见报错
第一次编译烧录,我建议不要一上来就改代码,先跑一个 SDK 自带的 sample,比如headset_demo。这个 demo 已经把按键、LED、音频都串好了,烧进去如果能出声、能配对,就说明你的环境没问题,后面改代码心里有底。
cd project/headset_demo make BOARD=ab1568_evb ls build/ab1568_evb/*.bin # 烧录:以 J-Link 为例 JLinkExe -device AB1568 -if SWD -speed 4000 -CommanderScript flash.jlink烧录后如果没有任何反应,先别急着查代码。用万用表确认核心电压有没有起来,复位引脚电平对不对,再看烧录器能不能读到芯片 ID。很多时候烧录失败不是软件问题,是板子上的 boot 模式引脚被拉到了错误电平。AB1568 这类芯片一般有专门的启动配置引脚,公版上会用跳线帽或者拨码开关选择,烧录模式和应用启动模式不一样,这个细节一定要看原理图确认。
日志是另一条命脉。SDK 的日志走 UART,波特率常见的是 921600 或 115200,版本不同差异很大。日志不输出时先试几个波特率,不要一上来就怀疑芯片坏了。如果日志乱码,检查 UART 的时钟配置和电平是否匹配,USB 转串口模块建议用 3.3V 电平的,5V 电平模块在部分板子上会拉不低 TX 导致乱码。
3. AB1568 应用层主路径:事件分发、按键控制与蓝牙状态机联动
环境通了,接下来就是真正的应用开发主战场。一个蓝牙耳机最基本的行为是:按键开机 → 进入配对 → 手机连接 → 播放音频 → 断开后回连。每一步都是“输入事件 → 状态流转 → 蓝牙动作”的链路。这一章把这条链路拆开讲。
3.1 事件分发机制:消息队列与主循环怎么流
AB1568 的应用层是消息驱动的。按键扫描、充电检测、蓝牙回调、音频事件都会转化为消息,投递到同一个队列,由应用任务统一处理。这样做的好处是避免了多线程并发访问共享资源的问题,坏处是如果你在某个事件处理里做了耗时操作,整个系统都会卡住。
typedef struct { uint32_t msg_id; uint32_t param; } app_msg_t; static void app_task_entry(void *arg) { app_msg_t msg; while (1) { /* 阻塞等待消息,超时时间 100ms,用于处理低优先级任务 */ if (xQueueReceive(app_msg_queue, &msg, 100) == pdPASS) { switch (msg.msg_id) { case MSG_BTN_CLICK: handle_btn_click(msg.param); break; case MSG_BT_CONNECTED: handle_bt_connected(); break; case MSG_CHARGER_IN: handle_charger_in(); break; default: break; } } /* 轮询型任务放这里,比如 LED 闪烁状态刷新 */ led_tick(); } }xQueueReceive的阻塞时间不是随便填的。填portMAX_DELAY会让 LED 这类轮询任务饿死,填 0 则会让 CPU 空转。100ms 是我常用的折中值,既能及时响应消息,又能保证 LED 之类的周期任务不掉拍。注意这里的 param 要注意生命周期,如果传的是指针,要确保指向的内存不被别的任务释放,否则就是野指针。
按键事件的处理有一个容易犯的错:在按键回调里直接做蓝牙配对操作。按键回调运行在中断上下文,里面有耗时操作会直接卡死音频。正确做法是按键回调只负责发送消息,实际逻辑放到应用任务里。这个区分一开始就要养成习惯。
3.2 按键控制与配对流程:代码逻辑与参数说明
配对流程是蓝牙耳机开发里最容易翻车的环节。产品需求一般是“长按 3 秒进入配对,配对超时 2 分钟后退出”。看起来简单,但涉及到一个状态转换:待机 → 可发现 → 配对中 → 已连接,每个状态都要处理超时和用户干预。
typedef enum { PAIR_IDLE, PAIR_DISCOVERABLE, PAIR_CONNECTING, PAIR_CONNECTED } pair_state_t; static pair_state_t pair_state = PAIR_IDLE; static void enter_pairing_mode(uint32_t timeout_ms) { bt_service_set_discoverable(1); /* 打开广播和可发现 */ bt_service_set_connectable(1); pair_state = PAIR_DISCOVERABLE; /* 启动超时定时器,超时后退出配对 */ app_timer_start(PAIR_TIMEOUT, timeout_ms); } static void exit_pairing_mode(void) { bt_service_set_discoverable(0); pair_state = PAIR_IDLE; } void handle_btn_click(uint32_t click_count) { if (click_count == 1) { /* 单击:播放/暂停 */ bt_service_toggle_play(); } else if (click_count == 2) { /* 双击:下一曲 */ bt_service_next_track(); } else if (click_count == 3) { /* 长按 3 秒:进入配对 */ enter_pairing_mode(120000); } }配对超时定时器的处理要特别注意。当手机已经连接时,定时器超时回调不应该退出配对模式,因为此时已经是PAIR_CONNECTED状态。很多开发者只判断了“超时了就退出配对”,把已连接的用户也踢出去了,体验就是“听歌听着突然断连”。正确逻辑是超时回调里先判断当前状态,只有PAIR_DISCOVERABLE状态才执行退出。
还有一个隐蔽问题:长按 3 秒进入配对的计时。如果用户在 3 秒内松手,会被识别成单击;3 秒后松手,进入配对。这个阈值要用带防抖的扫描逻辑,不能直接在中断里数时间。一般做法是按下启动定时器,1.5 秒触发一次“长按中”事件,3 秒触发“进入配对”,提前松手就取消定时器。两个阈值留一点差值,避免临界区域误触发。
3.3 状态机联动:连接、断开与回连的参数配置
蓝牙耳机的回连是用户感知最强的功能之一。“拿出耳机戴上,自动连上手机”是基本预期。回连逻辑看起来简单:开机后扫描之前配对过的设备,找到了就连。但实现的细节都在参数里。
void handle_power_on(void) { /* 回连尝试次数:3 次 */ bt_service_reconnect(3, 5000); /* 回连期间同时保持可广播,否则用户手动配对会冲突 */ bt_service_set_discoverable(1); bt_service_set_connectable(1); } void handle_disconnected(void) { /* 意外断开后立即尝试回连,最多 5 次,间隔 2 秒 */ if (disconnect_reason == REASON_LINK_LOSS) { bt_service_reconnect(5, 2000); } else { /* 用户主动断开,不做自动回连 */ enter_pairing_mode(60000); } }disconnect_reason的判断是回连逻辑的核心。蓝牙断开的原因分两类:链路丢失(走远了、干扰)和用户主动断开(手机上点了解除配对)。这两类的产品预期完全不同。链路丢失要快速回连,用户主动断开则应该进入可配对状态而不是反复尝试。如果不区分原因,会出现“手机端取消配对后,耳机还在疯狂回连”的尴尬。
回连参数里还有一个容易忽略的:回连期间的广播策略。很多产品在做自动回连时不广播,导致用户想手动连接而找不到设备。正确做法是回连和广播同时开着,先让用户能连上,失败了再进入配对模式。代价是广播会稍微增加功耗,但这个功耗换体验是值得的。
4. AB1568 音频链路与 TWS 同步机制:数据流路径与关键参数
蓝牙耳机最终是拿来听声音的,音频链路和 TWS 同步是 AB1568 开发里“技术含量最高”的部分。这一章不展开去写 DSP 算法,而是把数据流和参数讲清楚,让你知道音频是从哪进哪出的、TWS 两个耳机的数据怎么保持一致。
4.1 音频数据路径:A2DP 解码到扬声器的完整链路
在 AB1568 上,A2DP 音频从手机到达耳机,要经过:射频接收 → 协议栈解码 → PCM 缓冲 → 音频 DSP(EQ、降噪)→ DAC → 功放 → 扬声器。SDK 一般把这条链路封装成了audio_path_open/audio_path_close的接口,你要关心的是中间的配置参数,而不是每个环节的实现。
typedef struct { uint32_t sample_rate; /* 采样率:44100 或 48000 */ uint32_t bit_width; /* 位深:16 或 24 */ uint32_t channel_num; /* 通道数:1(单耳)或 2(TWS 双耳) */ uint32_t dac_gain; /* DAC 增益,范围 0~100 */ uint32_t buffer_ms; /* 音频缓冲时长,单位 ms,建议 40~60 */ } audio_config_t; static void audio_init(void) { audio_config_t cfg = { .sample_rate = 48000, .bit_width = 16, .channel_num = 2, .dac_gain = 80, .buffer_ms = 50, }; audio_path_configure(&cfg); audio_path_open(AUDIO_OUT_SPEAKER); }采样率的选择有几个原则。如果耳机只用来听歌,44.1kHz 是主流,因为大部分音乐本身就是 44.1kHz。如果要兼顾通话或者游戏模式,48kHz 更常见,因为 HFP 宽带语音采样率是 16kHz,做重采样到 48kHz 时对齐更方便。TWS 双耳播放时建议用 48kHz,时钟恢复更稳定。
buffer_ms这个参数是音频卡顿和延迟之间的权衡。缓冲越大,抗干扰能力越强,但延迟越高(看视频口型对不上)。50ms 是我常用的起点,如果测试发现卡顿,优先往上调而不是去调射频参数。注意,这个参数和 TWS 的重传窗口有关联,改了一边另一边也要跟着检查。
4.2 TWS 左右耳同步:时钟漂移与主从切换机制
TWS 模式下,手机只连接主耳,主耳再把音频通过私有协议转发给从耳。左右耳能同步的关键在于两边用同一个时钟基准。AB1568 的做法是主耳把自己恢复出来的音频时钟通过 TWS 链路发给从耳,从耳跟着这个时钟播放。
typedef struct { uint32_t drift_compensation_en; /* 时钟漂移补偿开关,默认 1 */ uint32_t max_drift_ppm; /* 最大容忍漂移,常见 20~50 ppm */ uint32_t resync_threshold_ms; /* 超过该差值触发同步校准,常见 5~10ms */ uint32_t buffer_size_ms; /* TWS 播放缓冲,常见 80~120ms */ } tws_sync_config_t; static void tws_sync_init(void) { tws_sync_config_t cfg = { .drift_compensation_en = 1, .max_drift_ppm = 30, .resync_threshold_ms = 8, .buffer_size_ms = 100, }; tws_sync_configure(&cfg); }时钟漂移是 TWS 方案里最“玄学”的问题。两颗芯片的晶振不可能完全一致,长时间播放后左右耳会逐渐不同步,听起来就是“声音左右漂移”。max_drift_ppm设置得太小,容易频繁触发校准,导致音频卡顿;设置得太大,声音漂移严重。30ppm 是常见的中间值,如果你的板子用的晶振精度较差,需要放宽到 50ppm,或者直接换 TCXO。
resync_threshold_ms是校准的触发阈值。当左右耳播放位置差超过这个值,主耳会发一次同步命令,从耳快速追帧。这个值设置成 8ms 左右,人耳基本感知不到校准动作。如果设置成 2ms 这种小值,会频繁触发校准,表现为“咔哒咔哒”的杂音——这就是为什么有些 TWS 耳机听歌时偶尔有杂音,不是喇叭问题,是同步参数不对。
主从切换是另一个大坑。当主耳没电或者被放进充电仓,从耳要能接管主耳角色。切换过程要做到两边不断音。这块 SDK 一般做了处理,但你要保证切换期间不调用audio_path_close,否则切换完会有一段静音。正确做法是切换期间只切换射频链路,音频保持打开。
4.3 音频参数配置表:不同场景下的推荐值
| 参数 | 听歌模式 | 通话模式 | 游戏模式 | 备注 |
|---|---|---|---|---|
| 采样率 | 44.1kHz | 16kHz(HFP) | 48kHz | 通话采样率由 HFP 决定,不可随意改 |
| 音频缓冲 | 50ms | 30ms | 20ms | 游戏模式优先低延迟 |
| TWS 缓冲 | 100ms | 80ms | 60ms | 取决于干扰环境 |
| DAC 增益 | 80 | 70 | 80 | 通话时适当降低,减少回声 |
| 重传窗口 | 3 包 | 2 包 | 4 包 | 干扰大时增加,延迟会增加 |
这张表的数值是我调试多个项目后的经验值,不是 SDK 默认值,拿来当起点是够用的。每个产品对“卡顿”和“延迟”的容忍度不一样,听歌为主的耳机会把缓冲调大保音质,游戏耳机则会把缓冲砍到很低。
一个需要特别注意的点是:重传窗口增大不一定会降低卡顿率,因为它同时还依赖射频灵敏度。如果你的天线匹配不好,一味调大重传窗口只会让功耗上升,卡顿依旧。这时候要回头查天线净空区、匹配调试,而不是继续堆软件参数。软件参数能掩盖一部分硬件问题,但掩盖不了全部。
5. AB1568 开发避坑指南:日志、掉电与时序这三类高频问题
写 AB1568 应用,前两周基本都在跟“现象”作斗争:屏不亮、连不上、声音断、莫名重启。这一章把我在多个项目里遇到的高频问题按“现象 → 原因 → 解决”的格式整理出来,多数问题不在蓝牙协议本身,而在基础工程素养。
5.1 日志不打印或者打印乱码
现象:板子跑起来了,蓝牙也能连上,但串口终端上什么都没有,或者全是乱码。
原因:95% 的情况是波特率不对,或者是 UART 引脚接错了。AB1568 的日志波特率在不同 SDK 版本里差异很大,有的默认 921600,有的默认 115200,还有的默认 1.5M。更隐蔽的是,有些板子把日志引脚和烧录引脚复用,你烧录时用的排针和日志输出用的排针不是同一个。
解决:先看 SDK 的配置文件里LOG_BAUDRATE定义的是多少,再用对应的波特率连接。确认引脚时不要只看丝印,去原理图里找日志 UART 的实际引脚号。如果确认接线和波特率都没问题,尝试用 3.3V 电平的 USB 转串口模块替换原来的,部分 5V 模块在高速波特率下信号质量差,会出现乱码。
5.2 睡眠掉电后蓝牙失联,按键唤醒不生效
现象:耳机静止一段时间后进入休眠,再按按键没反应,要插一下充电线才能复活,或者需要重新配对。
原因:睡眠配置里,GPIO 唤醒源没有注册成功。AB1568 的低功耗模式会把大部分外设的时钟关掉,按键对应的 GPIO 如果没配置成唤醒源,按下去只会改电平,不会触发系统恢复。另一个常见原因是按键扫描用的 ADC 检测而不是 GPIO 检测,ADC 在睡眠时是掉电的,当然唤不醒。
解决:在进入睡眠前,明确配置按键 GPIO 为唤醒中断源。
static void enter_sleep_if_idle(void) { /* 检查无连接且无操作 60 秒 */ if (system_idle_ms > 60000) { gpio_wakeup_enable(BTN_GPIO, WAKEUP_LEVEL_LOW); power_manage_sleep(); } }gpio_wakeup_enable应该放在power_manage_sleep()之前调用,并且 GPIO 要配置为上拉输入、下降沿唤醒。注意,按键的防抖电容会影响唤醒响应速度,如果电容太大(比如 100nF),按键唤醒时电平变化变慢,系统可能误判成干扰而再次睡眠。这种情况下把电容改到 10nF 以下,或者改在软件里做延时确认。
5.3 TWS 左右耳音频不同步,声音发飘
现象:放歌时左右耳声音位置不稳定,能明显感觉到声音在左右移动,或者偶尔一边耳机会有轻微回声感。
原因:时钟漂移补偿参数设置过松,resync_threshold_ms设得太大,导致差值积累到人耳可感知的程度才校准。也有可能是从耳的本地晶振精度差,正常 20ppm 的晶振漂到 50ppm。
解决:先把resync_threshold_ms调到 5ms,看是否有改善。如果改善不明显,把max_drift_ppm从 30 调高到 50,让补偿算法更积极。如果两个参数调完依旧漂移,就要怀疑晶振本身了,用示波器测两边的时钟频率,差值如果超过 30ppm 就是硬件问题了,软件只能缓解不能根治。
5.4 烧录后不开机,但烧录时报“连接成功”
现象:固件烧录成功,烧录器也没有报错,但断电重启后板上没有反应,电流只有几毫安。
原因:最常见的是 boot 模式没有切回应用启动模式。很多板子有 BOOT 引脚,烧录时拉高进入烧录模式,烧完要拉低才能从 Flash 启动。如果忘了拉回来,芯片每次上电都进入烧录模式等待命令,自然开不了机。另一种可能是烧录地址不对,SDK 的链接脚本有默认地址,手动用第三方工具烧录时选了错误的偏移。
解决:先对照原理图检查 BOOT 引脚电平。确认是应用启动模式后,用串口看启动日志,正常启动会有版本号、初始化信息。如果日志显示进到了烧录模式,检查引脚是否通过下拉电阻稳定在了低电平。烧录地址的问题,请直接用 SDK 自带的烧录脚本,不要手动指定地址,版本不匹配时烧进去了也是坏的。
6. 进阶:把 AB1568 开发做成可复用的量产工程
开发板跑通只是第一步,真正的工作量在“从样机到量产”。这一章不讲具体 API,讲工程习惯——你的代码怎么组织,才能让自己三个月后还能看懂,也能让同事接手时不骂人。
先做配置分离。把板级差异、音频参数、配对参数、日志开关全部放进一个配置文件里,用宏开关控制。我一般会建一个board_config.h,产品版本和开发版本共用一套逻辑代码,只在配置头文件里区分。改产品参数时不用动逻辑,出问题也好定位是配置问题还是代码问题。
/* board_config.h */ #define BOARD_DEV_VERSION 1 /* 开发板:开日志,开全部调试指令 */ #define BOARD_PROD_VERSION 2 /* 量产板:关日志,开产测模式 */ #define CURRENT_BOARD_VERSION BOARD_PROD_VERSION #if CURRENT_BOARD_VERSION == BOARD_DEV_VERSION #define LOG_LEVEL 4 /* 全量日志 */ #define ENABLE_CMD_DEBUG 1 #else #define LOG_LEVEL 1 /* 仅错误日志 */ #define ENABLE_CMD_DEBUG 0 #endif日志分级是量产工程里容易被忽视的环节。开发时全量打印没问题,量产后日志全开会拖慢系统、增加功耗。我会把日志分成错误、警告、信息、调试四级,量产版只留错误和警告。底层代码的日志级别不要写死,统一读LOG_LEVEL宏,这样出问题时可以在产线固件里临时开日志,定位完再关掉。
产测脚本是量产工程的下一个重点。每台出厂耳机至少要测三件事:蓝牙回连是否正常、音频通路是否没有杂音、按键是否响应。这些测试用人工点按效率太低,我在产线上用的是串口命令通道加脚本自动验证的思路。测试工具通过串口发送指令,固件执行并返回结果,自动化判断通过还是不通过。
# 产测脚本:自动化验证回连与音频通路 import serial import time ser = serial.Serial("COM10", 921600, timeout=2) def send_cmd(cmd: bytes) -> bytes: ser.write(cmd + b"\r\n") time.sleep(0.3) return ser.read_all() # 1. 进入产测模式 resp = send_cmd(b"factory_test start") assert b"OK" in resp, f"failed to start: {resp}" # 2. 模拟配对源,验证回连 resp = send_cmd(b"bt connect_test") assert b"CONNECTED" in resp, f"connect fail: {resp}" # 3. 播放 1kHz 正弦波,采集麦克风或 Codec 回读,判断失真 resp = send_cmd(b"audio loopback 1000") assert b"THD_OK" in resp, f"audio fail: {resp}"产测脚本里的关键点是每一步要有超时和失败重试机制。不能假设产线上每一台设备都完美,串口线接触不良、设备供电不稳都可能导致误判。我在脚本里会对每条指令做三次重试,三次都失败才判 NG,这样能把误判率控制在 5% 以下。另一个细节是产测完成后要恢复设备到用户模式,不能把测试指令留给用户。
关于编译脚本,我最后做一个建议:把编译、烧录、产测三个步骤串成一个总控脚本,开发人员一键跑完。这样无论谁接手,都能在十分钟内从拿到 SDK 到跑通产测。集成这一步的投入不大,收益却很实在。产线找人盯测试和找人盯编译是两种完全不同的效率,前者是保姆式管理,后者才是工程化交付。
我自己的习惯是,每次修改完 TWS 参数或者音频链路,先跑一轮“断开回连 + 睡眠唤醒 + 左右耳切换”的脚本回归,再决定要不要收工。这套脚本帮我挡掉过至少三次“环境配置改了但没人记得”的翻车。蓝牙音频开发的坑大多不深,但都很隐蔽,能自动化验证的地方就不要靠手感。希望这篇梳理能在你做 AB1568 的时候帮你少踩几个坑,也祝你的板子一次就能稳定出货。
本文还有配套的精品资源,点击获取