Skip navigation
> RoadTest Reviews

Sub-1 GHz Sensor to Cloud IoT Gateway - Review


Product Performed to Expectations: 10
Specifications were sufficient to design with: 10
Demo Software was of good quality: 10
Product was easy to use: 10
Support materials were available: 10
The price to performance ratio was good: 10
TotalScore: 60 / 60
  • RoadTest: Sub-1 GHz Sensor to Cloud IoT Gateway
  • Buy Now
  • Evaluation Type: Development Boards & Tools
  • Was everything in the box required?: Yes - null
  • Comparable Products/Other parts you considered: TBD
  • What were the biggest problems encountered?: TBD

  • Detailed Review:


    The element14 SimpleLink Sub-1GHz Sensor to Cloudelement14 SimpleLink Sub-1GHz Sensor to Cloud kit proclaims to be an “End-to-end solution for LPWAN enabling cloud connectivity for sending and receiving sensor data over a long range Sub-1GHz network”.


    I'd set myself some reasonably high expectations of what I could achieve in this RoadTest, with a plan that added functionality in incremental steps:

    • getting the kit and the try out the installed demo
    • add the necessary SDK to my existing TI CodeComposer Studio IDE
    • extend the demo functionality to include extra sensors ambient light and humidity
    • transfer this new data to the cloud
    • add battery power
    • deploy the system remotely

    There were about 8 weeks to do the project, although that did span Christmas, with a week at the end to write up and tidy up loose ends. With only 6 major steps in the plan, it ought to be manageable....


    Unboxing and trialling the demos

    First impressions are good, a smart protective box contains two TI SimpleLink CC1350 LaunchPadsTI SimpleLink CC1350 LaunchPads, and a BeagleBone BlackBeagleBone Black (BBB) with wireless cape, something I’d not used before. Also included were a 5V PSU, USB cables, and a pre-flashed microSD card for the BBB.

    Box contents

    I got onto the TI website and had a quick read of their Getting Started Guide, and by the time I’d got to page 3 I had the hardware hooked up and my iPad connected to the BBB acting as a Local Gateway.


    There’s a critical step where you need to Open the network to new devices, it’s fine when you read that the first time and do it, but when you’re in a hurry connecting later it’s something I forgot to do a few times.

    Data shown on the gateway

    I know you’d expect their demo to work out of the box, this is TI and element14 after all, but it’s still nice when it’s straightforward and well explained.


    Before moving on to the next step, I thought I’d look at the long range and power consumption of the demo.


    The signal strength received at the BBB end of the system is reported on the web page, so armed with iPad in one hand and the CC1350 SimpleLink board that was the sensor in the other, I moved away from the test setup finding some sockets at convenient distances for a plug-in USB power supply – it would have been good to have a battery powered board ready for this, but that’ll have to wait until later. Previous experience has highlighted the need to use TI’s BoosterPack compatibility checker, and the Fuel Gauge Mk II BoosterPack isn’t compatible, so no quick and easy solution there I’m afraid.


    I got the following measurements as a guideline, with the development set up on the 2nd floor:

    Right up close -15dBm

    1 floor below -30dBm

    2 floors below -64dBm


    That’s not exactly what I’d call long range, more than a BlueTooth Low Energy connection for sure, but only about 10m. It hadn’t lost the connection, but for me long range would be 100m minimum, and 1km would be good. I’ll come back to that range test later when I can battery power the device, and we’ll see how far we can go before the device stops responding.


    Powering the sensor board of a 3.3V bench power supply, and removing all the jumpers that power the USB interface, the board used about 10mA with both LEDs on, and about 0.1mA with both LEDs off.


    That’s a pretty good starting point, but I dropped the supply to just 2.4V and the consumption dropped to 4mA with both LEDs on, and about 0.03mA with both off. Some quick sums suggest a pair of 2400mAh AA batteries could last 13 days if we made sure at least one of the LEDs flashed on a 50% duty cycle, and we could be running for over a year without the LEDs on at all, so the low power claim on the box seems a fair one already.


    Next up I connected the BBB to my WiFi network, after making sure that the SSID wasn’t hidden, and ran the Network Gateway option. Selecting the IBM QuickStart option, so I wouldn’t have to wait and sign up to anything seemed the quickest and easiest approach, and within a few moments I had a page where I could select one of the measurements being made and get a graph on my iPad.


    IBM's QuickStart gateway

    Note that the data isn’t persistent, so the graph only shows the data collected since you started plotting that graph.


    Emailing myself the link confirmed you can access it off 2 devices simultaneously. This QuickStart cloud gateway doesn’t offer the option of controlling the sensor remotely, for that you need to sign up and register for one of the other options, but I wanted to take things in a slightly different direction.


    SDK installation

    Now I was happy with the demo, it was time to get the SDK installed and extend the functionality of the demo.


    First stop was TI’s website, where I found that unlike the SDK for the CC2640R2F SimpleLInk board I used in a previous RoadTest, the CC13x0 SimpleLink™ Sub-1 GHz

    Software Development Kit also had an installer for Mac OS-X. That’s going to have to wait for a different time, though, as I went for the Windows version to build on that existing Code Composer Studio installation.


    Having CCS already installed saved a fair bit of time, and being familiar with the IDE from a previous project definitely helped getting the Sensor example code installed from the \Examples\Dev tools\CC 1350 LaunchPad\TI 15.4 stack folder.


    I connected the CC1350 board marked sensor to a USB port and ran the code in debug mode, and it made a connection to the gateway and started reporting data - all was set now ready to start adding some additional sensor measurements!


    Ambient light and humidity sensors

    I’d already got a Sensors BoosterPackSensors BoosterPack, which is compatible with the CC1350 board, so I powered down the sensor node and connected the two up. The Quick Start guide for the Sensors Booster pack has the following diagram showing the key I2C addresses for the sensors we’re interested in.

    Sensors BoosterPack

    We’re interested in the following two sensors:

    • Light
    • Environmental


    The ambient light level is measured by an OPT3001 device on address 0x47.


    I found some great info from the following two links:


    I opted to write some I2C code directly in the routine that handles a polling message from the gateway, processSensorMsgEvt In the sensors.c file.


    I started off by adding some includes, both for the I2C and so I could print to the console, I also realised I’d need to do some maths on converting the lux readings, so in all I added the following:



    #include "board.h"
    #include "board_lcd.h"


    I started off by configuring the I2C


    I2C_Handle handle;
    I2C_Params params;
    I2C_Transaction i2cTrans;
    uint8_t rxBuf[32];      // Receive buffer
    uint8_t txBuf[32];      // Transmit buffer
    // Configure I2C parameters.
    // Initialize master I2C transaction structure
    i2cTrans.writeCount = 0;
    i2cTrans.writeBuf = txBuf;
    i2cTrans.readCount = 10;
    i2cTrans.readBuf = rxBuf;
    i2cTrans.slaveAddress = 0x47;
    // 0x47 OPT3001 Ambient light
    // 0x77 BME280 Environmental
    // Open I2C
    handle = I2C_open(CC13X0_LAUNCHXL_I2C0, ¶ms);
    if (handle == NULL) {
          System_printf("Error Initializing I2C");


    Now the I2C was all set up, I could read the ambient light value and convert it to Lux as described in the data sheet


    //Config OPT3001
    txBuf[0] = 0x01;
    txBuf[1] = 0xC4;
    txBuf[2] = 0x10;
    i2cTrans.writeCount = 3;
    i2cTrans.readCount = 0;
    // Do I2C transfer receive
    if (I2C_transfer(handle, &i2cTrans)){
         // System_printf("OPT3001 configure OK!");
    } else {
         System_printf("OPT3001 Configure fail!");
     //Read OPT3001
     txBuf[0] = 0x00;
     i2cTrans.writeCount = 1;
     i2cTrans.readCount = 2;
     // Do I2C transfer receive
     if (I2C_transfer(handle, &i2cTrans)){
          // LCD_WRITE_STRING("OPT3001 read OK!",8);
          int result = (rxBuf[0] << 8 ) | (rxBuf[1]);
          uint16_t e, m;
          float lux;
          m = result & 0x0FFF;
          e = (result & 0xF000) >> 12;
          lux= (float)m * (0.01 * exp2(e));
          System_printf("Light level (Lux): %d\r\n",(int)lux);
          // finally we also need to set the sensor value
          lightSensor.rawData = (int16_t)lux;
     } else {
          System_printf("OPT3001 read fail!");


    A fairly basic test got me readings of around 600 lux in daylight, and just 4 when I covered the sensor! I could print that out on the debug window, and I’d discovered that the code already contained the lightSensor variable ready to for this, but how to communicate that to the gateway?


    Luckily, the TI example code anticipated adding some of these sensors, so all I had to do was define LIGHT_SENSOR, and the place to do that is as a predefined symbol under the Project\Show Build Settings menu.

    Showing the project build options

    You can see I’ve added the HUMIDITY_SENSOR definition too.


    I added some more I2C code to configure the BME280 sensor


     // Now we make a start on reading environmental
     // data from the BME280
     i2cTrans.slaveAddress = 0x77;
     // write the configuration to the device
     txBuf[0] = 0xF2;
     txBuf[1] = 0x01;
     i2cTrans.writeCount = 2;
     i2cTrans.readCount = 0;
     // Do I2C transfer receive
     if (I2C_transfer(handle, &i2cTrans)){
          // System_printf("BME280 ctrl_hum OK!");
     } else {
          System_printf("BME280 ctrl_hum fail!");
     txBuf[0] = 0xF4;
     txBuf[1] = 0x25;
     i2cTrans.writeCount = 2;
     i2cTrans.readCount = 0;
     // Do I2C transfer receive
     if (I2C_transfer(handle, &i2cTrans)){
         // System_printf("BME280 ctrl_meas OK!");
     } else {
          System_printf("BME280 ctrl_meas fail!");


    Having read the datasheet, there’s some pretty complicated compensation you can do, so I defined some type definitions so I could use some of the example code more easily:


    typedef signed long BME280_S32_t;
    typedef unsigned long BME280_U32_t;
    typedef signed long long BME280_S64_t;


    Next step was to define all the required compensation variables


     // initialise all the compensation values
     uint16_t dig_T1;
     int16_t dig_T2;
     int16_t dig_T3;
     uint16_t dig_P1;
     int16_t dig_P2;
     int16_t dig_P3;
     int16_t dig_P4;
     int16_t dig_P5;
     int16_t dig_P6;
     int16_t dig_P7;
     int16_t dig_P8;
     int16_t dig_P9;
     uint8_t  dig_H1;
     int16_t dig_H2;
     uint8_t  dig_H3;
     int16_t dig_H4;
     int16_t dig_H5;
     int8_t  dig_H6;


    I then read out all the compensation values (every time, rather than just once, but it kept all the code together, and was quick, if a little dirty to implement).


     // read out the compensation values
     txBuf[0] = 0x88;
     i2cTrans.writeCount = 1;
     i2cTrans.readCount = 6;
     // Do I2C transfer receive
     if (I2C_transfer(handle, &i2cTrans)){
          dig_T1 = (rxBuf[1] << 8 ) | (rxBuf[0]);
          dig_T2 = ((int16_t)rxBuf[3] << 8 ) | ((int16_t)rxBuf[2]);
          dig_T3 = ((int16_t)rxBuf[5] << 8 ) | ((int16_t)rxBuf[4]);
     } else {
          System_printf("BME280 read temp comp failed!");
     txBuf[0] = 0x8E;
     i2cTrans.writeCount = 1;
     i2cTrans.readCount = 20;
     // Do I2C transfer receive
     if (I2C_transfer(handle, &i2cTrans)){
          dig_P1 = (rxBuf[1] << 8 ) | (rxBuf[0]);
          dig_P2 = (int16_t)((rxBuf[3] << 8 ) | rxBuf[2]);
          dig_P3 = (int16_t)((rxBuf[5] << 8 ) | rxBuf[4]);
          dig_P4 = (int16_t)((rxBuf[7] << 8 ) | rxBuf[6]);
          dig_P5 = (int16_t)((rxBuf[9] << 8 ) | rxBuf[8]);
          dig_P6 = (int16_t)((rxBuf[11] << 8 ) | rxBuf[10]);
          dig_P7 = (int16_t)((rxBuf[13] << 8 ) | rxBuf[12]);
          dig_P8 = (int16_t)((rxBuf[15] << 8 ) | rxBuf[14]);
          dig_P9 = (int16_t)((rxBuf[17] << 8 ) | rxBuf[16]);
          dig_H1 = rxBuf[19];
     } else {
         System_printf("BME280 read press comp failed!");
     txBuf[0] = 0xE1;
     i2cTrans.writeCount = 1;
     i2cTrans.readCount = 7;
     // Do I2C transfer receive
     if (I2C_transfer(handle, &i2cTrans)){
          dig_H2 = (int16_t)((rxBuf[1] << 8 ) | (rxBuf[0]));
          dig_H3 = rxBuf[2];
          dig_H4 = (int16_t)((rxBuf[3] << 4 ) | (rxBuf[4] & 0x0F));
          dig_H5 = (int16_t)((rxBuf[5] << 4 ) | ((rxBuf[4] >> 4 ) & 0x0F));
          dig_H6 = (int8_t)rxBuf[6];
     } else {
          System_printf("BME280 read hum comp failed!");


    Once we’d got all that, it was time to read some proper values:


     // read out the data
     txBuf[0] = 0xF7;
     i2cTrans.writeCount = 1;
     i2cTrans.readCount = 8;
     // Do I2C transfer receive
     if (I2C_transfer(handle, &i2cTrans)){
          // System_printf("BME280 data read OK!");
     } else {
          System_printf("BME280 data read failed!");
     // compensate the measurements...
     // converted the following from a function to inline code
     // taken from
     // Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
     // t_fine carries fine temperature as global value
     int32_t t_fine;
     uint32_t adc_T = ( (uint32_t)rxBuf[3] << 12 ) |
          ( (uint32_t)rxBuf[4] << 4 ) |
          ( (uint32_t)rxBuf[5] >> 4 );
          int32_t var1;
          int32_t var2;
          int32_t temperature;
          int32_t temperature_min = -4000;
          int32_t temperature_max = 8500;
          var1 = (int32_t)((adc_T / 8) - ((int32_t)dig_T1 * 2));
          var1 = (var1 * ((int32_t)dig_T2)) / 2048;
          var2 = (int32_t)((adc_T / 16) - ((int32_t)dig_T1));
          var2 = (((var2 * var2) / 4096) * ((int32_t)dig_T3)) / 16384;
          t_fine = var1 + var2;
          temperature = (t_fine * 5 + 128) / 256;
          if (temperature < temperature_min)
               temperature = temperature_min;
          else if (temperature > temperature_max)
               temperature = temperature_max;
          tempSensor.ambienceTemp = temperature / 100;
          tempSensor.objectTemp =  tempSensor.ambienceTemp;
          System_printf("Temperature (degC): %d\r\n", temperature/100);
     // This is an inline version of the compensate
     // pressure code
     uint32_t adc_P = ( (uint32_t)rxBuf[0] << 12 ) |
          ( (uint32_t)rxBuf[1] << 4 ) |
          ( (uint32_t)rxBuf[2] >> 4 );
          int32_t var1;
          int32_t var2;
          int32_t var3;
          int32_t var4;
          uint32_t var5;
          uint32_t pressure;
          uint32_t pressure_min = 30000;
          uint32_t pressure_max = 110000;
          var1 = (((int32_t) t_fine) / 2) - (int32_t) 64000;
          var2 = (((var1 / 4) * (var1 / 4)) / 2048)  * ((int32_t) dig_P6);
          var2 = var2 + ((var1 * ((int32_t) dig_P5)) * 2);
          var2 = (var2 / 4) + (((int32_t) dig_P4) * 65536);
          var3 = (dig_P3 * (((var1 / 4) * (var1 / 4)) / 8192))  / 8;
          var4 = (((int32_t) dig_P2) * var1) / 2;
          var1 = (var3 + var4) / 262144;
          var1 = (((32768 + var1)) * ((int32_t) dig_P1)) / 32768;
          /* avoid exception caused by division by zero */
          if (var1)
               var5 = (uint32_t) ((uint32_t) 1048576) - adc_P;
               pressure = ((uint32_t) (var5 - (uint32_t) (var2 / 4096)))  * 3125;
               if (pressure < 0x80000000)
                    pressure = (pressure << 1) / ((uint32_t) var1);     
                    pressure = (pressure / (uint32_t) var1) * 2;     
               var1 = (((int32_t) dig_P9) * ((int32_t) (((pressure / 8) * (pressure / 8)) / 8192))) / 4096;
               var2 = (((int32_t) (pressure / 4)) * ((int32_t) dig_P8)) / 8192;
               pressure = (uint32_t) ((int32_t) pressure + ((var1 + var2 + dig_P7) / 16));
               if (pressure < pressure_min)     
                    pressure = pressure_min;
               else if (pressure > pressure_max)
                    pressure = pressure_max;     
               pressure = pressure_min;
          System_printf("Pressure (mBar): %d\r\n", pressure/100);
     // This is an inline version of the compensate
     // humidity code
          uint32_t adc_H = ( (uint32_t)rxBuf[6] << 8 ) |
          ( (uint32_t)rxBuf[7] );
          int32_t var1;
          int32_t var2;
          int32_t var3;
          int32_t var4;
          int32_t var5;
          uint32_t humidity;
          uint32_t humidity_max = 100000;
          var1 = t_fine - ((int32_t) 76800);
          var2 = (int32_t) (adc_H * 16384);
          var3 = (int32_t) (((int32_t) dig_H4) * 1048576);
          var4 = ((int32_t) dig_H5) * var1;
          var5 = (((var2 - var3) - var4) + (int32_t) 16384) / 32768;
          var2 = (var1 * ((int32_t) dig_H6)) / 1024;
          var3 = (var1 * ((int32_t) dig_H3)) / 2048;
          var4 = ((var2 * (var3 + (int32_t) 32768)) / 1024)  + (int32_t) 2097152;
          var2 = ((var4 * ((int32_t) dig_H2)) + 8192) / 16384;
          var3 = var5 * var2;
          var4 = ((var3 / 32768) * (var3 / 32768)) / 128;
          var5 = var3 - ((var4 * ((int32_t) dig_H1)) / 16);
          var5 = (var5 < 0 ? 0 : var5);
          var5 = (var5 > 419430400 ? 419430400 : var5);
          humidity = (uint32_t) (var5 / 4096);
          if (humidity > humidity_max)
               humidity = humidity_max;
          humiditySensor.humidity = humidity / 1024;
          System_printf("Humidity (\%): %d\r\n", humidity/1024);


    All that remains is to remember to close the I2C device


    /* Deinitialized I2C */


    The end result is shown on the gateway:

    Web page of sensor data

    Next steps

    There are a few more aspects I’d like to add to this:

    • Battery powering the device, including reading battery volts through the ADC
    • Deploying the device remotely
    • Adding a second device to the gateway
    • Sending the data to my own MQTT cloud gateway


    There’s a few more weeks of this RoadTest left yet, hopefully I can manage at least the first two of these, and I’ll update this as I go!


Also Enrolling
Enrollment Closes: Feb 12 
Enrollment Closes: Feb 14 
Enrollment Closes: Feb 12