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.
Component | Description | Source |
---|---|---|
Cable | Micro-USB cable | |
TPH Board | Temperature, pressure and humidity board | Seeed Bazaar |
Logic Analyzer | 16-channel USB logic analyzer | Saleae 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.
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.
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.
- A
setup()
function that initializes resources, such as the I2C module. - A
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:
- Open the Arduino IDE and by default a new sketch is created and displayed.
- 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.
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
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.sht21temp
is the sensor ID that the master sends to the sensor to get the temperature data, in this case 11110011 in binary format.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:
- Initiating Serial communication with a baud rate of 9600 and printing a welcome message to the serial output.
- 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.
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 thebeginTransmission()
method of the Wire class. Then write the temperature sensor ID into the write queue with thewrite()
method. End the data transmission between master and slave devices by calling theendTransmission()
method of the Wire class, which transmit the data buffered in the queue bywrite()
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);
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);
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 thecrc
(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();
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.
And you’ll see something similar to this, showing the temperature and pressure reading from the sensors.
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:
- 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.
- Run the sketch then launch the logic analyzer’s software.
- Add a protocol analyzer from the Analyzers drop down menu for the I2C, as shown below.
- In the top menu provide the sampling rate (250MSamples) and frequency (8MHz).
- Click Start to observe the waveform behavior in the logic analyzer, as shown below.
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.
A closer look to a single set of data shows it took 0.224 seconds to read the set, as shown below.
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:
- The master device sends a start signal to the slave device.
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.
Function Wire.beginTransmission(sht21address); // where sht21address is B1000000
Description Master 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. 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:
Function Wire.write(sht21temp); // where sht21temp is B11110011
Description From 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. The master sends a stop signal to end the write session. The implementation of the step using the I2C APIs is described below.
Function Wire.endTransmission();
Description Master 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. 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.
Function Wire.requestFrom(sht21address, 3);
Description Function 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.- 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.
- If the master receives an ACK, the master begins to read data byte by byte from the slave.
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 afterrequestFrom()
is called. The values ofmsb
,lsb
andcrc
are0110_0111
,0010_0100
,1011_1001
.int crc = Wire.read();
The sketch calculates the temperature in degrees Celsius by concatenating the
msb
andlsb
together, such as0110_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.
and the final result is shown below:
Themsb
andlsb
bytes concatenate to become0110_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.