Software Defined Radio — как это работает? Часть 6

Привет, Хабр.

В предыдущей части мы рассмотрели возможность передачи простых сигналов с помощью GNU Radio. Сейчас мы пойдем дальше, и посмотрим, как передать что-нибудь посложнее. Начнем с радиолюбительских сигналов WSPR, а затем создадим работающий программный QAM-модем.

mlh20uqpjbuvfppveslwfemlhqu.png

И как и в предыдущем случае, мы сделаем это, не написав ни одной строчки кода, программа также будет кроссплатформенной, и сможет работать как под 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.

wyvn1uesjkbq5kun144_snvq_3i.png

Данный способ не претендует на максимальную эффективность, зато он довольно простой и понятный. Сигнал WSPR изначально находится на частоте 1500Гц, записанный wav-файл имеет частоту дискретизации 22050с/сек. Сначала мы ресемплируем сигнал в 57/5 раз, чтобы привести частоту дискретизации к требуемым 250.000с/сек. Затем мы сдвигаем частоту вверх на 10КГц (полезный сигнал при этом будет на частоте 11.5КГц), переводим сигнал в комплексный вид, требуемый приемнику, и вырезаем фильтром лишнее, оставляя частоты 11–12КГц.

Сигналы WSPR привязаны ко времени, и передаются каждую четную минуту (0:00, 0:02 и пр). Я запускал передачу в GNU Radio вручную, «на глаз» определяя интервал по часам, желающие могут дописать скрипт в Python для автоматического начала передачи.

Ждем нужное время, включаем передатчик, приемник, и проверяем результат.

ovml29pcsfrcd9s1xibh5aawzdg.png

При желании к программе также можно добавить автоматическую генерацию WSPR-файла на основе входных данных (позывного, местоположения, мощности передачи), примеры генерации WSPR для Python можно взять на github.

Интересно заметить, что на 432МГц дрифт частоты уже довольно заметен, хотя сигнал еще декодируется. А вот на частоте 1.3ГГц дрифт становится настолько большим, что WSPR уже не принимается — к SDR нужен внешний опорный генератор с более стабильным сигналом (или хотя бы программная коррекция частоты при передаче, хотя это менее удобно).

Если SDR позволяет передавать на низких частотах, то можно попробовать передачу и на КВ-диапазоне. Таким образом с HackRF удавалось передать сигнал на 1000 км на 14МГц с комнатной антенны, что можно считать неплохим результатом. Высокие частоты (433МГц и 1.3ГГц) пожалуй даже более интересны для опытов, но сигналы передаются только в прямой видимости, так что для таких экспериментов нужен второй участник на приемной стороне. Второй плюс таких тестов — на КВ в wspr не передавал только ленивый, а вот высокие частоты гораздо менее освоены.

QAM Модем


Пойдем дальше. WSPR достаточно несложный формат, сделаем что-нибудь поинтереснее — полноценный (ну почти) модем. При квадратурно-амплитудной модуляции одновременно изменяется и амплитуда, и фаза сигнала, что позволяет передавать данные с большей скоростью (но и занимаемая полоса также больше).

Рассмотрим первую часть — передатчик.

wwftou5marfw3ha-0wo_jvvvhus.png

Как можно видеть, мы читаем данные из файла data.txt, затем с частотой семплирования 25КГц посылаем данные на энкодер пакетов, который преобразует поток в 4х-битный код. Этот поток поступает на квадратурный модулятор, затем частота семплирования увеличивается до частоты передатчика 250КГц, и сигнал сдвигается вверх на 80КГц (у многих приемников есть пик на нулевой частоте, и это будет мешать). Компонент Constellation Rect задает параметры модуляции — количество символов и сдвиг фаз и амплитуд.

Первая часть готова. Запускаем «передачу» и видим наш сигнал.

e0ufpeo2hlppwkie5-n80paqi-i.png

Мы можем протестировать наш передатчик, даже не имея никакой аппаратуры — для этого есть специальный блок Channel Model — модель канала связи. Там можно задать шум, сдвиг частоты и пр.

Вот так выглядит наш сигнал до и после передачи. Кстати, как можно видеть все «гармоники» передатчика ниже 120Дб ушли гораздо ниже уровня шумов.

t3jfbbafy5ublqufx67rdw2ypi8.png

Теперь прием. Фактически то же самое, только в обратном порядке.

jijanlsmccpm5prtzvke8mdhlkg.png

Отдельно можно остановиться на последнем блоке 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, и видим в консоли принятые сообщения.

ts5evwfd6mcy6zmjmhnhvy1hhhw.png

Как можно видеть, все работает, и ни приемника, ни антенн можно не иметь :)

Для тех, кто захочет повторить эксперименты, grc-файл проекта под спойлером. Должно работать и под Linux и под Windows.

qam_test.grc



  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:\MyProjects\GNURadio\data.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 — довольно интересная программа для разной работы с сигналами, в которой можно делать много разных интересных вещей. Если у аудитории не пропадет интерес (есть чувство, что я углубляюсь в узкие темы, которые большинству уже мало интересны), можно попробовать рассмотреть передачу чего-то более интересного, например видео.

© Habrahabr.ru