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。

  1. Idle 状态:从机深度睡眠,只保留 32kHz 时钟计数。计数器一直跑,等它匹配到下一个锚点时间。
  2. Wakeup 状态:在锚点前 1ms,计数器触发中断。从机打开主晶振,启动射频,开始锁相环(PLL)。
  3. Rx 窗口:从机在锚点时刻打开接收窗口,等待主机的数据包。如果主机有数据要发,从机就收下来,然后回 ACK。
  4. Tx 窗口:如果从机自己有数据要发(比如传感器数据),它会在收到主机的包后,在下一个微帧里把自己的包发出去。或者,如果主机发的是空包(Empty PDU),从机可以直接在同一个连接事件里回数据。
  5. 回 Idle:收发完成后,从机关闭射频,切回低速时钟,继续睡到下一个锚点。

嗯,这里要注意:从机不能主动发包。它只能在主机发完包之后,在主机预留的 Tx 窗口里回包。这是 BLE 主从架构的核心规则。

个人经验:如果你发现从机发数据总是延迟一个连接间隔,大概率是主机端的调度策略问题。我曾经调试过一个心率设备,从机数据总是晚 30ms 才发出去,后来发现是主机在连接事件里先处理了其他从机,导致这个从机的 Tx 窗口被挤到下一个事件了。

3.4 状态机流程图

下面这张图是我自己画的状态机流转,你一看就明白。

从机链路层状态机:睡眠唤醒发包流程 从机链路层状态机(连接状态) Idle(睡眠) Wakeup(唤醒) Rx(接收) Tx(发送) 锚点前 1ms 锚点到达 主机发空包 收到主机包 超时无数据 发送完成 注意:从机只能在主机发完包后回包,不能主动发起连接事件

3.5 避坑指南:睡眠模式下的时钟漂移

我曾经在一个量产项目上吃过亏。从机用内部 RC 振荡器休眠,精度只有 ±500ppm。连接间隔设了 100ms,结果跑着跑着,从机的锚点时间漂了将近 50μs。主机那边锚点窗口只有 150μs,几次漂移之后,主机就收不到从机的包了。

解决办法有两个:

  • 用外部 32kHz 晶振:精度能到 ±20ppm,漂移小得多。
  • 增大主机端的 rx window:但这样会多耗电,不划算。

警告:千万不要在睡眠模式下关闭所有时钟!至少保留一个低速计数器,否则你永远醒不过来。我见过有同事把 RTC 也关了,结果设备变砖,只能重新烧录。

3.6 小结

从机的链路层状态机,说白了就是三个状态来回转:睡、醒、干活。核心是把握好 wakeup offset 和时钟精度。你只要记住:从机永远是被动的,它只能在主机划定的时间窗口里活动。