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

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

В предыдущей части про передачу в GNU Radio был задан вопрос о том, можно ли декодировать протокол LoRa (передача данных для устройств с низким энергопотреблением) с помощью SDR. Мне эта тема показалась интересной, тем более что и сам сигнал у LoRa довольно-таки необычный — так называемая Chirp Spread Spectrum modulation, или «модуляция чирпами».

ukugkay8xzuwpaxj1nnxs0tzduo.png

Как это работает, продолжение под катом.
Кстати, дословный перевод Chirp modulation звучал бы как «модуляция чириканием», но это звучит уж совсем сюрреалистично, так что лучше оставлю простое слово «чирп».

Модуляция LoRa


Как было сказано выше, при передаче LoRa используется способ модуляции при помощи «чирпов», кстати запатентованный компанией Semtech. Если кто хочет подробностей реализации с формулами, можно почитать PDF на сайте semtech или здесь, ну, а если совсем грубо, то один «чирп» — это одно изменение частоты, такими изменениями и кодируется битовый поток, как показано на картинке выше. Параметрами сигнала в LoRa являются SF (spreading factor — по сути, длительность одного «чирпа») и bandwidth — ширина полосы передачи. Параметр SF задается предопределенными значениями SF7 — SF12, где 7 самый быстрый, а 12 — самый медленный режим (для примера можно посмотреть картинку с иллюстрацией разных скоростей «чирпования» с researchgate).

Очевидно, чем меньше длина «чирпа» и чем шире полоса, тем больше можно получить скорость передачи. Все это связано примерно такой таблицей:

r5d9esglzagxbtyqyrb1ypwdlae.png

С точки зрения дальности и помехозащищенности выгодно передавать медленно и печально, но при этом мы во-первых, теряем в скорости, во-вторых, проигрываем во времени, а по правилам LoRa, устройство может передавать не более 1% времени, чтобы не мешать другим устройствам. Так что выбор оптимальной скорости передачи для малопотребляющих устройств задача тоже не простая.

С общим принципом надеюсь, ясно, теперь перейдем к SDR и декодированию.

Железо


Для тестирования я использовал LoRa Click RN2483 и Arduino M0, просто потому что они были в наличии.

egi4_tx77fmtnuqc3wf2zkznckc.png

Это достаточно удобный форм-фактор для прототипирования, т.к. позволяет легко заменить плату одну на другую без пайки (в этом формате, называемом MikroBUS доступно много разной периферии).

Код в черновом варианте, не претендующий на production, добавлен под спойлер. В качестве теста передается значение »1234».

rn2483_tx.ino
// RN2483 Modem and LoRa Click test TX. Tested with Arduino M0

int reset = A2;
int rts = 9; // CS
int cts = 3; // INT

// the setup routine runs once when you press reset:
void setup()
{
    Serial1.begin(57600); // Serial port to radio

    // output LED pin
    pinMode(LED_BUILTIN, OUTPUT);
    
    pinMode(cts, INPUT);
    
    pinMode(rts, OUTPUT);
    digitalWrite(rts, HIGH);

    // Reset rn2483
    pinMode(reset, OUTPUT);
    digitalWrite(reset, LOW);
    delay(100);
    digitalWrite(reset, HIGH);

    delay(100);

    sendCommand("sys get ver\r\n");
    sendCommand("sys get hweui\r\n");

    sendCommand("mac pause\r\n");
    sendCommand("radio set mod lora\r\n");
    sendCommand("radio set pwr -3\r\n"); // the transceiver output power, from -3 to 15
    sendCommand("radio set sf sf8\r\n"); // sf7..sf12, sf7 the fastest spreading factor but gives the shortest range
    // sendCommand("mac set dr 0\r\n"); // data rate: 0-4, 4 faster
    sendCommand("radio set freq 869100000\r\n");
    // sendCommand("radio set afcbw 41.7\r\n");
    sendCommand("radio set rxbw 125\r\n");
    // sendCommand("radio set prlen 8\r\n");
    sendCommand("radio set crc on\r\n");
    // sendCommand("radio set iqi off\r\n");
    sendCommand("radio set cr 4/8\r\n");
    // sendCommand("radio set wdt 60000\r\n"); // disable for continuous reception
    // sendCommand("radio set sync 12\r\n");
    sendCommand("radio set bw 125\r\n");
}

void sendCommand(const char *cmd) 
{
    Serial1.print(cmd);
    String incoming = Serial1.readString();
    // SerialUSB.print(cmd); 
    // SerialUSB.println(incoming); 
}

// the loop routine runs over and over again forever:
void loop()
{
    char data[64] = {0};

    //  hexadecimal value representing the data to be transmitted, from 0 to 255 bytes for LoRa modulation and from 0 to 64 bytes for FSK modulation.
    sprintf(data, "radio tx 1234\r\n");
    sendCommand(data);

    if (msg_num > 10000) msg_num=0;

    digitalWrite(LED_BUILTIN, 1);
    delay(400);
    digitalWrite(LED_BUILTIN, 0);
    delay(600);
}


Кстати, максимальная заявленная дальность передачи для RN2483 составляет до 15 км, на практике, при наличии одноэтажной застройки сигнал пропадает уже за 1 км, и может быть не более 100 м в городских «муравейниках».

Запускаем модем, и приступаем к декодированию.

Декодирование


В самом GNU Radio поддержки LoRa нет, так что придется использовать сторонние компоненты. Их нашлось всего два, и к сожалению, оба автора не проявили никакой фантазии в названии, и назвали их совершенно одинаково — gr-lora (https://github.com/rpp0/gr-lora и https://github.com/BastilleResearch/gr-lora соответственно).

rpp0/gr-lora

Скачать исходники декодера можно с github, сборка стандартна и затруднений не вызывает:

git clone https://github.com/rpp0/gr-lora.git
cd gr-lora
mkdir build
cd build
cmake ..
make && sudo make install && sudo ldconfig


После установки в GNU Radio появляются дополнительные блоки, из которых несложно собрать декодер. В качестве параметров декодера необходимо указать ширину полосы передачи, spreading factor, центральную частоту SDR и частоту приема.

o8gob67thwxeacgv721sw2limtk.png

Блоки в GNU Radio стандартизированы, так что можно использовать любой приемник, например RTL-SDR. Я использовал SDRPlay. Для вывода данных в консоль использовалась простая программа на Python.

udp_receive.py
import socket

UDP_IP = "127.0.0.1"
UDP_PORT = 40868

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((UDP_IP, UDP_PORT))
sock.settimeout(0.5)

while True:
    try:
        data, addr = sock.recvfrom(128) # buffer size is 1024 bytes
        print("Msg:", ' '.join('{:02x}'.format(x) for x in data))

    except socket.timeout:
        pass


Результат работы показан на рисунке.

orz0uq9nksc4y-zaf-r5jtoeuhi.png

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

BastilleResearch/gr-lora

Этот модуль примечателен тем, что может работать не только на прием, но и на передачу. Установка примерно такая же: компонент нужно собрать из исходников.

git clone git://github.com/BastilleResearch/gr-lora.git
cd gr-lora
mkdir build
cd build
cmake ..
make && sudo make install && sudo ldconfig


Connection Graph для данного декодера показан на рисунке.

yaxvcbiamqiossyk_upfhleswv4.png

Как можно видеть, блоков тут побольше. Rotator и Polyphase Resampler выделяют нужную частоту и обрезают лишнее, Demodulator преобразует «чирпы» в бинарный код (на выходе получается последовательность вроде »17 00 3e 00 38 00 2f 00 01 00 39 00 2c 00 30 00 c6 00 18 00 7e 00 d5 00 85 00 e9 00 d8 00 67 00 c4 00»), а Decoder формирует окончательный пакет.

К сожалению, нормально оно так и не заработало. Декодирование определенно работает, во время работы модема данные появляются, но принимаемые сообщения не имеют ничего общего с передаваемыми.

ariehu1hyren-01psl3slb09sn0.png

Причину я так и не понял, то ли где-то ошибся в настройках, то ли этот декодер совместим только со своим же кодером. Желающие могут проверить самостоятельно с помощью Channel Model.

LoRaWAN


Как можно видеть, выше рассматривался нижний, физический уровень передачи. В более высокоуровневом протоколе LoRaWAN поверх помещается еще один логический уровень — с шифрованием, ключами и прочими сервисами. Желающие посмотреть как устроено кодирование, могут попробовать онлайн-декодер здесь.

Заключение


Как можно видеть, декодирование LoRa средствами SDR вполне возможно. Конечно, реальный gateway делать на базе SDR вряд ли целесообразно — его чувствительность будет хуже чувствительности «настоящих» модемов, которые специально рассчитаны на прием слабых сигналов, и имеют более узкополосные фильтры и LNA. Но для тестирования или исследования это может быть вполне интересно.

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

receive1.grc



  Mon Jun  3 09:39:45 2019
  
    options
    
      author
      
    
    
      window_size
      
    
    
      category
      [GRC Hier Blocks]
    
    
      comment
      
    
    
      description
      
    
    
      _enabled
      True
    
    
      _coordinate
      (8, 8)
    
    
      _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
      
    
  
  
    variable
    
      comment
      
    
    
      _enabled
      True
    
    
      _coordinate
      (760, 12)
    
    
      _rotation
      0
    
    
      id
      bw
    
    
      value
      125000.0
    
  
  
    variable
    
      comment
      
    
    
      _enabled
      1
    
    
      _coordinate
      (936, 12)
    
    
      _rotation
      0
    
    
      id
      code_rate
    
    
      value
      4
    
  
  
    variable
    
      comment
      
    
    
      _enabled
      1
    
    
      _coordinate
      (848, 12)
    
    
      _rotation
      0
    
    
      id
      header
    
    
      value
      True
    
  
  
    variable
    
      comment
      
    
    
      _enabled
      1
    
    
      _coordinate
      (680, 12)
    
    
      _rotation
      0
    
    
      id
      ldr
    
    
      value
      True
    
  
  
    variable
    
      comment
      
    
    
      _enabled
      True
    
    
      _coordinate
      (400, 12)
    
    
      _rotation
      0
    
    
      id
      offset
    
    
      value
      -100000.0
    
  
  
    variable
    
      comment
      
    
    
      _enabled
      True
    
    
      _coordinate
      (8, 76)
    
    
      _rotation
      0
    
    
      id
      samp_rate
    
    
      value
      1000000
    
  
  
    variable
    
      comment
      
    
    
      _enabled
      1
    
    
      _coordinate
      (544, 12)
    
    
      _rotation
      0
    
    
      id
      spreading_factor
    
    
      value
      8
    
  
  
    blocks_rotator_cc
    
      alias
      
    
    
      comment
      
    
    
      affinity
      
    
    
      _enabled
      1
    
    
      _coordinate
      (416, 252)
    
    
      _rotation
      0
    
    
      id
      blocks_rotator_cc_0
    
    
      maxoutbuf
      0
    
    
      minoutbuf
      0
    
    
      phase_inc
      (2 * math.pi * offset) / samp_rate
    
  
  
    blocks_socket_pdu
    
      alias
      
    
    
      comment
      
    
    
      affinity
      
    
    
      _enabled
      1
    
    
      _coordinate
      (944, 452)
    
    
      _rotation
      0
    
    
      host
      127.0.0.1
    
    
      id
      blocks_socket_pdu_0
    
    
      mtu
      10000
    
    
      maxoutbuf
      0
    
    
      minoutbuf
      0
    
    
      port
      40868
    
    
      tcp_no_delay
      False
    
    
      type
      "UDP_CLIENT"
    
  
  
    import
    
      alias
      
    
    
      comment
      
    
    
      _enabled
      True
    
    
      _coordinate
      (296, 12)
    
    
      _rotation
      0
    
    
      id
      import_0
    
    
      import
      import math
    
  
  
    lora_decode
    
      alias
      
    
    
      code_rate
      code_rate
    
    
      comment
      
    
    
      affinity
      
    
    
      _enabled
      1
    
    
      header
      header
    
    
      _coordinate
      (648, 452)
    
    
      _rotation
      0
    
    
      id
      lora_decode_0
    
    
      low_data_rate
      ldr
    
    
      maxoutbuf
      0
    
    
      minoutbuf
      0
    
    
      spreading_factor
      spreading_factor
    
  
  
    lora_demod
    
      alias
      
    
    
      comment
      
    
    
      affinity
      
    
    
      _enabled
      1
    
    
      fft_factor
      2
    
    
      beta
      25.0
    
    
      _coordinate
      (384, 452)
    
    
      _rotation
      0
    
    
      id
      lora_demod_0
    
    
      low_data_rate
      ldr
    
    
      maxoutbuf
      0
    
    
      minoutbuf
      0
    
    
      spreading_factor
      spreading_factor
    
  
  
    pfb_arb_resampler_xxx
    
      alias
      
    
    
      comment
      
    
    
      affinity
      
    
    
      _enabled
      1
    
    
      _coordinate
      (656, 292)
    
    
      _rotation
      0
    
    
      id
      pfb_arb_resampler_xxx_0
    
    
      maxoutbuf
      0
    
    
      minoutbuf
      0
    
    
      nfilts
      32
    
    
      rrate
      bw/samp_rate
    
    
      samp_delay
      0
    
    
      atten
      100
    
    
      taps
      
    
    
      type
      ccf
    
  
  
    sdrplay_rsp2_source
    
      agc_enabled
      False
    
    
      antenna
      'A'
    
    
      bw
      400
    
    
      alias
      
    
    
      comment
      
    
    
      affinity
      
    
    
      dc_offset_mode
      True
    
    
      debug_enabled
      False
    
    
      device_serial
      '0'
    
    
      _enabled
      True
    
    
      _coordinate
      (144, 196)
    
    
      _rotation
      0
    
    
      id
      sdrplay_rsp2_source_0
    
    
      if_atten_db
      30
    
    
      ifType
      0
    
    
      iq_balance_mode
      True
    
    
      lna_atten_step
      3
    
    
      lo_mode
      1
    
    
      maxoutbuf
      0
    
    
      minoutbuf
      0
    
    
      rf_freq
      869.0e6
    
    
      sample_rate
      samp_rate
    
  
  
    wxgui_fftsink2
    
      avg_alpha
      0
    
    
      average
      False
    
    
      baseband_freq
      0
    
    
      alias
      
    
    
      comment
      
    
    
      affinity
      
    
    
      _enabled
      True
    
    
      fft_size
      1024
    
    
      freqvar
      None
    
    
      _coordinate
      (1000, 116)
    
    
      _rotation
      0
    
    
      grid_pos
      
    
    
      id
      wxgui_fftsink2_0
    
    
      notebook
      
    
    
      peak_hold
      False
    
    
      ref_level
      0
    
    
      ref_scale
      2.0
    
    
      fft_rate
      15
    
    
      samp_rate
      samp_rate
    
    
      title
      FFT Plot
    
    
      type
      complex
    
    
      win_size
      
    
    
      win
      None
    
    
      y_divs
      10
    
    
      y_per_div
      10
    
  
  
    blocks_rotator_cc_0
    pfb_arb_resampler_xxx_0
    0
    0
  
  
    blocks_rotator_cc_0
    wxgui_fftsink2_0
    0
    0
  
  
    lora_decode_0
    blocks_socket_pdu_0
    out
    pdus
  
  
    lora_demod_0
    lora_decode_0
    out
    in
  
  
    pfb_arb_resampler_xxx_0
    lora_demod_0
    0
    0
  
  
    sdrplay_rsp2_source_0
    blocks_rotator_cc_0
    0
    0
  



receive2.grc



  Mon Jun  3 09:39:45 2019
  
    options
    
      author
      
    
    
      window_size
      
    
    
      category
      [GRC Hier Blocks]
    
    
      comment
      
    
    
      description
      
    
    
      _enabled
      True
    
    
      _coordinate
      (8, 8)
    
    
      _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
      
    
  
  
    variable
    
      comment
      
    
    
      _enabled
      True
    
    
      _coordinate
      (184, 12)
    
    
      _rotation
      0
    
    
      id
      samp_rate
    
    
      value
      1000000
    
  
  
    lora_lora_receiver
    
      bandwidth
      125000
    
    
      alias
      
    
    
      crc
      True
    
    
      center_freq
      869e6
    
    
      channel_list
      [869.1e6]
    
    
      cr
      4
    
    
      comment
      
    
    
      conj
      False
    
    
      affinity
      
    
    
      decimation
      1
    
    
      disable_channelization
      False
    
    
      disable_drift_correction
      False
    
    
      _enabled
      True
    
    
      _coordinate
      (456, 332)
    
    
      _rotation
      0
    
    
      id
      lora_lora_receiver_0
    
    
      implicit
      False
    
    
      maxoutbuf
      0
    
    
      minoutbuf
      0
    
    
      reduced_rate
      False
    
    
      samp_rate
      1e6
    
    
      sf
      8
    
  
  
    lora_message_socket_sink
    
      alias
      
    
    
      comment
      
    
    
      affinity
      
    
    
      _enabled
      True
    
    
      _coordinate
      (696, 364)
    
    
      _rotation
      0
    
    
      id
      lora_message_socket_sink_0
    
    
      ip
      127.0.0.1
    
    
      layer
      1
    
    
      port
      40868
    
  
  
    sdrplay_rsp2_source
    
      agc_enabled
      False
    
    
      antenna
      'A'
    
    
      bw
      400
    
    
      alias
      
    
    
      comment
      
    
    
      affinity
      
    
    
      dc_offset_mode
      True
    
    
      debug_enabled
      False
    
    
      device_serial
      '0'
    
    
      _enabled
      True
    
    
      _coordinate
      (72, 148)
    
    
      _rotation
      0
    
    
      id
      sdrplay_rsp2_source_0
    
    
      if_atten_db
      30
    
    
      ifType
      0
    
    
      iq_balance_mode
      True
    
    
      lna_atten_step
      3
    
    
      lo_mode
      1
    
    
      maxoutbuf
      0
    
    
      minoutbuf
      0
    
    
      rf_freq
      869.0e6
    
    
      sample_rate
      samp_rate
    
  
  
    wxgui_fftsink2
    
      avg_alpha
      0
    
    
      average
      True
    
    
      baseband_freq
      0
    
    
      alias
      
    
    
      comment
      
    
    
      affinity
      
    
    
      _enabled
      True
    
    
      fft_size
      1024
    
    
      freqvar
      None
    
    
      _coordinate
      (688, 108)
    
    
      _rotation
      0
    
    
      grid_pos
      
    
    
      id
      wxgui_fftsink2_0
    
    
      notebook
      
    
    
      peak_hold
      True
    
    
      ref_level
      0
    
    
      ref_scale
      2.0
    
    
      fft_rate
      15
    
    
      samp_rate
      samp_rate
    
    
      title
      FFT Plot
    
    
      type
      complex
    
    
      win_size
      
    
    
      win
      None
    
    
      y_divs
      10
    
    
      y_per_div
      10
    
  
  
    lora_lora_receiver_0
    lora_message_socket_sink_0
    frames
    in
  
  
    sdrplay_rsp2_source_0
    lora_lora_receiver_0
    0
    0
  
  
    sdrplay_rsp2_source_0
    wxgui_fftsink2_0
    0
    0
  



Всем удачных экспериментов.

© Habrahabr.ru