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.log file 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

  1. First pass: Scans all frequencies, marks initial detections
  2. Second pass: Re-scans same frequencies, confirms signals
  3. 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:

  1. scanFrequency() locks logMutex
  2. While still holding the lock, it calls logMessage()
  3. logMessage() tries to lock logMutex again
  4. DEADLOCK - the same thread tries to lock a mutex it already owns!

The Fix

I restructured the code to:

  1. Lock the mutex only to access/modify detectionCount
  2. Release the lock (scope ends with {})
  3. 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;

}


Once copied over to other machine - REMEMBER to DELETE this article!


Comments