rf_diag.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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]; static int g_hostLen=0;
  24. static uint32_t g_sniffN=0;
  25. static uint8_t g_sbase[4]={0,0,0,0}, g_sprefix=0; static uint8_t g_schan[3]={78,2,80}; static uint8_t g_schi=0;
  26. // ===================== promiscuous raw capture (calibration only) =====================
  27. // preamble-match on 0x55 bytes, fixed-length grab, no CRC. Catches any 2Mbit packet on a channel so we can
  28. // read the controller's reconnect addr/prefix/framing.
  29. #define RAWCAP 48
  30. void rfRawStart(uint8_t ch){
  31. NRF_RADIO->TASKS_DISABLE=1; RWAIT_DISABLED(); NRF_RADIO->EVENTS_DISABLED=0;
  32. NRF_RADIO->MODE=(RADIO_MODE_MODE_Nrf_2Mbit<<RADIO_MODE_MODE_Pos);
  33. #if defined(RADIO_MODECNF0_RU_Fast)
  34. NRF_RADIO->MODECNF0=(RADIO_MODECNF0_RU_Fast<<RADIO_MODECNF0_RU_Pos);
  35. #endif
  36. NRF_RADIO->FREQUENCY=ch; NRF_RADIO->TXPOWER=(RADIO_TXPOWER_TXPOWER_0dBm<<RADIO_TXPOWER_TXPOWER_Pos);
  37. NRF_RADIO->CRCCNF=0; NRF_RADIO->PCNF0=0;
  38. NRF_RADIO->PCNF1=(RADIO_PCNF1_WHITEEN_Disabled<<RADIO_PCNF1_WHITEEN_Pos)
  39. |(RADIO_PCNF1_ENDIAN_Big<<RADIO_PCNF1_ENDIAN_Pos)
  40. |(2u<<RADIO_PCNF1_BALEN_Pos)|(RAWCAP<<RADIO_PCNF1_STATLEN_Pos)|(RAWCAP<<RADIO_PCNF1_MAXLEN_Pos);
  41. NRF_RADIO->BASE0=0x55555555; NRF_RADIO->PREFIX0=0x00000055;
  42. NRF_RADIO->TXADDRESS=0; NRF_RADIO->RXADDRESSES=1; NRF_RADIO->PACKETPTR=(uint32_t)rfrx;
  43. NRF_RADIO->SHORTS=RADIO_SHORTS_READY_START_Msk|RADIO_SHORTS_END_START_Msk;
  44. NRF_RADIO->EVENTS_END=0; NRF_RADIO->TASKS_RXEN=1;
  45. }
  46. // ===================== CRC-validating framing sweep =====================
  47. // Data looked bit-reversed (not whitened). Firmware-correct: ESB DPL (S0L0, LFLEN6or8, S1LEN3), BALEN4
  48. // (5-byte addr), CRC8/16 addr-included. Sweep PCNF0{LFLEN6,8} x CRC{8,16} x BALEN{4,3} x ENDIAN{Big,Lit}.
  49. static const uint32_t CAPP0B[]={0x00030006,0x00030008}; // S1LEN3 + LFLEN6 / LFLEN8 (dynamic length)
  50. static const uint32_t CAPCRCB[][3]={{0x1,0x107,0xFF},{0x2,0x11021,0xFFFF}};
  51. void rfCapStart(uint8_t ch){
  52. uint8_t ci=g_capV&1;
  53. uint8_t balen=((g_capV>>1)&1)?3:4; // BALEN 4 first
  54. uint8_t endbig=((g_capV>>2)&1)^1; // ENDIAN Big first
  55. uint32_t p0=CAPP0B[(g_capV>>3)&1];
  56. const uint32_t* cr=CAPCRCB[ci];
  57. NRF_RADIO->TASKS_DISABLE=1; RWAIT_DISABLED(); NRF_RADIO->EVENTS_DISABLED=0;
  58. NRF_RADIO->MODE=(RADIO_MODE_MODE_Ble_2Mbit<<RADIO_MODE_MODE_Pos);
  59. #if defined(RADIO_MODECNF0_RU_Fast)
  60. NRF_RADIO->MODECNF0=(RADIO_MODECNF0_RU_Fast<<RADIO_MODECNF0_RU_Pos);
  61. #endif
  62. NRF_RADIO->FREQUENCY=ch;
  63. NRF_RADIO->PCNF0=p0;
  64. NRF_RADIO->PCNF1=((uint32_t)endbig<<RADIO_PCNF1_ENDIAN_Pos)|((uint32_t)balen<<RADIO_PCNF1_BALEN_Pos)
  65. |(60u<<RADIO_PCNF1_MAXLEN_Pos);
  66. NRF_RADIO->CRCCNF=cr[0]; NRF_RADIO->CRCPOLY=cr[1]; NRF_RADIO->CRCINIT=cr[2];
  67. uint8_t b[4]; for(int i=0;i<4;i++) b[i]=rfBitrev8(g_rfBase[i]);
  68. NRF_RADIO->BASE0=((uint32_t)b[0]<<24)|((uint32_t)b[1]<<16)|((uint32_t)b[2]<<8)|b[3];
  69. NRF_RADIO->PREFIX0=rfBitrev8(g_rfPrefix); NRF_RADIO->TXADDRESS=0; NRF_RADIO->RXADDRESSES=1;
  70. NRF_RADIO->PACKETPTR=(uint32_t)rfrx; memset(rfrx,0,8);
  71. NRF_RADIO->SHORTS=RADIO_SHORTS_READY_START_Msk|RADIO_SHORTS_END_START_Msk;
  72. NRF_RADIO->EVENTS_END=0; NRF_RADIO->TASKS_RXEN=1;
  73. }
  74. // REPLAY: capture one real-puck frame (raw, ENDIAN=Little) then re-transmit it VERBATIM (same bits, addr, ch)
  75. // to impersonate the puck bit-for-bit -- sidesteps unknown framing/CRC. Controller must be bonded to the REAL
  76. // puck (replayed frame carries real-puck uuids + its valid CRC).
  77. static void rfCapPoll(){
  78. if(!g_rfCap) return;
  79. if(NRF_RADIO->EVENTS_END){ NRF_RADIO->EVENTS_END=0;
  80. g_capPass++;
  81. if(g_rfCapOne){ memcpy(g_replay,rfrx,sizeof g_replay); g_replayLen=32; g_rfCapOne=false; g_rfCap=false;
  82. Serial.printf("# captured replay frame (%uB): ",g_replayLen);
  83. for(int i=0;i<g_replayLen;i++)Serial.printf("%02X",g_replay[i]); Serial.println(); return; }
  84. if(NRF_RADIO->CRCSTATUS&1){
  85. uint8_t ci=g_capV&1; uint8_t balen=((g_capV>>1)&1)?3:4; uint8_t eb=((g_capV>>2)&1)^1;
  86. uint32_t p0=CAPP0B[(g_capV>>3)&1];
  87. Serial.printf("@@@ CRCOK PCNF0=%lX BALEN=%u ENDIAN=%s POLY=%lX: ",(unsigned long)p0,balen,
  88. eb?"Big":"Lit",(unsigned long)CAPCRCB[ci][1]);
  89. for(int i=0;i<28;i++) Serial.printf("%02X",rfrx[i]); Serial.println();
  90. }
  91. }
  92. }
  93. void rfReplayOnce(){
  94. if(!g_replayLen) return;
  95. NRF_RADIO->TASKS_DISABLE=1; RWAIT_DISABLED(); NRF_RADIO->EVENTS_DISABLED=0;
  96. NRF_RADIO->MODE=(RADIO_MODE_MODE_Ble_2Mbit<<RADIO_MODE_MODE_Pos);
  97. #if defined(RADIO_MODECNF0_RU_Fast)
  98. NRF_RADIO->MODECNF0=(RADIO_MODECNF0_RU_Fast<<RADIO_MODECNF0_RU_Pos);
  99. #endif
  100. NRF_RADIO->FREQUENCY=2; // ch2
  101. NRF_RADIO->PCNF0=0; // static, ENDIAN=Little -> reproduce on-air bits
  102. NRF_RADIO->PCNF1=(3u<<RADIO_PCNF1_BALEN_Pos)|((uint32_t)g_replayLen<<RADIO_PCNF1_STATLEN_Pos)|((uint32_t)g_replayLen<<RADIO_PCNF1_MAXLEN_Pos);
  103. NRF_RADIO->CRCCNF=0; // CRC already inside the captured bytes
  104. uint8_t b[4]; for(int i=0;i<4;i++) b[i]=rfBitrev8(g_rfBase[i]);
  105. NRF_RADIO->BASE0=((uint32_t)b[0]<<24)|((uint32_t)b[1]<<16)|((uint32_t)b[2]<<8)|b[3];
  106. NRF_RADIO->PREFIX0=rfBitrev8(g_rfPrefix); NRF_RADIO->TXADDRESS=0;
  107. NRF_RADIO->PACKETPTR=(uint32_t)g_replay;
  108. NRF_RADIO->SHORTS=RADIO_SHORTS_READY_START_Msk|RADIO_SHORTS_END_DISABLE_Msk;
  109. NRF_RADIO->EVENTS_DISABLED=0; NRF_RADIO->TASKS_TXEN=1;
  110. RWAIT_DISABLED(); NRF_RADIO->EVENTS_DISABLED=0;
  111. NRF_RADIO->TASKS_DISABLE=1; RWAIT_DISABLED(); NRF_RADIO->EVENTS_DISABLED=0;
  112. }
  113. // ===================== address/CRC listen =====================
  114. void rfListenStart(){
  115. rfConfig(g_rfCh); rfSetAddr(g_rfBase, g_rfPrefix);
  116. NRF_RADIO->PACKETPTR = (uint32_t)rfrx;
  117. NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_START_Msk;
  118. NRF_RADIO->EVENTS_END = 0; NRF_RADIO->TASKS_RXEN = 1;
  119. g_rfListen = true;
  120. Serial.printf("# RF listen ch%u base %02X%02X%02X%02X prefix %02X\n",
  121. g_rfCh, g_rfBase[0],g_rfBase[1],g_rfBase[2],g_rfBase[3], g_rfPrefix);
  122. }
  123. static void rfPoll(){
  124. if (g_rfRaw){
  125. if (NRF_RADIO->EVENTS_END){
  126. NRF_RADIO->EVENTS_END = 0;
  127. // filter noise: require some non-0x55/0xAA/0x00 structure
  128. int nz=0; for(int i=0;i<RAWCAP;i++){uint8_t b=rfrx[i]; if(b&&b!=0x55&&b!=0xAA&&b!=0xFF)nz++;}
  129. if (nz>=6){
  130. g_rfRxCount++;
  131. Serial.printf("RAW#%lu ch%u: ", (unsigned long)g_rfRxCount, g_rfCh);
  132. for (int i=0;i<RAWCAP;i++) Serial.printf("%02X", rfrx[i]);
  133. Serial.println();
  134. }
  135. }
  136. return;
  137. }
  138. if (!g_rfListen) return;
  139. if (NRF_RADIO->EVENTS_END){
  140. NRF_RADIO->EVENTS_END = 0;
  141. bool crcok = NRF_RADIO->CRCSTATUS & 1;
  142. uint8_t len = rfrx[0];
  143. if (len && len < 64){
  144. g_rfRxCount++;
  145. Serial.printf("RF#%lu ch%u crc%d len%u: ", (unsigned long)g_rfRxCount, g_rfCh, crcok, len);
  146. for (uint8_t i=0;i<=len && i<40;i++) Serial.printf("%02X ", rfrx[i]);
  147. Serial.println();
  148. }
  149. }
  150. }
  151. // ===================== autosweep candidate radio configs =====================
  152. // Cycle candidate radio configs while beaconing, so ONE controller search window covers the space. S0LEN=1 /
  153. // CRC16 0x11021 / addr "ibex" held fixed; sweep the residual unknowns (PHY, whitening, BALEN, prefix).
  154. struct RfCfg { uint8_t mode; uint32_t pcnf0, pcnf1; uint8_t whiteiv; uint16_t crccnf; bool prefRaw; const char* tag; };
  155. static const RfCfg SWEEP[] = {
  156. {RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x02040040, 37, 0x2, false, "1M BALEN4 whiv37 pfxRev"},
  157. {RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x00040040, 0, 0x2, false, "1M BALEN4 whOFF pfxRev"},
  158. {RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x02040040, 37, 0x2, true, "1M BALEN4 whiv37 pfxRAW"},
  159. {RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x00040040, 0, 0x2, true, "1M BALEN4 whOFF pfxRAW"},
  160. {RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x02030040, 37, 0x2, false, "1M BALEN3 whiv37 pfxRev"},
  161. {RADIO_MODE_MODE_Ble_1Mbit, 0x00100108, 0x00030040, 0, 0x2, true, "1M BALEN3 whOFF pfxRAW"},
  162. {RADIO_MODE_MODE_Ble_2Mbit, 0x00100108, 0x02040040, 37, 0x2, false, "2M BALEN4 whiv37 pfxRev"},
  163. {RADIO_MODE_MODE_Ble_2Mbit, 0x00100108, 0x00040040, 0, 0x2, true, "2M BALEN4 whOFF pfxRAW"},
  164. };
  165. void applyCfg(uint8_t i){
  166. const RfCfg &c = SWEEP[i];
  167. g_mode=c.mode; g_pcnf0=c.pcnf0; g_pcnf1=c.pcnf1; g_whiteiv=c.whiteiv; g_crccnf=c.crccnf; g_prefixRaw=c.prefRaw;
  168. Serial.printf("# cfg[%u] %s\n", i, c.tag);
  169. }
  170. #define SWEEP_N (sizeof SWEEP/sizeof SWEEP[0])
  171. // ===================== dongle beacon experiment =====================
  172. // Dongle is PTX: TX a beacon [01][seq][..][E2@5] then RX the controller's response (0xF0...).
  173. void rfBeaconOnce(){
  174. uint8_t pl[48]; memset(pl,0,sizeof pl);
  175. pl[0]=0x01; pl[1]=g_seq++; pl[5]=0xE2; // dongle beacon (FUN_00027ed8 format)
  176. rfConfig(g_rfCh); rfSetAddr(g_rfBase,g_rfPrefix);
  177. if (g_s1incl) NRF_RADIO->PCNF0 |= (1u<<RADIO_PCNF0_S1INCL_Pos);
  178. // RAM: [LENGTH][ (S1 if S1INCL) ][payload]
  179. rftx[0]=0; rftx[1]=g_plen; memcpy(rftx+2,pl,g_plen);
  180. NRF_RADIO->PACKETPTR=(uint32_t)rftx;
  181. NRF_RADIO->SHORTS=RADIO_SHORTS_READY_START_Msk|RADIO_SHORTS_END_DISABLE_Msk;
  182. NRF_RADIO->EVENTS_DISABLED=0; NRF_RADIO->TASKS_TXEN=1;
  183. RWAIT_DISABLED(); NRF_RADIO->EVENTS_DISABLED=0;
  184. // RX window for the controller's response
  185. NRF_RADIO->PACKETPTR=(uint32_t)rfrx; rfrx[0]=0;
  186. NRF_RADIO->SHORTS=RADIO_SHORTS_READY_START_Msk;
  187. NRF_RADIO->EVENTS_END=0; NRF_RADIO->TASKS_RXEN=1;
  188. uint32_t t0=micros(); while(!NRF_RADIO->EVENTS_END && (micros()-t0)<500){}
  189. if (NRF_RADIO->EVENTS_END){ NRF_RADIO->EVENTS_END=0;
  190. bool crcok=NRF_RADIO->CRCSTATUS&1; uint8_t len=rfrx[0];
  191. if (len && len<64){ g_rfRxCount++;
  192. Serial.printf("RESP#%lu crc%d len%u: ",(unsigned long)g_rfRxCount,crcok,len);
  193. for(uint8_t i=0;i<=len&&i<40;i++)Serial.printf("%02X ",rfrx[i]); Serial.println();
  194. }
  195. }
  196. NRF_RADIO->TASKS_DISABLE=1; RWAIT_DISABLED(); NRF_RADIO->EVENTS_DISABLED=0;
  197. }
  198. // ===================== scan-then-respond (dongle role) =====================
  199. // Listen on ch2 for the controller's advertisement, and the instant one arrives, TX the host frame (its ack).
  200. static void buildHostTx(){
  201. int slot=-1; for(int i=0;i<NSLOT;i++) if(g_slot[i].used){slot=i;break;}
  202. memset(g_hostTx,0,sizeof g_hostTx); g_hostLen=0; if(slot<0) return;
  203. // S0LEN=1 RAM layout: [0]=S0=0x12, [1]=LENGTH, raw RAM offsets validated by controller.
  204. memset(g_hostTx,0,sizeof g_hostTx);
  205. g_hostTx[0]=0x12; g_hostTx[1]=0x20; g_hostTx[3]=1; g_hostTx[5]=0xE1;
  206. memcpy(g_hostTx+6,g_slot[slot].rec,4); memcpy(g_hostTx+10,g_slot[slot].rec+4,4);
  207. g_hostTx[0xe]=2; memcpy(g_hostTx+0x12,g_rfBase,4); g_hostTx[0x16]=g_rfPrefix;
  208. g_hostLen=0x22;
  209. }
  210. void rfRespondStart(){
  211. buildHostTx();
  212. rfConfig(2); rfSetAddr(g_rfBase,g_rfPrefix); // RX ch2 / 91A2A793
  213. NRF_RADIO->PACKETPTR=(uint32_t)rfrx;
  214. NRF_RADIO->SHORTS=RADIO_SHORTS_READY_START_Msk|RADIO_SHORTS_END_START_Msk;
  215. NRF_RADIO->EVENTS_END=0; NRF_RADIO->TASKS_RXEN=1; g_rfRespond=true;
  216. Serial.printf("# RESPOND mode: RX ch2/91A2A793, reply host frame (len%d)\n", g_hostLen);
  217. }
  218. static void rfRespondPoll(){
  219. if(!g_rfRespond) return;
  220. if(NRF_RADIO->EVENTS_END){
  221. NRF_RADIO->EVENTS_END=0; uint8_t len=rfrx[0];
  222. if(len && len<64){
  223. g_advCount++;
  224. // reply: TX the host frame immediately (ack)
  225. NRF_RADIO->TASKS_DISABLE=1; RWAIT_DISABLED(); NRF_RADIO->EVENTS_DISABLED=0;
  226. NRF_RADIO->PACKETPTR=(uint32_t)g_hostTx;
  227. NRF_RADIO->SHORTS=RADIO_SHORTS_READY_START_Msk|RADIO_SHORTS_END_DISABLE_Msk;
  228. NRF_RADIO->EVENTS_DISABLED=0; NRF_RADIO->TASKS_TXEN=1;
  229. RWAIT_DISABLED(); NRF_RADIO->EVENTS_DISABLED=0;
  230. Serial.printf("ADV#%lu len%u: ",(unsigned long)g_advCount,len);
  231. for(uint8_t i=0;i<=len&&i<32;i++)Serial.printf("%02X",rfrx[i]); Serial.println();
  232. // re-arm RX
  233. NRF_RADIO->PACKETPTR=(uint32_t)rfrx;
  234. NRF_RADIO->SHORTS=RADIO_SHORTS_READY_START_Msk|RADIO_SHORTS_END_START_Msk;
  235. NRF_RADIO->EVENTS_END=0; NRF_RADIO->TASKS_RXEN=1;
  236. }
  237. }
  238. }
  239. // ===================== live-session sniffer =====================
  240. // phase 0 RX the real puck's host frame on "ibex"/ch2 to learn the random session base/prefix/channel; phase 1
  241. // RX that session address on the primary channel to capture the live connected exchange (E3 poll + F1 reply).
  242. void rfSniffStart(){
  243. uint8_t ch = (g_sniffPh==0)?2:(g_sniffPark?g_sniffPark:g_schan[0]); // PARK (no hop): primary carries the traffic
  244. const uint8_t* base = (g_sniffPh==0)?g_rfBase:g_sbase;
  245. uint8_t pfx = (g_sniffPh==0)?g_rfPrefix:g_sprefix;
  246. rfConfig(ch); // Ble_2Mbit, PCNF0=0x30008, ENDIAN big, BALEN4, CRC16 0x11021
  247. uint8_t b[4]; for(int i=0;i<4;i++) b[i]=rfBitrev8(base[i]);
  248. NRF_RADIO->BASE0=((uint32_t)b[0]<<24)|((uint32_t)b[1]<<16)|((uint32_t)b[2]<<8)|b[3];
  249. NRF_RADIO->PREFIX0=rfBitrev8(pfx); NRF_RADIO->TXADDRESS=0; NRF_RADIO->RXADDRESSES=1;
  250. NRF_RADIO->PACKETPTR=(uint32_t)rfrx; memset(rfrx,0,4);
  251. NRF_RADIO->SHORTS=RADIO_SHORTS_READY_START_Msk|RADIO_SHORTS_END_START_Msk;
  252. NRF_RADIO->EVENTS_END=0; NRF_RADIO->TASKS_RXEN=1;
  253. }
  254. static void rfSniffPoll(){
  255. if(!g_sniff) return;
  256. if(NRF_RADIO->EVENTS_END){ NRF_RADIO->EVENTS_END=0;
  257. bool crcok=NRF_RADIO->CRCSTATUS&1; uint8_t len=rfrx[0];
  258. if(crcok && len && len<40){
  259. g_sniffN++;
  260. if(g_sniffPh==0 && rfrx[2]==0xE1){ // host frame -> learn session params + DUMP FULL (app-data may ride here)
  261. memcpy(g_sbase,rfrx+15,4); g_sprefix=rfrx[19]; g_schan[0]=rfrx[11];
  262. Serial.printf("# HOSTFRAME ch=%u sbase=%02X%02X%02X%02X prefix=%02X len%u FULL: ",
  263. rfrx[11],g_sbase[0],g_sbase[1],g_sbase[2],g_sbase[3],g_sprefix,len);
  264. { uint8_t dn=(len+2<64)?len+2:64; for(uint8_t i=0;i<dn;i++)Serial.printf("%02X",rfrx[i]); } Serial.println();
  265. g_sniffPh=1; g_schi=0; rfSniffStart();
  266. } else if(g_sniffPh==1){
  267. uint8_t ty=rfrx[2]; // 0xF1=input, 0x12/0xE1/0xE4=puck host-frame w/ app-data
  268. const char* tag = (ty==0xF1)?"<<<INPUT":(ty==0x12||ty==0xE1||ty==0xE4)?">>>HOSTFR":" pkt";
  269. Serial.printf("%s SNIFF#%lu ch%u len%u: ",tag,(unsigned long)g_sniffN,g_schan[g_schi],len);
  270. uint8_t dn=(len+2<64)?len+2:64; // FULL frame (app-data TLVs sit near the end)
  271. for(uint8_t i=0;i<dn;i++)Serial.printf("%02X",rfrx[i]); Serial.println();
  272. }
  273. }
  274. }
  275. }
  276. void rfDiagTask(){
  277. rfPoll();
  278. rfRespondPoll();
  279. rfCapPoll();
  280. if (g_rfCap && millis() - g_lastHop >= 90) { // sweep config combos on ch2, CRC-validate
  281. g_lastHop = millis(); g_capV = (uint8_t)(g_capV+1); rfCapStart(g_rfCh);
  282. }
  283. if (g_rfBeacon && millis() - g_lastBeacon >= 5) { g_lastBeacon = millis(); rfBeaconOnce(); }
  284. if (g_rfSweep && millis() - g_lastHop >= 60) { g_lastHop = millis(); g_rfCh += 2; if (g_rfCh > 80) g_rfCh = 2; rfRawStart(g_rfCh); }
  285. if (g_rfAuto && millis() - g_lastCfg >= 500) { // advance to next candidate config every 500ms
  286. g_lastCfg = millis(); g_cfgIdx = (g_cfgIdx + 1) % SWEEP_N; applyCfg(g_cfgIdx);
  287. }
  288. if (g_rfReplay) { rfReplayOnce(); } // bit-perfect replay of a captured real-puck frame on ch2
  289. rfSniffPoll();
  290. if (g_rfRespond && millis() - g_lastHop >= 25) { // scan the 3 BLE adv channels w/ matched whitening
  291. g_lastHop = millis();
  292. static const uint8_t advf[3]={2,26,80}, advw[3]={37,38,39}; static uint8_t ai=0; ai=(ai+1)%3;
  293. g_rfCh=advf[ai]; g_whiteiv=advw[ai];
  294. rfConfig(g_rfCh); rfSetAddr(g_rfBase,g_rfPrefix); NRF_RADIO->PACKETPTR=(uint32_t)rfrx;
  295. NRF_RADIO->SHORTS=RADIO_SHORTS_READY_START_Msk|RADIO_SHORTS_END_START_Msk; NRF_RADIO->EVENTS_END=0; NRF_RADIO->TASKS_RXEN=1;
  296. }
  297. }