【Ble】(15)ble入门
一、几个基本角色概念
两个角色(GAP 层定义)
- Peripheral(外设/从机)= 你的戒指:发广播、被连接、提供数据。资源少、省电。
- Central(中心/主机)= 手机:扫描、发起连接、主动读写。
两个核心框架
- GAP(Generic Access Profile):管"怎么被发现、怎么连接"——广播、扫描、连接、断开。你日志里的
[GAP ROLE N]就是它的状态机。 - GATT(Generic Attribute Profile):管"连上之后怎么交换数据"——服务、特征、读/写/通知。A5C3/A5C4/A5C5 就是 GATT 的东西。
协议栈分层(从下到上,理解即可):
PHY(2.4GHz 物理射频,1M/2M 调制) └ LL 链路层(广播、连接、跳频、CRC、重传)——跑在芯片 ROM 里 └ L2CAP(通道复用、分包重组) └ ATT(属性协议:读/写一个个"属性") └ GATT(把属性组织成 服务/特征) └ SMP(配对加密,你现在没用) GAP(横跨:广播/连接策略 + 设备名)你写命令是在最上面 GATT/ATT 层,但底下 LL/PHY 一直在默默跳频、重传、维持连接。
二、完整流程(对应你的日志)
阶段 0:上电初始化
SDK Version ID … / rfClk … / [ble-ring] service registered (8 attrs) ======================SimpleBLEPeripheral_Init Done==================== [ble-mac] static C2:09:4D:FC:B7:40 cfg=0 [GAP ROLE 1]- 协议栈初始化、把 GATT 属性表(服务/特征)登记进去(
service registered 8 attrs)。 - 设好设备地址(那个出厂唯一 MAC)。
[GAP ROLE 1]=GAPROLE_STARTED:栈起来了,但还没开始广播。
阶段 1:广播(Advertising)
[GAP ROLE 2] = GAPROLE_ADVERTISING设备周期性在3 个广播信道(37/38/39,频率 2402/2426/2480 MHz——特意避开 WiFi)上发广播包。
- 广播间隔:每隔几十~几百毫秒发一轮(可配)。间隔越短越好被发现,但越费电。
- 广播包内容:设备名(
BS-Ring2)、Flags、有时带厂商数据。手机扫描看到的名字/地址就来自这里。 - 这是单向广撒,谁都能收,还没有"连接"。
阶段 2:扫描与发现(手机侧)
手机在同样 3 个信道上监听(扫描)。听到你的广播包 → 显示在列表里(名字 + 地址 + 信号强度 RSSI)。
- 手机可选发一个Scan Request问"还有更多信息吗?",设备回Scan Response(我们把地址尾段塞这里)。
- 这一步只是"看见",还没连。
阶段 3:建立连接(Connection)
你在手机点"连接" → 手机(此刻变成发起方)发一个CONNECT_IND(连接请求)包,里面包含一整套连接参数:
- 接入地址(Access Address):这条连接的唯一标识。
- 连接间隔(Connection Interval):每隔多久双方"碰一次头"(7.5ms~4s)。
- 从机延迟(Slave Latency):从机可以跳过几次碰头不回(省电)。
- 监督超时(Supervision Timeout):多久收不到对方就判定"断了"。
- 跳频参数(信道图 + hop):之后在37 个数据信道上自适应跳频通信(抗干扰)。
设备收到 → 进入连接态:
[GAP ROLE 5] = GAPROLE_CONNECTED从这一刻起,双方在每个"连接事件(Connection Event)"按约定的连接间隔在跳频信道上短暂收发,平时射频关掉省电。这就是 BLE 省电的关键——不是一直开着,是"约好时间见面"。
阶段 4:连接参数协商(你日志里这条)
[2Mbps | DLE | MTU 247]连上后双方会协商把"管道"加粗加快:
- 2Mbps PHY:物理层从 1M 提到 2M,传输更快。
- DLE(Data Length Extension):链路层单包数据从 27 字节扩到最多 251 字节。
- MTU 247:ATT 层单次能传的最大字节数从默认 23 提到 247。
→ 意义:一次能塞更多数据、更快。你的命令帧最长 254 字节,MTU 247 基本一次一帧搞定(再大就要分包)。
阶段 5:GATT 服务发现(上一条消息讲过)
手机问"你有哪些服务/特征?",设备把整张 ATT 表回出去 → 手机解析出 A5C3 服务、A5C4(可写)、A5C5(读+通知)及各自句柄。这是 app 不用预先知道 UUID 就能操作的原因。
阶段 6:数据交换(你正在做的)
连上 + 发现完,才真正收发业务数据:
- 写(Write):手机往A5C4写命令帧 → 设备
[ble-ring] rx 14 B→ 解析 → handler。 - 通知(Notify):设备要主动推数据,手机必须先订阅——往 A5C5 的CCCD(0x2902)写
01 00(就是你点"启用通知"做的)。订阅后设备GATT_Notification把回包推到 A5C5 → 手机收到。- 这就是为什么没订阅时回包被丢(我们之前
-> -1的坑)。
- 这就是为什么没订阅时回包被丢(我们之前
- (还有Read/Indicate等方式,你这套用的是 Write + Notify。)
阶段 7:连接维持与断开
- 连接期间,即使没数据,双方也按连接间隔定期碰头(空包)以维持链路;超过监督超时没碰上头 → 判定断开。
- 断开(对方主动断 / 超时 / 距离远信号丢):
[GAP ROLE 4] = GAPROLE_WAITING_AFTER_TIMEOUT(刚断,等待期) [GAP ROLE 2] = 重新开始广播断开时 CCCD 订阅会被清掉→ 重连后必须重新启用通知(你之前重连后第一次-> -1就是这个)。
三、把你设备的 GAP ROLE 状态串起来
[GAP ROLE 1] STARTED 栈就绪,未广播 ↓ [GAP ROLE 2] ADVERTISING 广播中(手机能扫到) ↓ 手机发起连接 [GAP ROLE 5] CONNECTED 已连接 → 协商(2M/DLE/MTU)→ 发现 → 收发数据 ↓ 断开/超时 [GAP ROLE 4] WAITING_AFTER_TIMEOUT 刚断 ↓ [GAP ROLE 2] ADVERTISING 回到广播,等下次连接(还有 0=INIT、3=WAITING、6=CONNECTED_ADV、7=ERROR,你常见的是 1→2→5→4→2 这个循环。)
四、几个容易混淆的点(给新手)
| 概念 | 一句话 |
|---|---|
| 广播 vs 连接 | 广播=单向广撒谁都能收;连接=一对一私有管道,要先建立 |
| GAP vs GATT | GAP 管"连不连";GATT 管"连上后传什么数据" |
| MTU vs DLE | MTU 是 ATT 层一次能传多少;DLE 是链路层单包多大。两层独立,都影响吞吐 |
| Write vs Notify | Write=手机推给设备(主动);Notify=设备推给手机(需先订阅 CCCD) |
| 连接间隔 vs 广播间隔 | 广播间隔=多久发一次广播;连接间隔=连上后多久碰一次头 |
| 配对/绑定(SMP) | 加密 + 记住对方。你现在没用(明文、每次重新发现),量产可能要加 |
五、你现在所处的位置 & 还没碰的部分
- 已打通:广播 → 连接 → 服务发现 → Write 命令 → Notify 回包(电量查询验证过)。
- 还没做/可选:
- 配对加密(SMP):目前明文传输,谁连上都能读写。
- 连接参数主动优化:从机可请求更省电的连接间隔/延迟(低功耗阶段会用到)。
- 断连自动重连 / 白名单:只让绑定过的手机连。
- 分包重组:超过一个 MTU 的大数据(历史记录、OTA)。
