ESP32 In MicroPython: I2C, HTU21D and Slow Reading
Written by Harry Fairhead & Mike James   
Monday, 10 July 2023
Article Index
ESP32 In MicroPython: I2C, HTU21D and Slow Reading
A First Program
Polling
Reading Humidity

Reading Temperature Data – Polling

As clock stretching doesn’t work for everything on the ESP32, let’s consider the alternative of polling. Unfortunately there is a problem in using I2C in polling mode as well. The basic idea in polling mode is that the master keeps trying to read the data from the slave but the slave responds with a NAK to indicate that data isn’t ready.

The problem is that the ESP32 doesn’t interpret the NAK to mean “give up this attempt to read” instead it waits for another timeout period to give the slave time to try again. Unfortunately the timeout is set to about one second which severely limits the rate at which you can acquire data.

For example:

from machine import Pin,I2C
i2c0=I2C(0,scl=Pin(18),sda=Pin(19),freq=100000)
buf = bytearray([0xF3])
i2c0.writeto( 0x40, buf, True)
while True:
    try:
        read= i2c0.readfrom(0x40, 3, False)
        break
    except:
        continue
msb = read[0]
lsb = read[1]
check = read[2]
print("msb lsb checksum =", msb, lsb, check)

You can see that the while loop repeats the attempt to read until it succeeds. The problem is that the first time through the loop the readfrom ignores the NAK and waits for one second before throwing an ETIMEDOUT timeout exception.

The next time through the loop the readfrom works and gets the data that has been available since 40ms from the start of the attempt to read.

In other words, this approach works but it is very slow. To see what is supposed to happen, all you have to do is switch to the software I2C:

from machine import Pin,SoftI2C
from time import sleep_ms
i2c0 = SoftI2C(scl=Pin(18),sda=Pin(19),freq=100000)
buf = bytearray([0xF3])
i2c0.writeto( 0x40, buf, False)
while True:
    sleep_ms(5)
    try:
        read = i2c0.readfrom(0x40, 3, True)
        break
    except:
        continue
msb = read[0]
lsb = read[1]
check = read[2]
print("msb lsb checksum =", msb, lsb, check)

If you run this the readfrom throws a ENODEV exception each time the slave sends a NAK. Eventually the slave is ready with the data and the readfrom reads the three bytes. As the readfrom doesn’t timeout it returns very quickly and we have to include a sleep in the loop to reduce the number of times the master tries to read the slave. The result on a logic analyzer shows the pattern of polling:

esp32i2c3


You can see that the master tries to read the data seven times before succeeding:

esp32i2c4

The advantage of this correct procedure is that it only takes 40ms rather than over one second.

One solution to the problem of the hardware not implementing polling correctly is to simply send the commands and then sleep for 50ms before reading the data. If the device is working correctly then the data should be available after this time and can be read without polling or clock stretching.

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 bytes 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:

data16= (msb << 8) |  (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 a raw temperature value but we have still have to convert it to standard units. The datasheet gives the formula:

Temperature in °C= -46.85 + 175.72 * data16 / 2

16

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:

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

Now all we have to do is print the temperature:

print("Temperature C ", temp)

The full listing is at the end of this chapter.



Last Updated ( Tuesday, 11 July 2023 )