Exploring Edison - I2C Measuring Temperature And Humidity
Written by Harry Fairhead   
Monday, 07 December 2015
Article Index
Exploring Edison - I2C Measuring Temperature And Humidity
Reading I2C data
Complete Program With CRC Checksum

In Detail

If you have a logic analyser that can interpret the I2C protocol connected, what you will see is:

readreg

 

You can see that the write_byte function sends an address packet set to the device's 7-bit address 0x40 as the high order bits and the low order bit set to zero to indicate a write. After this you get a data packet containing 0xE7. After a few milliseconds it sends the address frame again only this time with the low order bit set to 1 to indicate a read and it then receives back a single byte of data from the device - the 0x42 corresponding to the 66 decimal reported.

As this is a standard send a command/register address and receive back a single byte we can perform exactly the same task using the read_byte_data  function which does the write and the read as a single operation:

mraa_i2c_context i2c;
i2c = mraa_i2c_init(1);
mraa_i2c_address(i2c, 0x40);
uint8_t data =mraa_i2c_read_byte_data (i2c, 0xE7);
printf("Register= %d \n", data);

If you run this program you will get the same result. However if you look at the logic analyser result you will see:

 

readreg2

 

The same address and data frames are sent and received but now there is no delay between the first two and the last two. 

This is the advantage of using the command oriented mraa functions. 

Reading the raw temperature data

Now we come to reading one of the two quantities that the device measures - temperature. If you look back at the command table you will see that there are two possible commands for reading the temperature: 

 

Command  Code  Comment 
Trigger Temperature Measurement 0xE3  Hold master
Trigger Temperature Measurement 0xF3  No Hold master

 

What is the difference between Hold master and No Hold master?

This was discussed in the previous chapter in a general setting. The device cannot read the temperature instantaneously and the master can either opt to be held waiting for the data, i.e. hold master, or released to do something else and poll for the data until it is ready.

The hold master option works by allowing the device to stretch the clock pulse by holding the line low after the master has released it. In this mode the master will wait until the device releases the line.

Not all masters support this mode but the Edison does and this makes this the simplest option. 

To read the temperature using the Hold master mode you simply send 0xE3 and then read three bytes. As with the simple read register command there are two ways of doing this. 

You can use the write_byte function to send the command and then use the read function to read three bytes. Notice you cannot use the read_byte function three times to read the bytes the device sends because this would send three address frames rather than the one required. 

The program is:

uint8_t buf[3];
mraa_i2c_write_byte(i2c,0xE3);
mraa_i2c_read(i2c, buf, 3);
uint8_t msb= buf[0];
uint8_t lsb= buf[1];
uint8_t check= buf[2];
printf("msb %d \n lsb %d \n checksum %d \n", msb,lsb,check);

The buffer is unpacked into three variables with more meaningful names - the msb most significant byte, lsb - least significant byte and the check(sum). 

You should see something like:

msb 97
lsb 232
checksum 217

with the temperature in the 20C range. 

The logic analyser reveals what is happening.

First we send the usual address frame and write the 0xE3. Then after a pause the read address frame is sent and the clock line is held low by the device (lower trace):

 

tempread1a

 

The clock line is held low by the device for over 42ms while it gets the data ready. It is released and the three data frames are sent:  

 

tempread1b

 

The second way of doing the job sends the command and gets the data in one operation:

mraa_i2c_read_bytes_data(i2c,0xE3,buf,3);
uint8_t msb= buf[0];
uint8_t lsb= buf[1];
uint8_t check= buf[2];
printf("msb %d \n lsb %d \n checksum %d \n",
                  msb,lsb,check);

The only difference is that now there is no pause between sending the command 0xE3 and the read address frame - we still have to wait more than 42ms for the data however. 

Finally we have to find out how to use the No hold master mode of reading the data - it is sometimes useful. 

In this case we can't use the single read_bytes_data command because the data will not be ready to read and the master will not be forced to wait for it. We have to use the two-step send the command and read the data approach:

mraa_i2c_write_byte(i2c,0xF3);
uint8_t buf[3];
while(mraa_i2c_read(i2c, buf, 3)==0){};
uint8_t msb= buf[0];
uint8_t lsb= buf[1];
uint8_t check= buf[2];
printf("msb %d \n lsb %d \n checksum %d \n",
                                msb,lsb,check);

This polls repeatedly until the data is returned - notice that the read function returns zero if it has failed to read data. The big difference is that now we could do some other work in the while loop until the data is returned. 

Processing the data

Our next task isn't really directly related to the problem of using the I2C bus but it is a very typical next step. The device returns the data in three bytes but the way that this data relates to the temperature isn't simple. 

If you read the data sheet you will discover that the temperature data is the 14-bit value that results by putting together the most and least significant byte and zeroing the bottom two bits. The bottom two bits are used as status bits - bit zero currently isn't used and bit one is a 1 if the data is a humidity measurement and a 0 if it is a temperature measurement. 

To put the two bytes together we use:

unsigned int data16=((unsigned int) msb << 8) |
                    (unsigned int) (lsb & 0xFC);

This zeros the bottom two bits, shifts the msb up eight bits and Ors the two together. The result is a 16 bit temperature value with the bottom two bits zeroed. 

Now we have raw temperature value but we have still have to convert it to standard units. The datasheet gives the formula

Temp in C = -46.85 + 175.72 * data16 / 216

The only problem in implementing this is working out 216. You can work out 2x with the expression 1<<x i.e. shift 1 x places to the right. This gives:

float temp = (float)(-46.85 +
      (175.72 * data16 / (float)(1<<16))); 

Of course 216 is a constant that works out to 65536 so it is more efficient to write:

 float temp = (float)(-46.85 +
           (175.72 * data16 / (float)65536));

It is worth noting that the floating point arithmetic that the Edison provides makes all of this calculation very much easier than it would be on a small 8-bit micro controller.

The final program is: 

int main()
{
 mraa_i2c_context i2c;
 i2c = mraa_i2c_init(1);
 mraa_i2c_address(i2c, 0x40);
 uint8_t data =mraa_i2c_read_byte_data (i2c, 0xE7);
 printf("Register= %d \n", data);
uint8_t buf[3];
 mraa_i2c_read_bytes_data(i2c,0xE3,buf,3);
 uint8_t msb= buf[0];
 uint8_t lsb= buf[1];
 uint8_t check= buf[2];
 printf("msb %d \n lsb %d \n checksum %d \n",
                                  msb,lsb,check);

 unsigned int data16=((unsigned int) msb << 8) |
  (unsigned int) (lsb & 0xFC);
 float temp = (float)(-46.85 +
                (175.72 * data16 / (float)65536));
 printf("Temperature %f C \n",temp);
 
 return MRAA_SUCCESS;
}

 

Reading the humidity

The nice thing about I2C and using a particular I2C device is that it gets easier. Once you have seen how to do it with one device the skill generalizes and once you know how to deal with a particular device other aspects of the device are usually similar. 

To read the humidity we can more or less use the same program, we just need to change the command and the formula for the final percentage humidity.

The command needed to read the three data bytes is 0xE5 and the formula to convert the 16-bit value to percentage humidity is:

RH= -6 + 125 * data16 / 2^16

Putting all this together and reusing some variables from the previous program we have:

mraa_i2c_read_bytes_data(i2c,0xE5,buf,3);
msb= buf[0];
lsb= buf[1];
check= buf[2];
printf("msb %d \n lsb %d \n checksum %d \n",
                               msb,lsb,check);
data16=((unsigned int) msb << 8) |
                  (unsigned int) (lsb & 0xFC);
float hum = (float)(-6 +
             (125.0 * data16 / (float)65536));
printf("Humidity %f %% \n",hum);

The only unusual part of the program is using %% to print a single % character - necessary because % means something in printf.

<ASIN:B00O9PMEHY@UK>

<ASIN:B00ND1KH42@COM>

<ASIN:B00ND1KNXM>

<ASIN:B00ND1KH10>



Last Updated ( Saturday, 27 February 2016 )