#include "radio.h" // Pairing rendezvous bytes from IBEX rodata. The 0x91A2A793 key that precedes the "ibex" string is a // DIFFERENT key, NOT the discovery address (confirmed via PTR_s_ibexesb_common=0x56f99 -> "ibex..."). const uint8_t PAIR_BASE[4] = {0x69, 0x62, 0x65, 0x78}; // "ibex" uint8_t g_rfPrefix = 0x10; uint8_t g_rfCh = 2; uint8_t g_rfBase[4] = {0x69, 0x62, 0x65, 0x78}; // "ibex" // ch18 measured clean (RSSI 95/90, PER 0) on the REAL PUCK's own channel scan and is in its active hop set // {18,2,80}; trackpad data bursts collide on bad channels -> reply-rate crash. Tunable 'C'. uint8_t g_sessCh = 18; // pre-init to "ibex"/0x10 as a safe default; rfGenSessionAddr() overwrites with the per-device value at boot. uint8_t g_sessBase[4] = {0x69, 0x62, 0x65, 0x78}; uint8_t g_sessPrefix = 0x10; void rfGenSessionAddr(){ // Mix the 64-bit FICR DEVICEID into a 4-byte base + a prefix. Deterministic per chip (stable across reboots, // debuggable) and unique across chips (DEVICEID is unique) -> two OpenPucks never collide on the connected // session. Avoid degenerate address bytes (0x00/0xFF) for clean preamble correlation, and never reproduce // the shared discovery base "ibex" (else the isolation is lost). uint32_t h = NRF_FICR->DEVICEID[0] * 0x9E3779B1u ^ NRF_FICR->DEVICEID[1]; uint32_t h2 = NRF_FICR->DEVICEID[1] * 0x85EBCA6Bu ^ NRF_FICR->DEVICEID[0]; for(int i=0;i<4;i++){ uint8_t b=(uint8_t)(h>>(i*8)); if(b==0x00||b==0xFF) b^=0x5A; g_sessBase[i]=b; } uint8_t p=(uint8_t)(h2>>16); if(p==0x00||p==0xFF) p=0x5C; g_sessPrefix=p; if(g_sessBase[0]==g_rfBase[0] && g_sessBase[1]==g_rfBase[1] && g_sessBase[2]==g_rfBase[2] && g_sessBase[3]==g_rfBase[3]) g_sessBase[0]^=0x80; // collided with "ibex" } uint8_t rfrx[100], rftx[100]; uint32_t g_rfRxCount = 0; // HYBRID puck-link config (firmware-derived, definitive). DECODED from real-puck capture: puck TX = Ble_2Mbit, // ENDIAN=Big (MSB-first), NO whitening, addr "ibex", CRC16 0x11021/0xFFFF; frame = FUN_00027e9a (static length). uint32_t g_crcinit = 0xFFFF; uint8_t g_whiteiv = 37; uint8_t g_mode = RADIO_MODE_MODE_Ble_2Mbit; // <-- was the bug (1Mbit). Real puck = 2Mbit. uint32_t g_pcnf0 = 0x00030008UL; // S0LEN0, LFLEN8, S1LEN3 (ESB DPL) - CRC-VALIDATED uint8_t g_statlen = 0x20; uint32_t g_pcnf1 = 0x01040060; // ENDIAN=Big, BALEN4, MAXLEN=96 (was 64; longer F1 frames e.g. 0x43-augmented) uint32_t g_crcpoly = 0x11021UL; uint16_t g_crccnf = 0x2; // CRC16, address included uint8_t g_pid = 0; uint8_t g_balen = 4; // ESB 5-byte addr bool g_prefixRaw = false; uint8_t rfBitrev8(uint8_t x){ uint8_t y=0; for(int i=0;i<8;i++){y=(y<<1)|(x&1);x>>=1;} return y; } void rfSetAddr(const uint8_t b4[4], uint8_t prefix){ uint8_t b[4]; for(int i=0;i<4;i++) b[i]=rfBitrev8(b4[i]); NRF_RADIO->BASE0 = ((uint32_t)b[0]<<24)|((uint32_t)b[1]<<16)|((uint32_t)b[2]<<8)|b[3]; NRF_RADIO->PREFIX0 = g_prefixRaw ? prefix : rfBitrev8(prefix); NRF_RADIO->TXADDRESS = 0; NRF_RADIO->RXADDRESSES = 1u<<0; } // Listen on ONE base across ALL 8 ESB logical addresses (pipes): the real prefix on pipe 0, plus a spread of // neighbour prefixes on pipes 1..7 (BASE1 mirrors BASE0). Catches a controller whose reply stream rides the // same base on a DIFFERENT prefix -- a real possibility per the RE notes' "separate stream" model -- without // us having to know that prefix in advance. RXMATCH (reported per packet) then tells us which pipe it used. // Extra pipes only ADD matches; they never drop the pipe-0 (puck) frames. base/prefix are stored (pre-transform) // bytes; the on-air values get the same bitrev8 transform as rfSetAddr (g_prefixRaw=false). void rfSetAddrMulti(const uint8_t b4[4], uint8_t prefix){ uint8_t b[4]; for(int i=0;i<4;i++) b[i]=rfBitrev8(b4[i]); uint32_t base = ((uint32_t)b[0]<<24)|((uint32_t)b[1]<<16)|((uint32_t)b[2]<<8)|b[3]; NRF_RADIO->BASE0 = base; NRF_RADIO->BASE1 = base; // pipe 0 uses BASE0; pipes 1..7 use BASE1 // 8 candidate prefixes: the learned one first, then a spread. De-dup isn't needed (a repeat just matches twice). uint8_t cand[8] = { prefix, (uint8_t)(prefix^0x01), (uint8_t)(prefix+1), (uint8_t)(prefix-1), 0x10, (uint8_t)(prefix^0x80), (uint8_t)(prefix+0x10), (uint8_t)(prefix-0x10) }; uint8_t ap[8]; for(int i=0;i<8;i++) ap[i] = g_prefixRaw ? cand[i] : rfBitrev8(cand[i]); NRF_RADIO->PREFIX0 = (uint32_t)ap[0] | ((uint32_t)ap[1]<<8) | ((uint32_t)ap[2]<<16) | ((uint32_t)ap[3]<<24); NRF_RADIO->PREFIX1 = (uint32_t)ap[4] | ((uint32_t)ap[5]<<8) | ((uint32_t)ap[6]<<16) | ((uint32_t)ap[7]<<24); NRF_RADIO->TXADDRESS = 0; NRF_RADIO->RXADDRESSES = 0xFF; // enable all 8 pipes } void rfConfig(uint8_t ch){ NRF_RADIO->TASKS_DISABLE = 1; RWAIT_DISABLED(); NRF_RADIO->EVENTS_DISABLED = 0; NRF_RADIO->MODE = ((uint32_t)g_mode << RADIO_MODE_MODE_Pos); // Ble_2Mbit (tunable 'M') NRF_RADIO->FREQUENCY = ch; NRF_RADIO->TXPOWER = (RADIO_TXPOWER_TXPOWER_0dBm << RADIO_TXPOWER_TXPOWER_Pos); #if defined(RADIO_MODECNF0_RU_Fast) NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast<PCNF0 = g_pcnf0; // static length (tunable '0'); g_pcnf1=0 -> build from g_statlen/g_balen NRF_RADIO->PCNF1 = g_pcnf1 ? g_pcnf1 : ((1u<CRCCNF = g_crccnf; // CRC16, address included NRF_RADIO->CRCPOLY = g_crcpoly; NRF_RADIO->CRCINIT = g_crcinit; NRF_RADIO->DATAWHITEIV = g_whiteiv; NRF_RADIO->PACKETPTR = (uint32_t)rfrx; }