Showing posts with label fm radio. Show all posts
Showing posts with label fm radio. Show all posts

Redsea 0.7, a lightweight RDS decoder

I've written about redsea, my RDS decoder project, many times before. It has changed a lot lately; it even has a version number, 0.7.6 as of this writing. What follows is a summary of its current state and possible future developments.

Input formats

Redsea can decode several types of data streams. The command-line switches to activate these can be found in the readme.

Its main use, perhaps, is to demodulate an FM multiplex carrier, as received using a cheap rtl-sdr radio dongle and demodulated using rtl_fm. The multiplex is an FM demodulated signal sampled at 171 kHz, a convenient multiple of the RDS data rate (1187.5 bps) and the subcarrier frequency (57 kHz). There's a convenience shell script that starts both redsea and the rtl_fm receiver. For example, ./rtl-rx.sh -f 88.0M would start reception on 88.0 MHz.

It can also decode an "ASCII binary" stream (--input-ascii):

0001100100111001000101110000101110011000010010110010011001000000100001
1010010000011010110100010000000100000001101110000100010111000010111001
1001000010110000111111011101101011001010101110100011111101000011100010
100000011010010001011100001

Or hex-encoded RDS groups one per line (--input-hex), which is the format used by RDS Spy:

6201 01D8 E704 594C
6201 01D9 2217 4520
6201 E1C1 594C 6202
6201 01DA 1139 594B
6201 21DC 2020 2020

Output formats

The default output has changed drastically. There used to be no strict format to it, rather it was just a human-readable terminal display. This sort of output format will probably return at some point, as an option. But currently redsea outputs line-delimited JSON, where every group is a JSON object on a separate line. It is quite verbose but machine readable and well-suited for post-processing:

{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"alt_freqs":[87.9,88.5,89.2,89.5,89.8,90.9,93.2],"ps":"YLE YK
SI"}
{"pi":"0x6201","group":"14A","tp":false,"prog_type":"Serious classical","other_
network":{"pi":"0x6205","tp":false,"has_linkage":false}}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YL      "}
{"pi":"0x6201","group":"2A","tp":false,"prog_type":"Serious classical","partial
_radiotext":"Yöklassinen."}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YLE     "}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YLE YK  "}
{"pi":"0x6201","group":"2A","tp":false,"prog_type":"Serious classical","partial
_radiotext":"Yöklassinen."}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"alt_freqs":[87.9,88.5,89.2,89.5,89.8,90.9,93.2],"ps":"YLE YK
SI"}

Someone on GitHub hinted about jq, a command-line tool that can color and filter JSON, among other things:

> ./rtl-rx.sh -f 87.9M | jq -c
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YL      "}
{"pi":"0x6201","group":"14A","tp":false,"prog_type":"Serious classical","other_
network":{"pi":"0x6202","tp":false}}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YLE     "}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YLE YK  "}
{"pi":"0x6201","group":"1A","tp":false,"prog_type":"Serious classical","prog_it
em_started":{"day":9,"time":"23:10"},"has_linkage":false}
^C

> ./rtl-rx.sh -f 87.9M | grep "\"radiotext\"" | jq ".radiotext"
"Yöklassinen."
"Yöklassinen."
"Yöklassinen."
"Yöklassinen."
"Yöklassinen."
"Yöklassinen."
"Yöklassinen."

The output can be timestamped using the ts utility from moreutils.

Additionally, redsea can output hex-endoded groups, the same format mentioned above.

Fast and lightweight

I've made an effort to make redsea fast and lightweight, so that it could be run real-time on cheap single-board computers like the Raspberry Pi 1. I rewrote it in C++ and chose liquid-dsp as the DSP library, which seems to work very well for the purpose.

Redsea now uses around 40% CPU on the Pi 1. Enough cycles will be left for the FM receiver, rtl_fm, which has a similar CPU demand. On my laptop, redsea has negligible CPU usage (0.9% of a single core). Redsea only runs a single thread and takes up 1500 kilobytes of memory.

Sensitivity

I've gotten several reports that redsea requires a stronger signal than other RDS decoders. This has been improved in recent versions, but I think it still has problems with even many local stations.

Let's examine how a couple of test signals go through the demodulator in Subcarrier::​demodulateMoreBits() and list possible problems. The test signals shall be called the good one (blue) and the noisy one (magenta). They were recorded on different channels using different antenna setups. Here are their average demodulated power spectra:

[Image: Spectrum plots of the two signals superimposed.]

The noise floor around the RDS subcarrier is roughly 23 dB higher in the noisy signal. Redsea recovers 99.9 % of transmitted blocks from the good signal and 60.1 % from the noisy one.

Below, redsea locks onto our good-quality signal. Time is in seconds.

[Image: A graph of several signal properties against time.]

Out of the noisy signal, redsea could recover a majority of blocks as well, even though the PLL and constellations are all over the place:

[Image: A graph of several signal properties against time.]

1) PLL

There's some jitter in the 57 kHz PLL, especially pronounced when the signal is noisy. One would expect a PLL to slowly converge on a frequency, but instead it just fluctuates around it. The PLL is from the liquid-dsp library (internal PLL of the NCO object).

  • Is this an issue?
  • What could affect this? Loop filter bandwidth?
  • What about the gain, i.e. the multiplier applied to the phase error?

2) Symbol synchronizer

  • Is liquid's symbol synchronizer being used correctly?
  • What should be the correct values for bandwidth, delay, excess bandwidth factor?
  • Do we really need a separate PLL and symbol synchronizer? Couldn't they be combined somehow? Afterall, the PLL already gives us a multiple of the symbol speed (57,000 / 48 = 1187.5).

3) Pilot tone

The PLL could potentially be made to lock onto the pilot tone instead. It would yield a much higher SNR.

  • According to the specs, the RDS subcarrier is phase-locked to the pilot, but can we trust this? Also, the phase difference is not defined in the standard.
  • What about mono stations with no pilot tone?
  • Perhaps a command-line option?
See redsea wiki for discussion.

4) rtl_fm

  • Are the parameters for rtl_fm (gain, filter) optimal?
  • Is there a poor-quality resampling phase somewhere, such as the one mentioned in the rtl_fm guide? Probably not, since we don't specify -r
  • Is the bandwidth (171 kHz) right?

Other features (perhaps you can help!)

Besides the basic RDS features (program service name, radiotext, etc.) redsea can decode some Open Data applications as well. It receives traffic messages from the TMC service and prints them in English. These are partially encrypted in some areas. It can also decode RadioText+, a service used in some parts of Germany to transmit such information as artist/title tags, studio hotline numbers and web links.

If there's an interesting service in your area you'd like redsea to support, please tell me! I've heard eRT (Enhanced RadioText) being in use somewhere in the world, and RASANT is used to send DGPS corrections in Germany, but I haven't seen any good data on those.

A minute or two of example data would be helpful; you can get hex output by adding the -x switch to the redsea command in rtl-rx.sh.

Receiving RDS with the RTL-SDR

redsea is a command-line RDS decoder. I originally wrote it as a script to decode RDS from demultiplexed FM stereo sound. Later I've experimented with other ways to read the bits, and the latest addition is to support the RTL-SDR television receiver via the rtl_fm tool.

Redsea is on GitHub. It has minimal dependencies (perl core modules, C standard library, rtl-sdr command-line tools) and has been tested to work on OSX and Linux with good enough FM reception. All test results, ideas, and pull requests are welcome.

Update 12/2016: Redsea has seen a lot of development since this post was written; see Redsea 0.7, a lightweight RDS decoder.

What it says

The program prints out decoded RDS groups, one group per line. Each group will contain a PI code identifying the station plus varying other data, depending on the group type. The below picture explains the types of data you'll probably most often encounter.

[Image: Screenshot of textual output from redsea, with some parts explained.]

A more verbose output can be enabled with the -l option (it contains the same information though). The -t option prefixes all groups with an ISO timestamp.

How it works

The DSP side of my program, named rtl_redsea, is written in C99. It's a synchronous DBPSK receiver that first bandpass filters ① the multiplex signal. A PLL locks onto the 19 kHz stereo pilot tone; its third harmonic (57 kHz) is used to regenerate the RDS subcarrier. Dividing it by 16 also gives us the 1187.5 Hz clock frequency. Phase offsets of these derived signals are adjusted separately.

[Image: Oscillograms illustrating how the RDS subcarrier is gradually processed in redsea and finally reduced to a series of 1's and 0's.]

The local 57 kHz carrier is synchronized so that the constellation lines up on the real axis, so we can work on the real part only ②. Biphase symbols are multiplied by the square-wave clock and integrated ③ over a clock period, and then dumped into a delta decoder ④, which outputs the binary data as bit strings into stdout ⑤.

Signal quality is estimated a couple of times per second by counting the number of "suspicious" integrated biphase symbols, i.e. symbols with halves of opposite signs. The symbols are being sampled with a 180° phase shift as well, and we can switch to that stream if it seems to produce better results.

This low-throughput binary string data is then handled by redsea.pl via a pipe. Synchronization and error detection/correction happens there, as well as decoding. Group data is then displayed on the terminal, in semi-human-readable form.

Future

My ultimate goal is to have a tool useful for FM DX, i.e. pretty good noise resistance.

Decoding radio-controlled bus stop displays

In the previous post I told about the 16 kbps data stream on FM broadcast frequencies, and my suspicion that it's being used by the bus and tram stop display system here in Helsinki. Now it's time to find out the truth.

I had the opportunity to observe a display stuck in the middle of its bootup sequence, displaying a version string. This revealed that the system is called IBus and it's made by the Swedish company Axentia. Sure enough, their website talks about DARC and how it requires no return channel, making it possible to use battery-powered displays in remote areas.

[Image: Photo of a liquid crystal display showing the text: 8589 IBUS 6.5.70d]

Not much else is said about the system, though; there are no specs for the proprietary protocol. So I implemented the five-layer DARC protocol stack in Perl and was left with a stream of fully error-corrected packets on top of Layer 5, separated into hundreds of subchannels. Some of these contained human-readable ISO-8859-1 strings with names of terminal stations. They seemed like an easy starting point for reverse engineering.

Here's one such packet after dissection:

[Image: An infographic titled 'Bus & Destination Packet', showing the hex bytes of a 64-byte packet, divided into fields that are labeled according to their apparent purpose. There are counters, size fields, the bus stop identifier, bus line identifiers, and apparent references to other types of packets. Several fields containing Latin-1 text are also transcribed. The text in them reads '23N RUSKEASUO BRUNAKÄRR'.]

Each display seems to store in its memory a list of buses that can be expected to pass the stop, along with their destinations in both official languages. The above "bus & destination packets" are used to update the memory. This is done once a day for each display on a narrow-band subchannel, so that updating all the displays takes the whole day. The mapping of the bus stop ID to actual bus stops is not straightforward, and had to be guessed from the lists of buses, on a case-by-case basis.

A different kind of packet updates the remaining waiting time for each bus in minutes. This "minutes packet" is sent three times per minute for every display in the system.

[Image: An infographic titled 'Minutes Packet', showing another type of labeled packet. Most fields are one byte long and they contain the number of minutes until the next bus arrives, in 7 bits, and one bit telling whether this estimate is based on positioning or time tables. Information about which number belongs to which bus is contained in the references from other types of packets.]

These packets may contain waiting times for several displays using the same subchannel; the "bus & destination packet" contains an address to the correct minutes slot. (The subchannel address is signaled on a lower protocol layer.) A special flag is used to signify an unused slot; another flag indicates that the bus has no working GPS and that the time is an approximation based on timetables. This causes a tilde "~" to appear before the minutes field. This means all calculation is done centrally and the displays only show numbers they hear on the radio.

There's also an announcement packet that's used to sent text messages to the displays. Often these will be about traffic disruptions and diverted routes. It's up to the displays to decide how to display the message; in the low-end battery-powered ones, the actual minutes function is (annoyingly) blanked for the duration of the slowly scrolling message.

I have yet to figure out the meaning of some packet types with non-changing data.

A special subchannel contains test packets with messages such as "Tämä on Mono-Axentia-testiä... Toimiiko tää ees..." and "Määritykset ja tiedotteet tehty vain Monolla - ei mitään IBus:lla" suggesting that they're planning to migrate from IBus to something called Mono. Interestingly, there's also a repeating test message in German – "Bus 61 nach Flughafen aus Haltestelle 1".

What good is it, you ask? Well, who wouldn't want a personal display repeater at home, telling when it's time to go?

[Image: Photo of a small liquid crystal display kit with its PCB showing, obviously home-soldered to a bunch of wires, and displaying the text: '72 TAPANILA ~12'.]

Broadcast messages on the DARC side

By now I've rambled quite a lot about RDS, the data subcarrier on the third harmonic of the 19 kHz FM stereo pilot tone. And we know the second harmonic carries the stereo information. But is there something beyond that?

Using a wideband FM receiver, like an RTL-SDR, I can plot the whole demodulated spectrum of any station, say, from baseband to 90 kHz. Most often there is nothing but silence above the third pilot harmonic (denoted 3f here), which is the RDS subcarrier. But two local stations have this kind of a peculiar spectrogram:

[Image: A spectrogram showing a signal at the audible frequency range, labeled 'mono', and four carriers centered at 19, 38, 57, and 76 kHz, labeled pilot, 2f, 3f, and 4f, respectively. Pilot is a pure sinusoid; 2f and 3f are several kHz wide signals with mirrored sidebands; and 4f is 20 kHz wide and resembles wideband noise.]

There seems to be lots of more data on a pretty wide band centered on the fourth harmonic (4f)!

As so often with mysterious persistent signals, I got a lead by googling for the center frequency (76 kHz). So, meet the imaginatively named Data Radio Channel, or DARC for short. This 16,000 bps data stream uses level-controlled minimum-shift keying (L-MSK), which can be thought of as a kind of offset-quadrature phase-shift keying (O-QPSK) where consecutive bits are sent alternating between the in-phase and quadrature channels.

To find out more, I'll need to detect the signal first. Detecting L-MSK coherently is non-trivial ("tricky") for a hobbyist like me. So I'm going to cheat a little and treat the signal as continuous-phase but non-coherent binary FSK instead. This should give us good data, even though the bit error probability will be suboptimally high. I'll use a band-pass filter first; then a splitter filter to split the signal band into mark and space; detect both envelopes and calculate difference; lowpass filter with a cutoff at the bitrate; recover bit timing and synchronize using a PLL; and now we can just take the bits out by hard decision at bit middle points.

Oh yeah, we have a bitstream!

[Image: Three oscillograms followed by a stream of 1s and 0s. The first oscillogram is quite nondescript; the second one actually shows two waveforms, red and blue, in the same graph, with the red dominating in envelope power where blue is suppressed and vice versa. The third oscillograms shows a graph apparently following their envelope power difference, with sample points at regular intervals. The sign of this plot at sample points dictates whether a 0 or 1 is shown below that sample.]

Decoder software for DARC does not exist, according to search engines. To read the actual data frames, I'll have to acquire block synchronization, regenerate the pseudorandom scrambler used and reverse its action, check the data against CRCs for errors, and implement the stack of layers that make up the protocol. The DARC specification itself is luckily public, albeit not very helpfully written; in contrast to the RDS standard, it contains almost no example circuits, data, or calculations. So, a little recap of Galois field mathematics and linear feedback shift registers for me.

But what does it all mean? And who's listening to it?

Now, it's no secret (Signaali 3/2011, p. 16) that the HSL bus stop timetable displays in Helsinki get their information about the next arriving GPS positioned buses through this very FM station, YLE 1. That's the only thing they're telling though. I haven't found anything bus-related in the RDS data, so it's quite likely that they're listening to DARC.

[Image: Photo of a rugged LCD display mounted on a metal pole, displaying the text '55K FORSBY ~6' and stamped with the logo 'HSL HRT'.]

DARC is known to be used in various other applications as well, such as DGPS correction signals. So decoding it could prove interesting.

Sniffing the packet types being sent, it seems that quite a big part of the time is being used to send a transmit cycle schedule (COT and SCOT). And indeed, the aforementioned magazine article hints that the battery-powered displays use a precise radio schedule to save power, receiving only during a short period every minute each. (This grep only lists metadata type packets, not the actual data.)

$ perl darcdec-filters.pl | grep Type: | sort | uniq -c
  88 Type: 0 Channel Organization Table (COT)
   8 Type: 1 Alternative Frequency Table (AFT)
   8 Type: 2 Service Alternative Frequency Table (SAFT)
   1 Type: 4 Service Name Table (SNT)
   8 Type: 5 Time and Date Table (TDT)
 112 Type: 6 Synchronous Channel Organization Table (SCOT)
$ █

At the moment I'm only getting sensible data out at the very beginning of each packet (or "block"). I do get a solid, error-free block sync but, for example, the date is consistently showing the year 1922 and all CRCs fail. Other fields are similarly weird but consistent. This could mean that I've still got the descrambler PN polynomial wrong, only succeeding when it's using bits from the initialization seed. And this can only mean many more sleepless coding nights ahead.

(Continued in the next post)

(Pseudotags for Finns: HSL:n pysäkkinäytöt eli aikataulunäytöt.)

Update 1/2019: There's now a basic DARC decoder on GitHub, it's called darc2json.

A determined 'hacker' decrypts RDS-TMC

As told in a previous post, I like to watch the RDS-TMC traffic messages every now and then, just for fun. Even though I've never had a car. Actually I haven't done it for years now, but thought I'd share with you the joy of solving the enigma.[disclaimer 1]

RDS-TMC is used in car navigators to inform the driver about traffic jams, roadworks and urgent stuff like that. It's being broadcast on a subcarrier of a public radio FM transmission. It's encrypted in many countries, including mine, so that it could be monetized by selling the encryption keys.

A draft of the encryption standard, namely ISO/DIS 14819-6, is freely available online. Here's an excerpt[disclaimer 2] that reads blatantly like a challenge:

"After calling for candidate proposals [for a method of encryption], the submission from Deutsche Telekom was judged by an expert panel to have best met the pre-determined criteria the task force had established. The method encrypts the sixteen bits that form the Location element in each RDS-TMC message to render the message virtually useless without decryption. The encryption is only 'light' but was adjudged to be adequate to deter other than the most determined 'hacker'. More secure systems were rejected because of the RDS capacity overhead that was required."

TMC messages consist mostly of numeric references to a static database of preset sentences and locations; no actual text is being transmitted. The database is not a secret and is freely available. The location information is encrypted with a key that changes daily. Every night, a random key is picked from 31 pregenerated alternatives. The key is never transferred over the air, only its numeric ID (1–31). The keys are preprogrammed into all licensed TMC receivers, and they can decrypt the locations knowing the key ID.

The size of the key space is 216 and the encryption algorithm consists of three permutation operations:

[Image: A cipher diagram beginning with the 16-bit hex words C1A0 and F3D5, labeled 'location' and 'key', respectively. The 'location' word goes into a bitwise right rotation block, controlled by the first nybble of 'key'. The third and fourth nybble of 'key', taken as a single byte, go to a bitwise left shift block controlled by the second nybble of 'key'. Outputs from these two bitwise blocks are XORed. The result is the hex word 85E9, labeled 'encrypted location'.]

The algorithm is simple enough to be run using pen-and-paper hardware, and that's just what I did while creating the above crypto diagram:

[Image: A pink, heart-shaped Post-It note with a hand-written columnar XOR calculation in binary. One of the operands is shifted left from the alignment. The result is 85E9.]

The tricky part is that I don't know the keys. But there's a catch. To save bandwidth, only regional messages are transmitted. This limits the space of possible locations, giving us a lot of information about the encrypted data. Assuming all messages are from this limited region, we can limit the number of keys to a very small number, in the dozens.

The next day, we have an all new encryption key again. But there's another catch. Many messages persist over several days, if not weeks. These would be messages about long-lasting roadworks and such. We just need to wait for messages that we heard yesterday that only have their location code changed, and we can continue limiting the keyspace by collecting more data.

Once we've limited the keyspace to a single key, we can decrypt all of today's messages. When the key changes again, it is trivial to find today's key by knowing yesterday's key and comparing the locations of persistent messages; this is known as a known-plaintext attack or KPA.

Here's some encrypted data straight from the radio.

$ ./redsea.pl | grep TMC
══╡ TMC msg 00 1828 4400
══╡ TMC sysmsg 6040
══╡ TMC msg 00 1828 4400
══╡ TMC msg 07 8264 0294
══╡ TMC msg 07 8264 0294
══╡ TMC msg 07 8264 0294
══╡ TMC sysmsg 0021
══╡ TMC msg 07 5964 72ca
█

A little Perl script then decodes everything and even plots the affected segment on a little map. The screenshot is from a few years back.

[Image: Screenshot of a GUI with the title 'RDS-TMC'. It's divided into three sections. The first one, labeled 'TMC Service', tells that the service provider is 'MMN TMC+', we're using location table 6/17, and the data is encrypted with key number 5. The second section, labeled 'Received messages', shows a scrollable columnar list of traffic messages received, with a one-word description, an icon and rough location. The third section, 'Message', shows the selected message in detail. A map displays the affected road segment. A long event description is printed in Finnish, along with the road name, exact coordinates, speed limit, time of expiration, and the last time the message was received.]

Now I just need a car. Well, actually I prefer motorcycles. But I guess it would work, too.

Tools used: Ordinary FM radio, sound card, computer. All data is from public sources. RDS was decoded from intermodulation distortion in the radio's Line Out audio caused by the stereo demuxer circuitry.

Update 2014-07-27: Some news seem to highlight that I was the first one to break this joke of a cipher. This could be true; I don't really care. In any case, the often-referred-to 2007 work by Barisani and Bianco (PDF 13MB) was done on unencrypted RDS-TMC and no cryptanalysis was involved; "encryption is supported for commercial services but irrelevant to our goals". I encourage you to read it, it addressed some of the real-world security implications of injecting crafted TMC messages into cars.


Disclaimer 1: I will take this post down on the first appearance of any complaint from any party, of course. My intent is not malicious and I'm not even publishing any keys or code.

Disclaimer 2: This use of the above excerpt of the ISO standard is not an infringement of copyright as it is being used here under the doctrine of "Fair Use" of the United States Copyright Law (17 U.S.C. § 107), seeing as this blog is hosted on US soil.

How I discovered RDS

One stormy night in 2007, I was listening to local FM stations while viewing a live spectrogram of the audio on the computer, through the radio's Line Out. Nearly all stations seemed to have a persistent sinusoid tone at 19 kHz, on the edge of human hearing. Turned out it's a pilot tone that the receiver uses to regenerate various subcarriers at their correct phases. The most prominent of these is the stereo subcarrier at 38 kHz, used to transmit the stereo difference signal.

[Image: Spectrogram with a frequency scale from 0 to 22000 Hz. There's a signal that looks like music, from 0 to 17000 Hz. A pure sine tone is centered at 19000 Hz. A low-power, double-sideband signal with a bandwidth of 4000 Hz is also centered at 19000 Hz.]

But something else is also visible on the spectrogram, mirrored on both sides of the pilot tone. It's a quiet signal that has a strange repetitive pattern, almost like binary data of some sort. Let's listen to a gradual downconversion I made on an FM station, revealing how the mystery signal sounds. Here I tune the heterodyne from baseband to 19 kHz.

It definitely sounds like data. I knew FM station names are transmitted alongside the audio, and that it's called RDS (Radio Data System). Could this be the RDS signal? Why does it appear at 19 kHz when the specification says it should be on the third harmonic of the pilot tone, a 57 kHz subcarrier? Just a weird aliasing/mirroring artifact? It's so close to the audio band that it actually gets interfered by some of the highest audio frequencies. But the symbol rate indeed seems to be close to 1187.5 bps, which is the RDS baud rate.

How exactly did the data end up there from 57 kHz? It could be a complex mechanism. Afterall, what I'm getting out of Line Out is not the raw FM demodulated radio signal, because the sound would be monophonic. If I switch the radio to mono reception, the data disappears altogether. So the data is probably being leaked by the stereo decoder, and is somehow related to the way the decoder applies the difference signal to the monophonic main signal. The manufacturer just didn't want to go through the trouble of filtering the artifacts out, because they're near-ultrasonic and thus practically inaudible. The mono/stereo switch probably just switches the stereo decoder off and feeds the actual demodulated baseband audio to Line Out instead.

Being the signals geek that I am, I just couldn't leave it at that. I read the 160-page specification and wrote a complete RDS demodulator and decoder to solve this mystery. And I was right! Here's a station called YLE Radio Suomi, running Radioterapeutti. The data is being decoded live from the quiet artifacts in the Line Out audio.

[Image: A screenshot resembling an LCD with various text fields. The dominating elements read 'YLESUOMI' and 'Radioterapeutti'. Smaller fields read '6203', '94.0', and 'Varied Speech'. A row of indicators shows positive status for 'RadioText', 'EON', 'TMC', and 'TP', and negative status for 'RT+', 'eRT', and 'TA'. There's also a signal level indicator showing roughly 40%.]

But it's so much more than just a station name. There's RadioText that often contains a description for the currently running programme; there's a numeric station identifier that can be decoded from even faint signals; there's information about what's currently on on other stations and what are their frequencies; and so on. Also the traffic messages displayed by car GPS navigators are transmitted to cars over RDS. And there are options for pager systems, emergency messages, and whatnot.

All this led me to make modifications to my radio and also embed all this onto a Raspberry Pi.

RDS display on a Raspberry Pi

Earlier I modified my radio to output raw bits from its RDS decoder chip. The radio already displays a Programme Service name, but for FM DX purposes, it's fun to see more data. Also, the Programme Identification code can be decoded from very weak signals, identifying distant stations. And I can receive interesting traffic information messages by decrypting RDS-TMC with the encryption keys I've recovered. Now that I have a Raspberry Pi, it's time to make a dedicated little display.

First, I changed the radio connector from my previous modification. This one, salvaged from a mystery phone device I found at a flea market, doesn't come loose as easily as the Mini-DIN:

[Image: A corner of a radio labeled 'SANGEAN', with a hole cut in it and a phone connector in the hole. A phone connector is being held next to it.]

I bought a discount 2x16-character HD44780-compliant LCD at the local parts store and made a 4-pin connection to the Raspberry Pi GPIO header. I used another two GPIO pins to interface the Pi with my radio (one for data, one for clock). I then wrote a C program to read the 1187 bps RDS data, process it for error correction and synchronization, and output it to a Perl script for interpretation. The script then returns a text string to be displayed on the LCD. The bcm2835 library is used for GPIO interaction.

Turns out GPIO is not very good for data rates this high. The C program takes about 5 samples per RDS clock cycle, and uses 97 % of the CPU time. With the Perl script and LCD update running, the poor Pi can't keep up with realtime data, and lots of bits are lost. But after waiting patiently, I finally get a Programme Service name, Programme Identification code, and RadioText.

On the left, my radio. On the right, the RasPi in its doghouse:

[Image: The same radio as above, but now its LCD display and dozens of buttons are shown. On the display, the text '87.90 YLE YKSI', the time 18:41, and indicators 'FM', 'Stereo', 'RDS', 'HOME', 'Page 1'. A cable goes from the radio to a device on the right which is contained in a little thrash can of sorts. The device has a small LCD display that reads 'YLE YKSI 6201 Kultakuume'.]

Experimenting with lower sampling rates will hopefully alleviate the problems. But it kind of works already.

Update 2013-04-01: According to this benchmark, this very library should be able to handle GPIO speeds of several megahertz. So I tried running the program in strace to see what it's doing to burn so much CPU. Turns out it's sitting in a single nanosleep() call and never returning!

I experimented a bit and ended up replacing all calls to bcm2835_delayMicroseconds() (which uses nanosleep()) with calls to usleep(). And it works! CPU utilization is at 10% maximum, and a PS name appears lightning fast when changing stations. So, something was probably wrong with nanosleep() or bcm2835's implementation thereof.

Enchanting subcarriers on FM, part 2

As explained in the previous post, I modified my Sangean ATS 909 receiver by equipping it with a digital RDS output. May it now be told in pictures.

Of course everything had to start from reading some service manuals and data sheets. Energy drinks were also necessary. Yes, I colored the board schematic with color pencils for ease of use!

[Image: Papers titled BLOCK DIAGRAM and SCHEMATIC DIAGRAM lying on the table or floor. The schematic diagram is colored in. A bottle of energy drink to the right.]

I figured I would need four separate outputs from the decoder (data, clock, quality bit, and ground) so I chose a 4-pin mini-DIN plug in one end of the cable. The other end is a familiar DB-25. Data is input via the parallel port's ACK, BUSY, and Paper Out pins; I picked a random GND pin for the ground.

[Image: The schematic diagram with the part containing the RDS decoder chip magnified. The chip has 16 pins, 4 of which are encircled and labeled Qual, Data, Ground, and Clock.]
[Image: A cable with a  Mini-DIN connector on one end and a DB-25 connector on the other.]

The RDS circuit is hidden under the corner of an RF shielding plate. It would have been tedious and mistake-prone to remove the shielding, so I just used my precision soldering skillz and left all the wires outside the plate. Now the chip was wiretapped.

[Image: Logic board of the radio with four red botch wires going under an RF shielding plate. The wires are secured using cellotape.]

The front cover has a mini-DIN-sized placeholder-looking feature that I used for this purpose. Probably it was related to the speaker's airflow though. I hotglued the 4-pin connector in place. Holes in the control board came in handy when wires needed to be taken through.

[Image: Another part of the insides of the radio, with the addition of a hotglued Mini-DIN socket.]

The socket fits nicely in the front cover. I had to remove the Sangean logo from its place but hotglued it back next to the connector. (If something else looks dissimilar to the classic ATS 909, it's because I've also replaced the default backlight LEDs with super bright ones!)

[Image: The radio, now with its cover closed and operational, and the cable connected.]

I'm relieved the radio still works! Next it will be time to test the connection. It won't happen until my cheap parallel-to-USB converter arrives in the mail, since my laptop is new enough not to have a parallel port.

But even now already, when I wiggle the connector around, the speaker audio gets superimposed by data! Of course this isn't exactly what's supposed to happen since it means I've shorted a connection, but OMG! It's audible and even more evident in the oscillogram:

[Image: Oscillogram showing a signal alternating rougly between two states, with a lot of noise superimposed.]

Update: The converter (idVendor=1a86, idProduct=7584) arrived, but apparently it just emulates a USB printer and low-level parallel bit-banging is not even possible. So I took the thing apart and built a simple twin-channel voltage divider in place of its internals that I then attached to a stereo phone jack (all parts salvaged from an old radio). Data goes to left channel, clock to the right; I'll just have to ignore the quality bit.

[Image: Schematic diagram of the cable and connector setup with a voltage divider. Pins 10 and 12 of a DB-25 are connected to arbitraty points A and B, respectively, via 1 k-ohm resistors. Point A and B are both connected to point C via 200 ohm resistors each. Point C is connected to pin 23 of the DB-25. Points A and B are connected to terminals 1 and 2 of a 3.5mm socket, respectively.]
[Image: The above voltage divider in a series of photos, showing how it fits inside the casing of a parallel port printer adapter.]
[Image: The voltage divider connected to the radio's DB-25 cable. A LED indicator is on.]

I connected this to the sound card's line in and behold! The clearest possible data and clock signals on separate channels!

[Image: A two-channel oscillogram. One channel shows a square wave with regular transition intervals, the other one transitions only at some transition points of the first one.]

There is slight crosstalk, probably via ground since I used quite small resistors for the voltage divider. But the data is good:

[Image: Screenshot of a terminal and a small GUI window. The terminal is showing a stream of text describing RDS information. The little window is showing the text YLEYKSI and Muistojen bulevardi.]

This means I can completely comment out my software clock recovery and PLL. I rolled the FM dial and logged some stations.

Now I'm going to bed.

Enchanting subcarriers on FM

YLESUOMI

RDS has been my main object of interest in the field of FM listening for years now. RDS is the reason you can read the station name on a radio as soon as you tune onto it. It contains a lot of other info about the station and ongoing program as well (Wikipedia article). My interest is more in the decoding process than in the data it can reveal, although I like logging the data as well.

My radio (Sangean ATS 909) conveniently produces an alias of the 57 kHz RDS subcarrier around the stereo pilot tone at 19 kHz. This can be digitized using an ordinary sound card and demodulated + decoded in software (it's on GitHub, I named it redsea). However, the signal is very weak and suffers from interference due to its proximity to the audio band. Also, the radio already has an RDS decoder chip designed by someone wiser, so why not exploit it!

I plan to replace my sound card hack by redirecting the actual data from the decoder chip directly to a parallel port — which happens to use the same logic voltage levels (0/+5 V). Today I got the first components and already built part of the cable! So more updates soon.

My main concern is what happens to the voltage levels if they're fed both to the CPU in the radio and the parallel port at the same time. Will they still be over the threshold? We'll see! Ideas for amplifying the signal back to 5V are welcome.

Continues on the next post