Statt den im Vorposting gezeigten Entwurf weiter zu polieren, habe ich
mich darauf beschränkt, diesen i.W. unverändert mit dem IOT-SDK 1.5.0 zu
übersetzen, dies zusammen mit einem älteren Android-Tablet als
kabelloses Kontrolldisplay für IR-Codes zu verwenden und erst mal eine
andere Idee zu verfolgen, nämlich IRMP in die unter dem Namen NodeMCU
bekannte Lua-Adaption (https://en.wikipedia.org/wiki/NodeMCU)
einzubauen.
Trotz einiger überraschender Hürden (s.w.u.) war das insgesamt weniger
Aufwand als erwartet.
http://nodemcu.com/index_en.html vermittelt zunächst den Eindruck einer
Hardwareplattform. Tatsächlich ist aber die auf GitHub gehostete
Firmware https://github.com/nodemcu/nodemcu-firmware problemlos mit dem
ESP-Open-SDK https://github.com/pfalcon/esp-open-sdk übersetzbar und
läuft dann anstandslos auch auf dem Olimex-Board
(https://www.olimex.com/Products/IoT/ESP8266-EVB/open-source-hardware).
So weit, so gut. Dann fangen die Probleme aber an.
In der mit dem unveränderten Espressif-SDK realisierten IRMP-Adaption
hatte ich den sog. Hardware-Timer aus der driver_lib verwendet, um
Timer-Interrupts zuverlässig alle 67 µS (bei 15 kHz) realisieren zu
können.
Dummerweise fehlt aber NmiTimSetFunc(func) im esp-open-sdk in ets_sys.h
und das aus gutem Grunde, denn ein dafür wiederum benötigtes externes
Symbol fehlt in einer der nur als Object-Module verfügbaren Library, die
im open-sdk verwendet wird. Selbst der auf Minimum 100 µs beschränkte
Softwaretimer ist nicht verfügbar. Damit fehlt die wesentliche Grundlage
für den Betrieb der IRMP-ISR.
Es gibt aber einen Ausweg. Es läßt sich nämlich vergleichsweise einfach
ein Interrupt an die steigende, fallende oder an beide Flanken eines
Pegelwechsels an einem Pin binden. Der genaue Zeitpunkt steht über
einen API-Call µs-genau oder über inline-Assembly sogar taktgenau zur
Verfügung:
1 | #define STORE_CYCLES(r) do { \
|
2 | __asm__ __volatile__("rsr %0, CCOUNT \n\t" : "=a" (r)); \
|
3 | } while (0);
|
Zwar besagt das nichts über die zeitliche Genauigkeit des Interrupts
selber, in der Praxis ist es aber offenbar hinreichend präzise für IRMP.
Zusammen mit einem kleinen Ringpuffer läßt sich so ganz einfach eine
Pulse-Train mitschreiben. Jedoch ist dies nicht das, was IRMP erwartet.
Da IRMP allerdings völlig egal ist, wann und wie irmp_ISR() aufgerufen
wird, lassen sich die Aufrufe ohne weiteres vor dem Aufruf von
irmp_get_data nachholen. Ein positiver Nebeneffekt dieses Verfahrens
besteht darin, daß der IR-Empfang nebenläufig und gepuffert erfolgt.
Der Beispieloutput im Anhang zeigt z.B. das Dekodieren von 29
IRMP-Telegrammen am Klump aus bereits gepufferten IR-Daten.
Modul irmp mit den drei Funktionen init, get_data und close.
----------
Funktionen
----------
irmp.init(int queuesize) -> 0 (no memory) oder 1 (ok)
queuesize: Gesamtzahl von Pulsen und Lücken, die gepuffert werden. Wie
viele pro Tastendruck verwendet werden, hängt vom Protokoll ab.
irmp.get_data() -> protokollname(string) adress(int) command(int)
flag(int) oder 0 (nichts) oder -1 (keine queue, closed)
irmp.close() -> Queue wieder freigeben.
Für das Herumprobieren damit war ESPlorer (http://esp8266.ru/esplorer/)
nützlich.
Lua, das ich mir erst bei der Gelegenheit oberflächlich angeeignet habe,
hat ein etwas ungewöhnliches Features, das mit der Implementation von
mehrfachen Rückgabewerten von Funktionen zu tun hat:
http://www.lua.org/pil/5.1.html und das einige Verrenkungen erfordert,
wenn man dergl. Listen tatsächlich verwenden möchte. Zwar ist es
ziemlich einfach und elegant, mehrere Werte, auch unterschiedlich viele,
von einem C-Programm aus abzuliefern - man kippt sie einfach
nacheinander auf dem Stack ab - so einfach das aber ist, so umständlich
ist es dann wieder in Lua, mit diesen etwas anzufangen. Leider hat eine
der dafür benötigten Funktionen ("unpack") zwischen 5.1 und 5.3 ihren
Namen bzw. Ort gewechselt (von globalem Namensraum zu Attribut von
"tables"). Die einzige simple, auch mit variabel langen Rückgabelisten
zurechtkommende Verfahrensweise, ein direkt auf einen Funktionsaufruf
angewandter Table Constructor http://www.lua.org/pil/3.6.html
funktioniert in der vorliegenden NodeMCU-Version nicht, es kommen nur
zwei von vier oder mehr Rückgabewerten an. Zum Glück gibt es einen
Workaround, das Einsammeln der Rückgabewerte läßt sich nachbilden:
http://www.lua.org/pil/5.2.html.
Dies erklärt die Funktion collect in dem nachfolgenden Schnipsel.
1 | require "irmp"
|
2 | function collect(...)
|
3 | return arg
|
4 | end
|
5 | function irmpg()
|
6 | local r
|
7 | repeat
|
8 | r=collect(irmp.get_data())
|
9 | if #r>2 then print(string.format("%s(%d): addr=%04x cmd=%04x f=0x%02x ",unpack(r)))
|
10 | else break end
|
11 | until #r==1
|
12 | end
|
13 | print (irmp.init(1000))
|
Wenn dieses Schnipsel ausgeführt wurde, können nachfolgend alle
zwischenzeitlich aufgelaufenen Tastendrücke mit einem einfachen Aufruf
von irmpg() dekodiert und über das UART angezeigt werden. Siehe
Beispieloutput.
Es wäre schön, wenn IRMP statt per Polling wahlweise auch per
Pulse-Train mit Input versorgt werden könnte. Wenn ich mich erinnere,
hattte Frank vor einer Weile mal angedeutet, über so etwas nachdenken zu
wollen.