Project

Build an Arduino 101 Data Logger with the TI SensorTag

June 07, 2017 by Raymond Genovese

Use an Arduino/Genuino 101, a TI SensorTag, and an SD shield to build a BLE multi-sensor data logger that records temperature, humidity, atmospheric pressure, ambient light and more.

In this project, we build an economical and full-featured Bluetooth Low Energy (BLE) data logging system that takes readings from multiple sensors and saves the data to an SD memory card.

A data logger is an integral part of environmental monitoring with wide application. Essentially, the logger reads sensor data and records the readings over time.

In our project, the sensor array for the data logger is provided by a TI SensorTag that operates as a battery-powered BLE peripheral. To read the SensorTag, we use an Arduino 101 operating as a BLE central device. To log the sensor readings, we use the Arduino 101, along with an SD shield, to write the time-stamped sensor data to a standard Secure Digital High Capacity (SDHC) memory card (up to 32 GB) that can be read by multiple computer systems.

 

The completed data logger project.

Project Requirements

Bill of Materials

 Description  Price (USD)
 SensorTag CC2650STK also available here and here  $29.00
 Arduino/Genuino 101  $30.00
 Adafruit Data Logging shield for Arduino  $13.95
 Case, power supply, SDHC card, CR1220 battery  $25.10
**Note: The case listed was modified slightly to accommodate the SD card and allow access to the reset button on the shield.

 

Author's Setup Used for the Project

  • Arduino IDE 1.8.2
  • Intel Curie boards version 2.02 (installed from the Arduino Boards Manager)
  • Arduino SD library (included in the Arduino IDE)
  • SensorTag firmware revision 1.32
  • Windows 7 (64 bit) OS

 

Code

Arduino programs for the project are available for download at the end of the article.

SensorTag

The CC2650STK SensorTag in its external enclosure.

 

At the time of this writing, there are three SensorTags available from TI. The SensorTag that we use for the project (CC2650STK, Multi-standard SensorTag) is based on the CC2650 low-power wireless MCU and is a BLE 4.0 peripheral. The low-power feature allows the SensorTag to be battery powered and it can offer over a year of battery life from a single coin cell battery (CR2032).

The device hosts an impressive array of sensors as well as additional features. TI offers open source hardware and software reference design support for the device (see links here and search for "SensorTag" here). There is also a good deal of developer and user support for the SensorTag and its components on the dedicated TI forums.

 

The CC2650STK SensorTag without the external enclosure.

 

TI offers a complete teardown of the SensorTag online You can see all the details, including all of the sensors and other components and their placement on the board.

We will use the following sensors on the SensorTag in the data logger project:

  • TMP007 - Infrared thermopilesSensor
  • OPT3001 - Ambient light sensor
  • HDC1000 - Temperature and humidity sensor
  • BMP280 - Barometric pressure sensor

The SensorTag operates as a BLE peripheral device and, in addition to the datasheets for the sensors listed above, it is recommended that you become familiar with two additional documents: The SensorTag User's Guide and the full Generic Attributes (GATT) Table

It is notable that, within program memory limitations, another available sensor (MPU-9250) and other switches (e.g., magnetic reed switch, user buttons) could be added if you had a particular need for them. There is even a digital microphone that can potentially be used.

Arduino 101

To get data from the SensorTag, we need a microcontroller and BLE capability. Specifically, we need BLE capability that includes the ability to function in the central role. The SensorTag operates as a peripheral device. In order for the microcontroller to find it, connect to it and converse with it (i.e., read the sensors), it needs to work as a central device.

Another way to think about the peripheral vs. central roles for this project is to view the SensorTag as a GATT server and the microcontroller as the GATT client.

The Arduino 101 (Genuino 101 outside of the US) contains on-board BLE capability and, with the release of the Core version 2.02 (installable through the Arduino IDE), can function in either peripheral or central roles.

 

The Arduino/Genuino 101 board

 

It is a 3.3V board with 5V-tolerant I/O and contains the Intel Curie microprocessor (32-bit Intel Quark SoC). There is plenty of I/O as well as SPI and I2C interfaces. It has much of the programming ease of the familiar Arduino family and can use the ubiquitous Arduino IDE. The board's schematic is available online as is the source code for the firmware. Additionally, there are support forums for the board and for the Curie chip.

SD Card and RTC

The Adafruit Datalogging Shield (Rev B).

 

Once we are able to connect with the SensorTag and read the sensors with the Arduino 101, we need to be able to save the data. Additionally, because we will be logging the data periodically, we need to timestamp the sensor readings.

To accomplish these functions, the third and final hardware component for the project is the Adafruit datalogging shield pictured above. The schematic for the shield is available online and there is a considerable amount of detailed documentation for the board that includes examples.

The board features an SD card interface that works with FAT16 or FAT32 formatted cards. The board also features a PCF8523 real time clock (RTC) with battery (CR1220) back up. These two features are precisely what we need to complete the data logger.

To use the SD card feature, we will utilize the Arduino SD library that is included in the IDE. There are a number of programs available to format SD cards and I used SD Formatter for Windows with a 32GB card and a FAT32 format.

To use the RTC, you will need to install the library from Adafruit that is available online. The library includes a number of examples, including one that sets the clock using the time and date stamp from the computer used to compile the code.

The library provides a Unix time variable (unixtime) as well as the usual time and date variables. Unix time tracks time as the number of seconds since 00:00:00 Thursday, 1 January 1970 (UTC). We will use this variable to time stamp our sensor readings.

 

Connections for the User LEDs (LED1 and LED2).

 

There is one other feature of the shield that we will use for the data logger and that is the two user LEDs that are available. To make use of this feature, you will need to solder two connections from L1 and L2, to pins on the header connections for the digital I/O ports. I used ports 6 and 7, respectively (see figure above). With those connections made, we can use LED1 and LED2 (which are near the reset button) from within our program to signal events to the user.

Programming

Once we have settled on the hardware components, we are ready to address the software. The included program, SensorTagDLv1.ino, is the complete Arduino 101 program for the data logger. It is liberally commented and what follows is intended as a guide for understanding the program. Some basic knowledge of BLE programming and the linked reference material elsewhere in the article (user guide, GATT table, sensor data sheets) will also aid in understanding the program.

To get the sensor readings from the SensorTag, the processes listed below are required. Again, it is advisable that you become familiar with the aforementioned SensorTag user guide and the complete GATT table since the characteristics and services for the SensorTag, as well as the specific process for reading each of the sensors, are outlined in those documents.

1. Find the SensorTag. The Arduino 101 finds the SensorTag by scanning for peripherals and recognizing the SensorTag by the advertised name, "CC2650 SensorTag", which is available in the variable peripheral.localName() when the Arduino 101 has found a peripheral device.

The SensorTag must be advertising to be detected by any central device. The SensorTag advertises for approximately 2 minutes (100 msec interval) upon power up (by pressing the power button). Advertising also takes place for the same interval after disconnection from a device. Advertising is indicated by the green LED on the SensorTag blinking at a 1 Hz rate.

2. Connect to the SensorTag. Once it has been seen by the central device, connection to the SensorTag is easy. In this case, however, a connection is not the same as a secure pairing or bonding, which the SensorTag does not offer—it is a simple and non-secured connection.

It is also notable that the data logger expects only a single SensorTag to be out there and will connect to the first one found. If your environment contains multiple SensorTags, then the section in the program can be modified to search for the MAC address of a specific SensorTag.

3. Discover peripheral characteristics. Essentially, the discovery process is a BLE process to let the central device (Arduino 101) know about what the peripheral device (SensorTag) has to offer. Of course, we already know what attributes are in the SensorTag, but we still need to programmatically discover them. The function do_discovery() discovers the peripheral attributes for each of the sensors that we want to read. In each case, a “value” and a “control” attribute are discovered.

A failure of any characteristic discovery results in disconnection of the peripheral.

4. Subscribe to peripheral services. For each sensor, there is a service subscription function (e.g., subscribe_BP() for the barometric pressure sensor). The central device must be subscribed to a peripheral service before it can request that the peripheral provide use of the service.

5. Read a sensor. Once we have “connected”, “discovered”, and “subscribed”, we can read the sensors and do so without having to repeat the discovery and subscription processes, as long as we remain connected.

Basically, we need to request that the sensor be turned on and wait for the time it takes the sensor to perform a read. Then get the sensor values and, finally, request that the sensor be turned off.

Note that this procedure is distinct from using a notification process in which the sensors are continually read at some interval and the peripheral device notifies the central device that a sensor value has been updated.

The SensorTag can be configured either way. That is, we could turn notifications on and use them. The way we do it in the data logger, however, requires less power from the SensorTag and offers more control for the Arduino 101. We request that the sensors be read only when we want the readings.

To further illustrate the process, the code fragment below shows how we are reading the humidity sensor (after connection, discovery, and subscription). From the SensorTag User Guide, we know that the temperature and relative humidity values will be transmitted as 16-bit values (in LSB, MSB order). These values will be read from the byte array for the HUMValCharacteristic. After deriving the final values for temperature and relative humidity, they are saved in a data structure.


if (peripheral.connected()) {
    // while the peripheral is connected, turn the humidity sensor on by writing a '1' to the characteristic
    HUMConCharacteristic.writeByte(0x01);
    delay(1200); // wait for the sensor to do a read
    unsigned int rawtem = (HUMValCharacteristic[0]) + (HUMValCharacteristic[1] * 256);
    unsigned int rawhum = (HUMValCharacteristic[2]) + (HUMValCharacteristic[3] * 256);
    HUMConCharacteristic.writeByte(0x00); // turn the sensor off
    // calculate final temperature and relative humidity values
    float temp = (rawtem / 65536.0) * 165.0 - 40.0;
    temp = ((temp * 9.0) / 5.0) + 32.0; // convert to F - comment out to leave at C
    float hum = ((double)rawhum / 65536.0) * 100.0;
    // save into the structure
    SensorData.tem = temp;
    SensorData.hum = hum;
  }

Descriptions of Program Functions

setup() – contains the usual initializations and checks for the terminal errors (see description below). Initializes the BLE and begins to scan for peripherals.

write_data() – after reading the sensors and storing the values in a data structure, writes the elements of the structure to the SD card. 

t_error() – flashes both user LEDs in an endless loop.

do_discovery() – performs discovery for each of the SensorTag characteristics of interest.

subscribe and read functions for each sensor, e.g., subscribe_BP(BLEDevice peripheral) and read_BP(BLEDevice peripheral).

loop() – scans for the SensorTag, connects to the SensorTag, calls do_discovery() and calls the subscribe() functions for each sensor of interest. Subsequently, while connected, reads all of the sensors and calls write_data() and repeats after a period expires. If the SensorTag becomes disconnected, it will begin scanning and start the whole process again.

 

Program Notes

Period. The time interval for sensor reads is stored in the global variable, “period”, defined as the number of milliseconds before the sensor reads occur (after the initial sensor reads). Default value=600000 (10 minutes).

Data File. The name of the data file is stored in the global variable fname[] and the default name is "STDATA.CSV". The file is created if it does not exist. If it does exist, data are appended to the file.

Data are written in comma separated values (csv) format. Each line contains the following measures:

unixtime, temperature, humidity, barometric pressure, die temperature, lux, object temperature, die temperature.

The “die temperature” values are the on-chip temperature readings from the BMP280 and TMP007, respectively.

The object temperature is an uncalibrated value. See the TMP007 Calibration Guide PDF for more on this issue.

RTC. The RTC on the SD shield is set to the system time when the program is compiled using the line, "rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));". If you want to set the clock in another manner, just comment out that line. 

LED1 (green) and LED2 (red). The LEDs are used to signal the status to the user in the following ways:

Terminal Error – Both LEDs flash in an endless loop in the function t_error(). This function is called when there has been an error that requires user intervention. A terminal error occurs when any of the following situations arise: 1) RTC cannot be initialized, 2) SD cannot be initialized, 3) cannot connect to the SensorTag, 4) cannot discover or subscribe to services, and 5) SD cannot be opened to write the data. The first three situations are checked in the setup() function, the fourth in loop(), and the last in the write_data() function.

Period wait loop – the green LED flashes while the program is waiting for “period” to expire.

Writing data – the green LED is illuminated while the data are being written to the SD card. There is also a red LED marked "SD" on the shield (separate from LED1 and LED2) that will briefly illuminate when the SD card is accessed.

Connecting to the SensorTag – when not connected to the SensorTag, the red LED will be illuminated. If you see the red LED illuminated more than briefly on power up, it is likely that the SensorTag is not advertising, making it impossible for the Arduino 101 to find it. If you see the red LED illuminated during normal operation, connection has been lost for some reason such as intererence or distance limitation. In the case of a lost connection, the software will automatically try to reconnect and continue recording.

Test Program

When I construct a project like the data logger, I usually write a preliminary program early in the process of development. In this case, the included program, SensorTagTestv1.ino, is such a program.

This program does not use the SD shield at all. It will, however, connect to the SensorTag and read the sensors. It uses the Arduino serial monitor to display the status (and provide error messages as needed) of connection, discovery, subscription and the sensor values (see screenshot below). It is provided as a developmental aid to the data logger project and any similar projects that the reader might be interested in creating.

 

Screenshot from SensorTagTestv1.ino (click to enlarge).

Data Graphs

What fun is it to make a data logger if you can't show off a few graphs?

 

 

 

 

Closing Thoughts

This has been a rewarding project and I feel like it exceeded my expectations. It is particularly advantageous to have the sensor array battery-powered and the BLE functionality makes it easy to position it at a distance from the rest of the logging system. Additionally, the project can be enhanced and expanded to meet particular needs.

The SensorTag is an impressive device. The Arduino 101, with its on-board BLE radio and included CurieBLE software, including the capability to act as a central device, makes using the SensorTag easy. Add to this pair an SD shield with an RTC, and you have a very useful device that is relatively easy to construct.

 

Give this project a try for yourself! Get the BOM.

Program files for the project can be downloaded by clicking the link below.

DataLogger.zip

8 Comments
  • S
    Simset August 02, 2017

    Hello,

    I tested your project files and it worked well. Thank you for that.
    I would like to add de IMU support. But even after recreating a service discovery function, subscribing tests and reading tests, I have a strange result: every measure I make of the accelerations value are equal to 0.
    It seems that I never send the good parameters for waking up the IMU.
    I tried to have a look on TI BLE device monitor to see what values to set in parametters. But even with that, It don’t work.
    Have you an idea or a link which could help me with that?

    Thank you again for your work.

    Like. Reply
    • R
      Raymond Genovese August 02, 2017
      Thanks for the kind words. First off, the "IMU" was the term in the older SensorTag and that has been replaced with the Movement Sensor which is a MPU9250 from InvenSense. Just want to make sure we are on the same page. I have not used it, but I just got through looking at the GATT table. It's more complicated that a simple sensor, but I think the same procedures would be followed. If you look at the table for the movement sensor you can see that only three characteristics can be written to - just like, for example, the barometric sensor. For the latter sensor, you can write to a characteristic to 1) enable notifications, 2) start the sensor readings, 3) set a period. Starting the sensor is the important one for you at the moment since you are not getting any readings at all and that could simply be because it is not turned on. It looks like the same scheme works for the Movement Sensor as for the barometric sensor, for example, and the key for the movement sensor is: 0x3C 0xAA82 Movement Config RW Axis enable bits:gyro-z=0,gyro-y,gyro-x,acc-z=3,acc-y,acc-x,mag=6 Range: bit 8,9 So, instead of simply turning it on like with the barometric sensor, you have to enable those bits and you need to look at the explanation in the User's Guide under Movement Sensor. There, they have information about the different bits. Again, I have not used the movement sensor, but that is the approach I would take. Hope this helps and please let us know what happens.
      Like. Reply
      • S
        Simset August 03, 2017
        Hello, Thank you for your fast response. Yes, I'm currently working with the Sensortag second version. I went to the same conclusion about the path to follow. But after hours of "try something and don't get the result", I think that my problem is more syntax based. I created the BLECharacteristics according to the Gatt table. Noted what kind of data to write to be happy. But it's like the Characteristics just don't update when I send new values. I tried the writeByte, writeValue (with an array of bytes and even the version with an offset set to 0). Nothing seem to work. It's awckward but It's like when I need to write more than one byte, it is simply ignored. I just do: const byte paramaxl[2] = {0x38,0x02} ; IMUConCharacteristic.writeValue(paramaxl, 2, 0); (random delay) Serial.print(IMUValCharacteristic[0]); (I repeat this line until the value n°12 to be sure to cover all axes) And I only get a bunch of zeros. If you have an idea about what am I missing... Thanks again for your time.
        Like. Reply