Funkwecker mit ESP in MicroPython - Teil 1 - IR Empfänger - AZ-Delivery

This post is also available as PDF document.

As a preparation for a larger project, the ESP32 (ESP8266) must learn from IR remote control how a device can be addressed. To do this, he must internalize the dialect, the protocol, the RC (Remote Control). It requires an eye that can perceive the light impulses in the range of approx. 950NM, a spectral area on which the human visual organ does not appeal to.

So we have to present and measure what the RC sends. Then we put together the results in a file on the microcontroller. This allows him to pass commands that he receives from a smartphone, for example, to a device that "listens" to IR signals.

You can find out how this works in detail in the first post on this topic in the course of the series

Micropython on the ESP32 and ESP8266

today

The ESP32 learns RC5

First we look at the circuit and bring the components together. Then there is information on the data transfer from the RC broadcaster to the ESP32. We take a close look at the Manchester code, which is based on the transmission protocol, then discuss the program and the modules involved.

The circuit

The circuit for the first part of the project is ingeniously simple and therefore particularly suitable for beginners. It consists only of two modules, a transistor and two resistors. HoweverThe programming, on the other hand, is more demanding and reaches deep into the Micropython trick box a few times.

Everything is set up on a breakboard. I just had no small hand (30 contact series) and therefore used one with 62 contact series. It should turn out that this was a stroke of luck, because there were various components and modules piece by bit, so that the board was finally densely equipped. So that the controller also gets along with a board, I have for an ESP32 Lolin Decided, which is more narrow -breasted than his larger relatives. Thus, a contact series for jumper cables remains free on the two sides. For an ESP32 Dev Kit C V3 or ESP32 Dev Kit C V4, you need two Breadboards plugged in parallel to accommodate all pins.

Figure 1: RC receiver - very clear structure

Figure 1: RC receiver - very clear structure

The circuit diagram shows the wiring a little more precisely.

Figure 2: circuit with ESP32 Lolin

Figure 2: circuit with ESP32 Lolin

It looks like this with an ESP8266 D1 Mini.

Figure 3: circuit with ESP8266 D1 Mini

Figure 3: circuit with ESP8266 D1 Mini

In principle, even an ESP8266-01 would be enough because we only need a GPIO line as an entrance. If you want to use the present circuit only to read or test an RC, you can use an ESP8266. However, we want the controller to also send IR signals. But that is exactly what the ESP32 can do. The class is missing from the ESP8266 RMT From the module ESP32.

The transistor negates that at the output S the logical level of the recipient module. This measure was necessary because Peter Hinch's programs, many thanks to Peter, work with the levels of the station. The levels of the IR receiver module are negated in comparison. Instead of changing something on the program, I chose the hardware solution with the transistor.

We have already arrived at the hardware list.

Hardware

As you suspect, the partial list is very clear.

1

ESP32 Dev Kit C unpleasant

or ESP32 NODEMCU Module WiFi Development Board

or Nodemcu-ESP-32S kit

or Nodemcu Lua Amica Module V2

or ESP8266 ESP-01S WiFi WiFi module

or D1 Mini V3 Nodemcu with ESP8266-12F


1

Ky-022 Set IR receiver

1

Breadboard Kit - 3x Jumper Wire M2M/F2M/F2F + 3 Set MB102 Breadbord Compatible with Arduino and Raspberry Pi - 1x Set

various

Jumper Wire cable 3 x 40 pcs

1

NPN transistor BC337 or similar

1

Resistance 1.0 kΩ

1

Resistance 10 kΩ

Optional

Logic Analyzer

What the RC sends

The RC does not simply send out for 1 IR LED for and for 0 IR LED. That would be too prone to interference, because every heat source also sends IR light. Such IR emitter does not switch on and off with 36kHz, that can only be the RC. Depending on the manufacturer, this switching frequency can be between 36 kHz and 40 kHz. The recipient must or should at least work on the same frequency as the transmitter to ensure a higher reach and secure transmission.

Figure 4: The RC sends 36kHz

Figure 4: The RC sends 36kHz

A series of pulse (Burst) of 32 36 kHz pulses then stands for a logical 1 or 0, before or after the RC does not send. The breaks are as long as the 36 kHz bursts. A logical 1 begins with a break, the logical 0 ends with one. Figure 5 shows this for the bit sequence 1-1-0.

Figure 5: Bit sequence 1-1-0

Figure 5: Bit sequence 1-1-0

The transmission for a bit therefore takes 1778µs and because a package consists of 14 bits when transmission, the RC needs 24.89ms. This is followed by a break of approx. 89ms, which corresponds to 50 bit lengths. After this time, the bit sequence is repeated by my RC, even if I only press a button very briefly. This brings increased data security because the recipient only accepts the episode when the two packages are identical. A constant repetition takes place when I stay on the button.

How does the receiving device know whether I only pressed the button for a short or longer? Has a 1 have now been sent or 111? To answer this, we first take a closer look at the 14 bits transferred.

Figure 6: Meaning of the Bits

Figure 6: Meaning of the Bits

Each bit train basically begins with a 1st sequence in the RC5 protocol, a 5-bit long device address and a 7-bit long command word are encoded. The second bit was also originally a 1st to have more commands, later in the second bit the negated Bit 6 of the command word was encountered. With less than 64 commands it remains with a 1.

The third bit is the so-called Toggle bit. This is exactly what the recipient can see whether a button is held, then is T In every episode, as in Figure 7.

Figure 7: button 9 - fires twice with a 90ms break

Figure 7: button 9 - fires twice with a 90ms break

If the button is pressed twice in a row, the T-bit changes its level.

Figure 8: Key 9 - Repeated activity

Figure 8: Key 9 - Repeated activity

This is better to recognize if you put the two plots with each other. Incidentally, the pulse consequences are with one Logic Analyzer In connection with the free software Logic2 from Saleae recorded. How to work with this tool and where you get it from, the links reveal (Bernd Albrecht "Logic Analyzer - Part 1: Make I2C signals visible"). I had connected the channel 1 to the transistor collector here.

Figure 9: button 9 - Comparison of the pulse train

Figure 9: button 9 - Comparison of the pulse train

A 1-bit in the lower one becomes from a 0-bit in the upper train. The repeated episode is identical to the first. On the RC I pressed the 9 key, which also provides a 9 as a command word.

The Manchester code

How does the consequence of the logical bits come about now the pulse sequence with which the transmitter clocks the IR LED? As you can see in Figure 5, the flank change always takes place in the middle of the logical bits. The first half of the bit duration is the level on the negated bit value, in the second half the level corresponds to the actual bit value.

Figure 10: Manchester code egg

Figure 10: Manchester code egg

The frequency of the Bittransfer is 1/1778µS = 562 Hz. The episode is overlaid with a clock from the double frequency of 1124 Hz and links the two signals to an exor gate, then you get the pulse sequence in the Manchester code. While the level of this episode is on high, the 36kHz clock is switched on, nothing is sent in low. A level change of the user signal, the actual bit sequence (data), takes place exactly when a pulse or a break of the Mostly-Signals (Puls Train) has the length of 1778µs. If the pulse and break have the length of 889µs, the level of the user signal remains unchanged.

Because the first bit is always 1, the levels of the following bits are clearly determined. This can be used to win the user signal back from Manchester code. If the second flank follows after the start after 889µs, the following bit is also a 1 and so on. The fourth pulse includes bars 7 and 8. This means that a level change has to take place from A4 to A3, so A3 is low or 0. The following break is also long, so another level change has to take place, which makes A2 too high or 1. A following short pulse also leaves A1 on 1. Now you can certainly decipher the rest yourself.

If you are not sure whether your RC also opens properly, then simply test it with the cell phone camera, which can see infrared light.

Figure 11: IR proof

Figure 11: IR proof

The software

For flashing and the programming of the ESP32:

Thonny or

µpycraft

SaleaeLogic analyzer software (64 bit) For Windows 8, 10, 11

Used firmware for an ESP32:

Micropython firmware

 V1.19.1 (2022-06-18).

Used firmware for an ESP8266:

V1.19.1 (2022-06-18).

The micropython programs for the project:

Lern.py Main program

IR Micropython package By Peter Hinch and

What we need from it stripped down version

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 05.02.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.

The program and its somewhat cryptic environment

The package of Peter Hinch Contains the modules for various manufacturers and RC sub-types and is roughly divided into the recipient and transmitter. The packages are laced in such a way that after importing the program Test.py From the package IR_RX Test.py A user fo in Replica is output.

>>> From IR_RX.test import test

I have the package Revolved and made leaner for the purpose of this article, making it a little more manageable. Please copy the folder IR_RX After downloading and unpacking the ZIP archive into your work directory and from there into the flash memory of the controller.

Some information on the topic of micropython package makes it easier to understand the program process. A package or English Package is a directory in which several modules can live. Further sub -packages can also move into this flat share. A package draws its name from the name of the directory. A precise selection of the modules is possible through the point notation, as in the example above.

In the package it can be a file with the name __init__.py give. This file is always executed when the package or a module in it is imported. __init__.py Provides the initialization code, basic objects and/or basic classes that can be used by the other modules of the package. The declarations contained are integrated into the global name room by spelling the instruction, as is also the case with inheritance. Modules later imported can easily access it. The following replent line causes the execution of the __init__.pythat I expanded to include a print statement to prove the execution of the file.

>>> From IR_RX.philips import Rc5_ir
__init__.py became executed

The objects of Rc5_ir lie in the global name room.

>>> From machine import Pin code
>>> RC = Rc5_ir(Pin code(23),lambda _ : 0)
>>> RC
<Rc5_ir object at 3ffee3c0>

Reset!

But the import of the entire package also leads to processing the file __init__.py.

>>> import IR_RX
__init__.py became executed

Reset!

>>> import IR_RX.acquire
__init__.py became executed
>>> import IR_RX.philips
>>> From machine import Pin code
>>> RC=IR_RX.philips.Rc5_ir(Pin code(23),lambda _ : 0)

These lines show two things.

First: __init__.py is only carried out from the package when a module is imported for the first time.

Secondly: The class Rc5_ir is now in a separate name room that is achieved by the point notation.

This should meet information on packages and is sufficient to understand the way the program works, which we are now discussing. Finally, there is a graphic that shows the complex interaction of the components.

We have the main program Lern.py, the Philips-RC5-Komponente with the class Rc5_ir And the class IR_RX in the file __init__.py which serves as an interface to the hardware, i.e. the driver for the IR receiver.

We start with the main program and, as usual, with the import business.

From sys import platform, exit
From time import sleep
From GC import collect
From machine import Pin code, freq
From IR_RX.philips import Rc5_ir

With platform Finds the program to which family the controller belongs and the function exit() enables an orderly exit from the program. Then we get out of the module time the function sleep for breaks. Eliminated data waste Collect (). We need the PIN class for the entrance to GPIO23 and with the function freq() With an ESP8266, the clock from the standard value 80 MHz is increased to 160 MHz. The core of this project part is the import of the class Rc5_ir. From the information on packages we know that in addition to importing the class, the file __init__.py is started. The variant with From Insert the declarations that take place in the class into the global name room. The output of the actions shows the output in ReplaL after the start of Lern.py. We will come back to it when we discuss the other program parts.

After the start of Lern.py the following is output in Repl. The order of the actions is important for the interaction of the components.

>>> %run -C $Editor_content
__init__.py became executed
Rc5_ir became imported
constructor from Rc5_ir; decode <bound_method>
Constructor from IR_RX
IF platform == "ESP8266":
   freq(160000000)
   P = Pin code(13, Pin code.IN)
elif platform == "ESP32":
   P = Pin code(23, Pin code.IN)
Else:
   print("Unknown port")
   exit()

Now we determine the controller type and set up the corresponding GPIO PIN as the entrance.

keypinated=False
Data,AddDR,CTRL=0,0,0
rccode=[]

keypinated Is a flag with which the main loop experiences that a button was pressed on the RC. Data, AddDR and CTRL Later the values ​​for the corresponding part of the IR message. rccode is declared as an empty list and absorbs all the values ​​that were transmitted in a pulse train. This is necessary because the RC sends at least two packages for a button print, but we can only need one.

The function CB() is the callback function, which is ultimately called when a pulse train package is retracted. Here it is decided what to do with it. This can vary from application to application. That is why this function is also in the main program for which the user is responsible. If the code would be in a module, in addition to the main program, this module would also have to be changed and uploaded.

def CB(data, AddDR, CTRL):
   global keypinated,rccode
   IF data < 0:  # NEC Protocol Sends Repeat Codes.
       print("Repeat code.")
   Else:
       keypinated=True
       rccode.append((data, AddDR, CTRL))

If a valid data package was received, we set keypinated On the true and hang the command code, device address and the status of the Toggle-Bits as a tupel on the list rccode at. In this way it is ensured that all packages are removed from the receiving buffer and the first is secured.

Now is CB() defined and we can instance an RC5 object by the constructor of the GPIO object P And the callback routine CB hand over.

ir = Rc5_ir(P, CB)

Before we enter the Mainloop, we open the file commands.cfg to write.

F=open("commands.cfg","W")
while True:
   T=input("Key name on the RC ->")
   IF T=="Q":
       F.close()
       ir.close()
       exit()

The request to enter the name of a button on the RC appears in Repl. The cursor flashes. We save the input in T.

Was it a "Q", Then we exclude the file, switch off the interrupt sources and leave the program.

Otherwise we will receive the request to operate the button on the RC now. The While loop waits until it is happening. We treat the controller a short rest and leave the loop with break.

    Else:
       print(T,"Press the button on the RC briefly")
       while 1:
           IF keypinated:
               sleep(0.2)
               break
       Data,AddDR,CTRL=rccode[0]
       rccode=[]
       print(T,"->>",Data,AddDR,CTRL)
       line=T+","+Str(Data)+","+Str(AddDR)+","+Str(CTRL)+"\ n"
       F.write(line)
       keypinated=False
           
   GC.collect()

In the first list element, the tupel is now available with the command word, the device address and the Toggle-Bit status. This Tupel we unzip into the corresponding three variables and then remove the elements from the list rccode.

We can display the values, then we build the line for the output in the file and write them into the file.

Now ask for sure what is so mystical about the small program with 54 lines. Now that is due to the function CB(), rather, not in the function itself, but on the mechanism of your call, which goes around a few corners. In addition to the main program, we are dealing with two other files that give us two classes.

There is the class Rc5_ir in the module philipsthat we imported ourselves and which we have the method decode() gives. I would only like to have mentioned the name here so that you know where the identifier comes from. We will discuss the listing later.

We are now starting in secret in the back, with __init__.py. But actually the execution of this program is what happens first after the program starts, you can see yourself

>>> %run -C $Editor_content
__init__.py became executed
Rc5_ir became imported
constructor from Rc5_ir decode <bound_method>
Constructor from IR_RX

Already with the import of the class Rc5_ir Let's punch the file __init__.py and with that we put the program text of the class IR_RX ready. However, no instance is generated by this, rather inherited Rc5_ir from IR_RX And that means that their identifier Rc5_ir can be referenced as if they were in Rc5_ir were declared themselves. This is the ulterior motive to equip the philosophy of Peter Hintch, a special class for a special RC protocol with the basic field forest meadow properties that are needed for each RC application. Of course you could also use the basic properties in Rc5_ir, Rc6_ir, NEC8, NEC16 And insert other classes, then you would have to make changes in the base area but also in all of these classes. This is unnecessary work and a waste of time and creates redundancies. So it is enough if you only make the changes in IR_RX carried out, this applies to all special classes of RC types. In addition, the procedure keeps the memory requirement low because only what needs to be imported. This becomes even clearer if you study Peter's original files.

Rc5_ir So is specifically on that Decode tailored to an RC5 signal. The main task of the class IR_RX In contrast, the grasping is the timing Between the flanks of the signal, which from the IR reception module, negated via the transistor, to the input PIN GPIO23 got. You need these functions with every RC. The recipient diode sees the signal as the transmitter, i.e. the RC, sends away. The chip behind it filters out the 36 kHz carrier and gives a clean pulse sequence at its output S from, unfortunately in negated form. The transistor inverts the signal so that it is from the decoder in the class Rc5_ir is also understood.

Figure 12: From the transmitter to the ESP32 input

Figure 12: From the transmitter to the ESP32 input

__init__.py:

From machine import timer, Pin code
From array import array
From utime import Ticks_us

We need a hardware timer and a PIN object, so we import the corresponding classes from the module machine. The time intervals land in an array for 64-bit numbers. The time recording is dissolved in microse customers.

Then we declare the base class IR_RX And some class attributes.

class IR_RX():
   # Result/Error Codes
   # Repeat button code
   Repeat = -1
   # Error Codes
   Bathroom start = -2
   Bathing block = -3
   Bathing = -4
   Overrun = -5
   Baddata = -6
   Bathroom = -7

The declaration of the constructor follows __init__().

    def __init__(self, pin code, nedges, TBlock, callback, *args):
       # Optional Args for Callback
       self._pin code = pin code
       self._Nedges = nedges
       self._TBlock = TBlock
       self.callback = callback
       self.args = args
       self._errf = lambda _ : None
       self.verbosis = False

       self._Times = array('I', (0 for _ in range(nedges + 1)))  # +1 for overrun
       pin code.IRQ(handler = self._cb_pin, trigger = (Pin code.Irq_falling | Pin code.Irq_rising))
       self.edge = 0
       self.Tim = timer(0)
       self.CBCK = self.decode
       print("Constructor from IR_RX")

An RC5_ir object must know which PIN comes in the IR signal, how many flanks the signal has at the maximum, how many milliseconds of the Pulse Train takes and which function should be called when it is over. All this information is transferred to instance attributes. A function .rf () is predefined empty .bosis delivers expenses in replen True to assign.

The array . consists .Nedges +1 cells that can accommodate the signs ('i').

For the PIN object .pin code Will the IRQ handler .cb_pin() defined and sharpened for rising and falling flanks.

The index in the array . is .edge, he is initiated at 0. As a timer we use the hardware timer (0).

Finally we show .cbck The method Rc5_ir.deCode to. This works because at the time when we are the constructor of IR_RX () in Rc5_ir Call up the constructor of Rc5_ir The method decode() has already created and its designer is therefore known. We will be on the facts when discussing the class Rc5_ir come back again. You will understand the cryptic context if we are at the end of the meeting. The mysticism was created by Peter Hintch's philosophy that the decoder modules should be easily interchangeable and data must be transported between the different levels. Just trust me, everything will run perfectly in the end. After the start of Lern.py the following is output in Repl. You can see the order of the actions again

>>> %run -C $Editor_content
__init__.py became executed
Rc5_ir became imported
constructor from Rc5_ir; decode <bound_method>
Constructor from IR_RX
    def _cb_pin(self, line):
       T = Ticks_us()
       IF self.edge <= self._Nedges:  # Allow 1 extra pulse
           IF need self.edge:  # First Edge Received
               self.Tim.init(period=self._TBlock ,\
                     Fashion=timer.One_shot, callback=self.CBCK)
           self._Times[self.edge] = T
           self.edge += 1

This is the method _cb_pin(). It is the IRQ handler of the PIN object and is called when the level changes to GPIO23. As an IRQ handler, the routine must be kept as short as possible for various reasons. Therefore, only the times are recorded and the more extensive decoding is only carried out when the entire package has been read.

We remember the time in T and check whether the index .edge lies in the valid area. If .edge has the start value 0 is Need True, and we start the timer (0) for a unique shot. After the expiry of TBlock The function is called milliseconds, the reference of which we in .cbck have put off, .decode().

The current value moves to the array and we increase the index.

    def do_callback(self, CMD, AddDR, Ext, thirst=0):
       self.edge = 0
       IF CMD >= thirst:
           self.callback(CMD, AddDR, Ext, *self.args)
       Else:
           self._errf(CMD)

The method do_callback() lies in the call chain of the timer. Receives in the initialization . Callback The reference to the function CB () From the main program. This can do_callback() access the highest level from the lowest level.

If the timer has expired, the index must .edge to be set back to 0. If the command code from the IR signal is at least the value of thirst has, becomes CB() via . Callback() called, otherwise there is an error due to the function ._errf() can be reported if a function has been specified, which can be called up by the next method.

    def error_function(self, func):
       self._errf = func
    def close(self):
       self._pin code.IRQ(handler = None)
       self.Tim.deinit()

print ("__init__.py was executed")

Before the end of the program, the still active IRQ sources must be switched off because the ESP32 otherwise behaves strangely. Close() does that. Finally, we receive a message that __init__.py was executed.

Now only the class is missing Rc5_ir. The only function that she provides is determined from the flank distances in . The bit values ​​of the signal. Because the class of IR_RX inherits, has decode() Access to all in IR_RX Declared objects, especially on the instance attributes, i.e. also on the array ..

To calculate time differences, we import the function Ticks_Diff(). Then we get the basic class IR_RX on the boat.

From time import Ticks_Diff
From IR_RX import IR_RX

class Rc5_ir(IR_RX):
   def __init__(self, pin code, callback, *args):
       # Block Lasts <= 30ms and has <= 28 edges
       print("Constructor of RC5_ir; Decode", self.decode)
       Excellent().__init__(pin code, 28, 30, callback, *args)

When declaring the class Rc5_ir Let's state that we from IR_RX want to inherit. The constructor calls for the GPIO pin object and the callback function for the timer. A pulse block can take a maximum of 30 ms, in fact there are 25 ms. Up to 28 flanks occur in it.

The print command informs us that the constructor has been called up and that the function now decode() is known.

Only now calls Great () .__ Init__() the constructor of IR_RX and passes on the pin object, the number of flanks, the block duration and the callback function to it. Now White Ir_rx._cb_pin() at which connection must be obeyed and Ir_rx.do_callback() knows what function in the main program has an idea of ​​what should happen with the data received.

How ._cb_pin() is decode() An interrupt handler. Because several GPIO pins IRQ-It can be useful for the handler routine to find out which connection triggered the IRQ. That's why the line has

def _cb_pin(self, line):

The parameter line in the parameter list in which a reference to the Pinobject comes in. It looks analogous to the timers. Here, too, it is reported which timer has fired. However, because we are not interested in the number, the only emphasizes that the dummy variable is emphasized. Of course we could for the same reason line also replace it with the underlining. However, if no parameter is given, we get an error message.

    def decode(self, _ ):
       try:
           nedges = self.edge  # No. Of edges Detected
           IF need 14 <= nedges <= 28:
               raise Runtime\
              (self.Overrun IF nedges>28 Else self.Bathroom start)
           # Regenerate Bitstream
           bits = 1
           bit = 1
           V = 1  # 14 Bit Bitstream, MSB Always 1
           X = 0

The decoding runs in one Try - Except- construct. We shovel .edge after nedges around. The local variable nedges By the way, has nothing with the parameter nedges In the parameter list of the constructor IR_RX() to do.

If the actual number of flanks is between 14 and 28 including the borders, it is a valid package. Otherwise there is an error message.

Do you remember?

  • The first bit of a package is always a 1
  • In the case of long intervals, the following bit has the negated value of the current bit state.

bits Is the BIT counter, bit The current state, V The value of the IR word and X The index in the array ..

One BADBLOCK Exception can only occur with the ESP8266 here, according to Peter, because the IRQ has long delays between the appearance of the flank and the execution of the handler.

            while bits < 14:
               IF X > nedges - 2:
                   print('Bad Block 1 Edges', nedges, 'X', X)
                   raise Runtime(self.Bathing block)

We know that an interval between two flanks can be nominal 889µs or 1778µs. However, the times will deviate real, but they should not be shorter than 500µs in the RC5 protocol and not longer than 2100µs. X is at the moment 0 we calculate for Width The offset between the first and second flank and check whether the value is in the valid area.

                # Width is 889/1778 nominal
               Width = Ticks_Diff\
                      (self._Times[X + 1], self._Times[X])
               IF need 500 < Width < 2100:
                   self.verbosis and \
                   print('Bad Block 3 Width', Width, 'X', X)
                   raise Runtime(self.Bathing block)

The elegant integration of the debug message is interesting. You could of course also use an IF construction for this. How does this work?

>>> Width=400; X=1; verbosis=False
>>> verbosis and print('Bad Block 3 Width', Width, 'X', X)
False
>>> Width=400; X=1; verbosis=True
>>> verbosis and print('Bad Block 3 Width', Width, 'X', X)
bath block 3 Width 400 X 1

Micropython evaluates expressions from left to right. The evaluation of the link with and is canceled as soon as one of the participating partial expressions around the and around False becomes. The Value of this expression is returned. This behavior is also established in Lua. What is considered false in Micropython differs from that in Lua. Only the terms false and Nile are evaluated to false there. In Micropython, the following terms are evaluated as false: 0, false, none, empty lists [], dictionarys {} and tupel (()). Everything else will be True evaluated. The following examples speak for themselves.

>>> 0 and 4
0
>>> 3 and 5
5
>>> 7 and 0
0
>>> 1 and 4 and (4 < 7)
True

Is there True Not the BOOLE value of the overall expression, but only that of the last bracket.

>>> 1 and 4 and (4 > 7)
False
>>> 1 and None
>>> 1 and ""
''
>>> 1 and "" and 4
''
>>> r=[]
>>> 1 and r and 5
[]
>>>
>>> r={}
>>> 1 and r and 5
{}

It is important for understanding that with 1 and 7 there is not a Boolscher value, true or false, but just 7, which ultimately also in IF constructs as True is evaluated because 7 is not just 0.

>>> 1 and 7
7
>>> IF 1 and 7: print("Test")

test

Now you have met a piece of Micropython mysticism again, but back to the program. short Will be true if Width <1334 is the average of 889 and 1778. A short interval preserves the bit status, while in a long interval the bit status changes from 1, 0, 0 a 1. We realize this change by bit With 1 ex-or, long form: bit = bit ^ 1. The truth table of the exor operator can be found in Figure 10.

                short = Width < 1334
               IF need short:
                   bit ^= 1
               V <<= 1
               V |= bit
               bits += 1
               X += 1 + intimately(short)

We now push the previous result around a position logically to the left and then with the bit status - long form: v = v << 1 and v = v | bit. The heading counter is incremented and the next index is calculated in the array. It grows by 1 at long intervals, at short by 2. Int (short) Will 0, if short the value False has and 1, if short True is. After bits the value 14 has reached the While loop ended.

Based on Figures 13 and 14, you can understand the decoding algorithm and also the splitting of the IR word. The clock flanks are numbered in blue, the bit positions at the bottom in black.

Figure 13: Decode Pulse Train

Figure 13: Decode Pulse Train

Figure 14: Decoding table semi -automatic

Figure 14: Decoding table semi -automatic

            self.verbosis and print(am(V))
           # Split Into Fields (Val, AddR, CTRL)
           Val =(V & 0x3f) | (0 IF ((V >> 12) & 1) Else 0x40)
           AddDR = (V >> 6) & 0x1f
           CTRL = (V >> 11) & 1

When the debugging is switched on, we get the result in binary spelling. Then we divide the IR word into its shares. Val Contains the command word from bit position 5 to 0. The seventh bit is in position 12. We push it in position 0 fierce With 1 and realize whether it is set. If this is the case, ornament we the previous value of Val With 0 otherwise with 0x40. The bit 12 in V Is negated.

We find the address from position 10 to 6. To standardize, we push the masked bits by six positions to the right and mask with 0x1f = 0B0001111.

We get the Toggle-Bit from position 11. We push 11 positions to the right and mask at 1.

Kicks im try-Block an error, then we catch this through the except-Block off. Val then receives one of the negative values ​​that in IR_RX are declared as class attributes.

        except Runtime AS E:
           Val, AddDR, CTRL = E.args[0], 0, 0
       # Set Up for New Data Burst and Run User Callback
       self.do_callback(Val, AddDR, CTRL)

The determined values ​​then go to the function .do_callback() that you have to the function CB() passes on.

As soon as the file philips.py has been processed, we receive a message in Repl.

print("Rc5_ir was imported")

The circle is closed and the tour is over. In Figure 15 you can still inhale the entire mysticism of the program.

Figure 15: The interaction of the program parts is somewhat confusing

Figure 15: The interaction of the program parts is somewhat confusing

In the next post we will transform the ESP32 into an IR station. Stay tuned!

Figure 16: IR-RC recorder

Stop - I still have a miracle to offer. The following line throws the entire program and provides a huge amount of error messages. It is only a prompt prompt with a prompt line.

>>> T=input("Key on the RC >>>")

It took a long time until I found the cause for this, because this line was originally integrated into a While loop. So I had to find out which instruction did the program crash.

After all, I filtered out the prompt-string and since this part ">>>". The three larger characters are also the normal from Repl. Unfortunately, I could not fathom why part of my string brings the course of the interpreter so totally.

Esp-32Projekte für anfängerSensoren

4 comments

Jürgen

Jürgen

Hallo Ulf,
danke, dass du die ganzen Eingaben und Antworten vom System mit gepostet hast. So konnte ich deine Angaben bei mir ausprobieren und hab den SCHREIB-Fehler auch schon gefunden. Nun zur Meldung :
>>> rc = RC5_IR(Pin(14),lambda_:0)
Traceback (most recent call last):
File “”, line 1
SyntaxError: invalid syntax
>>>
>>> rc
Traceback (most recent call last):
File “”, line 1, in
NameError: name ‘rc’ isn’t defined

Das RC5-Objekt wurde nicht angelegt, kann also auch nicht referenziert werden. Das ist schon mal klar.klar. Aber warum wurde da ein Syntaxfehler gemeldet?
Vergleiche doch mal die Schreibweise im Blog
rc = RC5_IR(Pin(23),lambda _ : 0)
mit deiner
rc = RC5_IR(Pin(14),lambda_:0)
Erkennst du das Problem? Bei der namenlosen lambda-Funktion folgt nach dem Schlüsselwort lambda der Name einer oder mehrerer Variablen, die in dem Term , der nach dem Doppelpunkt kommt (hier einfach die 0) verwendet werden können. Natürlich muss der Name durch ein LEERZEICHEN von lambda abgesetzt werden. Genau das fehlt bei deiner Schreibweise. Der Unterstrich ist nicht Teil des Schlüsselworts sondern vertritt hier den Namen der Variablen, die vom Interpreter da erwartet wird. Weil der Term 0 keiner Variablen bedarf habe ich den Unterstrich verwendet, der einfach in Micropython eine namenlose Variable vertritt.

Der Konstruktor eines RC5_IR-Objekts fordert zwei Parameter, den GPIO-Pin, an dem die IR-Diode angeschlossen ist und den Bezeichner einer Funktion, die ausgeführt wird, wenn Impulse eintreffen. Im Projekt wird später eine solche Funktion definiert. Zum Ausprobieren über REPL steht sie halt noch nicht zur Verfügung und deshalb habe ich eine Funktion verwendet, die keinen Namen hat, aber eine Variable fordert und damit aber nichts tut.
RC5_IR(Pin(23),lambda _ : 0)
Die ähnlich lautende zweite Zeile hat dasselbe Problem.
Hier ein anderes Beispiel:
>>> (lambda x: x*x )(10)
100
x wird mit 10 belegt und mit sich selber multipliziert.

Viel Erfolg weiterhin!

Ulf Ihlefeldt

Ulf Ihlefeldt

Hallo,
ich habe auf dem ESP Lolin einen Ordner eingerichtet und die entsprechenden Dateien dort hinein kopiert.
Wenn ich die Tests durchführe, bekomme ich Fehlermeldungen.
>>> from ir_rx.philips import RC5_IR
init.py wurde ausgeführt
RC5_IR wurde importiert
>>> from machine import Pin
>>> rc = RC5_IR(Pin(14),lambda_:0)
Traceback (most recent call last):
File “”, line 1
SyntaxError: invalid syntax
>>>
>>> rc
Traceback (most recent call last):
File “”, line 1, in
NameError: name ‘rc’ isn’t defined

>>> import ir_rx.acquire
init.py wurde ausgeführt
>>> import ir_rx.philips
RC5_IR wurde importiert
>>> from machine import Pin
>>> rc=ir_rx.philips.RC5_IR(Pin(23),lambda_:0)
Traceback (most recent call last):
File “”, line 1
SyntaxError: invalid syntax
>>>
Was mache ich falsch?
Herzlichen Dank für die Mühen!
Ulf

Jürgen

Jürgen

Hallo, Michael,
es wäre hilfreich, wenn du den Text der Fehlermeldung mit gepostet hättest.
Sokann ich nur raten, wo und was den Fehler verursacht hat. Ich hab grade
die Schaltung mit lern.py getestet, hier läuft alles wie es sein soll, Keine Meldung
eines nicht vorhandenen Moduls.
1. Mit welchem Controller arbeitest du?
2. Arbeitest du mit dem Verzeichnis aus diesem Link?
http://grzesina.de/az/irrc/ir_rx.zip
3. Wurde des Archiv entpackt?
4. Ist der Ordner ir_rx im Root-Verzeichnis des Controllers sichtbar?
5. Stimmen die Schreibweisen?
6. Haben die im Blog aufgeführten Schritte funktioniert?
>>> from ir_rx.philips import RC5_IR
init.py wurde ausgeführt

>>> from machine import Pin
>>> rc = RC5_IR(Pin(14),lambda _ : 0)
>>> rc

Wenn diese Eingaben keinen Fehler verursachen, muss auch lern.py klaglos laufen.
Viel Erfolg
Jürgen

Michael D.

Michael D.

wie beschrieben: Ordner ir_rx auf Flash, lern.py starten:
=>Error: no module named ‘ir_rx’
Was mache ich da denn falsch?
Danke und VG

Leave a comment

All comments are moderated before being published

Recommended blog posts

  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