蓝牙协议栈的学习(二)
- [Controller 核心机制](#Controller 核心机制)
-
- Radio(射频)/射频层(RF/PHY):
- [Baseband(基带)/基带层(Link Layer,BLE 的"基带"):](#Baseband(基带)/基带层(Link Layer,BLE 的“基带”):)
- [Link Controller(LC)](#Link Controller(LC))
- [LMP(Link Manager Protocol)](#LMP(Link Manager Protocol))
-
- [LMP 是什么?](#LMP 是什么?)
- [LMP 负责哪些事情?](#LMP 负责哪些事情?)
- [LMP 消息格式](#LMP 消息格式)
- [LMP 的运行流程](#LMP 的运行流程)
- [HCI(Host Controller Interface)](#HCI(Host Controller Interface))
-
- [什么是 HCI(Host Controller Interface)](#什么是 HCI(Host Controller Interface))
- [HCI 的作用](#HCI 的作用)
- [HCI 由什么组成?](#HCI 由什么组成?)
- [HCI 有哪些数据包类型?](#HCI 有哪些数据包类型?)
- [HCI 的流控(flow control)](#HCI 的流控(flow control))
- [恒玄芯片 SDK 中的 HCI 对应位置(重点!)](#恒玄芯片 SDK 中的 HCI 对应位置(重点!))
Controller 核心机制
Radio(射频)/射频层(RF/PHY):
发:把基带送来的"小电平"变成大功率无线电磁波,往空中喊。
收:把空中极微弱的电磁波先"听见",再放大、过滤,变成干净的小电平还给基带。
- 选频道:蓝牙在 2.4 GHz 频段有 79 条(BR/EDR)或 40 条(BLE)小通道,射频先按指令调到对应"电台频率"。
- 调制/解调:基带的 0/1 要先变成"声音变化"才能通过喇叭发出去------蓝牙用的是 GFSK、π/4-DQPSK、8-DPSK 等调制方式。
- 功放与低噪放:发时加大音量,收时先悄悄把对方"悄悄话"放大。
- 天线开关:同一条天线又要发又要收,射频负责"说的时候闭嘴,听的时候闭嘴"。
- 射频(Radio)------只管"电磁波"这一层;
Baseband(基带)/基带层(Link Layer,BLE 的"基带"):
baseband基础概念
收:把上层(LC/链路控制器)要发的数据拆成"小包裹",加上地址、序号、校验,再交给射频;
发:收到射频给的"小包裹"后验货、拼回完整数据,再送交上层。
- 基带(Baseband)------只管"0/1 比特"这一层。
- 打包格式:BR/EDR 里叫"packet",BLE 里叫"PDU",都含:
-- 访问码(识别这是哪一组设备在通话)
-- 包头(谁发给谁、第几个包、要不要确认)
-- payload(真正有用的数据) - 跳频调度:经典蓝牙每 625 µs 换一条频道,基带按"跳频图案"表告诉射频下一微秒该跳去哪。
- ack/重传:对方没回"收到",基带自动重发,直到超时。
- 时钟同步:靠自身 28 bit 蓝牙时钟(CLK)与对端对齐,保证双方"同时跳"。
baseband包的类型
-
ID 包(ID packet)
用途:
只是用来 播报设备地址(AM_ADDR),非常短。
作用:
page
inquiry
没有 payload,属于最基础的"我在这里"的包。
-
NULL 包
用途:
不传数据
用于维持链路
不需要对方回复 ACK
-
POLL 包
和 NULL 很像,但区别:
需要对方回复 ACK
主机会用 POLL 去"点名" slave
-
FHS 包(Frequency Hop Synchronization)
这是非常重要的 Baseband 包。
用途:
在 inquiry 过程中,把 跳频序列信息 + 时钟 + BD_ADDR 发给对方
→ 对方才能同步到你的跳频序列。
特点:
长度固定:144 bits 信息 + 16 bits CRC(再加 FEC)
是建立连接前最重要的包之一
-
DM1 包
这是 ACL(数据链路)最基础的一种数据包格式之一。
用途:
传控制消息(LMP)
传少量用户数据
"为什么 LMP 消息通过 DM1 发送?"
因为 DM1 有 FEC + CRC,非常可靠,适合传控制指令。
Link Controller(LC)
射频相关控制
-
切换 TX / RX
射频芯片同一条天线不能同时"说"和"听"。
队长看表------625 µs 的时隙一到,立刻吼:
"下一条时隙是咱发,关接收,打开发射!"
硬件马上把开关拨到 TX 端。
下一时隙如果是对方发,队长又吼:
"闭嘴,切 RX!"
------这就是"施工队长"最实时的活。
-
按时跳频(根据主机 CLKN)
蓝牙经典模式每 625 µs 换频道,79 条频道跳来跳去。
队长口袋里有一张"跳频图案表",表的关键字就是本地时钟 CLKN 的若干位。
时间一到,他照着表喊:
"CLKN 低 7 位是 0x3A,下条频道 = 3 号,射频给我跳!"
射频 PLL 立即锁到新频率------节拍错一格就会跟对方对不上,直接失联。
-
按 slot (时隙)发送和接收数据包
队长手里拎着"时隙表":
偶数 slot → 主设备发
奇数 slot → 从设备发
他提前把要发的 payload 塞给基带,等时钟走到边界那一刻,吼:
"发!"------基带立即把 packet 推给射频。
收的时候也一样,窗口只开 366 µs,超时没收到就标记丢包,准备重传。
-
控制发射功率(物理层)
队长还管"嗓门大小"。
LMP 刚才协商说"对方 RSSI("接收信号强度指示") 太强,降 4 dB"。
队长转头对射频功放喊:
"功率寄存器减 4 dB,现在发射!"
硬件立即调增益,确保既省电量又不炸掉对方接收机。
Baseband 数据包处理
-
解析 Access Code、Header、Payload
这部分指的是对接收到的通信数据包进行解码,首先提取其中的Access Code 、Header (报头)和Payload (有效负载)部分。
Access Code:让接收方知道数据包的起始位置。
Header:告诉接收方数据包的基本信息(来源、目标、长度等),帮助接收方正确解析和处理数据包。
Payload:包含你传送的数据或信息,最终接收方需要读取和使用的部分。
-
生成 HEC/CRC、做 CRC 校验
- HEC(Header Error Check)是对数据包头部进行错误检测的一种方式,通常用于校验头部数据是否有误。HEC = Header Error Check(包头纠错/校验码),它不是"接收后才有的东西",而是:发送方在发包前,根据 Header 的内容算出来,然后放进 Header 的 HEC 字段里一起发出去。
- CRC(Cyclic Redundancy Check)是循环冗余校验,用于检测数据包在传输过程中是否发生错误。这里提到的"生成 CRC"指的是在发送数据前根据数据内容计算出 CRC 校验值;而"做 CRC 校验"则是接收方对收到的数据进行 CRC 校验,确认数据是否正确。Payload 末尾固定带一个 16-bit CRC 字段,这个 CRC 就是发送方算出来附在包后面一起发出去的。
-
判断 ARQN(ACK/NAK)
在蓝牙 Baseband 里,每个接收方在收到一个包之后,都会在下一次发包时带上一个字段:
ARQN = Automatic Repeat reQuest Number
用 1 bit 表示:1 = ACK(收到正确),0 = NAK(收到错误,需要重传),所以,ARQN 就是接收方对"上一包"给发送方的反馈结果
-
执行 ARQ 重传机制
如果接收方返回 NAK,表示数据包有错误或丢失,发送方会重新发送该数据包,这是 ARQ 协议中的重传机制。
-
判断 FLOW Control(STOP/GO)
Flow Control = 流量控制机制
- STOP 表示接收方的缓存或处理能力达到上限,要求发送方暂停数据发送。
- GO 表示接收方已经准备好继续接收数据,可以恢复数据的发送。
-
控制 synchronous / asynchronous buffer
同步缓冲区(synchronous buffer)是用来存放实时数据(例如语音)的,必须按时间节奏稳定输出。
异步缓冲区(asynchronous buffer)是用来存放普通数据(例如文件、网络数据)的,不需要严格时间节奏。
为什么会有两种 buffer?因为蓝牙(BR/EDR)同时支持两种不同类型的连接:
- Synchronous(SCO / eSCO)链路:用于实时数据
数据必须准时到达
延迟要求严格
比如:蓝牙语音(耳机、通话)
→ 不能丢包,也不能延迟太久
→ 必须严格按照固定时间间隔发数据(如每 6 个 slot 送一次)
这类数据需要一个 同步缓冲区。 - Asynchronous(ACL)链路:用于普通数据
数据不要求精确时间
可以排队等待
例如:文件传输、APP 数据、消息通信
→ 可以有重传
→ 可以等待空闲 slot
→ 可以做流量控制(STOP/GO)
这类数据放在 异步缓冲区。
同步(Synchronous)= 必须按固定节奏、固定时间点执行
大家必须一起步调一致,有严格的时间要求。
在通信中:
数据必须"准时到达"
有固定的发送周期
接收方必须"按时"来取数据
不能随意晚或早
异步(Asynchronous)= 不要求在同一时间点进行,随时可以处理
没有固定节奏,想什么时候做就什么时候做。
在通信中:
数据什么时候到达都可以
发送和接收不需要时间同步
可以排队
可以延迟
丢了还能重传(可以等待
状态机处理
所谓"状态机处理"其实就是:
蓝牙设备在不同阶段会处于不同的工作状态,每个状态有不同的行为、不同的允许动作,LC 就负责按照规则在这些状态之间切换。
蓝牙的主要状态组分为两类:
未连接状态(Connectionless States)
连接后状态(Connected States)
未连接状态:Standby / Inquiry / Page
这些状态是在蓝牙设备"还没建立连接"之前使用的。
Standby(待机)
蓝牙设备最普通的空闲状态,没做什么。
不主动发信号
不与其他设备通信
耗电最低
蓝牙平时不开搜索/不连接时,就是 Standby。
Inquiry(搜索)
设备主动"寻找附近的蓝牙设备"。
类似你打开"蓝牙搜索设备"时发生的事情。
发 Inquiry 包
等待别人回应
用于发现设备(但不能连接)
这是 "搜到别人"。
Page(呼叫)
找到设备后,要建立连接 → 必须 Page 对方。
类似你"点连接某个蓝牙耳机"时发生的事情。
根据目标设备的地址呼叫它
成功会进入 Connection 状态
这是 "找到某人然后叫他接电话"。
连接后状态:Connection / Active / Sniff / Hold / Park
Connection(已连接,总状态)
表示设备之间已经建立链路。
下面几个状态都是 Connection 的子状态。
Active(活动状态)
蓝牙正常通信的状态。
设备可以收发数据包
时刻参与连接
功耗最高
Sniff(轻度节能模式)
"间歇性地醒来收包",一种低功耗模式。
设备不是每个 slot 都监听
只在固定间隔醒来
耗电比 Active 低,非常常用
例如蓝牙鼠标键盘通常在 Sniff 模式下工作。
Hold(暂停模式)
临时不接收 ACL 数据,但保持同步。
设备暂时不参与通信
放弃几个 slot 或一段时间
适合短暂省电
比如手机在传 ACL 数据时出现 SCO 语音需要更多资源,会让 ACL 进入 Hold。
Park(深度休眠模式)
连接仍然存在,但设备几乎不参与访问。
不接收大部分数据
只保持 minimal sync 信息
超低功耗
唤醒要更长时间
Park 模式适用于大量设备连接一个主设备时(例如蓝牙广播系统)
ACL / SCO / eSCO 的时序实现
蓝牙在空中通信时,不同类型的数据(ACL、SCO、eSCO)是怎样按照时间排队发送,怎么做重传,以及怎么保证语音链路的固定时间槽。
认识三种链路
| 类型 | 用途 | 是否允许重传 | 时间要求 |
|---|---|---|---|
| ACL | 普通数据(如文件、GATT 数据) | ✔ 重传 | 不严格 |
| SCO | 传统语音 | ❌ 不重传 | 非常严格(固定时间) |
| eSCO | 升级版语音 | ✔ 可重传,但有次数/时间限制 | 较严格 |
1)ACL 自动重传、SEQN 切换 ------ 解决丢包的机制
ACL(普通数据)可以重传:
- 蓝牙每发一包 ACL 数据,会带一个 SEQN 位(序号位)
- 对方收到包后会回 ACK
- 如果 ACK 没来,发送方就用同一个 SEQN 重发
- 每发成功一包后,SEQN 才会切换(0→1 或 1→0)
ACL 就像快递:没送到就重送,送成功才盖章换下一单号。
2)SCO/eSCO 的固定 slot 保留 ------ 语音链路是强实时的
语音数据要求"准点到达",不能乱序、不能迟太久。
蓝牙的做法是:
- 规定某些时隙(slot)必须让给 SCO/eSCO
- 这些 slot 不允许普通 ACL 乱占
- SCO 是最严格的:绝对固定、不能延迟
- eSCO 稍微灵活一点,但也要预留时间
语音链路就像高铁的固定时刻表,不能因为走货车(ACL)而延误。
3)eSCO 的重传窗口 ------ 在语音链路里留一点小弹性
eSCO 比 SCO "聪明",可以允许:
- 在两个语音包之间,留一个"小窗口"用来重传
- 如果语音包丢了,可在窗口内重发
- 但不能无限重传,因为下一次语音包时间 slot 也要保证
eSCO 就像公交车,虽然固定时间来一班,但允许司机在站和站之间稍微停一下,把刚刚没上车的人补上。
ACL:保证送达 → 允许多次重传 → 时间不紧张
SCO:严格准点 → 不重传 → 时间 slot 必须保留
eSCO:准点 + 允许少量重传 → 在两次语音包之间存在"重传窗口"
LMP(Link Manager Protocol)
LMP 是什么?
LMP = 蓝牙连接的"协商大脑"。负责和对端"对话",把各种配置谈好,再交给 LC 和 Baseband 去执行。
LMP 是 Link Manager 之间通信,用于:
建立/控制 logical transport
控制 physical link
所有 LMP 消息通过 ACL-C 逻辑链路发送
LMP 负责哪些事情?
① 连接控制(建立 / 断开)
LMP 负责:
建立 ACL 连接
断开连接(detach)
设置 supervision timeout
② 功率控制(ARP/功率调整)
LM 可以要求对方 "one step up / one step down" 增减发射功率,只影响这两个设备的 ACL 链路
就是 LMP 在协商设备发射功率,让连接稳定又省电。
③ AFH(自适应跳频)控制
通知对方启用/停 AFH(Adaptive Frequency Hopping)
下发 AFH channel map(哪些频点不能用)
④ SCO / eSCO 链路建立(语音链路)
Master 通过 LMP 发起 SCO 或 eSCO 建链请求
参数例如:
Tesco(包周期)
Desco(offset)
eSCO 重传窗口
包类型(HVx、EV3/EV5...)
耳机语音链路完全由 LMP 建立!
⑤ Sniff、Hold 等低功耗模式控制
LMP 控制:
sniff interval(Tsniff)
sniff offset(Dsniff)
sniff attempt、sniff timeout
蓝牙"省电"都靠它谈判,尤其是 TWS 耳机。
⑥ 加密和认证
Authentication(认证)
Pairing(配对)
Encryption(加密控制)
SSP(Secure Simple Pairing)
也就是密钥交换 → 认证 → 开加密,这些全是 LMP 负责。
⑦ 多槽(multislot)协商(决定能不能发 DH5、DH3)
LM 可以限制 packet slot 数量:
"Limit the number of consecutive slots"
⑧ 版本、特性、名字请求
LMP_version request
LMP_supported_features
LMP_host_name request
这些都是设备互相"介绍自己"。
LMP 消息格式
LMP 消息通过 DM1 包发送
17 字节以内
有 OpCode(操作码)
有 TID(0 = master 发起,1 = slave 发起)
TID 含义
0 Master 发起
1 Slave 发起
例子:
主机发起建立 eSCO → TID=0
从机发起功率调节 → TID=1
LMP 的运行流程
① 建立 ACL(基本连接)
手机(master)→耳机(slave)
LMP 开始对话:
版本交换
功能交换(features)
supervision timeout
piconet 参数
② 建立 eSCO(语音链路)
Master 发 LMP:
LMP_esco_link_req
给出 Tesco/Desco、包类型
Slave 如果支持 → LMP_Accepted
然后 Baseband/LC 就按照这个参数开始排时隙。
③ 调整参数(低功耗等)
在连接中可能:
进入 Sniff → LMP 控制
改变功率 → LMP 控制
更新 AFH → LMP 控制
④ 加密/配对
数据即将传输时:
LMP_pairing
LMP_authentication
LMP_start_encryption
HCI(Host Controller Interface)
下面我根据你提供的 PPT 内容 ,用 小白能完全听懂 的方式,给你详细讲解:
什么是 HCI(Host Controller Interface)
APP(应用层)
↓
Host 层(协议、GATT、L2CAP......)
↓ ← 通过 HCI 交互
Controller 层(底层:Baseband、射频、Link Manager)
↓
2.4GHz 无线信号
简单来说:
- Host(上层):跑在 MCU、操作系统中,处理协议、GATT、连接逻辑等
- Controller(下层):跑在蓝牙芯片里,负责射频、Baseband、加密、LMP 等
HCI 就是连接 Host 和 Controller 的"桥"
HCI 的作用
HCI 负责:
-
Host → Controller:发指令
- 开个蓝牙
- 扫描
- 建立连接
- 发数据
- 设置发射功率
-
Controller → Host:回事件
- 扫描到设备了
- 连接成功
- 收到数据
- 重连失败
- 配对成功
-
双向的数据收发
- Host 发 ACL 数据(GATT、ATT 等)
- Controller 收到空口数据后回给 Host
HCI = "上下层通信协议 + 命令集合"
HCI 由什么组成?
PPT 给的说明很标准,我给你转成容易懂的形式:
HCI Driver(在 Host 上)
HCI Firmware(在 Controller 上)
HCI Transport(两者之间的通道)
HCI Driver(运行在 Host)
相当于:
- 驱动程序
- API 封装
- 把 Host 的命令装成 HCI 指令
HCI Firmware(运行在控制器)
相当于:
- 蓝牙芯片上的解析器
- 接收 HCI 命令并让硬件执行
- 把事件回报给 Host
HCI Transport(传输通道)
比如:
- UART(手机、单片机最常用)
- USB(电脑蓝牙)
- SPI
- SDIO
HCI 有哪些数据包类型?
1)HCI Command Packet
Host → Controller
结构:
Opcode(命令类型)
参数(比如扫描时间、发射功率)
比如:
- HCI_Reset
- HCI_Read_Local_Name
- HCI_LE_Set_Advertising_Parameters
2)HCI Event Packet
Controller → Host
比如:
- 扫描结果
- 连接建立成功
- 指令执行成功(HCI_Command_Complete)
- 遇到错误(如拒绝连接)
3)HCI ACL Data Packet
用于传普通数据(GATT、L2CAP、ATT)。
Host → Controller
Controller → Host
这就是 BLE 大部分数据的流动方式。
4)HCI Synchronous Data Packet
用于 SCO/eSCO(语音)数据
只有当控制器把 SCO 传给 Host 才会用(大部分 BLE 产品不会用)。
HCI 的流控(flow control)
- Packet-based flow control
- Data-block-based flow control
Host → Controller:
Host 发 ACL 数据太快,会导致芯片 buffer 满。
解决方法:
- Host 通过 HCI 命令询问控制器 buffer 大小
- 控制器回 Event 告诉 Host:我处理完多少包了(HCI_Number_Of_Completed_Packets)
- Host 根据这个信息控制发送速度
Controller → Host:
Host 也有 buffer
Controller 会通过:
- HCI_Set_Host_Controller_To_Host_Flow_Control
- HCI_Host_Buffer_Size
- HCI_Host_Number_Of_Completed_Packets
- 简单理解:HCI 保证上下层不会"塞车"。
恒玄芯片 SDK 中的 HCI 对应位置(重点!)
恒玄 SDK 中:
-
Controller(BB、PHY、LMP)都是固化在 ROM 中
-
Host 层由你写的 APP 在 MCU 上运行
-
HCI 通常内部实现,不需要你手写
典型文件:bes_hci_interface.c
hci_tl.c
bt_hci_if.c
bt_stack_task.c
常见 API:
hci_send_acl_data()
hci_send_cmd()
hci_recv_event()
如果用 BES 的全功能 SDK,你几乎看不到"裸 HCI"
因为 SDK 已经把 HCI+L2CAP+GATT 全封装好了。