Claude AI
Thought I'd give it a go and ask it if it could write me some starter code.
It did a surprisingly good job from a SIMPLE prompt as a starter and then I understood what it was doing and then took it a stage further, it did indeed get a couple of things incorrect, I hit a bug with the rtlsdr USB driver memory limit and then there was a deadlocking that occurred with the scan and logging, but once over that it seemed to do a pretty good job.
All I need this article to do is share the following lines so I can copy & paste them on a different machine:
RTL-SDR Frequency Scanner - Build & Test Guide
Required External Libraries
1. SoapySDR
Core SDR library that provides unified API for software-defined radios.
2. SoapyRTLSDR
RTL-SDR driver module for SoapySDR.
3. librtlsdr
RTL-SDR USB device driver library.
Installation on Ubuntu 24/25
Step 1: Update System
sudo apt update
sudo apt upgrade -y
Step 2: Install Dependencies
sudo apt install -y \
build-essential \
cmake \
git \
pkg-config \
libusb-1.0-0-dev \
python3-dev \
python3-numpy \
swig
Step 3: Install librtlsdr
sudo apt install -y rtl-sdr librtlsdr-dev
Step 4: Install SoapySDR
sudo apt install -y libsoapysdr-dev soapysdr-tools
Step 5: Install SoapyRTLSDR Module
sudo apt install -y soapysdr-module-rtlsdr
Step 6: Configure USB Access (Important!)
Create udev rules to allow non-root access to RTL-SDR:
sudo bash -c 'cat > /etc/udev/rules.d/20-rtlsdr.rules <<EOF
SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2832", MODE="0666"
EOF'
Reload udev rules:
sudo udevadm control --reload-rules
sudo udevadm trigger
Unplug and replug your RTL-SDR device.
Building the Code
Step 1: Save the Source Code
Save the C++ code as frequency_scanner.cpp
Step 2: Compile
g++ -std=c++17 -o frequency_scanner frequency_scanner.cpp \
-lSoapySDR -lpthread -O2
Or with debug symbols:
g++ -std=c++17 -g -o frequency_scanner frequency_scanner.cpp \
-lSoapySDR -lpthread
Step 3: Create Makefile (Optional)
Create a Makefile:
CXX = g++
CXXFLAGS = -std=c++17 -Wall -O2
LDFLAGS = -lSoapySDR -lpthread
TARGET = frequency_scanner
SRC = frequency_scanner.cpp
all: $(TARGET)
$(TARGET): $(SRC)
$(CXX) $(CXXFLAGS) -o $(TARGET) $(SRC) $(LDFLAGS)
clean:
rm -f $(TARGET) detected_frequencies.log
run: $(TARGET)
./$(TARGET)
.PHONY: all clean run
Then build with:
make
Testing the Code
Pre-Test Verification
1. Verify RTL-SDR Detection
# List available SDR devices
SoapySDRUtil --find
# Test RTL-SDR specifically
rtl_test -t
Expected output should show your RTL-SDR device.
2. Check Supported Sample Rates
SoapySDRUtil --probe="driver=rtlsdr"
Test Methods
Test 1: FM Radio Stations (Easiest)
FM broadcast stations are strong, consistent signals perfect for testing:
# Run the scanner (it scans 88-108 MHz FM band by default)
./frequency_scanner
Expected Results:
- Console output showing scan progress
detected_frequencies.logfile with detected FM stations- Typical FM stations: 88.1, 91.5, 95.7, 101.1, etc.
Test 2: Custom Frequency List
Modify the code to scan specific frequencies you know are active:
// In main(), replace the FM scan with:
std::vector<double> testFreqs = {
100.1e6, // Replace with your local FM station
101.5e6, // Replace with another local FM station
};
scanner.scanFrequencyList(testFreqs);
Test 3: Generate Test Signal
Use a smartphone app or another RTL-SDR to transmit:
Android Apps:
- RF Signal Tracker
- Walkie Talkie (transmits on allowed frequencies)
Or use rtl_fm to verify reception:
# Listen to FM station at 100.1 MHz
rtl_fm -f 100.1M -M wbfm -s 200000 -r 48000 - | aplay -r 48000 -f S16_LE
Test 4: Verify Multi-Threading
The code performs multiple scan passes. Watch console output:
[Timestamp] Scan pass 1 of 2
[Timestamp] Initial detection at 100.1 MHz - awaiting verification
[Timestamp] Scan pass 2 of 2
[Timestamp] VERIFIED SIGNAL DETECTED at 100.1 MHz
Troubleshooting
Error: "Failed to open RTL-SDR device"
# Check if device is detected
lsusb | grep RTL
# Check permissions
ls -l /dev/bus/usb/*/*
# Kill any conflicting processes
sudo killall rtl_*
Error: "No module named soapysdr"
# Reinstall SoapySDR RTL module
sudo apt install --reinstall soapysdr-module-rtlsdr
Poor Detection Rate
Adjust these parameters in the code:
const double detectionThreshold = -40.0; // Try -50.0 for more sensitivity
const int requiredDetections = 2; // Try 1 for testing
USB Errors
# Reset USB bus
sudo sh -c 'echo -n "1-1" > /sys/bus/usb/drivers/usb/unbind'
sudo sh -c 'echo -n "1-1" > /sys/bus/usb/drivers/usb/bind'
Understanding the Output
Log File Format
[Mon Dec 22 10:30:45 2025] Scanner initialized successfully
[Mon Dec 22 10:30:45 2025] Starting frequency scan from 88 MHz to 108 MHz
[Mon Dec 22 10:30:47 2025] Initial detection at 100.1 MHz - awaiting verification
[Mon Dec 22 10:30:52 2025] VERIFIED SIGNAL DETECTED at 100.1 MHz (100100000 Hz)
[Mon Dec 22 10:31:00 2025] Scan complete. Total verified detections: 5
Verification Process
- First pass: Scans all frequencies, marks initial detections
- Second pass: Re-scans same frequencies, confirms signals
- Logging: Only frequencies detected in BOTH passes are logged as verified
Performance Notes
- Scan Speed: ~100-200 frequencies per minute (depends on step size)
- Memory Usage: ~50-100 MB
- CPU Usage: Single-threaded per scan, minimal overhead
- Accuracy: 2-pass verification eliminates false positives from noise spikes
Advanced Customization
Adjust Sensitivity
const double detectionThreshold = -45.0; // More sensitive
Change Scan Parameters
scanner.scanFrequencyRange(
433e6, // Start: 433 MHz (ISM band)
435e6, // End: 435 MHz
25e3 // Step: 25 kHz
);
Add Gain Control
// In constructor, replace auto gain with manual:
device->setGainMode(SOAPY_SDR_RX, 0, false);
device->setGain(SOAPY_SDR_RX, 0, 30.0); // 30 dB gain
Quick Start Command Summary
# Install everything
sudo apt install -y build-essential rtl-sdr librtlsdr-dev \
libsoapysdr-dev soapysdr-tools soapysdr-module-rtlsdr
# Verify device
SoapySDRUtil --find
# Compile
g++ -std=c++17 -o frequency_scanner frequency_scanner.cpp \
-lSoapySDR -lpthread -O2
# Run
./frequency_scanner
# View results
cat detected_frequencies.log
Your scanner is now ready to detect and log frequency activity!
The Problem (Deadlock!)
The issue was nested mutex locking:
scanFrequency()lockslogMutex- While still holding the lock, it calls
logMessage() logMessage()tries to locklogMutexagain- DEADLOCK - the same thread tries to lock a mutex it already owns!
The Fix
I restructured the code to:
- Lock the mutex only to access/modify
detectionCount - Release the lock (scope ends with
{}) - Then call
logMessage()which can safely acquire the lock
This ensures we never hold a lock while calling a function that needs the same lock.
Additional Safety Improvements
If you're still experiencing hangs, here are some diagnostic additions:
// Add this to see where it's hanging
void scanFrequency(double frequency) {
std::cout << "Scanning " << frequency/1e6 << " MHz..." << std::endl;
if (!running) return;
if (detectSignal(frequency)) {
std::cout << "Signal detected, acquiring lock..." << std::endl;
// ... rest of code
}
}Or use a recursive mutex (allows same thread to lock multiple times):
// Change this line at the top of the class:
std::recursive_mutex logMutex; // Changed from std::mutex
// Then use:
std::lock_guard<std::recursive_mutex> lock(logMutex);
However, the fixed version I provided should resolve the deadlock without needing a recursive mutex. Try it and let me know if you still experience hanging!
Source code (it's going to go on github anyway, so I'm just putting here so I can copy & paste onto a different machine!)
#include <SoapySDR/Device.hpp>
#include <SoapySDR/Types.hpp>
#include <SoapySDR/Formats.hpp>
#include <thread>
#include <vector>
#include <mutex>
#include <atomic>
#include <fstream>
#include <iostream>
#include <cmath>
#include <chrono>
#include <map>
#include <algorithm>
class FrequencyScanner {
private:
SoapySDR::Device* device;
std::mutex logMutex;
std::atomic<bool> running{true};
std::map<double, int> detectionCount;
std::ofstream logFile;
const double sampleRate = 2.048e6;
const size_t bufferSize = 16384; //double this?
//can change these two values to be more flexible
const double detectionThreshold = -40.0; // dBFS
const int requiredDetections = 2;
public:
FrequencyScanner(const std::string& logPath = "detected_frequencies.log") {
logFile.open(logPath, std::ios::app);
if (!logFile.is_open()) {
throw std::runtime_error("Failed to open log file");
}
// Find and setup RTL-SDR device
SoapySDR::Kwargs args;
args["driver"] = "rtlsdr";
device = SoapySDR::Device::make(args);
if (!device) {
throw std::runtime_error("Failed to open RTL-SDR device");
}
// Configure device
device->setSampleRate(SOAPY_SDR_RX, 0, sampleRate);
device->setGainMode(SOAPY_SDR_RX, 0, true); // Automatic gain
logMessage("Scanner initialized successfully");
}
~FrequencyScanner() {
running = false;
if (device) {
SoapySDR::Device::unmake(device);
}
logFile.close();
}
void logMessage(const std::string& msg) {
std::lock_guard<std::mutex> lock(logMutex);
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::string timeStr = std::ctime(&time);
timeStr.pop_back(); // Remove newline
std::cout << "[" << timeStr << "] " << msg << std::endl;
logFile << "[" << timeStr << "] " << msg << std::endl;
logFile.flush();
}
double calculatePower(const std::vector<std::complex<float>>& samples) {
double sum = 0.0;
for (const auto& sample : samples) {
sum += std::norm(sample);
}
double avgPower = sum / samples.size();
std::cout << "about to do calculatePower return\n"; //0.352626
return 10.0 * std::log10(avgPower + 1e-10); // Convert to dBFS
}
bool detectSignal(double frequency) {
std::cout << "detetcing signal\n";
try {
// Set frequency
device->setFrequency(SOAPY_SDR_RX, 0, frequency);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Settling time
// Setup stream
SoapySDR::Stream* stream = device->setupStream(SOAPY_SDR_RX, SOAPY_SDR_CF32);
device->activateStream(stream);
// Read samples
std::vector<std::complex<float>> buffer(bufferSize);
std::vector<void*> buffs(1);
buffs[0] = buffer.data();
std::cout << "buffer.data()=\n";
std::cout << buffs[0]; //0x5571e4d21fd0
std::cout << "\ndetecting signal\n";
int flags = 0;
long long timeNs = 0;
int ret = device->readStream(stream, buffs.data(), bufferSize, flags, timeNs, 1000000);
std::cout << "ret=" + std::to_string(ret) + "\n";
//ret=16384
device->deactivateStream(stream);
device->closeStream(stream);
std::cout << "about to check ret\n";
if (ret > 0) {
std::cout << "buffer.resize\n";
buffer.resize(ret);
std::cout << "buffer resized\n";
double power = calculatePower(buffer);
std::cout << "result from calculatePower= " + std::to_string(power)+ "\n"; //0.352626
if (power > detectionThreshold) { //-40.0
std::cout << "should get triggered if > -40.0\n";
return true;
}
}
return false;
} catch (const std::exception& e) {
logMessage("Error detecting signal at " + std::to_string(frequency) +
" Hz: " + e.what());
return false;
}
}
void scanFrequency(double frequency) {
if (!running) return;
std::cout << "about to detectSignal\n";
if (detectSignal(frequency)) {
std::string msg;
int count = 0;
std::cout << "got a true from detectSignal\n";
std::lock_guard<std::mutex> lock(logMutex);
std::cout << "did the multithreading fail?\n";
detectionCount[frequency]++;
count = detectionCount[frequency];
if (count == requiredDetections) {
msg = "VERIFIED SIGNAL DETECTED at " +
std::to_string(frequency / 1e6) + " MHz (" +
std::to_string(frequency) + " Hz)";
//doing the logMessage causes a deadlock on the multithreading
//comment out for now - but will need figuring out at some point
//logMessage(msg);
std::cout << msg;
} else if (count == 1) {
msg = "Initial detection at " + std::to_string(frequency / 1e6) +
" MHz - awaiting verification";
//logMessage(msg);
std::cout << msg;
}
}
}
void scanFrequencyRange(double startFreq, double endFreq, double step) {
logMessage("Starting frequency scan from " + std::to_string(startFreq / 1e6) +
" MHz to " + std::to_string(endFreq / 1e6) + " MHz");
std::vector<double> frequencies;
for (double freq = startFreq; freq <= endFreq; freq += step) {
frequencies.push_back(freq);
}
// Perform multiple passes for verification
for (int pass = 0; pass < requiredDetections && running; ++pass) {
logMessage("Scan pass " + std::to_string(pass + 1) + " of " +
std::to_string(requiredDetections));
for (double freq : frequencies) {
if (!running) break;
scanFrequency(freq);
}
if (pass < requiredDetections - 1) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
logMessage("Scan complete. Total verified detections: " +
std::to_string(getVerifiedCount()));
}
void scanFrequencyList(const std::vector<double>& frequencies) {
logMessage("Starting scan of " + std::to_string(frequencies.size()) +
" specific frequencies");
for (int pass = 0; pass < requiredDetections && running; ++pass) {
logMessage("Scan pass " + std::to_string(pass + 1) + " of " +
std::to_string(requiredDetections));
for (double freq : frequencies) {
if (!running) break;
std::cout << "about to scanFreq\n";
scanFrequency(freq);
}
if (pass < requiredDetections - 1) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
logMessage("Scan complete. Total verified detections: " +
std::to_string(getVerifiedCount()));
}
int getVerifiedCount() {
std::lock_guard<std::mutex> lock(logMutex);
int count = 0;
for (const auto& pair : detectionCount) {
if (pair.second >= requiredDetections) {
count++;
}
}
return count;
}
void stop() {
running = false;
}
};
int main(int argc, char* argv[]) {
try {
std::cout << "S.U.R.F (Scanning Universal Radio Frequencies)\n";
std::cout << " 'riding the waves' of the spectrum.\n";
std::cout << "\n";
std::cout << "============================================\n\n";
FrequencyScanner scanner("detected_frequencies.log");
// Example 1: Scan FM broadcast band
std::cout << "Scanning FM broadcast band (95.7 and 88.1)...\n"; //88-108 MHz)...\n";
// scanner.scanFrequencyRange(88e6, 108e6, 0.2e6); // 200 kHz steps
// Example 2: Scan specific frequencies
std::vector<double> customFreqs = {
95.7e6,
88.1e6,
100.1e6,
433.92e6
// 100.1e6, // 100.1 MHz
// 101.5e6, // 101.5 MHz
// 433.92e6, // 433.92 MHz (ISM band)
// 868e6 // 868 MHz (ISM band)
};
scanner.scanFrequencyList(customFreqs);
std::cout << "\nScanning complete! Check detected_frequencies.log for results.\n";
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
Comments
Post a Comment