LoRa Gateway Antenna Upgrade

My self-assembled LoRa Gateway used to have an inexpensive SMA antenna, which actually did not fit to the “professional” casing with its prepared openings for Type-N connectors. And having plans to install the gateway somewhere outdoor sooner or later, I decided to upgrade the antenna. I ordered the following parts:

Here are some pictures how the installation looks now …

img_3493img_3491

And what about the effect?

I did some measurements before and after replacing the old antenna using the TTN Mapper app on iPhone and a LoRaWAN node based on the Adafruit Feather 32u4 with LoRa. On two measurement points in a distance of 1280m and 748m I measured the signal strengh two times.

Result: With the new antenna, the signal strengh improved from -117dBm to -112dBm

You will have to decide on your own if this gain of about 5 dBm actually justifies the purchase of such a more “professional” antenna. I think if you just want to play around and get experience, then an inexpensive SMA antenna is good enough. But if you actually plan to place the gateway outdoor, then maybe you should consider to do what I did.

ttnmapper

Advertisements

STM32 Microcontroller and LoRa Breakout Board

Another popular approach to build LoRa nodes is to use inexpensive but powerful 32-bit microcontrollers (like the STM32 family) in combination with a LoRa RF transceiver (like the Semtech SX1276).

To make it easier to build prototypes, there are vendors that provide modules that fit onto a breadboard, like the Adafruit RFM9X LoRa Packet Radio Breakout Board.

Sure, if you build your prototypes without a breadboard, you can also use the cheaper variant of directly using a HOPERF RFM95W Transceiver Module.

I am using a STM32F103C8. I don’t go into details about programming and flashing for the STM32 family of microprocessors. Pleaser refer to this page, which is a good starting point: http://wiki.stm32duino.com .

Wiring it up …

To communicate with the STM32 microcontroller, I use a FTDI USB-to-Serial interface cable.

stm32_adafruit_rfm9x_lora_Steckplatine

Next to the SPI interface of the RFM9x, also the data ports G0 and G1 are connected to the STM32.

The LoRaWAN stack …

Again we use the Arduino LMIC communication stack from https://github.com/matthijskooijman/arduino-lmic .

You need to adapt the pin mapping as follows:

const lmic_pinmap lmic_pins = { 
 .nss = PA4, 
 .rxtx = LMIC_UNUSED_PIN, 
 .rst = PB0, 
 .dio = {PA3, PB5, LMIC_UNUSED_PIN}, 
};

Use the ttn-otaa example to connect to The Things Network.

That’s it.

Please also check this interesting project, that uses similar components and software: CitizenSensorA low budget battery powered LoRa node to smart up your city at https://github.com/orangewaylab/CitizenSensor .

IMG_3488

 

Assembly of LoRa Gateway finished

Open issues

  • I don’t use a special antenna. This one has a SMA connector and I bought it together with the LoRa board from the Imst webshop. Would be nice to have an antenna with a Type N connector, as there are already fitting openings in the metal case.
  • To get rid of an additional power supply cable, I added a cheap passive PoE injector and splitter. In an outdoor installation, this should be better an active PoE solution.

Assembling my own LoRa Gateway

The christmas holidays are coming, and I decided to dive a little deeper into LoRaWAN, The Things Network, the LoRa Nodes and the data processing and visualization in the backend.

But all of the above is hard if there is no LoRa Gateway in the neighborhood 😬.

So I decided to assembly my own LoRa Gatway using the following components:

I couldn’t wait to start assembling the gateway, but unfortunately, I had no female-to-female jumper cable at hand, so I improvised with a breadboard.

The Things Network  Community Zürich provides instructions for assembling the components and for setting up the software: From zero to LoRaWAN network in a weekend. This was all I needed to follow to get the gateway up and running.

There are still many decision to make:

  • Use a simple backplane to connect Raspberry Pi with the iC880A-SPI board instead to use these jumper cables (already decided)
  • Outdoor or indoor installation? The casing says “outdoor”.
  • Outdoor scenario: External WiFi antenna or ethernet cable?
  • Outdoor scenario: Is the antenna sufficient, or do I need something more “professional” (expensive) ?
  • Outdoor scenario: 250V or 5V power cable or Power over Ethernet (PoE) solution?

Indoor Air Quality (IAQ) Measurement with Bosch BME680 and STM32F103C8T6

In my personal opinion, the key feature of the Bosch BME680 is its ability to output an Indoor Air Quality (IAQ) using its built-in sensors for gas, pressure, humidity and temperature. Bosch Sensortec provides a software solution (BSEC: Bosch Software Environmental Cluster) that utilizes the 4-in-1 integrated sensors inside the BME680: This software solution provides an Indoor Air Quality (IAQ) index ranging from 0 to 500 which quantifies the quality of the air available in the surrounding.

STM32F103 and BME680

IAQ Index Classification

  • 0 .. 50 Good
  • 51 .. 100 Average
  • 101 .. 150 Little bad
  • 151 .. 200 Bad
  • 201 .. 300 Worse
  • 301 .. 500 Very bad

Goal

Get the BSEC software up and running using the Arduino IDE, an inexpensive microcontroller board STM32F103C8T6 aka “Blue Pill” and a Bosch BME680 on a breakout board like the one offered by Watterott. The BSEC software is provided as pre-compiled library for various microprocessor architectures, and it is a little bit tricky to get it up and running in this particular combination.

Prerequisites

I don’t want to go into details about working with STM32 microcontroller boards in the Arduino IDE. A good entry point is here: https://github.com/rogerclarkmelbourne/Arduino_STM32
  • You already can flash and run applications on STM32 board.
  • You already have the demo BME680 I2C example up and running that can be found at https://github.com/BoschSensortec/BME680_driver. The application prints temperature, pressure, humidity and gas resistance.
  • Admittedly, setting up the code for I2C communication using the Arduino “Wire” library can be challenging. If you need inspiration, you can look further down this blog post at “How to implement I2C Communication”.
  • You already have downloaded the BSEC software at https://www.bosch-sensortec.com/bst/products/all_products/bsec. The version refered to in this blog post is 1.3.4.1

Get BSEC example up and running

In the /doc folder of the BSEC software archive you find step-by-step instructions (Section 2) for an example. However, there are some pitfalls …

Pitfall #1: Where can I find all those files?

In Arduino IDE, create a new sketch named bsec_iot_example.

Into this directory, you need to copy a bunch of files from the BSEC software archive:

  • from /algo:
    bsec_datatypes.h
    bsec_interface.h
  • from /API:
    bme680.c
    bme680.h
    bme680_calculations.c
    bme680_calculations.h
    bme680_internal.h
    sensor_api_common_types.h
  • from /example
    bsec_integration.c
    bsec_integration.h
    bsec_iot_example.c – Don’t copy this
    bsec_iot_example.ino

Finally, after restarting the Arduino IDE , there should be 11 files – no more and no less.

Pitfall #2: How to implement I2C Communication?

Fortunately, the example provided in bsec_iot_example.ino provides an implementation for I2C communication in functions bus_read and bus_write. Also the setup() function does the required initializations.

But now the bad part: The Wire (I2C) library for the STM32 seems not to adhere to Arduino Wire Specification: The method Wire.endTransmission() returns the number of bytes read or written, instead to just return 0 on success. This is an issue for the example application, as it relies on the return codes of bus_read and bus_write:

From bus_write (bsec_iot_example.ino):

return (int8_t) Wire.endTransmission();

replace with

Wire.endTransmission();
return 0;

From bus_read (bsec_iot_example.ino):

comResult = Wire.endTransmission();
...
return comResult;

replace with

return 0;

Pitfall #3: What is the right pre-compiled BSEC library for STM32F103 boards?

The STM32F103C8T6 board is based on an ARM Cortex-M3 core. You can find the appropriate pre-compiled BSEC library in the following directory of the BSEC software archive:

/algo/bin/ARM_CORTEX/GCC/IAQ_OUT/Cortex_M3/libalgobsec.a

Pitfall #4: Where to put the pre-compiled BSEC library?

You won’t be able to build the example application unless you copy the pre-compiled BSEC library to a place where it can be found by the linker and instruct the linker to include it.

The instructions following are for version 1.8.5 of Arduino on a Windows PC.

You need to find out which directories and files the linker uses. Enable verbose output in the Arduino IDE to find out more.

  • Open the “Preferences” under File –> Preferences.
  • Check the option “Show verbose output during [x] compilation”

arduino_preferences

Now try to build the example application in the Arduino IDE. Of course it won’t work at this point in time, as some methods from the pre-compiled BSEC library cannot be found.

But there should be some output available now from the building process in the bottom half of the IDE screen. Look out for “Linking everything together…”.

Linking everything together…
“C:\Users\wolfgang\AppData\Local\Arduino15\packages\arduino\tools\arm-none-eabi-gcc\4.8.3-2014q1/bin/arm-none-eabi-g++” -Os -Wl,–gc-sections -mcpu=cortex-m3 “-TC:\Program Files\Arduino\hardware\Arduino_STM32-master\STM32F1\variants\generic_stm32f103c/ld/jtag_c8.ld” “-Wl,-Map,C:\Users\wolfgang\AppData\Local\Temp\arduino_build_20308/bme680_iaq.ino.map” “-LC:\Program Files\Arduino\hardware\Arduino_STM32-master\STM32F1\variants\generic_stm32f103c/ld” -o “C:\Users\wolfgang\AppData\Local\Temp\arduino_build_20308/bme680_iaq.ino.elf” “-LC:\Users\wolfgang\AppData\Local\Temp\arduino_build_20308” -lm -lgcc -mthumb -Wl,–cref -Wl,–check-sections -Wl,–gc-sections -Wl,–unresolved-symbols=report-all -Wl,–warn-common -Wl,–warn-section-align -Wl,–warn-unresolved-symbols -Wl,–start-group “C:\Users\wolfgang\AppData\Local\Temp\arduino_build_20308\sketch\bme680.c.o” “C:\Users\wolfgang\AppData\Local\Temp\arduino_build_20308\sketch\bme680_calculations.c.o” “C:\Users\wolfgang\AppData\Local\Temp\arduino_build_20308\sketch\bsec_integration.c.o” …

This gives us two insights and follow-up tasks:

  • Look out for the value of option “-L”. This indicates the directories where the linker will look for libraries. In my case, it is C:\Program Files\Arduino\hardware\Arduino_STM32-master\STM32F1\variants\generic_stm32f103c\ld.
    Copy the pre-compiled BSEC library libalgobsec.a to this directory.
  • Look out for the value of option “-T”. This indicates where the linker script is located. In my case, the linker script is located at C:\Program Files\Arduino\hardware\Arduino_STM32-master\STM32F1\variants\generic_stm32f103c/ld/jtag_c8.ld.
  • In my case, the linker script included another script, common.inc./* Let common.inc handle the real work. */
    INCLUDE common.inc

    Open this common.inc in an editor and search for

    GROUP(libgcc.a libc.a libm.a)

    Put libalgobsec.a in front of libm.a, resulting in the line

    GROUP(libgcc.a libc.a libalgobsec.a libm.a)

     

  • Find the platform.txt file for the STM32F103 board. Already having found the paths for the platform specific linker script, in my case it is located in directory C:\Program Files\Arduino\hardware\Arduino_STM32-master\STM32F1.Open platform.txt in an editor and search for recipe.c.combine.pattern. This is a kind of “template” how to create the command and options for the linker. In my case, it looks as follows:## Combine gc-sections, archives, and objectsrecipe.c.combine.pattern=”{compiler.path}{compiler.c.elf.cmd}” {compiler.c.elf.flags} -mcpu={build.mcu} “-T{build.variant.path}/{build.ldscript}” “-Wl,-Map,{build.path}/{build.project_name}.map” {compiler.c.elf.extra_flags} -o “{build.path}/{build.project_name}.elf” “-L{build.path}” -lm -lgcc -mthumb -Wl,–cref -Wl,–check-sections -Wl,–gc-sections -Wl,–unresolved-symbols=report-all -Wl,–warn-common -Wl,–warn-section-align -Wl,–warn-unresolved-symbols -Wl,–start-group {object_files} “{build.path}/{archive_file}” -Wl,–end-group
    Put “-lalgobsec” in front of “-lm” to instruct the linker to also include the pre-compiled BSEC software library.

    ## Combine gc-sections, archives, and objects
    recipe.c.combine.pattern=”{compiler.path}{compiler.c.elf.cmd}” {compiler.c.elf.flags} -mcpu={build.mcu} “-T{build.variant.path}/{build.ldscript}” “-Wl,-Map,{build.path}/{build.project_name}.map” {compiler.c.elf.extra_flags} -o “{build.path}/{build.project_name}.elf” “-L{build.path}” -lalgobsec -lm -lgcc -mthumb -Wl,–cref -Wl,–check-sections -Wl,–gc-sections -Wl,–unresolved-symbols=report-all -Wl,–warn-common -Wl,–warn-section-align -Wl,–warn-unresolved-symbols -Wl,–start-group {object_files} “{build.path}/{archive_file}” -Wl,–end-group

Having done these modifications, this BSEC software library should now be linked into the example application. It may be necessary to restart the IDE to make it work. You may also want to check the linker output when you compile, to make sure that it really contains the -lalgobsec flag.

Note: Please be aware that this BSEC software library is now linked into each and every sketch you do on this STM32 platform. Of course you don’t want that, so don’t forget to clean up this Arduino setup if you do projects that don’t require this library.

Pitfall #5: What is this strange I2C code in bme680.c ?

In file bme680.c, search for string __KERNEL__.

In two places you find code like this:

#ifndef __KERNEL__
 bme680_buffer_restruct_burst_write(data_u8,
 0x70,
 BME680_SENS_CONF_LEN,
 (BME680_SENS_CONF_LEN * 2)-1);

com_status = (enum bme680_return_type)
 bme680->bme680_bus_write(bme680->dev_addr,
 BME680_ADDR_SENSOR_CONFIG,
 data_u8,
 (BME680_SENS_CONF_LEN * 2)-1);

#else

com_status = (enum bme680_return_type)
 bme680->bme680_bus_write(bme680->dev_addr,
 BME680_ADDR_SENSOR_CONFIG,
 data_u8,
 BME680_SENS_CONF_LEN);
#endif

Actually __KERNEL__ is not defined, so the first branch of the #ifndef statement is executed. But it is totally unclear to me why there should be any need to restructure the message before sending over I2C, and actually only the code in the #else section works.

So the recommendation is to remove this #ifndef – #else – #endif construct, and just keep the code in the #else section.

So in this one of two places in bme680.c, just stick with this:

com_status = (enum bme680_return_type)
 bme680->bme680_bus_write(bme680->dev_addr,
 BME680_ADDR_SENSOR_CONFIG,
 data_u8,
 BME680_SENS_CONF_LEN);

Again, don’t forget to remove this #ifndef – #else – #endif construct in the second place in file bme680.c, too.

 

Pitfall #6: The example application build, but why is there no IAQ output?

You managed to build the example application and flash it to the STM32 board, you see output lines in the serial monitor, but the values for IAQ stay 0.00 (0).

No need to panic, just wait 5 – 10 minutes, then there should be a value for IAQ.

...
[294910.00] T: 23.81| rH: 42.20| IAQ: 0.00 (0)
[297909.00] T: 23.81| rH: 42.21| IAQ: 0.00 (0)
[300908.00] T: 23.81| rH: 42.19| IAQ: 0.00 (0)
[303907.00] T: 23.81| rH: 42.18| IAQ: 25.00 (3)
[306906.00] T: 23.81| rH: 42.16| IAQ: 23.11 (3)
[309905.00] T: 23.81| rH: 42.18| IAQ: 25.12 (3)
[312904.00] T: 23.81| rH: 42.20| IAQ: 22.67 (3)
[315903.00] T: 23.81| rH: 42.22| IAQ: 34.15 (3)
[318902.00] T: 23.81| rH: 42.46| IAQ: 38.45 (3)
[321901.00] T: 23.81| rH: 42.73| IAQ: 25.17 (3)
[324900.00] T: 23.82| rH: 42.81| IAQ: 29.15 (3)
[327899.00] T: 23.82| rH: 43.01| IAQ: 38.77 (3)
[330898.00] T: 23.83| rH: 43.36| IAQ: 28.27 (3)
[333897.00] T: 23.83| rH: 43.23| IAQ: 28.39 (3)

In my case, first IAQ with value 25.00 after 304 seconds.

Pitfall #7: Why are there no more temperature and humidity measurements after 51 days?

There is a possible flaw in the function that returns the current time in microseconds.

From get_timestamp_us (bsec_iot_example.ino):

int64_t get_timestamp_us()
{
   return (int64_t) millis() * 1000;
}

From the Arduino specification: millis() returns the number of milliseconds since the Arduino board began running the current program. This number will overflow (go back to zero), after approximately 50 days.

For an alternative implementation, I chose to use function call micros() instead of millis(), as any overflow effects will already show up after 71 minutes and not after 51 days.

Alternative implementation of get_timestamp_us (bsec_iot_example.ino):

int64_t get_timestamp_us()
{
   static int64_t microseconds_since_start = 0;
 
   // micros() returns time (in microseconds) since the beginning of program execution. 
   // On overflow (after approximately 71 minutes), restarts at 0.
   uint32_t microseconds = micros();
   uint32_t microseconds_since_start_as_uint32 = microseconds_since_start & 0xFFFFFFFF;

   int64_t diff_since_last_call = microseconds - microseconds_since_start_as_uint32;
   microseconds_since_start += diff_since_last_call;
 
   return microseconds_since_start;
}

Adafruit Feather as LoRaWAN node

Next to the Raspberry Pi as a LoRaWAN node I also wanted to have a really tiny node, as tiny as possible, and with the capability to operate it with a battery.

I ordered a Adafruit Feather 32u4 RFM95 LoRa Radio module, as it already has a RFM95 LoRa radio module attached, and as it has a connector and charger for Lithium Polymer batteries.

feather

For the software part, I took the LMIC library for Arduino port from github and followed the instructions to get it up and working in the Arduino IDE.

To make it work for the Adafruit Feather 32u4 module, there was the need to adapt some settings. The pin mapping definition was changed as follows.

const lmic_pinmap lmic_pins = { 
   .nss = 8, 
   .rxtx = LMIC_UNUSED_PIN, 
   .rst = 4, 
   .dio = {7, 6, LMIC_UNUSED_PIN}, 
};

According to Adafruit pinout documentation,

  • the radio module’s IO0 pin is already connected internally to pin #7 of the Adafruit feather module.
  • Pin #8 is connected internally to the radio’s CD (chip select) pin
  • Pin #4 is connected internally to the radio’s RESET pin

So the one thing left to do is to connect the radio module’s IO1 pin with pin 6 using a wire bridge (see picture above). Connecting IO2 is not required (LMIC_UNUSED_PIN).

Further, there seems to be a little issue with the accurancy of the clock, which can cause problems with receiving data from the LoRa gateways, as the receive window can’t be hit exactly. For this reason, added the following line to work around this.

LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);

Having done that, the ttn-otaa.ino worked as expected: It took only one single request to join TTN using Over-the-Air-Activation (OTAA), and periodically sending a data record every 60 seconds.

Hardware Abstraction Layer (HAL) for LMIC 1.6

For users of a Dragino LoRa/GPS HAT (for Raspberry Pi) I have implemented a Hardware Abstraction Layer for the IBM LMIC 1.6 communication stack. This is similar to the solution of Ernst de Vreede, but as a difference it is based on IBM LMIC 1.6, which seems to be the most recent version of this communication stack. I tried not to touch the LMIC stack itself but only provide a HAL for RPi + LoRa HAT. I also tried not to put too much CPU load on the RPi when waiting for incoming messages.

Find the code in github at https://github.com/wklenk/lmic-rpi-lora-gps-hat8

  • The following LMIC examples are working yet with this HAL and TTN:
    examples/hello – Not using radio at all
  • examples/join – Join the TTN using OTAA (over the air activation)
  • examples/periodic – Join and periodically send a sensor value to TTN.

The examples work, however I would like to do some more analysis and fine tuning regarding the timing of the receive window, and I like to add more debug output to understand what is going on in the network in regards of the LoRaWAN protocol.

Note: I tested it with a Kerlink IoT Gateway. Single Channel Gateways won’t work.