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 Pin | DC9003 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 | Code | Hex |
---|---|---|
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.