torstai 16. toukokuuta 2019

Stereo FM receiving with RTL-SDR and Gnuradio - part 2

In the first part of stereo FM broadcast receiving we discussed how to receive mono FM broadcast and how the stereo signal differs.

In this second part I will explain how I made the stereo receiver.

Receiver graph

Following figure shows the finished receiver. I've cleaned it up (e.g. removed some unnecessary waveform displays) to have a clearer view of what is going on. I also added borders and numbers to denote different sections.
Finished stereo FM broadcast receiver

 

1. FM demodulation

The first section is the FM demodulator. I use the rtl-sdr block to interface with the radio module, then low pass filter the raw data and apply the FM demodulator. This block is the same as with mono receiver, except the demodulator low pass filter must be > 80 kHz and there must be no de-emphasis in this stage. The signal is then resampled to 384 kHz for faster calculations in later stages.

The waveform sources are shown as disabled. If this was used with the ramp waveforms generated with the python script, the raw waveform should look like following.
Raw baseband signal
The red section shows the part where I investigated the clock phase.

 

2. Delay block

I found out that I need to delay the signal for clock reconstruction by 3 samples. Most likely this is due to the phase shift of the low-pass filters. If the delay was something else than 3 samples, the reconstructed audio signals were completely wrong.

Here is how the clock signals look in the python script:
Clock signals in python script
For example we see that right after 0.00165 s the signal is going down and both clocks (19 kHz and 38 kHz) cross the signal at zero; 19 kHz going down and 38 kHz going up. This is a easily identifiable signal position so we can check the same signals in GRC also.
Clock signals in GRC
As we see, with the delay set to 3 the clocks are generated exactly as in the python script. This produces clean output as we will see later.

 

3. 19 kHz pilot signal

The 19 kHz pilot signal is first bandpass filtered, to remove all other content from the signal. My understanding is that band pass filter should not cause phase shift at the center frequency, but I'm not completely sure. If it did cause delay, it could be compensated with the delay discussed previously.

The filtered pilot signal is then fed into a PLL to regenerate a clean clock signal with amplitude of 1.0.  The minimum and maxium frequencies are given as radians per sample and are calculated with the variables. I gave something like +-50 Hz range for the PLL.

The bandwidth of the PLL is set to a very small value, by trial and error. I noticed that large bandwidth changed the output from sinusoidal to square wave and also I think the PLL should not respond to fast changes but rather gradually correct any phase or frequency errors. 200e-6 seemed to work fine.

 

4. 38 kHz carrier

The 38 kHz carrier signal is generated by squaring the PLL output. The resulting waveform contains multiple harmonics and only the 2nd harmonic is bandpass filtered. This results in clean output in the same phase as the PLL output.

I added gain of 2 here, but it could also be added later. The difference signal needs to be doubled in amplitude to give correct output.

 

5. Difference signal demodulation

The difference (L-R, "stereo") signal is obtained by demodulating the AM signal with the 38 kHz carrier, i.e. multiplying the raw baseband signal with the carrier and then low pass filtering with 15 kHz filter.

 

6. Sum (mono) signal 

The mono signal (sum, L+R) is got simply be low-pass filtering with 15 kHz the raw baseband signal, in the same way as with the mono receiver.

Now that we have both mono and "stereo" signals, we can compare them to the output of the python script.
Sum and difference signals from python
The previous figure shows the signals in python and the following shows them in GRC. The shape of the "received" waveform closely matches the expected one, except the amplitudes are different. Most likely this is due to normalization in the python script (the total baseband signal was later normalized to peak amplitude of 1, which was not accounted for in figure above).

Sum and difference signals from GRC

 

7. Channel reconstruction

Now that we seem to have correct data, we can reconstruct the audio signals. Left channel is the sum of the two streams 0.5*(L+R)+0.5*(L-R) =L and right channel is the difference 0.5*(L+R)-0.5*(L+R)=R.

After addition or subtraction, the de-emphasis filter is applied for both channels and they are resampled to 48 kHz and fed to an audio sink. We can still compare the original waveforms from python to what was received.

Original left and right channel data from python
As we see, the received waveforms are correct.
"Received" left and right waveforms in GRC
Now that everything works with the generated baseband signal, we can disable the waveform source and enable rtl-sdr source and the fm demodulator and see that the stereo receiver indeed works.

Conclusion

I uploaded two versions of the receiver to my GitHub.
  • 1st version is the cleand up version without additional waveform displays
  • 2nd version contains the waveform displays that can be used to compare the python script output to GRC.
Both versions have a checkbox to selecte either 75 us or 50 us de-emphasis (first is used in USA, latter in almost everywhere else I think). Honestly I cannot hear any difference between those though...

I also added a checkbox to enable stereo decoding (which changes the "stereo" channel low pass filter gain from 1.0 to 0.0 if stereo is disabled) so that it is easy to compare the audio quality.

Ei kommentteja:

Lähetä kommentti