Software Defined Radio — как это работает? Часть 8
Привет Хабр.
В одной из предыдущих статей про Software Defined Radio был задан вопрос, как декодировать RDS с помощью GNU Radio. Декодер RDS является не таким уж простым для создания с нуля, но к счастью для нас, в GNU Radio он уже встроен, так что несложно посмотреть как он работает, не написав ни одной строчки кода, достаточно лишь приемника RTL-SDR.
Как это работает, продолжение под катом.
Про GNU Radio и его установку я повторяться не буду, это уже описывалось ранее. Если совсем кратко, дистрибутив под Windows можно взять здесь. Перейдем собственно к RDS.
Если кто вдруг не в курсе, RDS (Radio Data System) — это протокол, используемый в обычном FM-вещании для передачи дополнительной цифровой информации (время, курсы валют, погода и пр). Передача идет со скоростью 1187.5bps на частоте 57КГц (3я гармоника пилот-тона), для минимизации полосы частот используется фазовое кодирование BPSK (Binary Phase Shift Keying). Побитовый уровень RDS уже рассматривался на хабре ранее, ну, а мы перейдем к GNU Radio.
Декодер для GNU Radio был сделан примерно лет 10 назад, но ту версию смотреть уже бесполезно, названия многих блоков поменялись, и приведенные там примеры уже не работают. Обновленный форк можно взять отсюда.
Собрать gr-rds из исходников под Windows, кстати, не получилось — в cmake выдаются ошибки на отсутствие Boost, хотя он поставлен. Непонятно кстати, почему за годы существования cmake и boost, cmake так и не научили находить пути в Windows — вроде найти папку на диске это совсем не rocket science (если кто знает решение, напишите в комментариях, хотя судя по Stack Overflow, проблема существует годами и всем пофиг). Но это нам как оказалось, и не нужно — декодер RDS уже добавлен в GNU Radio, так что из проекта на github нам нужны только примеры, которые лежат в папке apps.
Блок-схема, прилагаемая в примерах, является довольно-таки монструозной, к тому же, при ее открытии выдаются ошибки (блоки помечены красным).
В реале, впрочем, все проще — 2/3 схемы это стерео-плеер для FM, где из исходного сигнала извлекаются L+R и L-R каналы, обрабатываются и подаются на звуковую карту. Нам это сейчас неактуально, так что эти блоки можно удалить (тем более, что «из коробки» оно почему-то и не заработало, а разбираться было лень). Ошибки возникают из-за параметра Grid Position, который видимо, не поддерживается Windows-версией, но его можно без проблем удалить, на функциональность не влияет.
После удаления «всего лишнего» работающая схема RDS декодера выглядит так:
Посмотрим, что тут есть.
Исходный сигнал поступает из RTL-SDR Source, частота задается параметром freq, имеющим тип WX GUI Slider (да, в GNU Radio можно создать свой UI, и есть базовые контролы). Чтобы избежать пика на нулевой частоте в центре, используется параметр freq_offset, блок Frequency Xlating сдвигает частоту на это значение. Блок WBFM Receive, как понятно из названия, выполняет FM-демодуляцию, затем частота сдвигается еще раз, чтобы выделить сам RDS на 57КГц. Блок Root Cosined Filter выделяет узкую частоту, ну, а MPSK-декодер с параметром 2 выполняет BPSK декодирование (сам RDS передается с помощью фазовой модуляции с двумя состояниями). В RDS используется дифференциальное кодирование, поэтому вызывается соответствующий дифференциальный декодер, ну и наконец готовый бинарный поток подается на блок RDS Decoder (его исходники можно посмотреть на github). После декодера не менее важная часть это RDS-парсер — типов пакетов в RDS довольно-таки много, и парсер делает всю работу по их расшифровке.
Собственно и все. Результаты работы декодера на КДПВ и скриншотах.
Если кому-то необходимо использовать программу в no-UI режиме, можно использовать блок FR Tap, более подробное описание здесь, я лично его не пробовал. Если же интересен более низкий побитовый уровень RDS, я его рассматривал ранее, для общего интереса тоже может быть полезно.
Как обычно, всем удачных экспериментов.
Исходный GRC-файл, работающий под Windows, под спойлером (частоту радиостанции только придется поменять).
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