蓝牙跳频算法 CSA#1/CSA#2 手算拆解:两个公式算出 37 个信道怎么跳
发布时间:2026/10/23 6:25:37 锦皓数字建站

前两篇讲了广播信道怎么躲 WiFi、HCI 抓包怎么逐字节拆。这篇解决最后一个问题连接建立之后数据在 37 个信道上到底按什么顺序跳答案写在两个算法里——CSA#1 和 CSA#2。它们都是纯算术一个只有加法和取模另一个多了一层置换和乘法。这篇文章把两个算法的每一步都手算给你看所有中间值都来自我按 Zephyr 官方控制器源码lll_chan.c复刻的程序实算不是编的。1. 为什么需要跳频为什么要懂它2.4GHz 频段没有准入门槛WiFi、微波炉、别人的蓝牙全挤在一起。蓝牙把 2400~2483.5MHz 切成 40 个 2MHz 信道编号 0~36 是数据信道37/38/39 专门发广播。跳频的本质是分散押注每个连接事件connection event换一个信道这次撞上 WiFi下次大概率就躲开了。问题在于两端必须跳得完全一致否则各说各话。所以跳频不能真随机——必须是一个双方都能算的确定函数输入只有几样连接时定好的参数。这就是 CSA 算法的全部使命看起来像随机实际是确定函数。跳频参数在 CONNECT_IND 里就发给了从机就是上篇 HCI 抓包里那条连接的空口版本跳频算法选择位、hop 增量、信道图Channel Map。主机改信道图控制器照图跳——你调参数算法照跑但每一步都是可以手算的。2. 信道图跳频的第一输入信道图是 5 字节位图37 个 bit 对应 37 个数据信道1 可用、0 被禁。典型场景办公室 WiFi 占了 1/6/11 三个经典信道对应蓝牙信道 0~10、11~25 这段被打穿芯片或手机会把这段从图里抠掉只剩 26~36 和低段的一部分。本文手算用一个图做例子禁掉信道 11~25剩22 个可用信道0-10, 26-36 位图: FF 07 00 FC 1F位图是 5 字节共 40 bit只有前 37 bit 有效第 N 个 bit从 0 数对应信道 N是 1 即可用。按位展开每个字节单独看bit0 在最右对应编号最小的信道字节0 FF 11111111 → 信道 0~7 全可用 字节1 07 00000111 → 信道 8~10 可用11~15 禁 字节2 00 00000000 → 信道 16~23 全禁 字节3 FC 11111100 → 信道 24~25 禁26~31 可用 字节4 1F 00011111 → 信道 32~36 可用剩余 bit 对应广播信道不进图从表中可直接读出0~10、26~36 可用与位图一致。注意编码的坑字节内从右往左读bit0 对应编号最小的信道。所以禁掉 11~25写出来不是一段连续的 0而是07 00 FC这种两头被切的形状。第一次看信道图的人十个有九个在这翻车——拿到图先按上表展开一遍再往下算。算法拿这张图做两件事判断候选信道可不可用不可用时按可用信道表重映射。可用信道表就是把图中为 1 的信道按编号排开编号: 0 1 2 ... 10 11 12 ... 21 信道: 0 1 2 ... 10 26 27 ... 363. CSA#1一个加法解决跳频CSA#1 是蓝牙 4.0 的默认算法简单到不像算法nextChan (lastChan hop) mod 37hop 是 5~16 之间的连接参数CONNECT_IND 里带的 hopIncrementlastChan 是上个连接事件未经重映射的原始信道——被禁后做了重映射参与下一步加法的仍是重映射前的那个数。算出来的 nextChan 若在信道图里可用直接用不可用就重映射。手算一遍。设 hop5上一信道 lastChan10信道图还是上面那张 22 信道的nextChan (10 5) mod 37 15信道 15 被禁了11~25 全禁进入重映射。这一步公式的含义是把 0~36 的原始编号折叠到 0~21 的可用信道表下标上chanIndex nextChan mod numUsed numUsed 22信道图里数出来的可用信道数 chanIndex 15 mod 22 15为什么 mod 22因为可用信道只有 22 个重映射的结果必须在可用信道表里挑一个表的下标范围就是 0~21。15 mod 22 15——15 本来就小于 22取模不改变它这一步的实际作用是把候选位置压进可用表的下标范围比如原始算到 3535 mod 22 13就取表里第 13 个。到可用信道表里数第 15 个从 0 数0,1,2,…,10 是第 0~10 个26,27,…,36 是第 11~21 个。第 15 个 信道 30。所以这个连接事件在信道 30 上收发。注意重映射只看 nextChan 这一个数、不看跳频历史所以同一个被禁原始值每次都重映射到同一个可用信道——这就是后面 CSA#1 序列里 30 反复出现的根源。把整个序列连算 12 个连接事件hop5、从信道 0 起事件: 1 2 3 4 5 6 7 8 9 10 11 12 信道: 5 10 30 35 3 30 35 3 8 28 33 1规律一眼可见算术跳 5、撞禁段、被重映射拉到 30/35 一带再继续算术跳。CSA#1 的跳频图是固定周期循环——37 与 hop 互质序列周期最长 37 个事件然后整个重来。对躲干扰来说这够用周期对一个连接事件几十毫秒来说已经很长了但如果固定窄带干扰正好压住你循环里反复经过的几个点效率会掉。4. CSA#2三层运算换真随机CSA#2 是 4.2 引入的可选算法LL feature 协商里叫 Channel Selection Algorithm #2动机就一条CSA#1 的周期性可预测。CSA#2 用伪随机序列相邻连接事件落点之间看不出规律对固定窄带干扰和故意跟跳都更鲁棒。4.1 输入访问地址CSA#2 的随机性来自 Access Address——连接建立时主机生成的 32 位数规则是至少 24 位翻转、无 6 连 0/1 等本身就长得像随机数。先压缩成 16 位的 chan_idchan_id AA低16位 XOR AA高16位手算例设 AA 0x3050AB5F。AA低16位 0xAB5FAA高16位 0x3050 chan_id 0xAB5F XOR 0x3050 0x9B0F4.2 生成器perm 和 mamCSA#2 的核心是两个 16 位操作先各拆一步。两个操作的示例输入别凭空造——就用下面生成器的真实起点counter1 时s0 1 XOR chan_id(0x9B0F) 0x9B0E。注意这个 0x9B0E 不是笔误它就是 chan_id 被 counter 的最低位异或了一下F→E后面整个序列都从它出发。perm(i)—— 先把 16 位数拆成高低两个字节各自按 bit 反转bit0↔bit7、bit1↔bit6…再拼回去。纯粹是个位置打乱没有任何算术。手算perm(0x9B0E) → 低字节 0x0E 反转 0x70高字节 0x9B 反转 0xD9拼回 →0xD970。三步拆开mam(a, b)——a × 17 b保留低 16 位。第一参数是上一操作的输出第二参数固定是 chan_id。手算mam(0xD970, 0x9B0F)0xD970 × 17 0xE7070加 0x9B0F 0xF0B7F截 16 位 0x0B7F。这就是完整的第一轮。我自己手算到这一步时也翻过车——16 位乘加必须老老实实列竖式心算基本必错。perm 和 mam 串成一轮prn_s 生成器s counter XOR chan_id起步然后循环 3 次{perm → mam}。counter 是连接事件计数器每个连接事件 1。counter1、chan_id0x9B0F 的完整轨迹程序实算——开头两行就是上面 perm/mam 演算过的那两个数s0 1 XOR 0x9B0F 0x9B0E perm(0x9B0E) 0xD970 第1轮 mam(0xD970, 0x9B0F) 0x0B7F perm(0x0B7F) 0xD0FE 第2轮 mam(0xD0FE, 0x9B0F) 0x7BED perm(0x7BED) 0xDEB7 第3轮 mam(0xDEB7, 0x9B0F) 0x6536 prn_s 0x6536最后prn_e prn_s XOR chan_id 0x6536 XOR 0x9B0F 0xFE39。这就是本次连接事件的伪随机数。4.3 落信道mod 37禁了就重映射chan prn_e mod 370xFE39 6508165081 mod 37 35。查信道图35 可用本事件用信道 35。撞上禁段时的重映射和 CSA#1 不同这是 CSA#2 最精妙的一步chanIndex (numUsed × prn_e) 16不是对 37 取模而是把 prn_e 当 16 位小数0~1 之间的比例乘上可用信道数再截断——本质是在可用信道空间里等概率取点。手算counter3 时 prn_e4381raw 信道 4381 mod 37 15被禁。chanIndex (22 × 4381) 16 96382 16 1。查可用信道表第 1 个 信道 1。这里的22 不是固定值就是当前信道图里数出来的可用信道数和第 3 节 CSA#1 重映射里的 numUsed 是同一个数。37 个信道全可用时它就是 37换了信道图它跟着图变——算法不关心具体数值只要求可用信道空间有多大。4.4 完整序列同一 AA、同一信道图前 12 个连接事件的完整落点程序实算事件: 1 2 3 4 5 6 7 8 9 10 11 12 prn_e: FE39 9CF5 111D A533 DBFB 8DB7 070F CB52 EE1A 6ED6 ECFE B714 raw: 35 36 15 0 1 19 31 28 15 32 27 26 信道: 35 36 1* 0 1 27* 31 28 35* 32 27 26 (带*为撞禁段后重映射的结果)和 CSA#1 那条 5、10、30、35、3 的周期序列放一起对比CSA#2 的落点看不出任何规律而且同一个 prn_e 在不同信道图下会落到不同信道37 全可用时 raw 15 落信道 1522 可用时落信道 1——跳频结果由随机数 × 信道图共同决定改信道图不影响随机数本身只改映射。5. 两个算法怎么选什么时候在乎选择权在主机CONNECT_IND 里一个 bit 指明用哪个前提是双方 feature 协商里都支持 #2。协议栈配置项Zephyr 里是CONFIG_BT_CTLR_CHAN_SEL_2开了才有。实际在乎的场景固定干扰下的均匀性CSA#1 撞禁段时重映射结果有聚集性上面的序列里 30/35 反复出现窄带干扰若正好坐在聚集点上丢包率抬升CSA#2 的重映射在可用空间里均匀取点没有聚集。吞吐调优的配套上篇讲过 251 字节长包 DLE那是把单包做大跳频均匀性是另一个维度——跳得好重传少等于变相加吞吐。两者是配合关系。产品抗扰测试跳频跟 WiFi 共存测试ETSI 那套里用的算法和信道图直接决定通过率。懂手算才能在测试不过时判断是算法限制还是信道图配错。一个工程判断消费级产品多数默认 CSA#1 就过了共存测试不必折腾对跳频有硬指标的比如要求跟跳攻击抵抗、或共存测试余量小再开 #2。先把信道图配对比换算法收益大。说清楚配对什么意思。信道图确实是主机定的——但主机不是拍脑袋定的它要么靠控制器上报的信道质量报告要么靠协议栈自带的 WiFi 共存分类器来判断哪段频段有干扰、该禁哪段。所以配对指的是图要如实反映现场实际干扰段。常见翻车是图配得很随意比如照抄别人的五字节、或者干脆全 1 不禁办公室 WiFi 实际占的频段根本没禁掉——这时干扰段还在跳频循环里换 CSA#2 只是把落点弄均匀了照样每次都撞上去。图禁对了CSA#1 的周期序列也绕开干扰段图禁错了CSA#2 也救不回来。所以排查共存问题先核图再谈算法。6. 手算工具化一段 40 行的验证脚本懂手算的真正价值是能验证。把 Zephyr 的lll_chan.c翻译成 Python 只要几十行协议栈升级、芯片换代、抓包疑点都能用它对答案。核心函数原样照抄节选def perm(i): # 双字节 bit 反转 r8 lambda b: int(format(b, 08b)[::-1], 2) return (r8((i 8) 0xFF) 8) | r8(i 0xFF) def mam(a, b): # a*17b 截 16 位 return (a * 17 b) 0xFFFF def prn_s(counter, chan_id): s counter ^ chan_id for _ in range(3): s mam(perm(s), chan_id) return s def prn_e(counter, chan_id): return prn_s(counter, chan_id) ^ chan_id空口抓包Ellisys 这类仪器或者部分芯片的跟跳日志能拿到每个连接事件的实际信道跟脚本算出来的序列一比对信道图、算法选择位、hop 全都能反推验证。没有空口仪器也能用Linux 的 btmon 日志里连接事件信道有时可见至少能抽查落点分布。7. 总结两个算法三层结构就这些信道图圈出可用空间CSA#1 用(lastChan hop) mod 37加取模重映射周期可循CSA#2 用 chan_id来自 Access Address驱动 perm/mam 三轮生成 prn_emod 37落信道禁段用(numUsed × prn_e) 16均匀重映射。全部输入只有连接参数手算可复现。下一篇回到协议栈本身GATT 服务发现流程里主机和从机的攻防——谁决定句柄范围谁在偷懒。蓝牙 BLE 跳频 CSA#1 CSA#2 channel map hop increment prn_e Access Address 信道图 连接事件 抗干扰 共存
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。