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

The nice thing about using I2C devices 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 part of a device, other aspects of the device are usually similar. For this reason let's implement the humidity reading using polling which we know works with the hardware and the software I2C. We write the 0xF5 once to the slave and then repeatedly attempt to read the three-byte response. If the slave isn't ready it simply replies with a NAK which the read method interprets as throwing an exception.

Once we have the data, 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 parts of the program, we have:

```buf = bytearray([0xF5])
i2c0.writeto( 0x40, buf, True)
while True:
sleep_ms(1)
try:
break
except:
continue
print("msb lsb checksum =", msb, lsb, check)
data16 = (msb << 8) | (lsb & 0xFC)
hum = -6 + (125.0 * data16) / 65536
print("Humidity ", hum)```

## Checksum Calculation

Although computing a cyclic redundancy checksum, CRC, isn't specific to I2C, it is another common task. The datasheet explains that the polynomial used is:

X8+X5+X4+1

Once you have this information you can work out the divisor by writing a binary number with a one in each location corresponding to a power of X in the polynomial, in this case the 8th, 5th, 4th and 1st bit. Hence the divisor is:

`	0x0131`

What you do next is roughly the same for all CRCs. First you put the data that was used to compute the checksum together with the checksum value as the low-order bits:

`data32 = (msb << 16)|(lsb <<8)| check`

Now you have three bytes, i.e 24 bits, in a 32-bit variable. Next you adjust the divisor so that its most significant non-zero bit aligns with the most significant bit of the three bytes. As this divisor has a 1 at bit eight, it needs to be shifted 15 places to the right to move it to be the 24th bit:

`divisor =  0x0131 <<15 `

or

`divisor = 0x988000`

Now that you have both the data and the divisor aligned, you step through the topmost 16 bits, i.e. you don't process the low-order eight bits which hold the received checksum. For each bit you check to see if it is a 1. If it is you replace the data with the data XOR divisor. In either case you shift the divisor one place to the right:

``` for i in range(16):
if data32 & 1<<(23 - i):
data32 ^= divisor
divisor>>= 1```

When the loop ends, if there was no error, the data32 should be zeroed and the received checksum is correct and as computed on the data received.

A complete function to compute the checksum, with some optimization, is:

```def crcCheck(msb, lsb,check):
data32 = (msb << 16)|(lsb <<8)| check
divisor = 0x988000
for i in range(16):
if data32 & 1<<(23 - i):
data32 ^= divisor
divisor>>= 1
return data32```

It is rare to get a CRC error on an I2C bus unless it is overloaded or subject to a lot of noise.

## Complete Listing

The complete program for reading temperature and humidity, including checksum, is:

```from machine import Pin,I2C
def crcCheck(msb, lsb,check):
data32 = (msb << 16)|(lsb <<8)| check
divisor = 0x988000
for i in range(16):
if data32 & 1<<(23 - i):
data32 ^= divisor
divisor>>= 1
return data32
i2c0=I2C(0,scl=Pin(18),sda=Pin(19),freq=100000)
buf = bytearray([0xF3])
i2c0.writeto( 0x40, buf, False)
while True:
try:
break
except:
continue
print("msb lsb checksum =", msb, lsb, check)
data16= (msb << 8) |  (lsb & 0xFC)
temp = (-46.85 +(175.72 * data16 /(1<<16)))
print("Temperature C ", temp)
print("Checksum=",crcCheck(msb,lsb,check))
buf = bytearray([0xF5])
i2c0.writeto( 0x40, buf, True)
while True:
try:
break
except:
continue
print("msb lsb checksum =", msb, lsb, check)
data16 = (msb << 8) | (lsb & 0xFC)
hum = -6 + (125.0 * data16) / 65536
print("Humidity ", hum)
print("Checksum=",crcCheck(msb,lsb,check))```

This works but takes more than two seconds to read data that should take less than one second to read. The only solution is to switch to the software I2C implementation. If you do this include a sleep in each of the while loops to reduce the number of times you attempt to read the data.

If you move to the software implementation then clock stretching is a better approach.

Of course, this is just the start. Once you have the device working and supplying data, it is time to write your code in the form of functions that return the temperature and the humidity and generally make the whole thing more useful and easier to maintain. This is often how this sort of programming goes. First you write a lot of inline code so that it works as fast as it can, then you move blocks of code to functions to make the program more elegant and easy to maintain, checking at each refactoring that it all still works.

Not all devices used standard bus protocols. In Chapter 13 we’ll look at a custom serial protocol that we have to implement for ourselves.

## Summary

• The I2C bus is simple yet flexible and is one of the most commonly encountered ways of connecting devices.

• The I2C bus uses two wires – a data line and a clock.

• The ESP32 has two I2C interfaces.

• MicroPython provides a software implementation of the I2C bus which can be used with any GPIO lines. It isn’t as efficient as the hardware implementation, but it is worth using if the slave device is slow.

• Each I2C interface can be connected to a pair of GPIO lines.

• The I2C protocol isn’t standardized and you have to take account of variations in the way devices implement it.

• There are single byte transfer operations and multibyte transfers which differ in when a stop bit is sent.

• The low-level protocol can be made slightly more high-level by thinking of it as a single write/read a register operation.

• Sometimes a device cannot respond immediately and needs to keep the master waiting for data. There are two ways to do this, polling and clock stretching.

• The ESP32 implements clock stretching, but it has a very short timeout, 13ms, that is often too short to work.

• The ESP32 doesn’t implement polling correctly because it doesn’t abort the read when it first receives a NAK but only after a one second timeout.

• The HTU21D is a simple I2C device, but getting it working involves using polling with the hardware I2C or clock stretching with the software I2C.

• Computing a checksum is an involved, but common, operation.

## Programming the ESP32in MicroPython

#### Contents

Preface

1. The ESP32 – Before We Begin
2. Getting Started
3. Getting Started With The GPIO
4. Simple Output
5. Some Electronics
6. Simple Input
8. Pulse Width Modulation
Extract:
PWM And The Duty Cycle
9. Controlling Motors And Servos
10. Getting Started With The SPI Bus
11. Using Analog Sensors
Extract:
12. Using The I2C Bus
Extract
: I2C, HTU21D And Slow Reading
13. One-Wire Protocols
14. The Serial Port
15. Using WiFi
Extract:
WiFi
16. Sockets
Extract:
Client Sockets
Extract:
SSL Client Sockets***NEW!
17. Asyncio And Servers
18. Direct To The Hardware
Extract:
Using Hardware Registers

<ASIN:187196282X>