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;
}
Advertisements

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.

LoRa Gateway for Tests

I currently have a Kerlink LoRa IoT Station 868 for testing.

The Things Network supports this gateway, instructions how to update the gateway’s firmware to make it communicate with TTN can be found here.

Let me quote from TTN:

The Kerlink LoRa IoT Station is is an industrial solution suitable for people who want to mount the gateway outside and who have sufficient technical skills to connect, mount and maintain the device themselves.

We have tested the device and although we have remarks about the somewhat older software that is being used, this device will do the job. A trained software engineer will be able to update the device using the software from The Things Network.

img_2978

LoRaWAN Single Channel Gateway

For first steps with The Things Network, you can build a LoRa Gateway. This gateway will have some restrictions:

  • Listens only on one single channel (one single frequency) for nodes
  • Listens only with one predefined spreading factor (SF)
  • Only listens, does not send back any kind of acknowledge messages

Okay. These are pretty hard restrictions, maybe we better should name it “Forwarder” instead of “Gateway”.

fullsizerender

Hardware

Software

Configuration

You need to define the IP address of the TTN server in the gateway software

  • In main.cpp, update the IP address of TTN server.
    You can find the current IP addresses to use here: For different countries there are different server addresses. Pick the appropriate one.
  • For europe (EU), I choose
    router.eu.thethings.network # EU 433 and EU 863-870

    Note that you can’t enter this server name directly, you need to lookup the IP address before:

    $ nslookup router.eu.thethings.network
    Server: 192.168.178.1
    Address: 192.168.178.1#53
    
    Non-authoritative answer:
    router.eu.thethings.network canonical name = bridge.eu.thethings.network.
    Name: bridge.eu.thethings.network
    Address: 52.169.76.203

    The IP address to use in main.cpp is 52.169.76.203

  • In the code of main.cpp, it should look like this:
    // define servers
    // TODO: use host names and dns
    #define SERVER1 "52.169.76.203"
    //#define SERVER1 "54.72.145.119"    // The Things Network: croft.thethings.girovito.nl
  • Now compile the software and start it
    sudo ./single_chan_pkt_fwd
  • On start, it will output its Gateway ID, which is important for the next steps:
    pi@raspberrypi:~/single_chan_pkt_fwd $ sudo ./single_chan_pkt_fwd 
    SX1276 detected, starting.
    Gateway ID: aa:bb:cc:ee:ff:11:22:33
    Listening at SF7 on 868.100000 Mhz.

    This Gatway ID is unique for your gateway.

To create a gateway in TTN, you need to register at TTN and create an account. You then can register a gateway in the console.

  • Choose “bridge” as “Activation Method”
  • Cut and paste your Gateway ID as “Gateway EUI”
  • Choose the appropriate frequency plan
  • When you have filled out all fields, press button “Register Gateway” at the bottom of the page.

create_gateway

That’s it.

You now should have created you own experimental gateway in TTN and you should see that it has status “connected”.

Cross compiling to Raspberry Pi

Please don’t ask for the reasons, but the goal of today’s action was to get a cross compile environment running on Windows OS in order to compile to my RPi3 running Raspbian “jessie” (Raspbian GNU/Linux 8).

I write this blog post as a shortcut for people that like to do the same: Use the resources and comfortable IDE on a PC and do the code writing here, compile, link and finally transfer the executable binary to the Raspberry Pi and execute it there.

IDE

Downloaded Eclipse CDT as IDE for C/C++ programming.
https://eclipse.org/ide/

Eclipse IDE for C/C++ Developers
Version: Neon.2 Release (4.6.2)

Precompiled GNU Toolchain for Windows32

Linaro provides several precompiled toolchains. It is up to you to select the right one.

It needs to fit the system architecture on the Raspberry Pi and the remote operating system you are running on (Raspian “jessie”).

There is a list of Linaro releases here:
https://releases.linaro.org/archive/13.11/components/toolchain/binaries/

For Windows32 as host/development system, I chose
gcc-linaro-arm-linux-gnueabihf-4.8-2013.11_win32.zip

Download it and extract it on your windows filesystem.

Buildtools

You additionally need build tools like “make”.

This can be downloaded from here:
https://gnuarmeclipse.github.io/windows-build-tools/#download

Put the buildtool’s bin directory into your Windows PATH environment variable to make sure they are found by Eclipse. You possibly may need to restart Eclipse. If “make” ist not found when compiling some source files, then the reason is probably that the path to the buildtools is not configured properly.

Configuring Eclipse CDT

Create a new C project named “hello_world”, choose project type “Empty Project”. Toolchain “Cgross GCC” should be selected.

If you are ask for “Cross compiler prefix”, you enter

arm-linux-gnueabihf-

Note: Don’t forget the hyphen (“-“) at the end.

For “Cross compiler path”, navigate to the “bin” directory of your toolchain installation. In my case, it is

C:\gcc-linaro-arm-linux-gnueabihf-4.8-2013.11_win32\bin

Note: There are two bin directories in the toolchain installation. Make sure to select the one that contains binary executables starting with the filename “prefix arm-linux-gnueabihf-“, e.g. “arm-linux-gnueabihf-gcc.exe”.

Hello World Application

Create a small hello_world.c file.

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("Hello World.\n");
    exit(EXIT_SUCCESS);
}

Compile it in Eclipse using Project -> Build All, and that’s it (almost).

00:40:57 **** Build of configuration Debug for project hello_world ****
make all 
Building file: ../hello_world.c
Invoking: Cross GCC Compiler
arm-linux-gnueabihf-gcc -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"hello_world.d" -MT"hello_world.o" -o "hello_world.o" "../hello_world.c"
Finished building: ../hello_world.c
 
Building target: hello_world
Invoking: Cross GCC Linker
arm-linux-gnueabihf-gcc  -o "hello_world"  ./hello_world.o   
Finished building target: hello_world
 

00:40:59 Build Finished (took 1s.449ms)

Looks good. You can see that arm-linux-gnueabihf-gcc was both used for compiling and linking.

Transfer binary executable to Raspberry Pi

The binary is located in the project subfolder debug. Transfer it to the remote Raspberry Pi. Eclipse provides a way to build connections to remote systems using New -> Other -> Remote System Explorer. You can use this to transfer files without having to leave the IDE.

On Rasperry Pi, make the file “executable” and execute it:

pi@rpi3:~ $ chmod u+x hello_world
pi@rpi3:~ $ ./hello_world
Hello World.

Works. Fine. Go to bed now.

New project: LoRaWAN hacking

Inspired by the first meeting of the The Things Network Community Stuttgart this week I decided to make first steps with LoRaWAN communication. I was looking for a solution that works without soldering, and actually Dragino provides a extension module for Raspberry Pi, which is intended to build LoRaWAN solutions.

Dragino LoRA/GPS HAT

I ordered via Maker Shop EXP TECH, and actually the hardware arrived just one day after ordering. Great service.

img_2838

Next to the Semtech LoRa transceiver there is also a GPS receiver on the extension board.

As I already have experience with FSK modulation in the 868MHz band, I think the first steps will be to check out how to use this kind of communication, before switching to LoRa and LoRaWAN.