其实蓝牙和其他各种通讯协议是一样的,都是通讯的方式和过程不相同。就像是一个人要到一个目的地,方式有很多种,例如汽车,轮船,飞机。其承载的内容都是一致的(同一个数据帧--同一个人),只不过方式不同。
一、蓝牙协议结构

其实在硬件上,蓝牙协议就分为两个,一个是host-->mcu(微控制器),另一个是controller-->蓝牙射频芯片。两者的链接亦有多种方式,或集成一个芯片或使用spi等。这里的链接方式就对应了HCI的方式。所以HCI就是用来表达host和controller的一个链接中间层。而最后,蓝牙协议则是主要集中在host中,定义了host对数据接收和处理方式,而controller更多的是硬件上的操作,将射频信号转化为电信号,拼接成常见的数据帧。而对数据帧的处理,就是这个蓝牙协议栈结合host(mcu)的功能所在。
1. MCU(微控制器)
**作用:**MCU是蓝牙设备的核心,负责运行蓝牙协议栈、处理数据、控制外设以及执行应用程序逻辑。
功能: 运行蓝牙协议栈(如BLE协议栈)。 管理射频硬件(通过SPI、UART等接口)。 处理用户应用程序(如传感器数据采集、逻辑控制等)。
2. 射频硬件(RF Transceiver)
**作用:**射频硬件负责发送和接收蓝牙信号(2.4GHz频段)。
功能: 调制和解调蓝牙信号。 实现蓝牙物理层(PHY)和链路层(Link Layer)的功能。
常见射频硬件: Nordic nRF52系列(集成射频和MCU)。 TI CC2640系列。 ESP32(集成Wi-Fi和蓝牙)。
3. 蓝牙协议栈
**作用:**蓝牙协议栈是MCU上运行的软件,负责实现蓝牙的通信协议。
分层结构:分以下三层
物理层(PHY):处理射频信号。
链路层(Link Layer):管理连接、广播和数据包传输。
主机控制接口(HCI):MCU与射频硬件之间的通信接口。
逻辑链路控制与适配协议(L2CAP):数据包的分段和重组。属性协议(ATT):定义数据的读写操作。
通用属性配置文件(GATT):定义服务和特征值。
通用访问配置文件(GAP):管理设备发现和连接。
**小结:**硬件上分为mcu与射频硬件,mcu通过蓝牙协议去驱动射频硬件,即完成了蓝牙功能。
二、蓝牙协议的合作
蓝牙的就像包裹,层层封装。
- 数据本身:要发的那几个字节(比如心率 = 70)
- 蓝牙协议:一层一层往上包,保证能安全发到对方
就像:物品 → 小袋子 → 盒子 → 快递单 → 运输
蓝牙也是:应用数据 → GATT → L2CAP → Link Layer → PHY → 无线电发出去
ble controller是员工,接受mcu的指令。
| 角色 | 对应硬件 / 层级 | 具体工作 |
|---|---|---|
| MCU(老板) | 应用层 / GATT/ATT/L2CAP/GAP(Host 层) | 1. 给员工定规则(设置广播名、连接参数、GATT 服务);2. 不参与具体执行,只等员工汇报结果;3. 处理最终数据(比如把心率值显示在屏幕)。 |
| BLE Controller(员工) | LL 层 / PHY 层 | 1. 严格执行老板的指令(按参数广播、扫描、建立连接);2. 自主完成 LL 层核心工作(跳频、时序、CRC 校验、加密);3. 只把结果汇报给老板(比如 "已连接手机""收到读心率指令")。 |
三、蓝牙协议栈
我们从Host层开始,层层向上解析。

蓝牙协议栈主要分为两个部分:
配对链接-->GAP和SM :在两个设备互相发现,链接的配置和操作就由这两个层面完成;
数据传输-->GATT和ATT :在链接之后,两个设备互相传输数据,则是通过这两个层进行操作;
而最后一个 L2CAP则是一个分岔口,使得传输的数据通过L2CAP时能够被分拣到对应的 配对链接块 或 数据传输块。
- GAP
GAP(Generic Access Profile)通用访问协议:GAP 是所有的蓝牙设备均需实现的Profile,主要用于描述device discovery(设备发现)、connection(连接)、security requirement(安全要求)和authentication(认证) 的行为和方法。
GAP可以简单理解为"对LL层的配置"。但是不是直接 "配置 LL 层",而是「定义设备的对外行为规则」,并通过协议栈向下传递这些规则,最终由 LL 层(Controller)执行这些规则。
简单说:
- GAP 管「规则 / 策略」:比如 "我要可被发现""我要广播 100ms 一次""我是 Peripheral 角色";
- LL 层管「执行 / 落地」:严格按照 GAP 定义的规则,硬件级执行广播、扫描、连接等操作。
但是同时,GAP 不止管 LL 层:GAP 还定义了「设备配对 / 安全」「角色切换」「连接参数协商」等规则,这些规则不仅作用于 LL 层,还会涉及 ATT/GATT 层的安全逻辑;
GAP 还是 "行为规范" 而非 "配置工具":GAP 是蓝牙联盟定义的「通用访问规范」,是一套标准,而不是直接操作 LL 层的函数;LL 层是这套标准的 "硬件执行者"。
- GATT
GATT(Generic Attribute profile)通用属性协议:定义了服务的流程、格式及其所包含的特征,包含特征的发现、读取、写入、通知、指示;主要用来规范attribute中的数据内容,并将不同attribute进行分组分类。
从机中可以有多个服务(service),一个服务中可以有多个特征值(Characteristic),每个特征值又有自己的属(property),属性的取值有读、写、通知(Notify)。每个服务和特征值都有唯一的UUID标识(标准UUID为128位,协议栈中一般为16位)。
而我的理解是,GATT就是像给Modbus的寄存器定义数据含义,读写属性。用「Modbus 地址定义」对标「GATT 数据定义」(逐点对齐)
| 维度 | Modbus 地址定义 | GATT 数据定义 | 核心共性 |
|---|---|---|---|
| 核心目的 | 规定「哪个地址对应什么数据」,避免混乱 | 规定「哪个 Handle/UUID 对应什么数据」,避免混乱 | 给裸数据赋予「业务含义」 |
| 数据标识 | 地址(如 40001 = 温度、40002 = 湿度) | Handle(如 0x0012 = 心率)/ UUID(如 0x2A37 = 心率特征值) | 用唯一标识绑定「位置 - 数据」 |
| 数据属性 | 读 / 写 / 只读(如 40001 只读、40003 可写) | 读 / 写 / Notify/Indicate(如心率特征值 可读 + Notify) | 定义数据的操作权限 |
| 数据分类 | 功能码区分(如 03 读保持寄存器、06 写单个寄存器) | Service 分类(如 0x180D = 心率服务、0x180F = 电池服务) | 按业务逻辑分组数据 |
| 使用场景 | 上位机读 40001 → 知道是温度,按规则解析 | 手机读 0x2A37 → 知道是心率,按规则解析 | 双方按约定解析数据,不用传 "数据说明" |
- ATT
ATT(Attribute Protocol)配置属性协议:用于发现、读取和写入对端设备上的属性的规范;
它分为两个角色:Server和Client,通常从机为服务端,主机为客户端;服务端提供拥有关联值的属性集 ,客户端发现、读、写这些属性,服务端也可以主动通知客户端。
属性类型:用UUID(16bit or 128 bit)的形式来表现;
属性句柄:用于标识一个属性,服务器上的所有属性都会分配一个唯一非零的属性句柄;
属性权限:使用许可、认证许可、授权许可;
属性值 :0-512 byte。
ATT 不是 "定义规则" 的层,而是 "执行规则" 的层 ------ 它是 GATT 数据规则的「搬运执行者」,也是 BLE 数据交互的「底层操作指令集」,类比到 Modbus 里,ATT 就是「实现读 / 写寄存器的底层指令」。
| 层级 | 核心角色 | Modbus 类比 | 运行阶段 |
|---|---|---|---|
| GAP | 连接规则定义者(配置 LL 层行为) | 串口通信参数配置(波特率、校验位) | 初始化阶段为主 |
| GATT | 数据字典定义者(给数据赋含义 / 权限) | Modbus 地址表定义(40001 = 温度) | 初始化阶段为主 |
| ATT | 数据操作执行者(按 GATT 规则搬数据) | Modbus 读 / 写指令(03 读寄存器、06 写寄存器) | 运行阶段全程参与 |
- L2CAP
L2CAP(Logical Link Control and Adaption Protocol)链路控制和适配协议:对LL进行了一次简单封装,LL只关心传输的数据本身,L2CAP就要区分是加密通道还是普通通道,同时还要对连接间隔进行管理。
作用:把蓝牙的数据分成不同通道; 让 ATT、安全加密、其他数据互不干扰;分包、拼包
对应例子:心率数据走通道 1;配对加密走通道 2;
L2CAP = 交通分流,互不干扰
- Link Layer(链路层 LL)
Link layer链路层:链路管理,是整个协议栈的核心,定义了空中接口数据包格式、比特流处理程序(例如错误检查)、状态机以及用于无线通信和链路控制的协议;主要负责信道管理、广播和扫描、创建和保持连接、收发空中包和加密链路。
现实角色:两个人面对面喊话、建立对话
它干的事:
- 手环广播:"我在这,我叫 XX 手环"
- 手机扫描:"我听到你了"
- 双方建立连接
- 规定什么时候发、什么时候收、跳频、加密
- 保证包不乱、不丢
对应例子:
- 手环广播 → LL 层在工作
- 手机点连接 → LL 层握手
- 连接成功后,LL 层维持对话节奏
- Physical Layer(物理层 PHY)
现实角色:无线电波 / 空气
它只干一件事:把 0 和 1 变成电磁波发出去,再把电磁波变回 0 和 1。
对应例子:
- 手环把数据变成 2.4GHz 无线电
- 手机天线接收这个信号
- 不关心数据是什么,只负责收发比特
你可以理解:PHY = 路 + 交通工具
四、蓝牙开发理解
- 初始化 BLE 控制器(Controller 层)
作用:初始化 BLE 硬件核心(LL/PHY 层),给后续所有操作打基础(相当于给 "快递运输车" 通电、检查车况)。
核心操作:
使能 BLE 控制器时钟 / 电源;
- 配置控制器模式(仅 BLE / 双模,ESP32 需 menuconfig 选);
- 初始化控制器与 MCU 的通信接口(如 HCI)。
编码重点:
// 伪代码(ESP-IDF NimBLE 示例)
nimble_port_init(); // 初始化 NimBLE 端口(含 Controller)
nimble_port_freertos_init(ble_host_task); // 启动 BLE 任务
- 这一步是 "一次性配置",协议栈封装好,你只需调用初始化 API。
- 配置 GAP 层(连接规则)
作用:定义设备的「对外连接规则」,告诉 Controller 怎么广播、怎么被发现、是什么角色(相当于定 "快递揽收规则")。
核心操作:
设定角色:Peripheral(被连接,如传感器)/ Central(主动连接,如网关);
- 配置广播参数:广播间隔、是否可连接、设备名称;
- 配置连接参数:连接超时、从机延迟、心跳间隔。
编码重点:
// 伪代码:配置 Peripheral 广播
struct ble_gap_adv_params adv_params = {
.conn_mode = BLE_GAP_CONN_MODE_UNDIR, // 可连接
.disc_mode = BLE_GAP_DISC_MODE_GEN, // 可被发现
.interval_min = 100, // 广播间隔100ms
.interval_max = 100,
};
ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER,
&adv_params, gap_event_cb, NULL); // 启动广播
- 编码量 < 5%,一次性配置,核心是填对广播 / 连接参数;
- 只需处理 GAP 事件回调(如连接成功 / 断开)。
- 定义 GATT 层(数据字典)
作用:定义「数据的业务规则」,相当于给 BLE 数据建 "Modbus 地址表"(告诉协议栈 "哪个 Handle 对应什么数据、有什么权限")。
核心操作
- 创建 Service(业务分类,如心率服务、电池服务);
- 在 Service 下创建 Characteristic(具体数据项,如心率值、电池电量);
- 给 Characteristic 配置:UUID、操作权限(读 / 写 / Notify)、Handle 地址;
- 绑定读 / 写回调函数(核心:告诉协议栈 "读这个数据时该执行什么逻辑")。
编码重点:
// 伪代码:定义心率服务+特征值
// 1. 定义服务 UUID(0x180D 是蓝牙标准心率服务)
static const struct ble_gatt_svc_def gatt_svcs[] = {
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(0x180D), // 心率服务UUID
.characteristics = (struct ble_gatt_chr_def[]) {
{
.uuid = BLE_UUID16_DECLARE(0x2A37), // 心率特征值UUID
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, // 权限
.access_cb = heart_rate_read_cb, // 绑定读回调
},
{ 0 }, // 结束标记
},
},
{ 0 },
};
// 2. 注册 GATT 服务
ble_gatts_count_cfg(gatt_svcs);
ble_gatts_add_svcs(gatt_svcs);
- 编码量~10%,一次性配置,核心是 "UUID + 权限 + 回调绑定";
- 这一步是 "数据规则定义",后续所有数据交互都基于此。
- 编写 GATT 回调函数(业务逻辑核心)
作用:实现「数据的实际处理逻辑」(协议栈触发回调时,执行 "读传感器、解析写入数据、计算数值" 等核心业务)。
核心操作
- 读回调:实时获取 / 计算数据(如读心率传感器、读电池电压);
- 写回调:解析上位机写入的数据,执行硬件操作(如设置闹钟、控制 LED);
- Notify 逻辑:主动推送数据(如心率变化时,调用 API 推送给上位机)。
编码重点:
// 1. 读心率回调(你写的核心业务逻辑)
static int heart_rate_read_cb(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg) {
// 步骤1:读硬件传感器(你的业务逻辑)
uint8_t heart_rate = read_hr_sensor(); // 你自己实现的传感器读取函数
// 步骤2:把数据写入协议栈缓冲区(给 ATT 层自动发送)
os_mbuf_append(ctxt->om, &heart_rate, 1);
return 0;
}
// 2. 主动推送心率(Notify)
void send_heart_rate_notify(uint8_t heart_rate) {
struct ble_gatt_chr *chr = ble_gatt_chr_find_uuid(BLE_UUID16_DECLARE(0x2A37), gatt_svcs);
ble_gattc_notify(0, chr->value_handle, &heart_rate, 1); // 主动推送
}
- 编码量~85%,是你开发的核心工作;
- 所有和 "硬件交互、数据计算、业务规则" 相关的代码都在这里。
- 启动广播 / 扫描(GAP 层执行)
作用:让 Controller 按 GAP 配置的规则,开始广播(Peripheral)或扫描(Central)(相当于 "快递运输车按规则出发揽收")。
核心操作
- Peripheral:调用
ble_gap_adv_start()启动广播; - Central:调用
ble_gap_disc_start()启动扫描,发现设备后发起连接。
编码重点:
- 只需调用协议栈 API,无需复杂逻辑;
- 关注连接事件回调(如
gap_event_cb中处理连接成功)。
- 运行时数据交互(自动执行)
作用:协议栈自动处理底层数据传输,你只需响应回调(相当于 "快递分拣、运输全自动化,你只需要处理包裹内容")。
核心流程(全自动,无需编码)
- 上位机发读 / 写指令 → LL 层拆包 → L2CAP 分通道 → ATT 层校验权限;
- ATT 层触发你写的 GATT 回调 → 你返回数据 / 处理写入;
- ATT 层打包数据 → L2CAP 分通道 → LL 层封装无线包 → 发回上位机。
编码重点:
- 无需写任何底层代码,只需维护业务数据(如定时更新传感器值)。
- 异常处理(可选进阶)
作用:保障稳定性,处理连接断开、超时、权限错误等场景。
核心操作
- 在 GAP 事件回调中处理 "连接断开",重新启动广播;
- 在 GATT 回调中校验数据合法性(如写入的闹钟时间不能超 23 点);
- 处理 ATT 层权限错误(如拒绝非法写入)。
五、初始化用例
gap的配置分三个方面:
| 层级 | 核心内容 | 对应代码 / 函数 | 你的理解匹配度 |
|---|---|---|---|
| 1. 静态基础配置 | 初始化 GAP 标准服务、设置不依赖协议栈运行的静态属性(设备名、GAP 服务本身) | gap_init()(ble_svc_gap_init() + ble_svc_gap_device_name_set()) |
✅ 完全匹配(静态数据初始化) |
| 2. 动态广播配置 | 初始化依赖协议栈同步的动态参数(设备地址、广播数据 / 扫描响应、广播间隔),并启动广播 | adv_init() + start_advertising()(地址获取 + 广播参数配置 + ble_gap_adv_start()) |
✅ 核心匹配(动态数据 + 开启广播) |
| 3. 事件驱动处理 | 处理广播 / 连接全生命周期事件(连接成功 / 断开、广播完成、订阅 / MTU 更新等),保证设备行为符合预期 | gap_event_handler(由 ble_gap_adv_start() 注册,协议栈自动触发) |
✅ 完全匹配(链接相关事件处理) |
gatt的配置则是
/*
* GATT server initialization
* 1. Initialize GATT service
* 2. Update NimBLE host GATT services counter
* 3. Add GATT services to server
*/