一副 TWS 耳机,为什么既要用经典蓝牙,又要用 BLE

做 TWS 耳机时,经常会同时看到经典蓝牙和 BLE。 手机已经通过蓝牙连接耳机,可以播放音乐、接打电话,为什么配套 App 还要再连接一次 BLE?我刚开始接触时,也没有认真区分这两部分,只知道它们都叫蓝牙。 后来做 App 交互功能多了,我才慢慢理解:在目前常见的 TWS 产品里,它们处理的事情并不一样。经典蓝牙主要负责音乐、通话和媒体控制,BLE 更像一条给 App 使用的数据通道。 一、经典蓝牙主要负责耳机的基本功能 平时用耳机听歌、调节音量和接打电话,主要涉及经典蓝牙中的几个 Profile: Profile 在耳机中的主要作用 A2DP 传输音乐音频 AVRCP 播放、暂停、切歌和音量控制 HFP 来电、接听、挂断等通话控制 通话时的语音通常还会通过 SCO/eSCO 链路传输。 这些功能决定了一副耳机能不能正常听歌和通话。以我目前接触到的应用开发来说,平时主要处理 SDK 上报的 A2DP、AVRCP 和 HFP 事件,再根据当前状态执行对应的产品逻辑。 二、BLE 主要负责耳机与 App 交互 配套 App 需要做的事情和播放音乐不太一样。它传输的通常不是连续音频,而是一些控制命令和状态数据。 我做过的项目里,BLE 主要用于这些功能: 修改 EQ; 切换 ANC 模式; 读取耳机电量; OTA 升级; 查找耳机。 例如用户在 App 上切换 ANC,App 会通过 BLE 把命令发给耳机;耳机执行以后,还可能需要把最新状态返回给 App。读取电量也是类似的过程,只是数据方向和触发时机可能不同。 我接触过 GATT 的 Service、Characteristic、读写和通知,不过实际开发时,大部分数据收发还是通过 SDK 封装好的接口完成。刚开始看这些名词时,我觉得它们比较抽象。结合 TWS 耳机的功能,可以先这样理解: ...

做耳机开发,需要掌握哪些 C 语言知识

TWS 应用层的大部分工作,最后还是要落到 C 代码上。这篇整理开发中经常用到、也容易混淆的内容,方便需要时快速回顾。 一、数据类型与类型转换 嵌入式代码经常使用 uint8_t、uint16_t 和 uint32_t,因为它们的位宽是明确的。 int 占多少字节,由芯片架构和编译器决定。在常见的 32 位平台上,int 通常占 4 字节;在部分嵌入式平台上,也可能只占 2 字节。可以使用 sizeof 查看当前平台上的实际大小: printf("int size: %u\n", (unsigned int)sizeof(int)); sizeof 返回的是 size_t 类型。这里先把结果转换成 unsigned int,再使用 %u 输出,适合常见的嵌入式 printf。 普通计算和循环计数可以使用 int。通信协议、Flash 数据、文件格式和寄存器字段需要明确长度时,优先使用固定位宽类型。 类型转换主要注意下面几种情况: 情况 需要注意什么 大类型转小类型 高位可能被截断 有符号转无符号 负数会变成很大的正数 无符号与有符号比较 有符号数可能先被转成无符号数 指针类型转换 只改变读取内存的方式,不会改变原来的数据 注意:有符号数和无符号数比较前,尽量把两边统一成合适的类型。进行指针类型转换时,要确认原内存长度不小于目标类型的大小。例如原数据只有 1 字节,转换成 uint32_t * 后却按照 4 字节读取,就会读到不属于它的内存。 二、指针、数组与生命周期 指针保存的是地址。使用前主要检查四件事: 指针是否为空。 指向的内存是否仍然有效。 访问长度是否越界。 指针指向的数据类型是否正确。 void update_state(uint8_t *state) { if (state == NULL) { return; } *state = 1; } 数组传入函数后通常会退化成指针,函数内部无法再通过 sizeof 得到原数组长度。因此传递缓冲区时,最好同时传入长度。 ...

做 TWS 应用开发,到底需不需要懂协议栈

刚接触 TWS 应用开发时,我一直有一个疑问:蓝牙协议栈大部分已经由芯片原厂封装好了,平时做功能主要是调用 SDK 接口、处理事件和编写产品逻辑,还有没有必要专门学习协议栈? 刚入行的时候,我也尝试过从头学习蓝牙协议。当时下载了一份 Bluetooth Core Specification 6.0,打开后看到整整 3815 页,心里压力一下就上来了。里面有大量英文术语、状态、命令和数据包格式,我硬着头皮看了几天,还是很难把规范里的内容和手上的代码对应起来,最后没有坚持下去。 工作两年左右,我对协议栈内部仍然谈不上有多深入的理解,平时接触最多的还是应用层代码。所以我现在的看法是:**需要了解一些,但没必要一开始钻得特别深。**这篇文章只是记录我目前在项目中用到的一些认识。 一、我目前主要关注什么 以我目前的工作内容来说,HCI、L2CAP 以及各个 Profile 的内部状态机,我其实没有深入研究。日常开发更常见的是调用 SDK 接口、接收事件,再根据产品状态处理业务。 为了不把不同问题混在一起,我会先记住几个和工作直接相关的区别: ACL 连接和 Profile 连接不是一回事。 A2DP 负责音乐,AVRCP 负责播放控制和音量同步。 HFP 负责通话控制,SCO/eSCO 承载通话语音。 SPP 和 BLE GATT 常用于 App 数据通信。 接口调用成功,不一定代表异步操作已经完成,还要等待后续事件。 为了更直观地区分这些连接和业务,可参考蓝牙连接关系简化图: 图中把 GATT 单独放在 BLE 连接下。严格来说,协议内部的承载关系还要更细,这里只保留我在应用开发中经常接触到的部分。 我目前也就是先把这些常见概念分清。这样遇到异常时,至少知道先看哪一类状态,不至于一上来就把所有现象都归结成“蓝牙有问题”。 二、实际项目里,我能接触到什么 以我使用的中科蓝讯 bt8910 SDK 为例,工程里可以看到 A2DP、HFP、SPP 和 TWS 相关的配置及适配代码,但真正的核心实现主要由预编译的 libbtstack.a 提供。 应用层这边,A2DP 和 HFP 主要是调整部分参数、接收事件和处理业务回调;SPP 的 SDP Record 和数据收发回调可以看到并修改;GATT 可以添加 Service、Characteristic 和读写回调;TWS 这边则能做一些业务状态同步和主从切换处理。 ...

一副 TWS 耳机里,有哪些软件模块

一、先看整体 如果暂时不考虑具体芯片平台,TWS 耳机的软件大致可以分成下面几个部分: 这不是严格的分层架构。实际 SDK 中,各部分往往会互相调用,甚至放在同一个模块里。这样划分主要是为了看清楚:一个耳机功能背后,通常有哪些软件在配合。 软件模块 主要负责什么 常见内容 系统与 RTOS 让程序和多个任务运行起来 任务、消息、定时器、内存、看门狗 驱动与电源 与耳机硬件交互 GPIO、ADC、I2C、I2S、充电、休眠、唤醒 蓝牙连接 与手机建立和维护蓝牙业务 配对、回连、A2DP、AVRCP、HFP、BLE 双耳协同 让左右耳保持一致 双耳组对、角色、状态同步、音频协同 音频系统 管理声音输入和输出 编解码、播放、录音、提示音、ANC、ENC 产品应用 决定耳机具体怎样工作 按键、LED、佩戴检测、状态机、功能优先级 数据与升级 保存配置并更新固件 配对记录、用户设置、OTA、版本管理 调试与测试 帮助发现和定位问题 日志、异常信息、测试模式、量产支持 二、系统与 RTOS TWS 耳机一般不是从上到下只执行一条固定流程。蓝牙事件、按键、音频、充电和定时任务可能在不同时间发生,因此软件需要同时管理多种事情。 这一部分通常包括: 系统启动和各模块初始化 任务或线程的创建与调度 事件、消息和队列 软件定时器 内存分配与任务栈 中断处理 看门狗和异常恢复 应用层开发中经常接触到的,并不是 RTOS 调度器本身,而是平台提供的任务、消息和回调接口。 例如,按键驱动检测到一次双击后,不一定会直接执行“切换降噪模式”。它可能先产生按键事件,再把事件投递给应用任务,最后由应用代码根据当前状态决定是否执行这个功能。 所以,当我们阅读一个新 SDK 时,找到事件入口和消息流转路径很重要。 三、硬件驱动与电源管理 耳机虽然体积不大,但里面仍然有不少硬件。软件需要通过不同接口读取状态或者控制这些器件。 常见内容包括: GPIO:NTC供电、LED、使能信号 ADC:按键电压、其他模拟量 UART:日志输出、机盒 充电与盒仓检测:识别 DC、入盒、出盒、开盖、关盖、充电和满电等状态 佩戴检测:红外、触摸或其他传感器 I2C、SPI:传感器、外部芯片或存储器 I2S、PCM:数字音频数据传输 驱动层负责把硬件状态转换成软件可以识别的数据或事件,应用层再决定怎样处理。 “耳机入盒”背后涉及盒仓状态检测、TWS 对耳同步、按键禁用、ANC 和音乐处理、DAC 控制及主从切换;关盖后还要关闭蓝牙并进入 FUNC_IDLE或 charge等其他模式。处理不完整,可能导致左右耳状态不一致、盒内仍播放声音或关盖后仍连接手机。 ...