3. 休眠模式下的链路层状态机:从机在连接状态下的睡眠、唤醒、发包流程
好,咱们进入正题。上一章讲了连接事件和锚点,这一章我们聚焦在从机(Slave)身上,看看它在休眠模式下,链路层状态机到底是怎么转的。
说白了,BLE 从机大部分时间都在睡觉。你想想看,一个纽扣电池要撑一年,总不能一直开着射频吧?所以,链路层状态机里专门设计了几个状态,让设备能睡、能醒、能干活。
3.1 连接状态下的三个子状态
我个人习惯把连接状态下的从机链路层拆成三个子状态来看:
- Idle(空闲):射频关闭,时钟可能跑在低速模式。说白了就是“假寐”。
- Wakeup(唤醒):从机在锚点到来前,提前打开射频和高速时钟,准备接收主机的数据包。
- Tx/Rx(收发):在连接事件窗口内,完成数据包的发送和接收。
这三个状态在每次连接事件里都会走一遍。我刚开始做 BLE 项目时,以为从机只在收到包时才醒,后来发现不对——它得提前醒,不然来不及锁频。
核心要点:从机在连接间隔内,绝大多数时间处于 Idle 状态。只有锚点附近那几百微秒到几毫秒,才会进入 Wakeup 和 Tx/Rx。
3.2 睡眠→唤醒的时序细节
这里有个关键参数叫 wakeup offset。它决定了从机提前多久从 Idle 切到 Wakeup。
为什么会需要这个偏移量?因为从机休眠时,晶振可能跑在 32kHz 的 RC 振荡器上,精度差得远。等它切到 16MHz 或 32MHz 的主晶振时,需要时间稳定,还要重新校准射频频率。
我在项目中遇到过一个问题:某款芯片的 wakeup offset 设得太小,导致从机还没锁好频,主机的包就发过来了。结果连续丢包,连接超时。后来我把 offset 从 500μs 改到 1.2ms,问题就解决了。
| 参数 | 典型值 | 说明 |
|---|---|---|
| wakeup offset | 0.5 ~ 2.0 ms | 从机提前唤醒的时间,取决于晶振稳定时间 |
| rx timeout | 300 ~ 500 μs | 从机等待主机包的最大时间,超时则回 Idle |
| tx timeout | 300 ~ 500 μs | 从机发送完包后等待 ACK 的超时 |
3.3 发包流程:从机视角
好,咱们走一遍完整的流程。假设连接间隔是 30ms,锚点偏移是 0。
- Idle 状态:从机深度睡眠,只保留 32kHz 时钟计数。计数器一直跑,等它匹配到下一个锚点时间。
- Wakeup 状态:在锚点前 1ms,计数器触发中断。从机打开主晶振,启动射频,开始锁相环(PLL)。
- Rx 窗口:从机在锚点时刻打开接收窗口,等待主机的数据包。如果主机有数据要发,从机就收下来,然后回 ACK。
- Tx 窗口:如果从机自己有数据要发(比如传感器数据),它会在收到主机的包后,在下一个微帧里把自己的包发出去。或者,如果主机发的是空包(Empty PDU),从机可以直接在同一个连接事件里回数据。
- 回 Idle:收发完成后,从机关闭射频,切回低速时钟,继续睡到下一个锚点。
嗯,这里要注意:从机不能主动发包。它只能在主机发完包之后,在主机预留的 Tx 窗口里回包。这是 BLE 主从架构的核心规则。
个人经验:如果你发现从机发数据总是延迟一个连接间隔,大概率是主机端的调度策略问题。我曾经调试过一个心率设备,从机数据总是晚 30ms 才发出去,后来发现是主机在连接事件里先处理了其他从机,导致这个从机的 Tx 窗口被挤到下一个事件了。
3.4 状态机流程图
下面这张图是我自己画的状态机流转,你一看就明白。
3.5 避坑指南:睡眠模式下的时钟漂移
我曾经在一个量产项目上吃过亏。从机用内部 RC 振荡器休眠,精度只有 ±500ppm。连接间隔设了 100ms,结果跑着跑着,从机的锚点时间漂了将近 50μs。主机那边锚点窗口只有 150μs,几次漂移之后,主机就收不到从机的包了。
解决办法有两个:
- 用外部 32kHz 晶振:精度能到 ±20ppm,漂移小得多。
- 增大主机端的 rx window:但这样会多耗电,不划算。
警告:千万不要在睡眠模式下关闭所有时钟!至少保留一个低速计数器,否则你永远醒不过来。我见过有同事把 RTC 也关了,结果设备变砖,只能重新烧录。
3.6 小结
从机的链路层状态机,说白了就是三个状态来回转:睡、醒、干活。核心是把握好 wakeup offset 和时钟精度。你只要记住:从机永远是被动的,它只能在主机划定的时间窗口里活动。