Sensor data communication using the I2C protocol on LinkIt ONE development boards

Introduction 

This tutorial is based on the I2C standard peripheral communication protocol feature of the LinkIt ONE development board. By the end of this tutorial you’ll have a fully functioning IoT example, which gathers sensor data using the LinkIt ONE I2C APIs.

This tutorial guides you through:

  • Building the example, with details of the hardware requirements and how to put them together.
  • Creating the software to provide the interfaces with the temperature, pressure and humidity sensors (offered by a TPH board (STH21)) and LinkIt ONE development board so that the sensor data can be collected.
  • Running your application and viewing the data collected in the serial monitor of the Arduino IDE.
  • Analyzing the signals exchanged between the LinkIt ONE board and the sensors, and observing how they relate to the instructions in your application.

At the end of the tutorial there are details on where to go for additional information on the LinkIt ONE development board and how to create software for it.

Before you start

If you haven’t built a LinkIt ONE project before you need to download and install the Arduino IDE and LinkIt ONE SDK, then configure the IDE and upgrade the board’s firmware. Full details of how to do this are provided in the LinkIt ONE quick start guide. Once you’ve completed this setup you’ll be ready to continue with this tutorial.

Build the hardware

This section describes the hardware and electronics needed to build the setup for your project.

What you need

To build your project’s hardware, in addition to LinkIt ONE development board you need the following components.

ComponentDescriptionSource
CableMicro-USB cable 
TPH BoardTemperature, pressure and humidity boardSeeed Bazaar
Logic Analyzer16-channel USB logic analyzerSaleae Logic16 USB Logic Analyzer

There are two sensor modules on the TPH board, a Sensirion SHT21 with temperature and humidity sensors and a Bosch Sensortec BMP180 with temperature and pressure sensors. The board also includes a Grove socket for the I2C pins, as shown below.

The components on the TPH board

You’ll use the Sensirion SHT21 sensor only for this project.

Putting the components together

LinkIt ONE development board provides three Grove socket I2C interfaces. Connect the sensor board to the Grove interface using a 4-wire bus connector, as shown below.

Final hardware setup

Create your software

Your project needs an Arduino Sketch to collect data from the TPH board using the I2C interface on the LinkIt ONE development board. This section describes the details of the software required with an overview of the Arduino sketch.

Overview of the Arduino Sketch

To create the device that will collect TPH data and print it on the serial interface, an Arduino sketch is implemented, verified and uploaded to the LinkIt ONE development board.

The content of a sketch is as follows:

  • Definitions of header files.
  • Definitions of variables.
  • setup() function that initializes resources, such as the I2C module.
  • loop() function that continuously listens to and processes events from hardware and software modules such as the one for the sensor. The loop() function runs forever — until the device is shutdown.

Start your Arduino project

Before you can write the code for your sketch you need to create it, which is achieved as follows:

  1. Open the Arduino IDE and by default a new sketch is created and displayed.
  2. Create a folder named SensorI2C in your documents folder and on the File menu click Save As, navigate to the new folder, name the sketch SensorI2C.ino and save it.

The TPH sensor project in the Arduino IDE

Add the header files for the supporting libraries

The first part of your code adds the libraries required to the SensorI2C.ino Arduino sketch. The I2C communication support is provided in the Wire library. Import the library as follows:

//I2C connectivity support
#include <Wire.h>

The library provides the  TwoWire  class for I2C operations, which offers seven public methods.

Define the variables

Next, the static variables used in the sketch are defined.

#define sht21address B1000000
#define sht21temp  B11110011
#define sht21humid B11110101

  1. sht21address is the I2C ID of the sensor in binary format. When the master device — your board — wants to communicate with the slave device, it needs this ID to locate the slave. You can find your device’s ID on its datasheet, in this case the ID for the TPH board is 1000000.
  2. sht21temp is the sensor ID that the master sends to the sensor to get the temperature data, in this case 11110011 in binary format.
  3. sht21humid is the sensor ID that the master sends to the sensor to get the humidity data, in this case 11110101 in binary format.

This information is also provided in the SHT21 datasheet.

Define the setup() function

Now the setup() function can be defined, to start the various services that will be used by the sketch. This involves:

  1. Initiating Serial communication with a baud rate of 9600 and printing a welcome message to the serial output.
  2. Calling the begin() method of the Wire class to initiate the I2C bus on the LinkIt ONE development board as a master device.

Add a loop() function

The loop() function is executed continuously providing temperature and humidity data to the serial output. There are two user-defined functions called in the loop() function with a delay of 500ms: readSHT21Temp() and readSHT21Humidity().

void loop(){
  float temp = readSHT21Temp();
  float hum = readSHT21Humidity();
  delay(500);
}

Reading the temperature data

You can now create the code to read the temperature data from the SHT21 sensor as follows.

  1. Tell the sensor that your board wants to read temperature data by starting an I2C wire transmission to the slave device by specifying it’s ID (sht21address) in the beginTransmission() method of the Wire class. Then write the temperature sensor ID into the write queue with the  write()  method. End the data transmission between master and slave devices by calling the endTransmission() method of the Wire class, which transmit the data buffered in the queue by write() to the slave. Apply a delay of 100 ms before proceeding to the next operation.

    float readSHT21Temp() {
      Wire.beginTransmission(sht21address);
      Wire.write(sht21temp);   
      Wire.endTransmission();
      delay(100);
  2. Request the data from the slave device by calling the  requestFrom()  method of the Wire class. Three bytes are requested as shown below. 

    Wire.requestFrom(sht21address, 3);
  3. Read the most significant and the least significant bytes with read() method and then append these bytes together in the correct order. Read the last byte into the crc (cyclic redundancy check) variable for error detection. Apply this byte for error detection, refer to the SHT21 datasheet for more information. 

    byte msb = Wire.read();
    byte lsb = Wire.read();
    int t = (msb << 8) + lsb; 
    int crc = Wire.read();  
  4. Apply the formula from the datasheet to convert the temperature into Celsius degrees. Then print the value to the serial output.

    ,

    where  is the output temperature signal from the sensor.

    float temperature = -46.85 + 175.72 * t / 65536.0;
      Serial.print("temperature:");
      Serial.println(temperature);
      return temperature;
    } 

Reading the humidity data

To read the humidity data from the slave device follow steps 1 to 3 from Reading the temperature data, using sht21humid to read the humidity data as shown below.

Wire.write(sht21humid);

Apply the formula from the datasheet to find the relative humidity in percent. Then print the value to the serial output, as shown below.

,

where RH is the relative humidity and SRH  is the relative humidity signal output.

float humidity = -6 + 125 * h / 65536.0;
  Serial.print("humidity:");
  Serial.println(humidity);
  return humidity;

Full source code

You have now completed the sketch and its full source code is shown below.

#include <Wire.h>
 
#define sht21address B1000000
#define sht21temp  B11110011
#define sht21humid B11110101
 
float readSHT21Temp() {
  Wire.beginTransmission(sht21address);
  Wire.write(sht21temp);   
  Wire.endTransmission();
  delay(100);
  Wire.requestFrom(sht21address, 3);
 
  byte msb = Wire.read();
  byte lsb = Wire.read();
  int t = (msb << 8) + lsb;
  int crc = Wire.read();
 
  float temperature = -46.85 + 175.72 * t / 65536.0;
  Serial.print("temperature:");
  Serial.println(temperature);
  return temperature;
}
 
float readSHT21Humidity() {
  Wire.beginTransmission(sht21address);
  Wire.write(sht21humid);
  Wire.endTransmission();
  delay(100);
  Wire.requestFrom(sht21address, 3);
 
  byte msb = Wire.read();
  byte lsb = Wire.read();
  int h = (msb << 8) + lsb;
  int crc = Wire.read();
 
  float humidity = -6 + 125 * h / 65536.0;
  Serial.print("humidity:");
  Serial.println(humidity);
  return humidity;
}
 
void setup() {
  Serial.begin(9600);
  while(!Serial); 
  Serial.println("---- I2C tutorial ----");
  Wire.begin();
}
 
void loop(){
  float temp = readSHT21Temp();
  float hum = readSHT21Humidity();
  delay(500);
}  

Run your application

To run your sketch on the LinkIt ONE development board, follow the instructions in the Create and run your first sketch of the Get Started with the LinkIt ONE Development Platform. You can then view the serial output data for temperature and humidity; open the Serial Monitor by clicking the button on the right upper corner of Arduino, as shown below, or open it from Tools menu by clicking Serial Monitor.

Serial monitor in the Arduino sketch

And you’ll see something similar to this, showing the temperature and pressure reading from the sensors.

The Serial.println() messages displayed in the serial monitor

Analyze the process

If you have a logic analyzer you might want to look in more detail at how your board interacts with the sensor — if you don’t have a logic analyzer the following section might still be of interest, you can jump straight to Receiving data from the sensor.

Connecting the logic analyzer and collecting waveforms

To acquiring waveforms through an analyzer — the analyzer used in this tutorial is the Saleae Logic16 USB Logic Analyzer but, these instructions should be similar for any analyzer:

  1. Connect GND, CH0 and CH1 of the logic analyzer to GND, SDA and SCL respectively of the TPH board using an I2C cable, as shown below.

     
    Logic analyzer connected to the TPH board

  2. Run the sketch then launch the logic analyzer’s software.
  3. Add a protocol analyzer from the Analyzers drop down menu for the I2C, as shown below.

    Protocol analyzer for the I2C transmission

  4. In the top menu provide the sampling rate (250MSamples) and frequency (8MHz).
  5. Click Start to observe the waveform behavior in the logic analyzer, as shown below.

    Running the analyzer

The waveform shows that there are 9 sets of data captured (one set contains temperature and humidity data). The first set is captured at 6 seconds (you can find the timeline on the top of the waveform) and the last one at around 12 seconds. Since there is a  delay(500)  in the  loop()  function (see Add a loop() function ), there is a 0.5 second delay between every set of the captured data.

Delayed data capture

A closer look to a single set of data shows it took 0.224 seconds to read the set, as shown below.

Duration between each captured data set

Next, you can analyze the I2C communication step by step for temperature and humidity data.

Receiving data from the sensor

There is an eight step process to obtain the temperature (or humidity data) in a single cycle. Each cycle contains write and read sessions. This is the implementation detail of the process to collect the temperature data:

  1. The master device sends a start signal to the slave device.
  2. The master sends the slave’s I2C address with a Read/Write bit assigned to 0 to indicate to the slave that a write operation is wanted. The slave sends an ACK/NACK bit back to the master. The implementation of the steps using the I2C APIs is described below.

    FunctionWire.beginTransmission(sht21address); // where sht21address is B1000000
    DescriptionMaster sends a begin signal. The slave accepts the signal as an SDA falling edge from high to low, when the SCL is on high. Next, it starts the transmission of the address bits. The master sends 1000000 bits to the slave. From the waveform, the first byte is 1000_0000, the left 7 bits are the address and the last bit is Read/Write bit (0 for write operation). After these bits are received by the slave, an ACK is sent from the slave to the master.
  3. If the master receives an ACK, it sends the command to the slave device to request data from the slave’s register. Slave sends an ACK/NACK bit to the master.  The implementation of the step using the I2C APIs is described below:

    FunctionWire.write(sht21temp); // where sht21temp is B11110011
    DescriptionFrom the waveform, we can see that the byte sent to slave is 1111_0011, which asks the slave to send the data. And it receives an ACK signal.
  4. The master sends a stop signal to end the write session. The implementation of the step using the I2C APIs is described below.

    FunctionWire.endTransmission();
    DescriptionMaster ends the writing session by sending an end signal. The end signal is a SDA rising edge when the SCL is high, as marked with the red point as shown below.

    SCL and SDA states at start and write data operations for temperature data

    SCL and SDA states at start and write data operations after 0.1s

  5. The master sends another start signal to the slave to start a read session. The implementation of the step using the I2C APIs is described below.

    FunctionWire.requestFrom(sht21address, 3);
    DescriptionFunction requestFrom() requests 3 bytes of data from the slave. Then it sends a start signal to the slave to write. Master sends 1000000_1_0 ( 1000000 is the slave address, 1 is the Write/Read bit that represents a read operation, 0 is the ACK signal from the slave). After that, the master receives data from the slave.

    Read operation for the temperature data

  6. If the master receives an ACK, it resends the slave device’s address with a Read/Write bit assigned to 1 to indicate it’s a read operation and requests the slave to send data. The slave sends an ACK/NACK bit to the master.
  7. If the master receives an ACK, the master begins to read data byte by byte from the slave.
  8. The master sends a stop signal after data is successfully read. Then the master continues with the next cycle. The implementation of steps 6 to 8 is as follows:

    byte msb = Wire.read();
    byte lsb = Wire.read();
    int h = (msb << 8) + lsb;

    The  read()  method of Wire class reads a byte after  requestFrom()  is called. The values of  msb lsb  and  crc  are  0110_0111 , 0010_0100 1011_1001

    int crc = Wire.read();

    The sketch calculates the temperature in degrees Celsius by concatenating the  msb  and  lsb  together, such as  0110_0111_ 0010_0100 . Converting the binary into decimal provides the value of 26404. Using the formula to calculate the temperature will provide the final value of 23.95C which is the first data shown on the serial monitor for the temperature.

    -46.85 + 175.72 * t / 65536.0 = 23.95 (where t is 26404)

    Once the bytes are properly received, the master sends a stop signal, marked with a red point, as shown in the last figure.

    To read the humidity data, the same steps are applied. Once the new cycle starts the humidity data is read, as shown below.SCL and SDA states at start and write data operations for humidity data
    and the final result is shown below:
    Read operation for the humidity data The  msb  and  lsb  bytes concatenate to become  0110_0000_1010_1110 . Converting to decimal provides the value of 24750. Applying the formula described in Reading the humidity data  will result in 41.21, which is the first humidity data shown on the Serial Monitor. 

    -6 + 125 * h / 65536.0 = 41.21%

Your next steps

This tutorial provides an example of I2C communication through the Wire library. There are also other third-party library options to support specific I2C devices. These libraries usually encapsulate methods provided by the Wire library and only expose easy-to-use methods to public. You can download your I2C device’s library and import it in your LinkIt SDK development board’s installation folder to be able to use the public methods of the library.

FAQ

Can LinkIt ONE development board act like a slave device?

No, it cannot. The LinkIt ONE development board can be only configured as a master. The behavior is different from Arduino boards that can be configured as both master and slave. Note the behavior of Wire.begin() method. In Arduino API, Wire.begin()means the LinkIt ONE development board connects to the I2C bus as a master, while Wire.begin(address) initiates the I2C bus as a slave with the I2C address specified in the parameter. Since the LinkIt ONE development board cannot be configured as a slave,Wire.begin(address) shouldn’t be used on LinkIt ONE development board, although there is no compilation error when using it. The parameter should always be empty when using the LinkIt ONE development board.