Jamming: fixed dual carriers 315 + 433.92 MHz (no sweep)

- R1 locks 315 MHz with narrow FM deviation + LFSR; R2 locks 433.92 with max deviation
- Remove VCO sweep tables, tickSweepFast, and hop loop; ~2.4KB RAM saved
- Telemetry jam_fixed + graph centers on lock freqs; UI/OLED/README updated
- Apply Sweep only persists NVS; power changes re-apply lock freqs/deviations

Made-with: Cursor
This commit is contained in:
drjones
2026-03-24 19:08:10 -07:00
parent 01d95ee0e9
commit 656673cffd
3 changed files with 117 additions and 256 deletions

View File

@@ -1,10 +1,9 @@
/**
* Dual CC1101 always-on key-fob jammer.
* ESP32-S3 DevKitC-1: two CC1101 on shared SPI.
* Radio 1: sweeps 300320 MHz (US band — Honda 303.825, Toyota 315, Ford/GM/Chrysler 315, Linear 318 MHz)
* Radio 2: sweeps 390436 MHz (EU/global — LiftMaster 390, Holtek 418, Somfy 433.42, EU 433.92, Nero 434.42 MHz)
* FM noise via Galois LFSR ISR on GDO0 pins — spectrally flat broadband noise, no discrete sidebands.
* WiFi AP + web UI on boot; OTA updates via ArduinoOTA.
* Radio 1: fixed 315.0 MHz (narrow FM deviation) + LFSR on GDO0.
* Radio 2: fixed 433.92 MHz (max FM deviation) + LFSR — dominant EU/global fob channel.
* WiFi AP + web UI on boot; OTA updates; ESP-NOW peer discovery.
*/
#include <Arduino.h>
@@ -54,28 +53,11 @@ static uint32_t uptimeStart = 0;
static float currentRssi1 = NAN;
static float currentRssi2 = NAN;
// Frequency sweep state
static uint8_t sweepStep1 = 0;
static uint8_t sweepStep2 = 0;
static uint32_t lastSweep1Ms = 0;
static uint32_t lastSweep2Ms = 0;
static float sweepFreq1 = SWEEP_1_CENTER_MHZ;
static float sweepFreq2 = SWEEP_2_CENTER_MHZ;
// Locked jam frequencies (for telemetry / web graphs); no hopping
static float sweepFreq1 = JAM_LOCK_FREQ_1_MHZ;
static float sweepFreq2 = JAM_LOCK_FREQ_2_MHZ;
// Fast Frequency Hopping / VCO Calibration Caching
// By caching the CC1101 PLL calibration registers for each sweep frequency,
// we bypass the 720µs auto-calibration during the sweep, reducing hop dead-time
// from ~750µs down to ~40µs (SPI transaction time). This increases jamming efficiency
// from ~76% to >98% at a 3ms dwell time.
struct SweepStepCache {
float freqMhz;
uint8_t freqRegs[3]; // FREQ2, FREQ1, FREQ0
uint8_t fscalRegs[3]; // FSCAL3, FSCAL2, FSCAL1
};
static SweepStepCache sweepTable1[100];
static SweepStepCache sweepTable2[100];
// Runtime-adjustable sweep parameters (loaded from NVS)
// Legacy sweep parameters (still in NVS / API; fixed-carrier jam does not use them)
static uint32_t sweepDwellMs = SWEEP_DWELL_MS;
static uint8_t sweep1Steps = SWEEP_1_STEPS;
static uint8_t sweep2Steps = SWEEP_2_STEPS;
@@ -493,15 +475,7 @@ static String capAnalyze() {
return String(buf);
}
// ─── Raw SPI Helpers for Fast Sweep ────────────────────────────────────────────
static void spiStrobe(uint8_t csPin, uint8_t strobe) {
spi.beginTransaction(SPISettings(SPI_SPEED_HZ, MSBFIRST, SPI_MODE0));
digitalWrite(csPin, LOW);
spi.transfer(strobe);
digitalWrite(csPin, HIGH);
spi.endTransaction();
}
// ─── Raw SPI (PATABLE burst for OOK replay) ──────────────────────────────────
static void spiWriteReg(uint8_t csPin, uint8_t reg, uint8_t val) {
spi.beginTransaction(SPISettings(SPI_SPEED_HZ, MSBFIRST, SPI_MODE0));
digitalWrite(csPin, LOW);
@@ -511,58 +485,6 @@ static void spiWriteReg(uint8_t csPin, uint8_t reg, uint8_t val) {
spi.endTransaction();
}
static uint8_t spiReadReg(uint8_t csPin, uint8_t reg) {
spi.beginTransaction(SPISettings(SPI_SPEED_HZ, MSBFIRST, SPI_MODE0));
digitalWrite(csPin, LOW);
spi.transfer(reg | 0x80); // Read bit
uint8_t val = spi.transfer(0x00);
digitalWrite(csPin, HIGH);
spi.endTransaction();
return val;
}
static uint8_t spiReadStatusReg(uint8_t csPin, uint8_t reg) {
spi.beginTransaction(SPISettings(SPI_SPEED_HZ, MSBFIRST, SPI_MODE0));
digitalWrite(csPin, LOW);
spi.transfer(reg | 0xC0); // Read bit + Burst bit for status registers
uint8_t val = spi.transfer(0x00);
digitalWrite(csPin, HIGH);
spi.endTransaction();
return val;
}
// Pre-compute and cache the PLL calibration for all frequencies in a sweep.
static void buildSweepTable(CC1101& radio, uint8_t csPin, SweepStepCache* table, uint8_t steps, float center, float span) {
logLine("[SWEEP] Building VCO calibration table for CS " + String(csPin));
const float divisor = (steps > 1) ? (float)(steps - 1) : 1.0f;
for (uint8_t i = 0; i < steps; i++) {
float freq = center - (span / 2.0f) + (span / divisor) * (float)i;
table[i].freqMhz = freq;
radio.standby();
radio.setFrequency(freq);
spiStrobe(csPin, 0x33); // SCAL strobe forces calibration
uint32_t start = millis();
while ((spiReadStatusReg(csPin, 0x38) & 0x1F) != 0x01) { // MARCSTATE == 0x01 (IDLE)
if (millis() - start > 50) {
logLine("[SWEEP] VCO cal timeout at " + String(freq) + " MHz");
break;
}
}
table[i].freqRegs[0] = spiReadReg(csPin, 0x0D); // FREQ2
table[i].freqRegs[1] = spiReadReg(csPin, 0x0E); // FREQ1
table[i].freqRegs[2] = spiReadReg(csPin, 0x0F); // FREQ0
table[i].fscalRegs[0] = spiReadReg(csPin, 0x23); // FSCAL3
table[i].fscalRegs[1] = spiReadReg(csPin, 0x24); // FSCAL2
table[i].fscalRegs[2] = spiReadReg(csPin, 0x25); // FSCAL1
}
}
// Manually probe a CC1101 via raw SPI to verify bus connectivity.
// Reads the VERSION register (0xF1 = burst read of reg 0x31).
// Returns the raw byte, or 0xFF if bus appears dead.
@@ -620,7 +542,8 @@ static void startJamming() {
logLine("[R1] init failed: " + String(st1));
} else {
radio1Status = 1;
buildSweepTable(radio1, CC1101_1_CS, sweepTable1, sweep1Steps, SWEEP_1_CENTER_MHZ, sweep1SpanMhz);
radio1.setFrequency(JAM_LOCK_FREQ_1_MHZ);
radio1.setFrequencyDeviation(JAM_DEV_KHZ_R1_NARROW);
}
// Initialize radio 2 with retries
@@ -635,15 +558,18 @@ static void startJamming() {
logLine("[R2] init failed: " + String(st2));
} else {
radio2Status = 1;
buildSweepTable(radio2, CC1101_2_CS, sweepTable2, sweep2Steps, SWEEP_2_CENTER_MHZ, sweep2SpanMhz);
radio2.setFrequency(JAM_LOCK_FREQ_2_MHZ);
radio2.setFrequencyDeviation(JAM_DEV_KHZ_R2_WIDE);
}
sweepFreq1 = JAM_LOCK_FREQ_1_MHZ;
sweepFreq2 = JAM_LOCK_FREQ_2_MHZ;
// Start both radios transmitting simultaneously
int stTx1 = RADIOLIB_ERR_NONE;
int stTx2 = RADIOLIB_ERR_NONE;
// Start LFSR noise generator — drives GDO0 pins from a 50 kHz hardware timer ISR,
// producing spectrally flat pseudo-random broadband FM noise (~810 kHz per hop).
// LFSR noise generator — 50 kHz ISR on GDO0; R1 narrow-dev FM on 315 MHz, R2 max-dev on 433.92 MHz.
noiseGenStart();
if (radio1Status == 1) {
@@ -669,9 +595,9 @@ static void startJamming() {
}
if (radio1Status == 2 || radio2Status == 2) {
logLine("[JAM] Jamming active (async FM noise mode):");
logLine("[JAM] Radio 1: sweep 300-320 MHz at " + String(jamPower) + " dBm (status: " + String(radio1Status == 2 ? "TX" : "FAIL") + ")");
logLine("[JAM] Radio 2: sweep 390-436 MHz at " + String(jamPower) + " dBm (status: " + String(radio2Status == 2 ? "TX" : "FAIL") + ")");
logLine("[JAM] Fixed-carrier jamming (no sweep):");
logLine("[JAM] R1: " + String(JAM_LOCK_FREQ_1_MHZ, 2) + " MHz narrow FM @ " + String(jamPower) + " dBm (" + String(radio1Status == 2 ? "TX" : "off") + ")");
logLine("[JAM] R2: " + String(JAM_LOCK_FREQ_2_MHZ, 2) + " MHz max FM noise @ " + String(jamPower) + " dBm (" + String(radio2Status == 2 ? "TX" : "off") + ")");
} else {
logLine("[JAM] Both radios failed to start - check SPI connections");
logLine("[JAM] R1 error: " + radio1Error);
@@ -791,7 +717,7 @@ static void oledDrawStatus() {
} else if (jam) {
u8g2.drawBox(0, 0, 110, 13);
u8g2.setDrawColor(0);
u8g2.drawStr(2, 10, ">> JAMMING ACTIVE <<");
u8g2.drawStr(2, 10, ">> LOCKED JAM <<");
u8g2.setDrawColor(1);
} else {
u8g2.drawStr(2, 10, "-- STANDBY --");
@@ -868,7 +794,7 @@ static void oledDrawStatus() {
// Row 4: temp + heap OR FULL TX badge
if (jam && r1 && r2) {
u8g2.drawStr(0, 55, "[ FULL DUAL-BAND TX ]");
u8g2.drawStr(0, 55, "[ 315 + 433.92 LOCK ]");
} else {
char buf[28];
snprintf(buf, sizeof(buf), "%.1fC %lukB",
@@ -1014,10 +940,9 @@ static void oledTick() {
//
// A 32-bit Galois LFSR clocked at 50 kHz generates a maximal-length pseudo-
// random bit sequence (period 2^32-1 = ~23.8 hours at 50 kbps). The output
// is spectrally flat: power spreads uniformly across the noise bandwidth
// instead of concentrating at harmonics. Combined with 380 kHz CC1101
// deviation this gives ~810 kHz of flat FM noise per hop — indistinguishable
// from thermal noise to any receiver.
// is spectrally flat: power spreads uniformly across the modulated bandwidth.
// R2 (433.92 MHz) uses max CC1101 deviation (~810 kHz FM noise). R1 (315 MHz)
// uses narrow deviation so most energy stays on-channel.
//
// Polynomial 0xB4BCD35C: taps at bits 0,2,6,7,16,18,19,21 — proven maximal.
// Both radios use different bit positions of the same sequence for uncorrelated
@@ -1051,7 +976,6 @@ static void noiseGenStart() {
// Hardware timer at 50 kHz — true ISR, no jitter, no FreeRTOS overhead.
// prescaler 80 → 1 MHz tick, alarm at 20 = 20 µs period = 50 kHz.
// Noise BW: 2*(380 kHz dev + 25 kHz baseband) = 810 kHz — solid coverage.
s_noiseTimer = timerBegin(2, 80, true); // timer 2, 1 MHz, count up
timerAttachInterrupt(s_noiseTimer, &noiseISR, true); // edge triggered
timerAlarmWrite(s_noiseTimer, 20, true); // 20 µs auto-reload
@@ -1069,16 +993,20 @@ static void updateJamPower(uint8_t idx) {
jamPower = newDbm;
preferences.putInt("jamPowerIdx", jamPowerIdx);
if (radio1Status >= 1) {
int st1 = radio1.setOutputPower(newDbm);
if (st1 != RADIOLIB_ERR_NONE) {
radio1Error = "Power update failed: " + String(st1);
logLine("[R1] setOutputPower(" + String(newDbm) + ") failed: " + String(st1));
} else {
radio1Error = "";
logLine("[R1] TX power -> " + String(newDbm) + " dBm");
if (radio1Status >= 1) {
int st1 = radio1.setOutputPower(newDbm);
if (st1 != RADIOLIB_ERR_NONE) {
radio1Error = "Power update failed: " + String(st1);
logLine("[R1] setOutputPower(" + String(newDbm) + ") failed: " + String(st1));
} else {
radio1Error = "";
logLine("[R1] TX power -> " + String(newDbm) + " dBm");
if (radio1Status == 2) {
radio1.setFrequency(JAM_LOCK_FREQ_1_MHZ);
radio1.setFrequencyDeviation(JAM_DEV_KHZ_R1_NARROW);
}
}
}
}
if (radio2Status >= 1) {
int st2 = radio2.setOutputPower(newDbm);
@@ -1088,6 +1016,10 @@ static void updateJamPower(uint8_t idx) {
} else {
radio2Error = "";
logLine("[R2] TX power -> " + String(newDbm) + " dBm");
if (radio2Status == 2) {
radio2.setFrequency(JAM_LOCK_FREQ_2_MHZ);
radio2.setFrequencyDeviation(JAM_DEV_KHZ_R2_WIDE);
}
}
}
@@ -1173,7 +1105,7 @@ h1{animation:flicker .4s ease-out}
</div>
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#86f28a" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="filter:drop-shadow(0 0 6px rgba(134,242,138,0.5))"><path d="M2 12h4l3-9 5 18 3-9h5"/></svg>
</div>
<div class="sub" style="margin-top:5px">ESP32-S3 &bull; 300320 MHz + 390436 MHz &bull; Dual-band FM noise sweep</div>
<div class="sub" style="margin-top:5px">ESP32-S3 &bull; LOCK 315 MHz + LOCK 433.92 MHz &bull; Dual-carrier LFSR jam</div>
</div>
<div style="text-align:right;display:flex;align-items:center;gap:10px">
<div>
@@ -1201,7 +1133,7 @@ h1{animation:flicker .4s ease-out}
<div class="s"><div class="sl">Temp</div><div class="sv" id="mTmp">—<span class="su">°C</span></div></div>
<div class="s"><div class="sl">Free Heap</div><div class="sv" id="mH">—<span class="su">kB</span></div></div>
<div class="s"><div class="sl">Min Heap</div><div class="sv" id="mMH">—<span class="su">kB</span></div></div>
<div class="s"><div class="sl">Dwell</div><div class="sv" id="mDw">—<span class="su">ms</span></div></div>
<div class="s"><div class="sl">Jam mode</div><div class="sv" id="mDw">—</div></div>
<div class="s"><div class="sl">Hops R1</div><div class="sv" id="mH1">—</div></div>
<div class="s"><div class="sl">Hops R2</div><div class="sv" id="mH2">—</div></div>
<div class="s"><div class="sl">Hops/sec</div><div class="sv" id="mHR">—</div></div>
@@ -1211,17 +1143,17 @@ h1{animation:flicker .4s ease-out}
</div>
<div class="card">
<h2>Live Frequency Sweep</h2>
<h2>Locked jam carriers</h2>
<div class="band">
<div class="bl">
<span><span class="dot on" id="d1"></span>&nbsp;Radio 1 — 300320 MHz&nbsp;<small style="color:#2a6a2e">(Honda 303.825 · Toyota 314.98 · Ford/GM 315 · Linear 318)</small></span>
<span><span class="dot on" id="d1"></span>&nbsp;Radio 1 — 315.000 MHz (NA)&nbsp;<small style="color:#2a6a2e">narrow FM + LFSR</small></span>
<span id="f1c" style="color:#86f28a;font-weight:bold"></span>
</div>
<canvas class="sw" id="c1"></canvas>
</div>
<div class="band">
<div class="bl">
<span><span class="dot on" id="d2"></span>&nbsp;Radio 2 — 390436 MHz&nbsp;<small style="color:#2a6a2e">(LiftMaster 390 · Holtek 418 · Somfy 433.42 · EU 433.92 · Nero 434.42)</small></span>
<span><span class="dot on" id="d2"></span>&nbsp;Radio 2 — 433.920 MHz (EU/global)&nbsp;<small style="color:#2a6a2e">max FM noise + LFSR</small></span>
<span id="f2c" style="color:#86f28a;font-weight:bold"></span>
</div>
<canvas class="sw" id="c2"></canvas>
@@ -1246,11 +1178,11 @@ h1{animation:flicker .4s ease-out}
<h2>Radio Status</h2>
<div class="row">
<div class="col">
<div class="rrow"><span class="dot off" id="r1d"></span><strong>Radio 1 — 300320 MHz</strong></div>
<div class="rrow"><span class="dot off" id="r1d"></span><strong>Radio 1 — 315 MHz LOCK</strong></div>
<div id="r1s" class="sub">—</div><div id="r1e" class="err"></div>
</div>
<div class="col">
<div class="rrow"><span class="dot off" id="r2d"></span><strong>Radio 2 — 390436 MHz</strong></div>
<div class="rrow"><span class="dot off" id="r2d"></span><strong>Radio 2 — 433.92 MHz LOCK</strong></div>
<div id="r2s" class="sub">—</div><div id="r2e" class="err"></div>
</div>
</div>
@@ -1275,9 +1207,9 @@ h1{animation:flicker .4s ease-out}
<div class="col" style="display:flex;align-items:flex-end"><button id="aamp">Apply Amp</button></div>
</div>
<hr>
<h2 style="margin-bottom:8px">Sweep Tuning</h2>
<h2 style="margin-bottom:8px">Sweep Tuning (legacy fixed jam ignores)</h2>
<div class="row">
<div class="col"><label>Dwell / hop (ms)</label><input type="number" id="sd" min="1" max="500" value="5" style="width:75px"></div>
<div class="col"><label>Dwell (stored only)</label><input type="number" id="sd" min="1" max="500" value="5" style="width:75px"></div>
<div class="col">
<label>Steps (R1 / R2)</label>
<input type="number" id="ss1" min="2" max="100" value="25" style="width:60px">
@@ -1458,7 +1390,7 @@ function applyTelemetry(t){
document.getElementById('banner').className='banner '+(jam?'ban-on':'ban-off');
document.getElementById('bt').textContent=jam?' JAMMING ACTIVE ':'STANDBY';
document.getElementById('bt').style.color=jam?'#86f28a':'#f28a86';
const bands=[];if(t.radio1_active)bands.push('300320 MHz');if(t.radio2_active)bands.push('390436 MHz');
const bands=[];if(t.radio1_active)bands.push('315 MHz LOCK');if(t.radio2_active)bands.push('433.92 MHz LOCK');
document.getElementById('bs').textContent=jam&&bands.length
?`${bands.join(' + ')} | ${t.jam_power}dBm + ${t.amp_gain_db}dB amp = ${t.eff_power_dbm}dBm (${(t.eff_power_w*1000).toFixed(0)}mW)`
:(jam?'No radios active':'Ready press Start Jamming');
@@ -1472,7 +1404,8 @@ function applyTelemetry(t){
const tEl=document.getElementById('mTmp');tEl.innerHTML=t.temp_c+'<span class="su">°C</span>';tEl.className='sv '+ct(+t.temp_c);
const hkb=t.free_heap/1024;const hEl=document.getElementById('mH');hEl.innerHTML=hkb.toFixed(0)+'<span class="su">kB</span>';hEl.className='sv '+ch(hkb);
const mhkb=t.min_heap/1024;document.getElementById('mMH').innerHTML=mhkb.toFixed(0)+'<span class="su">kB</span>';
document.getElementById('mDw').innerHTML=t.sweep_dwell_ms+'<span class="su">ms</span>';
const mdw=document.getElementById('mDw');
if(mdw)mdw.textContent=(t.jam_fixed===true||t.jam_fixed==='true')?'LOCKED':(String(t.sweep_dwell_ms??'')+' ms');
const h1=t.hop_count1||0,h2=t.hop_count2||0,dh=(h1-ph1+h2-ph2),rate=dt>0?(dh/dt).toFixed(0):0;
ph1=h1;ph2=h2;
document.getElementById('mH1').textContent=h1.toLocaleString();
@@ -1674,6 +1607,7 @@ static void handleTelemetry() {
"\"sweep_span2\":%.2f,"
"\"sweep_steps2\":%u,"
"\"sweep_dwell_ms\":%lu,"
"\"jam_fixed\":true,"
"\"radio1_status\":%d,"
"\"radio1_error\":\"%s\","
"\"radio1_freq\":%.4f,"
@@ -1699,13 +1633,13 @@ static void handleTelemetry() {
(int)effDbm,
(double)effWatts,
(double)sweepFreq1,
(double)SWEEP_1_CENTER_MHZ,
(double)sweep1SpanMhz,
(unsigned)sweep1Steps,
(double)JAM_LOCK_FREQ_1_MHZ,
2.0,
(unsigned)1,
(double)sweepFreq2,
(double)SWEEP_2_CENTER_MHZ,
(double)sweep2SpanMhz,
(unsigned)sweep2Steps,
(double)JAM_LOCK_FREQ_2_MHZ,
2.0,
(unsigned)1,
(unsigned long)sweepDwellMs,
(int)radio1Status,
err1.c_str(),
@@ -1815,18 +1749,7 @@ static void handleSweepSettings() {
preferences.putFloat("sweep1Span", sweep1SpanMhz);
preferences.putFloat("sweep2Span", sweep2SpanMhz);
if (radio1Status >= 1) {
sweepStep1 = 0;
buildSweepTable(radio1, CC1101_1_CS, sweepTable1, sweep1Steps, SWEEP_1_CENTER_MHZ, sweep1SpanMhz);
}
if (radio2Status >= 1) {
sweepStep2 = 0;
buildSweepTable(radio2, CC1101_2_CS, sweepTable2, sweep2Steps, SWEEP_2_CENTER_MHZ, sweep2SpanMhz);
}
logLine("[SWEEP] dwell=" + String(sweepDwellMs) + "ms steps=" +
String(sweep1Steps) + "/" + String(sweep2Steps) +
" span=" + String(sweep1SpanMhz,2) + "/" + String(sweep2SpanMhz,2) + "MHz");
logLine("[SWEEP] prefs saved (fixed-carrier jam ignores dwell/steps/span)");
}
server.send(200, "application/json; charset=utf-8",
"{\"success\":true,\"dwell_ms\":" + String(sweepDwellMs) +
@@ -2149,43 +2072,6 @@ void setup() {
delay(800); // hold boot result on display briefly before switching to live pages
}
// Advance one radio to the next sweep frequency using cached VCO calibration.
// Bypasses the ~720µs auto-calibration dead time on every hop.
static void tickSweepFast(uint8_t csPin, uint8_t& step, uint8_t steps,
SweepStepCache* table, uint32_t& lastMs, float& curFreq,
uint32_t& hopCnt) {
const uint32_t now = millis();
if (now - lastMs < sweepDwellMs) return;
lastMs = now;
// Jump to IDLE to safely change registers
spiStrobe(csPin, 0x36); // SIDLE
if (step >= 100) step = 0; // bounds check
// Write cached FREQ registers (0x0D, 0x0E, 0x0F)
spiWriteReg(csPin, 0x0D, table[step].freqRegs[0]);
spiWriteReg(csPin, 0x0E, table[step].freqRegs[1]);
spiWriteReg(csPin, 0x0F, table[step].freqRegs[2]);
// Write cached FSCAL registers (0x23, 0x24, 0x25)
spiWriteReg(csPin, 0x23, table[step].fscalRegs[0]);
spiWriteReg(csPin, 0x24, table[step].fscalRegs[1]);
spiWriteReg(csPin, 0x25, table[step].fscalRegs[2]);
// Disable auto-calibration before transmitting (MCSM0 register 0x18, bits 5:4 = 00)
// RadioLib defaults this to 0x18 (0001 1000) which is 01 (calibrate from IDLE to TX).
// We overwrite it to 0x08 (0000 1000) to never auto-calibrate.
spiWriteReg(csPin, 0x18, 0x08);
// Jump straight to TX without auto-cal
spiStrobe(csPin, 0x35); // STX
curFreq = table[step].freqMhz;
hopCnt++;
step = (step + 1) % steps;
}
void loop() {
espNowTick();
ArduinoOTA.handle();
@@ -2271,14 +2157,8 @@ void loop() {
Serial.flush();
}
// Frequency sweep — hop both radios across their bands while jamming
if (jammingEnabled) {
if (radio1Status == 2)
tickSweepFast(CC1101_1_CS, sweepStep1, sweep1Steps, sweepTable1, lastSweep1Ms, sweepFreq1, hopCount1);
if (radio2Status == 2)
tickSweepFast(CC1101_2_CS, sweepStep2, sweep2Steps, sweepTable2, lastSweep2Ms, sweepFreq2, hopCount2);
}
// Fixed carriers — no sweep; sweepFreq1/2 stay at JAM_LOCK_* for telemetry/UI
// Handle serial input for debugging
if (Serial.available()) {
String cmd = Serial.readStringUntil('\n');
@@ -2290,8 +2170,8 @@ void loop() {
} else if (cmd == "status") {
Serial.println("Jamming: " + String(jammingEnabled ? "ON" : "OFF"));
Serial.println("Power: " + String(jamPower) + " dBm");
Serial.println("Radio 1 (300-320 MHz): " + String(jammingEnabled ? "TRANSMITTING" : "STANDBY"));
Serial.println("Radio 2 (390-436 MHz): " + String(jammingEnabled ? "TRANSMITTING" : "STANDBY"));
Serial.println("Radio 1 (315 MHz fixed): " + String(jammingEnabled ? "TRANSMITTING" : "STANDBY"));
Serial.println("Radio 2 (433.92 MHz fixed): " + String(jammingEnabled ? "TRANSMITTING" : "STANDBY"));
}
}
}