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 Humidity

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)
read = bytearray(3)
while True:
    sleep_ms(1)
    try:
        i2c0.readfrom_into(0x40,read, True)
        break
    except:
        continue
msb = read[0]
lsb = read[1]
check = read[2]
print("msb lsb checksum =", msb, lsb, check)
data16 = (msb << 8) | (lsb & 0xFC)
hum = -6 + (125.0 * data16) / 65536
print("Humidity ", hum)

esppython180

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: 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) 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) read=bytearray(3) while True: try: i2c0.readfrom_into(0x40,read, True) break except: continue msb = read[0] lsb = read[1] check = read[2] 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

By Harry Fairhead & Mike James

esppython360

Buy from Amazon.

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
  7. Advanced Input – Interrupts
  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:
    Analog Input
  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
  17. Asyncio And Servers
     Extract:
    Asyncio ***NEW!
  18. Direct To The Hardware
     Extract:
    Using Hardware Registers 

<ASIN:187196282X>

espbook

 

Comments




or email your comment to: comments@i-programmer.info

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner



Last Updated ( Tuesday, 11 July 2023 )