Skip to content

CrissCCL/FM-RDS-BPSK_Analysis_SDR-RTL

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“» FM RDS BPSK SDR Lab

Python RTL-SDR DSP RDS Status License

Overview

Educational SDR laboratory for extracting and decoding Radio Data System (RDS) information from a commercial FM broadcast station. The RDS subcarrier is isolated from the FM multiplex, translated to baseband, synchronized, interpreted as a BPSK signal, and decoded into valid RDS groups.

This repository follows a technical and educational style: real IQ capture, FM demodulation, spectral analysis, FIR filtering, carrier recovery, BPSK visualization, bit extraction, RDS block validation, Program Identification (PI), Program Service name (PS), and RadioText (RT) reconstruction.

The figuras_rds_A/ folder name is intentionally preserved because the analysis script writes figures there by default. The included images are placeholders and should be replaced by the real plots generated after running Part A.

πŸ“‚ Contents

  • src β†’ Python code for SDR capture, demodulation, and RDS decoding
  • results β†’ real capture logs and Part B summary figure

🧰 Hardware Setup

The measurements were performed using a low-cost RTL-SDR USB dongle connected to a standard FM broadcast antenna.

Device

  • SDR Receiver: RTL-SDR (RTL2832U compatible)
  • Frequency Band: FM broadcast band
  • Interface: USB
  • Antenna: Wideband FM antenna
  • Local FM station with RDS transmission.

If an IQ capture is already available in .npz format, the RTL-SDR is not required.

The RTL-SDR is used exclusively for IQ data acquisition, while all signal processing is performed offline in Python.

SDR-RTL

πŸš€ Main Scripts

Script Purpose
src/part_a_rds_analysis.py Captures or loads IQ data, demodulates FM, analyzes the FM multiplex, extracts the RDS component, validates BPSK behavior, and generates didactic figures.
src/part_b_rds_decoding.py Processes the IQ capture, searches for valid RDS groups, consolidates the dominant PI, reconstructs PS by segment voting, and extracts RadioText when enough segments are available.

🎯 Laboratory Goals

  • Capture or load IQ samples from an FM broadcast station.
  • Demodulate FM to obtain the composite multiplex signal.
  • Identify the main spectral components of the FM multiplex:
    • mono audio band,
    • 19 kHz stereo pilot,
    • stereo difference region,
    • 57 kHz RDS subcarrier.
  • Isolate the RDS band using FIR filtering.
  • Translate the RDS subcarrier to baseband.
  • Recover the BPSK-like symbol stream.
  • Extract a binary RDS bitstream.
  • Validate RDS blocks using syndrome checks.
  • Decode and consolidate:
    • PI: Program Identification,
    • PS: Program Service name,
    • RT: RadioText.

πŸ“‘ RDS in the FM Multiplex

In FM broadcasting, RDS is transmitted around the 57 kHz subcarrier inside the composite multiplex signal. This frequency is the third harmonic of the 19 kHz stereo pilot:

$$57\,\text{kHz} = 3 \cdot 19\,\text{kHz}$$

The RDS symbol rate is:

$$R_s = 1187.5\,\text{symbols/s}$$

In this implementation, the extracted RDS signal is resampled to:

$$F_s = 19\,\text{kHz}$$

which gives:

$$\frac{19000}{1187.5} = 16$$

samples per RDS bit/symbol interval.

πŸ”„ Processing Pipeline

pipeline

🧠 Key DSP Blocks

πŸ”Ή 1. FM Demodulation

The FM multiplex is obtained from the phase difference between consecutive IQ samples:

$$x_{FM}[n] = \angle\left(x[n] \cdot x^*[n-1]\right)$$

where x[n] is the complex IQ signal.

πŸ”Ή 2. RDS Band Extraction

The RDS component is isolated using a bandpass filter around the 57 kHz subcarrier:

$$54\,\text{kHz} \leq f \leq 60\,\text{kHz}$$

πŸ”Ή 3. Baseband Translation

Two routes are implemented:

Route Description
piloto_19k_al_cubo Uses the 19 kHz stereo pilot raised to the third power to generate a 57 kHz reference.
oscilador_fijo_57k Uses a fixed 57 kHz complex oscillator.

Using both routes improves robustness when different stations exhibit different RDS recovery behavior.

πŸ”Ή 4. BPSK Carrier Recovery

A BPSK Costas loop is used to stabilize the recovered baseband phase. A PCA-based rotation is then applied to align the BPSK symbol cloud with the in-phase axis.

πŸ”Ή 5. Biphase Bit Metric

The RDS bit decision metric compares the first and second halves of each bit interval:

$$m_k = \frac{1}{N_1}\sum_{n \in \text{first half}} x[n] - \frac{1}{N_2}\sum_{n \in \text{second half}} x[n]$$

The sign of this metric is used to estimate the corresponding bit state.

πŸ”Ή 6. RDS Group Validation

Each RDS block has:

16 information bits + 10 check bits = 26 bits

A complete RDS group contains four blocks:

A | B | C/C' | D

Therefore, one full RDS group contains:

$$4 \cdot 26 = 104\,\text{bits}$$

The decoder searches for valid A, B, C/C', and D blocks, filters by the dominant PI, removes duplicates, and consolidates the final information.


πŸ–ΌοΈ Main Figures

The most relevant figures are reserved in figuras_rds_A/. Replace the placeholder images with the real figures generated by the script.

πŸ“Š FM Multiplex Spectrum and RDS Zoom

RDS zoom

FM Multiplex Spectrogram

espectro

🧩 RDS Extraction Stages

extraction

πŸ”’ RDS Bitstream Extraction

extraction2

🟣 BPSK Time Signal and Validated Constellation

constelation

🧾 Validated 104-bit RDS Group

constelation

βœ… Real Capture Results: Radio Carolina 98.9 MHz

A real off-air FM capture was performed using an RTL-SDR receiver tuned to Radio Carolina 98.9 MHz.
This result is included as the main experimental validation case of the repository.

The capture demonstrates a realistic scenario: the RDS Program Service name was fully recovered, and the RadioText message was reconstructed almost completely from received RDS groups.

πŸ“‘ Capture Setup

Item Value
FM station Radio Carolina
Frequency 98.9 MHz
RF sample rate 2.048 MS/s
Capture duration 65 s
RTL-SDR gain 30 dB
IQ samples 133,120,000
Tuner detected Rafael Micro R828D

πŸ§ͺ Part A: RDS Extraction and BPSK Validation

Item Result
MPX sample rate 228 kHz
MPX duration 65.00 s
Main RDS route 19 kHz pilot cubed
Estimated residual RDS offset -0.86 Hz
Raw RDS groups found 104
Consolidated RDS groups 13
Dominant PI 0xCB1A
Program Service CAROLINA
PS segments 4/4
Part A total time 80.36 s

Part A confirms that the RDS component around 57 kHz was successfully extracted, translated to baseband, synchronized, and interpreted as a valid BPSK-like signal.

🧬 Part B: Generic AUTO RDS Decoding

Item Result
Search mode Generic AUTO
Main route used piloto_19k_al_cubo
Estimated residual offset -0.86 Hz
Useful RDS duration 64.74 s
Raw detected groups 272
PI count 0xCB1A: 272
Consolidated groups 20
Program Service CAROLINA
PS segments 4/4
RT segments received 12/16
Part B total time 115.87 s

Recovered Program Service:

CAROLINA

Recovered RadioText:

Escribenos un Whatsapp 569 61246007 ????arolina 98.9

The missing ???? characters correspond to incomplete RadioText segment coverage during the capture. This is expected in a real broadcast scenario when the receiver stops before all RadioText segments are repeated. Even so, the message is reconstructed almost entirely, which validates that the processing chain is working with real RDS data rather than a synthetic example.

πŸ–ΌοΈ Part B Summary Figure

Part B RDS summary for Radio Carolina 98.9 MHz

The full console logs are included in:

results/radio_carolina_98_9_part_a_console_output.txt
results/radio_carolina_98_9_part_b_console_output.txt

πŸ“¦ Python Dependencies

Install the main dependencies:

pip install numpy scipy matplotlib

For direct RTL-SDR capture:

pip install pyrtlsdr

On Linux, RTL-SDR system libraries may also be required:

sudo apt install rtl-sdr librtlsdr-dev

πŸ§ͺ Running Part A: Analysis and Figure Generation

Part A performs IQ capture or IQ loading, FM demodulation, multiplex analysis, RDS extraction, BPSK visualization, and figure generation.

python src/part_a_rds_analysis.py

Before running, review the configuration section at the beginning of the script:

STATION_MHZ = 98.9
CAPTURE_SECONDS = 65.0
GAIN_DB = 30.0
CAPTURE_NEW = False
SAVEFIGS = True

Relevant parameters:

Parameter Description
STATION_MHZ Selected FM station frequency.
CAPTURE_SECONDS IQ capture duration.
GAIN_DB RTL-SDR gain.
CAPTURE_NEW If True, captures from RTL-SDR. If False, loads an existing capture.
SAVEFIGS Saves the generated figures.
AUTO_IQ_FILENAME Automatically names captures based on station frequency.
STRICT_STATION_MATCH Verifies that the IQ capture matches the configured station.

🧬 Running Part B: RDS Decoding

Part B processes the same IQ capture and attempts to recover valid RDS groups.

python src/part_b_rds_decoding.py

This stage reports:

  • raw detected RDS groups,
  • dominant PI,
  • consolidated groups,
  • PS segments,
  • PS by voting,
  • RadioText segments,
  • reconstructed RadioText when enough segments are available.

πŸ” Search Modes

The scripts include automatic synchronization search options so the lab is not tied to a single radio station.

SEARCH_MODE = "AUTO"

For general laboratory use, AUTO is recommended.

πŸ’Ύ IQ Capture Naming

When AUTO_IQ_FILENAME = True, the IQ capture name is generated from the selected frequency.

Example:

STATION_MHZ = 98.9

produces:

fm_rds_iq_98_9MHz.npz

This avoids accidentally analyzing an old capture from a different station.

βœ… Expected Output

A successful decoding run may produce a console summary similar to:

============================================================
GLOBAL RESULT
============================================================

Raw detected groups: XX

Detected PI count:
  0xXXXX: XX

Dominant PI: 0xXXXX
Consolidated groups: XX

PS by voting:
  segment 0: 'XX' votes=X
  segment 1: 'XX' votes=X
  segment 2: 'XX' votes=X
  segment 3: 'XX' votes=X

Consolidated PS: 'XXXXXXXX'
PS segments: [True, True, True, True]

RadioText:
  group 2A segment 0: '....'
  group 2A segment 1: '....'

Consolidated RT: 'Recovered text from the FM station'

πŸ§‘β€πŸ« Educational Use

This project can be used in courses or workshops related to:

  • analog communications,
  • digital communications,
  • software-defined radio,
  • FM demodulation,
  • stereo FM multiplexing,
  • FIR filtering,
  • spectral analysis,
  • BPSK modulation,
  • carrier recovery,
  • symbol synchronization,
  • digital frame decoding,
  • error detection and block validation.

🧰 Suggested Future Improvements

  • Refactor the processing chain into reusable Python modules.
  • Add command-line arguments with argparse.
  • Export decoded PI, PS, and RT results to JSON or CSV.
  • Add a Jupyter notebook for teaching demonstrations.
  • Include a LaTeX laboratory guide.
  • Compare multiple FM stations in a single report.
  • Add unit tests for the RDS syndrome and block validation functions.
  • Add an optional graphical interface for selecting frequency and capture settings.

πŸ“š References

⚠️ Disclaimer

This project is intended for educational and experimental purposes only.

It is provided to demonstrate signal processing concepts related to FM broadcast reception and spectrum analysis.
The author does not encourage or endorse any unauthorized or improper use of radio equipment.

Users are responsible for ensuring compliance with local laws and regulations regarding radio reception and spectrum usage.

🀝 Support projects

Support me on Patreon https://www.patreon.com/c/CrissCCL

πŸ“œ License

MIT License

About

SDR-based FM RDS decoding lab using RTL-SDR and Python DSP: FM demodulation, 57 kHz RDS extraction, BPSK recovery, RDS block validation, PI/PS decoding, and RadioText reconstruction.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages