radio.cpp 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. #include "radio.h"
  2. #include "bonds.h" // g_slot[]
  3. #include "config.h"
  4. // Pairing rendezvous bytes from IBEX rodata. The 0x91A2A793 key preceding "ibex" is a DIFFERENT key,
  5. // NOT the discovery address.
  6. const uint8_t PAIR_BASE[4] = { 0x69, 0x62, 0x65, 0x78 }; // "ibex"
  7. uint8_t g_rfPrefix = 0x10;
  8. uint8_t g_rfCh = 2;
  9. uint8_t g_rfBase[4] = { 0x69, 0x62, 0x65, 0x78 }; // "ibex"
  10. // ch18: clean channel in the real puck's active hop set {18,2,80}; bad channels collide with trackpad
  11. // data bursts -> reply-rate crash. Tunable 'C'.
  12. uint8_t g_sessCh = 18;
  13. uint8_t g_rfStartupLastGoodChannel = 0;
  14. // Safe defaults; rfGenSessionAddr(slot) overwrites each used slot at boot. Discovery base "ibex" as a
  15. // degenerate starting point means an UN-bonded slot (and a not-yet-initialized one) won't accidentally
  16. // cross-talk with itself before the real address is set.
  17. uint8_t g_sessBase[NSLOT][4];
  18. uint8_t g_sessPrefix[NSLOT];
  19. static void sessSetDefault(int slot)
  20. {
  21. for (int i = 0; i < 4; i++)
  22. g_sessBase[slot][i] = g_rfBase[i];
  23. g_sessPrefix[slot] = g_rfPrefix;
  24. }
  25. void rfGenSessionAddr(int slot)
  26. {
  27. if (slot < 0 || slot >= NSLOT) {
  28. sessSetDefault(0);
  29. return;
  30. }
  31. sessSetDefault(slot);
  32. // Per-bond UUID (rec[0..8]) + FICR DEVICEID (8 bytes) -> 5-byte on-air address. Mixing the bond UUID in
  33. // makes the address deterministic per (puck, controller) pair: a slot's address is stable across reboots
  34. // without storing anything new in bonds.bin, and the same controller bonded to two OpenPucks gets two
  35. // different session addresses (DEVICEID differs) so the two pucks never collide on-air. The original
  36. // generator (UUID-free) is preserved as the no-UUID fallback for an empty/zero bond record.
  37. const uint8_t *uuid = g_slot[slot].rec;
  38. bool uuidLive = false;
  39. for (int i = 0; i < 8; i++)
  40. if (uuid[i]) {
  41. uuidLive = true;
  42. break;
  43. }
  44. uint32_t h, h2;
  45. if (uuidLive) {
  46. h = (uint32_t)uuid[0] * 0x9E3779B1u ^
  47. (uint32_t)uuid[2] * 0x85EBCA6Bu ^
  48. (uint32_t)uuid[4] * 0xC2B2AE35u ^ (uint32_t)uuid[6] ^
  49. NRF_FICR->DEVICEID[0];
  50. h2 = (uint32_t)uuid[1] * 0x27D4EB2Fu ^
  51. (uint32_t)uuid[3] * 0x165667B1u ^ (uint32_t)uuid[5] ^
  52. (uint32_t)uuid[7] * 0x9E3779B1u ^ NRF_FICR->DEVICEID[1];
  53. } else {
  54. h = NRF_FICR->DEVICEID[0] * 0x9E3779B1u ^ NRF_FICR->DEVICEID[1];
  55. h2 = NRF_FICR->DEVICEID[1] * 0x85EBCA6Bu ^
  56. NRF_FICR->DEVICEID[0];
  57. }
  58. // Scrub degenerate bytes (0x00/0xFF) for clean preamble correlation.
  59. for (int i = 0; i < 4; i++) {
  60. uint8_t b = (uint8_t)(h >> (i * 8));
  61. if (b == 0x00 || b == 0xFF)
  62. b ^= 0x5A;
  63. g_sessBase[slot][i] = b;
  64. }
  65. uint8_t p = (uint8_t)(h2 >> 16);
  66. if (p == 0x00 || p == 0xFF)
  67. p = 0x5C;
  68. g_sessPrefix[slot] = p;
  69. // Never reproduce the shared discovery base "ibex" (else the on-air address is shared with every other
  70. // controller scanning the rendezvous -> isolation is lost).
  71. if (g_sessBase[slot][0] == g_rfBase[0] &&
  72. g_sessBase[slot][1] == g_rfBase[1] &&
  73. g_sessBase[slot][2] == g_rfBase[2] &&
  74. g_sessBase[slot][3] == g_rfBase[3])
  75. g_sessBase[slot][0] ^= 0x80;
  76. }
  77. void rfApplyStartupLastGoodChannel()
  78. {
  79. const uint8_t saved = cfgExtRead(0u);
  80. g_rfStartupLastGoodChannel = (saved > 0u && saved <= 100u) ? saved : 0u;
  81. cfgExtWrite(1u, 1u);
  82. cfgExtWrite(2u, 1u);
  83. if (g_rfStartupLastGoodChannel)
  84. g_sessCh = g_rfStartupLastGoodChannel;
  85. }
  86. bool saveRfStartupLastGoodChannel(uint8_t channel)
  87. {
  88. if (channel == 0u || channel > 100u)
  89. return false;
  90. if (g_rfStartupLastGoodChannel == channel)
  91. return true;
  92. g_rfStartupLastGoodChannel = channel;
  93. cfgExtWrite(0u, channel);
  94. cfgExtWrite(1u, 1u);
  95. cfgExtWrite(2u, 1u);
  96. saveCfg();
  97. return true;
  98. }
  99. uint8_t rfrx[100], rftx[100];
  100. uint32_t g_rfRxCount = 0;
  101. // Puck-link config decoded from real-puck capture: puck TX = Ble_2Mbit, ENDIAN=Big (MSB-first), NO whitening,
  102. // addr "ibex", CRC16 0x11021/0xFFFF; static-length frame.
  103. uint32_t g_crcinit = 0xFFFF;
  104. uint8_t g_whiteiv = 37;
  105. uint8_t g_mode = RADIO_MODE_MODE_Ble_2Mbit;
  106. // S0LEN0, LFLEN8, S1LEN3 (ESB DPL) - CRC-VALIDATED
  107. uint32_t g_pcnf0 = 0x00030008UL;
  108. uint8_t g_statlen = 0x20;
  109. // ENDIAN=Big, BALEN4, MAXLEN=96. MAXLEN must be >=66 or the controller's 0x43-battery-augmented F1 (66B)
  110. // truncates -> CRC-fail -> battery never arrives.
  111. uint32_t g_pcnf1 = 0x01040060;
  112. uint32_t g_crcpoly = 0x11021UL;
  113. uint16_t g_crccnf = 0x2; // CRC16, address included
  114. uint8_t g_pid = 0;
  115. uint8_t g_balen = 4; // ESB 5-byte addr
  116. bool g_prefixRaw = false;
  117. uint8_t rfBitrev8(uint8_t x)
  118. {
  119. uint8_t y = 0;
  120. for (int i = 0; i < 8; i++) {
  121. y = (y << 1) | (x & 1);
  122. x >>= 1;
  123. }
  124. return y;
  125. }
  126. void rfSetAddr(const uint8_t b4[4], uint8_t prefix)
  127. {
  128. uint8_t b[4];
  129. for (int i = 0; i < 4; i++)
  130. b[i] = rfBitrev8(b4[i]);
  131. NRF_RADIO->BASE0 = ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
  132. ((uint32_t)b[2] << 8) | b[3];
  133. NRF_RADIO->PREFIX0 = g_prefixRaw ? prefix : rfBitrev8(prefix);
  134. NRF_RADIO->TXADDRESS = 0;
  135. NRF_RADIO->RXADDRESSES = 1u << 0;
  136. }
  137. void rfConfig(uint8_t ch)
  138. {
  139. NRF_RADIO->TASKS_DISABLE = 1;
  140. RWAIT_DISABLED();
  141. NRF_RADIO->EVENTS_DISABLED = 0;
  142. NRF_RADIO->MODE = ((uint32_t)g_mode << RADIO_MODE_MODE_Pos);
  143. NRF_RADIO->FREQUENCY = ch;
  144. // +8dBm = the nRF52840's max TX power (was 0dBm). The link margin is thin on
  145. // the Pro Micro's PCB-trace antenna (RSSI reads ~20dB below a real puck), and
  146. // 2.4GHz is crowded, so weak polls the controller can't hear -> no reply ->
  147. // the reply gaps that read as intermittent disconnects. This only lifts the
  148. // puck->controller side (poll/beacon reach); the controller's own TX (the F1
  149. // reply) is fixed -- but the poll MUST land for the controller to answer, so
  150. // the extra 8dB of headroom directly cuts missed-poll dropouts. USB-powered,
  151. // so the higher current draw is free.
  152. NRF_RADIO->TXPOWER =
  153. (RADIO_TXPOWER_TXPOWER_Pos8dBm << RADIO_TXPOWER_TXPOWER_Pos);
  154. #if defined(RADIO_MODECNF0_RU_Fast)
  155. NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
  156. #endif
  157. NRF_RADIO->PCNF0 = g_pcnf0;
  158. // g_pcnf1=0 -> build static-length config from g_statlen/g_balen (ENDIAN=Big, WHITEEN=0)
  159. NRF_RADIO->PCNF1 =
  160. g_pcnf1 ? g_pcnf1 :
  161. ((1u << RADIO_PCNF1_ENDIAN_Pos) |
  162. ((uint32_t)g_balen << RADIO_PCNF1_BALEN_Pos) |
  163. ((uint32_t)g_statlen << RADIO_PCNF1_STATLEN_Pos) |
  164. ((uint32_t)g_statlen << RADIO_PCNF1_MAXLEN_Pos));
  165. NRF_RADIO->CRCCNF = g_crccnf; // CRC16, address included
  166. NRF_RADIO->CRCPOLY = g_crcpoly;
  167. NRF_RADIO->CRCINIT = g_crcinit;
  168. NRF_RADIO->DATAWHITEIV = g_whiteiv;
  169. NRF_RADIO->PACKETPTR = (uint32_t)rfrx;
  170. }