rf_diag.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. #include "rf_diag.h"
  2. #include "radio.h"
  3. #include "bonds.h"
  4. #include <Arduino.h>
  5. #include <string.h>
  6. bool g_rfListen = false;
  7. bool g_rfBeacon = false;
  8. uint8_t g_plen = 0x18, g_s1incl = 0;
  9. bool g_rfRaw = false;
  10. bool g_rfSweep = false;
  11. bool g_rfCap = false, g_rfCapOne = false, g_rfReplay = false;
  12. uint8_t g_replayLen = 0;
  13. bool g_rfAuto = false;
  14. uint8_t g_capV = 0, g_cfgIdx = 0;
  15. bool g_rfRespond = false;
  16. bool g_sniff = false;
  17. uint8_t g_sniffPh = 0, g_sniffPark = 0;
  18. static uint8_t g_seq = 0;
  19. static unsigned long g_lastBeacon = 0, g_lastHop = 0, g_lastCfg = 0;
  20. static uint8_t g_replay[48];
  21. static uint32_t g_capPass = 0;
  22. static uint32_t g_advCount = 0;
  23. static uint8_t g_hostTx[48];
  24. static int g_hostLen = 0;
  25. static uint32_t g_sniffN = 0;
  26. static uint8_t g_sbase[4] = { 0, 0, 0, 0 }, g_sprefix = 0;
  27. static uint8_t g_schan[3] = { 78, 2, 80 };
  28. static uint8_t g_schi = 0;
  29. // ===================== promiscuous raw capture (calibration only) =====================
  30. // Preamble-match on 0x55, fixed-length grab, no CRC. Catches any 2Mbit packet on a channel so we can read the
  31. // controller's reconnect addr/prefix/framing.
  32. #define RAWCAP 48
  33. void rfRawStart(uint8_t ch)
  34. {
  35. NRF_RADIO->TASKS_DISABLE = 1;
  36. RWAIT_DISABLED();
  37. NRF_RADIO->EVENTS_DISABLED = 0;
  38. NRF_RADIO->MODE = (RADIO_MODE_MODE_Nrf_2Mbit << RADIO_MODE_MODE_Pos);
  39. #if defined(RADIO_MODECNF0_RU_Fast)
  40. NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
  41. #endif
  42. NRF_RADIO->FREQUENCY = ch;
  43. NRF_RADIO->TXPOWER =
  44. (RADIO_TXPOWER_TXPOWER_0dBm << RADIO_TXPOWER_TXPOWER_Pos);
  45. NRF_RADIO->CRCCNF = 0;
  46. NRF_RADIO->PCNF0 = 0;
  47. NRF_RADIO->PCNF1 =
  48. (RADIO_PCNF1_WHITEEN_Disabled << RADIO_PCNF1_WHITEEN_Pos) |
  49. (RADIO_PCNF1_ENDIAN_Big << RADIO_PCNF1_ENDIAN_Pos) |
  50. (2u << RADIO_PCNF1_BALEN_Pos) |
  51. (RAWCAP << RADIO_PCNF1_STATLEN_Pos) |
  52. (RAWCAP << RADIO_PCNF1_MAXLEN_Pos);
  53. NRF_RADIO->BASE0 = 0x55555555;
  54. NRF_RADIO->PREFIX0 = 0x00000055;
  55. NRF_RADIO->TXADDRESS = 0;
  56. NRF_RADIO->RXADDRESSES = 1;
  57. NRF_RADIO->PACKETPTR = (uint32_t)rfrx;
  58. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
  59. RADIO_SHORTS_END_START_Msk;
  60. NRF_RADIO->EVENTS_END = 0;
  61. NRF_RADIO->TASKS_RXEN = 1;
  62. }
  63. // ===================== CRC-validating framing sweep =====================
  64. // ESB DPL (S0L0, LFLEN6or8, S1LEN3), BALEN4 (5-byte addr), CRC8/16 addr-included.
  65. // Sweep PCNF0{LFLEN6,8} x CRC{8,16} x BALEN{4,3} x ENDIAN{Big,Lit}.
  66. static const uint32_t CAPP0B[] = {
  67. 0x00030006, 0x00030008
  68. }; // S1LEN3 + LFLEN6 / LFLEN8 (dynamic length)
  69. static const uint32_t CAPCRCB[][3] = { { 0x1, 0x107, 0xFF },
  70. { 0x2, 0x11021, 0xFFFF } };
  71. void rfCapStart(uint8_t ch)
  72. {
  73. uint8_t ci = g_capV & 1;
  74. uint8_t balen = ((g_capV >> 1) & 1) ? 3 : 4; // BALEN 4 first
  75. uint8_t endbig = ((g_capV >> 2) & 1) ^ 1; // ENDIAN Big first
  76. uint32_t p0 = CAPP0B[(g_capV >> 3) & 1];
  77. const uint32_t *cr = CAPCRCB[ci];
  78. NRF_RADIO->TASKS_DISABLE = 1;
  79. RWAIT_DISABLED();
  80. NRF_RADIO->EVENTS_DISABLED = 0;
  81. NRF_RADIO->MODE = (RADIO_MODE_MODE_Ble_2Mbit << RADIO_MODE_MODE_Pos);
  82. #if defined(RADIO_MODECNF0_RU_Fast)
  83. NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
  84. #endif
  85. NRF_RADIO->FREQUENCY = ch;
  86. NRF_RADIO->PCNF0 = p0;
  87. NRF_RADIO->PCNF1 = ((uint32_t)endbig << RADIO_PCNF1_ENDIAN_Pos) |
  88. ((uint32_t)balen << RADIO_PCNF1_BALEN_Pos) |
  89. (60u << RADIO_PCNF1_MAXLEN_Pos);
  90. NRF_RADIO->CRCCNF = cr[0];
  91. NRF_RADIO->CRCPOLY = cr[1];
  92. NRF_RADIO->CRCINIT = cr[2];
  93. uint8_t b[4];
  94. for (int i = 0; i < 4; i++)
  95. b[i] = rfBitrev8(g_rfBase[i]);
  96. NRF_RADIO->BASE0 = ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
  97. ((uint32_t)b[2] << 8) | b[3];
  98. NRF_RADIO->PREFIX0 = rfBitrev8(g_rfPrefix);
  99. NRF_RADIO->TXADDRESS = 0;
  100. NRF_RADIO->RXADDRESSES = 1;
  101. NRF_RADIO->PACKETPTR = (uint32_t)rfrx;
  102. memset(rfrx, 0, 8);
  103. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
  104. RADIO_SHORTS_END_START_Msk;
  105. NRF_RADIO->EVENTS_END = 0;
  106. NRF_RADIO->TASKS_RXEN = 1;
  107. }
  108. // REPLAY: capture one real-puck frame (raw, ENDIAN=Little) then re-transmit it VERBATIM to impersonate the puck
  109. // bit-for-bit -- sidesteps unknown framing/CRC. Controller must be bonded to the REAL puck (replayed frame
  110. // carries real-puck uuids + its valid CRC).
  111. static void rfCapPoll()
  112. {
  113. if (!g_rfCap)
  114. return;
  115. if (NRF_RADIO->EVENTS_END) {
  116. NRF_RADIO->EVENTS_END = 0;
  117. g_capPass++;
  118. if (g_rfCapOne) {
  119. memcpy(g_replay, rfrx, sizeof g_replay);
  120. g_replayLen = 32;
  121. g_rfCapOne = false;
  122. g_rfCap = false;
  123. Serial.printf("# captured replay frame (%uB): ",
  124. g_replayLen);
  125. for (int i = 0; i < g_replayLen; i++)
  126. Serial.printf("%02X", g_replay[i]);
  127. Serial.println();
  128. return;
  129. }
  130. if (NRF_RADIO->CRCSTATUS & 1) {
  131. uint8_t ci = g_capV & 1;
  132. uint8_t balen = ((g_capV >> 1) & 1) ? 3 : 4;
  133. uint8_t eb = ((g_capV >> 2) & 1) ^ 1;
  134. uint32_t p0 = CAPP0B[(g_capV >> 3) & 1];
  135. Serial.printf(
  136. "@@@ CRCOK PCNF0=%lX BALEN=%u ENDIAN=%s POLY=%lX: ",
  137. (unsigned long)p0, balen, eb ? "Big" : "Lit",
  138. (unsigned long)CAPCRCB[ci][1]);
  139. for (int i = 0; i < 28; i++)
  140. Serial.printf("%02X", rfrx[i]);
  141. Serial.println();
  142. }
  143. }
  144. }
  145. void rfReplayOnce()
  146. {
  147. if (!g_replayLen)
  148. return;
  149. NRF_RADIO->TASKS_DISABLE = 1;
  150. RWAIT_DISABLED();
  151. NRF_RADIO->EVENTS_DISABLED = 0;
  152. NRF_RADIO->MODE = (RADIO_MODE_MODE_Ble_2Mbit << RADIO_MODE_MODE_Pos);
  153. #if defined(RADIO_MODECNF0_RU_Fast)
  154. NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
  155. #endif
  156. NRF_RADIO->FREQUENCY = 2;
  157. NRF_RADIO->PCNF0 = 0; // static, ENDIAN=Little -> reproduce on-air bits
  158. NRF_RADIO->PCNF1 = (3u << RADIO_PCNF1_BALEN_Pos) |
  159. ((uint32_t)g_replayLen << RADIO_PCNF1_STATLEN_Pos) |
  160. ((uint32_t)g_replayLen << RADIO_PCNF1_MAXLEN_Pos);
  161. NRF_RADIO->CRCCNF = 0; // CRC already inside the captured bytes
  162. uint8_t b[4];
  163. for (int i = 0; i < 4; i++)
  164. b[i] = rfBitrev8(g_rfBase[i]);
  165. NRF_RADIO->BASE0 = ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
  166. ((uint32_t)b[2] << 8) | b[3];
  167. NRF_RADIO->PREFIX0 = rfBitrev8(g_rfPrefix);
  168. NRF_RADIO->TXADDRESS = 0;
  169. NRF_RADIO->PACKETPTR = (uint32_t)g_replay;
  170. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
  171. RADIO_SHORTS_END_DISABLE_Msk;
  172. NRF_RADIO->EVENTS_DISABLED = 0;
  173. NRF_RADIO->TASKS_TXEN = 1;
  174. RWAIT_DISABLED();
  175. NRF_RADIO->EVENTS_DISABLED = 0;
  176. NRF_RADIO->TASKS_DISABLE = 1;
  177. RWAIT_DISABLED();
  178. NRF_RADIO->EVENTS_DISABLED = 0;
  179. }
  180. // ===================== address/CRC listen =====================
  181. void rfListenStart()
  182. {
  183. rfConfig(g_rfCh);
  184. rfSetAddr(g_rfBase, g_rfPrefix);
  185. NRF_RADIO->PACKETPTR = (uint32_t)rfrx;
  186. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
  187. RADIO_SHORTS_END_START_Msk;
  188. NRF_RADIO->EVENTS_END = 0;
  189. NRF_RADIO->TASKS_RXEN = 1;
  190. g_rfListen = true;
  191. Serial.printf("# RF listen ch%u base %02X%02X%02X%02X prefix %02X\n",
  192. g_rfCh, g_rfBase[0], g_rfBase[1], g_rfBase[2],
  193. g_rfBase[3], g_rfPrefix);
  194. }
  195. static void rfPoll()
  196. {
  197. if (g_rfRaw) {
  198. if (NRF_RADIO->EVENTS_END) {
  199. NRF_RADIO->EVENTS_END = 0;
  200. // filter noise: require non-preamble/idle byte structure
  201. int nz = 0;
  202. for (int i = 0; i < RAWCAP; i++) {
  203. uint8_t b = rfrx[i];
  204. if (b && b != 0x55 && b != 0xAA && b != 0xFF)
  205. nz++;
  206. }
  207. if (nz >= 6) {
  208. g_rfRxCount++;
  209. Serial.printf("RAW#%lu ch%u: ",
  210. (unsigned long)g_rfRxCount,
  211. g_rfCh);
  212. for (int i = 0; i < RAWCAP; i++)
  213. Serial.printf("%02X", rfrx[i]);
  214. Serial.println();
  215. }
  216. }
  217. return;
  218. }
  219. if (!g_rfListen)
  220. return;
  221. if (NRF_RADIO->EVENTS_END) {
  222. NRF_RADIO->EVENTS_END = 0;
  223. bool crcok = NRF_RADIO->CRCSTATUS & 1;
  224. uint8_t len = rfrx[0];
  225. if (len && len < 64) {
  226. g_rfRxCount++;
  227. Serial.printf("RF#%lu ch%u crc%d len%u: ",
  228. (unsigned long)g_rfRxCount, g_rfCh, crcok,
  229. len);
  230. for (uint8_t i = 0; i <= len && i < 40; i++)
  231. Serial.printf("%02X ", rfrx[i]);
  232. Serial.println();
  233. }
  234. }
  235. }
  236. // ===================== autosweep candidate radio configs =====================
  237. // Cycle configs while beaconing so ONE controller search window covers the space. S0LEN=1 / CRC16 0x11021 /
  238. // addr "ibex" held fixed; sweep the residual unknowns (PHY, whitening, BALEN, prefix).
  239. struct RfCfg {
  240. uint8_t mode;
  241. uint32_t pcnf0, pcnf1;
  242. uint8_t whiteiv;
  243. uint16_t crccnf;
  244. bool prefRaw;
  245. const char *tag;
  246. };
  247. static const RfCfg SWEEP[] = {
  248. { RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x02040040, 37, 0x2, false,
  249. "1M BALEN4 whiv37 pfxRev" },
  250. { RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x00040040, 0, 0x2, false,
  251. "1M BALEN4 whOFF pfxRev" },
  252. { RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x02040040, 37, 0x2, true,
  253. "1M BALEN4 whiv37 pfxRAW" },
  254. { RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x00040040, 0, 0x2, true,
  255. "1M BALEN4 whOFF pfxRAW" },
  256. { RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x02030040, 37, 0x2, false,
  257. "1M BALEN3 whiv37 pfxRev" },
  258. { RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x00030040, 0, 0x2, true,
  259. "1M BALEN3 whOFF pfxRAW" },
  260. { RADIO_MODE_MODE_Ble_2Mbit, 0x00100108, 0x02040040, 37, 0x2, false,
  261. "2M BALEN4 whiv37 pfxRev" },
  262. { RADIO_MODE_MODE_Ble_2Mbit, 0x00100108, 0x00040040, 0, 0x2, true,
  263. "2M BALEN4 whOFF pfxRAW" },
  264. };
  265. void applyCfg(uint8_t i)
  266. {
  267. const RfCfg &c = SWEEP[i];
  268. g_mode = c.mode;
  269. g_pcnf0 = c.pcnf0;
  270. g_pcnf1 = c.pcnf1;
  271. g_whiteiv = c.whiteiv;
  272. g_crccnf = c.crccnf;
  273. g_prefixRaw = c.prefRaw;
  274. Serial.printf("# cfg[%u] %s\n", i, c.tag);
  275. }
  276. #define SWEEP_N (sizeof SWEEP / sizeof SWEEP[0])
  277. // ===================== dongle beacon experiment =====================
  278. // Dongle is PTX: TX a beacon [01][seq][..][E2@5] then RX the controller's response (0xF0...).
  279. void rfBeaconOnce()
  280. {
  281. uint8_t pl[48];
  282. memset(pl, 0, sizeof pl);
  283. pl[0] = 0x01;
  284. pl[1] = g_seq++;
  285. pl[5] = 0xE2; // dongle-beacon marker
  286. rfConfig(g_rfCh);
  287. rfSetAddr(g_rfBase, g_rfPrefix);
  288. if (g_s1incl)
  289. NRF_RADIO->PCNF0 |= (1u << RADIO_PCNF0_S1INCL_Pos);
  290. // RAM: [LENGTH][ (S1 if S1INCL) ][payload]
  291. rftx[0] = 0;
  292. rftx[1] = g_plen;
  293. // g_plen comes unclamped from the console 'L' command (0..255); the source is pl[48] and the dest is
  294. // rftx[100] written at +2, so copy at most 48 bytes -- otherwise we over-read the stack and overrun rftx.
  295. uint8_t cpLen = g_plen > sizeof pl ? (uint8_t)sizeof pl : g_plen;
  296. memcpy(rftx + 2, pl, cpLen);
  297. NRF_RADIO->PACKETPTR = (uint32_t)rftx;
  298. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
  299. RADIO_SHORTS_END_DISABLE_Msk;
  300. NRF_RADIO->EVENTS_DISABLED = 0;
  301. NRF_RADIO->TASKS_TXEN = 1;
  302. RWAIT_DISABLED();
  303. NRF_RADIO->EVENTS_DISABLED = 0;
  304. // RX window for the controller's response
  305. NRF_RADIO->PACKETPTR = (uint32_t)rfrx;
  306. rfrx[0] = 0;
  307. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk;
  308. NRF_RADIO->EVENTS_END = 0;
  309. NRF_RADIO->TASKS_RXEN = 1;
  310. uint32_t t0 = micros();
  311. while (!NRF_RADIO->EVENTS_END && (micros() - t0) < 500) {
  312. }
  313. if (NRF_RADIO->EVENTS_END) {
  314. NRF_RADIO->EVENTS_END = 0;
  315. bool crcok = NRF_RADIO->CRCSTATUS & 1;
  316. uint8_t len = rfrx[0];
  317. if (len && len < 64) {
  318. g_rfRxCount++;
  319. Serial.printf("RESP#%lu crc%d len%u: ",
  320. (unsigned long)g_rfRxCount, crcok, len);
  321. for (uint8_t i = 0; i <= len && i < 40; i++)
  322. Serial.printf("%02X ", rfrx[i]);
  323. Serial.println();
  324. }
  325. }
  326. NRF_RADIO->TASKS_DISABLE = 1;
  327. RWAIT_DISABLED();
  328. NRF_RADIO->EVENTS_DISABLED = 0;
  329. }
  330. // ===================== scan-then-respond (dongle role) =====================
  331. // Listen on ch2 for the controller's advertisement; the instant one arrives, TX the host frame (its ack).
  332. static void buildHostTx()
  333. {
  334. int slot = -1;
  335. for (int i = 0; i < NSLOT; i++)
  336. if (g_slot[i].used) {
  337. slot = i;
  338. break;
  339. }
  340. memset(g_hostTx, 0, sizeof g_hostTx);
  341. g_hostLen = 0;
  342. if (slot < 0)
  343. return;
  344. // S0LEN=1 RAM layout: [0]=S0=0x12, [1]=LENGTH, raw RAM offsets validated by controller.
  345. memset(g_hostTx, 0, sizeof g_hostTx);
  346. g_hostTx[0] = 0x12;
  347. g_hostTx[1] = 0x20;
  348. g_hostTx[3] = 1;
  349. g_hostTx[5] = 0xE1;
  350. memcpy(g_hostTx + 6, g_slot[slot].rec, 4);
  351. memcpy(g_hostTx + 10, g_slot[slot].rec + 4, 4);
  352. g_hostTx[0xe] = 2;
  353. memcpy(g_hostTx + 0x12, g_rfBase, 4);
  354. g_hostTx[0x16] = g_rfPrefix;
  355. g_hostLen = 0x22;
  356. }
  357. void rfRespondStart()
  358. {
  359. buildHostTx();
  360. rfConfig(2);
  361. rfSetAddr(g_rfBase, g_rfPrefix); // RX ch2 / 91A2A793
  362. NRF_RADIO->PACKETPTR = (uint32_t)rfrx;
  363. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
  364. RADIO_SHORTS_END_START_Msk;
  365. NRF_RADIO->EVENTS_END = 0;
  366. NRF_RADIO->TASKS_RXEN = 1;
  367. g_rfRespond = true;
  368. Serial.printf(
  369. "# RESPOND mode: RX ch2/91A2A793, reply host frame (len%d)\n",
  370. g_hostLen);
  371. }
  372. static void rfRespondPoll()
  373. {
  374. if (!g_rfRespond)
  375. return;
  376. if (NRF_RADIO->EVENTS_END) {
  377. NRF_RADIO->EVENTS_END = 0;
  378. uint8_t len = rfrx[0];
  379. if (len && len < 64) {
  380. g_advCount++;
  381. // reply: TX the host frame immediately (ack)
  382. NRF_RADIO->TASKS_DISABLE = 1;
  383. RWAIT_DISABLED();
  384. NRF_RADIO->EVENTS_DISABLED = 0;
  385. NRF_RADIO->PACKETPTR = (uint32_t)g_hostTx;
  386. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
  387. RADIO_SHORTS_END_DISABLE_Msk;
  388. NRF_RADIO->EVENTS_DISABLED = 0;
  389. NRF_RADIO->TASKS_TXEN = 1;
  390. RWAIT_DISABLED();
  391. NRF_RADIO->EVENTS_DISABLED = 0;
  392. Serial.printf("ADV#%lu len%u: ",
  393. (unsigned long)g_advCount, len);
  394. for (uint8_t i = 0; i <= len && i < 32; i++)
  395. Serial.printf("%02X", rfrx[i]);
  396. Serial.println();
  397. NRF_RADIO->PACKETPTR = (uint32_t)rfrx;
  398. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
  399. RADIO_SHORTS_END_START_Msk;
  400. NRF_RADIO->EVENTS_END = 0;
  401. NRF_RADIO->TASKS_RXEN = 1;
  402. }
  403. }
  404. }
  405. // ===================== live-session sniffer =====================
  406. // phase 0 RX the real puck's host frame on "ibex"/ch2 to learn the random session base/prefix/channel; phase 1
  407. // RX that session address on the primary channel to capture the live connected exchange (E3 poll + F1 reply).
  408. void rfSniffStart()
  409. {
  410. // PARK (no hop): primary carries the traffic
  411. uint8_t ch =
  412. (g_sniffPh == 0) ? 2 : (g_sniffPark ? g_sniffPark : g_schan[0]);
  413. const uint8_t *base = (g_sniffPh == 0) ? g_rfBase : g_sbase;
  414. uint8_t pfx = (g_sniffPh == 0) ? g_rfPrefix : g_sprefix;
  415. // Ble_2Mbit, PCNF0=0x30008, ENDIAN big, BALEN4, CRC16 0x11021
  416. rfConfig(ch);
  417. uint8_t b[4];
  418. for (int i = 0; i < 4; i++)
  419. b[i] = rfBitrev8(base[i]);
  420. NRF_RADIO->BASE0 = ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
  421. ((uint32_t)b[2] << 8) | b[3];
  422. NRF_RADIO->PREFIX0 = rfBitrev8(pfx);
  423. NRF_RADIO->TXADDRESS = 0;
  424. NRF_RADIO->RXADDRESSES = 1;
  425. NRF_RADIO->PACKETPTR = (uint32_t)rfrx;
  426. memset(rfrx, 0, 4);
  427. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
  428. RADIO_SHORTS_END_START_Msk;
  429. NRF_RADIO->EVENTS_END = 0;
  430. NRF_RADIO->TASKS_RXEN = 1;
  431. }
  432. static void rfSniffPoll()
  433. {
  434. if (!g_sniff)
  435. return;
  436. if (NRF_RADIO->EVENTS_END) {
  437. NRF_RADIO->EVENTS_END = 0;
  438. bool crcok = NRF_RADIO->CRCSTATUS & 1;
  439. uint8_t len = rfrx[0];
  440. if (crcok && len && len < 40) {
  441. g_sniffN++;
  442. // host frame -> learn session params + DUMP FULL (app-data may ride here)
  443. if (g_sniffPh == 0 && rfrx[2] == 0xE1) {
  444. memcpy(g_sbase, rfrx + 15, 4);
  445. g_sprefix = rfrx[19];
  446. g_schan[0] = rfrx[11];
  447. Serial.printf(
  448. "# HOSTFRAME ch=%u sbase=%02X%02X%02X%02X prefix=%02X len%u FULL: ",
  449. rfrx[11], g_sbase[0], g_sbase[1],
  450. g_sbase[2], g_sbase[3], g_sprefix, len);
  451. {
  452. uint8_t dn = (len + 2 < 64) ? len + 2 :
  453. 64;
  454. for (uint8_t i = 0; i < dn; i++)
  455. Serial.printf("%02X", rfrx[i]);
  456. }
  457. Serial.println();
  458. g_sniffPh = 1;
  459. g_schi = 0;
  460. rfSniffStart();
  461. } else if (g_sniffPh == 1) {
  462. // 0xF1=input, 0x12/0xE1/0xE4=puck host-frame w/ app-data
  463. uint8_t ty = rfrx[2];
  464. const char *tag = (ty == 0xF1) ? "<<<INPUT" :
  465. (ty == 0x12 || ty == 0xE1 ||
  466. ty == 0xE4) ?
  467. ">>>HOSTFR" :
  468. " pkt";
  469. Serial.printf("%s SNIFF#%lu ch%u len%u: ", tag,
  470. (unsigned long)g_sniffN,
  471. g_schan[g_schi], len);
  472. // FULL frame (app-data TLVs sit near the end)
  473. uint8_t dn = (len + 2 < 64) ? len + 2 : 64;
  474. for (uint8_t i = 0; i < dn; i++)
  475. Serial.printf("%02X", rfrx[i]);
  476. Serial.println();
  477. }
  478. }
  479. }
  480. }
  481. void rfDiagTask()
  482. {
  483. rfPoll();
  484. rfRespondPoll();
  485. rfCapPoll();
  486. if (g_rfCap && millis() - g_lastHop >=
  487. 90) { // sweep config combos on ch2, CRC-validate
  488. g_lastHop = millis();
  489. g_capV = (uint8_t)(g_capV + 1);
  490. rfCapStart(g_rfCh);
  491. }
  492. if (g_rfBeacon && millis() - g_lastBeacon >= 5) {
  493. g_lastBeacon = millis();
  494. rfBeaconOnce();
  495. }
  496. if (g_rfSweep && millis() - g_lastHop >= 60) {
  497. g_lastHop = millis();
  498. g_rfCh += 2;
  499. if (g_rfCh > 80)
  500. g_rfCh = 2;
  501. rfRawStart(g_rfCh);
  502. }
  503. if (g_rfAuto &&
  504. millis() - g_lastCfg >=
  505. 500) { // advance to next candidate config every 500ms
  506. g_lastCfg = millis();
  507. g_cfgIdx = (g_cfgIdx + 1) % SWEEP_N;
  508. applyCfg(g_cfgIdx);
  509. }
  510. if (g_rfReplay) {
  511. rfReplayOnce();
  512. } // bit-perfect replay of a captured real-puck frame on ch2
  513. rfSniffPoll();
  514. if (g_rfRespond &&
  515. millis() - g_lastHop >=
  516. 25) { // scan the 3 BLE adv channels w/ matched whitening
  517. g_lastHop = millis();
  518. static const uint8_t advf[3] = { 2, 26, 80 },
  519. advw[3] = { 37, 38, 39 };
  520. static uint8_t ai = 0;
  521. ai = (ai + 1) % 3;
  522. g_rfCh = advf[ai];
  523. g_whiteiv = advw[ai];
  524. rfConfig(g_rfCh);
  525. rfSetAddr(g_rfBase, g_rfPrefix);
  526. NRF_RADIO->PACKETPTR = (uint32_t)rfrx;
  527. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk |
  528. RADIO_SHORTS_END_START_Msk;
  529. NRF_RADIO->EVENTS_END = 0;
  530. NRF_RADIO->TASKS_RXEN = 1;
  531. }
  532. }