无线通信:让手机和手表交换数据
手表已经能读到运动、心率、电池和声音数据。接下来把它们送到手机,并让手机反过来设置手表。先把通信链路接通,再在下拉子篇里约定消息格式,最后沿仓库实现走完一次收发。
第 1 节 · 已经会用串口,怎样再接上无线?
前面的串口需要一根线把发送端接到接收端。手机和手表之间没有这根线,需要无线收发电路将数据通过无线信号传过去。当前手表采用低功耗蓝牙,英文缩写为 BLE。STM32 负责采集与处理,板上另一颗 CH582M 负责蓝牙连接和收发。
两颗芯片之间仍用已经学过的串口。STM32 发送字节,CH582M 通过蓝牙转给手机;手机写入的字节,则由 CH582M 通过串口交给 STM32。这种把两端字节转发过去的工作,通常叫透传或桥接。
| 位置 | 实际连接 | 负责什么 |
|---|---|---|
| STM32 发送端 | PA9 / USART1_TX → CH582M PA8 / UART1_RX | 把数据交给无线芯片 |
| STM32 接收端 | PA10 / USART1_RX ← CH582M PA9 / UART1_TX | 接收手机发来的内容 |
| 两颗芯片的串口 | 115200、8 位数据、无校验、1 位停止位,共地 | 在板内传递字节 |
接线与当前源码以 硬件说明、usart.c 和 peripheral.c 为准。部分旧注释仍写 LPUART1,当前主控实现用的是 USART1。调试打印也不要混进这条协议串口,否则手机会把日志文字一起当成待解析的数据。
本篇使用现有无线芯片固件。需要理解它怎样接收、排队和转发;无线射频收发和完整蓝牙协议栈由芯片及配套库完成。
第 2 节 · 手机怎样知道附近有一块手表?
手表上电以后,CH582M 定期发送一小段供附近设备发现的信息,叫广播。手机开启扫描,就能看到正在广播的设备。项目的广播名称为 SmartWatch,名称放在扫描响应数据里,调试工具可能要等主动扫描取得响应后才显示它。
手机从扫描结果中选择设备并建立连接。这个过程中,手机通常担任发起扫描、建立连接的一方,手表担任等待连接的一方,手册分别称为中心设备和外围设备。它们只说明连接角色;连接以后,双方都可以传送数据。
“扫描到了”“已经连接”“已经收到数据”是三个不同阶段。能看到名称只说明发现成功;连接成功以后,还需要找到正确的数据接口。连接断开再重新连接时,也要重新确认接口和订阅状态。
扫描 SmartWatch → 建立连接 → 找到项目服务 → 找到写入和接收接口 → 开启接收通知 → 验证双向收发。
第 3 节 · 连上了,字节往哪里放?
蓝牙连接上可以同时提供不同用途的数据接口。为了让手机找到它们,低功耗蓝牙把相关接口组织成服务,服务里具体可读、可写或可通知的数据项叫特征。这种组织和访问方式称为 GATT。先把它理解成“找到一组功能,再选择其中的数据入口”。
每个服务、特征都有自己的识别号,叫 UUID。它用于确定访问哪个接口,和广播名称用途不同。当前项目配置如下;表内用尾部标识简写,完整识别号以源码和规范为准:
| 接口 | 项目标识 | 从手机看怎么用 |
|---|---|---|
| 项目服务 | FFE0 | 在服务列表里找到这一组接口 |
| 手机发给手表 | FFE2 | 执行 Write,即写入字节 |
| 手表发给手机 | FFE3 | 开启 Notify,即订阅后接收通知 |
例如服务完整 UUID 为 0000FFE0-0000-1000-8000-00805F9B34FB;两个特征分别把 FFE0 换成 FFE2、FFE3。RX、TX 的名称通常从模块自身角度命名:模块 RX 接收手机写入,模块 TX 向手机发通知,所以用方向判断最清楚。
为什么接收前还要“订阅”?
连接后,手机需要告诉手表“我准备接收这个特征的通知”。调试工具里的 Notify 或订阅按钮会配置对应的通知开关,手册中称为客户端特征配置描述符。开启之后,CH582M 才能通过这个特征推送数据。只点连接却没订阅,是“手表一直发送,手机看不到”的常见原因。
有响应写入会报告这一写入操作的结果;无响应写入没有这个逐次响应。通知则由服务端主动发出。它们解决的是蓝牙接口收发,是否已经改变了手表里的设置,还要看 STM32 的应用处理和返回消息,后两篇会具体拆开。
第 4 节 · 先用调试工具,看到一组真实字节
先使用能够扫描低功耗蓝牙、浏览服务和读写特征的手机调试工具。在当前两颗芯片固件已经运行、板内串口连接正确的条件下,按下面顺序操作:
- 扫描并连接。给手表上电,扫描 SmartWatch。没有名称时检查扫描响应和手机的蓝牙扫描权限;不要把普通经典蓝牙串口界面当成这组低功耗蓝牙服务。
- 找到 FFE0。展开服务,确认存在 FFE2 写入特征、FFE3 通知特征。
- 订阅 FFE3。把接收显示切为十六进制,观察当前固件已经上报的数据。文本视图可能把二进制显示成乱码,不能据此判断发送失败。
- 向 FFE2 写入一条完整消息。选择十六进制输入,先发下面这条时间设置示例。接收和设置效果在手表一端观察。
- 断开并重新连接。重新确认通知订阅,观察上报能否恢复。
时间示例是 AA 91 04 00 00 00 01 C0,把手表日期设为 Unix 时间起点之后 1 秒,会改变手表当前时间。适合在实验状态下确认手机到手表的路径,之后再由手机同步正确时间。下一篇逐个解释这八个字节;此时先看“写入以后,手表是否真的执行”。
为什么一次收到的内容可能只有半条?
一次无线传输能装的字节数有限。设备连接后协商的 ATT MTU 表示相关协议单元的最大长度。普通写入、通知能携带的特征值通常不超过“实际 MTU 减 3”;比如 MTU 为 23 时,一次通常最多放 20 字节。
本项目请求较大的 MTU,实际仍以协商结果为准。长内容要分几次传;板内串口又有自己的接收节奏,所以一条应用消息可能分多次到达,一次收到的块也可能同时包含前后两条消息。把这些连续到来的字节重新整理成完整消息,是下一篇要解决的问题。
第 5 节 · 从“字节到了”,到“知道它是什么意思”
想上报 4000 毫伏电池电压,先要决定用整数还是文字、占几字节、哪个字节先发;想让手机设置时间,也要能区分这条命令和电量上报。这些约定组成应用协议,由 STM32 与手机共同遵守。
手写协议:从字节流认出一条消息
先提出边界、类型、长度和校验的问题,再写打包与逐字节解析,使用与仓库相同的外层格式。
无线通信子篇 · 02项目实现:一条电量消息怎样到达手机
沿现有发送、CH582M 转发、手机解码和反向命令,解释每个函数在链路中的作用。
目录点击“无线通信”即可展开这三篇。完成两篇子篇后,接着学习 DMA,把前面在屏幕、串口和声音采集中用过的搬运机制系统解释清楚。
手册与项目入口
- WCH CH583/CH582 数据手册:无线芯片与串口功能;Bluetooth SIG 低功耗蓝牙入门:发现、连接、服务、特征和收发操作。
- CH582M 模块说明与项目协议规范:以当前版本配置为准。