ESP8266 und ESP32 als I2C-Slave in MicroPython - AZ-Delivery

This post is also available as PDF document for download.

What to do if a number of sensors in the basement should provide data to the apartment on the 2nd floor, but they do not allow the floor ceiling to use a radio network? In this case I was lucky because I came back to the mind of a four -wire cable of an old intercom. What could be more obvious than to establish a data line and possibly power supply to the unit in the basement.

So the electrical resistance of the line is measured for a flight, for the first and back line 1.5 ohms. If a current consumption of a maximum of 100mA of the slave unit in the basement, this means a voltage drop of 0.15V. Of the 5V that the plug -in power supply provides, the ESP8266 still remain 4.85V. Great, that could work.

But which transmission protocol can I use? RS232 on the ESP8266 is not possible because there is no second serial interface available. So i2c - but no - the ESP8266 cannot play a I2C slave with what Micropython brings in the kernel, the ESP32, by the way. In addition, 20m cable length and I2C are probably not compatible. Search for an I2C slave module for micropython on the Internet-none! Well, it doesn't work - there is no, so I write such a module myself. I know the protocol and the whole in the master Translating into a signal sequence is anchored in the Kernel both with the ESP32 and the ESP8266. The slave is about palpating this signal sequence and returning data to the master. And it worked. However, Micropython unfortunately does not create a clock rate of 100kHz, but just up to 500Hz. This is due to the work speed of the ESP8266 below Micropython, but also due to the cable length. But what the hell, no Brockhaus volumes are transmitted, but only short data snippets. A clock rate of 500Hz brings about 50 bytes per second. This is enough to transfer a few commands or measured values.

You can find out how the project was implemented in this blog sequence from the series

Micropython on the ESP32 and ESP8266

today

ESP8266 and ESP32 as I2C-slave

With both controller families you have to think about the usable GPIO pins. Some of them are integrated into the boot process and therefore cannot be used without restrictions. The two tables provide information about this.

Label

GPIO

Input

output

Notes

D0

GPIO16

No IRQ

No PWM or I2C support

High at Boot Wake Up from Deep Sleep

D1

Gpio5

OK

OK

SCL at I2C use

D2

GPIO4

OK

OK

SDA for I2C use

D3

GPIO0

Pulled up

OK

Flash button when low

Normal boot when high

D4

Gpio2

Pulled up

OK

Must be connected to the on-board LED when booting high, low-activated LED

D5

GPIO14

OK

OK

SPI (SCLK)

D6

Gpio12

OK

OK

Spi (miso)

D7

GPIO13

OK

OK

Spi (Mosi)

D8

GPIO15

Pulled to GND

OK

Spi (CS) must be low when booting

RX

GPIO3

OK

Replica

Must be high when booting

Tx

Gpio1

TX PIN

Replica

Must be his debug exit when booting high when booting

A0

ADC0

Analog input

X


Table 1: Pin occupancy and system functions in the ESP8266

GPIO

Input

output

Notes

0

Pulled up

OK

Must be high when booting

1

TX PIN

Replica


2

Pulled up

OK

Must be high when booting

3

RX PIN

Replica

Must be high when booting

4

OK

OK


5

OK

OK


6

X

X

Spi flash

7

X

X

Spi flash

8

X

X

Spi flash

9

X

X

Spi flash

10

X

X

Spi flash

11

X

X

Spi flash

12

OK

OK

Must not be high when booting

13

OK

OK


14

OK

OK


15

OK

OK


16

OK

OK


17

OK

OK


18

OK

OK


19

OK

OK


21

OK

OK


22

OK

OK


23

OK

OK


25

OK

OK


26

OK

OK


27

OK

OK


32

OK

OK


33

OK

OK


34

OK


Only input

35

OK


Only input

36

OK


Only input (VP)

39

OK


Only input (VN)

Table 2: Pin occupancy and system functions in the ESP32

Since the SDA and SCL line must be drawn with a resistance of 10kΩ to +VCC (sweater), you cannot use a GPIO PIN that must not be high during normal booting. With the ESP8266 the D8 = GPIO15 and the ESP32 is GPIO12.

If the slave is also to hand in an I2C master in its own sensor environment, it makes sense to use the usual GPIOs. With the ESP8266 the D1 (GPIO5 = SCL) and D2 (GPIO4 = SDA) and the ESP32 GPIO22 = SCL and GPIO21 = SDA are.

In this project I will use a DHT20 together with a relay on the slave, an ESP8266. The main focus of this article is to set up a controller as a I2C slave. Which work of the slave has to do can vary from case to case. Therefore, the circuit and the selection of the sensor modules and actuators only have an example character to demonstrate the interaction of the various components. In addition to the relay, the slave also serves a DHT20, an LDR (Light dependent resistor) and a reed contact. Reed contacts are switches that are closed by a outer magnetic field. My module is connected according to the print on the board "-" to GND, the medium contact at +3.3V and "S" is due to D4 = GPIO2.

In the LDR module, "-" on +3.3V, the middle contact to GND and "S" on A0. In this way, I get higher converter values ​​when I gave a greater brightness. The change in the module is possible because the LDR has no polarity as a resistance, in contrast to photo diode or photo transistor.

Let's start with the hardware list.

Hardware

Which controller is used as a master or slave is a free choice. Of course, two modules are used, a master and a slave. I have an ESP8266-Amica And used for the master an ESP8266 D1 mini, they were just at hand. In addition to the transmission via the 20m cable with 100Hz, the slave also uses the system's own i2c bus with 100kHz for chat with the DHT20. Unlike DHT11 and DHT22, which work with a single data line via their own protocol, the DHT20 uses the I2C bus. Because he has no jumper to change the device address (0x38), only one of these building blocks can be operated on the bus. When working through the data sheet and during the programming of a corresponding micropython module, it turned out that the component has great similarities with the AHT10. In addition to the same device address, handling is also identical.

2

D1 Mini Nodemcu with ESP8266-12F WLAN module or

D1 Mini V3 Nodemcu with ESP8266-12F or

Nodemcu Lua Amica Module V2 ESP8266 ESP-12F WiFi or

Nodemcu Lua Lolin V3 Module ESP8266 ESP-12F WIFI or

ESP32 Dev Kit C unpleasant or

ESP32 Dev Kit C V4 unplacerated or

ESP32 NODEMCU Module WiFi Development Board with CP2102 or

Nodemcu-ESP-32S kit or

ESP32 Lolin Lolin32 WiFi Bluetooth Dev Kit

1

Photo resistance Photo Resistor

1

Ky-021 Magnet switch mini magnet reed module sensor

1

DHT20 Digital temperature sensor and humidity sensor

1

MB-102 Breadboard Pug with 830 contacts

various

Jumper Wire cable 3 x 40 pcs. 20 cm M2M / F2M / F2F each possibly too

65stk. Jumper wire cable plug -in bridges for Breadboard

optional

Logic Analyzer

The circuits

Two circuits are required for the project and a four -pin cable from the master to the slave. I tested 4 x 0.6mm² with 20m sounding line. A supply of the slave via this cable is possible if the master is supplied with a 5V connector power supply. The company runs autonomously without a PC when the operating programs, as explained above main.py are uploaded to the controller.

Figure 1: entire circuit

Figure 1: entire circuit

The left part in Figure 1 represents the slave, the right part of the master. Both parts can also be transferred to ESP32 units with the appropriate adaptation of the GPIO pins if more than the free connections should be used. An ESP32 can always be what the ESP8266 can do.

A note on the relay is important. If you use a module with a low-triggered relay, please note that

  • the modules usually have to be supplied with 5V and
  • the inputs via pull-up resistors to 5V can be raised.

This endangers the GPIOs of an ESP32/ESP8266. Therefore, the levels must be adjusted with a transistor level as in Figure 2 from 3.3V to 5V. As I have determined, this affects both magnetic relay modules and Solid-State modules as in Figure 2.

Figure 2: circuit for a low-triggered relay

Figure 2: circuit for a low-triggered relay

In the following explanations, the construction of the circuits is assumed. Incidentally, the Solid-State modules can only switch alternating current lines due to the component, electromagnetic relays can switch on the same and alternating current.

The software

For flashing and the programming of the ESP32:

Thonny or

µpycraft

Used firmware for the ESP32:

V1.19.1 (2022-06-18).

Used firmware for the ESP8266:

V1.19.1 (2022-06-18).

The micropython programs for the project:

dht20.py Driver module for the DHT20

slave.py Demo program for the micropython slave

I2SLAVE.PY Slave driver

master.py Demo program for the Micropython Master

timeout.py Non-blocking software timer

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.

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. I have the process for Thonny 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.

Signals on the I2C bus

Whenever there are problems with data transmission, I like to use the DSO (digital memory oscilloscope), or a smaller tool, which is cheaper, one of the world, one Logic analyzer (La) with 8 channels. The thing is connected to the USB bus and shows by means of a Free softwarewhat is going on on the bus pipes. Where the shape of impulses does not matter, but only on its time sequence, a LA is worth gold. And, while the DSO only provides snapshots of the curve, you can feel the LA over a long time and then zoom yourself into the interesting places. You can find a description of the device in the blog post "Logic Analyzer Part 1: Make I2C signals visible"by Bernd Albrecht. There is also described

How to scan the I2C bus. On the tortured label of the tool in Figure 3, you can see that the Logic Analyzer is often in use.

Figure 3: Logic Analyzer

Figure 3: Logic Analyzer

I will introduce you to the transfer of the hardware address (HWADR) to the DHT20, followed by a data byte. To do this, I put PIN 1 of the Logic Analyzer on the SCL line and the PIN 2 of the Logic Analyzer to the SDA line of the I2C bus in the slave circuit. I connect to GND.

Now I start Thonny, create a new file in the editor and enter the following text. Save the program under any name in your working directory.

 From machine import Pin code,Softi2c
 import sys
 
 IF sys.platform == "ESP8266":
     I2C=Softi2c(scl=Pin code(5),sda=Pin code(4))
 elif sys.platform == "ESP32":
     I2C=Softi2c(scl=Pin code(22),sda=Pin code(21))
 Else:
     raise Runtime("Unknown Port")

The variable sys.platform Tell us which controller type we use. An I2C object is instantiated on this, which we will use for a first test. Is the ESP8266 and its circuit ready and the controller connected to the PC? Then we start the program with the F5 button. This can be done faster than with the mouse to drive and click the green start button with the white triangle. If the program runs through without an error message, everything is ok. In the terminal we now enter the first I2C command, want to see who is there. I format input, the answers from the system italics.

 >>> I2C.scan()
 [56]

The square brackets in Micropython have a so -called list it contains the 7-bit hardware addresses of the I2C modules found as elements. 56 = 0x38 The number is responded to to which the DHT20 reacts. The bus is ready and is waiting for the communication of the ESP8266 with the connected parties.

In order for the bush drum to work, there must be one that indicates the clock, this is the ESP8266, it is the boss and that is called in the local system of the slave at I2C Master. The DHT20 is a slave locally, a slave. - Ohhh! I thought the age of slavery was long gone! - be it, the master indicates the slave with which he wants to parlize. He also creates one Start Condition As a kind of "respect for all" message. Then he puts the hardware address on the bus to which he as LSB (Least Significant Bit) attaches a 0 if he wants to send data (write) or 1 if he expects the slave an answer (read). The hardware address follows the data byte to be sent in the event of writing access. Finally comes as "ok, that's it", one Stop Condition. We take a look at what the signal sequence looks like.

I am now entering the following command in the terminal area of ​​Thonny, but don't send it off yet. The Logic Analyzer is connected as described above.

 >>> I2C.writeto(0x38,B "\ XBA")

Next I start the program Logic 2. In the menu on the right side of the window I click on Analyzers And then on the plus sign. I choose from the list I2C. If this operating mode has never been used, you have to indicate in the next window which analyzer line is on which bus line.

Now everything is prepared, we start the analyzer with the button R. The recording begins and we quickly switch to Thonny and press the Enter key to send the command. Then back to Logic 2 and with R Stop the recording.

Figure 4: Logic 2 - Analyzers - I2C

Figure 4: Logic 2 - Analyzers - I2C

To see the entire signal sequence, I put the mouse pointer on one of the signal tracks and turn the mouse wheel to me. At some point a vertical line appears in the recording tracks. Then I put the mouse pointer on this line and turn the mouse wheel away from me. The line is getting wider and wider until I can see the signal pulses. I have marked the most important places with time brands.

Figure 5: Soft reset of the DHT20

Figure 5: Soft reset of the DHT20

The brand 0 marks the Start Condition, Sda goes to low while SCL is high. Then the master waits about 15µs so that the slaves come from the springs to receive a hardware address. We have given 0x38 = 0b00111000. The ESP8266 turns it to the Bits 0B011100 and attaches a 0 to the LSB because it wants to send the DHT20 a byte, which results in 0x70 = 0B01110000. Then he puts the address byte on the bus. With every rising flank (e.g. brand 1 and 2) of his clock signal on SCL, he tells the slaves that they should now remember the condition of the SDA line. In Figure 5 you can read that the master sent the byte 0x70 = 0B01110000. With the Acknowledge-Bit (ACK), the slave on the ninth rising flank signals whether he received the byte from the master and recognized as his address. In this case, like the DHT20 here, he pulls the SDA line to 0. Any other slaves on the bus pull their sleeping caps over the ears and whip because they are not meant. In the same way, the Master will send the byte 0xBA next, which the DHT20 acknowledges again with a field. Then the slave releases the SDA line again. The master does not create a new clock pulse, SCL remains at 1. If the master now also releases the SDA line, released lines (switched to input) go through the pull-up resistors on high levels, this is the Stop Condition. The I2C routines of the Micropython module work according to this scheme DHT20.py and of course that of our module I2SLAVE.PY.

The module I2SLAVE.PY

In certain places, the controller must sleep briefly to give the periphery time to do the measuring job. For that we import sleep of the module machine.

 From time import sleep
 
 class Dht20_error(Exception):
     passport
 
 class Calibration(Dht20_error):
     def __init__(self):
         Excellent().__init__("Calibration failed")

From the class Exposure Let's lead the class Dht20_error from which Calibration inherits. One Calibration-Sexception we throw if the Calibration of the DHT20 should fail.

The class DHT20 inherit from Dht20_error. We define the device address of the DHT20 and declare the three commandobytes as constants. The status flags Busy and Calibrated can be found in the status byte, which is the first byte after a trigger command.

 class DHT20(Dht20_error):
     DHT_HWADR=const(0x38)
     # Commands
     cmdcalibrate=const(0xe1)
     Cmdtrigger=const(0xac)
     CMDReset=const(0xba)
     #Flags
     Busy=const(0x80)
     Calibrated=const(0x08)

The constructor of class DHT20, the routine __init __ () Take over an I2C bus object and wait for 100ms until the DHT20 building block has set up. I2C writing and reading instructions work exclusively with data structures that support the buffer protocol. This is what bytearays and bytes objects do. To receive the raw measurement values, we therefore declare a bytearar with six bytes to storage space. The attributes tempo and humble are declared and the reference to the i2C object we show the instance attribute I2C to. Then we call the method calibrate() to initialize the DHT20.

     def __init__(self,I2C):
         sleep(0.1)
         self.data=bytearar(6)
         self.tempo=None
         self.humble=None
         self.I2C=I2C
         self.calibrate()

The method writeregs() takes a command-byte and a bytearar dat with other data bytes. Both are merged into a nameless bytearar and sent to the DHT20.

     def writeregs(self, CMD, dat):
         self.I2C.writeto(DHT_HWADR,bytearar([CMD])+dat)

With reset() we send a soft reset command to the DHT20.

     def reset(self):
         self.I2C.writeto(DHT_HWADR,bytearar([CMDReset]))

Before the DHT20 can deliver measured values, its presence must be determined and a calibration must be carried out according to the data sheet. That makes the method calibrate().

     def calibrate(self):
         data=bytearar((0x08,0x00))
         self.writeregs(cmdcalibrate,data)
         status=self.read status()
         while status & Busy:
             sleep(0.01)
             status=self.read status()
         return Bool(status & Calibrated)

In addition to the command cmdcalibrate = 0xe1 the bytes 0x08 and 0x00 are to be transmitted. We convert the tupel (0x08.0x00) into a bytearar and send it over the bus together with the command-byte. Then we get the status byte from the DHT20 and repeat it until the chip reports by resetting the Bits 7 enforcement.

If now the bit 3 (Calibrated= 0x08 = 0b00001000), this delivers Fierce of the traffic jam with Calibrated the value 0b00001000, which we convert into a Boolian value and as a True hand back.

The method read status() reads a byte from the bus that Status byte And there is back.

     def read status(self):
         return self.I2C.readfom(DHT_HWADR,1)[0]

For a measurement, the trigger command for the DHT20 must be sent together with the bytes 0x33 and 0x00, which makes radrawdata(). According to a maximum of 20ms, the data is ready and we can pick up the status byte and the five temperature and moisture bytes. For further processing, we convert the received bytes object into a bytearar and show this to the instance attribute data to.

     def radrawdata(self):
         data=bytearar((0x33,0x00))
         self.writeregs(Cmdtrigger,data)
         sleep(0.02)
         self.data = bytearar(self.I2C.readfom(DHT_HWADR, 6))

The method temperature() Calculates the temperature in degrees Celsius. To do this, the raw data is picked up from the DHT20. The cells 3.4 and 5 of data contain the temperature raw value. In fact, only the lower 4 bits of data[3], the low-nibble, to attribute the temperature. But they form the four high -quality bits of the intermediate value. Because 16 bit positions are below, I insulate the low-nibble from data[3] through Fierce With 0x0f and push the bits 16 places to the left. The next 8 bits delivers data[4], I push them to the left by 8 positions and ornaments that with the previous value. The lowest 8 bits can then be data[5] to be filled by Oder.

The following presentation may better convey the process.

Let's assume, data[3: 6] Does the shape (0b ???? XXXX, 0Bbyyyyyyy, 0Bzzzzzzzzz), then the following happens, where?, X, Y and Z represent bit positions.

     @property
     def temperature(self):
         self.radrawdata()
         self.tempo = ((self.data[3] & 0xf) << 16) |\
                      (self.data[4] << 8) | \
                      self.data[5]
         self.tempo = ((self.tempo * 200.0) / (1 << 20)) - 50
         return self.tempo

The result in tempo is now multiplied by 200, divided by 2 high 20 according to the data sheet and relieved by 50. We return the Celsius temperature.

You are amazed by the Decorator @property? This line makes it possible to handle the return value like a reference to a variable. Instead of a method call like

DHT20.Temperatures ()

we can write now

DHT20.Temperatures

And use this expression in formulas. You can find out more about this type of syntactic sugar in Micropython here.

Works similarly humidity (), only here are the upper four bits of data[3] The lowest quality nibble of the raw value. Pushing the bits there by 4 positions to the right. data[1] and data[2] Jumps by 12 or 4 positions by pushing left. Again, a 20-digit binary number is created, which now only multiply by 100 and by constant 220 It is to be divided to obtain the value of the relative humidity.

     @property
     def humidity(self):
         self.radrawdata()
         self.humble = ((self.data[1] << 12) | \
                          (self.data[2] << 4) | \
                          (self.data[3] >> 4))
         self.humble = (self.humble * 100 ) / (1<<20)
         return self.humble

The module i2Slave

It starts with the import of the PIN class and some functions.

 From machine import Pin code, Softi2c
 From time import sleep,Sleep_us
 From time-out import *

With the class declaration we set the device address of our I2C slaves, 0x63.

 class I2CSLAVE:
 
     Hwad=const(0B1100011) # 0x63

The constructor takes the numbers of the pins for SCL and SDA, as well as the clock frequency and the hardware address.

     def __init__(self,scl=18,sda=19,freq=100,hwad=Hwad):
         self.clock=Pin code(scl,Pin code.IN)
         self.data=Pin code(sda,Pin code.IN)
         self.freq=freq
         self.Hwad=hwad
         self.Pulse=intimately(1000000/freq/2) #us
         self.Stop=False
         print("Slave Started @ {} hz".format(freq))

We create the PIN objects, remember the frequency and the hardware address in the appropriate instance attributes in order to be able to access it in every method of the class. We calculate the duration of a clock pulse in microseconds from the frequency.

The method submissive() makes it possible for us to change or access the device address retrospectively.

     def submissive(self,ADR=None):
         IF ADR is None:
             return self.Hwad
         Else:
             self.Hwad=(ADR & 0xff)

The same applies to the clock frequency.

     def frequency(self,freq=None):
         IF freq is None:
             return self.freq
         Else:
             self.freq=freq
             self.Pulse=intimately(1000000/freq/2)

setidle() puts the bus lines into the high -resistant state by programming as entrances. The two pull-up resistors pull the level to 3.3V = high.

     def setidle(self):
         self.clock(Pin code.IN)
         self.data(Pin code.IN)

The signal levels on the bus lines are scanned via loops that recognize a level change.

     def Waitdatalow(self):
         while self.data.value()==1:
             passport
         
     def Waitclocklow(self):
         while self.clock.value()==1:
             passport
 
     def Waitclockhigh(self):
         while self.clock.value()==0:
             passport

Each transmission is initiated with a start condition. SCL and SDA are both high. First goes to low, a little later SCL. Then the data bits are transferred.

     def awaitstart(self):
         while self.clock.value()==1:
             IF self.data.value()==0:
                 while self.clock.value()==1:
                     passport

The Stop Condition is the end of the transmission. To do this, the SDA line must be on low, then we are waiting for a high for SCL. If the SDA line went to high after a time duration, a stop condition was recognized. We report this back with the condition of the SDA input.

     def awaitstop(self):
         self.Waitdatalow()
         self.Waitclockhigh()
         Sleep_us(self.Pulse*2)
         return self.data.value()==1

In order to read a byte, we first set the byte value to 0 and Stop on false. In the for-loop, we are waiting for an increasing flank of the clock signal in eight rounds. We or we have received the received bit to the previous byte value after we have postponed it by a position to the left. The new round begins when the level has fallen on low.

     def reap(self):
         byte=0
         self.Stop=False
         for I in range(8):
             self.Waitclockhigh()
             byte = ((byte )<<1 ) | self.data.value()
             self.Waitclocklow()
         return byte

writebyte() takes a byte value. We are waiting for a falling clock, it is the sign of putting a bit on SDA. Of course, the PIN must first be switched as an output. With the value in mask Let's mask one bit after the other, starting with the MSB (Most Significant bit). Demoning with mask Provides a byte value that corresponds to the bit position or 0. The former is seen by Micropython as a true, the 0 as a false. Accordingly, we put the SDA line on high or low. Now the master has to put the clock line on high and then on low again. Once all bits are transferred, we switch the data line again as an entrance.

     def writebyte(self,byte):
         self.Waitclocklow()
         self.data.init(Pin code.OUT)
         mask=0x80
         for I in range(0,8):
             bit=byte & mask
             IF bit:
                 self.data.value(1)
             Else:
                 self.data.value(0)
             mask=mask >> 1
             self.Waitclockhigh()
             self.Waitclocklow()
         self.data.init(Pin code.IN)

The Acknowledge Bit, which is attached to the eight data bits as a ninth bit, serves to validate the data bytes. A low on the data line signals a high a high (not acknowledge). This bit is transmitted according to the same scheme or queried as the data bits.

     def transmitter(self,field):
         self.Waitclocklow()
         self.data.init(Pin code.OUT,value=field) # Access Data
         self.Waitclockhigh()
         self.Waitclocklow()
         self.data.init(Pin code.IN)# Release Data
         
     def awaitack(self):
         self.Waitclocklow()
         self.Waitclockhigh()
         field=self.data.value()
         self.Waitclocklow()
         return field

The I2C master

Figure 6: I2C master

Figure 6: I2C master

The master uses the transmission of the in-house Micropython I2C module. The module is at import struct With the method unpack worth mentioning. We unzip the transferred bytes object and convert it back into a byte, integer or fluid combination value. More on this later.

An I2C object is generated on the standard pins, the frequency of which we set to a safe 100 Hz. The device address of our slave will be 0x63.

 import sys
 From machine import Pin code, Softi2c
 # From Oled Import OLED
 From time import sleep
 From time-out import *
 From struct import unpack, pack
 
 I2C=Softi2c(Pin code(5),Pin code(4),freq=100,time-out=10000)
 hwad=0x63

The slave will allow us six actions: measure lighting, temperature and rel. Deliver air humidity, query reed contact and switch the relay on and off.

 readlight = const(0x01)
 Readtemp  = const(0x02)
 readhum   = const(0x04)
 read contact=const(0x08)
 Relay  = const(0x10)
 Relay = const(0x20)

As a result, a bytes object is associated with this, which can have different many places. The light value is an integer with two bytes, the values ​​of the DHT20 are floating comma numbers with 4 bytes and the remaining values ​​are individual byte values. The assignment regulates this Dictionary command.

Depending on the number of jobs, a format ring is required for decoding. "B" stands for a byte, "H" for an signless integer with two bytes, and "f" marks a floating point number with four bytes. The connection represents the dictionary P here.

 P={1:"B",
    2:"H",
    4:"F"
    }

The program reports its operational readiness and goes to the main loop, the main loop.

The input loop awaits a number value for the selection of the order. The string is converted into a number and the variable choice assigned.

     choice=intimately(input("1, 2, 4, 8, 16, 32 -> "))

To intercept errors, we install the rest into a TRY-Except structure. We first check whether the input in command as a key. Is that the case then we expose the key and the associated value command to a Tupel together. We send the commandobyte to the slave and wait 300ms for it to come up with the result. The number of bytes to be received is in the second field of the tupel. We give them I2C.Readfrom() continue and receive a bytes object of this length. Out of P Let's get the corresponding format ring with the unpack() transforms the bytes object into the numerical value. We let the value in Replica show.

The EXCEPT block reported to us without breaking off the program.

     try:
         IF choice in command.keys():
             command=(choice,command[choice])
             I2C.writeto(hwad,bytearar([command[0]]))
             sleep(0.3)
             code=(I2C.readfom(hwad,command[1]))
             S=P[command[1]]
             value=unpack(S,code)[0]
             print(value)
     except Exception AS E:
         print(E)

The i2c slave

Figure 7: I2C-slave

Figure 7: I2C-slave

The slave needs the classes Softi2c For the DHT20, also Pin code and ADC, Furthermore the classes DHT20 and of course I2CSLAVE. Serves to pack the numerical values pack.

 From machine import Softi2c, Pin code, ADC
 From DHT20 import DHT20
 From I2CSLAVE import I2CSLAVE
 From struct import pack

A micropython I2C object for access to the DHT20 is created on the standard pins.

 I2C=Softi2c(Pin code(5),Pin code(4),freq=100000)

The constructor of class I2CSLAVE provides the connection to the Master to the Pins D6 = GPIO12 and D7 = GPIO13.

 hwad=0x63
 slave=I2CSLAVE(scl=12,sda=13,freq=100,hwad=hwad)

We declare a PIN object for the relay output, which we put on low.

 relay=Pin code(0,Pin code.OUT,value=0)
 relay state=0

With the in-house I2C object, we instance the DHT20 object.

 DHT20=DHT20(I2C)

We read the door contact via GPIO2. The LDR level is picked up to A0.

 Contact=Pin code(2,Pin code.IN)
 LDR=ADC(0)

Command register and Val are declared, as well as the constants for the commandobytes.

 CMDReg=None
 Val=None
 # Commands
 readlight = const(0x01)
 Readtemp  = const(0x02)
 readhum   = const(0x04)
 read contact=const(0x08)
 relay  = const(0x10)
 relay = const(0x20)

Some functions get the values ​​from the sensors and, according to the number format, give them back packed. Here you can see the difference between a function call (LDR.Read(), Contact.Value()) and the queries of a property (DHT20.Temperatures, dht20.humidity).

 def scent():
     return pack("H",(1024-LDR.read())) # Standardize a byte
 
 def gettem temp():
     return pack("F",DHT20.temperature)
 
 def gefhum():
     return pack("F",DHT20.humidity)
 
 def gettuer contact():
     return pack("B",Contact.value())

The function relay() takes the switching status 0 or 1, which is passed on to the output. The query of the starting buffer delivers the current state as a control.

 def relay(Val):
     relay.value(Val)
     return pack("B",relay.value())

Our Micropython I2C-Slave interface shows the main loop. The two bus lines go to the entrance and we are waiting for a start condition.

 while 1:
     slave.setidle()
     slave.awaitstart()

If this was recognized, we first read the hardware address and send ACK, i.e. sda to low.

     HWA=slave.reap()
     slave.transmitter(0)

We areolating the R/-W-Bit from the byte and restoring the 7-bit device address. The values ​​in Replpl are issued for control.

     RW=HWA & 0x01 # Isolating direction bit
     HWA=HWA>>1 # Form 7-based address
     print("Hwadr:",hex(HWA),RW)

We are meant when the received device address matches the defined one.

     IF HWA==slave.Hwad: 

Now it is about whether a command was received or whether data has to be sent. We recognize a command by the fact that RW = 0 is.

         IF RW==0: # Receive, decoding, executing

In this case, the command-byte must be read, then we send ACK.

             CMD=slave.reap() # Read commandobytes
             slave.transmitter(0)

If one of the commandobytes is recognized, we trigger the corresponding action. For the time being, we hide the return value in the variable Val. If no command is recognized, nothing happens at all - passport.

             IF CMD==readlight:
                 Val=fetching()
             elif CMD==Readtemp:
                 Val=gettem temp()
             elif CMD==readhum:
                 Val=gefhum()
             elif CMD==read contact:
                 Val=gettuer contact()
             elif CMD==relay:
                 Val=relay(1)
             elif CMD==relay:
                 Val=relay(0)
             Else:
                 passport

Was RW = 1, then the bytes must be in Val be sent. Then we wait for a field from the master.

         Else: # Send data
             for I in range(len(Val)):
                 slave.writebyte(Val[I])
                 field=slave.awaitack()

Regardless of the mode, we wait for a stop condition from the master after the transfer and then put the lines at the entrance.

Class I2CSLAVE does not include all features of the Micropython's own interface. For example, the specification of a timeout is missing if a long time is waited for a stop condition. In this case, the lines would have to be released after the waiting time exceeded.

Figure 8: Overall structure

Figure 8: Overall structure

First of all, it is not intended to receive further data bytes with the command, such as the DHT20 with the calibration or trigger command. However, further tax structures would be necessary, which would lead to an additional slowdown of the transfer. Maybe I will process this field at some point. At the moment I am happy about the well-functioning data connection to my I2C slave.

Esp-32Esp-8266Projekte für anfängerSensoren

1 commentaire

Michael D.

Michael D.

Klasse Artikel, vielen Dank!
Zum schnellen Test, ob der Nachbau zum Erfolg führen kann, wäre es schön ein abgespecktes I2CSlave.py zu haben, welches Dummywerte an den Matster zurückspielt. Dann könnte man zwei ESPs nehmen, diese mit den zwei “I2C”-Leitungen verbinden und sehen, ob es spielt…und sich dann für den eigenen Nachbau entscheiden, meißt ja mit unterschiedlicher Sensorik. Wäre net, wenn das noch verfügbar wäre. Danke und VG Michael

Laisser un commentaire

Tous les commentaires sont modérés avant d'être publiés

Articles de blog recommandés

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery