Unterwegs mit dem YDLidar X2

Für unsere Experimente mit autonomem Fahren haben wir einen Raspberry Pi, zusammen mit Motoren, Akku, Kamera, Lidar und Fernbedienung auf Räder gesetzt und auf Reisen durch verschiedene Parcours geschickt – ein spannendes Experimentierfeld für Algorithmen zum maschinellem Lernen. Das kleine Fahrzeug bekam den Namen “RaspiCar”.

Der Lidar spielt dabei eine wesentliche Rolle. Er liefert einen Rundumblick auf mögliche Hindernisse auf der Strecke, die es zu vermeiden gilt. Wir haben uns für den YDLidar X2 entschieden. Es ist ein einfacher Laser-Scanner, stammt aus China und wird z.B. von Elektor (YDLIDAR X2 – 360-degree laser range scanner | Elektor) für einen akzeptablen Preis angeboten. Typischerweise sitzen solche Gerät in Roboter-Staubsaugern und ähnlichen Geräten. Der X2 ist das kleinste Gerät aus einer Reihe. Hier möchte ich von unseren Erfahrungen berichten und geeignete Treibersoftware vorstellen, die die Anwendung des Lidar einfach macht.

YDLidar X2

Der Lidar arbeitete mit einem Infrarot Laser, der durch Reflektionen den Abstand ermittelt. Laser, Detektor, Optik und Elektronik sitzen auf einer Drehscheibe, die mit einem kleinen Elektromotor über einen Riemen gedreht wird.

In der Drehscheibe befinden sich die beiden Öffnungen für Laser und Detektor. Rechts ist der Motor, der die Drehscheibe über einen Riemen anreibt.

So werden Messungen über den ganzen Bereich von 360 Grad produziert. Die Geschwindigkeit der Drehung lässt sich innerhalb Grenzen einstellen. Die maximale Reichweite beträgt 8 m. Für unsere Zwecke ist die minimale Reichweite fast wichtiger. Wenn die Abstände kleiner als etwa 10 cm werden, bekommt man keine brauchbaren Ergebnisse.

Der Lidar benötigt eine Spannungsversorgung von 5V und zieht zwischen 300 und 500 mA. Für batteriebetrieben Geräte ist das eine nicht zu vernachlässigende Anforderung.

Anschlüsse

Der Betrieb des Lidar ist einfach. Es gibt vier Anschlüsse: GND, +5V, Rx und M_CTR. Sobald die Betriebsspannung an GND und +5V anliegt, beginnt er zu drehen und liefert serielle Daten am Tx-Anschluss. Ein Empfang von seriellen Daten ist nicht vorgesehen. Die Baudrate ist fest eingestellt auf 115200. Im Paket liegt ein USB-Wandler dabei, so dass der Lidar direkt an einen der USB-Anschlüsse am PC oder Raspberry Pi angeschlossen werden kann. Für das RaspiCar verwenden wir aber den seriellen Port (Pin 10, RXD) am 40-poligen Pin Header.

Die vierte Leitung M_CTR dient dazu, die Drehgeschwindigkeit zwischen etwa 5 und 8 U/min einzustellen. Dazu wird eine Spannung zwischen 0 und 3.3V benötigt. Beim RaspiCar haben wir einen 10 kOhm Trimmer verbaut, der diesen Spannungsbereich liefert. So können wir mit verschiedenen Drehzahlen experimentieren.

Dieser USB-Konverter ist in der Lieferung enthalten. Auf der linken befinden sich die Anschlüsse für den Lidar. Rx wird nicht verwendet, da die Daten nur gesendet werden. Rechts ist ein USB-Anschluss vorhanden.

Mitteilsamer Sensor

Sobald der Lidar läuft, liefert er beständig Daten an der seriellen Schnittstelle. Der Datenstrom kann nicht abgestellt oder unterbrochen werden. Wenn keine Interesse an den Daten besteht, kann man sie natürlich ignorieren.

Das Hersteller bietet ein Development Manual zum Download an, in dem das Datenformat genau beschrieben wird. Zusätzlich gibt es ein SDK, das in C++ geschrieben ist, und zur Auswertung der Daten herangezogen werden kann. Um es kurz zu machen: Unsere Experimente mit dem SDK waren nicht wirklich zielführend. Entweder bekamen wir die Software auf dem jeweiligen Host-System nicht recht zum Laufen, oder das Ergebnis entsprach nicht unseren Anforderungen. Deshalb entschieden wir uns für den Eigenbau von Software, die den Datenstrom auswertet. Um die Programmierung zu vereinfachen und die Transparenz zu erhöhen, haben wir uns für Python entschieden. Tatsächlich gibt es im Netz bereits ein paar Beispiele für die Datenauswertung mit Python, die einen guten Startpunkt bereit stellten (z.B. YDLidarX2_python/LidarX2.py at master · nesnes/YDLidarX2_python · GitHub). Auch der Blog von msadowski mit einem C-Programm ist sehr informativ (YDLIDAR X2 – ROS review and Cartographer setup (msadowski.github.io) ).

Der hier vorgestellte Python-Treiber ist auf Github verfügbar:
smlaage/YDLidarX2: Python driver for the YDLidar X2 (github.com)

Python Berechnungen

Die Entwicklung der Software begann mit einem genaue Studium des Development Manuals. Der Datenstrom enthält Pakete, die durch einen Paket Header (PH) eingeleitet werden. Darauf folgen Angaben zum Typ des Pakets (CT) und die Anzahl der Messpunkte (LSN), den gemessenen Winkelbereich (FSA und LSA) und eine Prüfzahl (die wir aber getrost ignorieren). Schließlich folgen die einzelnen Datenpunkte (Si).

Ausschnitt aus dem Development Manual mit der Struktur der Datenpakete.
Quelle: https://www.ydlidar.com/dowfile.html?cid=6&type=3

Aus dem Datenpaket werden Datenpaare bestehend aus Winkel und gemessener Entfernung berechnet. Die dazu notwendigen Formeln sind im Development-Manual beschrieben und – naja – einigermaßen nachvollziehbar.

Schließlich wird noch eine Winkel-Korrektur benötigt, die abhängig von der gemessenen Entfernung ist. Jeder Winkel muss entsprechend umgerechnet werden. Freundlicherweise wird im Development-Manual ein Beispiel vorgerechnet, so dass man die eigene Software überprüfen kann. Am Ende des Prozesses stehen dann Datenpaare aus korrigierten Winkeln (Grad) und gemessenen Entfernungen (mm) bereit.

Vereinfachungen

Der Lidar liefert also Winkel und Entfernungen, wobei aufgrund der Winkelkorrektur nicht exakt vorhergesagt werden kann, welche Winkel tatsächlich gemessen wurden. Für unsere Anwendung zur autonomen Fahrzeugsteuerung ist eine exakte Winkelmessung aber gar nicht nötig. Wir möchten wissen, wo ungefähr ein Hindernis auftaucht und frühzeitig mit einem Ausweichkurs reagieren. Hier setzen zwei Vereinfachungen ein:

Ersten wird der gemessene Winkel als Integer interpretiert. Wir nehmen also nur ganzzahlige Winkel. Dazu verwendet das Programm ein Integer-Array mit 360 Werten (Index: 0 bis 359), in dem die gemessenen Entfernungen in Millimeter abgelegt werden. Es zeigt sich, dass oft Winkel übersprungen werden, so dass im Array fehlende Werte auftauchen. Diese werden mit dem Wert out_of_range (hier 32768) markiert.

Zweitens werden die Winkel in Sektoren zusammengefasst. Dabei wird der Rundumblick von 360 Grad reduziert auf 40 Sektoren von jeweils 9 Grad. Der Sektor 0 erfasst also 0 bis 8 Grad, Sektor 1 reicht von 9 bis 17 Grad, usw. Für das Navigieren wichtig sind die Sektoren 19 (171 – 179 Grad) und 20 (180 – 188 Grad), die den direkt vorausliegenden Bereich erfassen. Für jeden Sektor wird der kleinste gemessene Abstand erfasst, weil es für die Navigation letztlich nur darum geht, die am nächsten gelegenen Hindernisse zu “sehen”. Mit Blick auf maschinelles Lernen führen wir hier eine Reduktion der Dimensionen durch, die es einigen der Algorithmen einfacher macht.

Sectors are:
- sectors[ 0] -> 0 - 8 degree,
- sectors[ 1] -> 9 - 17 degree,
- sectors[ 2] -> 18 - 26 degree,

- sectors[38] -> 342 - 350 degree,
- sectors[39] -> 351 - 359 degree
Sectors with missing values are reset to the minimum range.

Wer es noch weiter vereinfacht haben möchte, kann auch mit 20 Sektoren mit jeweils 18 Grad arbeiten. Prinzipiell sollte es auch kein Problem sein, die Anzahl der Sektoren größer zu machen.

Das Python-Modul beinhaltet einige Funktionen, um die gemessenen Daten mit Hilfe einer Canvas in TKinter anzuzeigen. Das ist für die Visualisierung in der Testphase hilfreich. Allerdings benötigt das zeitnahe Updaten der TKinter-Grafik auf einem Raspberry Pi relativ viel Rechenpower, so dass man bei der eigentlichen Anwendung auf die Grafik lieber verzichtet.

Beispiel-Output: Der Lidar befindet sich im Zentrum des Bildes, markiert durch das Kreuz. Die blauen Kreise markieren 50 cm Entfernungen. Die Lidar-Messwerte sind als schwarze, und die Sektoren mit den jeweils kleinsten Werten als rote Linie dargestellt. Die Sektoren sind durchnummeriert.

Ablaufsteuerung

Der Treiber geht folgendermaßen vor: Der Datenstrom vom Lidar wird in Datenblöcke aufgeteilt. Die Blockgröße wird über den Parameter chunk_size festgelegt. Als Default-Wert wird 2000 verwendet, was ungefähr 2 Umdrehungen des Lidars entspricht und einen guten Kompromiss zwischen Geschwindigkeit und Genauigkeit darstellt. Wenn ein Datenblock voll ist, wird er ausgewertet und das Array mit den Entfernungen gefüllt. Wenn es mehrere Messungen (von mehreren Umdrehungen) für denselben Winkel gibt, wird der Mittelwert berechnet. Anschließend wird das Flag avaiable gesetzt, um dem Anwender zu zeigen, dass neue Daten verfügbar sind.

Je größer der Parameter chunk_size ist, desto mehr Daten werden gesammelt. Dadurch gibt es weniger fehlende Werte, und ein größerer Anteil der Winkel bekommt 2, 3 oder mehr Messungen. Auf der anderen Seite dauert es länger, bis neue Daten verfügbar sind. Diese Balance mag für jede Anwendung anders sein.

Der Paarmeter chunk_size (default: 2000) bestimmt die Balance zwischen der Datenqualität und der Geschiwndigkeit.

Awendungen

Wie verwendet man den Treiber? Zuerst wird er als Python-Modul importiert. Die Verbindung zur seriellen Schnittstelle wird mit der Methode .connect() hergestellt. Dann kann die Verarbeitung der Lidar-Daten mit der Methode .start_scan() aktiviert werden. Die Auswertung arbeitet als eigener Thread im Hintergrund. Immer dann, wenn ein Datenblock verarbeitet wurde, wird ein Flag gesetzt, das als Property .available abrufbar ist. Das aufrufende Programm kann also nachschauen, ob es aktuelle Daten gibt. Mit der Methode .get_data() können die Entfernungswerte über 360 Grad abgerufen werden. die Methoden .get_sectors20() und .get_sectors40() liefern die Werte für die Sektoren, wie oben beschrieben. Immer wenn die Daten oder Sektoren ausgelesen wurden, wird das Flag .available zurück gesetzt, bis eine neuer Satz von Daten bereitsteht. So kann vermieden werden, dass ein aufrufendes Programm mehrfach dieselben Daten verarbeitet.

Ein einfaches Beispiel-Programm (hier für den Raspberry Pi) ist unten gezeigt. Die Messdaten des Lidar werden als ein Numpy-Array distances ausgelesen und stehen für die weitere Verarbeitung bereit.

Performance

Eine Sorge bei der Entwicklung des Python-Treibers war, ob das Programm mit dem Tempo der Datenlieferung vom Lidar zurecht kommt, zumal Python die Ausführung von Threads nur auf demselben Prozessorkern ermöglicht. Deshalb verwendet der Python-Treiber in weiten Teilen Numpy-Arrays, die eine gute Performance haben. Für die Berechnungen der Winkel-Korrektur wird der Arkustangens benötigt. Damit das schneller geht, werden die Korrekturfaktoren für alle ganzzahlige Winkel-Grade vorbereitet und als Lockup-Array abgelegt.

Insgesamt haben wir eine gute Performance bei geringer Prozessor-Last erzielen können. Unsere Sorge hat sich als unnötig erwiesen. Das System läuft sehr gut auf einem Raspberry Pi 3 oder 4. Es ist erstaunlich, wie der Raspberry Pi den ständigen Datenstrom im Hintergrund abarbeitet. Auf einem Raspberry Pi 3 zeigt sich im Performance-Monitor nur eine geringe Erhöhung der CPU-Aktivität. Wenn parallel noch eine Kamera betrieben wird, dann ist der Raspberry Pi 4 sicherlich im Vorteil.

Erfahrungen

In der Praxis zeigt sich, dass der Lidar gut arbeitet und zuverlässig Hindernisse erkennt. Das Arbeiten mit den Sektoren funktioniert sehr gut. Wir haben erfolgreiche Routen durch anspruchsvolle Parcours mit automatischer Navigation absolviert. Die Frequenz von etwa 3 Datensätzen pro Sekunden (chunksize = 2000) ist für unser maximales Tempo von etwa mäßiger Schrittgeschwindigkeit vollständig ausreichend. Wenn die Abstände zu Hindernissen kleiner werden, regelt der Algorithmus die Fahr-Geschwindigkeit deutlich herunter.

Sehr schmale Objekte, zum Beispiele dünne Stuhlbeine, können aber übersehen werden. Außerdem haben wir den Eindruck, dass runde reflektierende Objekte nicht erkannt werden. Als weitere Entwicklung wird eine Kamera eingesetzt, die zusätzliche Daten für das maschinelle Lernen bereitstellen soll. Das sind zur Zeit laufende Experimente.

RaspiCar mit Lidar und Kamera

Ressourcen

Python-Treiber für den YDLidar X2:
smlaage/YDLidarX2: Python driver for the YDLidar X2 (github.com)

Gewitter-Monitor zum Dritten

Das Thema scheint mich nicht loszulassen. Zum einen hatten wir dieses Jahr einen Sommer mit vielen Unwettern, so dass ich den alten Gewitter-Monitor wieder einmal aus dem Schrank geholt. Zum anderen habe ich eine Reihe von Anfragen bekommen, ob es eine einfache Nachbau-Möglichkeit gibt. Tatsächlich ist das Projekt etwas in die Jahre gekommen und nicht mehr auf der Höhe der Zeit. So kam die Idee, eine neue Version zu entwickeln, die mehr Möglichkeiten bietet und einfacher nachzubauen ist.

Version 3: Neue Ideen für ein bewährtes Konzept

Das Konzept des Gewitter-Monitors ist schnell erklärt: Zwei AM-Empfänger hören im Langewellenbereich in den Äther, um das typische Knacken von Blitzen zu empfangen. Die Empfänger verwenden Ferritstab-Antennen mit einer Richtungsempfindlichkeit. Deshalb gibt es zwei Antennen, die rechtwinklig zueinander montiert werden, so dass 360 Grad abgedeckt sind. Das Empfangsergebnis wird verstärkt, von einem Mikrocontroller mit Analog-Digital-Converter (ADC) ausgewertet und grafisch dargestellt. Insbesondere der zeitliche Verlauf über die letzten Stunden ist hier von Interesse. Ein detaillierte Beschreibung des Prinzips gibt es in einem früheren Beitrag auf dieser Seite: Neues vom Gewitter-Monitor: Version 2 – Elektronik-Labor (laagewitt.de)

Das Konzept von Version 2 hat sich bewährt. Der Empfang der Blitze im Bereich von etwa 100 kHz klappt gut. Deshalb wird die Signalverarbeitung mit Verstärkung und Peak-Detection unverändert übernommen. Die Erfassung der Daten über die Zeit und die Darstellung der letzten 2 oder 3 Stunden mit Trendanalyse sind anschaulich. Soweit bleibt alles beim Alten. Was lässt sich also verbessern?

Farbiges Display

Zuerst einmal ist das einfarbige LC-Display mit einer Auflösung von 192 x 64 Punkten nicht mehr zeitgemäß. Das geht inzwischen deutlich besser. Außerdem hat sich der Nachbau mit dem Smart-I2C-Interface für einige Interessenten als nicht ganz einfach herausgestellt. Inzwischen gibt es kleine, farbige Displays, die gut für Mikrocontroller-Projekte geeignet sind. Ich habe mich für ein 3.5inch TFT-Display entschieden, das eigentlich für den Raspberry Pi vorgesehen ist (siehe 3.5inch RPi Display – LCD wiki). Die Auflösung beträgt 480 x 320 Pixel. Das Display kann alle Farben darstellen. Es hat zusätzlich einen resistiven Touch-Controller, den ich in diesem Projekt aber nicht verwende. Und das Display ist gut verfügbar. Es kann bei den üblichen Online-Händlern unter verschiedenen Namen (z.B. Elegoo 3.5 480×320 SPI) zu akzeptablen Preisen erworben werden. Der Anschluss erfolgt über eine Buchsenleiste mit 26 Kontakten, die eigentlich für den Raspberry Pi vorgesehen ist.

3.5inch TFT-Display mit SPI-Schnittstelle. Auf der Rückseite (rechts) ist die zweipolige Anschlussleiste zu sehen.

Leistungsfähiger Mikrocontroller

Ein grafisches Display mit höherer Auflösung erfordert mehr Rechenkraft. Besonders das verfügbare RAM ist bei den 8-Bit Arduinos nicht ausreichend. Deshalb verwende ich den ESP32, den ich auch bei vielen anderen Projekten einsetze.

ESP32 Devkit

Der ADC des ESP32 arbeitet bei diesem Projekt mit einer im Vergleich zum ATmega328 höheren Frequenz von 10 kHz. Dabei werden immer abwechselnd die beiden Eingangskanäle abgefragt. Jeder Kanal kommt also auf 5 kHz, was zusammen mit der Peak-Detection-Schaltung völlig ausreicht, um zuverlässig alle Ereignisse zu erfassen.

Für das Display gibt es glücklicherweise eine hervorragende Grafik-Bibliothek zur Ansteuerung des oben genannten Displays, die auf GitHub veröffentlich wurde (github.com/Bodmer/TFT_eSPI). Ein besonderer Dank an den Autor und die Community, die diese Bibliothek ständig erweitert!

Die Verbindung zwischen dem ESP32 und dem Display geschieht mit einem SPI-Interface. Die Dokumentation ist nicht gerade selbsterklärend. Hier ist die Liste der Verbindungen, die ich für meine Projekte standardmäßig verwende:

Anschluss-Schema zwischen ESP32 und TFT-Display

Wichtig sind einige Anpassung in der Datei user_setup.h. Die Datei befindet sich in der Bibliothek TFT_eSPI und ist für die Projekt-spezifischen Einstellungen zuständig. Die Details dazu sind im ReadMe bei Bodmer gut beschrieben.

Notwendige Anpassungen in der Datei user_setup.h in der Bibliothek TFT_eSPI. Achtung: Bei neueren Versionen der Datei passen die Zeilennummern nicht mehr exakt mit den hier gezeigten Angaben. Die entsprechenden Zeilen können aber gut gefunden und entsprechend aktiviert oder deaktiviert werden.

Eine herausragende Eigenschaft des ESP32 ist seine Netzwerkfähigkeit. Hier könnte man natürlich spannende Ideen ausprobieren, z.B. das Einspeisen der Messdaten via MQTT in eine Datenbank in der Cloud. Im Moment sind das aber nur Ideen. Die aktuelle Version der Software ist noch nicht so weit. Vielleicht folgt so etwas zu einem späteren Zeitpunkt, gerne auch mit Beiträgen von anderen Autoren. Quellcode und Daten der aktuellen Version sind auf GitHub verfügbar.

Luftdruck und Zeit

Wenn schon alles neu, dann auch richtig! Die neue Version hat einen Luftdrucksensor bekommen (Bosch BMP280). Die Idee ist, zusätzlich zu den Blitzen auch den aktuellen Luftdruck zu beobachten. Gelegentlich zeigt sich ein nahendes Gewitter durch eine Abfall des Luftdrucks. Ob das eine auswertbare Größe ist, muss die praktische Erfahrung zeigen.

Und schließlich möchte ich die Zeitachse mit der Echtzeit beschriften. Dazu hat die Schaltung eine Echtzeituhr (DS3231) bekommen, die vom Prozessor ausgelesen und in der Grafik dargestellt wird.

Die Schaltung

Der Gewitter-Monitor besteht aus zwei Modulen, dem Hauptmodul und dem Antennen-Modul

Das Projekt besteht nach wie vor aus zwei Modulen, dem Hauptmodul mit Prozessor, Display und Zubehör und dem Antennen-Modul mit zwei AM-Empfängern und Ferritstäben.

Schaltplan des Hauptmoduls

Das Hauptmodul trägt den Prozessor (links unten) und das Display (links oben). Außerdem befindet sich dort (Mitte rechts) die Signalverarbeitung zur Verstärkung der Signale von der Antenne zusammen mit zwei OpAmps, die die Peak-Detection übernehmen. Der Output geht dann auf zwei ADC-Eingänge vom Prozessor.

Weiterhin gibt es hier (unten rechts) zwei einstellbare Konstantspannungs-Quellen zur Versorgung der AM-Empfänger auf dem Antennen-Modul. Die Versorgungsspannung regelt die Empfindlichkeit der Empfänger. Jeder Kanal lässt sich mit jeweils einem Trimm-Poti zwischen 0.8 und 1.5V einstellen. Ich habe gute Ergebnisse mit 1.2V erzielt. Die getrennte Einstellung der Spannung für die beiden Kanäle kann zum Ausgleichen von unterschiedlicher Empfindlichkeit genutzt werden, ist in der Praxis aber nicht unbedingt notwendig. Wer beide Kanäle mit derselben Spannung versorgen möchte, kann ein Trimm-Poti weglassen und statt dessen die Brücke “Single Mode” setzen.

Oben rechts befinden sich der Luftdrucksensor und die Echtzeituhr, die über das I2C-Interface mit dem Prozessor kommunizieren.

Im Plan links in der Mitte ist eine kleine Schaltung mit einem P-Kanal MOSFET IRLML2244 gezeigt, mit der das Display ein- bzw. ausgeschaltet werden kann. Der Gewitter-Monitor schaltet das Display ab, wenn nach einer einstellbaren Zeit keine relevanten Signale erkannt werden. Wenn sich ein Gewitter nähert, schaltet das Display automatisch ein.

Unten in der Mitte im Plan sind die Bedienelemente. Es sind 5 Taster vorgesehen. Weiterhin gibt es zwei weiße LEDs, die den Empfang von Blitzen durch kurzes Aufblinken anzeigen, und drei farbige LEDs (grün, gelb, rot), die den Alarmstatus kenntlich machen.

Schließlich befindet sich im Plan links in der Mitte ein Spannungswandler zur Stromversorgung. Die Schaltung kann mit 9 oder 12V aus einem kleinen Netzteil betrieben werden. Alternativ kann aber auch ein Mikro-USB-Kabel in das ESP32-Modul gesteckt werden. In dem Fall kann der Spannungswandler TSR einfach weggelassen werden.

Schaltplan des Antennen-Moduls

Das Antennen-Modul ist deutlich einfacher gestrickt. Jeder Kanal bekommt einen TA7642 AM-Empfänger mit der entsprechenden Beschaltung. Der Eingangskreis mit einer Spule auf einem Ferritstab und einem 1nF Kondensator ist für etwa 100 kHz ausgelegt. Die Spule besteht aus 0.3 mm lack-isoliertem Kupferdraht, sauber gewickelt auf eine Länge von 8 cm über einen 10 cm Ferritstab. Die exakte Frequenz ist nicht relevant, weil Blitze als Breitbandsender einen großen Bereich abdecken.

Die beiden Module werden über ein 6-poliges Flachbandkabel mit Pfostenstecker verbunden.

Platinen

Der Schaltung hat im Vergleich zu den früheren Versionen des Gewitter-Monitors an Komplexität zugenommen. Ein Aufbau auf einer Lochraster-Platine wäre möglich aber recht mühevoll. Deshalb habe ich zwei Platinen entworfen, eine für das Hauptmodul und eine für die Antenne. Alle Bau- und Bedienelemente haben darauf Platz gefunden. Abgesehen vom Verbindungskabel zwischen den Modulen gibt es keine weiteren Verdrahtungsarbeiten.

Platine des Hauptmoduls im Leiterbahnen-Editor. Die aktuelle Version ist 3b.

Die Rückseite der Platine des Hauptmoduls trägt alle Bauelemente. Die Taster, LEDs und das Display sind auf der Vorderseite angeordnet. So ergibt sich eine platzsparende Bauweise.

Rückseite der bestückten Platine. Der ESP32 (rechts), das Uhrenmodul DS3231 (links) und der Luftdrucksensor BME280 (oben) werden über Steckleisten montiert. Links in der Mitte befinden sich die beiden Trimm-Potis zur Einstellung der Betriebsspannung der AM-Empfänger auf dem Antennen-Modul. Rechts oben gibt es Schraubklemmen für die Spannungsversorgung mit einem 9 oder 12V Steckernetzteil.
Vorderseite der Platine. Oben links befindet sich die Steckerleiste für das Display, oben rechts die Leuchtdioden und darunter 5 Taster für die Bedienung.
Die Platine für die Antenne ist vergleichsweise klein. Das Bild zeigt die Platine noch ohne Bestückung.

3D-Druck

Für den mechanischen Aufbau gibt es 4 passgenaue Kunststoffteile aus dem 3D-Drucker. Das größte Teil ist die Frontplatte, an der die Platine verschraubt wird. Alle notendigen Abstandshalter und Durchbrüche für Display, LEDs und Taster sind vorhanden. Dadurch wird die Montage einfach.

Frontplatte aus dem 3D-Drucker
Die Rückseite der Frontplatte trägt Abstandshalter und Bohrungen für die Montage der Platine.

Die Anschlussleiste des Displays ist ausgelegt für den Raspberry Pi. Bei dieser Anwendung steht das Display relativ weit aus der Frontplatte hervor. Deshalb gibt es einen Abstandshalter (Display-Spacer), der unter das Display geschoben wird.

Display-Spacer

Nachdem das Display aufgesteckt ist, wird es von einem Rahmen gehalten, der von oben aufgeschraubt wird. Beim Prototypen wurde der Rahmen mit blauem Filament gedruckt. Die Farbwahl ist offensichtlich Geschmackssache.

Display-Rahmen

Als viertes Teil gibt es einen Träger für das Antennen-Modul mit den beiden Ferritstäben. Die Ferritstäbe werden mit kleinen Kabelbindern an den Stützen fixiert.

Antennen-Träger

Software

Die Elektronik und die Mechanik sind zusammengelötet und verschraubt. Aber erst mit der Software wird daraus ein funktionierendes Gerät. Erst dachte ich, dass das in 1 oder 2 Tagen erledigt ist. Wie so oft beim Freizeit-Programmieren lag ich deutlich falsch. Es benötigte wesentlich mehr Zeit, um alle Funktionen sinnvoll zu gestalten und zu testen. Und die Software ist noch lange nicht fertig (wann ist Software jemals fertig?). Die aktuelle Version der Software kann aber schon eine ganze Menge und scheint weitgehend stabil zu laufen. Mit der Zeit wird noch weitere Funktionalität dazu kommen.

Die Software ist mit der Arduino IDE in C++ geschrieben. Das Hauptprogramm ist gewitter_monitor_v3_esp32.ino. Dazu gibt es eine Reihe von Klassen (display, my_rtc, command_line, menu, keyboard) und eine Sammlung von Hilfsfunktionen util. Die Arduino-Software ist auf GitHub verfügbar. Wer Interesse hat, an der Software-Entwicklung mitzuarbeiten, ist sehr willkommen.

Abhängigkeiten

Um den Sketch zu kompilieren, werden eine Reihe von Arduino-Bibliotheken benötigt. Die (minimale) Ausstattung beinhaltet die folgenden Module:

Wie oben beschrieben muss die Datei User_Setup.h im TFT_eSPI Library-Folder angepasst werden, damit das Programm das richtige Display unter den passenden Anschlüssen findet.

Anwendung und Bedienung

Gewitter-Monitor in Aktion. Der Alarm ist gesetzt und zeigt gelb/rot.

Der Gewitter-Monitor hört ständig in den Äther auf der Suche nach Blitzen. Wenn ein Blitz erkannt wird, wird das Maximum der Feldstärke gesucht. Der Monitor speichert für jede Minute zwei Datenpunkte, die Summe der registrierten Blitz-Maxima für beide Kanäle. Auf der rechten Seite des Displays werden für die laufende Minute die Anzahl der Blitze (Count) und die Summe der Maxima (Sum) angezeigt. Außerdem befindet sich dort der letzte Luftdruck-Messwert und die aktuelle Zeit in Minute und Sekunde.

Der größte Teil des Displays zeigt den Verlauf über die letzten drei Stunden, also Datenpunkte für 180 Minuten. Die X-Achse ist eingeteilt in Stunden mit Markierungen für viertel und halbe Stunden. Die grünen und roten Balken zeigen die registrierten Summen-Werte für die abgelaufene Minute. Die Y-Skala wird automatische skaliert, so dass alle Werte sinnvoll dargestellt werden.

Zusätzlich gibt es eine Trend-Berechnung mit linearer Regression über die letzten 60 Minuten, sofern die aktuellen Daten eine sinnvolle Berechnung zulassen. Die Steigung der Geraden wird als Trend-Wert auf der rechten Seite gezeigt. Positive Werte bedeuten, dass die Gerade ansteigt und Gewitter-Aktivitäten möglicherweise weiter zunehmen.

Die blaue Linie zeigt den relativen Luftdruck. Hier lässt sich die Entwicklung des Luftdrucks über die letzten 3 Stunden ablesen.

Die Funktion der Leuchtdioden ist schnell erklärt. Die beiden weißen Leuchtdioden blinken kurz auf, wenn ein Blitz erkannt wurde. Die grüne, gelbe und rote Leuchtdiode zeigen den Alarm-Status.

Aufstellungs-Ort

Der Gewitter-Monitor hat eine hohe Empfindlichkeit und benötigt eine Standort, der einigermaßen frei von magnetischen Störsignalen ist. Störungen können z.B. von Schaltnetzteilen oder Dimmern kommen. Auch das Netzteil zur Stromversorgung der Schaltung kann stören. Wenn es auch ohne Gewitter immer wieder blinkt, dann kann es helfen, einen anderen Ort zu suchen, der einen geringeren Störlevel hat.

Gegebenenfalls kann es nützlich sein, die Masse (Minus-Pol der Stromversorgung) mit einem Erdungsanschluss zu verbinden (wie früher beim Detektor-Radio). Die Schraubklemmen auf der Platine haben zwei GND-Anschlüsse, von denen einer mit einem Draht z.B. mit einer blanke Stelle einer Wasserleitung oder eines Heizungsrohres verbunden werden kann. Auch ein Anschluss an den Schutzleiter des Hausstromnetzes kann helfen.

Erfahrungen

Bei ruhiger, stabiler Wetterlage registriert der Monitor nur gelegentliche, schwache Ausschläge. Signalpegel unter 500 kann man getrost ignorieren. Dabei handelt es sich wahrscheinlich um technische Schaltvorgänge im Haus oder der Umgebung.

Ein ruhiger Morgen bei stabiler Wetterlage. Einzelne Ausschläge stammen von elektrischen Schaltvorgängen im Haus oder in der Nachbarschaft. Davon sieht man in der Nach weniger als am Tag.

Spannend wird es, wenn in der Atmosphäre etwas passiert, z.B. eine Kaltfront herannaht. Dann gibt es in dichter Folge Ausschläge mit Summenwerten von 1000 oder darüber. Interessanterweise registriert der Gewitter-Monitor auch dann Aktivitäten, wenn die Webseite blitzortung.de (noch) gar nichts zeigt. Ich vermute, dass der Gewitter-Monitor auch schwache Entladungen zwischen den Wolken empfängt, die bei blitzortung.de nicht registriert werden.

Hier war der Nachmittag noch ruhig. Aber ab etwa 16 Uhr zog eine Kaltfront mit viel Wind durch unsere Region. Interessant war, dass auf blitzortung.de keine Blitze registriert wurden.

Wenn Gewitter dann tatsächliche in Erscheinung treten, werden sie schon in Entfernungen von einigen 100 km registriert. Die Ausschläge können dann bis weit über 5000 reichen. In der Nacht ist dann meist Wetterleuchten in der Ferne zu sehen.

Dieses Bild zeigt zwei Vorgänge: Bis etwa 19 Uhr ist eine Gewitterfront durch den Schwarzwald gezogen, etwa 200 km nördlich von uns, und hat sich dann abgeschwächt. Ab etwa 19:30 Uhr gab es mehrere Gewitter in den Schweizer Alpen, nach blitzortung.de etwa 300 km östlich von uns, die später wieder abnahmen.
Durchzug eines lokalen Gewitters in der Nachbarschaft. Das Zentrum hat unseren Ort aber nicht erreicht.

Wenn ein Gewitter in die direkter Nähe kommt, es also hörbar “kracht”, dann reichen die Summen-Werte bis weit über 10000. Die stärksten Werte, die ich bisher registriert habe, lagen bei 50000.

Alarm

Das System arbeitet mit 5 verschiedenen Alarmstufen. Dazu wird der Mittelwert der letzten 5 Minuten berechnet. Wenn dieser Wert festgelegte Schwellen überschreitet, wird der Alarm ausgelöst. Der höchste Level kann mit Hilfe der Konfiguration nach Bedarf justiert werden. Die anderen Stufen ergeben sich als Bruchteile des höchsten Alarm-Levels. Ein Beispiel mit der Standardeinstellung:

  • Werte unter 1000 -> Das Display ist (bzw. wird nach einigen Minuten) ausgeschaltet (display timeout). Die grüne LED leuchtet.
  • Alarm Level 1 – Werte über 1000 -> Das Display wird eingeschaltet, die grüne LED leuchtet
  • Alarm Level 2 – Werte über 2000 -> Die gelbe und die grüne LED leuchten
  • Alarm Level 3 – Werte über 3000 -> Die gelbe LED leuchtet
  • Alarm Level 4 – Werte über 4000 -> Die gelbe und die rote LED leuchten
  • Alarm Level 5 -> Werte über 5000 -> Die rote LED leuchtet

Der Wert für den Alarm-Level 5 lässt sich anpassen. Die anderen Alarm-Level ergeben sich als anteilige Faktoren.

Tastenfeld und Konfiguration

Mit den 5 Tasten (4 mal gelb links, rechts, oben, unten, und einmal blau in der Mitte) können Einstellungen verändert werden.

Mit 5 Tasten können häufige Einstellungen vorgenommen werden

Die Software enthält eine ganze Reihe von Parametern, die vom Anwender angepasst werden können. Die Werte werden im non-volatile Speicher des ESP32 abgelegt, sind also auch über das Abschalten hinaus verfügbar. Das Konfigurations-Menü wird über die blaue Tasten geöffnet und über den Menüpunkt Exit wieder verlassen.

Das Konfigurations-Menü wird über die blaue Taste aufgerufen. Dann können die einzelnen Werte mit den gelben Tasten auf und ab ausgewählt werden. Die Werte werden mit den gelben Tasten links und rechts innerhalb sinnvoller Grenzen verändert. Ein neuer Wert muss mit der blauen Taste übernommen werden, damit er aktiv wird.

Paramater:

  • Hour: aktuelle Stunde der Echtzeituhr
  • Minute: aktuelle Minute der Echtzeituhr. Wenn der Minuten-Wert eingestellt wird, wird die Sekunde auf 0 gesetzt.
  • Detector Threshold: Wenn sich zwei aufeinanderfolgende ADC-Werte um den Threshold unterscheiden, dann wird das Signal als Blitz gewertet. Kleinere Werte bedeuten höhere Empfindlichkeit, größere Werte entsprechend weniger Empfindlichkeit.
  • Flash Duration [ms]: Dauer des Aufleuchtens der weißen LEDs, wenn ein Blitz erkannt wird.
  • Display Timeout [min]: Wenn kein Alarm-Level gesetzt ist, schaltet das Display nach dieser Zeit ab.
  • Alarm Level: Summen-Wert, ab dem die rote Leuchtdiode gesetzt wird. Die niedrigeren Alatm-Level werden als anteilige Faktoren berechnet.
  • Alarm Window: Die Anzahl der letzten Minuten, die für die Durchschnittsberechnung des Alarm-Wertes verwendet werden.
  • Scale Minimum: Die kleinste Skalierung der Y-Achse. Wenn gar keine oder nur sehr geringe Signale gemessen wurden, dann wird die Y-Achse mit diesem Wert skaliert.
  • hPa: Offset für die Luftdruck-Einstellung. Je nach der Höhe des Standortes ist der tatsächlich gemessene Luftdruck geringer als der auf Meeresniveau bezogene Luftdruck. Hier kann ein Standardluftdruck eingebeben werden. Das System berechnet daraus einen Offset zum gemessene Druck, den es dann für die Anzeige verwendet.

Schließlich gibt es noch eine Einstellhilfe für die Spannungsversorgung der AM-Empfänger auf den Antennen. Die Werte werden vom ADC des ESP32 ausgelesen und unter dem Menüpunkt Voltage Adjust angezeigt. Wenn dieser Punkt ausgewählt ist, können die Spannungen an den Trimm-Potis auf der Platine eingestellt werden. Die in diesem Beitrag gezeigten Bilder wurden mit einer Spannung von 1.20 V gemacht.

Einstellung der Spannungsversorgung der AM-Empfänger. Größere Werte ergeben eine höhere Empfindlichkeit, womit aber auch die Anfälligkeit für Störungen zunimmt.

Fazit

Der neue Gewitter-Monitor steht bei mir jetzt einige Wochen im Regal und zeigt immer wieder interessante Grafiken und Daten. Das farbige Display macht sich gut. Wenn sich das Display einschaltet und besonders die gelbe und rote LED aktiv werden, ist das ein untrügliches Zeichen für Gewitter-Aktivitäten in der Atmosphäre.

Die Funktionsweise wurde weitgehend vom Vorgänger übernommen, im Detail aber deutlich überarbeitet. Der Nachbau ist durch die Platine und die Verwendung von Standardbauteilen deutlich einfacher geworden. Zusammen mit den Teilen aus dem 3D-Drucker ergibt sich ein kompaktes Gerät, dass wenig Handarbeit benötigt. Die Entwicklung der Software hat viel Zeit in Anspruch genommen – und reizt noch lange nicht alle Möglichkeiten aus. Hier gibt es noch viel Spielraum.

Ich würde mich freuen, wenn es Interesse gibt, das Projekt nachzubauen oder auch an der Entwicklung mitzuarbeiten, und werde entsprechende Vorhaben nach Möglichkeit unterstützen.

Ressourcen

Alle technischen Details sind auf GitHub verfügbar : smlaage/GewitterMonitor_v3: Lightning detection system with ESP32 (github.com)

Spezifische Links:

Schaltplan für das Hauptmodul (PDF): GewitterMonitor_v3/GewitterMonitor_v3_Schema.pdf at main · smlaage/GewitterMonitor_v3 (github.com)

Schaltplan für das Antennen-Modul (PDF): GewitterMonitor_v3/GewitterMonitor_v3_Antenna_Schema.pdf at main · smlaage/GewitterMonitor_v3 (github.com)

Stückliste (Excel): GewitterMonitor_v3/GewitterMonitor_v3_PartList.xlsx at main · smlaage/GewitterMonitor_v3 (github.com)

Arduino Source-Code: GewitterMonitor_v3/gewitter_monitor_v3_esp32 at main · smlaage/GewitterMonitor_v3 (github.com)

Gerber- und Bohr-Files für die beiden Platinen: GewitterMonitor_v3/PCB at main · smlaage/GewitterMonitor_v3 (github.com)

STL-Files für den 3D-Druck: GewitterMonitor_v3/3D-Printing at main · smlaage/GewitterMonitor_v3 (github.com)

XY-Plotter

Der Corona-Lockdown bedeutet mehr Zeit zuhause und im Home-Office. Was also machen mit der vielen Zeit? Ich habe eine alte Idee ausgegraben, der Selbstbau eines XY-Plotters, also einer Maschine, die einen Stift über Papier führt und so Zeichnungen produziert.

Natürlich kann jeder Laser- oder Inkjet-Drucker beliebige Bilder mit hoher Qualität ausdrucken. Es reizte mich aber, Zeichnungen mit einem geführten Stift maschinell zu erstellen. Die Resultate sind Unikate, die von den Eigenschaften von Stift und Papier abhängen. Und mir gefiel die selbst gestellte Aufgabe als eine spannende Herausforderung.

Dieses Projekt begann im ersten Corona-Lockdown im März 2020 und hat bis heute einen sehr schön funktionierenden Prototyp und allerlei interessante Zeichnungen ergeben. Eine Galerie findet sich am Ende dieses Beitrags.

Inhalt

Das Konzept

Die Aufgabe eines Plotters ist es, einen Stift zu führen, so dass er Spuren auf dem Papier hinterlässt. Ein Bild kommt aber erst im Zusammenspiel mit einem PC zustande, der die passenden Zeichenanweisungen erzeugt und an den Plotter sendet. Dazu stellt der Plotter Funktionen zum Zeichnen von Linien, Rechtecke, Kreise, Ellipsen, Kreisbögen und mehr bereit, beherrscht sogar einen einfachen Zeichensatz für Texte. Es gibt eine (stetig wachsende) Befehlstabelle (siehe hier). Die Verbindung zwischen PC and Plotter kann über die serielle USB-Schnittstelle oder über das WLAN erfolgen.

Die Zeichnungen können auf dem PC mit beliebiger Software erstellt werden. Ich verwende hauptsächlich Python-Skripts, die ansprechende Grafiken produzieren. Dabei beschäftige ich mich auch mit der Umwandlung von Fotos in Vektor-Zeichnungen im SVG-Format, die dann vom Plotter zu Papier gebracht werden – ein weites Experimentierfeld.

Plotter in action

Die Mechanik

Das Design ist schnell beschrieben. Es gibt eine X- und eine Y-Achse, die mit Schrittmotoren betrieben werden. Die Mechanik basiert auf dem von 3D-Druckern bekannten Verfahren. Die Achsen werden aus Linearwellen (polierter Rundstahl mit 8 mm Durchmesser) aufgebaut, auf denen Schlitten mit Linearlagern laufen. Die horizontale X-Achse ist doppelt ausgeführt und bewegt zwei gegenüberliegende Schlitten, die die vertikale Y-Achse tragen. Auf der Y-Achse bewegt sich ein Schlitten mit dem Stifthalter. Die Positionierung erfolgt über Zahnriemen.

Der Plotter im Überblick

Der Zeichenstift ist an einer Wippe befestigt, die mit einem Modellbau-Servo gehoben oder gesenkt wird. Der Anpressdruck auf dem Papier wird mit einer Feder angepasst. Der Stifthalter kann beliebige Stifte aufnehmen, allerdings nur einen Stift zur Zeit. Mehrere Farben benötigen also einen manuellen Stiftwechsel. Das Papier wird mit Magneten fixiert, so dass es schnell gewechselt werden kann. Die bei professionellen Plottern verbreitete Fixierung mit Unterdruck erschien mir für den Selbstbau zu aufwendig.

Überall kommen Linear- und Kugellager zum Einsatz, so dass die Mechanik weitgehend spielfrei funktioniert. Die benötigten Komponenten sind bei den üblichen Online-Händlern zu akzeptablen Preisen verfügbar.

Konstruktion der Bauteile mit FreeCAD

Die benötigten Konstruktionsteile kommen aus dem 3D-Drucker. Zur Konstruktion verwende ich durchgängig FreeCad und zur Herstellung der Teile meinen bewährten Dremel 3D40.

Detail-Ansichten

Die Auswahl eines geeignetes Stifts ist nicht ganz unproblematisch. Beim Zeichnen können durchaus lange Strecken zusammenkommen, so dass sich der Stift schnell abnutzt. Ich habe gute Erfahrungen mit Tintenrollern oder Gelrollern gemacht.

Die Elektronik

Im Zentrum der Maschine werkelt ein ESP32. Er treibt die Schrittmotoren XA, XB und Y mit Hilfe der üblichen A4988 Treiber-Module, die als Breakout-Board verfügbar sind. Ich verwende den 8tel-Schritt-Modus (MS1 und MS2 liegen auf +3.3V). Die Enable-Leitungen aller 3 Treiber sind zusammengefasst. Der Prozessor kann damit alle Motoren ein- oder ausschalten. Jeder Motor-Treiber hat einen Step- und einen Dir-Eingang, die unabhängig voneinander angesteuert werden. Für jede Achse gibt es einen Endtaster, der nach dem Einschalten angefahren wird (“Referenzfahrt”), um eine definierte 0-Position zu finden.

Weiterhin gibt es ein alphanumerisches LCD-Display, das über das I2C-Interface betrieben wird. Das Display hat 4 Zeilen mit jeweils 20 Zeichen und gibt Status-Informationen aus.

Das System beinhaltet 3 Taster, mit denen der Anwender den Ablauf beeinflussen kann:

  • Taster 0 (“Stop”, grün) hält die Ausführung an bzw. setzt sie fort.
  • Taster 1 (“Standby”, blau) schaltet den Motostrom ab. Sobald der Plotter irgendein Zeichenkommando bekommt, werden die Motoren wieder eingeschaltet.
  • Taster 2 (“Pen Up/Down”, gelb) hebt oder senkt den Stift. Das ist hilfreich zum Einsetzen eines Stifts.
Die drei Taster befinden sich unterhalb vom LC-Display

Die Schaltung arbeitet mit 3 verschiedenen Spannungen: Sie wird von einem externen Netzteil mit 12V versorgt. Die Spannung dient direkt zum Treiben der Motoren (Vmot). Ein Spannungsregler auf der Platine erzeugt daraus 5V (Vdd) für die Stromversorgung von ESP32, LCD-Display und Stift-Servo. Der ESP32 trägt auf seinem Board einen 3.3V Spannungsregler (Vcc). Diese Spannung wird außerdem für die Versorgung der Logik der Schrittmotortreiber verwendet

Der Schaltplan für den Plotter. Links oben die Spannungsregelung (12 -> 5V). Links der ESP32. Oben Mitte das LC-Display. In der Mitte die drei Motortreiber mit A4988. Unten Mitte die Anschlüsse für die Endschalter, den Servo und die Taster.

Die Schaltung ist im Laufe der Zeit auf einer Lochrasterplatine im Prototyp-Stil “gewachsen”. Das Resultat ist nicht schön, funktioniert aber zuverlässig. Bei Gelegenheit kann man dafür eine Platine anfertigen.

Der Aufbau der Platine ist historisch gewachsen …

Ursprünglich war der Plotter als horizontaler “Flachbett-Plotter” konzipiert. Aus Platzgründen in meinem Home-Office habe ich ihn dann aber auf ein Gestell gesetzt, so dass er mit etwa 60 Grad Neigung im Bücherregal steht. Leider reicht es jetzt nicht mehr ganz für das A3-Papierformat.

Der Plotter steht auf einem Gestell mit etwa 60 Grad Neigung und passt so ins Bücherregal.

Die Software

Wie so oft bei diesen Projekten benötigt die Software den größten Arbeitsaufwand. Die “Firmware” für den ESP32 habe ich mit der Arduino-IDE entwickelt. Sie übernimmt die Steuerung der Motoren und des Servos für den Stift und kann eine Reihe von Grafik-Primitiven ausführen. Eingaben kommen über die serielle Schnittstelle oder das WLAN und landen in einem internen Puffer, der sukzessive abgearbeitet wird.

Die Schrittmotoren werden über einen Timer-Interrupt gesteuert. Der Interrupt beherrscht den Bresenham-Algorithmus, um zwei beliebige Punkte mit einer geraden Linie zu verbinden, was die Grundlage für alle Zeichenfunktionen bildet. Die Motoren werden mit einer Rampe auf Geschwindigkeit gebracht und vor dem Ziel wieder abgebremst. So kann der Plotter den Schlitten relativ schnell bewegen, ohne Schritte zu verlieren. Das klappt tatsächlich ausgesprochen gut. Die Logik unterscheidet zwischen Bewegungen mit gehobenem (schnell) und gesenktem Stift (einstellbare Geschwindigkeit, normalerweise langsamer). Weitere Anforderungen an das Timing müssen beachten werden. Zum Beispiel wird nach dem Befehl pen down für einen kurzen Moment gewartet, bis der Stift auf dem Papier angekommen ist.

Der Eingabepuffer nimmt mit 70 kB den größten Teil des verfügbaren RAM-Speichers auf dem ESP32 in Anspruch. Die Steuerung der Motoren und das Abarbeiten des Puffers geschieht vollständig im Hintergrund. Die Schnittstellen werden auch während des Plot-Betriebs bedient. Das sendende Programm muss allerdings abfragen, ob genügend Platz für weitere Anweisungen im Puffer vorhanden ist. Dazu dient das Kommando F, das den aktuell verfügbaren Speicherplatz zurück gibt.

Für die WLAN-Verbindung verwende ich die Socket-Kommunikation, die an anderer Stelle beschrieben wurde. Bei der ersten Inbetriebnahme muss der Anwender das Netzwerk über die serielle Schnittstelle auswählen und das zugehörige Passwort eingeben. Die Einstellung einer statischen Adresse ist möglich. Die Daten werden im Flash-Speicher abgelegt, so dass sich der Plotter beim nächsten Einschalten direkt und ohne weitere Eingaben mit dem Netzwerk verbindet.

Das LC-Display zeigt einige System-Parameter im laufenden Betrieb.

Die Software ist ein umfangreiches Projekt und hat in der aktuellen Version sicherlich noch einen experimentellen Charakter, läuft aber stabil und produziert schon sehr ansehnliche Bilder.

Protokollarisches

Wie bringt man den Plotter dazu, etwas zu zeichnen? Dafür verwende ich ein einfaches Protokoll. Die Syntax ist angelehnt an die Scalable Vector Graphics-Sprache. Jede Anweisung besteht aus einem Buchstaben, der die Art des Befehls kennzeichnet, gefolgt von Parametern, z.B. Koordinaten. Die Parameter werden durch Komma getrennt. Leerzeichen werden ignoriert. Eine Anweisung wird durch ein Semikolon oder das Zeilenende abgeschlossen.

Der Plotter verwendet ein X/Y-Koordinatensystem. Der Nullpunkt ist unten links und wird nach dem Einschalten angesteuert. Ein Schritt entspricht 0,025 mm, was eine recht hohe Auflösung für diese Anwendung ist. Auch mit sehr feinen Stiften lassen sich keine Treppenstufen erkennen. Zur Zeit gibt es 37 Befehle. Hier ist ein Ausschnitt der Tabelle.

Ausschnitt aus der Befehlstabelle. Die Tabelle umfasst derzeit 37 Anweisungen.

Ein einfaches Beispiel: Die hier gezeigte Sequenz zeichnet ein Smiley, bestehend aus Kreisen, Ellipsen, Kreisbögen und geraden Linien, die ein Dreieck bilden. Die Anweisung Z (Zeile 4) schließt den Polygon-Zug, wobei der Stift an die letzte durch ein M angesteuerte Position zurückkehrt.

Smiley (Bleistift auf Zeichenkarton)

Es gibt auch Anweisungen zum Zeichnen von Text. Der Plotter hat einen eingebauten Zeichensatz, der allerdings noch recht eingeschränkt ist und im Wesentlichen die unteren 80 Zeichen der ASCII-Tabelle enthält.

Zeichensatz

Zeichenprogramme

Der Plotter ist ein Werkzeug, das Anweisungen empfängt und den Stift bedient. Eine ansprechende Grafik besteht aber aus sehr vielen Anweisungen und kommt in der Regel von einem Programm auf dem PC. Ich verwende verschiedene Python Programme, die mit unterschiedlichen Algorithmen Grafiken produzieren oder Fotos in Zeichnungen umsetzen.

Zum Testen des Plotters und der Verbindung eignet sich eine kleine Python-Anwendung, die manuelle Eingaben per Tastatur entgegennimmt und über die WebSocket-Schnittstelle an den Plotter sendet.

Das Skript verbindet sich mit dem WebSocket-Server auf dem Plotter und öffnet eine interaktive Session, in der man die einzelnen Grafik-Befehle ausprobieren kann.

Die Python-Programme, mit denen ich meine Grafiken erstelle, produzieren eine Liste von Zeichenanweisungen, die als Zwischenschritt erst einmal in einer Datei landen. Bei mir heißt diese Datei meistens plot_file.plt. Diese Datei wird dann mit einem Python-Skript zum Plotter gesendet. Dabei muss das Skript regelmäßig den verfügbaren Pufferplatz auf dem Plotter abfragen und darf die Daten nur bei ausreichend Platz senden. Ein sehr einfaches Programm für diese Aufgabe ist hier gezeigt. Dieses Skript hält den Transfer an, wenn der freie Pufferplatz unter 1 kB fällt und startet den Transfer, wenn er über 10 kB ansteigt:

Zum Abschluss ein kurzes Python-Programm, das eine einfache Zeichnung erstellt und in der Datei plot_file.plt ablegt. Die Computer-Grafiken der 80er Jahre feiern hier ein Revival.

Das Ergebnis des Skripts “nested_check.py”

Die Python-Unterstützung des Plotters ist im Moment noch rudimentär und wird im Laufe der Zeit weiter entwickelt. Auch die Erstellung von Grafiken mit Python-Skripts ist ein weites Feld. Zu gegebener Zeit werden weitere Beispiele folge.

Fazit

Spielereien mit Computer-Grafik haben mich schon immer fasziniert. Der Bau eines Plotters ist ein spannendes Projekt. Die mit dem Stift gezeichneten Ergebnisse habe einen eigenen Reiz.

Der Eigenbau eines solchen Gerätes wird durch die Verfügbarkeit von 3D-Druck und leistungsfähiger Hardware-Komponenten erst möglich. Es ist erstaunlich, welche Präzision sich in der heimischen Werkstatt erreichen lässt. Der ESP32 hat mich mit seiner Leistungsfähigkeit wieder einmal beeindruckt. Die Verwaltung der Motoren mit schnellen Interrupt-Folgen parallel zur Bedienung des Eingabepuffers, LCD, Stift-Servo und Netzwerk sind eine anspruchsvolle Aufgabe, die den ESP32 aber noch lange nicht an seine Grenzen bringt.

Galerie

Baum – entstanden aus einer rekursive Funktion mit zufälligen Variationen
Bäume – rekursive Funktion in Python
dat10 – Drehende gekoppelte Scheiben
dat07 – Noch mehr Drehungen
Pusteblume – rekursive Strukturen im Kreis
Sailboat – Konvertierung eines Handy-Fotos in eine SVG-Grafik mit Nachbearbeitung in Python

Ressourcen

WebSocket-Kommunikation mit dem ESP32 – Eine Wetterstation mit BME280

In den letzten Monaten hat sich der ESP32 zu einem Arbeitspferd für viele Projekte entwickelt. Zum einen ist es ein leistungsfähiger Prozessor mit beachtlicher Busbreite und Taktrate. Zum anderen ist die Kommunikation via WLAN eine sehr praktische Angelegenheit.

Es muss aber nicht immer eine Web-Server sein, der, wie in vielen Tutorials gezeigt, HTML produziert. In diesem Projekt gehe ich auf einen tieferen Level und möchte zeigen, wie man mit der WebSocket-Schnittstelle eine Datenverbindung mit geringem Overhead aufbauen kann. WebSocket bietet eine zuverlässige Verbindung, bei der Nachrichten nicht verloren gehen und in der korrekten Reihenfolge übermittelt werden. Der Komfort steht dem einer seriellen Schnittstelle in nichts nach … ganz ohne lästiges Kabel!

Die Idee

Als Beispiel-Anwendung habe ich mir den Sensor BME280 herausgesucht, der Luftdruck, Luftfeuchtigkeit und Temperatur misst und die Daten per I2C zur Verfügung stellt. Die Daten werden vom ESP32 ausgelesen, der sie dann als WebSocket-Server für Clients abrufbar macht. Bei der Gelegenheit habe ich noch zwei Leuchtdioden an den ESP32 angeschlossen, die über den Client geschaltet werden können. Als Beispiel für einen Client gibt es hier eine GUI-Anwendung mit Python und TKinter, die zum Beispiel auf einem Laptop oder einem Raspberry Pi läuft. Die Verbindung über das serielle Interface und den WebSocket werden exakt gleich behandelt und können vom ESP32 auch gleichzeitig bedient werden. Soweit das Konzept.

Der ESP32 empfängt die Daten vom Sensor (BME280) (rechts) und stellt sie über die serielle Schnittstelle und über den WebSocket-Server zur Verfügung. Als WebSocket-Client dient ein Python-Programm auf einem Computer (links).

Die Schaltung

Die Schaltung ist auf einem Steckbrett schnell zusammengebaut. Für den BME280-Sensor gibt es ein praktisches Breakout-Board, das Masse, Vcc und die beiden I2C-Leitung SCL und SDA herausführt. Die I2C-Leitungen bekommen Pull Up-Widerstände, die den Ruhepegel auf Vcc ziehen. Auf dem Steckbrett befindet sich noch eine zweifarbige LED mit gemeinsamer Kathode, die den Status der Netzwerkverbindung anzeigt. Schließlich gibt es noch zwei Leuchtdioden, die an Ports des ESP32 gelegt sind.

Schaltplan des ESP32 mit dem Sensor BME280 und einigen Leuchtdioden
Aufbau auf einem Steckbrett

BME280

Der Bosch-Sensor ist bereits kalibriert und stellt die Daten in digitaler Form bereit, was die Anwendung enorm vereinfacht. Ich verwende die Arduino-Bibliothek BME280 von Tyler Glenn, die schon in der Version 3.0 vorliegt. Der Sensor wird nach dem Laden der Include-Datei als globales Objekt eingerichtet. Die Funktionen zum Auslesen von Luftdruck, Temperatur und Luftfeuchtigkeit können direkt aufgerufen werden und liefern die Ergebnisse als Float-Variable.

Verbindungsaufbau mit dem Modul web_socket

Bei meiner Arbeit mit dem ESP32 habe ich ein Software-Modul web_socket entwickelt, das den Verbindungsaufbau übernimmt und Funktionen zum Lesen und Schreiben der Daten zur Verfügung stellt. Es besteht aus den Dateien web_socket.cpp und web_socket.h und verwendet seinerseits die WebSockets-Bibliothek für Arduino (GitHub – Links2004/arduinoWebSockets: arduinoWebSockets), die gegebenenfalls in der Arduino-Umgebung installiert werden muss.

Eine der Aufgaben von web_socket ist der Aufbau der Verbindung zum WLAN-Router. In vielen Beispielen im Internet wird dabei der Namen des Netzwerks (SSID) und das Passwort in den Source-Code eingebettet. Das ist für Experimente praktisch, für reale Anwendungen aber nicht tragbar. Deshalb bietet das Modul web_socket die Möglichkeit, die Netzwerk-Credentials per Dialog über die serielle Schnittstelle abzufragen und im nichtflüchtigen Speicher (preferences) abzulegen. Beim nächsten Start klappt der Verbindungsaufbau dann ohne weitere Angaben. Die Schaltung muss also nur bei der ersten Inbetriebnahme an der seriellen Schnittstelle angeschlossen sein. Außerdem gibt es die Möglichkeit, eine statische IP-Adresse anzugeben, so dass der WebSocket-Server immer unter einer festen Adresse erreichbar ist.

Auf dem PC kann die serielle Schnittstelle z.B. mit dem seriellen Monitor der Arduino-Umgebung oder einem der vielen Terminal-Programme bedient werden. Der Dialog zur Auswahl des Netzwerks und zur Abfrage des Passwords sieht so aus:

Dialog über die serielle Schnittstelle zur Auswahl des Netzwerks und Eingabe der Credentials

Hier eine kurze Beschreibung, wie das Modul web_socket in der Praxis eingesetzt wird. Es enthält die Klasse Socket. Ein Objekt dieser Klasse wird im Anwenderprogramm als globale Variable erstellt.

Das Objekt stellt alle Funktionen zum Aufbau der Verbindung und zur Kommunikation mit dem Client bereit. Zuerst wird die Methode begin() aufgerufen. Damit werden die Netzwerk-Daten aus dem nichtflüchtigen Speicher kopiert, sofern sie dort vorhanden sind. begin() meldet true zurück, wenn das der Fall ist. Für den Verbindungsaufbau zum WLAN gibt es die Methode connect(), und zwar in drei Varianten.

  1. connect() ohne Argumente verwendet die vorhanden Netzwerkdaten aus dem nichtflüchtigen Speicher.
  2. connect(ssid, password) stellt eine Verbindung für das angegebene Netzwerk her, wobei die IP-Adresse dynamisch vom Router zugewiesen wird.
  3. connect(ssid, password, static_ip, gateway_ip) stellt eine Verbindung mit einer statischen IP-Adresse her. Das kann natürlich nur dann funktionieren, wenn auf dem Router die statische IP-Adresse auch verfügbar ist.

Wenn eine Verbindung erfolgreich aufgebaut ist, werden die Netzwerk-Daten in den nichtflüchtigen Speicher kopiert und stehen dort für den nächsten Systemstart zur Verfügung.

Die manuelle Auswahl des Netzwerkes mit dem oben gezeigten Dialog kann mit der Methode select() ausgelöst werden. All das kann in der Arduino setup()-Funktion komfortabel erledigt werden, z.B. so:

Senden und Empfangen

Jetzt können eingehende Nachrichten mit read_chararray(char buf[], int size) gelesen oder ausgehende Nachrichten mit send(char buf[]) gesendet werden. Um die Sache einfach zu halten, beschränke ich mich auf Text-Nachrichten von maximal 256 ASCII-Zeichen. Das ist für die kleinen Datenmengen, um die es hier geht, völlig ausreichend und hat den angenehmen Nebeneffekt, dass der Programmierer bei Bedarf mitlesen kann.

Das Modul web_socket enthält einen internen Puffer (2048 Bytes), der mehrere eingehende Nachrichten aufnehmen kann. Die Methode available() gibt die aktuelle Zahl der Zeichen im Eingangspuffer aus.

Hier ist eine Übersicht der Methoden des Objekts Socket:

Wichtig ist, dass die Methode run_socket_loop() regelmässig aufgerufen wird. Diese Funktion bedient die webSocket.loop(), die benötigt wird, um auf Ereignisse zu reagieren. In meinen Anwendungen wird run_socket_loop() mehrmals pro Sekunde aufgerufen. So werden timeout-Fehler auf der Client-Seite vermieden.

Gesprächsbedarf: Das Protokoll

Für die Kommunikation zwischen Client und Sever verwende ich bei meinen Projekten ein einfaches Protokoll. Dabei sendet der Client eine Anfrage an den Server. Diese Anfrage beginnt immer mit einem “Command-Token”. Das ist ein ASCII-Zeichen, das die Art der Anfrage kennzeichnet. Gegebenenfalls können zusätzliche Daten folgen. Der Server antwortet dann mit den gewünschten Daten und/oder führt die jeweilige Aktion aus, z.B. An- oder Ausschalten einer LED. Ich habe mir angewöhnt, in jedem Fall eine Antwort zum Client zu senden, auch dann, wenn eigentlich kein Bedarf für eine Rückmeldung besteht. So kann der Client sicherstellen, dass er “gehört” wurde.

Das Protokoll für den Datenaustausch ist für das konkrete Beispiel sehr einfach. Es gibt die folgenden Command-Token:

Die Arduino loop()-Funktion des EPS32 hat die Aufgabe, das serielle Interface und den WebSocket-Server ständig abzufragen, ob eine Nachricht angekommen ist. Wenn das der Fall ist, wird die einkommende Nachricht in der Funktion process_input() bearbeitet, also z.B. der Luftdruck-Wert gelesen und oder eine der LEDs geschaltet. Die booleschen Variablen net_input und ser_input merken sich, aus welchem Kanal die Eingabe kam, und verwendet diese, um die Antwort an den richtigen Kanal zurück zu senden.

Der Luftdruck bekommt eine zusätzliche Behandlung: Die eigentlich interessante Information ist die zeitliche Entwicklung. Deshalb wird alle 10 Minuten der Luftdruck gemessen und in einem Array float pres_trend[] abgelegt. Dort ist Platz für 30 Werte, also die Daten der letzten 5 Stunden. Die Daten können mit dem Command-Token “D” abgerufen werden. Daraus produziert der Client eine kleine Grafik zum zeitlichen Verlauf.

Das Protokoll kann mit dem seriellen Monitor der Arduino-IDE sehr schön ausprobiert werden. Die ASCII-String-Kommunikation macht es möglich. Hier eine Beispiel-Session mit dem seriellen Monitor der Arduino-IDE:

Das Protokoll wird an der seriellen Schnittstelle ausprobiert. Die Pfeile markieren die Eingaben von der Tastatur.

WebSocket Client

Für erste Tests verwende ich ein einfaches Python Skript, das auf einem PC oder Raspberry Pi läuft. Der PC und der ESP32 müssen in demselben Netzwerk angemeldet sein, so dass sie sich im Netzwerk “sehen” können. IP-Adresse und Port-Nummer des Servers müssen natürlich bekannt sein, in meinem Fall 192.168.1.210 und Port 90.

Auf der Python-Seite verwende ich das Modul websocket, das gegebenenfalls mit pip nachinstalliert werden muss. Das Skript verbindet sich mit dem WebSocket-Server, wartet auf einer User-Eingabe, sendet diese an den Server und zeigt die Antwort. Natürlich kann es immer die Situation geben, dass der Server nicht erreichbar ist (z.B. abgeschaltet oder hat eine andere IP-Adresse). Dafür gibt es eine Fehlerbehandlung mit try … except.

Jetzt sind die Dienstleistungen des WebSocket-Servers genau wie mit der seriellen Schnittstelle abrufbar. Hier eine Beispiel-Session mit der Python-Shell:

Zum Abschluss: Eine GUI-Applikation mit Wetter-Daten

Nachdem die Verbindung zuverlässig und stabil ist, kann man mit etwas Python und TKinter eine kleine GUI-Applikation zusammensetzen. Das Skript besteht aus nur einem File “bme.py”. Es öffnet ein Fenster auf dem Desktop, platziert alle Widgets und führt ein regelmäßiges Refresh der Daten durch, so dass die Daten immer aktuell sind. Damit die Datenbeschaffung per WebSocket die Antwortzeiten des User-Interface nicht beeinträchtigt, wird sie in einen eigenen Thread ausgelagert, der im Hintergrund läuft und alle 120 Sekunden die aktuellen Werte anfordert. Außerdem gibt es noch eine Konfigurations-Datei bme_config.dat, in der die wichtigsten Parameter, besonders die IP-Adresse und die Port-Nummer bereit liegen müssen. Diese Datei wird beim Start des Python-Skripts eingelesen.

Die Datei bme_config.dat hält die wichtigsten System-Parameter bereit.

Das Ergebnis ist eine kleine “Wetter-App”, die die Daten vom BME280 auf dem Desktop sichtbar macht.

Downloads

Der Source Code für die Arduino-Anwendung und alle hier erwähnten Python-Skripts sind auf GitHub verfügbar. Dort befinden sich auch der Schaltplan und weitere Fotos.

Link: smlaage/esp32_websocket: ESP32 WebSocket Server with Python Client (github.com)

Ultrasonic Modul mit I2C-Interface

Am Schülerforschungszentrum phænovum in Lörrach experimentieren wir mit autonomen Modell-Fahrzeugen, also Fahrzeuge, die weitgehend selbstständig ihren Weg finden. Dieses Thema ist ein wunderschönes Experimentierfeld für Algorithmen.

Hier zeige ich ein Modul mit 5 Abstandssensoren, das die Experimente mit den autonomen Fahrzeugen vereinfachen und unterstützen soll. Die Verwaltung der Sensoren wird von einem separaten Prozessor übernommen und das Ergebnis der Messungen über eine einfache Schnittstelle bereit gestellt. Firmware, Schaltplan, Platinen-Design und Arduino-Bibliothek stehen auf GitHub zur Verfügung.

Die Idee

Die wichtigste Anforderung an ein autonomes Fahrzeug ist, dass es Kollisionen vermeidet. Dazu muss die Umgebung aktiv nach Hindernissen abgesucht werden. Eine einfache Methode bieten die Ultraschall-Sensoren HC-SR04. Sie sind preisgünstig und einigermaßen einfach auszulesen. Die Sensoren werden mit einem kurzen Trigger-Signal aktiviert und antworten darauf mit einem Echo-Impuls. Die Länge des Echos entspricht der Laufzeit des Ultraschall-Signals und ist ein Maß für die Entfernung zum nächsten Hindernis. Zur Auswertung der Antwort muss ein Mikrocontroller (z.B. ein Arduino) die Zeit des Impulses exakt in Mikrosekunden messen.

Die Signalfolge am HC-SR04 mit dem Oszilloskop: Die blaue Kurv zeigt das Trigger-Signal und die gelbe Kurve das Echo. Die Länge des Echo-Impulses ist ein Maß für den Abstand zum Hindernis.

Diese eigentlich einfache Aufgabe kann kompliziert werden, wenn mehrere Sensoren ins Spiel kommen und der Arduino gleichzeitig noch andere Aufgaben hat, z.B. die Motoren drehen, weitere Sensoren auslesen, die Fahrtrichtung bestimmen und mehr. An dieser Stelle soll das hier gezeigte “Ultrasonic Modul” helfen. Es kombiniert 5 Sensoren auf einem Board. Ein ATmega328 ist dazu abgestellt, sich ausschließlich um die Sensoren zu kümmern, wodurch der Hauptprozessor auf dem Fahrzeug entlastet wird. Die Messergebnisse werden über eine I2C-Schnittstelle zur Verfügung gestellt. In der Arduino-Welt ist diese Schnittstelle als Wire bekannt und kann einfach ausgelesen werden.

Die Schaltung

Der Schaltplan ist vergleichsweise simpel. Die 5 Ultraschall-Module (nummeriert von 0 bis 4) werden mit 5V versorgt und liegen mit ihren Trigger- und Echo-Pins direkt an den Ports des ATmega. Der Prozessor wird über einen Low Drop-Spannungsregler mit 3.3V versorgt, so dass das I2C-Interface kompatibel mit dem Spannungslevel von Raspberry Pi oder ESP32 und gleichzeitig 5V tolerant ist. Da der Prozessor mit dem internen Taktgenerator (8 MHz) arbeitet, ist kein Quarz notwendig. Schließlich gibt es noch eine Leuchtdiode, die von der Arbeit des Prozessors berichtet. Die Leitungen für das I2C-Interface werden an eine Steckerleiste herausgeführt. Die Schaltung enthält Pull Up-Widerstände für SDA und SCL, die bei Bedarf bestückt werden können. Alles wird mit 100 nF Blockkondensatoren versehen. Das ist dann auch schon alles.

Die Schaltung ist überschaubar. Der Stecker für den ISP-Adapter (Mitte rechts) ist optional.

Die Platine

Um den Aufbau zu erleichtern, habe ich eine Platine mit KiCad entworfen. Die Sensoren sitzen direkt auf der Platine und zeigen nach links, links vorne, vorne, rechts vorne und rechts. Ob die Winkel der seitlichen Sensoren von 90 und 45 Grad wirklich geeignet sind, wird sich erweisen. Möglicherweise sind kleinere Winkel sinnvoll. Z.B. wären 30 und 60 Grad denkbar.

Auf der Platine ist ein Lötbrücke, mit der die I2C-Adresse verändert werden kann. So können zwei solcher Module an einem Arduino betrieben werden.

Platine im 3D-Viewer. Vorne rechts befindet sich der 6-polige Stecker für den ISP-Programmier-Adapter, so dass der ATmega direkt in der Schaltung programmiert werden kann. Wenn kein Bedarf dafür besteht, kann der Stecker einfach unbestückt bleiben
Stückliste

Software

Die Software wurde mit dem Atmel Studio Version 7 entwickelt und wird weitgehend über Interrupts gesteuert. Folgende Interrupts kommen zum Einsatz:

  • Timer 0 (ein 8-Bit Timer) produziert einen regelmäßigen Interrupt, typischerweise mit einem 40 ms Intervall. Das Programm verwendet den Interrupt als Erinnerung (via job_flag), dass es Zeit ist, den nächsten Sensor zu aktivieren und dort ein Trigger-Signal auszulösen.
  • Die Echo-Signale der Sensoren lösen Pin-Change-Interrupts aus. Die entsprechende Interrupt-Routine übernimmt die Messung der Laufzeit des Echo-Signals.
  • I2C-Interrupts werden von der Schnittstelle ausgelöst und bedeuten, dass der Host Daten benötigt. Mehr dazu weiter unten.
  • Schließlich gibt es noch Timer 1 (ein 16-Bit Timer), der zwar keinen Interrupt auslöst, aber mit einem Vorteiler (Prescaler) von 8 die Zeit in Mikrosekunden misst.

Ein Herzstück des Programms ist der Pin-Change-Interrupt, der vom Echo-Impuls der Sensoren ausgelöst wird. Zur Steuerung der Auswertung gibt es eine Variable echo_flag, die den Status des zugehörigen Sensors festhält. Wenn das Hauptprogramm einen Trigger setzt, wird echo_flag für den entsprechenden Sensor auf den Wert 1 gesetzt. So hat die Interrupt-Routine die Information, dass von diesem Sensor ein Echo-Impuls zu erwarten ist. Der Pin-Change-Interrupt wird sowohl von steigenden als auch von fallenden Flanken aktiviert. In der Interrupt-Routine muss untersucht werden, (1) welcher Sensor den Interrupt ausgelöst hat und (2) ob es sich um eine ansteigende oder eine fallende Flanke handelt. Zu (1) wird die beschrieben Variable echo_flag ausgewertet. Für (2) wird der aktuelle Wert des Ports abgefragt. Ist dieser high, dann war es eine ansteigende Flanke, und der aktuelle Wert des Timer 1 wird als Startzeit abgelegt. Außerdem wird der Wert des echo_flag auf 2 erhöht. Damit bekommt die Interrupt-Routine die Information, dass für diesen Sensor eine fallende Flanke zu erwarten ist. Ist das echo_flag also 2 und zeigt der zugehörige Port den Pegel low, dann war es offensichtlich eine fallende Flanke. Jetzt wird die Laufzeit aus der Differenz des aktuellen Timer-Werts und der Startzeit errechnet. Dabei muss ein möglicher Überlauf des Timers berücksichtigt werden. Der zugehörige Source-Code sieht so aus:

Die vollständige Software besteht aus 3 Dateien.

  • main.c enthält das Hauptprogramm mit der Verwaltung der Sensoren
  • TWI_slave.c und TWI_slave.h enthalten die Logik für das I2C-Slave-Interface. Ich verwende den offiziell von Microchip publizierten Source-Code (siehe AVR311), der den I2C-Slave-Mode auf dem ATmega bereit stellt.

Alle Dateien müssen im Atmel-Studio in das Projekt eingebunden werden, damit die Kompilierung klappt. Der kommentierte Source-Code mit weiteren Details befindet sich auf GitHub.

Gelegentlich bekomme ich Fragen zur Programmierung der ATmega und ATiny-Chips: Der ATmega arbeitet hier ohne die Arduino-Umgebung und hat deshalb auch kein USB-Interface an Board. Die Software muss mit einem der üblichen Programmier-Adapter auf den Prozessor kopiert werden. Ich arbeite seit vielen Jahren mit einem Atmel AVRISP mkII. Aber es gibt auch andere. Mehr dazu unter AVR In System Programmer – Mikrocontroller.net.

Die Platine trägt einen Stecker für den AVRISP Programmierer, so dass der ATmega direkt in der Schaltung “geflasht” werden kann. Das ist sehr nützlich, wenn die Software weiter entwickelt werden soll.

I2C Interface

Das Modul ist als I2C-Slave unter der Adresse 0x5A (Lötbrücke an Port B2 offen) oder 0x5B (Port B2 über Brücke auf Masse gelegt) erreichbar.

Der Host (z.B. ein Arduino) muss das Modul ansprechen, um eine Antwort zu bekommen. Das geschieht, indem ein Byte gesendet wird. Wir nennen es “Register”. Als Antwort auf Register-Werte zwischen 0 und 4 liefert das Modul die aktuelle gemessene Entfernung des entsprechenden Sensors in Millimetern. Das ist ein 16-Bit-Wert. Die Sensoren sind im Uhrzeigersinn von links durchlaufend nummeriert. Der Sensor vorne in der Mitte hat also die Nummer 2.

Darüber hinaus gibt es drei Register, die die Arbeitsweise des Moduls beeinflussen. Das Register ist der untere Nibble. Die eigentlichen Daten werden als oberes Nibble übertragen :

  • set_status (unteres Nibble = 10) kann die Entfernungsmessungen anhalten (oberes Nibble == 0) oder wieder starten (oberes Nibble > 0).
  • set_cycle_time (unteres Nibble = 11) setzt die Zeit (oberes Nibble mit dem Wert 1 bis 15) zwischen zwei aufeinanderfolgenden Messungen in 10 ms. Der Default-Wert ist 4 (entspricht 40 ms). Kleinere Werte ergeben schnellere Abfolgen, können aber möglicherwiese dazu führen, dass überlappende Echo-Signale eintreffen, was zu Fehlmessungen führt. Hier ist Raum zum Experimentieren.
  • set_mode (unteres Nibble = 12) definiert die Anzahl der Sensoren und die Sequenz (wir nennen es “Mode”), mit der sie nacheinander abgefragt werden. Das obere Nibble kann folgende Werte annehmen:
  • Mode 0: Sensor 0 -> Sensor 2 -> Sensor 4 -> Sensor 1 -> Sensor 3
  • Mode 1: Sensor 0 -> Sensor 2 -> Sensor 4
  • Mode 2: Sensor 1 -> Sensor 3
  • Mode 3: Sensor 2

Die Werte für Cycle Time und Mode werden im EEPROM abgelegt und bleiben nach dem Abschalten der Spannungsversorgung erhalten.

Einsatz mit Arduino

Das Modul benötigt 4 Anschlussleitungen zum Host: GND, SDA, SCL und Vcc (+5V). Die Spannungsversorgung geschieht über GND und Vcc. Diese Pins sind auf der Platine doppelt ausgeführt, da man in einem Projekt nie genug solcher Anschlüsse haben kann.

Die I2C-Schnittstelle mit den beiden Leitungen SCL (Takt) und SDA (daten) muss mit den entsprechenden Pins am Arduino verbunden werden. Z.B. beim Arduino Uno oder Nano sind es A5 für SCL und A4 für SDA.

Es gibt eine kleine Arduino-Bibliothek, die alle Funktionen des Moduls zur Verfügung stellt.

Ein einfaches Beispiel für ein Anwendungs-Programm ist hier gezeigt:

Fazit

Testlauf an einem Arduino Nano, der die Messdaten auf einem LCD-Display (ebenfalls per I2C) anzeigt.

Das Ultrasonic Modul macht den Einsatz von mehreren Ultraschall-Abstands-Sensoren so einfach wie möglich. Das Modul lässt sich gut in Arduino-Systeme integrieren, arbeitet aber auch mit einem Raspberry Pi zusammen. Ob es tatsächlich für das autonome Steuern von Fahrzeugen hilfreich ist, wird sich noch erweisen müssen. Mehr davon zu gegebener Zeit …

Update vom17. März 2021

In der ersten Version der Software hatte sich ein Bug versteckt, der dazu führte, dass das Modul nach kurzer Zeit keine sinnvollen Daten mehr lieferte. Das I2C-Slave-Interface blieb am “Busy-Flag” hängen. Der Fehler ist jetzt behoben. Inzwischen gibt es auch ein Fahrzeug, dass mit dem Modul seine Runden dreht und auch bei längeren Touren zuverlässige Daten bekommt.

Prototyp-Fahrzeug mit dem Ultrasonic-Modul

Ressourcen

GitHub – smlaage/Ultrasonic-I2C-Module: A module comprising 5 ultrasonic distance sensors and I2C interface

Erste Schritte mit dem Prototyp auf Lochrasterplatte …

Komfortable Tasten-Matrix mit I2C-Schnittselle

Viele Projekte mit Mikrocontroller, Arduino oder Raspberry Pi verwenden eine kleine Tasten-Matrix mit Zahlen und einigen zusätzlichen Funktionen für Anwender-Eingaben. Üblicherweise sind es 12 oder 16 Tasten, die als Matrix, also mit Anschlüssen für 3 oder 4 Spalten und Reihen geschaltet sind.

Tasten-Matrix mit 16 Tasten

Ich verwende für meine Projekte gerne eine 4×4-Matrix mit 16 Tasten. Der hier vorgestellte Ansatz funktioniert aber gleichermaßen mit Folien-Tastaturen, auch dann, wenn sie über weniger Tasten verfügen. Die einzige Voraussetzung ist, dass die Tasten elektrisch als Matrix angeordnet sind, also jede Taste bei Betätigung eine Spalte mit einer Zeile verbindet.

Folien-Tastaturen mit 12 bzw. 16 Tasten

Für diese Tastaturen gibt es Bibliotheken für Arduino oder Raspberry Pi, die den Anschluss relativ einfach ermöglichen. Ein Nachteil ist aber, dass bei einer 4×4-Matrix 8 Port-Anschlüsse gebraucht werden. Wenn der Mikrocontroller noch andere Aufgaben zu erledigen hat, dann kann es eng werden. Außerdem benötigt die Bibliothek einen Timer-gesteuerten Interrupt, um die Tasten auszulesen. Dieser Interrupt kann mit anderen Funktionen eines Anwenderprogramms in einen Konflikt geraten.

Treiber-Schaltung für die Tasten-Matrix

Um diesen Problemen entgegen zu wirken, habe ich einen anderen Ansatz gewählt. Die Tastatur bekommt einen eigenen Mikrocontroller, einen ATmega328, der sich vollständig um die Tasten kümmert und die Daten über eine I2C-Schnittstelle dem Anwendersystem, wir nennen es den “Host”, zur Verfügung stellt. Die Tasten-Matrix ist dabei ein I2C-Slave. So werden auf dem Host (fast) keine Ressourcen für die Matrix benötigt.

Sicherlich hätte man dieses Projekt auch mit einem Arduino, z.B. einem Nano aufbauen können. Aus meiner Sicht wäre das aber ein unangemessener Aufwand für diese einfache Aufgabe. Der Nano ist größer, teurer und benötigt mit seiner Peripherie mehr Strom als ein “nackter” ATmega. Deshalb habe ich mich für die kleine Lösung entschieden.

Die Schaltung benötigt nur sehr wenig Hardware. Ein ATmega328 kostet zur Zeit weniger als 2 Euro, so dass die Kosten gering bleiben. Er läuft mit seinem internen 8 MHz-Taktgenerator, so dass kein externen Quarz benötigt wird. Wenn man schon einmal dabei ist, kann der ATmega noch ein paar zusätzliche Aufgaben übernehmen. Meine Schaltung bekommt einen kleinen Piezo-Schallwandler, um Tasten-Klicks zu erzeugen. Außerdem habe ich vier LEDs vorgesehen, die über die I2C-Schnittstelle ein- oder ausgeschaltet werden können. Der Piezo-Buzzer und die Leuchtdioden sind natürlich optional und können auch weggelassen werden.

I2C-Keyboard-Circuit.jpg
Die Schaltung benötigt nur wenige Bauteile:
– Die Tastenmatrix kommt an Port D, Bits 0-7.
– An Port B, Bit 0, ist der optionale Piezo-Buzzer.
– Am Port C, Bits 0-3 befinden sich bis zu 4 LEDs, die ebenfalls optional sind.

Die Schaltung benötigt eine Spannungsversorgung von 3.3 oder 5V. Es werden also 4 Verbindungsleitungen zum Host benötigt: GND, Vcc und die beiden I2C-Leitungen SDA und SCL. Diese beiden Leitungen benötigen einen PullUp-Widerstand nach Vcc, sofern das Host-System nicht bereits über PullUp-Widerstände verfügt. Ich verwende an dieser Stelle gerne 2,2 kOhm-Widerstände. Das I2C-Interface kann mit den üblichen 100 kHz oder im Fast-Mode mit 400 kHz betrieben werden.

Die Schaltung ist schnell auf einem Stück Lochraster-Platine aufgebaut. Ich habe die Platine auf die Größe der Tastenmatrix zurecht geschnitten und mit Abstandshaltern direkt unter die Tastatur geschraubt.

Aufbau der Schaltung auf einem Stück Lochraster-Platine.
– In der Mitte der ATmega328
– Links die 4 Anschlüsse mit I2C-Interface zum Host.
– Unten die 8 Leitungen zur Tasten-Matrix
– Rechts unten der Piezo-Buzzer, rechts zwei LEDs, oben rechts das ISP-Interface zum Programmieradapter

Software mit Komfort-Funktionen

Die Software für den ATmega328 im Zentrum der Schaltung wurde mit dem Atmel-Studio entwickelt. Da der ATmega hier ohne Peripherie arbeitet, gibt es kein USB-Interface, wie wir es vom Arduino kennen. Deshalb benötigt man einen Programmier-Adapter, um die Software auf den Chip zu laden. Ich verwende seit vielen Jahren den AVR-ISP mkII für diese Aufgabe.

Das Programm fragt die Spalten und Zeilen der Tasten-Matrix 20 mal pro Sekunde ab. Wenn eine Tasten gedrückt ist, wird der Tastenwert entprellt und in einen internen Puffer geschrieben. Ein kurzes akustisches Signal vom Piezo-Buzzer bestätigt den Tastendruck.

Das Host-System kann über das I2C-Interface zu jeder Zeit anfragen, ob eine Taste gedrückt wurde. Im Register 0 findet sich der jeweilige noch nicht gelesene Tastenwert aus dem Puffer. Dazu gibt es – wie bei einer normalen Computer-Tastatur – eine Repeat-Funktion. Wenn eine Taste länger als eine Sekunde gerückt ist, wird der Tastendruck 5 mal pro Sekunde erzeugt.

Alternativ gibt es im Register 1 die Möglichkeit, die Tasten vom Host ohne den Puffer direkt abzufragen, z.B. um irgendetwas auf dem Host-System auszulösen, solange eine bestimmte Taste gedrückt ist.

Schließlich können noch die Leuchtioden über das I2C-Interface geschaltet werden oder die Konfigurations-Parameter für Sound und Auto-Repeat gesetzt werden.

Der Quellcode der Software verteilt sich über 6 Dateien, die in das Projekt im Atmel-Studio eingebunden werden müssen. main.c ist das Hauptprogramm. keyboard.c und keyboard.h sind die Funktionen zur Bearbeitung der Tasten-Matrix. twi_slave.c und twi_slave.h kümmern sich um das I2C-Interface. keyboard_layout.h enthält die ASCII-Zeichen für die Tasten.

Der Quellcode verteilt sich über 6 Files

Die I2C-Slave Adresse ist 0x5f. Sie kann in der Software auf jeden anderen gültigen Wert umdefiniert werden (File main.c)

Übersicht der I2C-Funktionen:

  • Register 0: Retrieves the most recent key stroke from the buffer. The keyboard sends the ASCII character of the pressed key or 0 in case there is no pending key stroke in the buffer.
  • Register 1: Retrieves the current key from the keyboard without buffering. This function implicitly clears the buffer.
  • Register 2: Retrieves the number of bytes (= keys pressed) currently in the buffer
  • Register 10: Requires one byte as argument. Sets LED 0 to On (argument > 0) or Off (argument = 0)
  • Register 11: Requires one byte as argument. Sets LED 1
  • Register 12: Requires one byte as argument. Sets LED 2
  • Register 13: Requires one byte as argument. Sets LED 3
  • Register 20: Requires one byte as argument. Sets sound On (argument > 0) or Off (argument = 0)
  • Register 22: Requires one byte as argument. Sets auto repeat to On (argument > 0) or Off (argument = 0)

Die Einstellungen für Sound On/Off und Auto Repeat On/Off werden im EEPROM gespeichert und damit über das Ausschalten hinaus erhalten.

Das Layout der Tasten ist in der Datei keyboard_layout.h hinterlegt. Hier können Anpassungen vorgenommen werden.

Die Datei keyboard_layout.h enthält die ASCII-Zeichen für die entsprechenden Tasten

Anwendung mit Arduino

Jedes Arduino-System, das eine I2C-Schnittstelle hat, kann die Tastatur verwenden. Dazu gibt es die Dateien KeyBoard_I2C.h und KeyBoard_I2C.cpp. Nachdem diese Dateien in den Arduino-Sketch eingebunden sind, stehen alle Funktionen bereit.

  • int check(void); zur Abfrage, ob ein Tastendruck im Puffer enthalten ist.
  • int read(void); liefert einen Tastendruck aus dem Puffer, oder 0 im Fall, dass der Puffer leer ist.
  • int read_direct(void); liest die Tasten-Matrix ohne Puffer aus.
  • void set_led(uint8_t led, bool led_status); setzt oder löscht eine der LEDs
  • void set_dound(bool onoff); schaltet die Funktion des Piezo-Buzzers ein oder aus
  • void set_repeat(bool onoff); schaltet die Auto-Repeat Funktion ein oder aus

Fazit

Die kleine Schaltung vereinfacht den Einsatz von Tasten-Matrizen für Arduino und Raspberry Pi Systeme. Der ATmega arbeitet unauffällig im Hintergrund und entbindet das Host-System von einer lästigen Aufgabe. Die zusätzlichen Funktionen wie Sound und Auto Repeat geben dem Anwender einen Komfort, wie er oder sie es von einer üblichen Computer-Tastatur kennt.

Inzwischen habe ich die Tasten-Matrix in einigen Projekten im Einsatz und möchte sie nicht mehr missen.

Ressourcen auf GitHub

Software, Schaltplan und Fotos vom Aufbau sind auf GitHub zum Download verfügbar: https://github.com/smlaage/i2c-keyboard-matrix

WLAN IR Brücke mit ESP32

Der letzte Blog-Eintrag Lernende Infrarot-Fernbedienung hat gezeigt, wie man mit dem ESP32 Infrarot-Fernbedienungssignale aufzeichnen und wiedergeben kann. Nachdem das gut funktioniert, ergibt sich die Frage, ob man die IR-Kommandos über eine WiFi-Verbindung (statt mit den Tastern an der Schaltung) steuern kann. In diesem Eintrag geht es darum, die angelernten IR-Kommandos über eine Web-Page abrufbar zu machen, eine WLAN-IR-Brücke.

Wofür braucht man so etwas? Es ist doch sehr komfortabel, ein elektronisches Gerät, z.B. eine Radio oder eine Stereo-Anlage, mit der zugehörigen Fernbedienung direkt zu steuern. Tatsächlich gibt es aber durchaus Fälle, in denen der Zugriff über eine Web-Page hilfreich sein kann, z.B., wenn das Gerät in einem anderen Raum steht und über IR nicht erreichbar ist, oder das Radio aus der Ferne an – oder ausgeschaltet werden soll.

Dieses Projekt hat zwei Komponenten. Erstens wurden die IR-Funktionen aus dem vorherigen Blog-Eintrag in eine eigene C++-Klasse IR_Module verpackt, wodurch sie einfacher zu verwenden sind. Zweitens kommt das WiFi-Modul des ESP32 zum Einsatz, um eine Web-Page mit Buttons zu erzeugen. Die Web-Page ist mit jedem normalen Browsers auf Computer, Tablett oder Handy aufrufbar, ohne dass weitere Installation auf dem Gerät notwendig sind. Aber der Reihe nach:

Die Hardware

Im Vergleich zum vorherigen Beispiel ist die Schaltung etwas einfacher geworden. IR-Empfänger und IR-Sender mit Transistor und IR-LED bleiben unverändert. Es gibt jetzt nur noch einen Taster, der zum Anlernen der IR-Fernbedienung benötigt wird. Die Tasten zum Abruf der IR-Signale sind nicht mehr nötig, weil der Abruf jetzt über die Buttons auf der Web-Page geschieht. Schließlich gibt es noch eine zweifarbige Leuchtiode zur Kommunikation mit dem Anwender.

Die Schaltung ist einfacher geworden. Die Hardware-Taster zum Auslösen der IR-Kommandos sind verschwunden. Sie werden durch Buttons auf der Web-Page ersetzt.
Aufbau der WLAN-IR-Bridge auf einem Breadboard

Klasse IR_Module

Die Vorgehensweise zum Lernen und Abspielen von IR-Signalen wurden im vorherigen Blog-Eintrag beschrieben. Um die Anwendung der zugehörigen Funktionen zu vereinfachen, haben ich Daten und Funktionen in einer Klasse zusammengefasst. Der Quellcode der Klasse besteht aus zwei Dateien, die Header-Datei ESP32_IR_module.h und die Methoden-Datei ESP32_IR_module.cpp.

In der Header-Datei werden die benötigten Daten bereit gestellt. Dreh- und Angelpunkt sind die Daten der IR-Signale. Sie werden in einem zweidimensionalen Array ir_data[][] abgelegt.

Man kann sich das Array als Tabelle mit Zeilen und Spalten vorstellen. Jede Zeile bildet ein IR-Kommando und enthält als Spalten die Ein- und Ausschalt-Zeitpunkte des zugehörigen IR-Signals. Das Array verwendet 32-Bit-Integer Variablen, die die Zeitpunkte in Mikrosekunden relativ zum Anfang des Signals darstellen. Der ESP32 biete viel Speicherplatz. Hier werden maximal 10 (definiert in CHANNEL_MAX) angelernte Kommandos mit jeweils bis zu 250 (definiert in IR_DATA_SIZE) Umschaltzeitpunkten bereit gestellt. Es hat sich gezeigt, dass die Datenmenge ausreichend für die üblichen Anwendungen ist.

Das eindimensionale Array ir_data_len[] hält fest, wieviel Datenpunkte in jeder Zeile von ir_data[][] tatsächlich verwendet werden. Die Variable channel_cnt enthält die aktuelle genutzte Anzahl der IR-Signale, also die Zahl der verwendeten Zeilen.

Die Klasse stellt eine Reihe von Methoden bereit. load_ir_data(), save_ir_data() und clear_ir_data() dienen dem Speichermanagement. Die angelernten IR-Daten werden mit save_ir_data() in den permanenten “non-volatile” Speicher übertragen. Beim Systemstart können sie mit load_ir_data() aus dem permanenten Speicher in das RAM geladen und damit verfügbar gemacht werden. Mit clear_ir_data() werden die bestehenden Daten gelöscht, z.B. wenn eine neuen Fernbedienung angerlernt werden soll.

Mit der Methode read_ir_data() werden IR-Daten angelernt. Dabei wartet die Schaltung auf IR-Signale von einer Fernbedienung und speichert neue Kommandos ab. Die Funktion führt auch eine einfache Plausibilitäts-Prüfungen durch und quittiert IR-Impulse, die offensichtloch kein sinnvolles IR-Fernbedienungssignal darstellen, mit einer Fehlermeldung.

Die Methode play_ir_data(int channel) spielt das angewählte IR-Signal aus dem Speicher ab, sendet also das gewünschte Signal aus.

Die Methode print_ir_data(int chnnel) gibt die Daten eines Kanals auf dem seriellen Monitor aus. Das ist nur für debugging-Zwecke gedacht.

Und schließlich gibt es noch die Methode get_channel_cnt(), die die Anzahl der gespeicherten IR-Kommandos zurück gibt.

Um die Klasse zu verwenden, muss das aufrufende Programm ein Objekt vom Typ IR_Module anlegen, üblicherweise als globale Variable, so dass alle Funktionen darauf zugreifen können. Der Konstruktor benötigt die Pin-Nummern, an denen der IR-Empfänger und der IR-Sender angeschlossen sind.

Web-Server mit dem ESP32

Es wurde schon viel über das WiFi-Modul des ESP32 geschrieben, deshalb hier nur in Kürze. Ich verwende einen Web-Server, der sich mit einer festen (statischen) IP-Adresse (hier 192.168.1.222) in das Heimnetzwerk einklinkt. Der Vorteil der statischen IP-Adresse ist, dass die zugehörige Web-Page direkt über die IP-Adresse abrufbar ist. Natürlich muss sichergestellt werden, dass die IP-Adresse im Netzwerk frei und verfügbar ist. Gegebenenfalls lässt sich das im Router einstellen. Der zugehörige Programm-Code sieht so aus:

In der Arduino-Setup-Funktion verbindet sich der Web-Server mit dem Netzwerk. Die Zugangsdaten mit SSID und Passwort sind der Einfachheit halber direkt in den Programm-Code eingebettet. Während des Verbindungsaufbaus blinkt die rote LED. Wenn alles geklappt hat, gibt es eine Sekunde lang ein grünes Signal.

Wenn die Verbindung steht, kann der Web-Server gestartet werden. Die Web-Page ist dann über die http://192.168.1.222 erreichbar.

Die Tasten werden als eigene Seiten unter dieser Adresse, also über 192.168.1.222/0, …/1, …/2 usw. bereit gestellt. Wenn eine Taste auf der Web-Page gedrückt wird, erzeugt der Browser einen Aufruf der entsprechenden URL. Der Web-Server des ESP32 empfängt den Aufruf und leitet ihn auf die Funktion handle_channel(). Diese führt dann die gewünschte Aktion aus, als das Abspielen des jeweiligen IR-Signals.

Der Start des Web-Servers in der setup()-Funktion ist hier gezeigt:

Schließlich fehlt noch der HTML-Code, der an den Browser gesendet wird, wenn dieser sich mit der Seite verbindet. Dazu dient die Funktion send_html(), die den HTML-Code generiert und an den Client sendet. Die Buttons sind als Cascading Style Sheets (CSS) in den HTML-Code integriert. Der CSS-Code definiert die Farbe, Größe und Text der Buttons. Außerdem sorgt der Code mit Hilfe des Strings href dafür, dass bei einem Tastendruck die entsprechende URL aufgerufen wird. Damit ist der Kreis zum Web-Server und der Funktion handle_channel() geschlossen.

Die Hauptarbeit des Programms wird vom Web-Server und der Funktion handle-channel() übernommen. Entsprechend kurz fällt die Arduino loop()-Funktion au. Hier wird abgefragt, ob die Learn-Taste der Schaltung für mindestens 3 Sekunden gedrückt wurde. Wenn das der Fall ist, dann verzweigt das Programm in die Funktion learn_sequence(), die zum Anlernen einer neuen IR-Fernbedienung dient. Außerdem wird regelmäßig die Methode handleClient() des Web-Servers aufgerufen. Dadurch wird sichergestellt, dass der Web-Server auf die Verbindungsaufnahem durch einen Client reagiert.

Inbetriebnahme

Hier eine kurze Beschreibung, wie die Schaltung und das Programm in Betrieb genommen werden.

Das Programm besteht aus 3 Dateien: ESP32_WLAN_IR_Bridge.ino, ESP32_IR_module.cpp und ESP32_IR_Module.h. Alle 3 Dateien müssen im entsprechenden Arduino-Folder liegen.

In der Datei ESP32_WLAN_IR_Bridge.ino gibt es einige Anpassungen am Anfang des Programms zu machen. Name und Zugangspasswort für das Heimnetz müssen bei SSID und Password eingetragen werden. Die globale Konstante char *bt_labels bietet die Möglichkeit, die Buttons auf der Web-Page mit sinnvollen kurzen Labels zu versehen, je nachdem, was die IR-Steuerung machen soll. Das Programm kann maximal 10 Buttons (Kanäle) bedienen. Im Zweifelsfall kann man einfach eine fortlaufende Nummer verwenden. Schließlich sollte die statische IP-Adresse entsprechende den eigenen Bedürfnissen angepasst werden

Wenn das Programm kompiliert und auf den ESP32 übertragen ist, ist das System einsatzbereit. Nach dem Einschalten verbindet sich der ESP32 mit dem Netzwerk. Das ist an der blinkenden Leuchtdiode erkennbar. Wenn die Verbindung erfolgreich ist, zeigt die LED eine Sekunde lang grün und geht dann aus. Die Web-Page ist jetzt über einen Browser über die IP-Adresse abrufbar.

Zum Anlernen einer IR-Fernbedienung muss die Lern-Taste für mindestens 3 Sekunden gedrückt werden. Achtung, dadurch werden alle bestehenden Daten gelöscht! Die blinkende LED zeigt jetzt an, dass die Schaltung bereit ist zum Lernen. Die gewünschten Fernbedienung wird auf den IR-Empfänger gerichtet und die erste Taste kurz (!) gedrückt. Wenn das Signal akzeptiert wurde, leuchtet die LED grün auf. Nach einer kurzen Pause ist die Schaltung bereit zum Lernen der nächsten Taste, was wieder durch Blinken angezeigt wird. Das Spiel geht solange, bis entweder für 20 Sekunden keine IR-Signal mehr kommt oder der Speicher mit 10 gelernten Tasten voll ist. Übrigens hat sich in der Praxis gezeigt, dass beim Anlernen direkte Beleuchtung mit manchen LED-Lampen etwas stören kann. Wenn es Probleme gibt, kann es helfen, z.B. das Schreibtischlicht auszuschalten.

Die WLAN-IR-Brücke wird mit den IR-Signalen einer Fernbedienung angelernt.

Nach dem erfolgreichen Lern-Prozess sind die IR-Kommandos über die Web-Page verfügbar. Die IR-Sende-Diode sollte in die Richtung des zu gewünschten Geräts gerichtet sein. Die IR-Kommandos stehen über die Web-Page via Computer oder Handy zur Verfügung, und das Zielgerät kann über das Web gesteuert werden.

So meldet sich der ESP32 beim Aufruf mit dem Browser.

Wie immer an dieser Stelle wünsche ich viel Spaß mit dem kleinen Projekt. Rückmeldung, Kommentare und Verbesserungsvorschläge sind willkommen.

Arduino-Software zur ESP32 WLAN-IR-Bridge

Lernende Infrarot-Fernbedienung mit ESP32

In einem Kurs mit dem ESP32 kam die Frage, ob man einen Sender für eine Infrarot-Fernbedienung selbst bauen kann. Ja, das geht. Es ist eine schöne Anwendung für den ESP32.

Zielvorgaben

Worum geht es also? Die Idee war, Signale von einer vorhandenen Infrarot-Fernbedienung einzulesen und dann gesteuert durch den Mikrocontroller zu reproduzieren. Der Controller kann z.B. zu einer bestimmten Zeit das Radio einschalten. Oder man kann einen kleinen Web-Server programmieren, der vom Smartphone gesteuert Licht oder Musik ein- oder ausschaltet. Geräte lassen sich automatisch steuern, ohne dass irgendein Eingriff in das Gerät notwendig ist. Vieles ist denkbar.

Um die Komplexität in Grenzen zu halten, nehmen wir für den Anfang nur drei Kanäle. Es soll also 3 Tasten geben, die jeweils ein angelerntes Signal abspielen können. Eine weitere Taste wird gebraucht, um das kleine Gerät in den Lernmodus zu versetzen. Wenn die Anlern-Taste für eine bestimmte Zeit (hier 3 Sekunden) gedrückt wird, soll das Gerät bereit sein, Signale von einer IR-Fernbedienung zu empfangen und aufzuzeichnen. Schließlich möchten wir noch Leuchtdioden haben, die den aktuellen Status anzeigen. Und die angelernten Daten sollen natürlich dauerhaft über das Stromabschalten hinaus erhalten bleiben. Das ist dann eigentlich schon alles.

IR Signal-Übertragung

Wie funktioniert die IR-Signalübertragung? Die üblichen IR-Sender produzieren Lichtsignale, die mit einer festen Frequenz zwischen 36 und 40 kHz moduliert sind, also sehr schnell ein- und ausgeschaltet werden. Dieser Frequenzbereich wurde gewählt, um den Signalabstand zu Störungen möglichst groß zu halten, denn schließlich ist Infrarotlicht allgegenwärtig, sei es als natürliches Licht von der Sonne oder als gepulstes Licht von diversen Lampen. Die eigentliche Information (z.B. “Radio einschalten”) befindet sich digital kodiert in der zeitlichen Abfolge von kurzen und langen Pulsen (“bursts”) mit z.B. 38 kHz. Ein typisches Signal von einer Fernbedienung kann z.B. 30 Millisekunden lang sein und 30, 40 oder 50 Pulse von kurzer oder langer Dauer enthalten.

Soweit die Theorie. Zum Glück ist in diesem Fall die Praxis nicht weit: Man kann mit einem Oszilloskop das Signal einer IR-Fernbedienung an einer Infrarot-Photodiode gut beobachten.

Schaltung zur Messung des Infrarot-Signals mit einer IR-Fotodiode. Die Fotodiode muss in Sperrrichtung gepolt sein.
Gemessene 38 kHz Bursts einer IR-Fernbedienung, hier eine “Bose Wave” Anlage. Einzelne Bursts sind etwa 550 Mikrosekunden lang. Die gesamte Sequenz erstreckt sich über 30 Millisekunden.

Für unsere Anwendung müssen wir das Signal zum Glück nicht “verstehen”. Aber es muss möglichst exakt aufgezeichnet und wieder abgespielt werden, damit das empfangende Gerät entsprechend reagiert.

Hardware

Es ist relativ einfach, IR-Signale mit einem Mikrocontroller zu generieren. Mit einem Timer wird eine Rechteckschwingung von 38 kHz erzeugt und auf einen der Ports gegeben. Typische IR-Leuchtdioden vertragen Ströme von bis zu 100 mA, was den Port überfordern würde. Deshalb kommt ein einfacher Transistor hinzu, z.B. ein BC337, der das Signal verstärkt. Um die 3.3V Board-Spannung des ESP32 nicht zu sehr durch die 38 kHz Pulse von 100mA zu belasten, wird die Infrarot-LED mit einem 33 Ohm Widerstand nach +5V verschaltet. Damit ist der Strom auf etwas unter 100 mA begrenzt.

Auf der Empfangsseite wird eine Schaltung benötigt, die möglichst empfindlich und gleichzeitig selektiv 38 kHz-IR-Signale herausfiltert und diese dann als digitale Zustände Ein (= Signal vorhanden) oder Aus (= kein Signal vorhanden) verfügbar macht. Das könnte aufwendig werden, wenn es dafür nicht fertige und preisgünstige Komponenten gäbe. Ich verwende den TSOP1138. Dieser oder ähnliche Empfänger mit guter Selektivität und hoher Empfindlichkeit sind in der Arduino-Welt verbreitet und z.B. in Funduino-Bausätzen enthalten. Der TSOP1138 hat drei Anschlüsse: Ground, Spannungsversorgung Vs und Signalausgang. Das Datenblatt sagt, dass die Betriebsspannung 5V betragen sollte. Tatsächlich habe ich sehr gute Ergebnisse mit den 3.3V des ESP32 erreicht. Es besteht also kein Bedarf für Pegelumsetzer.

TSOP1138

Schließlich kommen noch 4 Taster und 3 Leuchtdioden dazu. Damit ist die Hardware komplett. Die Schaltung ist einfach und kann gut auf einem Steckbrett aufgebaut werden.

Schaltbild IR Remote Control
Die Schaltung ist schnell auf einem Breadboard zusammengesetzt. Oben links befindet sich die IR-Sende-Diode mit dem Transistor zur Verstärkung. Oben mittig ist der IR-Empfänger. Taster und Leuchtdioden befinden sich auf der rechten Seite.

Wenn der Schaltplan klar ist, kann man die entsprechenden GPIO-Nummern im Sketch bereitstellen. Das geschieht im Programm ganz am Anfang als globale Konstanten. Damit sind die Definitionen an einem Ort und können bei Bedarf schnell angepasst werden.

Software

Der eigentlich spannende Teil des Projekts ist die Software. Natürlich gibt es ausgefeilte Bibliotheken für die Bearbeitung von Infrarot-Signalen mit einem Arduino (z.B. die multi-protocol-infrared-remote-library von Ken Shirriff). Diese sind aber nicht unbedingt für den ESP32 geeignet. Außerdem fand ich es interessanter, die notwendige Funktionalität mit Board-Mitteln zu programmieren.

System-Takt in Mikrosekunden

Grundsätzlich gibt es zwei mögliche Ansätze, um Zeitverläufe von Signalen aufzuzeichnen:

  1. Man kann mit einem regelmäßigen, möglichst engen Abstand den Zustand des Eingangs-Port testen und den jeweils aktuellen Wert als 0 oder 1 abspeichern. Üblich ist z.B. eine Abtastrate von 50 Mikrosekunden. Der Nachteil dieser Methode besteht darin, dass die Abtastrate unweigerlich einen möglichen zeitlichen Fehler (hier +/- 25 Mikrosekunden) mitbringt.
  2. Alternativ kann der Eingangs-Port ständig mit hoher Geschwindigkeit abgefragt (“Polling”) und jeweils der Zeitpunkt der Umschaltung aufgezeichnet werden. Wenn das Polling ausreichend schnell geschieht, ist die zeitliche Auflösung dieser Methode besser. Dieser Ansatz setzt allerdings eine möglichst exakte System-Uhr im Bereich von Mikrosekunden voraus.

Glücklicherweise verfügt der ESP32 über genau das: Eine Timer-gesteuerte System-Uhr, die die Anzahl der Mikrosekunden seit dem Einschalten zur Verfügung stellt: esp_timer_get_time() (siehe ESP Referenz). Die Ausführung der Funktion benötigt selbst weniger als eine Mikrosekunde, stellt also keine besondere Systemlast dar.

Die Zeitpunkte (Mikrosekunden) des Anfangs (Einschalten) und Ende (Ausschaltens) der einzelnen Bursts werden in Arrays abgelegt. Da wir mit 3 Kanälen arbeiten, gibt es ein zwedimensionales Array, um die Signal-Zeitpunkte abzuspeichern: ir_data[3][250] . Es enthält drei Zeilen, wobei jede Zeile bis zu 250 Datenpunkte aufnehmen kann. In meinen bisherigen Versuchen waren in der Regel nie mehr als 100 Datenpunkte für ein IR-Signal notwendig – aber es kann nicht schaden, Luft nach oben zu lassen.

Zusätzlich benötigen wir noch jeweils eine Integer-Variable, die die tatsächliche Anzahl der Einträge in den Arrays abspeichert. Ich nenne sie ir_data_len[3], ebenfalls ein Array mit 3 Werten für drei Kanäle. ir_data[][] und ir_data_len[] werden als globale Variablen definiert, also im Programmablauf vor allen Funktionen.

Funktion read_ir_data()

Damit wird die Funktion zum Einlesen der IR-Signale read_ir_data() unkompliziert. Die Funktion übernimmt einen Zeiger auf das Daten-Array, in dem die Daten abgelegt werden, und einen Zeiger auf die zugehörige Zähler-Variable. Am Anfang des Lesevorgangs wartet die Software auf das erste Signal von der IR-Fernbedienung und erfasst die aktuelle Startzeit (start_time). Ab jetzt wird bei jedem Wechsel des Eingangs-Ports die aktuelle Zeit gelesen, die Differenz zur Startzeit errechnet (time_stamp) und diese im Array gespeichert. Die boolesche Variable edge sorgt für die Unterscheidung zwischen dem Wechsel von LOW -> HIGH oder HIGH -> LOW. Nach dem Ablauf der voreingestellten Zeit, hier 250 Millisekunden, wird die Erfassung beendet. Die Arrays mit den Umschalt-Zeitpunkten bilden die Datenbasis für eine exakte Reproduktion des Signals.

Um die Arbeitsweise zu kontrollieren, gibt es eine Funktion print_ir_data(), die die eingelesenen Daten auf dem seriellen Monitor ausgibt. Hier die Daten für “Einschalten” bei der “Bose Wave” Anlage. Das Signal wird zum Zeitpunkt 0 eingeschaltet, nach 1047 Mikrosekunden aus, dann nach 2499 Mikrosekunden wieder ein, nach 3040 Mikrosekunden aus, usw.

PWM zur Erzeugung des 38 kHz-Signals

Zum Abspielen der aufgezeichneten Signale muss ein 38 kHz-Signal erzeugt und entsprechend ein- und ausgeschaltet werden. Dazu eignet sich die PWM-Funktion der Arduinos. Diese unterscheidet sich beim ESP32 von den 8-Bit-Arduinos, die über die Funktion analogWrite() verfügen. Der ESP32 hat mehr Hardware-Möglichkeiten, insbesondere mehr PWM-Kanäle. Hier wird die PWM mit drei Low Level-Funktionen gesteuert.

Mit ledcSetup() wird der gewünschte PWM-Kanals konfiguriert. Die Frequenz ist in unserem Fall 38 kHz. Die Funktion ledcAttachPin() bindet den PWM-Kanal an einen der Ausgabe-Ports (hier der Port für die Infrarot-Leuchtdiode). Schließlich setzt ledcWrite() den gewünschten Duty-Cycle. Wir arbeiten mit einer Bit Resolution von 8, so dass der Wert für Duty Cycle zwischen 0 (entspricht dauerhaft Aus) und 255 (entspricht dauerhaft Ein) liegen darf.

Eine gute Zusammenfassung der PWM beim ESP32 gibt es hier: EPS32 Arduino LED PWM Fading.

Der entsprechende Code im setup()-Block sieht so aus:

Funktion play_ir_data()

Im Programm übernimmt die Funktion play_ir_data() die Aufgabe, das IR-Signal – basierend auf den angelernten Daten – zu reproduzieren. Dabei kommt wieder die System-Uhr zur Hilfe, um im richtigen Moment die jeweiligen Bursts ein- oder auszuschalten. Einschalten heißt, den Duty-Cycle auf die Hälfte des Maximalwertes, also 128, zu setzen, so dass das PWM ein möglichst ausgeprägtes 38 kHz Signal erzeugt. Ausschalten heißt, den Duty-Cycle auf 0 zu setzen.

Das Oszilloskop zeigt, dass das ursprüngliche Signal mit guter Genauigkeit reproduziert wird.

Ausgangs-Signal am IR-Empfänger für die Funktion “Einschalten” des Bose Wave-Systems
Signal am GPIO-Ausgang des ESP32. Der zeitliche Verlauf des ursprünglichen Signals wird exakt reproduziert.

Daten langfristig sichern: Preferences

Schließlich gibt es noch die Anforderung, dass der ESP32 die Daten der gelernten Signale über das Ausschalten hinaus im Speicher behalten soll. Beim 8-Bit Arduino gibt es das EEPROM zum dauerhaften Abspeichern von Daten. Leider ist der Speicherplatz des EEPROMS begrenzt und die Programmierung dazu etwas umständlich.

Der ESP32 bietet die Möglichkeit, aus dem Programm heraus den Flash-Speicher, in der ESP-Terminologie non-volatile storage (NVS), zu lesen und zu beschreiben. Der hat natürlich sehr viel Platz. Im Arduino-Framework gibt es das Preference-Objekt, mit dem der Flash-Speicher erreichbar ist. Dazu wird ein globales Objekt prefs vom Typ Preferences erzeugt.

Dort kann man Bereiche einrichten, die zum Abspeichern beliebiger Daten zur Verfügung stehen. Der Bereich bekommt einen Namen, der als String übergeben wird, und wird mit Begin geöffnet. Der Name kann willkürlich gewählt werden, hier ir_nvs.

False bedeutet, dass der Bereich sowohl gelesen als auch beschrieben werden darf. Zum Lesen und Schreiben gibt es die Objekt-Methoden get…() und put…(), jeweils für die üblichen Datentypen, z.B.

zum Schreiben eines 16-Bit Integer-Wertes ohne Vorzeichen. Der name_string kann wieder beliebig gewählt werden, muss aber eindeutig sein und dient zur Identifikation der Variable. Eine gute Praxis ist, dafür den Namen der entsprechenden Variable im RAM zu verwenden.

Für Arrays und andere größere Objekte kann man auf die Funktion

zurückgreifen, die den entsprechenden Speicherbereich ins Flash kopiert und so dauerhaft verfügbar macht.

Als Gegenstück kann mit

der gespeicherte Wert gelesen werden. Wenn man auf einen bisher noch unbeschrieben Namen zugreift, bekommt man den Wert 0.

Im Programm werden nach dem Einschalten im setup()-Block die Anzahl der abgespeicherten Daten für die drei Kanäle aus dem Flash-Speicher in das Array ir_data_len[] gelesen. Wenn die Werte größer als 0 sind, dann gibt es tatsächlich abgelegte IR-Daten, die dann in das Array ir_data[][] im RAM kopiert werden und für die Funktion play_ir_data() zur Verfügung stehen.

Entsprechend werden nach dem Anlernen von IR-Signalen die Daten in den Flash-Speicher geschrieben, so dass sie dauerhaft erhalten bleiben und beim nächsten Einschalten des Geräts gelesen werden können. Das übernimmt die Funktion learn_sequence(), die nacheinander für alle 3 Kanäle die Funktion read_ir_data() aufruft und die Daten dann in den Flash-Speicher schreibt.

Alle Komponenten zusammen setzen

Damit sind die wesentlichen Komponenten für das System vorhanden. Die Schleife loop() fragt die Buttons ab und verzweigt in die jeweiligen Funktionen.

Die Bedienung geschieht folgendermaßen:

  • Nach dem Einschalten (Power On) werden die drei Leuchtdioden der Reihe nach kurz durchgeschaltet, um zu zeigen, dass das System aktiv ist.
  • Das Gerät kann drei verschiedene Signale aufzeichnen und abspielen. Dazu dienen die drei Taster. Ein Druck auf einen der Taster bewirkt das Aussenden des entsprechenden Signals. Dabei leuchtet die zugehörige Leuchtdiode kurz auf.
  • Um in den Lernmodus zu gelangen, muss die Anlern-Taste für mindestens 3 Sekunden gedrückt werden. Dann blinkt die Leuchtdiode des ersten Kanals in einem schnellen Rhythmus, um anzuzeigen, dass das Gerät auf ein Signal zum Anlernen wartet. Man sollte jetzt die Fernbedienung auf den IR-Empfänger richten und kurz (!) die gewünschte Funktion drücken. Nachdem das passiert ist, wiederholt sich der Vorgang für Kanal 2 und 3. Damit ist das Gerät programmiert und bereit zum Einsatz.

Hier das ganze Programm.

Fazit

Eine lernfähige IR-Fernbedienung kann mit dem ESP32 relativ schnell entwickelt werden. Das Gerät arbeitet zuverlässig und hat sich bei verschiedenen Anwendungen bewährt. Die Genauigkeit der Reproduktion ist sehr hoch. Bei mir haben die Zielgeräte bisher klaglos die Signale der “fremden” Fernbedienung akzeptiert.

Bei Bedarf kann die Anzahl der Kanäle weiter erhöht werden, solange GPIOs für Tasten und Leuchtdioden vorhanden sind.

Bleibt die Frage, ob der ESP32 für diese Anwendung Vorteile im Vergleich zum 8-Bit Arduino bietet. Natürlich kann man ein ähnliches Ergebnis auch mit eine Arduino Nano erreichen. Dabei würde es aber im RAM knapp. 3 * 250 Datenpunkte von jeweils 32 Bit belegen mehr als 2 kB und würden das verfügbare RAM des ATmega328 bereits überfordern. Außerdem macht sich beim ESP32 der schnelle Systemtakt für eine höhere Genauigkeit der reproduzierten Signale und der der große Flash-Speicher zum dauerhaften Ablegen der Sequenzen nützlich. Spätestens wenn mehr Kanäle notwendig werden, wird der 8-Bitter nicht ausreichen. Und als Erweiterung kann der ESp32 die Bedienung über ein Web-Interface ermöglichen, was ein weiterer Schritt in Richtung Smart-Home wäre.

Downloads

ESP32

Die 8-Bit Mikroprozessoren sind aus dem elektronischen Alltag nicht mehr wegzudenken. Es gibt kaum ein Projekt in meiner Werkstatt, das nicht mindestens einen ATmega328 verwendet, sei es als “nackter” Prozessor auf der Platine oder zusammen mit USB-Interface und Quarz als Arduino Nano. Die Chips sind zuverlässig, vielseitig und vertraut – wie ein Werkzeug, das sehr gut in der Hand liegt.

Aber das Design der AVR-Prozessoren geht zurück auf die 90er Jahre und stößt natürlich an Grenzen. Besonders wünschenswert wären mehr RAM-Speicher, eine Floating-Point-Unit, mehr Timer und eine höhere Taktrate, die für zeitkritische Anwendungen hilfreich sein kann. Schon seit einiger Zeit gibt es verschiedene 32-Bit-Prozessoren, die mit wesentlich mehr Leistung anbieten und für eigene Entwicklungen geeignet sind. Hervorzuheben sind die STM32-Controller von ST (Nucleo-Boards) oder die SAM D21-Chips von Microchip Technology. Beide haben es auch in die Arduino-Welt geschafft (z.B. Arduino Due, Arduino-Board mit 32-Bit Architektur). Jedoch haben sich diese Chips in der Maker-Szene nicht so durchgesetzt, wie man es hätte erwarten können.

Anders erging des dem ESP32, eine neuere Entwicklung der chinesischen Firma Espressif. Dieser Chip verfügt über (fast) alles, was das Bastler-Herz begehrt, arbeitet mit Taktfrequenzen bis zu 240 MHz und ist darüber hinaus auf kleinen, günstigen Breakout-Boards verfügbar. Espressif hat es geschafft, den ESP32 sehr gut in die Arduino-Welt einzubetten, so dass der Umstieg von anderen Arduinos nicht schwer fällt. So ist es vielleicht nicht überraschend, dass der ESP32 eine beachtliche Verbreitung gefunden hat.

ESP32 Breakout Boards: Links das ESP32 Dev Modul, das leider etwas zu breit für einfache Steckbretter ist, dafür aber sehr preisgünstig. Rechts das etwas teurere ESP32 Pico Board.

Ein Grund für die große Akzeptanz ist sicherlich die Tatsache, dass Espressif WLAN- und Bluetooth-Kommunikation integriert hat. Ein zweiter Prozessor kümmert sich um die Netzwerkschnittstellen und arbeitet die entsprechenden Protokolle ab. Als Anwendungsentwickler hat man auch bei aktivem Netzwerk den Hauptprozessor vollständig zur Verfügung und muss sich nicht mit den Details der Kommunikation beschäftigen. Damit sind auch kleine Projekte ohne großen Aufwand über das Netz erreichbar. Der ESP32 ist zu einer Ikone des Internet of Things (IoT) geworden.

Breakout-Boards

Bei den Boards für den ESP32 gibt es etwas Wildwuchs. In meiner Praxis arbeite ich mit dem ESP32 Dev Module oder dem ESP32 Pico Board. Leider ist das preisgünstige Dev Module etwas zu breit für einfache Breadboards. Man kann aber zwei Breadboards zusammenstecken, so dass das Dev Module gut Platz findet.

ESP32 Dev Module auf zwei zusammengesetzten Breadboards.

Und es muss nicht unbedingt ein Steckbrett sein. Ich habe die Boards mit entsprechenden Buchsen-Leisten auch auf fertigen Platinen im Einsatz.

Die Boards haben viele Pins. Hier ist eine Übersicht der Pins mit ihren wichtigsten Funktionen für das Dev Module:

ESP32 Dev Module Pinout

Wie üblich sind die Pins mehrfach belegt und bieten je nach Konfiguration verschiedenen Funktionen an. Es gibt aber eine Reihe von Einschränkungen:

  • Die Pins 34, 35, 36 und 39 sind nur als Eingänge verfügbar. Eine digitalWrite()-Anweisung für diese Ports wird von der Arduino-IDE klaglos akzeptiert, bleibt aber völlig wirkungslos.
  • GPIO 6 bis 11 werden für den integrierten Flash-Speicher verwendet und stehen daher nicht frei zur Verfügung.
  • Der zweite ADC ist nicht verfügbar, solange der WiFi-Modus aktiv ist.

Wen man diese Dinge nicht weiß, kann die Fehlersuche sehr mühsam werden. Eine gute Zusammenstellung der möglichen Verwendung der Pins gibt es hier: https://randomnerdtutorials.com/esp32-pinout-reference-gpios/

Viel Peripherie

Ich beschäftige mich seit einigen Jahren mit dem ESP32 und habe ihn für verschiedene Projekte eingesetzt, durchweg mit guten Resultaten. Selbst wenn das WLAN nicht gebraucht wird, verfügt der Chip über eine ganze Menge nützlicher Peripherie. Neben den GPIOs und den üblichen Schnittstellen (ISP, SPI, UART) gibt es zwei ADCs, einen DAC, Deep-Sleep-Modus, Zugriff auf den Flash zur permanenten Speicherung von Daten und vieles mehr. Einzig die ADCs erfüllen nicht ganz die Erwartungen. Sie arbeiten zwar mit einer Auflösung von bis zu beachtlichen 12 Bit, leider aber mit mäßiger Linearität (siehe ESP32 ADC Non-linear Issue). Damit sind die ADCs für absolute und genaue Messungen nur eingeschränkt brauchbar. Für relative Vergleiche von Messwerten reicht es aber auf jeden Fall. Wenn für eine gegebene Anwendung ein exakter ADC unentbehrlich ist, dann bleibt natürlich noch die Möglichkeit, einen externen ADC über I2C oder SPI anzuschließen. Ein Ansatz, der z.B. beim Raspberry Pi in jedem Fall notwendig ist.

Vereinfachtes Pinout des ESP32 Dev Module

PlatformIO als Entwicklungsumgebung

Es gibt eine Reihe von Entwicklungsumgebungen, die zur Programmierung verwendet werden können. An erster Stelle steht natürlich die Arduino-IDE, die über den Boardverwalter mit der URL https://dl.espressif.com/dl/package_esp32_index.json für den ESP32 leicht erweitert werden kann. Man fühlt sich schnell Zuhause, wenn auch die Arduino-IDE für komplexere Projekte zu einfach gestrickt ist.

Eine positive Überraschung war für mich PlatformIO (https://platformio.org/), eine komfortable, plattform-übergreifende und als Open Source kostenlose IDE, mit der ich inzwischen sehr gerne an Mikrocontroller-Projekten arbeite. Die Bibliotheken für Arduino und für den ESP32 werden als Extensions installiert. Danach hat man eine große Auswahl an ESP32-Boards.

Der Umgang mit PlatformIO ist im Vergleich zur Arduino IDE etwas gewöhnungsbedürftig. Aber schon nach kurzer Zeit findet man alles, was gebraucht wird. Die zentralen Projekt-Einstellungen werden über die Datei platformio.ini vorgenommen. Hier ein typisches Beispiel für den ESP32:

platformio.ini für ein Projekt mit dem ESP32

Der serielle Monitor, als Debugging-Tool unerlässlich, lässt sich unter den Project Tasks mit “Monitor” anwählen. Mit “Upload and Monitor” wird direkt nach dem Laden des Programms der serielle Monitor aktiviert.

PlatformIO Tasks. Hier findet sich auch der serielle Monitor.

Außerdem sind die wichtigsten Funktionen (Übersetzen, Upload, serieller Monitor und mehr) über eine kleine Taskleiste am unteren Bildschirmrand verfügbar.

PlatformIO Task-Leiste mit vielen Funktionen

Die umfangreiche Funktionalität des ESP32 ist weitgehend in Arduino-Funktionen verpackt, so dass die ersten Programme ohne große Umstellung funktionieren. Wenn man auf spezifische Funktionen des ESP32 zugreifen möchte, dann ist auch das kein Problem. Wie üblich im Ardunio Framework sind die Bibliotheken des Herstellers (nach Einbinden der entsprechenden Header-Files) transparent verfügbar, z.B. Funktionen wie adc1_get_raw(), um den ADC anzusprechen, oder esp_adc_cal_raw_to_voltage() für die Umrechnung der Werte mit Kompensierung von Referenzspannungs-Abweichungen (siehe ESP ADC Reference). Damit steht dem tieferen Einstieg in die Welt des ESP32 nichts im Wege – sofern man bereit ist, sich durch die Beschreibungen und Datenblätter zu arbeiten.

Fazit

Der ESp32 hat sich schnell in meinen Arbeitsalltag integriert und kommt oft zum Einsatz. Umfang und Leistungsfähigkeit stehen zwischen den 8-Bit AVRs auf der einen Seite und dem Raspberry Pi auf der anderen Seite. wobei der Chip kaum teurer ist, als ein einfacher Arduino. Die Integration in das Arduino-Framework und die Verfügbarkeit einer leistungsfähigen Open Source-IDE lassen kaum Wünsche offen.

Software-Update für den Gewitter Monitor v2

Gestern ist wieder einmal ein heftiges Gewitter durch unsere Region gezogen und hinterließ seine Spuren auf dem Display des Gewitter-Monitors. Das war eine gute Gelegenheit, die letzten Software-Updates zu testen. Zur Zeit des Fotos lag das Maximum des Gewitters etwa 1 Stunde zurück. Die Trendanalyse über die letzten 45 Minuten (jetzt durch Pfeilsymbole  unterstützt) zeigte, dass die Summe der Blitze pro Minute stark rückläufig war. Der Mittelwert der letzten 15 Minuten lag aber immer noch so hoch, dass alle drei Alarm-LEDs aktiviert waren.

Seitdem ich die Version 2 im Februar in Betrieb genommen hatte, gab es viele Gelegenheiten, die Funktion der Software im Detail zu testen. Allerlei Feinheiten mussten korrigiert werden. Besonders ärgerlich war ein Fehler in der Trendanalyse, der den Prozessor in eine Endlosschleife schickte, wenn die Zahl der Blitze pro Minute sehr hoch wurde. Außerdem gab es Ungereimtheiten mit der Anzeige des Maximums. Jetzt funktioniert aber alles so, wie es soll.

Die neue Software steht wie immer auf der Ressourcen-Seite zum Download bereit. Die neue Software benötigt die Smart I2C GLCD-Software ab Juni 2018. Sonst können die Pfeilsymbole nicht dargestellt werden.

Von Herrn Wolfgang Richter erreichte mich ein Foto. Er hat seinen Gewitter-Monitor in ein Gehäuse aus schwarzem ABS-Kunststoff (200 x 100 x 65 mm) gesetzt. Sehr schön ist die Antenne,  die über dem Gerät schwebt. Vielen Dank für das Foto!

Gewitter-Monitor von Wolfgang Richter

Herr Richter hat mich auch auf die Forschungen von Hans Baumer zu den sogenannten Sferics aufmerksam gemacht. Dabei handelt es sich um elektrische Entladungen in den Wolken, die ohne Lichterscheinungen von Blitzen einhergehen. Tatsächlich ist mir schon seit langem aufgefallen, dass der Gewitter-Monitor bei labilen Wetterlagen (feuchte und warme Luft) deutliche Aktivitäten anzeigt, und zwar schon lange bevor Gewitter zu beobachten sind. Hierbei handelt es sich wahrscheinlich um die genannten Sferics. Übrigens habe ich die Empfindlichkeit des Geräts so eingestellt, dass die Alarmlevel (rote Leuchtdioden) erst dann aktiviert werden, wenn tatsächliche Gewitter im Umkreis von einigen hundert km registriert sind. Zur Kontrolle eignet sich die sehr informative Website Blitzortung.org.