Raspberry Pi IoT In C Using Linux Drivers - The I2C Linux Driver
Written by Harry Fairhead   
Monday, 25 July 2022
Article Index
Raspberry Pi IoT In C Using Linux Drivers - The I2C Linux Driver
A Real Device
Reading Temperature Data
Checksum

Reading 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 earlier in a general context. 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 simplest thing to do is poll for the data. How to use clock stretching is described in the next chapter.

 

In this case we send F3 and then wait for there to be something to read. If the slave isn't ready it simply replies with a NAK and this causes the read to return -1. When the slave returns an ACK then three bytes are read and result is 3, bringing the loop to an end:

    char buf[3] = {0xF3};
    write(i2cfd, buf, 1);
    while (1) {
        int result = read(i2cfd, buf, 3);
        if (result > 0) break;
        usleep(100*1000);
    }

This polls repeatedly until the slave device returns an ACK, when the data is loaded into the data.

Putting this into a complete program gives:

int main(int argc, char** argv) {
    checkI2CBus();
    int i2cfd = open("/dev/i2c-1", O_RDWR);
    ioctl(i2cfd, I2C_SLAVE, 0x40);
    char buf[3] = {0xF3};
    write(i2cfd, buf, 1);
    while (1) {
        int result = read(i2cfd, buf, 3);
        if (result > 0) break;
        usleep(10*1000);
    }
    uint8_t msb = buf[0];
    uint8_t lsb = buf[1];
    uint8_t check = buf[2];
    printf("msb %d \n\rlsb %d \n\rchecksum %d \n\r",
msb, lsb, check); close(i2cfd); return (EXIT_SUCCESS); }

Where the checkI2CBus has been omitted as it was given earlier.

If you try this out you should find that it works and it prints something like:

​msb 97 
lsb 232
checksum 217

with the temperature in the 20C range.

If you look at what is happening using a logic analyzer then you will see the i
nitial interaction:

I2Clogicpoll1

followed by repeated attempts to read the data:

I2Clogicpoll2

When the device is ready it responds with an ACK and the three data bytes are read:

I2Clogicpoll3

You can also see that only one start and stop bit is used.

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 datasheet you will discover that the temperature data is the 14-bit value that results from 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 1 if the data is a humidity measurement and 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 left.

This gives:

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

As 216 is a constant that works out to 65536 it is more efficient to write:

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

Now all we have to do is print the temperature:

printf("Temperature %f C \n\r", temp);

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. Reading the humidity using polling is exactly the same as reading the temperature - all that changes is the command code we send:

  buf[0] = 0xF5;
    write(i2cfd, buf, 1);
    while (1) {
        int result = read(i2cfd, buf, 3);
        if (result > 0) break;
        usleep(10 * 1000);
    }
    msb = buf[0];
    lsb = buf[1];
    check = buf[2];

 

Once we have the data the formula to convert the 16-bit value to percentage humidity is:

RH= -6 + 125 * data16 / 216

and the Temperature in C is:

data16 = ((unsigned int) msb << 8) | (unsigned int) (lsb & 0xFC);
float hum = -6 + (125.0 * (float) data16) / 65536;
printf("Humidity %f %% \n\r", hum);



Last Updated ( Monday, 25 July 2022 )