大家好,我是杂烩君。
在嵌入式开发中,蓝牙是一块很独立的内容,常常需要专人专岗来对接。
但有时候我们也需要对接蓝牙相关功能,了解一些基础知识,既方便跟蓝牙同事沟通协作,也能对项目中各个模块保持一定的敏感度。
1. 经典蓝牙与低功耗蓝牙
蓝牙不是一个单一协议,它是一个庞大的协议族。我们平时说的"蓝牙",其实包含两大分支:经典蓝牙(BR/EDR)和低功耗蓝牙(BLE)。
这两者差别很大,下面这张表格帮你快速区分:
| 对比维度 | 经典蓝牙(BR/EDR) | 低功耗蓝牙(BLE) |
|---|---|---|
| 传输速率 | 1-3 Mbps,带宽大 | 1 Mbps(BLE 5.0可达 2 Mbps) |
| 功耗水平 | 高,持续传输耗电明显 | 极低,纽扣电池能撑一两年 |
| 典型场景 | 蓝牙音箱、耳机、车载音频 | 智能手环、传感器、物联网终端 |
| 协议复杂度 | 复杂,涉及音频编解码、高速数据流 | 精简,专为短数据、低频交互设计 |
| 嵌入式对接频率 | 偶尔碰到(音频类项目) | 非常高频,绝大多数IoT项目都用它 |
说白了,你要是在做物联网、穿戴设备、传感器这类项目,日常打交道的基本都是BLE。手机APP读设备的温湿度数据、下发控制指令、OTA升级——这些全是BLE的活儿。所以接下来所有内容,我们都围绕BLE展开。
2. BLE协议栈分层
蓝牙的协议栈架构图,从物理层到应用层内容很多,非必要我们不用陷进去。
只需要知道一点:底层的PHY(物理层)、LL(链路层)、L2CAP(逻辑链路控制与适配层),芯片厂商(Nordic、Dialog、TI、瑞昱……)全都封装进SDK了,我们碰都碰不到,也不需要碰。我们真正要关心的就三层半——GAP、GATT、HCI,再加上我们自己写的应用层代码。
用一个生活化的类比把这几层讲清楚——假设你在相亲:
GAP层 = 相亲的认识流程:你在什么平台上展示资料(广播)、对方怎么找到你(扫描发现)、双方怎么加微信(连接配对)、聊不来怎么删好友(断开)。GAP管的就是设备间"能不能认识、怎么认识"。
GATT层 = 相亲后的信息交换规则:加了微信之后,你的朋友圈有什么内容(服务和特征值)、对方能看哪些(读权限)、能给你留言吗(写权限)、你会主动发朋友圈通知对方吗(通知推送)。GATT管的就是"连上之后传什么数据、怎么传"。
HCI层 = 你和手机之间的操作接口:你想发消息,得在手机上点按钮,手机把你的操作翻译成底层的网络请求。HCI就是应用层和底层芯片之间的命令接口,我们调SDK的API,底层走的就是HCI指令。
应用层 = 你本人:决定发什么内容、怎么回复、出了问题怎么处理,这部分逻辑是你自己写的。
搞清楚这个分工,对接的时候就不会迷路了。记住一句口诀:GAP管连接,GATT管数据,HCI管通信。先配GAP让手机搜得到、连得上,再配GATT让数据传得了,中间出了问题看HCI的状态反馈。
3. 5个关键概念
协议栈分层搞明白了,接下来看几个对接应用层时绑不开的核心概念。
3.1 广播(Advertising)——设备的"存在感"
BLE设备想被手机发现,必须主动往外发"广播包"。你可以理解成,设备在不停地往四周喊:"我叫温湿度传感器_01,我的地址是XX:XX:XX:XX:XX:XX,想跟我聊天的来连我!"
手机打开蓝牙扫描,就是在"听"周围有没有人在喊。
我们在代码里要做的就是配好广播参数然后启动广播,核心参数有三个:
广播名称
-
- :手机扫描列表里显示的设备名,取个好认的名字;
广播间隔
-
- :多久发一次广播包,一般设100ms-500ms。间隔越短手机越容易搜到,但功耗越高,需要在搜索速度和续航之间取个平衡;
广播内容
- :除了名称和地址,还可以塞一些自定义数据(广播包最大31字节),比如设备电量、固件版本。
通常我们只需要调用厂商SDK提供的接口、传入对应的参数就行,底层怎么把广播包调制成射频信号发出去,完全不用操心。
3.2 UUID——数据接口的"门牌号"
UUID(Universally Unique Identifier)是GATT层给每个数据接口分配的唯一标识。你可以把它理解成"门牌号"——手机APP要读温度数据,得知道温度数据的UUID是啥;设备要接收控制指令,也得定义好对应的UUID,双方对上号才能通信。
GATT层的数据组织方式是一个树形结构,关系很清晰:
一个设备可以有多个"服务"(Service),每个服务下面挂多个"特征值"(Characteristic),每一级都有自己的UUID。手机APP对接的时候,先按服务UUID找到对应服务,再按特征值UUID找到具体的数据接口。
关于UUID格式,有两种你需要知道的:
16位UUID:蓝牙SIG官方定义的标准UUID,比如 0x180A 代表设备信息服务,0x2A6E 代表温度特征值。如果你用的数据类型刚好有标准定义,直接用16位的就行。
128位UUID:自定义数据用这个,格式类似 0000FFE0-0000-1000-8000-00805F9B34FB。我建议凡是自定义服务和特征值,一律用128位,避免跟标准UUID撞车。
3.3 特征值属性——决定数据"能怎么用"
每个特征值都有"属性"标记,它决定了手机端能对这条数据做什么操作。这是对接时最容易出问题的地方,属性配错了,功能直接不通。
常用的四种属性:
一个特征值可以同时拥有多个属性。比如温度特征值,我一般设成"可读 + 通知"——手机既可以主动来问"现在多少度"(Read),设备也会定时主动推数据过去(Notify)。采集间隔特征值设成"可读 + 可写"——手机能查当前间隔是多少(Read),也能下发新的间隔值(Write)。
实际项目中,Notify(通知)用得最多。因为大部分IoT设备的工作模式是:传感器采集到数据就主动上报,不需要手机每次来轮询。这样通信效率高,功耗也低。
3.4 BLE连接与数据交互的完整过程
前面讲了广播、UUID、特征值属性,现在把它们串起来,看一次完整的BLE数据交互到底是怎么走的:
看完这张图,我们对整个BLE交互流程应该就有底了。手机先通过广播发现设备、发起连接,连上之后按UUID找到对应的服务和特征值,然后就可以读写数据、接收通知推送了。
3.5 主从角色——谁发起连接,谁等着被连
BLE把设备分成两种角色:
从设备(Slave / Peripheral):我们做的传感器、手环、门锁,绝大多数都是从设备。从设备的工作就是广播自己、等别人来连、连上之后提供数据服务。嵌入式IoT项目里,90%以上你碰到的都是从设备的开发。
主设备(Master / Central):手机、平板、蓝牙网关,这些是主设备。主设备负责主动扫描、主动连接、主动发起数据请求。如果你做的是蓝牙网关(需要同时连好几个传感器汇总数据),那你得做主设备开发,难度会高一些。
4. 应用层代码的核心工作流程
还是以"BLE温湿度采集设备"为例,应用层代码的整体流程长这样:
图里的流程已经很清晰了,补充几个图上体现不出来的点:
GAP配置决定能不能被搜到,GATT配置决定"连上后能交换什么数据",两者缺一不可,顺序也不能反。
异常处理别偷懒
-
-
- ——连接断开要重新广播、数据发送失败要重试、指令解析出错要返回错误码,这些直接关系到产品稳定性。我们不用碰底层蓝牙驱动,芯片SDK把射频收发、链路管理都封装好了,我们的活儿就是
配参数 + 写交互逻辑 + 做异常兜底
- 。
-
5. 写在最后
蓝牙协议栈看着庞大,但嵌入式工程师要抓的就三条线:GAP管连接、GATT管数据、HCI管通信。把广播配置、UUID定义、特征值属性、数据交互流程、异常处理这几件事搞清楚,日常的BLE应用层对接就不会有大问题。
我自己的经验是:刚接触BLE的时候,别急着从零写代码,先把芯片SDK的demo跑起来,改几个参数让手机连上设备、收发数据。在demo的基础上一点一点加自己的逻辑,比从零搭建快得多,也不容易出错。
碰到问题别闷头查协议文档,先拿nRF Connect或者LightBlue这类蓝牙调试APP抓一下,看看广播包对不对、UUID对不对、特征值属性对不对,80%的问题靠工具就能定位。
284