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()
'''