Fix Special/Flood jam duty cycle: 26% → 85%

Root cause: transmit() wraps startTransmit()+finishTransmit(). RadioLib's
finishTransmit() has a timeout of (1/bitRate)*128 = 0.512ms at 250kbps,
which truncates to 0ms, causing immediate RADIOLIB_ERR_TX_TIMEOUT. The
CC1101 was transmitting its 2.59ms packet correctly, but the 10ms inter-
packet guard meant 7.41ms of dead air — only 26% duty cycle. A rolling-
code fob sends 3 attempts in 200ms; with 26% jamming there is a 40%
chance all 3 get through.

Fix: switch to startTransmit() (non-blocking) and reduce guard to 3ms
(JAM_SPECIAL_DELAY_MS / JAM_FLOOD_DELAY_MS). CC1101 finishes the 2.59ms
packet autonomously and returns to IDLE. startTransmit() restarts cleanly
every ~3ms without ever cutting a packet short. Duty cycle: ~85%.

Made-with: Cursor
This commit is contained in:
drjones
2026-04-02 18:39:50 -07:00
parent a44462e905
commit a867707a12
2 changed files with 34 additions and 20 deletions

View File

@@ -64,7 +64,11 @@
#define JAM_FLOOD_DEV_R1_KHZ 140.0f
#define JAM_FLOOD_DEV_R2_KHZ 200.0f
// Special jam: 60-byte random payloads with 10ms delay (cypher-pulse exact clone).
// Special/Flood jam timing: CC1101 at 250 kbps takes 2.59ms to send a 61-byte packet (preamble+sync+data).
// 3ms restart gap allows each packet to complete naturally before startTransmit() is called again.
// Duty cycle: ~85%. 10ms (original cypher-pulse) only achieves 26% — too many gaps for rolling-code fobs.
#define JAM_SPECIAL_DELAY_MS 3
#define JAM_FLOOD_DELAY_MS 3
// SmartRF-style dump: config space only (TI SWRS061); PATABLE/ strobes not included.
#define CC1101_CFG_REG_LAST 0x2E

View File

@@ -1065,39 +1065,49 @@ static void stopJamming() {
}
// Packet-flood jam: PRNG frames on both radios (shared SPI — sequential).
//
// Uses startTransmit() (non-blocking) so the CC1101 finishes each packet autonomously
// and returns to IDLE on its own. The 3ms guard (JAM_SPECIAL_DELAY_MS) is just over
// the 2.59ms on-air time for a 61-byte packet at 250 kbps, so we restart cleanly
// without ever cutting a packet short. Duty cycle: ~85% vs. the previous 26%.
//
// RadioLib's transmit() wraps startTransmit()+finishTransmit(); finishTransmit() has
// a timeout bug at 250 kbps (timeout = 0ms) causing it to immediately return
// RADIOLIB_ERR_TX_TIMEOUT while the CC1101 is still transmitting. Using startTransmit()
// directly avoids that entirely — we just let the CC1101 run and restart after 3ms.
static void jamFloodTick() {
if (!s_floodJamActive || !jammingEnabled) return;
static uint8_t pkt[JAM_FLOOD_PKT_BYTES];
static uint32_t lastMs;
static uint32_t lastMs = 0;
const uint32_t now = millis();
const uint32_t delay = (jamMode == JamMode::SPECIAL)
? (uint32_t)JAM_SPECIAL_DELAY_MS
: (uint32_t)JAM_FLOOD_DELAY_MS;
if ((uint32_t)(now - lastMs) < delay) return;
lastMs = now; // timestamp BEFORE SPI — gives tightest pacing
if (jamMode == JamMode::SPECIAL) {
// Exact cypher-pulse clone: 60-byte payload, 10ms wait, blocking transmit.
if ((uint32_t)(millis() - lastMs) < 10u) return;
if (radio1Status == 2) {
esp_fill_random(pkt, 60);
(void)radio1.transmit(pkt, 60);
esp_fill_random(pkt, JAM_FLOOD_PKT_BYTES);
(void)radio1.startTransmit(pkt, JAM_FLOOD_PKT_BYTES);
}
if (radio2Status == 2) {
esp_fill_random(pkt, 60);
(void)radio2.transmit(pkt, 60);
esp_fill_random(pkt, JAM_FLOOD_PKT_BYTES);
(void)radio2.startTransmit(pkt, JAM_FLOOD_PKT_BYTES);
}
} else {
// Flood: full transmit() — startTransmit() without finishTransmit() was aborting
// each packet on the next tick (standby() at start of TX), so almost no on-air energy.
if ((uint32_t)(millis() - lastMs) < 2u) return;
// FLOOD mode: same non-blocking approach, same packet size.
if (radio1Status == 2) {
esp_fill_random(pkt, sizeof(pkt));
(void)radio1.transmit(pkt, sizeof(pkt));
esp_fill_random(pkt, JAM_FLOOD_PKT_BYTES);
(void)radio1.startTransmit(pkt, JAM_FLOOD_PKT_BYTES);
}
if (radio2Status == 2) {
esp_fill_random(pkt, sizeof(pkt));
(void)radio2.transmit(pkt, sizeof(pkt));
esp_fill_random(pkt, JAM_FLOOD_PKT_BYTES);
(void)radio2.startTransmit(pkt, JAM_FLOOD_PKT_BYTES);
}
}
lastMs = millis(); // record time AFTER transmit completes
}
static int hexNibble(char c) {