Czym jest modulacja FM?

W modulacji częstotliwościowej (FM — Frequency Modulation) informacja jest zakodowana w częstotliwości chwilowej. Gdy sygnał audio rośnie — częstotliwość nośnej rośnie powyżej nominalnej. Gdy spada — częstotliwość maleje. Amplituda nośnej pozostaje stała.

Dlaczego FM brzmi lepiej niż AM?

  • Odporność na zakłócenia — szumy atmosferyczne i interferencje wpływają na amplitudę sygnału, nie na częstotliwość. FM jest na nie naturalnie odporna
  • Efekt capture — gdy dwa nadajniki nadają na tej samej częstotliwości, odbiornik FM „chwyta" silniejszy i całkowicie tłumi słabszy. W AM oba sygnały mieszają się i powstaje kakofonia
  • Szerokie pasmo audio — 15 kHz pasma vs 5 kHz w AM. Słychać pełne brzmienie muzyki, nie tylko mowę
  • Stereo — FM broadcast obsługuje stereo dzięki pilotowi 19 kHz i składowej L−R. AM jest monofoniczne

Parametry sygnału FM broadcast

Parametry sygnału FM broadcast w Europie
Wartość
Zakres częstotliwości87,5 – 108 MHz
ModulacjaWideband FM (WFM)
Dewiacja±75 kHz
Szerokość kanału200 kHz
Pilot stereo19 kHz subcarrier
RDS57 kHz subcarrier
De-emphasis50 µs (Europa) / 75 µs (USA)

Maksymalne odchylenie częstotliwości od nominalnej to dewiacja — w europejskim FM broadcast wynosi ±75 kHz. Kanał zajmuje 200 kHz, co daje miejsce na sygnał audio, pilot stereo i dane RDS.

Widmo sygnału FM — co jest w środku

Sygnał FM broadcast to nie tylko dźwięk mono. Po demodulacji FM w paśmie podstawowym widać cztery składowe:

Widmo sygnału FM po demodulacji — mono L+R, pilot 19 kHz, stereo L−R, RDS
Wszystkie komponenty są pochodnymi pilota 19 kHz — stereo = 2×, RDS = 3×
Składowe widma FM broadcast po demodulacji
SkładowaCzęstotliwośćOpis
L+R (mono)0–15 kHzsygnał monofoniczny — kompatybilny z każdym odbiornikiem
Pilot19 kHzton referencyjny — informuje odbiornik o transmisji stereo
L−R (stereo)23–53 kHzróżnica kanałów, modulowana wokół 2×pilot = 38 kHz
RDS57 kHzdane tekstowe (nazwa stacji, PI code, typ programu), modulowane wokół 3×pilot

Dlaczego suma i różnica?

Stereo FM wprowadzono w 1961 roku, gdy miliony odbiorników monofonicznych były już w użyciu. Kompatybilność wsteczna była warunkiem koniecznym — standard nie mógł wymagać wymiany odbiorników.

Gdyby nadawać kanały lewy i prawy jako dwa osobne sygnały L(t) i R(t), odbiornik mono musiałby wybrać jeden z nich. Słuchacz usłyszałby tylko połowę dźwięku. Zamiast tego zdefiniowano dwie składowe:

M(t) = L(t) + R(t)
S(t) = L(t) - R(t)

M(t) — sygnał sumy (mono) — trafia do pasma 0–15 kHz, dokładnie tam, gdzie stary odbiornik oczekuje dźwięku. Odbiornik mono odbiera M(t) i słyszy pełny miks obu kanałów, nic nie tracąc.

S(t) — sygnał różnicy — moduluje nośną 38 kHz (DSB-SC) i zajmuje pasmo 23–53 kHz. Odbiornik mono nie ma demodulatora w tym zakresie, więc tę składową całkowicie ignoruje.

Odbiornik stereo demoduluje obie składowe i odtwarza oryginalne kanały z prostych przekształceń algebraicznych:

L(t) = \frac{M(t) + S(t)}{2} = \frac{(L+R) + (L-R)}{2}
R(t) = \frac{M(t) - S(t)}{2} = \frac{(L+R) - (L-R)}{2}

Konsekwencje tej decyzji są odczuwalne do dziś. S(t) zajmuje pasmo 23–53 kHz — wyższe częstotliwości, gdzie SNR jest gorszy niż w paśmie M(t). Po rekonstrukcji szum z pasma S(t) trafia w równych częściach do obu kanałów jako nieskorelowany szum różnicowy — przestrzenny, dobrze słyszalny. Przy słabym sygnale odbiór stereo szumi wyraźnie bardziej niż mono. Dlatego wiele odbiorników automatycznie przełącza się na mono poniżej pewnego progu SNR — traci przestrzenność, ale zyskuje czysty dźwięk.


Jak działa demodulator FM

Demodulacja FM to proces odzyskiwania sygnału audio z modulowanej fali nośnej. W SDR składa się z dwóch części: toru analogowego w sprzęcie i cyfrowego w oprogramowaniu.

Tor analogowy

Poniższy diagram pokazuje przykładowy tor analogowy z podwójną przemianą częstotliwości (superheterodyna). To klasyczna architektura — RTL-SDR i Airspy działają podobnie — ale wcale nie jedyna możliwa. Prostsze układy SDR stosują tylko jedną przemianę lub konwertują sygnał bezpośrednio do baseband (direct conversion). Różnice wpływają na poziom szumów, zakres częstotliwości i poziom artefaktów (np. DC spike w centrum pasma przy direct conversion).

graph LR
    A["Antena"] --> B["LNA\n(wzmacniacz\nniskoszumowy)"]
    B --> C["Mikser 1\n+ heterodyna 1\n→ IF1"]
    C --> D["Filtr IF1\n(ceramiczny)"]
    D --> E["Mikser 2\n+ heterodyna 2\n→ IF2 / baseband"]
    E --> F["ADC\n(8–12 bit)"]
    F --> G["Próbki IQ\ndo komputera"]

Kluczowa granica: za ADC kończy się analogowe przetwarzanie sygnału — dalej wszystko dzieje się w oprogramowaniu.

Tor cyfrowy — łańcuch demodulacji

Schemat blokowy demodulatora FM z rozdzielonymi torami I i Q, blokami różniczkowania, mnożnikami i sumatorem
Demodulator kwadraturowy FM — operacje na torach I i Q prowadzą do chwilowej częstotliwości sygnału

Łańcuch demodulacji — blok po bloku

Diagram powyżej pokazuje klasyczny demodulator kwadraturowy. Sygnał z ADC to strumień par próbek I i Q — razem tworzą sygnał zespolony s(t) = I(t) + jQ(t), w którym faza \phi(t) niesie zakodowany dźwięk.

Celem demodulatora jest odzyskanie chwilowej częstotliwości sygnału — tempa zmian fazy. Tor I i tor Q rozchodzą się na dwie ścieżki. Każdy jest osobno różniczkowany — to daje I'(t) i Q'(t). Jednocześnie oryginalne, nieróżniczkowane sygnały trafiają na skrzyż do mnożników (linie przerywane na diagramie): I'(t) jest mnożone przez Q(t), a Q'(t) przez I(t). Sumator oblicza różnicę tych iloczynów — wynik jest proporcjonalny do chwilowej częstotliwości sygnału.

Po demodulatorze sygnał zawiera cały multipleks FM — mono, pilot 19 kHz, stereo i RDS. Filtr dolnoprzepustowy 0–15 kHz przepuszcza tylko składową mono, odcinając resztę. Bez niego ton pilota 19 kHz byłby słyszalny w głośniku.

De-emphasis kompensuje preemfazę nadajnika — nadajnik celowo wzmacnia wysokie częstotliwości, żeby przebić szum. Odbiornik stosuje odwrotny filtr (τ = 50 µs w Europie, 75 µs w USA), przywracając naturalny balans brzmienia.


Odbiornik FM w GNU Radio

GNU Radio pozwala zbudować działający odbiornik FM blok po bloku — każdy element łańcucha demodulacji z poprzedniej sekcji staje się osobnym blokiem w flowgraphu.

Flowgraph — ręczna demodulacja

Zamiast gotowego bloku WBFM Receive, rozbijamy demodulację na elementy składowe — dokładnie tak, jak na diagramie powyżej:

[Osmocom Source] → [Low Pass Filter] → [Quadrature Demod] → [Low Pass Filter] → [FM Deemphasis] → [Rational Resampler] → [Audio Sink]

Pierwszy filtr LPF odcina sąsiednie kanały FM i zmniejsza sample rate (decimacja ×10). Quadrature Demod to odpowiednik mnożników i sumatora z diagramu — oblicza chwilową częstotliwość z próbek IQ. Drugi filtr LPF (0–15 kHz) przepuszcza tylko mono, odcinając pilot i stereo. De-emphasis przywraca naturalny balans częstotliwości. Resampler dopasowuje sample rate do karty dźwiękowej.

Flowgraph demodulatora FM w GNU Radio Companion

Plik projektu GNU Radio

Gotowy flowgraph do otwarcia w GNU Radio Companion — wszystkie parametry (sample rate, gain, decimacja, de-emphasis) są ustawione w pliku:

options:
  parameters:
    author: robertolechowski.com
    catch_exceptions: 'True'
    category: '[GRC Hier Blocks]'
    cmake_opt: ''
    comment: 'Demodulator FM mono — reczna demodulacja IQ'
    copyright: ''
    description: 'SDR #3 — odbior FM broadcast z rozbitym torem demodulacji'
    gen_cmake: 'On'
    gen_linking: dynamic
    generate_options: qt_gui
    hier_block_src_path: '.:'
    id: fm_demod_manual
    max_nouts: '0'
    output_language: python
    placement: (0,0)
    qt_qss_theme: ''
    realtime_scheduling: ''
    run: 'True'
    run_command: '{python} -u {filename}'
    run_options: prompt
    sizing_mode: fixed
    thread_safe_setters: ''
    title: 'FM Demod Manual (mono)'
    window_size: (1000,1000)
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [8, 8]
    rotation: 0
    state: enabled

blocks:
- name: samp_rate
  id: variable
  parameters:
    comment: ''
    value: 2.4e6
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [8, 160]
    rotation: 0
    state: enabled

- name: freq
  id: variable
  parameters:
    comment: ''
    value: 98e6
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [8, 240]
    rotation: 0
    state: enabled

- name: decimation
  id: variable
  parameters:
    comment: ''
    value: '10'
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [8, 320]
    rotation: 0
    state: enabled

- name: quad_rate
  id: variable
  parameters:
    comment: ''
    value: samp_rate / decimation
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [8, 400]
    rotation: 0
    state: enabled

- name: audio_rate
  id: variable
  parameters:
    comment: ''
    value: '48000'
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [8, 480]
    rotation: 0
    state: enabled

- name: max_dev
  id: variable
  parameters:
    comment: ''
    value: 75e3
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [8, 560]
    rotation: 0
    state: enabled

# --- Source ---
- name: osmosdr_source
  id: osmosdr_source
  parameters:
    args: rtl=0
    sample_rate: samp_rate
    freq: freq
    gain: '30'
    if_gain: '20'
    bb_gain: '20'
    ant: ''
    bandwidth: '0'
    comment: ''
    corr: '0'
    dc_offset_mode: '0'
    freq_corr: '0'
    iq_balance_mode: '0'
    maxoutbuf: '0'
    minoutbuf: '0'
    nchan: '1'
    num_mboards: '1'
    type: fc32
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [56, 700]
    rotation: 0
    state: enabled

# --- LPF kanalowy (100 kHz + decimacja x10) ---
- name: lpf_channel
  id: low_pass_filter
  parameters:
    beta: '6.76'
    comment: 'LPF kanalowy — odcina sasiednie kanaly FM'
    cutoff_freq: 100e3
    decim: decimation
    gain: '1'
    interp: '1'
    maxoutbuf: '0'
    minoutbuf: '0'
    samp_rate: samp_rate
    type: fir_filter_ccf
    width: 25e3
    win: window.WIN_HAMMING
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [320, 700]
    rotation: 0
    state: enabled

# --- Quadrature Demod ---
- name: quad_demod
  id: analog_quadrature_demod_cf
  parameters:
    comment: 'Demodulator kwadraturowy — serce toru FM'
    gain: quad_rate / (2 * math.pi * max_dev)
    maxoutbuf: '0'
    minoutbuf: '0'
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [584, 740]
    rotation: 0
    state: enabled

# --- LPF audio (0-15 kHz) ---
- name: lpf_audio
  id: low_pass_filter
  parameters:
    beta: '6.76'
    comment: 'LPF audio — przepuszcza mono 0-15 kHz, odcina pilot i stereo'
    cutoff_freq: 15e3
    decim: '1'
    gain: '1'
    interp: '1'
    maxoutbuf: '0'
    minoutbuf: '0'
    samp_rate: quad_rate
    type: fir_filter_fff
    width: 3e3
    win: window.WIN_HAMMING
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [56, 900]
    rotation: 0
    state: enabled

# --- FM De-emphasis ---
- name: fm_deemph
  id: analog_fm_deemph
  parameters:
    comment: 'De-emphasis tau=50us (Europa)'
    maxoutbuf: '0'
    minoutbuf: '0'
    samp_rate: quad_rate
    tau: 50e-6
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [320, 920]
    rotation: 0
    state: enabled

# --- Rational Resampler (240k -> 48k = /5) ---
- name: resampler
  id: rational_resampler_xxx
  parameters:
    comment: 'Resampler 240 kHz -> 48 kHz'
    decim: '5'
    fbw: '0'
    interp: '1'
    maxoutbuf: '0'
    minoutbuf: '0'
    taps: '[]'
    type: fff
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [584, 920]
    rotation: 0
    state: enabled

# --- Audio Sink ---
- name: audio_sink
  id: audio_sink
  parameters:
    comment: ''
    device_name: ''
    num_inputs: '1'
    ok_to_block: 'True'
    samp_rate: audio_rate
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [800, 932]
    rotation: 0
    state: enabled

connections:
- [osmosdr_source, '0', lpf_channel, '0']
- [lpf_channel, '0', quad_demod, '0']
- [quad_demod, '0', lpf_audio, '0']
- [lpf_audio, '0', fm_deemph, '0']
- [fm_deemph, '0', resampler, '0']
- [resampler, '0', audio_sink, '0']

metadata:
  file_format: 1
  grc_version: 3.10.9.2

Uruchomienie:

gnuradio-companion sdr_03_img/fm_demod_manual.grc
# lub wygeneruj skrypt i uruchom:
grcc fm_demod_manual.grc && python3 fm_demod_manual.py