Python IR-Modul

Der Artikel beschreibt das Micropython Infrarot-Modul für den Betrieb am Calli:bot. Das Modul ist eine Umprogrammierung aus einer Vorlage der github.com Website (siehe Quelle, in Arbeit).


Einleitung

Nachdem die Hardware des Calli:bot mit dem ESP32 gut lief entstand schnell der Wunsch nach einer besseren Steuerung des Calli:bot. Grundsätzlich wäre eine Steuerung per Infrarot, Bluetooth und/oder WLAN denkbar. Aus der Sicht von Hard- und Software ist Infrarot mit Sicherheit die einfachste Lösung. IR-Empfänger sind klein und preiswert. Auch IR-Fernbedienungen gibt es reichlich. Für Bluetooth und WLAN wäre zusätzliche Hardware (Handy, Tablet oder Notebook) notwendig und es müsste eine steuernde App oder Websitesteuerung (IoT), welche auch noch mit dem ESP32 kommunizieren muss, geschrieben werden.


Merkmale des IR-Moduls

  • unterstützt ausschließlich das NEC-Protokoll
  • arbeitet mit der Frequenz eines 38 kHz IR-Empfängers
  • ist nur am ESP32 getestet
  • benutzt Signal-Pin 25 am ESP32 (kann leicht angepasst werden)
  • Repeat-Signal eingeschränkt, Repeat wird ca. 5 bis 10 mal nacheinander erkannt und bricht dann ab, daher ist dauerhaftes Halten einer Taste nicht möglich, Ursache unklar und nicht weiter analysiert
  • die Klasse”IRResceiver()” darf nur einmal instanziert werden (Grund: Interrupt- und Timerüberschneidung)
  • arbeitet interruptgesteuert am Signal-Pin
  • benutzt zwei ESP32 Timer
  • Ergebnis (gedrückte Taste) liegt während des Tastendrucks im Attribut: Instanzbezeichner.tastendruck
  • ohne Tastendruck enthält das Attribut: Instanzbezeichner.tastendruck den Wert 0

Untersuchungen und Tests vorab

Zur Analyse des IR-Signals leistete ein einfaches Testprogramm gute Dienste. Dazu liest das Programm interruptgesteuert hintereinander die Zeitdifferenzen zwischen steigenden und fallenden Flanken am Pin-Eingang ein. Anschließend werden die Zahlenwerte in der Konsole ausgegeben.


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 # IR Signal Aufzeichnung # mittels Interrupt bei steigender und fallender Flanke # Werte in einer globalen Liste und Ausgabe in der Konsole from machine import Timer, Pin from utime import sleep, ticks_us, ticks_diff def tickZaehler(pin): # Interruptroutine global interruptCounter, werte, start if interruptCounter < 1: start = ticks_us() werte.append(start) werte.append(0) interruptCounter = interruptCounter + 1 t = ticks_diff(ticks_us(), start) t = t - werte[len(werte)-1] # Zeitdifferenz zum vorherigen Wert werte.append(t) werte.append(pin.value()) pin = Pin(25, Pin.IN) # Initialiserung Interrupt und Variablen pin.irq(handler = tickZaehler, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING)) start = 0 interruptCounter = 0 werte = [] while interruptCounter < 80: # Ausgabe nach 80 Interrupts/Flanken sleep(1) print('Interrupt Zähler: ', interruptCounter) for i in range(0, len(werte), 2): print(str(werte[i]), ' ', str(werte[i+1])) pin.irq(handler = None)

Das Ergebnis der Ausgabe in der Konsole ist hier zu sehen:

>>> %Run -c $EDITOR_CONTENT
Interrupt Zähler:   84
466231289     0
119     0
8966     1
13458     0
14018     1
14588     0
15151     1
...

Ergebnis als Diagramm:


Qelltext des fertigen IR-Moduls


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 # Modul: irReceiverNec, IR-Empfänger mit ESP32 # kompakte Variante ohne zusätzliche Ausgaben # Hardware: # ESP32 # IR-Empfänger (38 kHz) an Pin 25 # Software: # Klasse IR-Receiver # zwei Timer benutzt # Adresse, Tastencode wird nach 80 ms gelöscht, wenn kein Repeatsignal folgt # NEC-Protokoll: # 68 Interruptpulse pro Tastendruck /-block # 4 Interruptpulse bei Repeat-Signal # Blocklänge für Tasten- und Repeat-Signal <= 80 ms # Quelle: Peter Hinch, https://github.com/peterhinch/micropython_ir # Autor: Wolfgang Rafelt from machine import Timer, Pin from utime import sleep, sleep_ms, ticks_us, ticks_diff class IRResceiver(): def __init__(self, pin): self.pin = pin # Pinnummer und Art. Eingabe self.zeitWerte = [] # Liste Pulslängen in ms self.irqPulse = 0 # Pulszähler self.blockPulse = 68 # Pulsanzahl maximal 68 self.blockLaenge = 80 # 80 Millisekunden für Datenblock und Repeatblock self.tastenCode = 0 self.adresse = 0 self.repeat = False self.necBitwert = False # False = 8 Bit und True = 16 Bit pin.irq(handler = self.pulsZaehler, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING)) self.timer0 = Timer(0) self.timer1 = Timer(1) def pulsZaehler(self, x): # Interrupts eines Blocks einlesen t = ticks_us() if self.irqPulse <= self.blockPulse: if not self.irqPulse: # erster Puls registriert, startet den Timer self.timer0.init(period=self.blockLaenge , mode=Timer.ONE_SHOT, callback=self.decodieren) self.zeitWerte.append(t) # Pulszeiten in Liste speichern self.irqPulse += 1 def decodieren(self, _): # Analyse eines 80 ms Blocks # timer1 löscht Adresse und Tastencode nach 80 ms, wenn kein Repeatsignal folgt self.timer1.init(period=self.blockLaenge , mode=Timer.PERIODIC, callback=self.loeschen) try: if self.irqPulse > 68: # zu viele Pulse, Block fehlerhaft raise pulsLaenge = ticks_diff(self.zeitWerte[1], self.zeitWerte[0]) if pulsLaenge < 4000: # kein Startblock erkannt raise pulsLaenge = ticks_diff(self.zeitWerte[2], self.zeitWerte[1]) if pulsLaenge > 3000: # 4.5 ms Puls vor den Daten if self.irqPulse < 68: # Pulsanzahl fehlerhaft raise # Abbruch # Adresse und Tastencode ermitteln, Pulslänge 1 = 1.69 ms, Pulslänge 0 = 562.5 µs wert = 0 for self.irqPulse in range(3, 68 - 2, 2): # einlesen aller 32 Bits (64 Flanken) in wert wert >>= 1 # jeweils letztes Bit auf 1 setzen # Nullbit erkennen if ticks_diff(self.zeitWerte[self.irqPulse + 1], self.zeitWerte[self.irqPulse]) > 1120: wert |= 0x80000000 # Bitwert 1 invertieren auf Nullbit elif pulsLaenge > 1700 and self.irqPulse == 4: # Repeat-Code erkennen, 4 Pulse in 80 ms raise else: raise # Startblock Fehler ad = wert & 0xff # 8 bit Addresse tc = (wert >> 16) & 0xff if tc != (wert >> 24) ^ 0xff: raise # Daten fehlerhaft if ad != ((wert >> 8) ^ 0xff) & 0xff: # Prüfung invertierter 8 Bit Addresswert if not self.necBitwert: raise # Adressfehler ad |= wert & 0xff00 # 16 Bit Addresse self.adresse = ad self.tastenCode = tc self.irqPulse = 0 # Pulse und Liste zurücksetzen self.zeitWerte = [] except: # Löschungen nach Fehlern self.irqPulse = 0 # Pulse und Liste zurücksetzen self.zeitWerte = [] self.adresse = 0 self.tastenCode = 0 def getTastenCode(self): # gibt Tastencode zurück return self.tastenCode def getAdresse(self): # gibt Adresse zurück return self.adresse def loeschen(self, _): # Interruptroutine if self.irqPulse != 4: self.irqPulse = 0 # Pulse und Liste zurücksetzen self.zeitWerte = [] self.adresse = 0 self.tastenCode = 0 self.timer1.deinit() def close(self): # Interrupt und Timer beenden self.pin.irq(handler = None) self.timer0.deinit() self.timer1.deinit() # Hauptprogramm # dieser Programmteil kann bei Verwendung als Modul entfallen # Beispielcode für Verwendung in eigenen Programmen ''' p = Pin(25, Pin.IN) # Eingang Pin 25 am ESP32 irrec = IRResceiver(p) # Instanzierung des Receiver try: while True: while irrec.tastenCode != 0: # Ausgabe nur wenn Tastencode empfangen print('Tastencode: ', irrec.getTastenCode(), ' Adresse: ', irrec.getAdresse()) sleep_ms(20) except KeyboardInterrupt: # Ende über "Strg+C" irrec.close() '''

Quelle:

https://github.com/peterhinch/micropython_ir