mirror of
https://github.com/pascallanger/DIY-Multiprotocol-TX-Module.git
synced 2026-10-30 21:44:24 +00:00
Compute xn297 whitening instead of using an hardcoded array (#1201)
* Compute xn297 whitening instead of using an hardcoded array * Add function to scramble byte for XN297 interface * Refactor XN297 scrambling logic to use a global LFSR and improve byte generation * Add technical specification and implementation details for XN297 whitening and CRC
This commit is contained in:
@@ -70,12 +70,14 @@ static void __attribute__((unused)) XN297Dump_RF_init()
|
||||
}
|
||||
}
|
||||
|
||||
extern const uint8_t xn297_scramble[];
|
||||
|
||||
static boolean __attribute__((unused)) XN297Dump_process_packet(void)
|
||||
{
|
||||
uint16_t crcxored;
|
||||
uint8_t packet_sc[XN297DUMP_MAX_PACKET_LEN], packet_un[XN297DUMP_MAX_PACKET_LEN];
|
||||
uint8_t xn297_scramble[XN297DUMP_MAX_PACKET_LEN];
|
||||
xn297_lfsr = XN297_SCRAMBLE_SEED;
|
||||
for (uint8_t i = 0; i < XN297DUMP_MAX_PACKET_LEN; i++)
|
||||
xn297_scramble[i] = XN297_scramble_byte();
|
||||
enhanced=false;
|
||||
// init crc
|
||||
crc = 0xb5d2;
|
||||
|
||||
+81
-106
@@ -6,22 +6,49 @@ bool xn297_scramble_enabled, xn297_crc, xn297_bitrate, xn297_rf;
|
||||
uint8_t xn297_addr_len, xn297_rx_packet_len;
|
||||
uint8_t xn297_tx_addr[5], xn297_rx_addr[5];
|
||||
|
||||
// xn297 address / pcf / payload scramble table
|
||||
const uint8_t xn297_scramble[] = {
|
||||
0xE3, 0xB1, 0x4B, 0xEA, 0x85, 0xBC, 0xE5, 0x66,
|
||||
0x0D, 0xAE, 0x8C, 0x88, 0x12, 0x69, 0xEE, 0x1F,
|
||||
0xC7, 0x62, 0x97, 0xD5, 0x0B, 0x79, 0xCA, 0xCC,
|
||||
0x1B, 0x5D, 0x19, 0x10, 0x24, 0xD3, 0xDC, 0x3F,
|
||||
0x8E, 0xC5, 0x2F, 0xAA, 0x16, 0xF3, 0x95 };
|
||||
// XN297 whitening: x^7 + x^3 + 1, seed 0x71, MSB first (E3 B1 4B EA 85 ...).
|
||||
// Generated a byte at a time with (x^7 + x^3 + 1)^4 = x^28 + x^12 + 1: s[i] = s[i-28] ^ s[i-12].
|
||||
// xn297_lfsr holds the next 28 bits in bits 31..4, 0 when scrambling is off.
|
||||
uint32_t xn297_lfsr;
|
||||
|
||||
static uint8_t __attribute__((unused)) XN297_scramble_byte()
|
||||
{
|
||||
uint8_t s = xn297_lfsr >> 24;
|
||||
xn297_lfsr = (xn297_lfsr << 8) | (uint8_t)(s ^ (xn297_lfsr >> 8)) << 4;
|
||||
return s;
|
||||
}
|
||||
|
||||
static void __attribute__((unused)) XN297_scramble_init()
|
||||
{
|
||||
xn297_lfsr = xn297_scramble_enabled ? XN297_SCRAMBLE_SEED : 0;
|
||||
}
|
||||
|
||||
// XN297 CRC: CRC-16/CCITT (init 0xFFFF, xorout 0xFFFF) over the unscrambled
|
||||
// address, pcf and payload, sent scrambled. The emulation computes it from 0xB5D2
|
||||
// over the bits as sent: the xorout is the difference for a CRC over 'bits' bits,
|
||||
// the CRC of the scramble bits plus the scramble of the CRC bits.
|
||||
// address, pcf and payload, sent scrambled: XORed with the scramble bits that follow,
|
||||
// (xn297_lfsr >> 16) once the last byte has been scrambled.
|
||||
// Returns the scrambled byte, crc updated with the unscrambled one.
|
||||
static uint8_t __attribute__((unused)) XN297_crc_scramble(uint8_t b)
|
||||
{
|
||||
crc16_update(b, 8);
|
||||
return b ^ XN297_scramble_byte();
|
||||
}
|
||||
|
||||
// Returns the unscrambled byte, crc updated with it.
|
||||
static uint8_t __attribute__((unused)) XN297_unscramble_crc(uint8_t b)
|
||||
{
|
||||
b ^= XN297_scramble_byte();
|
||||
crc16_update(b, 8);
|
||||
return b;
|
||||
}
|
||||
|
||||
#ifdef XN297DUMP_NRF24L01_INO
|
||||
// XN297Dump computes the CRC from 0xB5D2 over the bits as sent: the xorout is the
|
||||
// difference for a CRC over 'bits' bits, the CRC of the scramble bits plus the
|
||||
// scramble of the CRC bits.
|
||||
static uint16_t __attribute__((unused)) XN297_crc_xorout(bool scramble, uint16_t bits)
|
||||
{
|
||||
uint16_t xorout = 0xFFFF ^ 0xB5D2;
|
||||
uint8_t lfsr = 0x71, s; // scramble bits (xn297_scramble[]): 1110001, then s[i] = s[i-7] ^ s[i-3]
|
||||
uint8_t lfsr = 0x71, s; // scramble bits: 1110001, then s[i] = s[i-7] ^ s[i-3]
|
||||
for(uint16_t i = 0; i < bits + 16; i++)
|
||||
{
|
||||
s = scramble ? lfsr >> 6 : 0;
|
||||
@@ -38,6 +65,7 @@ static uint16_t __attribute__((unused)) XN297_crc_xorout(bool scramble, uint16_t
|
||||
}
|
||||
return ~xorout;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool __attribute__((unused)) XN297_Configure(bool crc_en, bool scramble_en, bool bitrate, bool force_nrf)
|
||||
{
|
||||
@@ -103,13 +131,10 @@ static void __attribute__((unused)) XN297_SetTXAddr(const uint8_t* addr, uint8_t
|
||||
|
||||
static void __attribute__((unused)) XN297_SetRXAddr(const uint8_t* addr, uint8_t rx_packet_len)
|
||||
{
|
||||
//Scramble address
|
||||
for (uint8_t i = 0; i < xn297_addr_len; ++i)
|
||||
{
|
||||
xn297_rx_addr[i] = addr[i];
|
||||
if(xn297_scramble_enabled)
|
||||
xn297_rx_addr[i] ^= xn297_scramble[xn297_addr_len-i-1];
|
||||
}
|
||||
//Scramble address in transmission order
|
||||
XN297_scramble_init();
|
||||
for (uint8_t i = xn297_addr_len; i--; )
|
||||
xn297_rx_addr[i] = addr[i] ^ XN297_scramble_byte();
|
||||
|
||||
if(xn297_crc)
|
||||
rx_packet_len += 2; // Include CRC
|
||||
@@ -257,32 +282,19 @@ static void __attribute__((unused)) XN297_WritePayload(uint8_t* msg, uint8_t len
|
||||
}
|
||||
|
||||
// address
|
||||
crc = 0xFFFF;
|
||||
XN297_scramble_init();
|
||||
for (uint8_t i = 0; i < xn297_addr_len; ++i)
|
||||
{
|
||||
buf[last] = xn297_tx_addr[xn297_addr_len-i-1];
|
||||
if(xn297_scramble_enabled)
|
||||
buf[last] ^= xn297_scramble[i];
|
||||
last++;
|
||||
}
|
||||
buf[last++] = XN297_crc_scramble(xn297_tx_addr[xn297_addr_len-i-1]);
|
||||
|
||||
// payload
|
||||
for (uint8_t i = 0; i < len; ++i)
|
||||
{
|
||||
// bit-reverse bytes in packet
|
||||
buf[last] = bit_reverse(msg[i]);
|
||||
if(xn297_scramble_enabled)
|
||||
buf[last] ^= xn297_scramble[xn297_addr_len+i];
|
||||
last++;
|
||||
}
|
||||
buf[last++] = XN297_crc_scramble(bit_reverse(msg[i])); // bit-reverse bytes in packet
|
||||
|
||||
// crc
|
||||
if (xn297_crc)
|
||||
{
|
||||
uint8_t offset = (xn297_addr_len < 4 && xn297_rf == XN297_NRF) ? 1 : 0;
|
||||
crc = 0xb5d2;
|
||||
for (uint8_t i = offset; i < last; ++i)
|
||||
crc16_update( buf[i], 8);
|
||||
crc ^= XN297_crc_xorout(xn297_scramble_enabled, 8 * (xn297_addr_len + len));
|
||||
crc = ~crc ^ (xn297_lfsr >> 16);
|
||||
buf[last++] = crc >> 8;
|
||||
buf[last++] = crc & 0xff;
|
||||
}
|
||||
@@ -294,7 +306,6 @@ static void __attribute__((unused)) XN297_WritePayload(uint8_t* msg, uint8_t len
|
||||
static void __attribute__((unused)) XN297_WriteEnhancedPayload(uint8_t* msg, uint8_t len, uint8_t noack)
|
||||
{
|
||||
uint8_t buf[32];
|
||||
uint8_t scramble_index=0;
|
||||
uint8_t last = 0;
|
||||
static uint8_t pid=0;
|
||||
|
||||
@@ -304,54 +315,37 @@ static void __attribute__((unused)) XN297_WriteEnhancedPayload(uint8_t* msg, uin
|
||||
}
|
||||
|
||||
// address
|
||||
crc = xn297_scramble_enabled ? 0xFFFF : 0xB5D2; // unscrambled: no xorout
|
||||
XN297_scramble_init();
|
||||
for (uint8_t i = 0; i < xn297_addr_len; ++i)
|
||||
{
|
||||
buf[last] = xn297_tx_addr[xn297_addr_len-i-1];
|
||||
if(xn297_scramble_enabled)
|
||||
buf[last] ^= xn297_scramble[scramble_index++];
|
||||
last++;
|
||||
}
|
||||
buf[last++] = XN297_crc_scramble(xn297_tx_addr[xn297_addr_len-i-1]);
|
||||
|
||||
// pcf
|
||||
buf[last] = (len << 1) | (pid>>1);
|
||||
if(xn297_scramble_enabled)
|
||||
buf[last] ^= xn297_scramble[scramble_index++];
|
||||
last++;
|
||||
buf[last++] = XN297_crc_scramble((len << 1) | (pid>>1));
|
||||
buf[last] = (pid << 7) | (noack << 6);
|
||||
|
||||
// payload
|
||||
if(len)
|
||||
{
|
||||
buf[last]|= bit_reverse(msg[0]) >> 2; // first 6 bit of payload
|
||||
if(xn297_scramble_enabled)
|
||||
buf[last] ^= xn297_scramble[scramble_index++];
|
||||
buf[last] = XN297_crc_scramble(buf[last] | (bit_reverse(msg[0]) >> 2)); // first 6 bit of payload
|
||||
|
||||
for (uint8_t i = 0; i < len-1; ++i)
|
||||
{
|
||||
last++;
|
||||
buf[last] = (bit_reverse(msg[i]) << 6) | (bit_reverse(msg[i+1]) >> 2);
|
||||
if(xn297_scramble_enabled)
|
||||
buf[last] ^= xn297_scramble[scramble_index++];
|
||||
buf[last] = XN297_crc_scramble((bit_reverse(msg[i]) << 6) | (bit_reverse(msg[i+1]) >> 2));
|
||||
}
|
||||
|
||||
last++;
|
||||
buf[last] = bit_reverse(msg[len-1]) << 6; // last 2 bit of payload
|
||||
}
|
||||
if(xn297_scramble_enabled)
|
||||
buf[last] ^= xn297_scramble[scramble_index++] & 0xc0;
|
||||
crc16_update( buf[last], 2);
|
||||
buf[last] ^= (xn297_lfsr >> 24) & 0xc0;
|
||||
|
||||
// crc
|
||||
if (xn297_crc)
|
||||
{
|
||||
uint8_t offset = (xn297_addr_len < 4 && xn297_rf == XN297_NRF) ? 1 : 0;
|
||||
crc = 0xb5d2;
|
||||
for (uint8_t i = offset; i < last; ++i)
|
||||
crc16_update( buf[i], 8);
|
||||
crc16_update( buf[last] & 0xc0, 2);
|
||||
if (xn297_scramble_enabled)
|
||||
crc ^= XN297_crc_xorout(true, 8 * (xn297_addr_len + len) + 10);
|
||||
//else
|
||||
// crc ^= XN297_crc_xorout(false, 8 * (xn297_addr_len + len) + 10);
|
||||
crc = ~crc ^ (xn297_lfsr >> 14);
|
||||
|
||||
buf[last++] |= (crc >> 8) >> 2;
|
||||
buf[last++] = ((crc >> 8) << 6) | ((crc & 0xff) >> 2);
|
||||
@@ -408,28 +402,18 @@ static bool __attribute__((unused)) XN297_ReadPayload(uint8_t* msg, uint8_t len)
|
||||
// Read payload
|
||||
XN297_ReceivePayload(buf, len);
|
||||
|
||||
// Decode payload
|
||||
// Decode payload, calculate CRC
|
||||
crc = 0xFFFF;
|
||||
XN297_scramble_init();
|
||||
for (uint8_t i = 0; i < xn297_addr_len; ++i)
|
||||
XN297_unscramble_crc(xn297_rx_addr[xn297_addr_len-i-1]);
|
||||
for(uint8_t i=0; i<len; i++)
|
||||
{
|
||||
uint8_t b_in=buf[i];
|
||||
if(xn297_scramble_enabled)
|
||||
b_in ^= xn297_scramble[i+xn297_addr_len];
|
||||
msg[i] = bit_reverse(b_in);
|
||||
}
|
||||
msg[i] = bit_reverse(XN297_unscramble_crc(buf[i]));
|
||||
|
||||
if (!xn297_crc)
|
||||
return true; // No CRC so OK by default...
|
||||
|
||||
// Calculate CRC
|
||||
crc = 0xb5d2;
|
||||
//process address
|
||||
for (uint8_t i = 0; i < xn297_addr_len; ++i)
|
||||
crc16_update( xn297_rx_addr[xn297_addr_len-i-1], 8);
|
||||
//process payload
|
||||
for (uint8_t i = 0; i < len; ++i)
|
||||
crc16_update( buf[i], 8);
|
||||
//xorout
|
||||
crc ^= XN297_crc_xorout(xn297_scramble_enabled, 8 * (xn297_addr_len + len));
|
||||
crc = ~crc ^ (xn297_lfsr >> 16);
|
||||
//test
|
||||
if( (crc >> 8) == buf[len] && (crc & 0xff) == buf[len+1])
|
||||
return true; // CRC OK
|
||||
@@ -443,40 +427,31 @@ static uint8_t __attribute__((unused)) XN297_ReadEnhancedPayload(uint8_t* msg, u
|
||||
|
||||
// Read payload
|
||||
XN297_ReceivePayload(buffer, len+2); // Read pcf + payload + CRC
|
||||
// Decode payload
|
||||
pcf_size = buffer[0];
|
||||
if(xn297_scramble_enabled)
|
||||
pcf_size ^= xn297_scramble[xn297_addr_len];
|
||||
pcf_size = pcf_size >> 1;
|
||||
// Decode payload, calculate CRC
|
||||
#ifdef XN297DUMP_NRF24L01_INO
|
||||
bool xorout = true;
|
||||
#else
|
||||
bool xorout = xn297_scramble_enabled; // unscrambled: no xorout
|
||||
#endif
|
||||
crc = xorout ? 0xFFFF : 0xB5D2;
|
||||
XN297_scramble_init();
|
||||
for (uint8_t i = 0; i < xn297_addr_len; ++i)
|
||||
XN297_unscramble_crc(xn297_rx_addr[xn297_addr_len-i-1]);
|
||||
pcf_size = XN297_unscramble_crc(buffer[0]) >> 1;
|
||||
if(pcf_size>32)
|
||||
return 255; // Error
|
||||
for(uint8_t i=0; i< pcf_size; i++)
|
||||
{
|
||||
msg[i] = bit_reverse((buffer[i+1] << 2) | (buffer[i+2] >> 6));
|
||||
if(xn297_scramble_enabled)
|
||||
msg[i] ^= bit_reverse((xn297_scramble[xn297_addr_len+i+1] << 2) |
|
||||
(xn297_scramble[xn297_addr_len+i+2] >> 6));
|
||||
{ // payload is shifted by 2 bits: unscramble with bits 29..22 of the next scramble bits
|
||||
msg[i] = bit_reverse(((buffer[i+1] << 2) | (buffer[i+2] >> 6)) ^ (xn297_lfsr >> 22));
|
||||
XN297_unscramble_crc(buffer[i+1]);
|
||||
}
|
||||
crc16_update( (buffer[pcf_size+1] ^ (xn297_lfsr >> 24)) & 0xc0, 2);
|
||||
|
||||
if (!xn297_crc)
|
||||
return pcf_size; // No CRC so OK by default...
|
||||
|
||||
// Calculate CRC
|
||||
crc = 0xb5d2;
|
||||
//process address
|
||||
for (uint8_t i = 0; i < xn297_addr_len; ++i)
|
||||
crc16_update( xn297_rx_addr[xn297_addr_len-i-1], 8);
|
||||
//process payload
|
||||
for (uint8_t i = 0; i < pcf_size+1; ++i)
|
||||
crc16_update( buffer[i], 8);
|
||||
crc16_update( buffer[pcf_size+1] & 0xc0, 2);
|
||||
//xorout
|
||||
if (xn297_scramble_enabled)
|
||||
crc ^= XN297_crc_xorout(true, 8 * (xn297_addr_len + pcf_size) + 10);
|
||||
#ifdef XN297DUMP_NRF24L01_INO
|
||||
else
|
||||
crc ^= XN297_crc_xorout(false, 8 * (xn297_addr_len + pcf_size) + 10);
|
||||
#endif
|
||||
if (xorout)
|
||||
crc = ~crc ^ (xn297_lfsr >> 14);
|
||||
uint16_t crcxored=(buffer[pcf_size+1]<<10)|(buffer[pcf_size+2]<<2)|(buffer[pcf_size+3]>>6) ;
|
||||
if( crc == crcxored)
|
||||
return pcf_size; // CRC OK
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
#define XN297_250K true
|
||||
#define XN297_NRF false
|
||||
#define XN297_CC2500 true
|
||||
#define XN297_SCRAMBLE_SEED 0xE3B14BE0 // whitening x^7 + x^3 + 1, seed 0x71: first 28 bits
|
||||
|
||||
extern uint32_t xn297_lfsr;
|
||||
|
||||
//////////////
|
||||
// Functions
|
||||
@@ -41,7 +44,10 @@ static void __attribute__((unused)) XN297_RFChannel(uint8_t);
|
||||
static void __attribute__((unused)) XN297_SetPower();
|
||||
static void __attribute__((unused)) XN297_SetFreqOffset();
|
||||
static bool __attribute__((unused)) XN297_IsPacketSent();
|
||||
static uint8_t __attribute__((unused)) XN297_scramble_byte();
|
||||
#ifdef XN297DUMP_NRF24L01_INO
|
||||
static uint16_t __attribute__((unused)) XN297_crc_xorout(bool, uint16_t);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,496 @@
|
||||
# XN297 Whitening and CRC: Technical Specification and Implementation
|
||||
|
||||
**Scope:** XN297 packet whitening and 16-bit CRC as implemented and reconstructed in the DIY Multiprotocol TX Module's XN297 emulation, including standard and enhanced packet formats, historical CRC correction tables, and reference test vectors.
|
||||
|
||||
## 1. Conventions
|
||||
|
||||
- Bits within each transmitted byte are processed **most significant bit first**.
|
||||
- `^` or `⊕` denotes bitwise XOR; `~` denotes bitwise complement.
|
||||
- `R8(x)` denotes reversal of the eight bits in byte `x`.
|
||||
- The address length `A` is 3, 4, or 5 bytes; payload length `P` is in bytes.
|
||||
- `M` denotes the **unwhitened sequence of bits in transmission order** that is covered by the CRC.
|
||||
- `S[i]` denotes bit `i` of the whitening sequence, starting at the first transmitted address bit (`i = 0`).
|
||||
- `S[a:b]` denotes the bits at positions `a` through `b-1`, interpreted MSB first when converted to an integer.
|
||||
- `CRC(I, M)` denotes the uncomplemented CRC-16 register after processing bitstream `M` from initial register value `I`.
|
||||
|
||||
The synchronization/preamble sequence precedes the address. It is separate from `M` and from the whitening sequence indexed here. For the XN297 emulation, the 28-bit synchronization pattern is `0xC710F55`; its representation in the underlying radio interface depends on the transceiver used.
|
||||
|
||||
## 2. Packet-field ordering
|
||||
|
||||
### 2.1 Standard packets
|
||||
|
||||
The CRC-covered, unwhitened bitstream is:
|
||||
|
||||
```text
|
||||
address bytes in reverse API order | payload bytes with bits reversed
|
||||
```
|
||||
|
||||
For API address `01 02 03 04 05` and payload `01 02 03 04 05`, the CRC input bytes are:
|
||||
|
||||
```text
|
||||
05 04 03 02 01 80 40 C0 20 A0
|
||||
```
|
||||
|
||||
The address bytes are reversed in **byte order**. The payload bytes are individually reversed in **bit order**. The address bytes are not individually bit-reversed in this representation.
|
||||
|
||||
With whitening enabled, the address and payload bits are XORed with consecutive whitening bits. The CRC is then XORed with the **next 16 whitening bits**. Whitening is continuous across the address, packet-control field when present, payload, and CRC; it is not restarted at field boundaries.
|
||||
|
||||
The standard CRC input length is:
|
||||
|
||||
```text
|
||||
n = 8 * (A + P)
|
||||
```
|
||||
|
||||
### 2.2 Enhanced packets
|
||||
|
||||
Enhanced packets include a packet-control region between the address and payload. The implementation serializes this region as 10 bits:
|
||||
|
||||
```text
|
||||
0 | payload_length[5:0] | PID[1:0] | NO_ACK
|
||||
```
|
||||
|
||||
The payload then follows as a contiguous stream of bit-reversed payload bytes, without byte alignment after the 10-bit packet-control region.
|
||||
|
||||
The enhanced CRC input length is:
|
||||
|
||||
```text
|
||||
n = 8 * (A + P) + 10
|
||||
```
|
||||
|
||||
Consequently, the CRC begins **two bits into a byte**. Its 16 bits span parts of three transmitted bytes. The final packed byte has six unused low-order bits, set to zero by the implementation.
|
||||
|
||||
## 3. Whitening generator
|
||||
|
||||
### 3.1 Parameters
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| Generator | 7-bit linear-feedback shift register (LFSR) |
|
||||
| Connection polynomial | `x^7 + x^3 + 1` |
|
||||
| Initial register value | `0x71` |
|
||||
| Output bit | Register bit 6 (MSB) |
|
||||
| Feedback bit | Register bit 6 XOR register bit 2 |
|
||||
| Register shift | Left by one; feedback enters bit 0 |
|
||||
| Output-byte assembly | MSB first, eight consecutive output bits |
|
||||
|
||||
For register state `r`, one whitening bit and the next state are:
|
||||
|
||||
```text
|
||||
output = (r >> 6) & 1
|
||||
r_next = ((r << 1) | (((r >> 6) ^ (r >> 2)) & 1)) & 0x7F
|
||||
```
|
||||
|
||||
The initial seven output bits are `1110001`, corresponding to the seed `0x71`.
|
||||
|
||||
### 3.2 Bit-level reference implementation
|
||||
|
||||
```c
|
||||
#include <stdint.h>
|
||||
|
||||
static uint8_t xn297_whitening_bit(uint8_t *lfsr)
|
||||
{
|
||||
uint8_t output = (*lfsr >> 6) & 1u;
|
||||
uint8_t feedback = ((*lfsr >> 6) ^ (*lfsr >> 2)) & 1u;
|
||||
*lfsr = (uint8_t)(((*lfsr << 1) | feedback) & 0x7Fu);
|
||||
return output;
|
||||
}
|
||||
|
||||
static uint8_t xn297_whitening_byte(uint8_t *lfsr)
|
||||
{
|
||||
uint8_t value = 0;
|
||||
for (unsigned i = 0; i < 8; ++i)
|
||||
value = (uint8_t)((value << 1) | xn297_whitening_bit(lfsr));
|
||||
return value;
|
||||
}
|
||||
```
|
||||
|
||||
Initialize `uint8_t lfsr = 0x71;` at the start of the packet's address field.
|
||||
|
||||
### 3.3 Whitening reference bytes
|
||||
|
||||
The first 39 output bytes are:
|
||||
|
||||
```text
|
||||
E3 B1 4B EA 85 BC E5 66
|
||||
0D AE 8C 88 12 69 EE 1F
|
||||
C7 62 97 D5 0B 79 CA CC
|
||||
1B 5D 19 10 24 D3 DC 3F
|
||||
8E C5 2F AA 16 F3 95
|
||||
```
|
||||
|
||||
These reproduce the historical `xn297_scramble[]` lookup table. The LFSR generates additional whitening bits beyond the end of that table, including the bits needed for enhanced-packet CRCs.
|
||||
|
||||
### 3.4 Eight-bit-at-a-time generator
|
||||
|
||||
Let `s[i]` denote the individual whitening bits. The seven-bit recurrence is:
|
||||
|
||||
```text
|
||||
s[i] = s[i-7] ^ s[i-3] for i >= 7
|
||||
```
|
||||
|
||||
Over GF(2), raising the connection polynomial to the fourth power gives:
|
||||
|
||||
```text
|
||||
(x^7 + x^3 + 1)^4 = x^28 + x^12 + 1
|
||||
```
|
||||
|
||||
Therefore the same sequence also satisfies:
|
||||
|
||||
```text
|
||||
s[i] = s[i-28] ^ s[i-12] for i >= 28
|
||||
```
|
||||
|
||||
This permits an eight-bit-at-a-time generator using a 32-bit word containing the **next 28 whitening bits in bits 31..4**. Its initial value is the first 28 stream bits followed by four zero bits:
|
||||
|
||||
```text
|
||||
0xE3B14BE0
|
||||
```
|
||||
|
||||
```c
|
||||
#include <stdint.h>
|
||||
|
||||
static uint8_t xn297_next_byte_optimized(uint32_t *state)
|
||||
{
|
||||
uint8_t output = (uint8_t)(*state >> 24);
|
||||
*state = (*state << 8) |
|
||||
((uint32_t)(uint8_t)(output ^ (*state >> 8)) << 4);
|
||||
return output;
|
||||
}
|
||||
```
|
||||
|
||||
Initialize `uint32_t state = 0xE3B14BE0u;` when whitening is enabled, or `state = 0;` when whitening is disabled. The zero state makes the same XOR-based packing operations emit unmodified data.
|
||||
|
||||
At an aligned byte boundary, after all CRC-input bytes have been processed, the next 16 whitening bits are:
|
||||
|
||||
```c
|
||||
uint16_t crc_whitening = (uint16_t)(state >> 16);
|
||||
```
|
||||
|
||||
For an enhanced packet with two additional CRC-input bits after the last full byte, the CRC whitening mask is:
|
||||
|
||||
```c
|
||||
uint16_t crc_whitening = (uint16_t)(state >> 14);
|
||||
```
|
||||
|
||||
The second expression skips the two whitening bits consumed by the final partial data byte.
|
||||
|
||||
## 4. CRC algorithm
|
||||
|
||||
### 4.1 CRC parameters
|
||||
|
||||
The packet-level CRC model for standard packets and scrambled enhanced packets is:
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| Width | 16 bits |
|
||||
| Polynomial | `0x1021` (`x^16 + x^12 + x^5 + 1`) |
|
||||
| Initial register | `0xFFFF` |
|
||||
| Input bit order | MSB first |
|
||||
| Reflected input | No |
|
||||
| Reflected output | No |
|
||||
| Final XOR | `0xFFFF` |
|
||||
| Transmitted CRC bit order | MSB first |
|
||||
| CRC whitening | XOR with the next 16 whitening bits when enabled |
|
||||
|
||||
These parameters correspond to the CRC-16/GENIBUS parameterization of the CCITT polynomial. They are distinct from CRC-16/CCITT-FALSE, which uses a final XOR of zero.
|
||||
|
||||
For one input bit `b` and current register `c`:
|
||||
|
||||
```text
|
||||
feedback = ((c >> 15) ^ b) & 1
|
||||
c = (c << 1) & 0xFFFF
|
||||
if feedback: c ^= 0x1021
|
||||
```
|
||||
|
||||
A reference function is:
|
||||
|
||||
```c
|
||||
static uint16_t crc1021_bit(uint16_t crc, uint8_t bit)
|
||||
{
|
||||
uint8_t feedback = (uint8_t)(((crc >> 15) ^ bit) & 1u);
|
||||
crc = (uint16_t)(crc << 1);
|
||||
if (feedback)
|
||||
crc ^= 0x1021u;
|
||||
return crc;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 CRC over unwhitened data
|
||||
|
||||
For a CRC input bitstream `M` of length `n`, define:
|
||||
|
||||
```text
|
||||
C_raw = CRC(0xFFFF, M)
|
||||
C_final = C_raw ^ 0xFFFF
|
||||
```
|
||||
|
||||
With whitening enabled, the transmitted CRC is:
|
||||
|
||||
```text
|
||||
C_tx = C_final ^ S[n:n+16]
|
||||
```
|
||||
|
||||
With whitening disabled, the standard-packet transmitted CRC is simply `C_final`.
|
||||
|
||||
The whitening applied to the CRC uses whitening bits at **bit offsets `n` through `n+15`**, not the first 16 bits of the whitening sequence. In standard packets those bits are byte-aligned; in enhanced packets they begin two bits into a byte.
|
||||
|
||||
### 4.3 Processing order
|
||||
|
||||
For standard packets, the encoder performs the following operations in order:
|
||||
|
||||
1. Initialize the CRC register to `0xFFFF` and the whitening generator at the beginning of the address.
|
||||
2. Feed each unwhitened address byte into the CRC in transmitted byte order; XOR the same byte with its whitening byte for transmission.
|
||||
3. Feed each bit-reversed payload byte into the CRC; XOR it with the next whitening byte for transmission.
|
||||
4. Complement the CRC register and XOR it with the next 16 whitening bits.
|
||||
5. Append the resulting CRC most-significant byte first.
|
||||
|
||||
The corresponding decoder XORs each received data byte with the same whitening stream **before** feeding it into the CRC. It then compares the received CRC with the complemented calculated CRC XORed with the 16 CRC whitening bits.
|
||||
|
||||
For enhanced packets, the same process applies to the extra packet-control bits and the non-byte-aligned end of the payload.
|
||||
|
||||
## 5. Historical `0xB5D2` CRC model and XOROUT correction
|
||||
|
||||
Earlier XN297 emulation computed the CRC directly over the **already whitened, on-air data bits**, using initial register `0xB5D2`. It then XORed the CRC result with a correction value that depended on the packet length and mode.
|
||||
|
||||
Let:
|
||||
|
||||
```text
|
||||
I_old = 0xB5D2
|
||||
I_new = 0xFFFF
|
||||
D = I_old ^ I_new = 0x4A2D
|
||||
```
|
||||
|
||||
With whitening enabled, the on-air data bitstream is `M ^ S[0:n]`. The old intermediate value is:
|
||||
|
||||
```text
|
||||
C_old = CRC(0xB5D2, M ^ S[0:n])
|
||||
```
|
||||
|
||||
The transmitted CRC defined in section 4 is:
|
||||
|
||||
```text
|
||||
C_tx = CRC(0xFFFF, M) ^ 0xFFFF ^ S[n:n+16]
|
||||
```
|
||||
|
||||
Because an uncomplemented CRC register transition is linear over GF(2), their difference is independent of the contents of `M`:
|
||||
|
||||
```text
|
||||
XOROUT(n, scrambled)
|
||||
= CRC(0x4A2D, S[0:n]) ^ 0xFFFF ^ S[n:n+16]
|
||||
```
|
||||
|
||||
The old calculation is therefore equivalent to:
|
||||
|
||||
```text
|
||||
C_tx = C_old ^ XOROUT(n, scrambled)
|
||||
```
|
||||
|
||||
When whitening is disabled, substitute zero for every whitening bit:
|
||||
|
||||
```text
|
||||
XOROUT(n, unscrambled)
|
||||
= CRC(0x4A2D, n zero bits) ^ 0xFFFF
|
||||
```
|
||||
|
||||
This explains why a correction indexed by packet length can compensate for both the different CRC initialization and the use of whitened rather than unwhitened CRC input.
|
||||
|
||||
### 5.1 Reference correction function
|
||||
|
||||
```c
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static uint16_t xn297_crc_xorout(bool scrambled, size_t nbits)
|
||||
{
|
||||
uint16_t correction = 0xFFFFu ^ 0xB5D2u;
|
||||
uint8_t lfsr = 0x71u;
|
||||
|
||||
for (size_t i = 0; i < nbits; ++i) {
|
||||
uint8_t mask_bit = xn297_whitening_bit(&lfsr);
|
||||
correction = crc1021_bit(correction,
|
||||
scrambled ? mask_bit : 0u);
|
||||
}
|
||||
|
||||
uint16_t crc_mask = 0;
|
||||
for (unsigned i = 0; i < 16; ++i) {
|
||||
uint8_t mask_bit = xn297_whitening_bit(&lfsr);
|
||||
crc_mask = (uint16_t)((crc_mask << 1) |
|
||||
(scrambled ? mask_bit : 0u));
|
||||
}
|
||||
|
||||
return (uint16_t)(correction ^ 0xFFFFu ^ crc_mask);
|
||||
}
|
||||
```
|
||||
|
||||
This function uses `xn297_whitening_bit()` and `crc1021_bit()` from sections 3 and 4.
|
||||
|
||||
### 5.2 Old lookup-table indexing
|
||||
|
||||
Both standard and enhanced correction tables used:
|
||||
|
||||
```text
|
||||
index = A - 3 + P
|
||||
```
|
||||
|
||||
The bit count supplied to the computed replacement is:
|
||||
|
||||
| Packet type | CRC-covered bits |
|
||||
|---|---|
|
||||
| Standard | `8 * (A + P)` |
|
||||
| Enhanced | `8 * (A + P) + 10` |
|
||||
|
||||
The original source contained four correction tables: standard scrambled, standard unscrambled, enhanced scrambled, and enhanced unscrambled. Entry zero in each table was a placeholder.
|
||||
|
||||
The computed formula matches **135 of the 136 non-placeholder historical entries**. The sole discrepancy is the standard scrambled entry for `A = 3`, `P = 1`:
|
||||
|
||||
```text
|
||||
Historical table: 0x3448
|
||||
Computed value: 0xE348
|
||||
```
|
||||
|
||||
The new correction implementation uses `0xE348`. The remaining historical entries follow the same formula.
|
||||
|
||||
### 5.3 Enhanced unscrambled compatibility behavior
|
||||
|
||||
The multiprotocol firmware's enhanced-packet **transmit** path retains a distinct legacy convention when scrambling is disabled:
|
||||
|
||||
```text
|
||||
CRC init = 0xB5D2
|
||||
Final XOR = 0x0000
|
||||
CRC whitening = disabled
|
||||
```
|
||||
|
||||
That path therefore transmits `CRC(0xB5D2, M)` without applying the general `0xFFFF`-initialized, `0xFFFF`-complemented model. This is an implementation-specific distinction and must be preserved when reproducing that firmware behavior.
|
||||
|
||||
The XN297Dump analyzer also contains a computed correction path for enhanced unscrambled packets using the general `XOROUT(n, unscrambled)` formula. The analyzer and transmit-path conventions should not be conflated.
|
||||
|
||||
## 6. Enhanced-packet CRC alignment
|
||||
|
||||
An enhanced packet's unwhitened CRC input consists of:
|
||||
|
||||
```text
|
||||
reversed-order address bytes
|
||||
+ 10-bit packet-control region
|
||||
+ bit-reversed payload bytes
|
||||
```
|
||||
|
||||
With `A` address bytes and `P` payload bytes:
|
||||
|
||||
```text
|
||||
n = 8 * (A + P) + 10
|
||||
n mod 8 = 2
|
||||
```
|
||||
|
||||
Thus the last two data bits occupy the most significant two bits of a byte. The first six bits of the 16-bit CRC occupy the remaining six bits of that byte; the next eight CRC bits occupy another byte; and the last two CRC bits occupy the two most significant bits of a third byte.
|
||||
|
||||
If `C` is the 16-bit transmitted CRC and `tail` is the encoded final two-bit data field already stored in bits 7..6:
|
||||
|
||||
```c
|
||||
out0 = (uint8_t)(tail | (C >> 10));
|
||||
out1 = (uint8_t)(C >> 2);
|
||||
out2 = (uint8_t)(C << 6);
|
||||
```
|
||||
|
||||
The resulting CRC bit layout is:
|
||||
|
||||
```text
|
||||
[data:2][CRC:6] [CRC:8] [CRC:2][zero:6]
|
||||
```
|
||||
|
||||
The CRC-input calculation covers the final **two data bits** but does not feed CRC bits or the six trailing padding bits back into the CRC register.
|
||||
|
||||
## 7. Reference test vectors
|
||||
|
||||
All hexadecimal bytes below use the transmission-order representation defined in section 2, excluding the radio-specific synchronization/preamble sequence.
|
||||
|
||||
### 7.1 Whitening sequence
|
||||
|
||||
```text
|
||||
LFSR seed: 71
|
||||
First eight bytes:E3 B1 4B EA 85 BC E5 66
|
||||
28-bit seed word: E3B14BE0
|
||||
```
|
||||
|
||||
Both whitening implementations in section 3 produce the same stream.
|
||||
|
||||
### 7.2 Standard scrambled packet
|
||||
|
||||
```text
|
||||
Address (API order): 01 02 03 04 05
|
||||
Payload (API order): 01 02 03 04 05
|
||||
CRC input, unwhitened: 05 04 03 02 01 80 40 C0 20 A0
|
||||
Whitening, first 10 bytes: E3 B1 4B EA 85 BC E5 66 0D AE
|
||||
Transmitted data: E6 B5 48 E8 84 3C A5 A6 2D 0E
|
||||
CRC input bit length: 80
|
||||
CRC(FFFF, input): 8B27
|
||||
CRC after final XOR: 74D8
|
||||
CRC whitening mask: 8C88
|
||||
Transmitted CRC: F850
|
||||
```
|
||||
|
||||
Cross-check against the historical CRC procedure:
|
||||
|
||||
```text
|
||||
CRC(B5D2, transmitted data): E0B6
|
||||
XOROUT(80, scrambled): 18E6
|
||||
E0B6 ^ 18E6: F850
|
||||
```
|
||||
|
||||
Complete data-and-CRC sequence:
|
||||
|
||||
```text
|
||||
E6 B5 48 E8 84 3C A5 A6 2D 0E F8 50
|
||||
```
|
||||
|
||||
### 7.3 Enhanced scrambled packet
|
||||
|
||||
```text
|
||||
Address (API order): 01 02 03 04 05
|
||||
Payload (API order): 01 02
|
||||
PID: 0
|
||||
NO_ACK: 0
|
||||
Packet-control bits: 0 | 000010 | 00 | 0
|
||||
CRC input bit length: 66
|
||||
CRC(FFFF, input): 125A
|
||||
CRC after final XOR: EDA5
|
||||
CRC whitening mask: 36BA
|
||||
Transmitted CRC: DB1F
|
||||
XOROUT(66, scrambled): CA52
|
||||
```
|
||||
|
||||
Complete encoded bytes, including the final six zero padding bits:
|
||||
|
||||
```text
|
||||
E6 B5 48 E8 84 B8 C5 76 36 C7 C0
|
||||
```
|
||||
|
||||
The enhanced CRC begins two bits into the ninth byte of this encoded sequence.
|
||||
|
||||
### 7.4 Short-length historical-table correction
|
||||
|
||||
For a three-byte address and one-byte payload in standard scrambled mode:
|
||||
|
||||
```text
|
||||
A = 3
|
||||
P = 1
|
||||
n = 32 bits
|
||||
XOROUT(32, scrambled) = E348
|
||||
```
|
||||
|
||||
## 8. Implementation integration
|
||||
|
||||
The optimized firmware implementation uses a shared 32-bit whitening state to perform whitening, unwhitening, and CRC-mask extraction without the historical `xn297_scramble[]` table. The standard packet encoder/decoder computes the CRC over **unwhitened** bytes and applies the CRC whitening mask directly. The enhanced implementation uses the same model with the additional two-bit alignment.
|
||||
|
||||
`XN297Dump` continues to calculate CRC values over received encoded bytes and uses the computed historical correction function to evaluate candidate lengths and scrambling modes. Its local whitening-byte buffer is generated at runtime rather than stored as a global constant table.
|
||||
|
||||
The original XOROUT tables are therefore not necessary for the described emulation. The calculated correction depends only on scrambling mode and the number of CRC-covered bits; it does not depend on packet contents.
|
||||
|
||||
## 9. Sources and implementation references
|
||||
|
||||
1. **DIY Multiprotocol TX Module, PR #1199:** *HoTT, XN297 emu: compute the hop and CRC xorout tables* — derivation of the CRC correction function and comparison with historical tables. <https://github.com/pascallanger/DIY-Multiprotocol-TX-Module/pull/1199>
|
||||
2. **DIY Multiprotocol TX Module, PR #1201:** *Compute xn297 whitening instead of using an hardcoded array* — 28-bit recurrence, optimized byte generator, direct CRC handling, and enhanced-packet implementation. <https://github.com/pascallanger/DIY-Multiprotocol-TX-Module/pull/1201>
|
||||
3. **XN297 emulation source at PR #1201 revision `0cfaad0d`:** <https://github.com/goebish/DIY-Multiprotocol-TX-Module/blob/0cfaad0d893ef9e111febbf9b4c2c4019201d83b/Multiprotocol/XN297_EMU.ino>
|
||||
4. **XN297 interface definitions at the same revision:** <https://github.com/goebish/DIY-Multiprotocol-TX-Module/blob/0cfaad0d893ef9e111febbf9b4c2c4019201d83b/Multiprotocol/iface_xn297.h>
|
||||
5. **Historical XN297 reverse-engineering materials and HackRF recordings:** <https://github.com/victzh/xn297_reveng>
|
||||
|
||||
Reference in New Issue
Block a user