RTL-SDR dongel · üks antenn aknal · kõik, mis eetris liigub

2026-09-18 FM 88–108

RDS: SDR++ vaikis, kirjutasin oma dekoodri

#rds #python #numpy #dsp #fm

Iga korralik FM-jaam saadab lisaks helile ka RDS-i – 1187,5 bitti sekundis 57 kHz alamkandjal, kus on jaama nimi (PS), tekst (RT), programmitüüp ja PI-kood. See on see, mille pärast autoraadio näitab "VIKER". Ma tahtsin sama.

SDR++ proovis, ei saanud

SDR++-il on Decode RDS linnuke. Panin sisse, ootasin. Tulemus: PI-kood hüppas 0x3BE6 → 0x3ECD → 0xA87B, konstellatsioonipilt oli ühtlane sinine pall, mitte kaks eraldi punktipilve. Ei lukustunud üldse, ka gaini tõstmine ei aidanud.

Miks? Heli oli ju puhas, SNR ~23 dB. Aga RDS-alamkandja on põhihelist umbes 25 dB nõrgem. See, mis heli jaoks on "väga hea", on RDS-i jaoks "vaevu". SDR++ dekooder töötab bit-haaval reaalajas ja loobub, kui üksik plokk on vigane. Mul oli vaja midagi, mis integreerib – kogub 10 sekundi jagu kordusi ja hääletab.

Toores IQ, mitte heli

Esimene otsus: salvestada mitte heli, vaid toorest IQ-d. SDR++ Recorder oskab seda (Baseband režiim): 2,4 MSPS, 16-bit I/Q, 10 s = 98 MB WAV. Selle sees on kõik, mis 2,4 MHz laiuses aknas eetris oli – mitte ainult üks jaam, vaid kõik ±1,1 MHz ulatuses. Ühest failist saab spektri, heli ja RDS-i, ja seda saab uuesti töödelda nii palju kordi kui tahes.

Rigctl AOS alustab salvestust, LOS lõpetab. capture.ps1 -MHz 101.6 -Seconds 10.

Ahel

iq.py, ainult numpy. (scipy oleks olnud mugavam, aga Windowsi Smart App Control blokeerib selle allkirjastamata DLL-id. Numpy on allkirjastatud. Elu.)

  1. Kanali väljalõikamine – FFT kogu 24,5M sämplist, võta bin'id jaama ümber ±120 kHz, IFFT → 240 kHz kompleksne signaal. Sagedusruumi dekimeerimine: täpne, ilma FIR-filtrit disainimata.
  2. FM-demodangle(x[n] · conj(x[n-1])) → MPX-signaal hertsides.
  3. Pilot – 19 kHz ± 50 Hz FFT-maskiga välja, analüütiline signaal, faas. RDS-kandja on täpselt 3× pilot, seega carrier = (pilot/|pilot|)³.
  4. RDS riba – 57 kHz ± 2,4 kHz välja, korruta kandja kaaskompleksiga → BPSK nullis.
  5. Telg – BPSK võib olla ükskõik mis nurga all; angle(mean(z²))/2 ütleb nurga, pööra tagasi reaalteljele.
  6. Sobitatud filter – bifaasi impulss (+pool, −pool bitti), FFT-konvolutsioon.
  7. Sümbolikell – proovi kõiki algfaase 0…T, vali see, kus sämplipunktide energia on suurim. Ja proovi ka T/2 nihkega, sest bifaasil on see kahemõtteline.
  8. Diferentsiaaldekoodd[n] = t[n] XOR t[n-1]. Kaotab polaarsuse ambivalentsuse.
  9. Plokisünkro – RDS-plokk on 26 bitti: 16 andme + 10 kontrollbitti. Kontrollsõna on CRC (polünoom x¹⁰+x⁸+x⁷+x⁵+x⁴+x³+1) XOR offset-sõnaga A/B/C/C'/D. Arvutan sündroomi iga bitipositsiooni jaoks ja otsin kohti, kus A-le järgneb 26 bitti hiljem B. Enim hääli saanud faas (mod 104) on grupi algus.
  10. Hääletus – PS-nimi tuleb 2 tähte korraga (grupp 0, 4 segmenti), RT 4 tähte korraga (grupp 2A, 16 segmenti). Iga positsiooni kohta loen, mis täht kõige rohkem kordi tuli. Kui 10 sekundi jooksul tuleb "R" viis korda ja "Q" korra, on see R.

Sündroomi arvutus on lineaarne, seega tegin tabeli 26 väärtusega ja kogu 12 000-bitise salvestise sündroomid tulevad numpy'ga korraga:

def block_offsets(bits):
    n = len(bits) - 26
    words = np.zeros(n, dtype=np.int64)
    syn = np.zeros(n, dtype=np.int64)
    for j in range(26):
        b = bits[j:j + n].astype(np.int64)
        words |= b << (25 - j)
        syn ^= b * SYN[25 - j]
    return words, [OFFSETS.get(int(s)) for s in syn]

Tulemus

Esimene katse, sama 101.6 salvestis, millel SDR++ ei lukustunud:

RDS: 33% plokke kehtivad (151/464), silm 1.55
PI 22E2 (kindlus 100%)  PTY Pop Music
PS  'RAADIO 2'
RT  '????op 15 ??Ki??e Antsm?e'

33% plokke ja nimi on täielik. RT on lünklik (10 s on 64-täheline tekst vaevu korra läbi), aga "Top 15" ja saatejuhi nimi on ära tuntavad.

Siis käisin kogu ala läbi – 9 akent, 10 s igaüks, iga aken skaneeritakse ja RDS-i proovitakse igal jaamal üle 12 dB:

MHz PI PS RT plokke OK
88.8 231E Rock FM "…kr??si- ja ??ustamis…" 50 %
91.5 2322 ????R "…õhus on Eestit" → Raadio Elmar 23 %
97.8 23EB RE????FMRetro FM 9 %
101.6 22E2 RAADIO 2 "Top 15 …" 33 %
104.1 22E1 VIKER "Uudised" 46 %
106.6 22E3 KLASSIKA "Uudised" 98 %

PI-koodi esimene number on riik – 2 on Eesti. 22Ex on ERR-i jaamad järjest: Viker E1, Raadio 2 E2, Klassika E3. Erakanalid on 23xx.

Kaks helinäidet samast loendusest, otse IQ-st demoduleeritud (10 s, mono, de-emphasis 50 µs):

Vikerraadio 104.1 – uudised

Klassikaraadio 106.6 – uudised

See, kus ma eksisin

Eelmises postituses ütlesin, et 104.1 ja 106.6 peal on "kitsad piigid, dongli spuurid". Need olid Vikerraadio ja Klassikaraadio. Jutusaade ja klassikaline muusika moduleerivad kandjat vähe – spekter jääb kitsaks ja teravaks, mitte laiaks küüruks nagu kompressoriga popmuusika.

Spekter 104 MHz: Vikerraadio kitsas piik, mida ma spuuriks pidasin

Pilt valetas. Numbrid ei valetanud. iq.py --scan ütles 104.1 SNR-iks 21,5 dB, tugevaim jaam terves akna.

Mis edasi

  • Veaparandus. RDS-kood parandab kuni 5-bitiseid purskeid; ma praegu lihtsalt viskan vigased plokid ära. Retro FM 9% → tõenäoliselt 30%+.
  • Pikemad salvestised RT jaoks – 30 s annab teksti 3× läbi.
  • Parem antenn. Väike teleskoop toas on see, mis see on. Dipool aknal peaks andma 6–10 dB.
  • Ja siis lennuside, 118–137 MHz, AM. Sellest saab teine lugu.

Kood: iq.py. Kui sul on RTL-SDR ja Python, siis capture.ps1 + iq.py peaks töötama ka sinu aknal.

73, Tõnu

Kommentaarid

Laen…