3. 链路层状态机与并发调度:空闲、连接、同步状态转换、多连接时隙分配

BLE 链路层(Link Layer)的核心是一个有限状态机,控制着设备在 空闲、连接、同步 等模式下的行为。当系统需要同时维护多个连接时,状态转换必须与精确的时隙分配配合,避免包冲突。本章剖析 LL 状态机模型,并重点讲解连接事件调度算法、偏移量冲突检测与常见错误对策。

3.1 BLE 链路层状态机

BLE 规范定义了 5 种主要状态:Standby、Advertising、Scanning、Initiating、Connection,以及 2 种同步相关状态:Synchronization Train 和 Periodic Advertising Sync。根据题目要求,本章聚焦于核心三态:空闲(Standby)、连接(Connection) 和 同步(Synchronization)。状态转换由特定事件触发:

当前状态触发事件下一状态说明
Standby连接参数应答完成(LL_CONNECTION_IND)Connection发起者或广播者进入连接态
Standby同步建立请求(LL_SYNC_IND)Synchronization接收等时流或周期性广播同步
Connection连接超时或本地断开Standby重置控制器
Connection同步训练请求(LL_SYNC_INFO)Synchronization从中继等时流同步信息
Synchronization同步丢失或超时Standby自动回退到空闲
Synchronization连接参数更新(可选)Connection某些实现允许从同步直接进入连接
链路层并发关键:每个连接或同步流都对应一个独立的链路层实例,共享同一个射频时序。状态机必须由 调度器 统一协调,否则多个事件的 tx/rx 窗口会相互覆盖。
实现提示:在 Zephyr 或 Nordic 的协议栈中,链路层状态机通常用 enum ll_state 加 switch 实现,每个连接分配一个 struct ll_conn 实例,内嵌当前状态和调度参数。

3.2 多连接时隙分配原理

每一个连接(或同步流)在建立时都要协商 连接间隔(connInterval)、连接偏移量(connOffset)和 连接窗口(connWindow)。调度器的工作就是将多个连接的锚点(anchor point)映射到时间轴上,确保任意两个连接事件不重叠。

  • 连接间隔:两次连接锚点之间的时间,单位 1.25 ms。典型值 7.5 ms ~ 4 s。
  • 连接偏移量:相对于全局基准的延迟,单位与间隔相同。
  • 连接窗口:主设备在锚点附近监听从设备数据包的时间范围,通常 2 – 4 个单元。

调度器采用 空闲间隙扫描 + 偏移量试错 的方法:当新连接建立时,扫描已有连接占用的时间区间,找到第一个长度 ≥ 窗口的空白区段,将新连接的偏移量设置为该区段起点。若连续冲突,则逐次后移 1.25 ms 直至插入成功。

// 伪代码:简单偏移量分配
bool assign_offset(uint16_t interval, uint16_t window) {
    uint16_t time = 0;
    while (time + window <= MAX_SCHEDULE) {
        if (!collision_check(time, window)) {
            new_conn.offset = time;
            insert_occupied(time, window);
            return true;
        }
        time += 1; // 1 个单元 = 1.25 ms
    }
    return false; // 无可用时隙
}
典型错误:若窗口值被设为大于连接间隔的一半,则无论怎么偏移都会与下一个锚点冲突。此情况应在连接参数更新时拒绝(返回 Invalid Parameters)。

3.3 状态机与调度的协同流程

实际嵌入式系统中,调度器运行在 射频中断上下文,状态机运行在 协议栈线程上下文。两者通过事件队列通信:

  1. 调度器在每次连接事件开始时更新对应 ll_conn 的 expired_event_count,若缺失超过 connSupervisionTimeout 则触发断开事件。
  2. 状态机收到断开事件后进入 Standby 状态,并通知调度器释放该连接的占位区间。
  3. 连接参数更新请求(LL_CONNECTION_PARAM_REQ)会变更 interval/offset,调度器需立即重新评估冲突,若无法满足则拒绝。

3.4 调试与验证

推荐使用 双通道逻辑分析仪 捕获射频使能引脚(RF_ACTIVE)和连接事件中断信号。一个典型的调试步骤:

# 在协议栈开启调度器日志(以 Zephyr 为例)
CONFIG_BT_LL_SW_SCHEDULER_DEBUG=y
CONFIG_BT_DEBUG_LOG=y

# 观察输出日志
[0:00:01.234] LL: conn 0x2000 anchor=1250 offset=10 interval=40
[0:00:01.235] LL: conn 0x2001 anchor=1290 offset=50 interval=30
[0:00:01.236] LL: SCHED: conn 0x2000 at 1260~1270 OK
[0:00:01.237] LL: SCHED: conn 0x2001 at 1340~1350 OK

若日志中出现 SCHED: overlap detected,说明偏移量分配算法失败,应检查窗口大小或连接间隔。

3.5 性能 & 稳定性优化

优化项方法效果
偏移量间距最小化将窗口值设定为 2 单元(2.5 ms),尽可能密排连接提高总连接数上限
同步组调度将等时流(CIS)的连接间隔统一为公约数,锚点对齐减少调度器重算频率
动态调整连接窗口在空闲时扩展窗口,在冲突时缩小窗口平衡可靠性与时隙利用率
预计算分配表在连接建立后立即计算未来 N 个锚点,生成 DMA 描述符降低中断开销
关键指标:在 1M PHY 下,一个射频核心最多可承载约 20 个连接(间隔 30 ms、窗口 2.5 ms)。超出该数量时,应考虑使用多射频模块或减少每连接的数据吞吐量。
链路层状态机与时隙分配 Standby Connection Sync conn_ind sync_ind timeout / disc disconnect 连接事件时间轴 0 10 20 30 40 Conn#1 Conn#2 Sync 冲突 调度要点 ● 每个连接/同步流在时间轴上占据一个窗口(绿色/橙色/青色矩形)。 ● 偏移量分配保证矩形不重叠;红色虚线表示 Conn#1 与 Conn#2 在锚点 30 处冲突。 ● 调度器通过移动 Conn#2 的偏移量至 32 单元避免冲突,或缩小窗口。

3.6 常见错误与对策

  • 连接事件重叠:因偏移量分配未考虑窗口尾部,导致两个连接在同一微秒内占用射频。解决方法:在分配时预留 1 个单元(1.25 ms) 作为防护间隔。
  • 同步流漂移:等时流锚点受晶振漂移影响,累积后可能侵入相邻连接窗口。对策:每个连接事件后调度器执行 漂移补偿,修正下一次锚点偏移。
  • 状态机死锁:调度器因冲突拒绝新连接,但上层应用持续重试,导致协议栈阻塞。应在驱动层增加 max_retry 计数器,超过后返回Insufficient Resources。
  • 调度器上下文超时:若调度函数内执行了阻塞操作(如打印日志),会导致射频中断丢失。务必确保调度器代码为 无锁且 O(1)。
硬件相关常见错误:Nordic nRF5 系列在使用多连接时,必须将 RADIO_POWER 模式设为 RADIO_MODE_FAST 以减少预热时间。若使用默认模式,连接窗口应至少增加 80 μs 预热时间,否则容易丢包。