В предыдущей части мы рассмотрели возможность передачи простых сигналов с помощью GNU Radio. Сейчас мы пойдем дальше, и посмотрим, как передать что-нибудь посложнее. Начнем с радиолюбительских сигналов WSPR, а затем создадим работающий программный QAM-модем.
И как и в предыдущем случае, мы сделаем это, не написав ни одной строчки кода, программа также будет кроссплатформенной, и сможет работать как под OSX/Linux, так и под Windows. Я также покажу, как отлаживать модем средствами GNU Radio, вообще не имея никакого «железа».
Для тех, кто не пользовался GNU Radio, рекомендуется прочитать части 4 и 5, где описывались принципы работы с программой.
WSPR
Начнем с более простого, с WSPR — этот вид связи специально делался для тестов распространения слабых сигналов, т.е. то что нам и нужно — уровень мощности устройств типа LimeSDR не более 100мВт. Сигнал WSPR передается о очень малой скоростью (2 минуты на сообщение из примерно 30 байт) в очень узкой полосе, что позволяет принимать его даже ниже уровня шумов. Передадим такой сигнал с помощью GNU Radio.
Для начала сигнал надо записать. Для этого возьмем программу WSJT, выставим все необходимые параметры (мощность, позывной, местоположение и пр). Укажем в настройках в качестве выходного аудиоустройства Virtual Audio Cable, и запишем сигнал в WAV. Паузы по краям нужно обрезать в любом редакторе (например Cool Edit), в итоге у нас должен получиться файл длительностью примерно в 2 минуты.
Теперь создаем граф в GNU Radio Companion.
Данный способ не претендует на максимальную эффективность, зато он довольно простой и понятный. Сигнал WSPR изначально находится на частоте 1500Гц, записанный wav-файл имеет частоту дискретизации 22050с/сек. Сначала мы ресемплируем сигнал в 57/5 раз, чтобы привести частоту дискретизации к требуемым 250.000с/сек. Затем мы сдвигаем частоту вверх на 10КГц (полезный сигнал при этом будет на частоте 11.5КГц), переводим сигнал в комплексный вид, требуемый приемнику, и вырезаем фильтром лишнее, оставляя частоты 11-12КГц.
Сигналы WSPR привязаны ко времени, и передаются каждую четную минуту (0:00, 0:02 и пр). Я запускал передачу в GNU Radio вручную, «на глаз» определяя интервал по часам, желающие могут дописать скрипт в Python для автоматического начала передачи.
Ждем нужное время, включаем передатчик, приемник, и проверяем результат.
При желании к программе также можно добавить автоматическую генерацию WSPR-файла на основе входных данных (позывного, местоположения, мощности передачи), примеры генерации WSPR на Python можно взять на github.
Интересно заметить, что на 432МГц дрифт частоты уже довольно заметен, хотя сигнал еще декодируется. А вот на частоте 1.3ГГц дрифт становится настолько большим, что WSPR уже не принимается — к SDR нужен внешний опорный генератор с более стабильным сигналом (или хотя бы программная коррекция частоты при передаче, хотя это менее удобно).
Если SDR позволяет передавать на низких частотах, то можно попробовать передачу и на КВ-диапазоне. Таким образом с HackRF удавалось передать сигнал на 1000км на 14МГц с комнатной антенны, что можно считать неплохим результатом. Высокие частоты (433МГц и 1.3ГГц) пожалуй даже более интересны для опытов, но сигналы передаются только в прямой видимости, так что для таких экспериментов нужен второй участник на приемной стороне. Второй плюс таких тестов — на КВ в wspr не передавал только ленивый, а вот высокие частоты гораздо менее освоены.
QAM Модем
Пойдем дальше. WSPR достаточно несложный формат, сделаем что-нибудь поинтереснее — полноценный (ну почти) модем. При квадратурно-амплитудной модуляции одновременно изменяется и амплитуда, и фаза сигнала, что позволяет передавать данные с большей скоростью (но и занимаемая полоса также больше).
Рассмотрим первую часть — передатчик.
Как можно видеть, мы читаем данные из файла data.txt, затем с частотой семплирования 25КГц посылаем данные на энкодер пакетов, который преобразует поток в 4х-битный код. Этот поток поступает на квадратурный модулятор, затем частота семплирования увеличивается до частоты передатчика 250КГц, и сигнал сдвигается вверх на 80КГц (у многих приемников есть пик на нулевой частоте, и это будет мешать). Компонент Constellation Rect задает параметры модуляции — количество символов и сдвиг фаз и амплитуд.
Первая часть готова. Запускаем «передачу» и видим наш сигнал.
Мы можем протестировать наш передатчик, даже не имея никакой аппаратуры — для этого есть специальный блок Channel Model — модель канала связи. Там можно задать шум, сдвиг частоты и пр.
Вот так выглядит наш сигнал до и после передачи. Кстати, как можно видеть все «гармоники» передатчика ниже 120Дб ушли гораздо ниже уровня шумов.
Теперь прием. Фактически то же самое, только в обратном порядке.
Отдельно можно остановиться на последнем блоке UDP Sink. Непонятно почему, но в GNU Radio нет никакого компонента для просмотра текстовых данных. Поэтому мы просто посылаем данные по UDP на любой локальный порт (я выбрал 999).
Для приема напишем несложную программу на Python.
import socket UDP_IP = "127.0.0.1" UDP_PORT = 999 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP sock.bind((UDP_IP, UDP_PORT)) sock.settimeout(1.0) while True: try: data, addr = sock.recvfrom(64) # buffer size is 64 bytes print("Msg:", data) except socket.timeout: pass
Результат: запускаем скрипт, запускаем GNU Radio, и видим в консоли принятые сообщения.
Как можно видеть, все работает, и ни приемника, ни антенн можно не иметь 🙂
Для тех, кто захочет повторить эксперименты, grc-файл проекта под спойлером. Должно работать и под Linux и под Windows.
Mon May 27 21:52:42 2019 options author window_size category [GRC Hier Blocks] comment description _enabled True _coordinate (8, 8) _rotation 0 generate_options qt_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 variable comment _enabled True _coordinate (1144, 172) _rotation 0 id excess_bw value 0.35 variable comment _enabled True _coordinate (1104, 436) _rotation 0 id nfilts value 32 variable comment _enabled True _coordinate (1096, 588) _rotation 0 id nfilts_0 value 32 variable_constellation_rect comment const_points [0.707+0.707j, -0.707+0.707j, -0.707-0.707j, 0.707-0.707j] _enabled True _coordinate (1104, 16) _rotation 0 id qpsk imag_sect 2 real_sect 2 rot_sym 4 soft_dec_lut None precision 8 sym_map [0, 1, 2, 3] w_imag_sect 1 w_real_sect 1 variable comment _enabled True _coordinate (1104, 372) _rotation 0 id rrc_taps value firdes.root_raised_cosine(nfilts, nfilts, 1.0/float(sps), 0.35, 45*nfilts) variable comment _enabled True _coordinate (1112, 508) _rotation 0 id rrc_taps_0 value firdes.root_raised_cosine(nfilts, nfilts, 1.0/float(sps), 0.35, 45*nfilts) variable comment _enabled True _coordinate (168, 12) _rotation 0 id samp_rate value 250000 variable comment _enabled True _coordinate (1144, 244) _rotation 0 id sps value 4 variable comment _enabled True _coordinate (1104, 308) _rotation 0 id timing_loop_bw value 6.28/100.0 analog_sig_source_x amp 1 alias comment affinity _enabled True freq 80000 _coordinate (664, 20) _rotation 0 id analog_sig_source_x_0 maxoutbuf 0 minoutbuf 0 offset 0 type complex samp_rate samp_rate waveform analog.GR_COS_WAVE analog_sig_source_x amp 1 alias comment affinity _enabled True freq -80000 _coordinate (48, 540) _rotation 0 id analog_sig_source_x_1 maxoutbuf 0 minoutbuf 0 offset 0 type complex samp_rate samp_rate waveform analog.GR_COS_WAVE blks2_packet_decoder access_code alias comment affinity _enabled 1 _coordinate (296, 676) _rotation 0 id blks2_packet_decoder_0 maxoutbuf 0 minoutbuf 0 type byte threshold -1 blks2_packet_encoder access_code bits_per_symbol 4 alias comment affinity _enabled 1 _coordinate (224, 76) _rotation 0 id blks2_packet_encoder_0 type byte maxoutbuf 0 minoutbuf 0 pad_for_usrp True payload_length 0 preamble samples_per_symbol 4 blocks_file_source alias comment affinity _enabled 1 file D:MyProjectsGNURadiodata.txt _coordinate (8, 92) _rotation 0 id blocks_file_source_0 maxoutbuf 0 minoutbuf 0 type byte repeat True vlen 1 blocks_multiply_xx alias comment affinity _enabled True _coordinate (920, 88) _rotation 0 id blocks_multiply_xx_0 type complex maxoutbuf 0 minoutbuf 0 num_inputs 2 vlen 1 blocks_multiply_xx alias comment affinity _enabled True _coordinate (224, 496) _rotation 0 id blocks_multiply_xx_1 type complex maxoutbuf 0 minoutbuf 0 num_inputs 2 vlen 1 blocks_throttle alias comment affinity _enabled 1 _coordinate (96, 196) _rotation 0 id blocks_throttle_1 ignoretag True maxoutbuf 0 minoutbuf 0 samples_per_second 25000 type byte vlen 1 blocks_udp_sink alias comment affinity ipaddr 127.0.0.1 port 999 _enabled 1 _coordinate (680, 660) _rotation 0 id blocks_udp_sink_0 type byte psize 64 eof True vlen 1 channels_channel_model alias block_tags False comment affinity _enabled 1 epsilon 1.0 freq_offset 0.0 _coordinate (504, 284) _rotation 180 id channels_channel_model_0 maxoutbuf 0 minoutbuf 0 noise_voltage 0.1 seed 0 taps 1.0 + 1.0j digital_qam_demod alias comment affinity differential True _enabled 1 excess_bw 0.35 freq_bw 6.28/100.0 _coordinate (672, 456) _rotation 0 mod_code "gray" id digital_qam_demod_0 log False maxoutbuf 0 minoutbuf 0 constellation_points 4 phase_bw 6.28/100.0 samples_per_symbol 4 timing_bw 6.28/100.0 verbose False digital_qam_mod alias comment affinity differential True _enabled True excess_bw 0.35 _coordinate (384, 116) _rotation 0 mod_code "gray" id digital_qam_mod_0 log False maxoutbuf 0 minoutbuf 0 constellation_points 4 samples_per_symbol 4 verbose False low_pass_filter beta 6.76 alias comment affinity cutoff_freq 12000 decim 1 _enabled True type fir_filter_ccf _coordinate (320, 460) _rotation 0 gain 1 id low_pass_filter_0 interp 1 maxoutbuf 0 minoutbuf 0 samp_rate samp_rate width 1000 win firdes.WIN_HAMMING qtgui_const_sink_x autoscale False axislabels True alias comment affinity _enabled 1 _coordinate (456, 20) gui_hint _rotation 0 grid False id qtgui_const_sink_x_0 legend True alpha1 1.0 color1 "blue" label1 marker1 0 style1 0 width1 1 alpha10 1.0 color10 "red" label10 marker10 0 style10 0 width10 1 alpha2 1.0 color2 "red" label2 marker2 0 style2 0 width2 1 alpha3 1.0 color3 "red" label3 marker3 0 style3 0 width3 1 alpha4 1.0 color4 "red" label4 marker4 0 style4 0 width4 1 alpha5 1.0 color5 "red" label5 marker5 0 style5 0 width5 1 alpha6 1.0 color6 "red" label6 marker6 0 style6 0 width6 1 alpha7 1.0 color7 "red" label7 marker7 0 style7 0 width7 1 alpha8 1.0 color8 "red" label8 marker8 0 style8 0 width8 1 alpha9 1.0 color9 "red" label9 marker9 0 style9 0 width9 1 name "" nconnections 1 size 1024 tr_chan 0 tr_level 0.0 tr_mode qtgui.TRIG_MODE_FREE tr_slope qtgui.TRIG_SLOPE_POS tr_tag "" type complex update_time 0.10 xmax 2 xmin -2 ymax 2 ymin -2 rational_resampler_xxx alias comment affinity decim 1 _enabled True fbw 0 _coordinate (648, 148) _rotation 0 id rational_resampler_xxx_0 interp 10 maxoutbuf 0 minoutbuf 0 taps type ccc rational_resampler_xxx alias comment affinity decim 10 _enabled True fbw 0 _coordinate (480, 484) _rotation 0 id rational_resampler_xxx_0_0 interp 1 maxoutbuf 0 minoutbuf 0 taps type ccc analog_sig_source_x_0 blocks_multiply_xx_0 0 0 analog_sig_source_x_1 blocks_multiply_xx_1 0 1 blks2_packet_decoder_0 blocks_udp_sink_0 0 0 blks2_packet_encoder_0 digital_qam_mod_0 0 0 blocks_file_source_0 blocks_throttle_1 0 0 blocks_multiply_xx_0 channels_channel_model_0 0 0 blocks_multiply_xx_1 low_pass_filter_0 0 0 blocks_throttle_1 blks2_packet_encoder_0 0 0 channels_channel_model_0 blocks_multiply_xx_1 0 0 digital_qam_demod_0 blks2_packet_decoder_0 0 0 digital_qam_mod_0 qtgui_const_sink_x_0 0 0 digital_qam_mod_0 rational_resampler_xxx_0 0 0 low_pass_filter_0 rational_resampler_xxx_0_0 0 0 rational_resampler_xxx_0 blocks_multiply_xx_0 0 1 rational_resampler_xxx_0_0 digital_qam_demod_0 0 0
Хорошее руководство по созданию более сложного модема есть на сайте GNU Radio, но они используют custom block для демодуляции, запустить который удалось только под Linux. В вышеприведенном примере с этим проблем нет.
Заключение
Как можно видеть, GNU Radio — довольно интересная программа для разной работы с сигналами, в которой можно делать много разных интересных вещей. Если у аудитории не пропадет интерес (у меня есть чувство, что я углубляюсь в узкие темы, которые большинству уже мало интересны), можно попробовать рассмотреть передачу чего-то более интересного, например видео.
Источник