To build a reliable Raspberry Pi FM receiver, you need an I2C tuner module like the TEA5767 wired to the Pi's GPIO header, controlled via Python's smbus2 library to calculate and write the PLL tuning registers. Unlike USB software-defined radios that hog CPU cycles, a dedicated hardware tuner offloads the demodulation, outputting clean analog audio directly from the breakout board.

Hardware Decision Path: Which FM Receiver Module?

Before ordering parts, you must match the receiver module to your project's end goal. The embedded market offers three primary paths for adding FM reception to a Raspberry Pi. Use this decision matrix to select the right hardware.

Module / Hardware Interface Best For Drawbacks
RTL-SDR V3 Dongle USB 2.0 Spectrum analysis, decoding RDS/RBDS, wideband scanning (24MHz - 1.7GHz). High CPU usage for software demodulation; requires external rtl_fm pipeline.
Si4703 Breakout I2C / SPI Projects requiring RDS (Radio Data System) text parsing and high sensitivity. More expensive (~$15); complex 16-bit register mapping; 3.3V strict logic.
TEA5767 Module I2C Simple bench audio projects, basic I2C learning, low-cost headless radios. No RDS support; analog audio out requires external amplification for loud playback.
Concrete Pick: For this build, we are using the Generic TEA5767 I2C FM Radio Module. It terminates the decision path for 90% of hobbyist audio projects because it costs under $3, requires only four wires, and handles the FM demodulation in silicon, leaving the Pi's CPU free for MQTT, web interfaces, or home automation logic.

Spec Sheet and I2C Pin Mapping

The TEA5767 operates on a 5-byte write sequence to set the Phase-Locked Loop (PLL) frequency synthesizer. The target board for the code below is the Raspberry Pi 4 Model B (4GB RAM) running Raspberry Pi OS (Bookworm, 64-bit). We will use the primary I2C bus (Bus 1).

Wiring Table

Raspberry Pi 4 Pin GPIO / Function TEA5767 Module Pin Notes
Pin 1 3.3V Power VCC The TEA5767 chip accepts 2.5V-5V. Using 3.3V protects the Pi's I2C pull-ups from 5V backfeed.
Pin 6 Ground GND Keep ground wires short to minimize analog noise floor.
Pin 3 GPIO 2 (SDA1) SDA Hardware I2C data line. Do not add external pull-ups; the Pi has 1.8kΩ onboard.
Pin 5 GPIO 3 (SCL1) SCL Hardware I2C clock line. Default speed is 100kHz, sufficient for this module.

Antenna Note: The module includes a 3.5mm headphone jack for audio out and a dedicated ANT pad. Solder a 75cm (approx. 29.5 inches) piece of 22 AWG stranded copper wire to the ANT pad. This length acts as a quarter-wave monopole for the center of the 88-108 MHz FM broadcast band.

Wiring Steps and Python Tuning Code

Follow these steps to configure the OS and deploy the tuning script. We use the smbus2 library for reliable I2C block writes.

  1. Enable I2C: Open the terminal and run sudo raspi-config. Navigate to Interface Options > I2C and enable it. Reboot the Pi.
  2. Verify Hardware: Run sudo i2cdetect -y 1. You should see 60 in the grid. This is the hardcoded I2C address of the TEA5767.
  3. Install Dependencies: Run pip3 install smbus2 in your virtual environment.
  4. Deploy Code: Save the following script as fm_radio.py.
import smbus2
import time
import sys

# Target Board: Raspberry Pi 4 Model B (I2C Bus 1)
I2C_BUS = 1
TEA5767_ADDR = 0x60

def calculate_pll(freq_mhz):
    """
    Calculates the PLL word for High-Side Injection.
    Formula derived from NXP TEA5767 datasheet.
    """
    freq_hz = freq_mhz * 1000000
    intermediate_freq = 225000  # 225 kHz IF
    ref_freq = 32768            # 32.768 kHz crystal reference
    
    # High-side injection multiplier is 4
    pll = int(4 * (freq_hz + intermediate_freq) / ref_freq)
    return pll

def set_frequency(bus, freq_mhz):
    pll = calculate_pll(freq_mhz)
    pll_high = (pll >> 8) & 0xFF
    pll_low = pll & 0xFF
    
    # Byte 3: 0x90 -> High-side injection, Stereo mode, PLL enabled
    # Byte 4: 0x11 -> Crystal ref (32.768kHz), Soft mute off, SNC on
    # Byte 5: 0x00 -> Standby off, port configurations
    data = [pll_high, pll_low, 0x90, 0x11, 0x00]
    
    try:
        # smbus2 write_i2c_block_data sends the first byte as 'cmd' and the rest as 'vals'
        bus.write_i2c_block_data(TEA5767_ADDR, data[0], data[1:])
        print(f"Successfully tuned to {freq_mhz} MHz")
    except OSError as e:
        print(f"I2C Communication Failed: {e}")
        sys.exit(1)

if __name__ == "__main__":
    try:
        bus = smbus2.SMBus(I2C_BUS)
    except FileNotFoundError as e:
        print(f"Bus Init Error: {e}")
        print("Fix: I2C bus not found. Ensure I2C is enabled via raspi-config.")
        sys.exit(1)
        
    # Hardcoded target: 97.3 MHz (Replace with your local station)
    target_freq = 97.3 
    set_frequency(bus, target_freq)
    
    print("Radio active. Audio output is on the module's 3.5mm jack.")
    print("Press Ctrl+C to exit.")
    
    try:
        while True:
            time.sleep(1) # Keep script alive for monitoring
    except KeyboardInterrupt:
        print("\nRadio script terminated.")

Debugging: First Three Things to Check When It Fails

I2C is notoriously unforgiving of marginal connections. If your script crashes, do not guess. Match the exact Python traceback to the ranked causes below.

1. The 'No such file' Bus Error

Exact Error String: FileNotFoundError: [Errno 2] No such file or directory: '/dev/i2c-1'

  • Cause A (Most Likely): I2C is disabled in the OS. Fix: Run sudo raspi-config and enable the I2C interface, then reboot.
  • Cause B: You are running on a compute module or older Pi Zero where the primary bus maps to /dev/i2c-0. Fix: Change I2C_BUS = 1 to 0 in the script.

2. The 'Remote I/O' NACK Error

Exact Error String: OSError: [Errno 121] Remote I/O error

  • Cause A (Most Likely): The TEA5767 is not acknowledging its address. Fix: Run i2cdetect -y 1. If the grid is empty, check your SDA/SCL wiring. Ensure you haven't swapped them.
  • Cause B: Insufficient power to the module causing brownouts during the PLL charge pump write. Fix: Move the VCC connection from Pi Pin 1 (3.3V) to Pi Pin 2 (5V), but only if your specific breakout board has an onboard 3.3V LDO regulator for the I2C lines. If it lacks an LDO, feeding 5V directly will fry the Pi's GPIO. Stick to 3.3V and verify your USB power supply can deliver 3A.

3. The 'Device Busy' Lock Error

Exact Error String: OSError: [Errno 16] Device or resource busy

  • Cause A: Another process (like a background MQTT radio script or an i2cdetect loop) has an open file handle to /dev/i2c-1. Fix: Run sudo fuser /dev/i2c-1 to find the PID, then kill -9 [PID].
Bench Warning: Never hot-swap the I2C wires while the Pi is powered. The TEA5767 lacks ESD protection on its data lines. Plugging in SDA while VCC is disconnected can forward-bias the internal protection diodes and latch up the Pi's I2C controller, requiring a full power cycle to reset.

Extending and Simplifying the Build

Once you have clean I2C communication and audio output, you must decide how to package the receiver for its final environment.

How to Extend (Add Amplification and Control)

The TEA5767 analog audio out is roughly 50mV RMS—enough to drive high-impedance headphones, but far too weak for standard 8-ohm speakers.
The Fix: Wire a PAM8403 3W Stereo Class-D Amplifier board ($2) to the TEA5767's 3.5mm jack. Feed the PAM8403 from the Pi's 5V rail (Pin 2). To add physical tuning, wire a KY-040 Rotary Encoder to GPIO 17 and 27, using the gpiozero library's RotaryEncoder class to increment or decrement the target_freq variable in 0.1 MHz steps.

How to Simplify (Headless MQTT Node)

If you are integrating this into a home automation setup and don't need physical knobs, strip the build down to a headless node.
The Fix: Remove the while True loop. Write the frequency once, then let the script exit. The TEA5767 is a stateful hardware synthesizer; it will hold the PLL lock and continue outputting audio as long as VCC is applied, even if the Python script terminates and the Pi's CPU goes to sleep. Trigger the script via an MQTT payload using os.system('python3 fm_radio.py 101.1') from your home automation broker.