RAKwireless WisBlock talks LoRa with PineCone BL602 RISC-V Board

📝 11 Mar 2021

Suppose we've created a wireless LoRa Sensor.

(Maybe a sensor that monitors the soil moisture in our home garden)

Is there a simple way to check...

  1. Whether our LoRa Sensor is transmitting packets correctly...

  2. And what's the Wireless Range of our LoRa Sensor?

Today we shall install RAKwireless WisBlock to check the packets transmitted by our LoRa Sensor.

We'll be testing WisBlock with a LoRa Sensor built with the PineCone BL602 RISC-V Board...

Many thanks to RAKwireless for providing the WisBlock Connected Box!

RAKwireless WisBlock LPWAN Module mounted on WisBlock Base Board

RAKwireless WisBlock LPWAN Module mounted on WisBlock Base Board

1 Connect WisBlock

Connect the following components according to the pic above...

  1. WisBlock LPWAN Module: This is the Nordic nRF52840 Microcontroller with Semtech SX1262 LoRa Transceiver. (More about this)

    Mount the LPWAN Module onto the WisBlock Base Board.

    (The LPWAN Module is already mounted when get the WisBlock Connected Box)

  2. WisBlock Base Board: This provides power to the LPWAN Module and exposes the USB and I/O ports. (More about this)

    The LPWAN Module should be mounted on the Base Board.

  3. LoRa Antenna: Connect the LoRa Antenna to the LPWAN Module.

    (That's the black rod. Use the Antenna Adapter Cable)

  4. Bluetooth LE Antenna: Connect the Bluetooth LE Antenna to the LPWAN Module.

    (The stringy flappy thingy)

CAUTION: Always connect the LoRa Antenna and Bluetooth LE Antenna before Powering On... Or the LoRa and Bluetooth Transceivers may get damaged! See this

The above components are shipped in the WisBlock Connected Box. (Which includes many more goodies!)

For the LPWAN Module, be sure to choose the right LoRa Frequency for your region...

RAKwireless WisBlock Connected Box

RAKwireless WisBlock Connected Box

2 Initialise LoRa Transceiver

WisBlock is based on the powerful nRF52840 Microcontroller. Do we program it with FreeRTOS, Zephyr, Mynewt, ...?

Here's the surprisingly thing about WisBlock... We program WisBlock with Arduino! (C++)

The Arduino Drivers for Semtech SX1262 will work fine with WisBlock.

(Most other Arduino Drivers will run fine too!)

But Arduino doesn't support Multitasking... No?

Here's another surprisingly thing about WisBlock... WisBlock Arduino is based on FreeRTOS!

(Technically: WisBlock is based on the Adafruit nRF52 Arduino Framework, which is based on FreeRTOS. See this)

So Multitasking Firmware coded in FreeRTOS will run fine on WisBlock.

Arduino programs will generally expose these two functions...

  1. Setup Function that's run when the micrcontroller starts up

  2. Loop Function that's called repeatedly to handle events

Let's see what happens inside the Setup and Loop Functions for our WisBlock LoRa Receiver.

2.1 Setup Function

In the Setup Function, we start by initialising the LoRa Module and the Serial Port: main.cpp

//  Setup Function is called upon startup
void setup() {

    //  Initialize the LoRa Module
    lora_rak4630_init();

    //  Initialize the Serial Port for debug output
    Serial.begin(115200);
    while (!Serial) { delay(10); }

Next we set the Callback Functions that will be triggered by the LoRa Driver...

    //  Set the LoRa Callback Functions
    RadioEvents.TxDone    = NULL;
    RadioEvents.RxDone    = OnRxDone;
    RadioEvents.TxTimeout = NULL;
    RadioEvents.RxTimeout = OnRxTimeout;
    RadioEvents.RxError   = OnRxError;
    RadioEvents.CadDone   = NULL;

The Callback Functions are...

RadioEvents has been defined earlier like so...

//  Callback Functions for LoRa Events
static RadioEvents_t RadioEvents;

We initialise the LoRa Transceiver and register the Callback Functions with the LoRa Driver...

    //  Initialize the LoRa Transceiver
    Radio.Init(&RadioEvents);

We set the LoRa Frequency (434, 780, 868, 915 or 923 MHz) which depends on your region. (More about this in a while)

    //  Set the LoRa Frequency
    Radio.SetChannel(RF_FREQUENCY);

Then we set the LoRa Parameters for receiving the packets...

    //  Configure the LoRa Transceiver for receiving messages
    Radio.SetRxConfig(
        MODEM_LORA, 
        LORA_BANDWIDTH, 
        LORA_SPREADING_FACTOR,
        LORA_CODINGRATE, 
        0,        //  AFC bandwidth: Unused with LoRa
        LORA_PREAMBLE_LENGTH,
        LORA_SYMBOL_TIMEOUT, 
        LORA_FIX_LENGTH_PAYLOAD_ON,
        0,        //  Fixed payload length: N/A
        true,     //  CRC enabled
        0,        //  Frequency hopping disabled
        0,        //  Hop period: N/A
        LORA_IQ_INVERSION_ON, 
        true      //  Continuous receive mode
    );

These must match the LoRa Parameters used in our LoRa Transmitter. (More about this later)

Finally we ask the LoRa Driver to start receiving LoRa Packets...

    //  Start receiving LoRa packets
    Radio.Rx(RX_TIMEOUT_VALUE);
}

The timeout value RX_TIMEOUT_VALUE is set to 3 seconds.

2.2 Loop Function

After calling the Setup Function, the Arduino Framework calls the Loop Function repeatedly to handle events.

At the start of the Loop Function, we handle the Callback Functions triggered by the LoRa Transceiver: main.cpp

//  Loop Function is called repeatedly to handle events
void loop() {
    //  Handle Radio events
    Radio.IrqProcess();

Finally we yield control to FreeRTOS, to allow other tasks to run...

    //  We are on FreeRTOS, give other tasks a chance to run
    delay(100);
    yield();
}

The code in this article is based on the (now obsolete) WisBlock LoRa Receiver Example: LoRaP2P_RX.ino

(And it bears a striking resemblance to the code for PineCone BL602 LoRa)

LoRa pushing 64-byte packets from BL602 to WisBlock

LoRa pushing 64-byte packets from BL602 to WisBlock

3 Receive LoRa Packets

We have prepped our WisBlock LoRa Transceiver to receive packets... Let's watch how we handle the received LoRa Packets.

3.1 Receive Callback Function

The Callback Function OnRxDone is triggered by the LoRa Driver whenever a LoRa Packet has been received successfully.

First we show the Timestamp (in seconds), Signal Strength and Signal To Noise Ratio: main.cpp

//  Callback Function to be executed on Packet Received event
void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr) {
    //  We have received a valid packet. Show the timestamp in seconds.
    Serial.printf("OnRxDone: Timestamp=%d, ", millis() / 1000);

    //  Show the signal strength, signal to noise ratio
    Serial.printf("RssiValue=%d dBm, SnrValue=%d, Data=", rssi, snr);

We pause a short while and copy the received packet into our 64-byte buffer RcvBuffer...

    delay(10);
    memcpy(RcvBuffer, payload, size);

RcvBuffer is defined as a 64-byte buffer...

    //  Buffer for received LoRa Packet
    static uint8_t RcvBuffer[64];

Then we display the 64 bytes received...

    //  Show the packet received
    for (int idx = 0; idx < size; idx++) {
        Serial.printf("%02X ", RcvBuffer[idx]);
    }
    Serial.println("");

Finally we ask the LoRa Driver to receive the next packet...

    //  Receive the next packet
    Radio.Rx(RX_TIMEOUT_VALUE);
}

The timeout value RX_TIMEOUT_VALUE is set to 3 seconds.

Check out the log of received packets

Looks kinda crowded... We show everything in a single line?

Yes because we'll be copying and pasting the lines into a spreadsheet for analysis.

This way it's easier to scrub the data.

3.2 Timeout Callback Function

We've set the Receive Timeout to 3 seconds. What happens if we don't receive a LoRa Packet in 3 seconds?

The LoRa Driver will trigger our OnRxTimeout Callback Function: main.cpp

//  Callback Function to be executed on Receive Timeout event
void OnRxTimeout(void) {
    //  We haven't received a packet during the timeout period.
    //  We disable the timeout message because it makes the log much longer.
    //  Serial.println("OnRxTimeout");

We don't do much in OnRxTimeout because it's perfectly OK to time out... We don't expect a LoRa Packet every 3 seconds.

All we do in OnRxTimeout is to ask the LoRa Driver to receive the next packet...

    //  Receive the next packet. Timeout in 3 seconds.
    Radio.Rx(RX_TIMEOUT_VALUE);
}

3.3 Error Callback Function

The LoRa Driver triggers our Callback Function OnRxError whenever it receives a corrupted LoRa Packet.

(Likely due to interference or weak signal. Or an itchy finger powered off the LoRa Transceiver during transmission)

We show the Timestamp (in seconds) for troubleshooting and analysis: main.cpp

//  Callback Function to be executed on Receive Error event
void OnRxError(void) {
    //  We have received a corrupted packet, probably due to weak signal.
    //  Show the timestamp in seconds.
    Serial.printf("OnRxError: Timestamp=%d\n", millis() / 1000);

And we ask the LoRa Driver to receive the next packet...

    //  Receive the next packet. Timeout in 3 seconds.
    Radio.Rx(RX_TIMEOUT_VALUE);
}

LoRa Parameters in WisBlock LoRa Receiver must match those in the LoRa Transmitter (PineCone BL602)

LoRa Parameters in WisBlock LoRa Receiver must match those in the LoRa Transmitter (PineCone BL602)

4 LoRa Configuration

Our WisBlock LoRa Receiver must be configured with the same settings as the LoRa Transmitter... Or we won't receive any LoRa Packets!

Here's the LoRa Configuration for our WisBlock LoRa Receiver: main.cpp

// Define LoRa parameters. To receive LoRa packets from BL602, sync the parameters with
// https://github.com/lupyuen/bl_iot_sdk/blob/lora/customer_app/sdk_app_lora/sdk_app_lora/demo.c#L41-L77
// TODO: Change RF_FREQUENCY for your region
#define RF_FREQUENCY          923000000	// Hz
#define TX_OUTPUT_POWER       22		// dBm
#define LORA_BANDWIDTH        0		    // [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
#define LORA_SPREADING_FACTOR 7         // [SF7..SF12]
#define LORA_CODINGRATE       1		    // [1: 4/5, 2: 4/6,  3: 4/7,  4: 4/8]
#define LORA_PREAMBLE_LENGTH  8	        // Same for Tx and Rx
#define LORA_SYMBOL_TIMEOUT   0	        // Symbols
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON       false
#define RX_TIMEOUT_VALUE      3000
#define TX_TIMEOUT_VALUE      3000

For RF_FREQUENCY, be sure to specify the right LoRa Frequency for your region: 434, 780, 868, 915 or 923 MHz...

Check also the LoRa Packet Size. Our WisBlock Receiver handles LoRa Packets that are 64 bytes or smaller...

//  Buffer for received LoRa Packet
static uint8_t RcvBuffer[64];

Fortunately the LoRa Configuration matches perfectly across our WisBlock Receiver and PineCone BL602 Transmitter... So nothing needs to be changed! (See pic above)

(Except for the LoRa Frequency)

PineCone BL602 sending LoRa packets to WisBlock

PineCone BL602 (left) sending LoRa packets to WisBlock (right)

5 Build and Run the LoRa Firmware

Let's run the LoRa Firmware for WisBlock and receive some LoRa Packets!

5.1 Install VSCode and PlatformIO

  1. Follow the instructions in this excellent article to install VSCode and PlatformIO...

  2. Remember to install the LoRa Library SX126x-Arduino according to the steps above.

    (We may skip the LoRaWAN OTAA Example)

  3. Find out which LoRa Frequency we should use for your region...

    We'll set the LoRa Frequency in a while.

5.2 Build the firmware

  1. Enter this at the command line...

    # Download the wisblock-lora-receiver source code
    git clone --recursive https://github.com/lupyuen/wisblock-lora-receiver
    
  2. In VSCode, click File → Open Folder

    Select the folder that we have just downloaded: wisblock-lora-receiver

  3. Edit the file src/main.cpp

    Look for this code...

    // Define LoRa parameters.
    // TODO: Change RF_FREQUENCY for your region
    #define RF_FREQUENCY 923000000  // Hz
    

    Change 923 to the LoRa Frequency for your region: 434, 780, 868, 915 or 923

  4. Modify the LoRa Parameters in src/main.cpp so that they match those in the LoRa Transmitter (PineCone BL602)

  5. Build the LoRa Firmware by clicking the Build icon at the lower left...

    Build Icon

  6. We should see this...

    Processing wiscore_rak4631 (platform: nordicnrf52; board: wiscore_rak4631; framework: arduino)
    ...
    Building in release mode
    Checking size .pio/build/wiscore_rak4631/firmware.elf
    Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
    RAM:   [          ]   3.1% (used 7668 bytes from 248832 bytes)
    Flash: [=         ]   7.3% (used 59800 bytes from 815104 bytes)
    =========================== [SUCCESS] Took 4.49 seconds ===========================
    

5.3 Flash the firmware

  1. Connect WisBlock to our computer's USB port

  2. Flash the LoRa Firmware to WisBlock by clicking the Upload icon...

    Upload Icon

  3. We should see this...

    Firmware flashed successfully

  4. If we see the message...

    Timed out waiting for acknowledgement from device
    

    Then disconnect WisBlock from the USB port, reconnect and flash again.

    Firmware flashing failed

5.4 Run the firmware

  1. Run the LoRa Firmware by clicking the Monitor icon...

    Monitor Icon

  2. We should see this...

    > Executing task: platformio device monitor <
    --- Miniterm on /dev/cu.usbmodem14201  9600,8,N,1 ---
    --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
    Starting Radio.Rx
    
  3. Power on our LoRa Transmitter (PineCone BL602) and start transmitting LoRa Packets.

  4. In the WisBlock Log we will see the LoRa Packet received...

    OnRxDone: 
    Timestamp=23, 
    RssiValue=-48 dBm, 
    SnrValue=13, 
    Data=50 49 4E 47 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 
    
  5. As we move the LoRa Transmitter (PineCone BL602) around, the Signal Strength and Signal To Noise Ratio will change...

    OnRxDone: 
    Timestamp=196, 
    RssiValue=-63 dBm, 
    SnrValue=13, 
    Data=50 49 4E 47 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 
    
  6. When we move the LoRa Transmitter too far from the WisBlock Receiver, we will see this...

    OnRxError: Timestamp=619
    

    This means that our WisBlock Receiver couldn't receive the LoRa Packet because the signal was too weak.

    Watch the demo video on YouTube

    See the received LoRa Packets

Now that we understand LoRa Packets and their Signal Strength, let's measure the LoRa Network Coverage!

WisBlock LoRa Receiver right by the coconut trees

WisBlock LoRa Receiver right by the coconut trees

6 LoRa Field Test

Here comes the #1 Question when deciding where to install our LoRa Sensor and LoRa Receiver...

What's the Maximum Distance between the LoRa Transmitter and LoRa Receiver? 100 metres? 200 metres? Or more?!

LoRa was designed to send packets over great distances with little power. Let's find out how far!

  1. We'll put our WisBlock LoRa Receiver on the balcony, right by the coconut trees. (See pic above)

    (It's OK to run WisBlock temporarily inside the padded WisBlock Connected Box, antenna sticking out and upwards... Just make sure the metal parts of the LoRa Antenna and the Bluetooth Antenna don't touch any metal parts on WisBlock)

  2. Prepare the LoRa Transmitter Kit. We'll pack the following...

    LoRa Transmitter Kit with PineCone BL602

  3. Pack the LoRa Transmitter Kit into a backpack, antenna pointing up

    LoRa Transmitter Kit in a backpack

  4. Go hiking with the backpack!

    We'll do it like Pokemon Snap... Everywhere we go, we snap a photo on our phone.

    Like this Grassy Field...

    Geocoded, Timestamped photo

  5. This photo is Geocoded and Timestamped by our phone.

    Later we shall use this photo to tell us where we were located at a specific time.

    Keep snapping more photos as we walk. Like these Geocoded, Timestamped Chickens...

    Geocoded, Timestamped Chickens

  6. At the end of our hike, we'll have a collection of Geocoded Timestamped Photos.

    In the next section we'll match the photos with the log of LoRa Packets received by WisBlock.

Geocoded, Timestamped Chicken Rice (no relation with the earlier chickens)

Geocoded, Timestamped Chicken Rice (no relation with the earlier chickens)

7 Analyse the LoRa Coverage

Back to our #1 Question...

What's the Maximum Distance between the LoRa Transmitter and LoRa Receiver? 100 metres? 200 metres? Or more?!

To answer that, we have two helpful things...

  1. A bunch of Geocoded, Timestamped Photos that we have collected during our LoRa Field Test

  2. A log of LoRa Packets received by WisBlock...

WisBlock Arduino Log of Received LoRa Packets

See the log of received LoRa Packets

Here's what we'll do...

  1. We copy and paste the log of received LoRa Packets into a spreadsheet.

    Split the data neatly into columns.

  2. Based on the Timestamp (in seconds), we compute the Actual Time Of Day.

    Our spreadsheet should look like this...

    Spreadsheet with received LoRa Packets

    See the Google Sheets spreadsheet

  3. Plot a chart of Signal Strength vs Actual Time (See pic above)

    This shows us the Signal Strength of the LoRa Packets received by WisBlock as we walked about.

  4. Look for the dips and valleys in the chart.

    These are the places with poor LoRa Coverage.

    When we see missing dots in the chart,these are the places with zero LoRa Coverage.

    (Don't place our LoRa Sensor here!)

  5. To find these places with poor LoRa Coverage, match the Actual Time against the photos that we have collected...

    Geocoded Timestamped Chickens

  6. Each photo is Geocoded, so we can identify the places. Voila!

    Places with poor LoRa Coverage

  7. For this test, we were able to receive LoRa Packets up to 300 metres away.

    Not bad for this dense, blocky neighborhood!

    Dense blocky neighbourhood

  8. How is this possible when there's No Line Of Sight between the LoRa Transmitter and the LoRa Receiver?

  9. Exercise For The Reader...

    How would you conduct this LoRa Field Test and analyse the LoRa Network Coverage more efficiently?

    (Check out the solution by @Kongduino on Twitter)

    (Check the Appendix for another solution)

WisBlock receiving LoRa packets in the night

WisBlock receiving LoRa packets in the night

8 What's Next

In the next article we shall head back to PineCone BL602 and finish Level 1 of our LoRa Stack...

And then we can progress to LoRa Levels 2 and 3: LoRaWAN and The Things Network.

Read the next article here...

We have come a loooong way since I first experimented with LoRa in 2016...

Now is the right time to build LoRa gadgets. Stay tuned for more LoRa Adventures!

Got a question, comment or suggestion? Create an Issue or submit a Pull Request here...

lupyuen.github.io/src/wisblock.md

9 Notes

  1. This article is the expanded version of this Twitter Thread

10 Appendix: LoRa Ping Firmware for BL602

In the previous article we've created a LoRa Transmitter with PineCone BL602...

For the LoRa Field Test we installed the BL602 LoRa Ping Firmware: sdk_app_loraping

This is a modified version of sdk_app_lora that does the following...

  1. At startup, we initialise the LoRa Transceiver

  2. Then we transmit a 64-byte PING LoRa Message every 10 seconds

  3. We flash the Blue LED on PineCone every 10 seconds

    Watch the BlinkenLED demo video on YouTube

The changes are made in the function cli_init in demo.c

Modified cli_init function

Modified cli_init function

Remember to check that the BL602 LoRa Parameters in LoRa Ping match those in the WisBlock Receiver.

From demo.c

BL602 LoRa Parameters

BL602 LoRa Parameters

Here's how it looks when BL602 LoRa Ping sends LoRa Packets to WisBlock...

PineCone BL602 sending LoRa packets to WisBlock

PineCone BL602 (left) sending LoRa packets to WisBlock (right)

There seems to be an intermittent problem with my LoRa Transmitter (hardware or firmware?)... WisBlock receives the first LoRa Packet transmitted but doesn't receive subsequent packets.

My workaround for now: As I walk, I disconnect and reconnect the USB power to my LoRa Transmitter (every 20 seconds).

11 Appendix: Stream LoRa Packets to YouTube

When we're out doing the LoRa Field Test, how far shall we walk?

Is there some way to watch the log of packets received by WisBlock... As we walk?

Here's a solution: We stream the WisBlock Arduino Log live to YouTube!

Livestream of WisBlock Arduino Log on YouTube

Livestream of WisBlock Arduino Log on YouTube

Watch the recoded video on YouTube

We do this by running OBS Studio on our computer.

OBS Studio streams our desktop to YouTube as a live video stream, so that we may watch the WisBlock Arduino Log on the go.

OBS Studio streaming WisBlock Arduino Log to YouTube

OBS Studio streaming WisBlock Arduino Log to YouTube