A nice large display can't be everything. After all, you don't buy a car just to put it in the garage so that it doesn't feel alone. So in this episode I will present you some nice application examples of the Mammut matrix displays from part 1. Because of the few components, this project, as well as the Matrix display itself, is well suited for the beginners. I start today with a small weather station made of only two parts, not counting the display, which was already discussed in detail in the previous episode, together wit the MicroPython module matrix8x8.py
In this episode you will also learn how a CRC test sum is calculated and how a CRC test works. But, before we get started, welcome to
MicroPython on the ESP32 and ESP8266
The Mammut matrix display and the HTU21
The HTU21 is a dwarf of 3mm x 3mm, which is located on a platinum with 10mm x 13mm, which bears the name GY-21. On the underside of the circuit board, I discover a voltage controller and diverse "chicken food". The building block can therefore be supplied with voltages of 5V, but works with 3.3V. This is very good, because this lies the level on the I2C bus lines with which the part is controlled, in the safe area for the ESP32. He already has the task of heading for the display and now chatting the additional job with the HTU21 alias SHT21.
Illustration 1: HTU21 alias GY-21
The topics of these conversations will be the temperature and the relative humidity. The result of the conversations should then land on the matrix display. The latter should already consist of at least 8 elements for this purpose.
Illustration 2: Humidity indicator
With regard to the controllers, ESP32 and ESP8266, I was interested to what extent both families are suitable for the tasks mentioned. With one exception, both have a SPI bus and an I2C bus and have therefore proven to be easy to use. The exception is the small ESP8266-01. Only GPIO0 and GPIO2 are led with him. So you could flang an HTU21, which we will do in the next episode.
Plywood strips 5x 25cm ...
When Iining up matrix units, individually or in groups, a plywood strip of 5cm wide and the length of the entire display has proven itself. I fixed the boards with Eternit sealing mass. The plasticine holds where it should, but can also be completely removed.
The switching sketches for the ESP32 and the ESP8266 are very manageable.
Illustration 3: SHT21-Thermo+Hyrdometer with ESP32
Illustration 4: SHT21-Thermo+Hyrdometer with ESP8266
An Amica Node-MCU fits on a mini-breakboard. The same applies to an ESP32S that has a narrower footprint than, for example, an ESP32 Dev Kit C V3
Illustration 5: ESP32S with GY-21
Illustration 6: Amica Node-MCU from the ESP8266 family
For flashing and the programming of the ESP32:
Used Firmware for the ESP8266/ESP32:
Please choose a stable version
The MicroPython programs for the project:
MicroPython - Language - Modules and Programs
For the installation of Thonny you will find here a detailed instructions (English version). In it there is also a description of how to burn the Micropython firmware (state 05.02.2022) on the ESP chip.
MicroPython is programming language. The main difference to the Arduino IDE, where you always and only flash entire programs, is that you only need to flash the MicroPython Firmware once at the beginning on the ESP32, so that the controller understands MicroPython instructions. You can use Thonny, µPyCraft or esptool.py for this purpose. For Thonny I have described the process here.
As soon as the Firmware has flashed, you can easily talk to your controller in a dialogue, test individual commands and see the answer immediately without having to compile and transfer an entire program beforehand. That is exactly what bothers me on the Arduino IDE. You simply save an enormous time if you can check simple tests of the syntax and hardware up to trying out and refining functions and entire program parts via the command line before knitting a program from it. For this purpose, I always like to create small test programs. As a kind of macro, they summarize recurring commands. Whole applications then develop from such program fragments.
If the program is to start autonomously by switching on the controller, copy the program text into a newly created blanks file. Save this file under boot.py in Workspace and upload it to the ESP chip. The program starts automatically the next time the reset or switching on.
Manually, programs are started from the current editor window in the Thonny IDE via the F5 key. This is faster than clicking on the start button, or via the menu Run. Only the modules used in the program must be in the flash of the ESP32.
In the meantime, Arduino IDE again?
If you want to use the controller together with the Arduino IDE again later, just flash the program in the usual way. However, the ESP32/ESP8266 has then forgotten that it ever spoke MicroPython. Conversely, any Espressif chip that contains a compiled program from the Arduino IDE or the AT firmware or LUA or ... can easily be provided with the MicroPython firmware. The process is always as here described.
Signals on the I2C bus
In the past episodes on the subject of I2C bus or SPI bus, I sometimes recorded and mapped the signals on the bus lines with the DSO (AKA digital memory oscilloscope). I recently received inquiries from readers who had problems with the I2C bus. The occasion was a defective cable in one case, in the other case it was bad contacts. The errors were quickly limited by a few MicroPython commands. Other mistakes are more stubborn.
During the preparation of this article I had a HTU21 in use, which could be localized on the I2C bus without problems. But it strictly refused to read out its register contents. Changes of my driver software were unsuccessful. In such cases I then use the DSO, or a much cheaper, small tool, a Logic analyzer (LA) with 8 channels. The thing is connected to the USB bus and shows by means of a free software what is going on the bus lines. Where the form of pulses is not important, but only their timing, an LA is extremely valuable. And, while the DSO only provides snapshots of the curve, with the LA you can sample over a longer period of time and then zoom in on the interesting parts. By the way, you can find a description of the device in the blogpost Logic Analyzer - Part 1: Make I2C signals visible by Bernd Albrecht. There it is also described how to scan the I2C bus.
Well, after a few hours of data sheet studies and unsuccessful troubleshooting I also used the program. If I had only done that much earlier ... in short, the situation was clear. This is how the curve shapes in the defective HTU21 look:
Illustration 7: Addressing of the HTU21 with NACK response
And this is what it should have looked like. Do you see the difference in the first frame? The hardware address is already acknowledged with a NACK instead of an ACK bit
Illustration 8: Order for a temperature scan
And so the reading command should have looked like, which did not happen due to the error in the chip.
Illustration 9: Coll up the temperature raw value 91ms later
Instead, the faulty module is again a NACK (Not Acknowledge), which is caused by the fact that the slave, here the HTU21, does not pull the SDA line from the controller during the ninth clock pulse. Fatally, in a relaxed episode, a field came in the expected position, but mostly not. You are looking for a wolf. The problem was solved with the exchange of the building block.
Illustration 10: Reading command on the HTU21 with NACK
The SHT21 class
After this short excursion to troubleshooting, we now build the SHT21 class. Compared to the MATRIX class, it is much narrower. Again, the SHT21 data sheet is the source of all wisdom.
I import the class I2CBus and the function sleep_ms(). In I2CBus I have declared a set of methods that unify the parameter passing to the methods of the Softi2c class. The conversion from the number formats byte and integer to the type bytearray is done there automatically.
My class SHT21 inherits from the class I2CBus. Thus all methods of I2CBus are available in the namespace of SHT21 without Prefix. It is not to be expected that an identifier in SHT21 could overwrite a namesake in I2CBus.
In the section 5.3 Singing A Command on page 7 of the data sheet I find the hardware address of the SHT21.
After sending the Start Condition, the subsequent I2C
header consists of the 7-bit I2C Device Address ‘1000,000’
and an SDA direction bit (Read R: '1 ’, Write w:‘ 0 ’).
Like most I2C devices, the hardware address (Device Address) is a 7-bit value here. 0B1000000 = 0x40 = 64. By adding the directional bits R/-W at the LSB position, it becomes the 8-bit address that we see on the plots in Figures 7 to 10. The SHT21 gropes the SDA line when the flank rises to SCL. Eight rising flanks for 8 bits, the ninth rising flank reads the ACK (0) or Nack-Bit (1) on the SDA line.
On page 8 in the data sheet, at the top left, the table follows the register of the SHT21. I transfer them to programlisting accordingly.
Figure 11: Register or command table
The constructor needs the I2C object created in the main program and optionally takes a hardware address. Its value is preassigned by default by the constant HWADR and also immediately transferred to an instance variable self.hwadr so that it is available to all methods of the class.
The next command super().__init__(i2c,self.hwadr) instantiates the class I2CBus. It corresponds to the otherwise necessary constructor call I2CBus(i2c,0x40).
The following are the declarations for the waiting times after sending a transducer command for temperature and relative humidity until the values are read in. I take these from the columns RH max and T max from table 7 on page 9 in the data sheet.
Illustration 12: Converter waiting times
There are four combinations for the resolution of the temperature and humidity measurement. Bit 7 and bit 0 in the user register are responsible for this. These combis are in the resFlags tuple. resText specifies the combis in plain text and resolution holds the current resolution setting. The info for this is in Table 8 in the data sheet, page 9.
Illustration 13: User register and converter resolution
Then I reset the SHT21 with my routine sht21Reset() and create the global variables for temperature and humidity. The edition of the constructor follows. Then we are waiting for the reset command. Section 5.5 on page 9 informs about this.
The soft reset takes less than 15ms.
The method for the chip reset consists of a write command to the SHT21. We access the register SoftReset = 0xFE writing as specified in the data sheet in section 5.5 on page 9.
Illustration 14: Reset command
The conversion of a temperature value and the reading in is done by the method
And that's what's behind it - data sheet page 9:
Illustration 15: Process of a conversation
I send the command to start a temperature measurement, TriggerTnoHold = 0xF3. noHold refers to the behavior of the SCL line during the measurement and says that SCL is not held at GND level by the SHT21. So the bus remains free during the measurement time.
Either I ask again and again whether it is already so far, or I simply wait the time given by Sensirion. I have chosen the second solution and send the ESP to sleep for a few milliseconds. How long that is depends on the current resolution setting in resolution. After that three bytes are fetched from the bus, which the SHT has to send. If the checksum, the third byte, is 0, I compose the raw value of the temperature from the first two bytes. data contains the MSB. I shift the bits 8 places to the left, which corresponds to a multiplication by 256 and oriere with the LSB in data, from which I delete the two least significant bits. This is written on page 8 in the penultimate paragraph:
For the calculation of physical value status bits must be set to '0' - See Chapter 6.
If there is a transmission error, I set TRaw to 0
This is followed by the calculation of the true temperature, as specified on page 10 of the data sheet. I also make the result round in two places after the comma. To do this, I multiply the calculated value by 100, add up 0.5, so that 5 is correctly rounded up and make a total of 100 parts out of the sum.
For the relative humidity, the processes are the same, only the formula for calculating the final value is different.
The procedure for the calculation of the checksum (crc = cyclic redundancy check) I have described exactly in Crc8 calculation.pdf with an example. Here it is applied for three bytes in a row. Because the crc byte transmitted by the SHT21 is integrated in the check, the result must be 0x00. data gives the byte array and nob the number of bytes to be tested (number of bytes) to the method. With this test method the remainder of a polynomial division is determined by continued subtraction. Each occurring power in the test polynomial and in the remainder corresponds to a 1.
Remember me to carry out a test of this method if everything is remaining in the box.
To be able to change the resolution, I must have read and write access to the user register. This is done by the two relevant methods readUser() and writeUser(). A bytes object is read from the bus and returned as an integer by readUser().
The method resolve() accesses the two mainzelmännchen. The data sheet explains on page 9 in section 5.6 that the reserved bits 3, 4 and 5 must not be changed. Therefore, I read the user register , clear bits 7 and 0, and ORing them with the pattern fetched by the index res from the tuple resFlags. If the res parameter is not specified, then resolve() queries the current resolution and returns the index.
The method tellResolution() goes one step further. It returns the index and the plaintext message from the resText tuple.
The user register can also provide information about the battery status. This may be helpful if the power supply does not come from a power supply unit. If the voltage drops below 2,25V, bit 6 goes to 1. The query is done by the method tellBattStatus(). I get the register content and rapid BatteryMask=0x40 and move the bit in position 6 to position 0. This gives me an index 0 or 1 that I can use as a pointer into the tuple ("OK", "BAD").
Good, everything in the box, then you have now either entered the program text of the module sht21.py itself or you have already downloaded it before or just now. If not, then you should do so now at the latest. Because, wasn't there something else you should remind me of? Exactly, the test of calcChecksum(). But it doesn't work that quickly.
First you have to upload the module sht21.py into the flash of the ESP. Surely you have already built the circuit? Then you need a test program that does the extensive preliminary work so that you don't have to enter the many lines by hand each time. For an ESP32 the program is called sht21_32_test.py and for an ESP8266 it is sht21_8266_test.py. The difference is only in the pins for the I2C bus.
Open the program in an editor window and start it with F5. If you have done everything right, you will get the first message from the HTU21.
Now we test the crc function. Assume that the HTU wants to send bytes 37 and 142 for the temperature. Then it must calculate a checksum. It does that just like the calcChecksum() method. We create a byte array with these values because we also get such a data type from HTU21. We pass this to the calcChecksum() method. s is the HTU21 or SHT21 object. 206 is the checksum of the two bytes.
The SHT21 hangs the 206 on the other bytes on it and sends it to us. We take the bytolge and chase them through the redundancy check. Isn't that a great magic? In fact, 0 comes out, so no mistake in transmission.
The climate program with a large display
Maybe you wonder why I knitted a module with a SHT21 class. Wouldn't it have been enough to simply incorporate all the methods into a corresponding program as functions? There are at least two good reasons in favor of the class solution.
- Modules and classes are universally reusable.
- Modules and classes detoxify a program and make it easier to read, clearer and easier to maintain.
- Both Matrix and SHT21 will reappear as classes in two other episodes in this series. The import is done with a line. Otherwise I would have to copy a variety of program lines.
- And what if I want to change a function and used the package in 20 other programs? Then I would have to change the function in 20 programs. As a method in one class, I do this exactly once and the change is transparently adopted in 20 programs.
Now you not only have two, but four good reasons and there are more!
Well now let's put everything together into one program, for ESP32 and ESP8266 and the matrix display and the HTU21 sensor: SHTDISPLAY.py
The program has 84 lines. With all imports, this adds up to over 500 lines. Another good reason for modules and classes, right?
The most interesting line in this listing is
This provides us with the type of the used controller. With this info it is easy to preassign the GPIO pins for SPI and I2C bus correctly before instantiating the corresponding objects with it. If the further program does not use any type-specific commands, you can use it for multiple controllers without having to make any adjustments to it.
One flaw is the size of this section. But maybe I have infected you with the class virus and you tinker until the next time a module with a class, whereby one can outsource this story. Have fun trying, researching and with the homework.