从零实现BLE协议栈(4-3)追逐游戏:实现 CSA#1 跳频算法
追逐游戏:实现 CSA#1 跳频算法
前提知识
阅读本文需要具备以下基础:
- 理解
CONNECT_IND的ChM、Hop字段(第 3-1 篇有详细讲解):跳频算法的所有输入参数——信道图(Channel Map)和跳步数(Hop)——都来自CONNECT_IND。 - 理解永动机(第 4-2 篇有详细讲解):跳频的核心输入是 Event Counter(或者每次增加的未映射信道)。
- 能够阅读 C 代码和位操作:本文包含实际代码实现,会解释信道图的 Remapping 逻辑。
不需要提前了解最新 5.0 以后引入的 CSA#2 算法,本文的代码实现专注于经典且基石所在的 CSA#1。
一、为什么每次约会都要换个地方
2.4 GHz 是全球非授权频段。Wi-Fi、微波炉、Zigbee、蓝牙经典——全部挤在这 80 MHz 的频带里。如果 BLE 连接固定使用某一个信道,那么一旦这个信道被 Wi-Fi 持续占用,整条连接就会在干扰中瘫痪。
跳频(Frequency Hopping)的解决逻辑很简单:让两端按照同一个规律,每次约会都换一个频道。即使某个频道被干扰,双方同时跳到下一个干净的频道,最多丢失一次数据包,然后连接恢复正常。
BLE 把 2.4 GHz 频段分成 40 个信道:
- 信道 37、38、39:广播专用信道
- 信道 0–36:连接数据通信使用的 37 个数据信道
跳频算法的输入是当前的 Connection Event Counter,输出是这次连接事件使用的信道编号。每次连接事件完成后,Counter 自增,下次事件在新信道上进行。
二、信道图:把"有干扰的信道"标记出来
在 CONNECT_IND 里有一个叫 ChM(Channel Map)的字段,占 5 字节(40 bit)。每个 bit 对应一个信道:bit = 1 表示可用,bit = 0 表示不可用。
Master 的 Host 可以通过 HCI_LE_Set_Host_Channel_Classification 通知 Controller 某些信道受到干扰,此时 Controller 会在下一次参数更新中下发新的 ChM,把这些信道标记为不可用。
在代码里,这 5 字节直接存放在 conn_params.chan_map[5] 里。查一个信道是否可用,只需要:
/* 检查信道 ch 是否在 chan_map 中被标记为可用(bit = 1 表示可用) */
bool chan_available(uint8_t ch) {
return (conn_params.chan_map[ch >> 3] & (1U << (ch & 7))) != 0;
}
三、CSA # 1:BLE 4.x 的经典跳频算法
3.1 未映射信道计算
每次连接事件后,先用上一次的未映射信道值加上 Hop,对 37 取模:
Hop 来自 CONNECT_IND,取值范围 5–16。初始值为 0。
3.2 信道可用性检查与映射
如果 \(UnmappedChannel\) 在 ChM 中被标记为可用(bit = 1),就直接使用它。
如果它被标记为不可用(bit = 0),就需要进行映射(Remapping):在 37 个信道中,把所有标记为可用的信道按升序编号,取第 \(UnmappedChannel \bmod N_{used}\) 个:
这样做的结果是:不可用信道的对应请求,会被均匀分散到其余可用信道上。
3.3 完整 C 实现
/*
* Channel Selection Algorithm #1(来自 05_phy_anchor/src/ll_pdu.c)
*
* 每次调用推进一个 Event,返回本次事件使用的数据信道编号(0–36)。
*/
uint8_t chan_sel_1(void)
{
uint8_t unmapped;
/* ① 推进未映射信道:(上次值 + Hop) mod 37 */
unmapped = (last_unmapped_chan + conn_params.hop) % 37;
last_unmapped_chan = unmapped;
/* ② 检查该信道是否可用(chan_map 里对应 bit 是否为 1) */
if (conn_params.chan_map[unmapped >> 3] & (1U << (unmapped & 7))) {
return unmapped; /* 可用:直接返回 */
}
/* ③ 不可用:映射到第 (unmapped % N_used) 个可用信道 */
uint8_t remap_index = unmapped % conn_params.chan_count;
uint8_t count = 0;
for (uint8_t i = 0; i < 37; i++) {
if (conn_params.chan_map[i >> 3] & (1U << (i & 7))) {
if (count == remap_index) {
return i; /* 找到第 remap_index 个可用信道 */
}
count++;
}
}
return 0; /* 不应到达,chan_count > 0 有保证 */
}
3.4 chan_sel_1() 在连接事件中的调用方式
每次进入连接事件时调用 chan_sel_1(),得到本次事件应使用的信道:
/* conn.c: conn_event() 的第一行 */
uint8_t chan = chan_sel_1();
data_chan_set(chan);
注意:即使本次事件因为时序错误被提前放弃(ticks_at_start 已过期),chan_sel_1() 也必须先调用,以保证 last_unmapped_chan 正确推进:
static bool conn_event(uint32_t ticks_at_start, ...)
{
/* 检查目标 RTC tick 是否在未来 */
uint32_t ahead = (ticks_at_start - NRF_RTC0->COUNTER) & HAL_TICKER_CNTR_MASK;
if (ahead > (HAL_TICKER_CNTR_MASK >> 1) || ahead < RTC0_CMP_OFFSET_MIN) {
(void)chan_sel_1(); /* ← 即使放弃执行,也必须推进信道计数 */
conn_event_rx_timeout++;
conn_event_counter++;
return false;
}
uint8_t chan = chan_sel_1(); /* ← 正常路径:计算并使用本次信道 */
data_chan_set(chan);
...
}
四、Event Counter 同步:最容易出错的细节
跳频的核心是双方的 Event Counter 保持一致。每完成一次 Connection Event,无论这次事件有没有成功收到包,Counter 都必须自增一次。
4.1 普通情况
每次进入 conn_event() 前,conn_event_counter 不需要显式传入,但每次执行完一次事件循环(不论成功与否)都会 conn_event_counter++:
/* conn.c: 每次连接事件结束后 */
conn_event_counter++;
4.2 Slave Latency 期间的 Counter 处理
BLE 允许 Slave 跳过若干个连接事件(没有上行数据时)。当 Slave 跳过 N 个事件后醒来,它在这 N 个事件期间没有调用 chan_sel_1(),但 Master 的 Counter 已经加了 N 次。
在真正的 Latency 实现里,Slave 需要在醒来前"追赶"信道计算:连续调用 N 次 chan_sel_1() 或等效地手动快进 last_unmapped_chan,使双方在唤醒时的信道再次对齐。如果忽略这一步,唤醒后 Master 和 Slave 将在不同信道上等待彼此,连接必然超时断开。
4.3 Channel Map 更新的时机
如果 Host 通过 HCI_LE_Set_Host_Channel_Classification 发来了新的信道图,新图不能立刻生效,必须等到 LL_CHANNEL_MAP_IND 控制包中指定的 Instant(某个特定的 Event Counter 值)才能切换。
原因是:如果 Master 在 Counter = 50 时提前切换到了新 ChM,而 Slave 还在用旧 ChM,双方对"哪些信道可用"的理解已经不同,整个 Remapping 逻辑的输出自然也不同,下一次事件双方必定在不同信道上,连接立刻断开。
五、CSA # 2:BLE 5.0 引入的伪随机跳频(原理概述)
CSA #1 的跳频序列有明显的周期性。对于有经验的干扰攻击者,只需监听几次就能推算出完整序列,实施定向干扰。
BLE 5.0 引入了 CSA # 2(Channel Selection Algorithm #2 ),基于伪随机排列,生成看起来随机但双方完全同步的信道序列:
MAM(Multiply, Add, Mod)运算模块对 Channel Identifier 和 Event Counter 的组合进行一系列确定性变换,最终输出 0–36 范围内的信道编号。由于 Access Address 每次连接都不同,即使在同一个区域内的两条连接也会使用完全不同的跳频序列。
本系列教程同款硬件:👇
芯片: nRF 52832 开发板
工具: nRF 52840 BLE Dongle 蓝牙嗅探器
工具: 逻辑分析仪
工具: BPA low energy 蓝牙分析仪
本文版权归作者:ixbwer所有,转载请注明原文链接:https://www.cnblogs.com/ixbwer/p/19796682,否则保留追究法律责任的权利。

浙公网安备 33010602011771号