In the first post On the subject of AZ-Eoneboard, we had taken care of the breakout board with the BH1750 and created access to the GPIOs not used by the system. Today it is about programming a module for access to the SHT30 board, with which temperature and relative humidity can be recorded. The basis for this is that Sensirion data sheet. The chip can capture temperatures from 0 ° C to 65 ° C and, like the BH1750, is controlled via commands. However, these are 16-bit words here, represented by two bytes. We will build our own exception class for the SHT30, program a CRC test routine and use the knowledge about decorators again. There is also a tip for the easy testing of modules. You can find out more from the series in this new episode
Micropython on the ESP32, ESP8266 and Raspberry Pi Pico
today
The AZ-Onoboard and the SHT30
The plug strip of the breakout board with the SHT30 has the same pin sequence as the BH1750 and can therefore also be infected to any of the three socket strips.
Figure 1: AZ onboard with two satellites
The hardware
The usual board with the controller, here an ESP8266, is replaced by the AZ onboard. At the hardware list from the first episode Nothing has changed. The SHT30 board is already included in the kit.
Figure 2: Connection of the SHT30 board
The software
For flashes and the programming of the controller:
Thonny or
Signal tracking:
Used firmware for the ESP8266:
The micropython programs for the project:
timeout.py: Non-blocking software timer
oled.py: OLED-API
SSD1306.PY: OLED hardware driver
bh1750.py: Hardware driver module
bh1750_test.py: Demo program
bh1750_kal.py: Program for calibrating the Lux values
sht30.py: Hardware driver module
sht30_test.py: Demo program
Micropython - Language - Modules and Programs
To install Thonny you will find one here Detailed instructions (English version). There is also a description of how that Micropython firmware (As of 18.06.2022) on the ESP chip burned becomes. Like you that Raspberry Pi Pico get ready for use, you will find here.
Micropython is an interpreter language. The main difference to the Arduino IDE, where you always flash entire programs, is that you only have to flash the Micropython firmware once on the ESP32 so that the controller understands micropython instructions. You can use Thonny, µpycraft or ESPTOOL.PY. For Thonny I have the process here described.
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 transmit 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 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.
Autostart
If the program is to start autonomously by switching on the controller, copy the program text into a newly created blank tile. 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.
Test programs
Programs from the current editor window in the Thonny-IDE are started manually via the F5 button. This can be done faster than the mouse click 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 between, Arduino id again?
Should you later use the controller together with the Arduino IDE, just flash the program in the usual way. However, the ESP32/ESP8266 then forgot that it has ever spoken Micropython. Conversely, any espressif chip that contains a compiled program from the Arduino IDE or AT-Firmware or Lua or ... can be easily provided with the micropython firmware. The process is always like here described.
The preparation of the AZ onboard
The board comes with a finished programmed ESP8266. The program demonstrates the possibilities of the three sensors. Figure 3 shows the edition of the program with the terminal program Putty.
Figure 3: Edition with the demosoftware
But we want to write and let our own program in Micropython. The first step to the goal is that we burn a corresponding Micropython core on the ESP8266 that overwrites the demo program. So first load them Firmware down and then follow of these instructions. The ESP8266 then reports in the Terminal of Thonny:
Micropython V1.23.0 on 2024-06-02; ESP modules (1m) with ESP8266
Type "Help ()" for more information.
>>>
The programming of the module
The AZ-Eoneboard is now infected: the SHT30 board, the BH1750 board and the OLED display. We start again with the manual review of the devices. Inputs in Replica Become in this script fat formatted, the answers from the ESP8266 italic.
XXXXXXXXXX
>>> From machine import Pin,Softi2c
>>> I2C=Softi2c(Pin(5),Pin(4),freq=100000)
>>> I2C.scan()
[35, 60, 68]
We know the 35 = 0x23 from the last episode as a device address of the BH1750. 60 = 0x3c comes from the OLED display. So 68 = 0x44 The hardware address of the SHT30 and all three peripheral devices work so far, so we start.
Import a few things.
XXXXXXXXXX
From machine import Pin
From time import Sleep_ms
From sys import exit
A separate exception class
Micropython has the base class Exception with a large number of subclasses that cover certain areas for error protection. Today we want to create our own exceptional class for the treatment of errors that specifically affect the SHT30. It should Shterror Name and must inherit exception.
XXXXXXXXXX
class Shterror(Exception):
The following types of errors may occur. We assign constants to the identifiers.
XXXXXXXXXX
Shttempcrcerror=const(1)
Shthumcrcerror=const(2)
Shtboard=const(3)
Shtstatuscrcerror=const(4)
As with every class, we need a constructor that is handed over one of the constants when calling. We show the value of the instance attribute code To and initialize the top class Exception, which we have a reference to the method message() hand over. This will take care of the error messages.
XXXXXXXXXX
def __init__(self, code=None):
self.code=code
super().__init__(self.message())
def message(self):
IF self.code == Temporary:
return "CRC error temperature"
elif self.code == Humcrcerror:
return "CRC error rel. Moist"
elif self.code == Boarder:
return "No SHT3X board found"
Else:
return "Non -identified errors"
Now let's start the program in the Thonny - F5 editor window.
XXXXXXXXXX
>>> %run -C $Editor_content
With the following line we trigger an error message, which reports the exact position and the type of error.
XXXXXXXXXX
>>> raise Shterror(0x01)
Traceback (custody recent call load):
File "" , line 1, in <modules>
File "" , line 13, in __init__
File "" , line 16, in message
Name: name 'Tempcrcerror' isn't defined
Next we go to the end of the program. Through the following trick we can easily test the module, which we will create immediately, without having to upload the file to the ESP8266 after each change. This saves a lot of time and circumstances. So we complement the program with the following lines.
XXXXXXXXXX
IF __name__ == "__Main__":
From machine import Softi2c
I2C=Softi2c(Pin(5),Pin(4),freq=100000)
# SH = SHT3X (I2C)
If the module is launched as the main program in the editor window, Micropython shows the attribute name the string "Main" to. If a class is literally imported, then gets name assigned the name of the class. If we save our previous program and upload to the ESP8266, we can convince ourselves with the following inputs.
XXXXXXXXXX
>>> From SHT30 import Shterror
>>> Shterror.__name__
'Shterror'
With the F5 function key, the module in the editor window is started as the main program.
XXXXXXXXXX
>>> __name__
'__Main__'
We therefore import the class Softi2c And instantiate an I2C bus object as we would do in a main program. As soon as the first methods of our SHT3X class exist, we can compensate the lowest line and thus have direct access to the routines after printing on F5.
The SHT3X class
In the BH1750 we defined the commands by constants. In principle, this would also be possible with the help of 16-bit words. Nevertheless, I chose a different way. As we know from the previous contribution, only Bytes objects or bytearars can be transmitted via the I2C bus because these data types support the buffer protocol. The commands for the SHT30 are basically composed of two bytes. So why not use Bytes objects for determining? This also saves the conversion. The definition block looks like this.
XXXXXXXXXX
class SHT3X():
Shthwadr = const(0x44) # ADR -> GND
SHTPoly = const(0x131) # x^8 + X^5 + X^4 + 1 = 100110001
Shtcmd_break = B '\ X30 \ X93'
Shtcmd_softrst = B '\ X30 \ XA2'
Shtcmd_heater_enable = B '\ X30 \ X6D'
Shtcmd_heater_disable = B '\ X30 \ X66'
Shtcmd_read_status = B '\ XF3 \ X2D'
Shtcmd_clear_status = B '\ X30 \ X41'
Shtcmd_art = B '\ X2B \ X32'
Shtcmd_read_val = B '\ XE0 \ X00'
Shtcmd_heater_on = B '\ X30 \ X6D'
Shtcmd_heater_off = B '\ X30 \ X66'
Shtcmd_oneshot = B '\ X2C \ X24'
Shtoneshot = [B '\ X06 \ X0D \ X10',B '\ x00 \ x0b \ x16']
Shtcmd_periodic = B '\ x20 \ x21 \ x22 \ x23 \ x27'
Shtperiodic = [B '\ X32 \ X24 \ X2F',
B '\ X30 \ X26 \ X2D',
B '\ x36 \ x20 \ x2b',
B '\ X34 \ X22 \ X29',
B '\ X37 \ X21 \ X2A']
Follow a few more definitions.
XXXXXXXXXX
stretch=const(0)
nostretch=const(1)
mtime = [12,5,2]
r_high =const(0)
R_Med = const(1)
r_low = const(2)
Querydelay=(2000,1000,5000,250,100) #ms
So we are with init() Even with the constructor of the SHT3X class. An I2C bus object is handed over to the call.
XXXXXXXXXX
def __init__(self, I2C, hwad=Shthwadr):
self.I2C=I2C
self.hwad=hwad
self.name="Sht30"
self.reps=0
self.Rawtemp=B '\ x00 \ x00'
self.rawhum=B '\ x00 \ x00'
self.tempo=0.0
self.humble=0.0
IF self.hwad in I2C.scan():
print(self.name,"Initialized")
Else:
raise Shterror(Shtboard)
self.clear status()
self.status=0
Sleep_ms(2)
After determining some instance attributes, we check whether the Hardware address of the SHT30 is located in the list scan() returns. If that is not the case, we throw one Shtboarder Roror Exception. The calling program can react with Try - Except. If it doesn't, the program is terminated. Then we reset the status register in the SHT30 and define the attribute status.
The method broadcommand() we hand over the command code, which means I2C.Writeto() is sent via the I2C bus according to the hardware address.
XXXXXXXXXX
def broadcommand(self,CMD):
self.I2C.writeto(self.hwad,CMD)
Now we can do the method clear status() define that on broadcommand() picks up.
XXXXXXXXXX
def clear status(self):
self.broadcommand(SHT3X.Shtcmd_clear_status)
Attentive readers find here that the command Shtcmd_clear_status is referenced together with the class identifier. It wasn't the case with the BH1750. The reason is that the commands here are defined as Bytes objects and not as constants. Constants are stored directly in the program code, this is not possible with variables. If you were to define all command definitions as constants of bytes objects, then could SHT3X Stay away. Some types of basic types are accepted as constants, for example, for example, integer, floating point numbers, bytes objects and string literals.
Now we can escape the last line in the program.
XXXXXXXXXX
sh=SHT3X(I2C)
Next, a routine is to be created that triggered a single shot and then reads the measured values. The table in Figure 4 shows us the possibilities. Repeatibility translates Uncle Google as repeat accuracy. On page 2 in the small print, it says that this is the triple value of the standard deviation of several measurements. This is probably achieved by multiple samps of the measurements from which an average is then calculated. This would also explain the different measurement times of the individual modes that are between 4.5ms (low) and 15.5ms (high)
Figure 4: Singleshot Modi
Clockstretching enabled In the I2C bus protocol, means that a peripheral device can pull the clock line to 0 when it is busy and cannot send any data. The situation is similar in the SHT30. If the converter process was triggered after a received command and a reading request is sent from the ESP8266 immediately afterwards, the SHT30 responds with an ACKNOWLEDGE-BIT (ACK), but the clock line places 0, until the process has ended and measurement data is available. Then he releases the clock line and sends the data bytes with the reinstalling clock from the ESP8266. The blocks on the left in Figure 5 correspond to the sending of the command and a reading request, which the SHT30 cannot serve because there is no data yet. The change takes about 12ms. The data is then transferred. Figure 6 and 7 show the details.
Figure 5: Overview of Clock Stretching
Figure 6: Clock-Stretching-Command 0x2C06 and reading request
According to the reading request, clockstretching begins at the red dot. For approx. 11.6 ms, the SHT30 holds the CLK line to 0.
Figure 7: Send data after time out
Then the ESP8266 can take over the clock line again and read the six data bytes.
If clockstretching is not enabled, the SHT30 answers a reading request with a not Acknowledge-bit (nack). Our program must act this. It is best to wait for the reading request to be sent until the SHT30 should be ready according to the data sheet.
The routine must now be from the specifications by the parameters stretching and rep Assemble the command word, send, wait if stretching disabled is and then read the data.
The arguments passed are checked for compliance with the value range. Then we use it as an indices in the bytes object Sht_oneshot and in the list of low-bytes that determine the repeat accuracy. We send the peaked out values as bytearar to the SHT30.
XXXXXXXXXX
def getoneshot(self, stretching=0, rep=0):
assert stretching in [stretch, nostretch]
assert rep in [r_high, R_Med, r_low]
buf=bytearar(2)
buf[0]=SHT3X.Shtcmd_oneshot[stretching]
buf[1]=SHT3X.Shtoneshot[stretching][rep]
self.broadcommand(buf)
If the stretching is deactivated, we have to keep a break ourselves, the duration of which depends on the repeatability value and from the list mtime with rep as an index can be fetched. If stretching is activated, the ESP8266 takes care of the correct processing even in the hidden.
But why do we pick up six bytes from the SHT30, temperature and moisture are only 16-bit values? Now, according to the two bytes for the measured value, a byte is hung for a test amount in order to be able to check the correctness of the values.
XXXXXXXXXX
IF stretching==nostretch:
Sleep_ms(SHT3X.mtime[rep])
echo=self.I2C.readfom(self.hwad,6)
The routine takes over this cyclic redundancy check or short CRC crctest(), which we hand over the two data bytes and the test sum in a slice of the received bytes object and to which we come to. She reports True back if no error was found, otherwise False. In this case, we throw the corresponding exception.
XXXXXXXXXX
IF need self.crctest(echo[:3]): # the first three bytes
raise Shterror(Shterror.Shttempcrcerror)
IF need self.crctest(echo[3:6]): # the last three
raise Shterror(Shterror.Shthumcrcerror)
self.Rawtemp=echo[:2]
self.rawhum=echo[3:5]
return [echo[:2],echo[3:5]]
The raw values are shipped into instance attributes and as list returned.
For the CRC we need three things: the data, the cheek and a so -called polynomial. The SHT30 is 0x131. The value of 0x31 specified in the manual is wrong, as can be seen on the polynomial given next to it! Each potency of X corresponds to a 1 in the bit position that the exponent specifies. You can find a description of the test process here, together with an example.
Shtpoly = Const (0x131) # x^8 + X^5 + X^4 + 1 = 0B100110001
XXXXXXXXXX
def crctest(self, blobb):
start=0xff
for h in blobb[:-1]:
start ^= h
for I in range(8):
IF start & 0x80:
start = (start << 1) ^ Poly
Else:
start <<= 1
return blobb[-1] == start
Through slicing and indexing, we separate the bytes object into the two data bytes and the test sum byte. Blobb [:-1] delivers Byte 0 and byte 1 and Blobb [-1] delivers the test sum. Start program - F5.
XXXXXXXXXX
>>> blobb=B '\ x8e \ xd9 \ x43'
>>> blobb[:-1]
B '\ X8E \ XD9'
>>> blobb[-1]
67 (= 0x43)
>>> sh.crctest(blobb)
True
For the continuous recording of measured values, the toast of the series of measurements and reading the values in separate routines are realized. There is no clock stretching, but you can set different measuring intervals. The waiting times are in that Tupel Querydelay accommodated to which a main program can access.
As with the single shot, the command word is composed of two bytes according to our wishes. MPS Specifies the number of measurements per second.
Figure 8: These are the permanent runners
StartPeriodic() corresponds to the first part of getoneshot(). MPS and rep are indices in the respective lists.
XXXXXXXXXX
def StartPeriodic(self, MPS=0, rep=0):
assert MPS in range(5)
assert rep in range(3)
buf=bytearar(2)
buf[0]=SHT3X.Shtcmd_periodic[MPS]
buf[1]=SHT3X.Shtperiodic[MPS][rep]
self.broadcommand(buf)
The reading of values takes over readperiodic(), which is similar to the second part of getoneshot() is built. If the series measurement is triggered, all you need readperiodic() call.
XXXXXXXXXX
def readperiodic(self):
self.broadcommand(SHT3X.Shtcmd_read_val)
echo=self.I2C.readfom(self.hwad,6)
IF need self.crctest(echo[:3]):
raise Shterror(Shterror.Shttempcrcerror)
IF need self.crctest(echo[3:6]):
raise Shterror(Shterror.Shthumcrcerror)
self.Rawtemp=echo[:2]
self.rawhum=echo[3:5]
return [echo[:2],echo[3:5]]
We will start the program again - F5
XXXXXXXXXX
>>> sh.StartPeriodic(2,0)
>>> sh.readperiodic()
[B'Ky ', B '\ x92 \ xfc']
>>> sh.readperiodic()
[B'Kd ', B '\ X92 \ XAA']
>>> sh.readperiodic()
[B'Kn ', B '\ x92 \ xD2']
All of the commands that are still missing have fixed bytes objects that do not have to be put together and therefore they also have the same simple structure.
Scan processes can be included sendbrake() cancel.
XXXXXXXXXX
def sendbrake(self):
self.broadcommand(SHT3X.Shtcmd_break)
silket set() reset the sensor and causes the new shop of calibration data.
XXXXXXXXXX
def silket set(self):
self.broadcommand(SHT3X.Shtcmd_softrst)
The chip contains a heat source that can be used to check the function. Called without an argument, delivers hater() The status is given by Bit13 of the status register. We areolating the bit and sliding it to position 0. With the argument values 0 or 1 we switch off or in.
XXXXXXXXXX
def hater(self, status=None):
IF status is None:
S=self.read status()
return (S & 0x2000) >> 13
IF status:
self.broadcommand(SHT3X.Shtcmd_heater_enable)
Else:
self.broadcommand(SHT3X.Shtcmd_heater_disable)
read status() follows in the structure to request and read in measured values. A test sum is also included here.
XXXXXXXXXX
def read status(self):
self.broadcommand(SHT3X.Shtcmd_read_status)
Sleep_ms(15)
echo=self.I2C.readfom(self.hwad,3)
IF need self.crctest(echo[:3]):
raise Shterror(Shterror.Shtstatuscrcerror)
self.status=(echo[0]<<8) | echo[1]
return self.status
A number of status bits are available. Through the decorator @property Let's save the entry of brackets.
XXXXXXXXXX
def Resetdetected(self):
return (self.read status() & 0x0010) >> 4
def command status(self):
return (self.read status() & 0x0002) >> 1
def rhtracking status(self):
return (self.read status() & 0x0800) >> 11
def ttracking status(self):
return (self.read status() & 0x0400) >> 10
def check status(self):
return self.read status() & 0x0001
Program start - F5
XXXXXXXXXX
>>> sh.hater()
0
>>> sh.hater(1)
>>> sh.hater()
1
>>> sh.hater(0)
>>> sh.hater()
0
>>> sh.Resetdetected
0
>>> sh.command status
0
>>> sh.rhtracking status
0
Delete status register:
XXXXXXXXXX
def clear status(self):
self.broadcommand(SHT3X.Shtcmd_clear_status)
The methods getoneshot() and readperiodic() only deliver the raw values for temperature and relative air humidity. Calcvalues() calculates the correct measured values from this. The data sheet tells us the formulas for this. For the calculation, the raw values already read in the attributes is Rawtemp and rawhum back.
XXXXXXXXXX
def Calcvalues(self):
self.tempo=175.0 * ((self.Rawtemp[0]<<8) | self.Rawtemp[1]) / 0xffff - 45
self.humble= 100.0 * ((self.rawhum[0]<<8) | self.rawhum[1]) / 0xffff
return [self.tempo,self.humble]
The values are saved in instance attributes and also returned directly in the form of a list. tempo, humble and value enable the subsequent reading of the calculated values.
XXXXXXXXXX
def Tempo(self):
return self.tempo
def Humble(self):
return self.humble
def Value(self):
return [self.tempo,self.humble]
Program start - F5
XXXXXXXXXX
>>> sh.getoneshot()
[B'l#', B '\ x90>']
>>> sh.Calcvalues()
[28.9227, 56.3455]
>>> sh.Tempo
28.9227
>>> sh.Humble
56.3455
>>> sh.Value
[28.9227, 56.3455]
Sometimes you want to have separated the integer and the fracture share of the measured values. The method does that readvaluesdecimal(). She gets the values from the instance attributes and each produces a list for temperature and moisture.
XXXXXXXXXX
def readvaluesdecimal (self):
tempo=[intimately(self.tempo),intimately((self.tempo-intimately(self.tempo)+0.005)*100)]
humble=[intimately(self.humble),intimately((self.humble-intimately(self.humble)+0.005)*100)]
return [tempo,humble]
Program start - F5
XXXXXXXXXX
>>> sh.readvaluesdecimal()
[[28, 92], [56, 35]]
For the test of the module we have to sht30.py Save and upload to the ESP8266.
Test program
The program sht30_test.py Contains four routines that demonstrate the function of the module in the program context. The routines summarize, which we have already tried on Repl.
A fifth function includes the brightness measurement with the module BH1750. The file bh1750.py To do this, must be uploaded to the flash of the ESP8266. We have the program run in the Thonny editor window and can then call up each of the functions individually.
XXXXXXXXXX
From machine import Pin,Softi2c
From SHT30 import SHT3X,Shterror
From time import Sleep_ms
I2C=Softi2c(Pin(5),Pin(4),freq=100000)
SHT=SHT3X(I2C)
def trawvals(MPS):
SHT.StartPeriodic(MPS,0)
Sleep_ms(16)
print(SHT.readperiodic())
Sleep_ms(SHT3X.Querydelay[MPS])
print(SHT.readperiodic())
Sleep_ms(SHT3X.Querydelay[MPS])
print(SHT.readperiodic())
def ghettemphum():
read=False
while need read:
try:
SHT.getoneshot(SHT3X.nostretch,SHT3X.r_low)
tempo,humble=SHT.Calcvalues()
st=SHT.read status()
print(tempo,humble,"{: 16b}".format(st))
print(SHT.readvaluesdecimal())
read=True
except:
Sleep_ms(50)
def Getperiodic(MPS):
SHT.StartPeriodic(MPS,SHT3X.r_high)
Sleep_ms(16)
while 1:
SHT.readperiodic()
tempo,humble=SHT.Calcvalues()
st=SHT.read status()
print("{: 04.2f}, {: 04.2f} {: 016b}".format(tempo,humble,st))
Sleep_ms(SHT3X.Querydelay[MPS])
def thermohygrometer(MPS):
SHT.StartPeriodic(MPS,SHT3X.r_high)
Sleep_ms(16)
D.writer("Thermo-Hygro-01",1,0,False)
while 1:
D.clearft(0,1,15,2,False)
SHT.readperiodic()
tempo,humble=SHT.Calcvalues()
D.writer("Temp: {: 04.2f} *c".format(tempo),1,1,False)
D.writer("Hum: {: 04.2f} %".format(humble),1,2)
Sleep_ms(SHT3X.Querydelay[MPS])
def duotest(MPS):
From BH1750 import BH1750
bra=BH1750(I2C)
SHT.StartPeriodic(MPS,SHT3X.r_high)
Sleep_ms(16)
while 1:
D.clearall(False)
lumi=bra.luminance()
D.writer("Lumi: {} lux".format(lumi), 1,0,False)
SHT.readperiodic()
tempo,humble=SHT.Calcvalues()
D.writer("Temp: {: 04.2f} *c".format(tempo),1,1,False)
D.writer("Hum: {: 04.2f} %".format(humble),1,2)
Sleep_ms(SHT3X.Querydelay[MPS])
In the next episode, we initially complete the examination of the AZ outboard with a module for the SGP30 board. There are also interesting things to learn again.
So stay tuned!
2 Reacties
Andreas Wolter
@UKTechman: Yes it is. As workaround you could save the english version of the website as PDF in your webbrowser. That’s why we normally don’t make PDFs. The customers could do that in this easy way.
Best regards,
Andreas Wolter
AZ-Delivery Blog
UKTechman
The pdf seems to be in German only