Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tang Nano 9K FM Transmitter

An FM radio station on a $15 FPGA board: plays WAV files from a microSD card and transmits them over FM using nothing but a GPIO pin and a piece of wire. The current track shows on the little ST7789 screen; the two onboard buttons switch tracks and hop between FM frequencies.

Tang Nano 9K running the FM radio, LCD showing frequency and track

Demo videos: playing on a real FM radio · switching tracks with S1

Українська версія

How it works

  • FM carrier: a DDR DDS — the rPLL makes a 126 MHz clock, a 32-bit phase accumulator advances by 2*inc per cycle, and the IOB's ODDR primitive shifts out two phase MSBs per cycle: an effective 252 MS/s 1-bit carrier synthesizer on a plain LVCMOS33 pin. Frequency modulation is literally inc = preset + 39*sample (±75 kHz at full scale).
  • Audio: 16-bit PCM mono WAV files stream from a FAT16/FAT32 microSD card (SPI mode) through a 2 KB FIFO, paced by a fractional divider at the header's sample rate (canonical 32 kHz; 8–48 kHz works).
  • Playlist: the FPGA scans the card's root directory for *.WAV entries (up to 16), shows the 8.3 name on the LCD, auto-advances at end of track and wraps.

Hardware

Thing Details
Board Sipeed Tang Nano 9K (Gowin GW1NR-LV9QN88PC6/I5, 27 MHz)
Screen Sipeed 1.14" ST7789 240×135 SPI (plugs into the board connector)
Card microSD in the onboard TF slot, FAT16 or FAT32 (not exFAT)
Antenna ~75 cm wire (≈ λ/4 @ 100 MHz) on header J5 position 9 (FPGA pin 29) through a ~100 Ω series resistor
Radio any FM receiver

Controls: S1 = next track, S2 = cycle frequency presets (88.0 / 90.0 / 94.0 / 98.0 / 100.0 / 103.5 / 106.0 / 107.9 MHz — boots at 100.0). Full pin map: docs/pinmap.md.

Assembly tip: the back of the 1.14" LCD module is bare metal and sits right over the board — put a small piece of transparent tape on it, or it can short the USB-C pins / header pads if it flexes down.

⚠️ Legal / RF note

This is a milliwatt-class toy transmitter for personal, short-range experiments (a few meters). Still:

  • check your local regulations on unlicensed FM transmission — rules differ by country (e.g. FCC Part 15 in the US, similar low-power rules elsewhere);
  • pick a free frequency, never overlap a real station;
  • keep the antenna short and the range minimal — the 1-bit output also emits harmonics (2f, 3f) and a DDS image at 252 MHz − f (144–164 MHz, near the 2 m amateur band), so don't crank anything up;
  • the series resistor is not optional kindness, it keeps the edge rates and radiated harmonics down.

Build & flash (fully open-source toolchain)

Needs oss-cad-suite (yosys + nextpnr-himbaechel + apicula + iverilog) and openFPGALoader ≥ 1.1.x.

source env.sh              # adjust paths (or copy to env.local.sh)
make doctor                # check tools + board detection
make bit  TOP=m7_fm_radio  # synthesize + place&route + pack
make load TOP=m7_fm_radio  # load to SRAM (volatile)
make flash TOP=m7_fm_radio # or persist to SPI flash

The design was brought up as a ladder of standalone milestones, each a buildable top (make bit TOP=...): m0_hello (LEDs/UART), m1_carrier (bare FM carrier), m2_tone_fm (1 kHz test tone), m3_lcd_text (screen), m4_sd_stream (SD sector reads), m6_playlist (radio without screen), m7_fm_radio (everything). Results log: docs/bringup_log.md.

Preparing the SD card

scripts/download_tracks.sh 8              # CC-BY music by Kevin MacLeod
scripts/prepare_sd.sh /Volumes/YOUR_CARD  # convert + copy + verify

prepare_sd.sh converts anything ffmpeg can read into the canonical 16-bit/mono/32 kHz WAV the FPGA expects, names files TRACK01.WAV…, and verifies the header. Keep ATTRIBUTION.md with the music — CC-BY requires attribution.

Simulation

Everything above the pins is verified in Icarus Verilog, including the SD stack against a behavioral SPI SD-card model with generated FAT16 and FAT32 images, and the audio path bit-exactly (every PCM sample reproduced in the FM increment stream):

python3 sim/tb/make_test_images.py
make sim TB=tb_sd_player     # SD + FAT playlist, both filesystems
make sim TB=tb_audio_chain   # SD -> WAV -> FIFO -> FM, sample-exact
make sim TB=tb_dds           # DDR DDS phase/output-rate checks
make sim TB=tb_fm_mod TB=tb_pacer TB=tb_text_renderer   # units

Repo layout

rtl/tops/      milestone tops m0..m7
rtl/fm/        fm_mod (presets + deviation), dds_core (DDR DDS)
rtl/audio/     wav_parser, audio_fifo, audio_feed, sample_pacer
rtl/sd/        sd_wav_player (FAT16/32 playlist reader)
rtl/vendor/    WangXuan95 SD SPI core (GPL-3.0, documented mods)
rtl/ui/        lcd_controller (ST7789), text_renderer, font_rom, ui_ctrl
rtl/{ctrl,clocks,dbg}/  por, debounce, player_ctrl, rPLL, UART debug
constraints/   pins (audited vs schematic) + per-clock SDC
sim/           testbenches + SD-card/rPLL/ODDR models + image generator
scripts/       music download / SD prep / font ROM generator
docs/          pinmap.md, bringup_log.md

License & credits

GPL-3.0 (see LICENSE) — required by the vendored SD core.

About

FM radio station on a Tang Nano 9K FPGA: WAV playlist from microSD, DDR DDS transmitter on a GPIO pin, ST7789 track display. Fully open-source toolchain.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages