Software Defined Radio — как это работает? Часть 9
Привет Хабр.
В предыдущей части было рассмотрено декодирование сигналов RDS для FM-радиостанций, и идея следующей статьи возникла сама собой — нужно сделать свой собственный FM-трансмиттер.
Вариантов будет два — простой, и более сложный, с RDS.
Как и в предыдущих случаях, сделаем мы все это в GNU Radio, не написав ни одной строчки кода. Для тех кому интересно, продолжение под катом.
Итак, приступим. Разумеется, для тестов нам понадобится SDR с возможностью передачи (HackRF, USRP, LimeSDR).
FM трансмиттер
Схема простого трансмиттера делается практически в два клика, и надеюсь, никаких сложностей в понимании не представляет. В качестве источника я использую WAV-file, хотя при желании можно использовать и другой источник, например вход звуковой карты.
Собственно, ключевых блоков в этой схеме два — WBFM-трансмиттер и Resampler, преобразующий частоту дискретизации потока. Все значения частоты дискретизации должны соответствовать друг другу, иначе будет пропуск семплов, что на слух будет слышно как щелчки. Блок Multiply Const используется для регулировки входного уровня. Также важно не перепутать, в GNU Radio есть два разных блока для FM — WBFM и NBFM. Нам нужен именно первый, Wide Band FM. Модуляция NFM используется для портативных радиостанций.
В общем, все просто, запускаем, работает. Никаких UI-блоков в схеме нет, так что использовать ее можно и из командной строки (подробнее в 4й части).
Кстати, если посмотреть спектр, то можно убедиться, что передается простой моно сигнал, без каких-либо каналов, пилот-тонов и прочего.
Пора перейти к более сложному варианту.
Трансмиттер с RDS
Схема передатчика с RDS будет разумеется, посложнее. Ее оригинал был взят из примеров gr-rds с небольшими изменениями (оригинальная версия под Windows не заработала, «правильная» версия внизу статьи), рассмотрим, из каких компонентов она состоит.
(оригинал в полном разрешении)
Верхяя треть схемы — это передача RDS. На входе имеется RDS-энкодер, который исходя из имеющихся данных (имя станции, код страны и пр) формирует RDS-пакеты. Затем битовый поток подвергается дифференциальному кодированию, идея которого — убрать повторяющиеся последовательности вида 001000001. Затем сигнал обрабатывается, и им модулируется «несущая» на 57КГц. Центральная часть — создание звука. Тут формируются каналы L+R и L-R. Снизу в качестве источника звука указан WAV-файл. И наконец, блок Add складывает все это вместе, также к сигналу добавляется пилот-тон на 19КГц и второй тон на 38КГц. Весь этот суммарный сигнал отправляется на частотный модулятор, все это с помощью SDR отправляется в эфир.
По идее, эта схема не является 100% полной, например нет блока предыскажений, фазы тонов на 19, 38 и 57КГц не синхронизированы. С другой стороны, для понимания того, как вообще звук передается в эфир, такой схемы вполне достаточно, желающие могут доработать ее самостоятельно.
При запуске открывается окно, часть параметров можно менять.
Было желание протестировать RDS с реальным приемником, но оказалось что FM-радио — это уже атавизм, и у меня дома его нет. Даже в смартфоне последней модели оно тоже отсутствует. Так что пришлось использовать RTL-SDR V3 и GQRX в качестве контрольного приемника, результат на КДПВ.
Разумеется, перед тестированием необходимо выбрать свободную частоту, чтобы на ней не было станций, ну и желательно не превышать разрешенную мощность. Для тех же, кто захочет получить максимум дальности можно докупить усилитель, желательно сделать антенну на нужную частоту, чтобы был хотя бы диполь ¼ длины волны.
Заключение
Как можно видеть, в передаче FM-радио в принципе, нет ничего сверхсложного, все вполне реализуемо в GNU Radio (хотя без наличия примеров повторить такое было бы непросто, учитывая что система не документирована вообще никак). Ну теперь, по крайней мере, работающий пример у читателей есть.
Исходные коды блоков под спойлером.
Tue Jun 18 20:27:26 2019
options
author
window_size
category
[GRC Hier Blocks]
comment
description
_enabled
True
_coordinate
(16, 20)
_rotation
0
generate_options
wx_gui
hier_block_src_path
.:
id
top_block
max_nouts
0
qt_qss_theme
realtime_scheduling
run_command
{python} -u {filename}
run_options
prompt
run
True
thread_safe_setters
title
analog_wfm_tx
audio_rate
80000
alias
comment
affinity
_enabled
True
_coordinate
(592, 76)
_rotation
0
id
analog_wfm_tx_0
max_dev
75e3
maxoutbuf
0
minoutbuf
0
fh
-1.0
quad_rate
320000
tau
75e-6
blocks_multiply_const_vxx
alias
comment
const
0.45
affinity
_enabled
True
_coordinate
(248, 108)
_rotation
0
id
blocks_multiply_const_vxx_0
type
float
maxoutbuf
0
minoutbuf
0
vlen
1
blocks_wavfile_source
alias
comment
affinity
_enabled
True
file
D:\MyProjects\GNURadio\sound.wav
_coordinate
(24, 100)
_rotation
0
id
blocks_wavfile_source_0
maxoutbuf
0
minoutbuf
0
nchan
1
repeat
True
rational_resampler_xxx
alias
comment
affinity
decim
3
_enabled
True
fbw
0
_coordinate
(408, 84)
_rotation
0
id
rational_resampler_xxx_0
interp
5
maxoutbuf
0
minoutbuf
0
taps
type
fff
uhd_usrp_sink
alias
ant0
bw0
0
center_freq0
95.6e6
norm_gain0
False
gain0
30
ant10
bw10
0
center_freq10
0
norm_gain10
False
gain10
0
ant11
bw11
0
center_freq11
0
norm_gain11
False
gain11
0
ant12
bw12
0
center_freq12
0
norm_gain12
False
gain12
0
ant13
bw13
0
center_freq13
0
norm_gain13
False
gain13
0
ant14
bw14
0
center_freq14
0
norm_gain14
False
gain14
0
ant15
bw15
0
center_freq15
0
norm_gain15
False
gain15
0
ant16
bw16
0
center_freq16
0
norm_gain16
False
gain16
0
ant17
bw17
0
center_freq17
0
norm_gain17
False
gain17
0
ant18
bw18
0
center_freq18
0
norm_gain18
False
gain18
0
ant19
bw19
0
center_freq19
0
norm_gain19
False
gain19
0
ant1
bw1
0
center_freq1
0
norm_gain1
False
gain1
0
ant20
bw20
0
center_freq20
0
norm_gain20
False
gain20
0
ant21
bw21
0
center_freq21
0
norm_gain21
False
gain21
0
ant22
bw22
0
center_freq22
0
norm_gain22
False
gain22
0
ant23
bw23
0
center_freq23
0
norm_gain23
False
gain23
0
ant24
bw24
0
center_freq24
0
norm_gain24
False
gain24
0
ant25
bw25
0
center_freq25
0
norm_gain25
False
gain25
0
ant26
bw26
0
center_freq26
0
norm_gain26
False
gain26
0
ant27
bw27
0
center_freq27
0
norm_gain27
False
gain27
0
ant28
bw28
0
center_freq28
0
norm_gain28
False
gain28
0
ant29
bw29
0
center_freq29
0
norm_gain29
False
gain29
0
ant2
bw2
0
center_freq2
0
norm_gain2
False
gain2
0
ant30
bw30
0
center_freq30
0
norm_gain30
False
gain30
0
ant31
bw31
0
center_freq31
0
norm_gain31
False
gain31
0
ant3
bw3
0
center_freq3
0
norm_gain3
False
gain3
0
ant4
bw4
0
center_freq4
0
norm_gain4
False
gain4
0
ant5
bw5
0
center_freq5
0
norm_gain5
False
gain5
0
ant6
bw6
0
center_freq6
0
norm_gain6
False
gain6
0
ant7
bw7
0
center_freq7
0
norm_gain7
False
gain7
0
ant8
bw8
0
center_freq8
0
norm_gain8
False
gain8
0
ant9
bw9
0
center_freq9
0
norm_gain9
False
gain9
0
clock_rate
0.0
comment
affinity
dev_addr
""
dev_args
""
_enabled
True
_coordinate
(848, 68)
_rotation
0
id
uhd_usrp_sink_0
type
fc32
clock_source0
sd_spec0
time_source0
clock_source1
sd_spec1
time_source1
clock_source2
sd_spec2
time_source2
clock_source3
sd_spec3
time_source3
clock_source4
sd_spec4
time_source4
clock_source5
sd_spec5
time_source5
clock_source6
sd_spec6
time_source6
clock_source7
sd_spec7
time_source7
nchan
1
num_mboards
1
samp_rate
320000
hide_cmd_port
False
hide_lo_controls
True
stream_args
stream_chans
[]
sync
len_tag_name
otw
analog_wfm_tx_0
uhd_usrp_sink_0
0
0
blocks_multiply_const_vxx_0
rational_resampler_xxx_0
0
0
blocks_wavfile_source_0
blocks_multiply_const_vxx_0
0
0
rational_resampler_xxx_0
analog_wfm_tx_0
0
0
Thu Aug 28 08:28:15 2014
options
author
window_size
1600, 2048
category
RDS
comment
description
_enabled
True
_coordinate
(104, 4)
_rotation
0
generate_options
wx_gui
hier_block_src_path
.:
id
rds_tx
max_nouts
0
qt_qss_theme
realtime_scheduling
run_command
{python} -u {filename}
run_options
prompt
run
True
thread_safe_setters
title
variable
comment
_enabled
True
_coordinate
(352, 4)
_rotation
0
id
data_rate
value
380000
variable
comment
_enabled
True
_coordinate
(440, 4)
_rotation
0
id
fm_max_dev
value
80e3
variable
comment
_enabled
True
_coordinate
(264, 4)
_rotation
0
id
freq
value
95.6e6
variable_slider
comment
converver
float_converter
value
.75
_enabled
True
_coordinate
(544, 4)
_rotation
0
grid_pos
id
input_gain
label
max
10
min
0
notebook
num_steps
100
style
wx.SL_HORIZONTAL
variable_slider
comment
converver
float_converter
value
.09
_enabled
True
_coordinate
(928, 4)
_rotation
0
grid_pos
id
pilot_gain
label
max
3
min
0
notebook
num_steps
100
style
wx.SL_HORIZONTAL
variable_slider
comment
converver
float_converter
value
.05
_enabled
True
_coordinate
(800, 4)
_rotation
0
grid_pos
id
rds_gain
label
max
3
min
0
notebook
num_steps
100
style
wx.SL_HORIZONTAL
variable_slider
comment
converver
float_converter
value
.45
_enabled
True
_coordinate
(672, 4)
_rotation
0
grid_pos
id
stereo_gain
label
max
3
min
0
notebook
num_steps
100
style
wx.SL_HORIZONTAL
blocks_multiply_const_vxx
alias
comment
const
input_gain
affinity
_enabled
True
_coordinate
(392, 764)
_rotation
0
id
blocks_multiply_const_vxx_0
type
float
maxoutbuf
0
minoutbuf
0
vlen
1
blocks_multiply_const_vxx
alias
comment
const
rds_gain
affinity
_enabled
1
_coordinate
(948, 432)
_rotation
270
id
blocks_multiply_const_vxx_0_0
type
float
maxoutbuf
0
minoutbuf
0
vlen
1
blocks_multiply_const_vxx
alias
comment
const
pilot_gain
affinity
_enabled
True
_coordinate
(200, 300)
_rotation
0
id
blocks_multiply_const_vxx_0_0_1
type
float
maxoutbuf
0
minoutbuf
0
vlen
1
blocks_multiply_const_vxx
alias
comment
const
input_gain
affinity
_enabled
True
_coordinate
(400, 812)
_rotation
0
id
blocks_multiply_const_vxx_0_1
type
float
maxoutbuf
0
minoutbuf
0
vlen
1
blocks_repeat
alias
comment
affinity
_enabled
1
_coordinate
(1080, 172)
_rotation
0
id
blocks_repeat_0
interp
160
maxoutbuf
0
minoutbuf
0
type
float
vlen
1
blocks_socket_pdu
alias
comment
affinity
_enabled
1
_coordinate
(24, 148)
_rotation
0
host
id
blocks_socket_pdu_0
mtu
10000
maxoutbuf
0
minoutbuf
0
port
52001
tcp_no_delay
False
type
"TCP_SERVER"
blocks_throttle
alias
comment
affinity
_enabled
True
_coordinate
(232, 764)
_rotation
0
id
blocks_throttle_1
ignoretag
True
maxoutbuf
0
minoutbuf
0
samples_per_second
48000
type
float
vlen
1
blocks_throttle
alias
comment
affinity
_enabled
True
_coordinate
(240, 812)
_rotation
0
id
blocks_throttle_2
ignoretag
True
maxoutbuf
0
minoutbuf
0
samples_per_second
48000
type
float
vlen
1
blocks_wavfile_source
alias
comment
affinity
_enabled
1
file
D:\MyProjects\GNURadio\sound.wav
_coordinate
(16, 768)
_rotation
0
id
blocks_wavfile_source_0
maxoutbuf
0
minoutbuf
0
nchan
2
repeat
True
blocks_add_xx
alias
comment
affinity
_enabled
True
_coordinate
(472, 552)
_rotation
0
id
gr_add_xx_0
type
float
maxoutbuf
0
minoutbuf
0
num_inputs
2
vlen
1
blocks_add_xx
alias
comment
affinity
_enabled
True
_coordinate
(840, 640)
_rotation
270
id
gr_add_xx_1
type
float
maxoutbuf
0
minoutbuf
0
num_inputs
4
vlen
1
blocks_char_to_float
alias
comment
affinity
_enabled
1
_coordinate
(944, 172)
_rotation
0
id
gr_char_to_float_0
maxoutbuf
0
minoutbuf
0
scale
1
vlen
1
digital_diff_encoder_bb
alias
comment
affinity
_enabled
1
_coordinate
(400, 172)
_rotation
0
id
gr_diff_encoder_bb_0
maxoutbuf
0
minoutbuf
0
modulus
2
analog_frequency_modulator_fc
alias
comment
affinity
_enabled
True
_coordinate
(944, 764)
_rotation
0
id
gr_frequency_modulator_fc_0
maxoutbuf
0
minoutbuf
0
sensitivity
2*math.pi*fm_max_dev/data_rate
digital_map_bb
alias
comment
affinity
_enabled
1
_coordinate
(824, 172)
_rotation
0
id
gr_map_bb_0
map
[-1,1]
maxoutbuf
0
minoutbuf
0
digital_map_bb
alias
comment
affinity
_enabled
1
_coordinate
(576, 172)
_rotation
0
id
gr_map_bb_1
map
[1,2]
maxoutbuf
0
minoutbuf
0
blocks_multiply_xx
alias
comment
affinity
_enabled
1
_coordinate
(1432, 232)
_rotation
0
id
gr_multiply_xx_0
type
float
maxoutbuf
0
minoutbuf
0
num_inputs
2
vlen
1
blocks_multiply_xx
alias
comment
affinity
_enabled
True
_coordinate
(768, 360)
_rotation
0
id
gr_multiply_xx_1
type
float
maxoutbuf
0
minoutbuf
0
num_inputs
2
vlen
1
gr_rds_encoder
af1
89.8e6
alias
comment
affinity
_enabled
1
_coordinate
(160, 96)
_rotation
0
id
gr_rds_encoder_0
ms
True
maxoutbuf
0
minoutbuf
0
pi_country_code
13
pi_coverage_area
0
pi_reference_number
147
ps
SDRRADIO
pty_locale
0
pty
0
radiotext
Hello HABR!
ta
False
tp
True
analog_sig_source_x
amp
1
alias
comment
affinity
_enabled
True
freq
38e3
_coordinate
(24, 372)
_rotation
0
id
gr_sig_source_x_0
maxoutbuf
0
minoutbuf
0
offset
0
type
float
samp_rate
data_rate
waveform
analog.GR_SIN_WAVE
analog_sig_source_x
amp
1
alias
comment
affinity
_enabled
1
freq
57e3
_coordinate
(1264, 100)
_rotation
0
id
gr_sig_source_x_0_0
maxoutbuf
0
minoutbuf
0
offset
0
type
float
samp_rate
data_rate
waveform
analog.GR_SIN_WAVE
analog_sig_source_x
amp
1
alias
comment
affinity
_enabled
True
freq
19e3
_coordinate
(24, 268)
_rotation
0
id
gr_sig_source_x_0_1
maxoutbuf
0
minoutbuf
0
offset
0
type
float
samp_rate
data_rate
waveform
analog.GR_SIN_WAVE
blocks_sub_xx
alias
comment
affinity
_enabled
True
_coordinate
(448, 456)
_rotation
0
id
gr_sub_xx_0
type
float
maxoutbuf
0
minoutbuf
0
num_inputs
2
vlen
1
blocks_unpack_k_bits_bb
alias
comment
affinity
_enabled
1
_coordinate
(680, 172)
_rotation
0
id
gr_unpack_k_bits_bb_0
k
2
maxoutbuf
0
minoutbuf
0
import
alias
comment
_enabled
True
_coordinate
(8, 4)
_rotation
0
id
import_0
import
import math
low_pass_filter
beta
6.76
alias
comment
affinity
cutoff_freq
2.5e3
decim
1
_enabled
1
type
interp_fir_filter_fff
_coordinate
(1248, 212)
_rotation
0
gain
1
id
low_pass_filter_0
interp
1
maxoutbuf
0
minoutbuf
0
samp_rate
data_rate
width
.5e3
win
firdes.WIN_HAMMING
low_pass_filter
beta
6.76
alias
comment
affinity
cutoff_freq
15e3
decim
1
_enabled
True
type
interp_fir_filter_fff
_coordinate
(584, 532)
_rotation
0
gain
1
id
low_pass_filter_0_0
interp
1
maxoutbuf
0
minoutbuf
0
samp_rate
data_rate
width
2e3