Interface your Mote to a Sensirion Temperature/Humidity Sensor

Sensirion I2C temperature/humidity sensors are almost trivially easy to use with the On-Chip SDK.  In this recipe, we connect a mote to the SHT21.


Introduction

Sensirion makes a line of very low power I2C temperature and humidity sensors that are almost trivially easy to interface to a mote using the On-chip SDK (OCSDK).  We only need to make a few changes to the 02-i2c sample to start making measurements with the SHT21. This exercise assumes you have already set up your IAR toolchain and can compile the examples found in the OCSDK.

Hardware Connection

SHT21 PinDC9003 Pin
1 (SDA)P3.9 (S_SSn/SDA)
2 (VSS)P1.24 (GND)
5 (VDD)P1.22 (VSUPPLY)
6 (SCL)P3.11 (S_SCK/SCL)

Software

The first thing we need to do is select the correct slave address I2C_SLAVE_ADDR for the SHT21.  I2C uses 7-bit addresses, which are followed by a read/write bit.   The SHT21 data sheet gives the device address as '1000’000’ - it's tempting to interpret this as 0x80, but the OCSDK driver is expecting a right-justified 7-bit value.  The driver shifts the 7-bit word left and inserts the read/write bit automatically depending on the format of the dn_ioctl call. Thus the I2_SLAVE_ADDR is:

#define I2C_SLAVE_ADDR       0x40  // b100 0000

Next, the 02-i2c sample has a one second loop where it writes a 3-byte command (0x80 0x81 0x82) from the buffer i2c_app_v.i2cBuffer, then reads a 3-byte command into the sample into the same buffer. 

The SHT21 uses 1-byte commands, so we need to change the contents of the buffer for the write.  The SHT21 datasheet defines the command set:

Command

CodeHex
Temperature Measurement

1110’0011 

0xE3
Relative Humidity Measurement

1110’0101 

0xE5
Temperature Measurement (no hold)

1111’0011 

0xF3
Relative Humidity Measurement (no hold)

1111’0101 

0xF3

Write user register

1110’0110 

0xE6

Read user register

1110’0111 

0xE7
Soft Reset

1111’1110 

0xFE

Normally the sensor holds the SCL line to force the master to pause (here during the measurement) until the slave is ready to answer. In the "no hold" versions of the command the sensor does not hold the line - this allows the master to move to another device in a multi-drop scenario.  We use the normal temperature measurement in the sample, so we set the i2cbuffer as follows:

#define MEAS_TEMP           0xE3
#define I2C_WRITE_LENGTH    1
#define I2C_READ_LENGTH     3
...
 	//===== step 1. write to I2C slave
    // wait a bit
    OSTimeDly(1*SECOND);
 
	// prepare buffer
    i2c_app_v.i2cBuffer[0] = MEAS_TEMP;
 
    // initialize I2C communication parameters
      i2c_app_v.i2cTransfer.slaveAddress    = I2C_SLAVE_ADDR;
      i2c_app_v.i2cTransfer.writeBuf        = i2c_app_v.i2cBuffer;
      i2c_app_v.i2cTransfer.readBuf         = NULL;
      i2c_app_v.i2cTransfer.writeLen        = I2C_WRITE_LENGTH;
      i2c_app_v.i2cTransfer.readLen         = 0;
      i2c_app_v.i2cTransfer.timeout         = 0xff;


We then read the 3-byte command, which consists of a 2-byte big-endian temperature measurement, followed by a 1-byte CRC.  The CRC is used to detect errors on the I2C bus, which can occur if twisted pairs are not used, or the run length is long.  We ignore it in this sample, but it is straightforward to compute and use to reject measurements with errors.

 	//===== step 2. read from I2C slave
    // wait a bit
    OSTimeDly(1*SECOND);
 
	// prepare buffer
    memset(i2c_app_v.i2cBuffer,0,sizeof(i2c_app_v.i2cBuffer));
 
    // initialize I2C communication parameters
      i2c_app_v.i2cTransfer.slaveAddress    = I2C_SLAVE_ADDR;
      i2c_app_v.i2cTransfer.writeBuf        = NULL;
      i2c_app_v.i2cTransfer.readBuf         = i2c_app_v.i2cBuffer;
      i2c_app_v.i2cTransfer.writeLen        = 0;
      i2c_app_v.i2cTransfer.readLen         = I2C_READ_LENGTH;
      i2c_app_v.i2cTransfer.timeout         = 0xff;


Lastly we copy the temperature measurement from the i2cBuffer and swap endinanness.  The value return needs to be scaled before we print it on the CLI - we can sacrifice some accuracy and use integer math, or use floating point math at the expense of some code space from the larger library :

INT16S  temperature;
INT32S  tempInC;
float ftemp;
 
memcopy(&temperature, i2c_app_v.i2cBuffer, sizeof(INT16));
temperature = htons(temperature);
tempInC = (176 * (INT32S)temperature) / 65536 - 47;
ftemp = 175.72 * ((float)temperature / 65536.0) - 46.85;
 
dnm_ucli_printf("Slave temp measure = 0x%02x%02x (%d)\r\n", i2c_app_v.i2cBuffer[0], i2c_app_v.i2cBuffer[1], temperature);
dnm_ucli_printf("INT: %ld, float: %f\r\n", tempInC, ftemp); 


The full source is included here.  Note that per this app note we moved the task out of the global struct in order to use the data_alignment pragma to get floats to print properly - there may be other ways to address this problem. 

Conclusion

With a few minor changes to the I2C sample code, we quickly connected a Sensirion SHT21 to a mote. It is a straightforward task to add additional commands such as relative humidity measurements or custom configuration of the sensor.