Software Defined Radio - how does it work? Part 7

    Hi, Habr.

    In the previous part about transmission to GNU Radio , the question was asked about whether it is possible to decode the LoRa protocol (data transfer for low-power devices) using SDR. This topic seemed interesting to me, especially since the LoRa signal itself is rather unusual - the so-called Chirp Spread Spectrum modulation, or “chirp modulation”.

    How it works, continued under the cut.

    By the way, the literal translation of Chirp modulation would have sounded like “tweet modulation,” but that sounds completely surreal, so I'd rather leave the simple word “chirp” (as suggested in the comments, in Russian this is called linear-frequency modulation ).

    LoRa Modulation

    As mentioned above, when transmitting LoRa, a modulation method using “chirps” is used, by the way patented by Semtech. If anyone wants implementation details with formulas, you can read the PDF on the semtech website or here , but if it’s very rude, then one “chirp” is one frequency change, and the bit stream is encoded with such changes, as shown in the picture above. Signal parameters in LoRa are SF (spreading factor - in fact, the duration of one "chirp") and bandwidth - transmission bandwidth. The SF parameter is set by the predefined SF7 values ​​- SF12, where 7 is the fastest and 12 is the slowest mode (for example, you can see a picture with an illustration of different speeds of chirping with researchgate ).

    Obviously, the shorter the length of the “chirp” and the wider the band, the more you can get the transmission speed. All this is connected with approximately the following table:

    From the point of view of range and noise immunity, it is advantageous to transmit slowly and sadly, but at the same time we lose speed, secondly, we lose time, and according to LoRa rules, a device can transmit no more than 1 % of the time so as not to interfere with other devices. So the choice of the optimal transmission speed for low-power devices is also not an easy task.

    With a general principle, I hope it’s clear, now let's move on to SDR and decoding.


    For testing, I used the LoRa Click RN2483 and Arduino M0, simply because they were available.

    This is a fairly convenient form factor for prototyping, as it allows you to easily replace the board one with another without soldering (in this format, called MikroBUS , many different peripherals are available).

    The draft code, not pretending to be production, has been added under the spoiler. The value "1234" is passed as a test.

    // 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);
        digitalWrite(reset, HIGH);
        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) 
        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");
        if (msg_num > 10000) msg_num=0;
        digitalWrite(LED_BUILTIN, 1);
        digitalWrite(LED_BUILTIN, 0);

    By the way, the maximum declared transmission range for RN2483 is up to 15 km, in practice, in the presence of a one-story building, the signal disappears already for 1 km, and can be no more than 100 m in urban "anthills".

    We start the modem, and proceed to decoding.


    There is no LoRa support in GNU Radio itself, so you have to use third-party components. There were only two of them, and unfortunately, both authors did not show any imagination in the name, and they called them exactly the same - gr-lora ( and BastilleResearch / gr-lora respectively). Unfortunately, because GNU Radio cannot have both components at once, the installer of one component overwrites the files of the other.

    rpp0 / gr-lora

    You can download the decoder sources from github , the assembly is standard and does not cause difficulties:

    git clone
    cd gr-lora
    mkdir build
    cd build
    cmake ..
    make && sudo make install && sudo ldconfig

    After installation, additional blocks appear in GNU Radio, from which it is easy to assemble a decoder. As the parameters of the decoder, you must specify the transmission bandwidth, spreading factor, the center frequency of the SDR and the reception frequency.

    The units in GNU Radio are standardized so that any receiver, such as RTL-SDR, can be used. I used SDRPlay. A simple Python program was used to output data to the console.
    import socket
    UDP_IP = ""
    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))
    while True:
            data, addr = sock.recvfrom(128) # buffer size is 1024 bytes
            print("Msg:", ' '.join('{:02x}'.format(x) for x in data))
        except socket.timeout:

    The result of the work is shown in the figure.

    As you can see, the row contains the header and end blocks of the transmission, and in the middle we see our data “1234”.

    BastilleResearch / gr-lora

    This module is notable for the fact that it can work not only on reception, but also on transmission. The installation is about the same: the component needs to be assembled from source.

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

    The Connection Graph for this decoder is shown in the figure.

    As you can see, there are more blocks. Rotator and Polyphase Resampler select the desired frequency and cut off the excess, the Demodulator converts the “chirps” into a binary code (the output is a sequence like “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 ”), and Decoder forms the final package.

    Unfortunately, it never worked properly. Decoding definitely works, while the modem is working, the data appears, but the received messages have nothing to do with the transmitted ones.

    I did not understand the reason, either I made a mistake in the settings somewhere, or this decoder is compatible only with my own encoder. Those who wish can check independently using the Channel Model.


    As you can see, the lower, physical layer of transmission was considered here. In the higher-level LoRaWAN protocol, another logical layer is placed on top - with encryption, keys and other services. Those who want to see how encoding works can try the online decoder here .

    By the way, it follows that even if we receive a LoRaWAN signal using SDR, without the presence of encryption keys (one of which is stored on the provider's server), we still do not recognize the contents of the packet.


    As you can see, decoding LoRa using SDR is quite possible. Of course, making a real gateway based on SDR is hardly advisable - its sensitivity will be worse than the sensitivity of "real" modems, which are specially designed to receive weak signals, and have more narrow-band filters and LNA. But for testing or research, this can be quite interesting.

    For those who want to try it on their own, the GNU Radio source grc files are under the spoiler.

    Mon Jun  3 09:39:45 2019optionsauthorwindow_sizecategory[GRC Hier Blocks]commentdescription_enabledTrue_coordinate(8, 8)_rotation0generate_optionswx_guihier_block_src_path.:idtop_blockmax_nouts0qt_qss_themerealtime_schedulingrun_command{python} -u {filename}run_optionspromptrunTruethread_safe_setterstitlevariablecomment_enabledTrue_coordinate(760, 12)_rotation0idbwvalue125000.0variablecomment_enabled1_coordinate(936, 12)_rotation0idcode_ratevalue4variablecomment_enabled1_coordinate(848, 12)_rotation0idheadervalueTruevariablecomment_enabled1_coordinate(680, 12)_rotation0idldrvalueTruevariablecomment_enabledTrue_coordinate(400, 12)_rotation0idoffsetvalue-100000.0variablecomment_enabledTrue_coordinate(8, 76)_rotation0idsamp_ratevalue1000000variablecomment_enabled1_coordinate(544, 12)_rotation0idspreading_factorvalue8blocks_rotator_ccaliascommentaffinity_enabled1_coordinate(416, 252)_rotation0idblocks_rotator_cc_0maxoutbuf0minoutbuf0phase_inc(2 * math.pi * offset) / samp_rateblocks_socket_pdualiascommentaffinity_enabled1_coordinate(944, 452)_rotation0host127.0.0.1idblocks_socket_pdu_0mtu10000maxoutbuf0minoutbuf0port40868tcp_no_delayFalsetype"UDP_CLIENT"importaliascomment_enabledTrue_coordinate(296, 12)_rotation0idimport_0importimport mathlora_decodealiascode_ratecode_ratecommentaffinity_enabled1headerheader_coordinate(648, 452)_rotation0idlora_decode_0low_data_rateldrmaxoutbuf0minoutbuf0spreading_factorspreading_factorlora_demodaliascommentaffinity_enabled1fft_factor2beta25.0_coordinate(384, 452)_rotation0idlora_demod_0low_data_rateldrmaxoutbuf0minoutbuf0spreading_factorspreading_factorpfb_arb_resampler_xxxaliascommentaffinity_enabled1_coordinate(656, 292)_rotation0idpfb_arb_resampler_xxx_0maxoutbuf0minoutbuf0nfilts32rratebw/samp_ratesamp_delay0atten100tapstypeccfsdrplay_rsp2_sourceagc_enabledFalseantenna'A'bw400aliascommentaffinitydc_offset_modeTruedebug_enabledFalsedevice_serial'0'_enabledTrue_coordinate(144, 196)_rotation0idsdrplay_rsp2_source_0if_atten_db30ifType0iq_balance_modeTruelna_atten_step3lo_mode1maxoutbuf0minoutbuf0rf_freq869.0e6sample_ratesamp_ratewxgui_fftsink2avg_alpha0averageFalsebaseband_freq0aliascommentaffinity_enabledTruefft_size1024freqvarNone_coordinate(1000, 116)_rotation0grid_posidwxgui_fftsink2_0notebookpeak_holdFalseref_level0ref_scale2.0fft_rate15samp_ratesamp_ratetitleFFT Plottypecomplexwin_sizewinNoney_divs10y_per_div10blocks_rotator_cc_0pfb_arb_resampler_xxx_000blocks_rotator_cc_0wxgui_fftsink2_000lora_decode_0blocks_socket_pdu_0outpduslora_demod_0lora_decode_0outinpfb_arb_resampler_xxx_0lora_demod_000sdrplay_rsp2_source_0blocks_rotator_cc_000

    Mon Jun  3 09:39:45 2019optionsauthorwindow_sizecategory[GRC Hier Blocks]commentdescription_enabledTrue_coordinate(8, 8)_rotation0generate_optionswx_guihier_block_src_path.:idtop_blockmax_nouts0qt_qss_themerealtime_schedulingrun_command{python} -u {filename}run_optionspromptrunTruethread_safe_setterstitlevariablecomment_enabledTrue_coordinate(184, 12)_rotation0idsamp_ratevalue1000000lora_lora_receiverbandwidth125000aliascrcTruecenter_freq869e6channel_list[869.1e6]cr4commentconjFalseaffinitydecimation1disable_channelizationFalsedisable_drift_correctionFalse_enabledTrue_coordinate(456, 332)_rotation0idlora_lora_receiver_0implicitFalsemaxoutbuf0minoutbuf0reduced_rateFalsesamp_rate1e6sf8lora_message_socket_sinkaliascommentaffinity_enabledTrue_coordinate(696, 364)_rotation0idlora_message_socket_sink_0ip127.0.0.1layer1port40868sdrplay_rsp2_sourceagc_enabledFalseantenna'A'bw400aliascommentaffinitydc_offset_modeTruedebug_enabledFalsedevice_serial'0'_enabledTrue_coordinate(72, 148)_rotation0idsdrplay_rsp2_source_0if_atten_db30ifType0iq_balance_modeTruelna_atten_step3lo_mode1maxoutbuf0minoutbuf0rf_freq869.0e6sample_ratesamp_ratewxgui_fftsink2avg_alpha0averageTruebaseband_freq0aliascommentaffinity_enabledTruefft_size1024freqvarNone_coordinate(688, 108)_rotation0grid_posidwxgui_fftsink2_0notebookpeak_holdTrueref_level0ref_scale2.0fft_rate15samp_ratesamp_ratetitleFFT Plottypecomplexwin_sizewinNoney_divs10y_per_div10lora_lora_receiver_0lora_message_socket_sink_0framesinsdrplay_rsp2_source_0lora_lora_receiver_000sdrplay_rsp2_source_0wxgui_fftsink2_000

    All successful experiments.

    Also popular now: