Software Defined Radio - how does it work? Part 8
Hi Habr.
In a previous article about Software Defined Radio, I was asked how to decode RDS using GNU Radio. The RDS decoder is not so easy to create from scratch, but fortunately for us, it is already built into GNU Radio, so it's easy to see how it works without writing a single line of code, just the RTL-SDR receiver.
How it works, continued under the cut.
I will not repeat about GNU Radio and its installation, it has already been described previously . In short, a distribution for Windows can be found here . Let's move on to RDS.
If someone doesn’t know, RDS(Radio Data System) is a protocol used in conventional FM broadcasting to transmit additional digital information (time, exchange rates, weather, etc.). The transmission is at a speed of 1187.5bps at a frequency of 57KHz (3rd harmonic of the pilot tone), phase minimization using BPSK phase encoding (Binary Phase Shift Keying) is used. The bit-level RDS has already been considered on the hub before , well, we will move on to GNU Radio.
The decoder for GNU Radio was made about 10 years ago , but it’s useless to watch that version, the names of many blocks have changed, and the examples given there no longer work. Updated fork can be taken from here .
By the way, it did not work to collect gr-rds from the source for Windows - cmake gives errors for the absence of Boost, although it is installed. It’s incomprehensible, by the way, that over the years of existence cmake and boost, cmake have not been taught how to find paths in Windows - like finding a folder on a disk is not rocket science at all (if anyone knows the solution, write in the comments, although judging by Stack Overflow, the problem has existed for years and all do not care). But as it turned out, we don’t need it - the RDS decoder has already been added to GNU Radio, so from the project on github we only need examples that are in the apps folder .
The block diagram enclosed in the examples is quite monstrous, and besides, errors are generated when it is opened (blocks are marked in red).
In real life, however, everything is simpler - 2/3 of the circuit is a stereo player for FM, where L + R and LR channels are extracted from the original signal, processed and fed to the sound card. This is not relevant for us now, so these blocks can be removed (all the more so because for some reason it didn’t work out of the box, but it was too lazy to understand). Errors arise due to the Grid Position parameter, which, apparently, is not supported by the Windows version, but it can be removed without problems, it does not affect the functionality.
After removing "all that is superfluous", the working RDS decoder circuit looks like this:
Let's see what is there.
The initial signal comes from RTL-SDR Source, the frequency is set by the freq parameter, which is of the WX GUI Slider type (yes, you can create your own UI in GNU Radio, and there are basic controls). To avoid a peak at zero frequency in the center, the freq_offset parameter is used, the Frequency Xlating block shifts the frequency by this value. The WBFM Receive block, as the name implies, performs FM demodulation, then the frequency is shifted again to highlight 57KHz RDS itself. The Root Cosined Filter block selects a narrow frequency, but the MPSK decoder with parameter 2 performs BPSK decoding (the RDS itself is transmitted using phase modulation with two states). RDS uses differential encoding, so the corresponding differential decoder is called, and finally, the finished binary stream is fed to the RDS Decoder block (its source can be viewed on github ). After the decoder, an equally important part is the RDS parser - there are quite a lot of packet types in RDS, and the parser does all the work to decrypt them.
Actually that's all. The results of the decoder on the KDPV and screenshots.
If someone needs to use the program in no-UI mode, you can use the FR Tap block, a more detailed description here , I personally have not tried it. If the lower bit-level RDS is interesting, I considered it earlier , for general interest it can also be useful.
As usual, all successful experiments.
The source GRC file running under Windows is under a spoiler (the frequency of the radio station will only have to be changed).
In a previous article about Software Defined Radio, I was asked how to decode RDS using GNU Radio. The RDS decoder is not so easy to create from scratch, but fortunately for us, it is already built into GNU Radio, so it's easy to see how it works without writing a single line of code, just the RTL-SDR receiver.
How it works, continued under the cut.
I will not repeat about GNU Radio and its installation, it has already been described previously . In short, a distribution for Windows can be found here . Let's move on to RDS.
If someone doesn’t know, RDS(Radio Data System) is a protocol used in conventional FM broadcasting to transmit additional digital information (time, exchange rates, weather, etc.). The transmission is at a speed of 1187.5bps at a frequency of 57KHz (3rd harmonic of the pilot tone), phase minimization using BPSK phase encoding (Binary Phase Shift Keying) is used. The bit-level RDS has already been considered on the hub before , well, we will move on to GNU Radio.
The decoder for GNU Radio was made about 10 years ago , but it’s useless to watch that version, the names of many blocks have changed, and the examples given there no longer work. Updated fork can be taken from here .
By the way, it did not work to collect gr-rds from the source for Windows - cmake gives errors for the absence of Boost, although it is installed. It’s incomprehensible, by the way, that over the years of existence cmake and boost, cmake have not been taught how to find paths in Windows - like finding a folder on a disk is not rocket science at all (if anyone knows the solution, write in the comments, although judging by Stack Overflow, the problem has existed for years and all do not care). But as it turned out, we don’t need it - the RDS decoder has already been added to GNU Radio, so from the project on github we only need examples that are in the apps folder .
The block diagram enclosed in the examples is quite monstrous, and besides, errors are generated when it is opened (blocks are marked in red).
In real life, however, everything is simpler - 2/3 of the circuit is a stereo player for FM, where L + R and LR channels are extracted from the original signal, processed and fed to the sound card. This is not relevant for us now, so these blocks can be removed (all the more so because for some reason it didn’t work out of the box, but it was too lazy to understand). Errors arise due to the Grid Position parameter, which, apparently, is not supported by the Windows version, but it can be removed without problems, it does not affect the functionality.
After removing "all that is superfluous", the working RDS decoder circuit looks like this:
Let's see what is there.
The initial signal comes from RTL-SDR Source, the frequency is set by the freq parameter, which is of the WX GUI Slider type (yes, you can create your own UI in GNU Radio, and there are basic controls). To avoid a peak at zero frequency in the center, the freq_offset parameter is used, the Frequency Xlating block shifts the frequency by this value. The WBFM Receive block, as the name implies, performs FM demodulation, then the frequency is shifted again to highlight 57KHz RDS itself. The Root Cosined Filter block selects a narrow frequency, but the MPSK decoder with parameter 2 performs BPSK decoding (the RDS itself is transmitted using phase modulation with two states). RDS uses differential encoding, so the corresponding differential decoder is called, and finally, the finished binary stream is fed to the RDS Decoder block (its source can be viewed on github ). After the decoder, an equally important part is the RDS parser - there are quite a lot of packet types in RDS, and the parser does all the work to decrypt them.
Actually that's all. The results of the decoder on the KDPV and screenshots.
If someone needs to use the program in no-UI mode, you can use the FR Tap block, a more detailed description here , I personally have not tried it. If the lower bit-level RDS is interesting, I considered it earlier , for general interest it can also be useful.
As usual, all successful experiments.
The source GRC file running under Windows is under a spoiler (the frequency of the radio station will only have to be changed).
rds_rx.grc
Thu Aug 28 08:24:49 2014 options author window_size 1600, 1600 category Custom comment description _enabled True _coordinate (14, 9) _rotation 0 generate_options wx_gui hier_block_src_path .: id rds_rx max_nouts 0 qt_qss_theme realtime_scheduling run_command {python} -u {filename} run_options prompt run True thread_safe_setters title Stereo FM receiver and RDS Decoder variable comment _enabled True _coordinate (8, 156) _rotation 0 id audio_decim value 5 variable comment _enabled True _coordinate (112, 156) _rotation 0 id audio_decim_rate value baseband_rate/audio_decim variable comment _enabled True _coordinate (112, 92) _rotation 0 id baseband_rate value samp_rate/bb_decim variable comment _enabled True _coordinate (240, 156) _rotation 0 id bb_decim value 4 variable_slider comment converver float_converter value 100.7e6 _enabled True _coordinate (448, 4) _rotation 0 grid_pos id freq label Freq max 107.9e6 min 88.1e6 notebook num_steps 99 style wx.SL_HORIZONTAL variable comment _enabled True _coordinate (448, 132) _rotation 0 id freq_offset value 250000 variable comment _enabled True _coordinate (224, 92) _rotation 0 id freq_tune value freq - freq_offset variable_slider comment converver float_converter value 20 _enabled True _coordinate (336, 4) _rotation 0 grid_pos id gain label RF Gain max 49.6 min 0 notebook num_steps 124 style wx.SL_HORIZONTAL variable comment _enabled True _coordinate (8, 92) _rotation 0 id samp_rate value 1000000 variable comment _enabled True _coordinate (336, 132) _rotation 0 id xlate_bandwidth value 100000 analog_wfm_rcv audio_decimation bb_decim alias comment affinity _enabled True _coordinate (576, 356) _rotation 0 id analog_wfm_rcv_0 maxoutbuf 0 minoutbuf 0 quad_rate samp_rate blocks_complex_to_real alias comment affinity _enabled True _coordinate (792, 632) _rotation 0 id blocks_complex_to_real_0 maxoutbuf 0 minoutbuf 0 vlen 1 blocks_keep_one_in_n alias comment affinity _enabled True _coordinate (280, 788) _rotation 0 id blocks_keep_one_in_n_0 maxoutbuf 0 minoutbuf 0 n 2 type byte vlen 1 digital_binary_slicer_fb alias comment affinity _enabled True _coordinate (112, 792) _rotation 0 id digital_binary_slicer_fb_0 maxoutbuf 0 minoutbuf 0 digital_diff_decoder_bb alias comment affinity _enabled True _coordinate (424, 788) _rotation 0 id digital_diff_decoder_bb_0 maxoutbuf 0 minoutbuf 0 modulus 2 digital_mpsk_receiver_cc alias comment affinity _enabled True _coordinate (528, 488) _rotation 0 gain_mu 0.05 gain_omega 0.001 id digital_mpsk_receiver_cc_0 w 1*cmath.pi/100.0 M 2 fmax 0.06 maxoutbuf 0 fmin -0.06 minoutbuf 0 mu 0.5 omega_relative_limit 0.005 omega samp_rate/bb_decim/audio_decim/ 2375.0 theta 0 freq_xlating_fir_filter_xxx alias center_freq freq_offset comment affinity decim 1 _enabled True _coordinate (279, 296) _rotation 0 id freq_xlating_fir_filter_xxx_0 maxoutbuf 0 minoutbuf 0 samp_rate samp_rate taps firdes.low_pass(1, samp_rate, xlate_bandwidth, 100000) type ccc freq_xlating_fir_filter_xxx alias center_freq 57e3 comment affinity decim audio_decim _enabled True _coordinate (72, 532) _rotation 0 id freq_xlating_fir_filter_xxx_1 maxoutbuf 0 minoutbuf 0 samp_rate baseband_rate taps firdes.low_pass(2500.0,baseband_rate,2.4e3,2e3,firdes.WIN_HAMMING) type fcc gr_rds_decoder alias comment affinity debug False _enabled True _coordinate (632, 780) _rotation 0 id gr_rds_decoder_0 log False maxoutbuf 0 minoutbuf 0 gr_rds_panel alias comment affinity _enabled True freq freq _coordinate (984, 792) _rotation 0 grid_pos id gr_rds_panel_0 notebook gr_rds_parser alias comment affinity debug False _enabled True _coordinate (800, 772) _rotation 0 id gr_rds_parser_0 log True maxoutbuf 0 minoutbuf 0 pty_locale 0 reset 0 import alias comment _enabled True _coordinate (576, 4) _rotation 0 id import_0 import import math notebook alias comment _enabled True _coordinate (184, 6) _rotation 0 grid_pos id nb labels ['BB', 'Demod', 'L+R', 'Pilot', 'DSBSC', 'RDS', 'L-R', 'RDS constellation','Waterfall'] notebook style wx.NB_TOP root_raised_cosine_filter alpha 1 alias comment affinity decim 1 _enabled True type fir_filter_ccf _coordinate (304, 516) _rotation 0 gain 1 id root_raised_cosine_filter_0 interp 1 maxoutbuf 0 minoutbuf 0 ntaps 100 samp_rate samp_rate/bb_decim/audio_decim sym_rate 2375 rtlsdr_source alias ant0 bb_gain0 20 bw0 0 dc_offset_mode0 0 corr0 0 freq0 freq_tune gain_mode0 False if_gain0 20 iq_balance_mode0 0 gain0 gain ant10 bb_gain10 20 bw10 0 dc_offset_mode10 0 corr10 0 freq10 100e6 gain_mode10 False if_gain10 20 iq_balance_mode10 0 gain10 10 ant11 bb_gain11 20 bw11 0 dc_offset_mode11 0 corr11 0 freq11 100e6 gain_mode11 False if_gain11 20 iq_balance_mode11 0 gain11 10 ant12 bb_gain12 20 bw12 0 dc_offset_mode12 0 corr12 0 freq12 100e6 gain_mode12 False if_gain12 20 iq_balance_mode12 0 gain12 10 ant13 bb_gain13 20 bw13 0 dc_offset_mode13 0 corr13 0 freq13 100e6 gain_mode13 False if_gain13 20 iq_balance_mode13 0 gain13 10 ant14 bb_gain14 20 bw14 0 dc_offset_mode14 0 corr14 0 freq14 100e6 gain_mode14 False if_gain14 20 iq_balance_mode14 0 gain14 10 ant15 bb_gain15 20 bw15 0 dc_offset_mode15 0 corr15 0 freq15 100e6 gain_mode15 False if_gain15 20 iq_balance_mode15 0 gain15 10 ant16 bb_gain16 20 bw16 0 dc_offset_mode16 0 corr16 0 freq16 100e6 gain_mode16 False if_gain16 20 iq_balance_mode16 0 gain16 10 ant17 bb_gain17 20 bw17 0 dc_offset_mode17 0 corr17 0 freq17 100e6 gain_mode17 False if_gain17 20 iq_balance_mode17 0 gain17 10 ant18 bb_gain18 20 bw18 0 dc_offset_mode18 0 corr18 0 freq18 100e6 gain_mode18 False if_gain18 20 iq_balance_mode18 0 gain18 10 ant19 bb_gain19 20 bw19 0 dc_offset_mode19 0 corr19 0 freq19 100e6 gain_mode19 False if_gain19 20 iq_balance_mode19 0 gain19 10 ant1 bb_gain1 20 bw1 0 dc_offset_mode1 0 corr1 0 freq1 100e6 gain_mode1 False if_gain1 20 iq_balance_mode1 0 gain1 10 ant20 bb_gain20 20 bw20 0 dc_offset_mode20 0 corr20 0 freq20 100e6 gain_mode20 False if_gain20 20 iq_balance_mode20 0 gain20 10 ant21 bb_gain21 20 bw21 0 dc_offset_mode21 0 corr21 0 freq21 100e6 gain_mode21 False if_gain21 20 iq_balance_mode21 0 gain21 10 ant22 bb_gain22 20 bw22 0 dc_offset_mode22 0 corr22 0 freq22 100e6 gain_mode22 False if_gain22 20 iq_balance_mode22 0 gain22 10 ant23 bb_gain23 20 bw23 0 dc_offset_mode23 0 corr23 0 freq23 100e6 gain_mode23 False if_gain23 20 iq_balance_mode23 0 gain23 10 ant24 bb_gain24 20 bw24 0 dc_offset_mode24 0 corr24 0 freq24 100e6 gain_mode24 False if_gain24 20 iq_balance_mode24 0 gain24 10 ant25 bb_gain25 20 bw25 0 dc_offset_mode25 0 corr25 0 freq25 100e6 gain_mode25 False if_gain25 20 iq_balance_mode25 0 gain25 10 ant26 bb_gain26 20 bw26 0 dc_offset_mode26 0 corr26 0 freq26 100e6 gain_mode26 False if_gain26 20 iq_balance_mode26 0 gain26 10 ant27 bb_gain27 20 bw27 0 dc_offset_mode27 0 corr27 0 freq27 100e6 gain_mode27 False if_gain27 20 iq_balance_mode27 0 gain27 10 ant28 bb_gain28 20 bw28 0 dc_offset_mode28 0 corr28 0 freq28 100e6 gain_mode28 False if_gain28 20 iq_balance_mode28 0 gain28 10 ant29 bb_gain29 20 bw29 0 dc_offset_mode29 0 corr29 0 freq29 100e6 gain_mode29 False if_gain29 20 iq_balance_mode29 0 gain29 10 ant2 bb_gain2 20 bw2 0 dc_offset_mode2 0 corr2 0 freq2 100e6 gain_mode2 False if_gain2 20 iq_balance_mode2 0 gain2 10 ant30 bb_gain30 20 bw30 0 dc_offset_mode30 0 corr30 0 freq30 100e6 gain_mode30 False if_gain30 20 iq_balance_mode30 0 gain30 10 ant31 bb_gain31 20 bw31 0 dc_offset_mode31 0 corr31 0 freq31 100e6 gain_mode31 False if_gain31 20 iq_balance_mode31 0 gain31 10 ant3 bb_gain3 20 bw3 0 dc_offset_mode3 0 corr3 0 freq3 100e6 gain_mode3 False if_gain3 20 iq_balance_mode3 0 gain3 10 ant4 bb_gain4 20 bw4 0 dc_offset_mode4 0 corr4 0 freq4 100e6 gain_mode4 False if_gain4 20 iq_balance_mode4 0 gain4 10 ant5 bb_gain5 20 bw5 0 dc_offset_mode5 0 corr5 0 freq5 100e6 gain_mode5 False if_gain5 20 iq_balance_mode5 0 gain5 10 ant6 bb_gain6 20 bw6 0 dc_offset_mode6 0 corr6 0 freq6 100e6 gain_mode6 False if_gain6 20 iq_balance_mode6 0 gain6 10 ant7 bb_gain7 20 bw7 0 dc_offset_mode7 0 corr7 0 freq7 100e6 gain_mode7 False if_gain7 20 iq_balance_mode7 0 gain7 10 ant8 bb_gain8 20 bw8 0 dc_offset_mode8 0 corr8 0 freq8 100e6 gain_mode8 False if_gain8 20 iq_balance_mode8 0 gain8 10 ant9 bb_gain9 20 bw9 0 dc_offset_mode9 0 corr9 0 freq9 100e6 gain_mode9 False if_gain9 20 iq_balance_mode9 0 gain9 10 comment affinity args _enabled 1 _coordinate (24, 272) _rotation 0 id rtlsdr_source_0 maxoutbuf 0 clock_source0 time_source0 clock_source1 time_source1 clock_source2 time_source2 clock_source3 time_source3 clock_source4 time_source4 clock_source5 time_source5 clock_source6 time_source6 clock_source7 time_source7 minoutbuf 0 nchan 1 num_mboards 1 type fc32 sample_rate samp_rate sync wxgui_fftsink2 avg_alpha 0.8 average True baseband_freq 0 alias comment affinity _enabled True fft_size 1024 freqvar None _coordinate (1056, 56) _rotation 0 grid_pos id wxgui_fftsink2_0 notebook nb, 0 peak_hold False ref_level -30 ref_scale 2.0 fft_rate 15 samp_rate samp_rate title Baseband type complex win_size win None y_divs 10 y_per_div 10 wxgui_fftsink2 avg_alpha 0.8 average True baseband_freq 0 alias comment affinity _enabled True fft_size 1024 freqvar None _coordinate (1056, 280) _rotation 0 grid_pos id wxgui_fftsink2_0_0 notebook nb, 1 peak_hold False ref_level 0 ref_scale 2.0 fft_rate 15 samp_rate baseband_rate title FM Demod type float win_size win None y_divs 10 y_per_div 10 wxgui_scopesink2 ac_couple False alias comment affinity _enabled True _coordinate (1056, 500) _rotation 0 grid_pos id wxgui_scopesink2_1 notebook nb,7 num_inputs 1 samp_rate 2375 t_scale 0 title Scope Plot trig_mode wxgui.TRIG_MODE_AUTO type complex v_offset 0 v_scale 0.4 win_size xy_mode True y_axis_label Counts analog_wfm_rcv_0 freq_xlating_fir_filter_xxx_1 0 0 analog_wfm_rcv_0 wxgui_fftsink2_0_0 0 0 blocks_complex_to_real_0 digital_binary_slicer_fb_0 0 0 blocks_keep_one_in_n_0 digital_diff_decoder_bb_0 0 0 digital_binary_slicer_fb_0 blocks_keep_one_in_n_0 0 0 digital_diff_decoder_bb_0 gr_rds_decoder_0 0 0 digital_mpsk_receiver_cc_0 blocks_complex_to_real_0 0 0 digital_mpsk_receiver_cc_0 wxgui_scopesink2_1 0 0 freq_xlating_fir_filter_xxx_0 analog_wfm_rcv_0 0 0 freq_xlating_fir_filter_xxx_0 wxgui_fftsink2_0 0 0 freq_xlating_fir_filter_xxx_1 root_raised_cosine_filter_0 0 0 gr_rds_decoder_0 gr_rds_parser_0 out in gr_rds_parser_0 gr_rds_panel_0 out in root_raised_cosine_filter_0 digital_mpsk_receiver_cc_0 0 0 rtlsdr_source_0 freq_xlating_fir_filter_xxx_0 0 0