В предыдущей части было рассмотрено декодирование сигналов 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 в качестве контрольного приемника, результат на КДПВ.
Разумеется, перед тестированием необходимо выбрать свободную частоту, чтобы на ней не было станций, ну и желательно не превышать разрешенную мощность. Для тех же, кто захочет получить максимум дальности можно докупить усилитель, желательно сделать антенну на нужную частоту, чтобы был хотя бы диполь 1/4 длины волны.
Заключение
Как можно видеть, в передаче 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:MyProjectsGNURadiosound.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:MyProjectsGNURadiosound.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 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, 396) _rotation 0 gain 1 id low_pass_filter_0_0_0 interp 1 maxoutbuf 0 minoutbuf 0 samp_rate data_rate width 2e3 win firdes.WIN_HAMMING rational_resampler_xxx alias comment affinity decim 6 _enabled True fbw 0 _coordinate (168, 444) _rotation 0 id rational_resampler_xxx_0 interp 48 maxoutbuf 0 minoutbuf 0 taps type fff rational_resampler_xxx alias comment affinity decim 6 _enabled True fbw 0 _coordinate (160, 540) _rotation 0 id rational_resampler_xxx_0_0 interp 48 maxoutbuf 0 minoutbuf 0 taps type fff rational_resampler_xxx alias comment affinity decim 38 _enabled 1 fbw 0 _coordinate (1104, 740) _rotation 0 id rational_resampler_xxx_1 interp 50 maxoutbuf 0 minoutbuf 0 taps type ccc uhd_usrp_sink alias ant0 TX/RX bw0 0 center_freq0 freq norm_gain0 False gain0 50 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 1 _coordinate (1296, 724) _rotation 0 id uhd_usrp_sink 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 500000 hide_cmd_port False hide_lo_controls True stream_args stream_chans [] sync len_tag_name otw wxgui_fftsink2 avg_alpha 0 average False baseband_freq 0 alias comment affinity _enabled 1 fft_size 1024 freqvar None _coordinate (1104, 520) _rotation 0 grid_pos id wxgui_fftsink2_0 notebook peak_hold False ref_level 0 ref_scale 2.0 fft_rate 30 samp_rate data_rate title FFT Plot type float win_size win None y_divs 10 y_per_div 20 blocks_multiply_const_vxx_0 rational_resampler_xxx_0 0 0 blocks_multiply_const_vxx_0_0 gr_add_xx_1 0 0 blocks_multiply_const_vxx_0_0_1 gr_add_xx_1 0 1 blocks_multiply_const_vxx_0_1 rational_resampler_xxx_0_0 0 0 blocks_repeat_0 low_pass_filter_0 0 0 blocks_socket_pdu_0 gr_rds_encoder_0 pdus rds in blocks_throttle_1 blocks_multiply_const_vxx_0 0 0 blocks_throttle_2 blocks_multiply_const_vxx_0_1 0 0 blocks_wavfile_source_0 blocks_throttle_1 0 0 blocks_wavfile_source_0 blocks_throttle_2 1 0 gr_add_xx_0 low_pass_filter_0_0 0 0 gr_add_xx_1 gr_frequency_modulator_fc_0 0 0 gr_add_xx_1 wxgui_fftsink2_0 0 0 gr_char_to_float_0 blocks_repeat_0 0 0 gr_diff_encoder_bb_0 gr_map_bb_1 0 0 gr_frequency_modulator_fc_0 rational_resampler_xxx_1 0 0 gr_map_bb_0 gr_char_to_float_0 0 0 gr_map_bb_1 gr_unpack_k_bits_bb_0 0 0 gr_multiply_xx_0 blocks_multiply_const_vxx_0_0 0 0 gr_multiply_xx_1 gr_add_xx_1 0 2 gr_rds_encoder_0 gr_diff_encoder_bb_0 0 0 gr_sig_source_x_0 gr_multiply_xx_1 0 0 gr_sig_source_x_0_0 gr_multiply_xx_0 0 0 gr_sig_source_x_0_1 blocks_multiply_const_vxx_0_0_1 0 0 gr_sub_xx_0 low_pass_filter_0_0_0 0 0 gr_unpack_k_bits_bb_0 gr_map_bb_0 0 0 low_pass_filter_0 gr_multiply_xx_0 0 1 low_pass_filter_0_0 gr_add_xx_1 0 3 low_pass_filter_0_0_0 gr_multiply_xx_1 0 1 rational_resampler_xxx_0 gr_add_xx_0 0 0 rational_resampler_xxx_0 gr_sub_xx_0 0 0 rational_resampler_xxx_0_0 gr_add_xx_0 0 1 rational_resampler_xxx_0_0 gr_sub_xx_0 0 1 rational_resampler_xxx_1 uhd_usrp_sink 0 0
И как обычно, всем удачных экспериментов. И не создавайте помехи другим радиостанциям.
Источник