btfz-sdr/gnuradio_demo/pluto-ble-adv-rx

Claude Code and agent skill by btfz-sdr/gnuradio_demo

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

PlutoSDR(AD9361) 纯 C 接收 BLE LE 1M 广播包 —— 解调链设计、access code 一次 100% 命中的根因、全相位 AA 搜索的两个坑、离线回放验证流程。改 ble_phy.c/排查 AA 匹配不到 40-40/漏包/移植 GNU Radio gfsk_demod 到嵌入式时用本技能。

Install

npx vskill@latest install btfz-sdr/gnuradio_demo/pluto-ble-adv-rx

SKILL.md

PlutoSDR BLE 广播包接收(LE 1M)

工程根目录:本文件所在目录往上两级(即 ble_phy.c 所在目录)。 编译+上机:make run CH=37 SAMPLE=10(scp + ssh root@pluto.local,密码 analog)。

触发条件

  • 改 ble_phy.c(解调、AA 搜索、解白化、CRC、包解析)
  • access code 匹配率上不去("只能 32/40""从来没有 40/40")
  • 收包比预期少(漏包)、解出来的 PDU 是乱码、CRC 不过
  • 把 GNU Radio gfsk_demod 的思路搬到嵌入式实时目标
  • 出现"射频看着没问题但判决比特是随机的"这类 GFSK/FSK 症状

工程文件地图

文件作用
ble_phy.c / ble_phy.h解调器(IQ → 比特 → AA → PDU)+ 发射端通用函数(CRC/白化)
pluto_ble_adv_rx.c主程序:IIO 采集、AD9361 配置、分块喂解调器
ble_replay.c离线回放:把录到的 IQ 喂同一个 ble_phy.c(宿主机 gcc)
makefilebuild / run / capture(网络 IIO 抓 IQ)/ replay
doc/demodulator.md解调链设计、四个问题的证据、调参表、实测数据
doc/gnuradio-gfsk-demod.mdGNU Radio 参考实现的确切参数与公式、为什么不能照搬
tools/独立参考实现:合成包生成器、AA 全相位扫描、候选解码(含 README)

处理链(当前实现)

IQ → 正交鉴频 → sps 点 boxcar(1 符号积分) → 0 门限判决
→ sps 个定时相位并行 40bit 窗口(相位 = 样点序号 % sps)
→ AA 汉明距离 ≤ 3 → **每个命中的相位各起一路收包** → 解白化 → CRC-24

链路上没有 DC/频偏扣除,也没有时钟恢复环。与 GNU Radio digital.gfsk_demod(鉴频 → M&M → 硬判决)的刻意不同:

  1. 加一级 1 符号 boxcar(FSK 匹配滤波)。10 Msps 下单样点鉴频的相位步进只有

±0.157 rad,噪声按 5 MHz 级带宽进来,不平均判不出来(约 +10 dB Eb/N0)。

  1. 定时用前馈全相位并行,不用反馈环。每样点只把 1 bit 推进"相位 = 样点 % sps"

那个窗口并只比较该窗口 → O(1)/样点,覆盖全部相位空间。 M&M 环在 BLE 上不可用:8 bit 前导码太短收不敛,且环在眼图交叉点另有一个 稳定平衡点(实测锁在交叉点,软判决幅度 0.16 而正确采样点 1.5)。

四个已定位的坑(都在真实空口上量化过)

1. access code 比特序("40/40 永远不可能"的元凶)

BLE 的 0x8E89BED6 是 LSB 先发,空中字节顺序是 D6 BE 89 8E。 按书写顺序拼 8E 89 BE D6 当模板 = 拿反的地址做相关, 任何真实包最多落到随机水平(~20/40),解调器再完美也到不了 40/40。

c
const uint32_t aa = 0x8E89BED6u;
for (int b = 0; b < 32; b++) v |= (uint64_t)((aa >> b) & 1u) << (39 - k++);
2. CRC-24 的空中字节序

三个 CRC 字节是 swap(高字节), swap(中字节), swap(低字节)(swap = 字节内比特反转), 不是 LSB-first。任何生成器都要拿真实包验证一次: 美的设备 PDU ...414331313639 → CRC 字节 A1 5B A5。

3. AA 命中的相位 ≠ 能解出 PDU 的相位 —— 每个相位都要各起一路收包(漏包主因)

40 bit 的 AA 对采样时刻不敏感(判断裕量大),后面 ~300 bit 的 PDU 不是: 逐包把 10 个定时相位都试一遍后,同一个真实包平均只有 4.9/10 个相位能过 CRC, 其余相位 AA 误差同样是 0,但 PDU 里错 1~2 bit → CRC 必挂。

所以"AA 一命中就锁死那个相位、收包期间还停掉 AA 搜索"≈ 按 5/10 的概率抽签。 实测同一批 2.1 s 录波:修前 ch37/ch38/ch39 = 4 / 7 / 16 包,修后 = 18 / 23 / 47 包。

正确做法:每个定时相位各起一路收包 —— AA 在哪个相位命中就在哪个相位上独立累计 PDU 位,谁先过 CRC 谁算数;同一包会有多路同时成功,报告端按样点位置去重 (100 样点足够,真实 BLE 包至少 88 符号 = 880 样点)。 实现要点:某一路刚起头时,当前样点的 bit 已经含在 40 bit 窗口里, 不要再往这一路追加一次 —— 否则整包错位 1 bit、全部解不出来(这个坑我踩过, 现象是"改完一个包都不出了")。

4. 不要盲目扣 DC/频偏(试过,删掉了)

"AD9361 有残余 LO 偏置所以要在鉴频输出上扣 DC"听着很合理,实现成 "只喂 |d| < 0.5 rad 的样点、时间常数 13 ms 的门控跟踪器"在 0.26 s 录波上 确实从 0 包变 2 包。但在 2.1 s 录波上量化后是整体有害的:

  • 跟踪器收敛的是噪声的不对称性(-0.061 rad/样点),不是包处的眼图偏置(~0);
  • 扣掉它等于把眼图平移 0.6(信号只有 ±1.5),弱符号成片翻错:

门控 DC 6 包 vs 不扣 DC 16 包;全 6 段录波 35 vs 45 包。

所以链路不做 DC 扣除,和 GNU Radio/参考实现一致。真看到眼图整体偏一侧, 先查增益(削顶会产生真实的鉴频偏置),别用盲目估计器补。

排查方法论

  1. 先证明锅在谁身上:拿原始录波用匹配滤波滑窗量 AA 相关(tools/aa_scan.py)。

随机比特的汉明距离均值约 20/40;真实干净信号匹配滤波后是 40/40、软幅度 ~±1.5。 若真实信号 40/40 而 C 的输出落在随机统计上 → 锅在解调器,不在射频/增益。 (旧代码基线:257801 bit 内最高 34/40、≥32 的 16 个、CRC OK 0 个 = 纯随机。)

  1. 拿独立实现当基准(tools/decode_cand.py,不共用 ble_phy.c):

同一段录波它解出多少 CRC-OK 包,C 就该接近多少。差很多就继续查。

  1. 录波 + 回放(不接设备也能迭代):

``bash make capture CH=37 SEC=2 FILE=ch37.bin # 走网络 IIO,不需要登录 Pluto make replay FILE=ch37.bin CH=37 ` ble_replay.c 与设备端共用同一个 ble_phy.c`,离线结论可以代表设备端。

  1. 合成信号回归(不依赖空口):

python3 tools/gen_ble.py 30 && make replay FILE=synth_snr30.bin CH=37

  1. 要定位"某个包为什么丢了",把 C 内部的候选表和独立参考的候选表按全局样点号

对齐比较。注意 `demodulate_ble_packets` 的 `i` 是块内下标(每次调用从 0 开始, 主程序按 262144 分块),跟全局样点号差一个块偏移 —— 直接拿它和录波位置比会得出 完全错误的"这里没有候选"结论。要自己加 static long long g_n 数全局序号。

上机/环境注意事项

  • Pluto 上没有 timeout(busybox),也没有 sshpass:

非交互 ssh/scp 用 SSH_ASKPASS=<脚本> SSH_ASKPASS_REQUIRE=force setsid -w ssh/scp ...; scp 必须加 -O(固件里没有 /usr/libexec/sftp-server)。 askpass 脚本是从环境变量 `PLUTO_PW` 取密码的(脚本本体只有 printf "%s\n" "$PLUTO_PW"): 新 shell 里忘了 export 就会稳定报 Permission denied,很容易误判成"设备挂了/密码不对"。 工程里的 pluto.sh/pluto_scp.sh 会自己带上,手写命令前先 source pluto_lib.sh。 批量 scp 还会偶发静默失败:传完必须核对设备端文件的 md5/size, 否则会出现"程序跑了但一个包都没有"——其实是文件根本没传上去。

  • 不要留进程在设备上:(cmd > log 2>&1 & echo $! > pid); sleep N; kill $(cat pid)。
  • 某些固件上 iio_attr ... hardwaregain <n> 会返回 EINVAL(即使模式已是 manual)——

在主程序里无副作用(程序只设 fast_attack AGC),但辅助命令要写成非致命。

  • 采样率通过 ble_phy_set_sample_rate(fs_mhz) 传入,sps 由此推导(默认 10 MHz → 10 样点/符号)。

采样率低于 4 MHz 时匹配滤波增益太小,不建议。

实测基线(当前版本,改动后可对比)

场景结果
离线回放真机录波(各 2.1 s,ch37/38/39)18 / 23 / 47 包;独立 Python 参考 18 / 18 / 36(参考每位置只试一个相位,所以它自己漏包)
上机实时 ch37 / 16 s(10 Msps, fast_attack)61 包、无 refill 溢出
上机实时 ch38 / ch39(各 12 s,旧版本)25 包 / 24 包,多设备(含 type 0x7)
宿主机回放速度~1.9x 实时
合成回归≥10 dB 出包、≤5 dB 不出(dB 是总带内 SNR,等效带内约 +20 dB)

改动解调器后的验收顺序:合成回归 → 三段录波回放 → 上机 16 s。任一明显低于上表就是回退。

已知局限

  • 单信道:一次只收一个广播信道,只能拿到该信道约 1/3 的广播包。

要扫遍周围设备需加 37→38→39 跳频;换频时解调器状态要复位 (ble_phy.c 里已有 static 的 ble_reset_state(),跳频场景需要开放一个重置入口)。

  • ADV 解析目前只输出 PDU 类型 + AdvA;名称/厂商数据等 AD 结构还没解析。
  • AA 门限是硬判决(40 bit 里允许 ≤3 错)。若要再压灵敏度,可考虑 AA 软判决相关

(参考 tools/aa_scan.py 的归一化相关度),代价是误报要靠 CRC 兜。

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
201
Repository forks
45
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/btfz-sdr/gnuradio_demo/pluto-ble-adv-rx/badge)](https://verified-skill.com/skills/btfz-sdr/gnuradio_demo/pluto-ble-adv-rx)

Something wrong with this skill or its page? Tell us.