从零实现BLE协议栈(2-3)nRF52 的 TIFS 硬件实现

带着镣铐跳舞:nRF52 的 TIFS 硬件实现

本章示例代码:https://github.com/ixbwer/write-BLE-stack-from-scratch/tree/master/02_active_scan

前提知识

阅读本文需要具备以下基础:

  • 理解 T_IFS 是什么以及为何 CPU 无法胜任(2-2 篇有详细讲解):本文直接从"计时必须下沉到硬件"出发,不重复 T_IFS 的成因分析。
  • 理解 TASK / EVENT 模型(1-1 有详细讲解):本文大量使用 TASKS_TXENEVENTS_ENDEVENTS_COMPARE 等寄存器。
  • 理解 SHORTS 机制(1-1 有详细讲解):本文后半段的 SW TIFS 刻意不使用 DISABLED_RXEN/TXEN 这条 SHORTS,理解 SHORTS 是什么,才能理解"为什么要绕开它"。
  • 理解三包握手场景(2-1 篇有详细讲解):本文的代码示例以 ADV_IND → SCAN_REQ → SCAN_RSP 交互为场景。

一、两种实现:HW TIFS 和 SW TIFS

nRF52 上有两套完全不同的机制可以实现 T_IFS 的精确计时。它们解决同一个问题,用的却是截然不同的策略:

HW TIFS(硬件 TIFS):RADIO 外设内置了一个 TIFS 寄存器。向它写入 150 后,RADIO 自己记住这个间隔,在每次 RX/TX 结束后自动以芯片级时钟精度完成"110 µs 后触发下一次 Ramp-Up"的动作,CPU 完全不参与计时。一行寄存器写入,搞定一切。

SW TIFS(软件 TIFS):完全不依赖 RADIO 内置逻辑,改用外挂的 TIMER1 外设作为精度为 1 µs 的时钟,用 PPI(可编程外设互联) 把 Timer 的比较事件直接连线到 RADIO 的 TXEN/RXEN 输入。Timer 计到 110 µs 时,PPI 触发 Ramp-Up,150 µs 时第一个 bit 准时发出——全程不经过 CPU。

Zephyr BLE Controller 用一个编译开关来选择:CONFIG_BT_CTLR_TIFS_HW=y 时走 HW TIFS 路径,关闭时走 SW TIFS 路径。理解两者的差异,就是理解 Zephyr BLE Controller 底层两条计时路径的真实工作方式。


二、HW TIFS:让 RADIO 自己记住 150 µs

使用 HW TIFS 只需要一行配置:

NRF_RADIO->TIFS = 150;   /* 单位 µs */

这行写入后,RADIO 外设内部的一个硬件倒计时电路被激活。它的工作逻辑是这样的:

每次 RADIO 完成一帧收发(EVENTS_END 触发),RADIO 内部的倒计时电路立即启动。这个电路做了一件聪明的事:它不是从 150 µs 开始倒计时再触发 Ramp-Up,而是自动从 150 µs 中减去 Ramp-Up 时间(约 40 µs),然后在 110 µs 后触发 TXEN/RXEN。这样,PLL 有 40 µs 完成预热,TX/RX 在 150 µs 时恰好准备好发出第一个 bit。

正确的 SHORTS 配置是:

NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk   |  /* PLL 就绪后自动开始收发 */
                    RADIO_SHORTS_END_DISABLE_Msk    |  /* 收发完毕后自动进入 DISABLED */
                    RADIO_SHORTS_DISABLED_RXEN_Msk;   /* DISABLED 后自动切换到 RX(或 TXEN)*/

CPU 只需要在 EVENTS_END 触发后的约 100 µs 窗口内,PACKETPTR 更新为指向待发送的回包缓冲区。精确的 150 µs 计时完全由硬件保证。


三、PPI:跨外设的飞线

HW TIFS 看起来完美,但有时候不够用——例如,当 Coded PHY(BLE 长距离模式)的 Ramp-Up 时间与 1M PHY 不同时,内置 TIFS 电路的行为就可能需要额外补偿。这时需要一套更灵活的方案:使用 PPI 完全掌控计时逻辑

在解释 SW TIFS 之前,先单独介绍 PPI(Programmable Peripheral Interconnect,可编程外设互联)——这是理解整套方案的核心。

你在 1-1 里学到 SHORTS:它是 RADIO 外设内部的硬件短路,把同一外设内的一个 EVENT 和一个 TASK 用导线直连。SHORTS 的限制是:它只能连接同一外设内部的 EVENT 和 TASK,无法跨外设。

PPI 是 SHORTS 的超集,是整个 nRF52 芯片级别的"飞线板"。它允许你把任意外设的任意 EVENT 直接连到任意外设的任意 TASK,完全不经过 CPU。就像在电路板上焊一根飞线,把两个芯片的引脚直接连通——信号触发是纯电气的,不过 MCU 的任何软件路径。

nRF52840 有 20 个可编程 PPI 通道(编号 0~19),每个通道独立配置:

/* 把"外设 A 的事件 X"连接到"外设 B 的任务 Y" */
nrf_ppi_channel_endpoint_setup(
    NRF_PPI,
    PPI_CHANNEL_0,                        /* 使用哪条通道 */
    (uint32_t)&PERIPHERAL_A->EVENTS_X,   /* 触发源(EVENT 寄存器地址) */
    (uint32_t)&PERIPHERAL_B->TASKS_Y     /* 目标(TASK 寄存器地址) */
);
nrf_ppi_channels_enable(NRF_PPI, BIT(PPI_CHANNEL_0));  /* 启用该通道 */

PPI 通道一旦启用,每次"外设 A 的事件 X"触发,"外设 B 的任务 Y"就在硬件层面立即执行,延迟在纳秒量级,完全不经过任何软件代码。


四、SW TIFS 的架构:三条 PPI 通道

SW TIFS 用三条 PPI 通道构建了一套独立的计时系统,脱离 RADIO 内置逻辑,用 TIMER1 作为精确时钟:

SW TIFS 的 PPI 连线架构:

外设 A                    PPI 通道     外设 B(目标动作)
─────────────────────────────────────────────────────────────
RADIO EVENTS_END    ──[CH14]──→  TIMER1 TASKS_CLEAR    (包结束 → Timer 归零)
TIMER1 CC[0] 匹配   ──[CH15]──→  RADIO  TASKS_RXEN     (110 µs → 触发 RX 预热)
TIMER1 CC[1] 匹配   ──[CH16]──→  RADIO  TASKS_TXEN     (100 µs → 触发 TX 预热)

CH14:全程始终启用,每次包结束都让 Timer 归零并重新计数。
CH15:在需要 TX→RX 切换之前启用,CC[0]=110µs 比较匹配时触发 RXEN。
CH16:在需要 RX→TX 切换之前启用,CC[1]=100µs 比较匹配时触发 TXEN。

★ CC[0] 和 CC[1] 不同的原因:
  TX END 与空口最后 bit 对齐(无延迟),所以 CC[0] = T_IFS - Ramp = 110µs。
  RX END 比空口最后 bit 晚 ~10µs(RX chain delay),必须额外扣除,
  所以 CC[1] = T_IFS - Ramp - RX_chain_delay = 150 - 40 - 10 = 100µs。

TIMER1 被配置为每 tick 等于 1 µs(16 MHz 系统时钟经过 2⁴ = 16 分频后得到 1 MHz,1 µs/tick):

NRF_TIMER1->MODE      = TIMER_MODE_MODE_Timer;
NRF_TIMER1->PRESCALER = 4;          /* 16 MHz / 2^4 = 1 MHz → 1 µs/tick */
NRF_TIMER1->BITMODE   = TIMER_BITMODE_BITMODE_16Bit;
NRF_TIMER1->TASKS_START = 1;        /* 立即开始计数 */

CC(Compare/Capture)是 TIMER 外设的比较寄存器。当 Timer 的计数值达到 CC[x] 里写入的数值时,EVENTS_COMPARE[x] 会被置 1——这个事件就是 PPI 通道的触发源:

#define T_IFS_US          150
#define RAMP_UP_US         40    /* TX/RX Ramp-Up 时间,nRF52832 PS Section 6.20.15.7 */
#define RX_CHAIN_DELAY_US  10    /* RX END 比空口最后 bit 晚 ~10µs */

/* CC[0]: TX→RX 转换 (TX END 后切 RX)
 * TX END 与空口最后 bit 对齐(无 chain delay)
 * 110 + 40 µs Ramp-Up = 150 µs 时 RX 启动 */
nrf_timer_cc_set(NRF_TIMER1, CC_IDX_RXEN, T_IFS_US - RAMP_UP_US);  /* = 110 */

/* CC[1]: RX→TX 转换 (RX END 后切 TX)
 * RX END 比空口最后 bit 晚 RX_CHAIN_DELAY(~10µs),必须额外扣除
 * 100 + 40 + 10 = 150 µs 时 TX 第一个 bit 出现在天线上 */
nrf_timer_cc_set(NRF_TIMER1, CC_IDX_TXEN,
                 T_IFS_US - RAMP_UP_US - RX_CHAIN_DELAY_US);  /* = 100 */

五、SW TIFS 不能使用 DISABLED_RXEN/TXEN SHORTS

这里有一个容易混淆的关键差异。在 HW TIFS 模式下,SHORTS 配置包含 DISABLED_RXEN

/* HW TIFS 的 SHORTS 配置 */
NRF_RADIO->SHORTS = READY_START | END_DISABLE | DISABLED_RXEN;

而在 SW TIFS 模式下,DISABLED_RXEN(和 DISABLED_TXEN)这两条 SHORTS 绝对不能出现

/* SW TIFS 的 SHORTS 配置(不含任何 DISABLED_RXEN/TXEN!) */
NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
                    RADIO_SHORTS_END_DISABLE_Msk;

原因是:SW TIFS 通过 PPI 的 CC[x] 事件来控制 Ramp-Up 的触发时机(CC[0]=110 µs 或 CC[1]=100 µs 后)。如果同时启用了 DISABLED_RXEN SHORTS,RADIO 进入 DISABLED 状态后会立刻触发 RXEN——完全不等 PPI 的 CC 匹配,Ramp-Up 在 0 µs 时就开始了,最终 TX 或 RX 会在 40 µs 处启动,而不是 150 µs 处。T_IFS 的计时逻辑就此彻底被破坏。

如果不小心保留了 DISABLED_RXEN SHORTS,系统不会崩溃,但所有回包都会提前约 110 µs 发出,必然导致通信对端 CRC 校验失败、扫描请求无法建立——属于静默的时序错误,调试极为困难。

此外,CC 值需要在最开始就预置好,由 TIMER1 在每次 EVENTS_END 触发时归零(PPI CH14)来刷新计时基准。归零之后,下一次 CC 匹配就会精确地在对应的 µs 后发生(CC[0]=110 µs 用于 TX→RX,CC[1]=100 µs 用于 RX→TX)。这是 SW TIFS 和 HW TIFS 最大的思维差异:HW TIFS 是"响应式"的(END 发生了再自动启动计时),SW TIFS 是"预置式"的(提前设好目标,用归零事件来刷新基准)。

六、HW TIFS 与 SW TIFS 的对比

特性 HW TIFS SW TIFS (PPI 方案)
配置 RADIO->TIFS = 150 TIMER1 + PPI 通道
灵活性 较低(固定逻辑) 极高(支持 Coded PHY 等各种模式)
SHORTS 必须开启 DISABLED_RXEN 严禁开启 DISABLED_RXEN
CPU 职责 只需更新数据指针 需管理 PPI 开关及清除 EVENT
适用场景 简单、标准 BLE 应用 复杂协议栈、多 PHY 切换(Zephyr 默认)

本系列教程同款硬件:👇
芯片: nRF 52832 开发板
工具: nRF 52840 BLE Dongle 蓝牙嗅探器
工具: 逻辑分析仪
工具: BPA low energy 蓝牙分析仪

posted @ 2026-03-28 10:54  ixbwer  阅读(76)  评论(0)    收藏  举报