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

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.

Neue Software für das Smart I2C Graphik-LCD

Es ist an der Zeit für ein paar kleine Updates zum beschriebenen I2C-Interface für Graphik LC Displays.

Erstens … wollte ich für den Gewitter-Monitor Pfeilsymbole haben, die die Trendanalyse unterstützen. Ein oder zwei Pfeil nach oben oder unten – so schwer kann das doch nicht sein. Herausgekommen ist ein weiterer Zeichensatz, Font #7, mit einigen Symbolen. Der neue Zeichensatz mit einer Höhe von 12 Pixeln wird auch im User-Dokument beschrieben.
Bei Gelegenheit werden noch ein paar Symbole dazu kommen.

Außerdem wurde der Startup-Screen, der sich nach dem Anlegen der Betriebsspannung zeigt, erweitert. Neben der Software-Versionsnummer und Datum werden der Display-Typ mit der Anzahl der Pixel in horizontaler Richtung, die I2C-Puffergröße und Adresse und die aktuelle Helligkeitseinstellung (0 …. 10) präsentiert. Übrigens lässt sich der Startup-Screen unterdrücken durch Abschalten des Verbose-Mode.

Smart I2C GLCD Startup Screen im “Verbose”-Mode

Abgesehen von diesen Erweiterungen und zwei kleinen Bugfixes wurde die Software nicht mehr verändert. Das Display hat sich als recht zuverlässig erwiesen.

Zweitens … ist der erste Satz Platinen (für Display Typ 3 mit 4 Zoll Diagonale) bereits verbaut und tut in verschiedenen Anwendungen seinen Dienst. Deshalb musste ich neue Platinen bestellen. Bei der Gelegenheit habe ich das Layout noch einmal überarbeitet. Die Platine ist etwas kleiner geworden. Es gibt ein Befestigungsloch, das mit dem entsprechenden Loch der Display-Platine fluchtet. Bei der Gelegenheit habe ich den PNP-Transistor für die Steuerung der Beleuchtung ausgetauscht. Der eher seltene BC636 wurde durch den Standard-Transistor BC327 ersetzt.

Neues Layout für das Smart I2C GLCD Interface
Neues Layout für das Smart I2C GLCD Interface

Transistor Q2 (ursprünglich BC636) wurde durch den weit verbreiteten BC327 ersetzt.

Und drittens … gibt es eine erweiterte Arduino-Library zur Ansteuerung des Displays. Die Bibliothek glcd_functions_avr ist speziell für die kleinen 8Bit-AVR-Prozessoren (z. B. ATmega168 oder ATmega328) gedacht, wie sie im Arduino Nano oder Uno zum Einsatz kommen. Dort hat man oft das ärgerliche Problem, dass der beschränkte RAM-Speicher (1 oder 2k Byte) nicht ausreicht. Das hat zwar mit dem Display erst einmal nichts zu tun. Aber wenn man viele Texte im Programm verwalten und auf dem Display anzeigen möchte, dann wird RAM-Speicherplatz sehr schnell eng.  Deshalb ist es sinnvoll, konstante Texte im viel größeren ROM-Speicher zu halten. Dazu gibt es die zusätzlichen Funktionen
draw_pgm_str()draw_center_pgm_str() und draw_radj_pgm_str(). Diese Funktionen übernehmen Texte direkt aus dem Programmspeicher, so dass dafür kein RAM “verbraucht” wird.

Diese zusätzlichen Funktionen machen aber nur Sinn für die kleinen AVR-Prozessoren. Deshalb sollten alle anderen Arduinos (z.B. mit STM32, ESP32, etc.), die wesentlich mehr RAM-Speicher zur Verfügung haben, die normale Library glcd_functions verwenden.

Die neuen Gerber-Files, Software und Dokumente sind auf der Ressourcen-Seite verfügbar.

Übrigens: Da ich jetzt einen kleinen Vorrat an Platinen habe, gebe ich gerne Einzelstücke zum Selbstkostenpreis ab. Einfach per E-Mail melden.

Neues vom Gewitter-Monitor: Version 2

Der Gewitter-Monitor mit graphischer Anzeige war ein Projekt, das ich im Jahr 2014 fertig gestellt hatte. Seit dem habe ich das Gerät immer wieder einmal im Einsatz, und es tut gute Dienste. Inzwischen ist es aber an der Zeit für etwas Modell-Pflege. Rechtzeitig vor der nächsten Gewitter-Saison stelle ich hier die neue Version 2 vor.

Version 2 verwendet im Kern einen Arduino, wodurch das Programmieren und Flashen vereinfacht wurde. Das I2C-Smart Graphic LCD ermöglicht ansprechende Graphiken. Dazu gibt es eine vollständig neu entwickelte Software mit vielen neuen Features, z.B. einen mehrstufigen Alarm-Level, eine Trend-Analyse und ein Konfigurations-Menü.

Bewährtes Design mit neuen Funktionen

Das Grundprinzip des Gewitter-Monitors bleibt bestehen und wird hier kurz zusammengefasst.

Wer Radiohören noch aus den Zeiten von Mittelwelle und Langewelle kennt, der weiß, dass Gewitter-Blitze als störendes Krachen im Lautsprecher zu hören waren. Seit dem Umstieg auf Frequenzmodulation oder gar digitale Verfahren ist das aus unserem Radio-Alltag verschwunden. Das Prinzip eignet sich aber nach wie vor, um Gewitter-Blitze zu beobachten und zu registrieren.

Die hier vorgestellte Schaltung arbeitet mit zwei Langewelle-Empfängern, die auf einer freien Frequenz (ungefähr 100kHz) ständig in den Äther hören. Sobald ein Signal erfasst wird, wird es von einem Mikrocontroller untersucht. Typisch für Blitze sind schnell ansteigende Flanken (im Lautsprecher als “Knacken” vernehmbar), die mit Hilfe des Analog-Digital-Converters (ADC) im Mikrocontroller selektiert und vermessen werden. Die Software sammelt die Anzahl der registrierten Blitze und die Summe der Pegel, die für jeden Blitz erreicht werden. Die Daten werden dann auf einem graphischen Display angezeigt. Das Display zeigt nicht nur die aktuell gemessenen Werte, sondern auch den Verlauf über die letzten Stunden. Die Daten erlauben die Analyse des Alarm-Levels, der über Leuchtdioden angezeigt werden. Darüber hinaus gibt es eine Trend-Analyse, um abzuschätzen, wie sich die Gewitteraktivitäten entwickeln werden.

Block-Diagramm
Block-Diagramm

Der analoge Schaltungsteil des vorherigen Gewitter-Monitors mit AM-Empfängern und der Peak-Detection-Schaltung hat sich bewährt und wurde übernommen. Die einzige Veränderung bezieht sich auf die Zeitkonstante der Peak-Detection, was weiter unten beschrieben wird.

Es gibt aber drei wesentliche Neuerungen:

(1) Die erste Veränderung trägt dem Feedback von einigen Nachbauten Rechnung. Basierend auf der zunehmenden Popularität des Arduino-Frameworks habe ich mich entschlossen, den ATmega-Prozessor im DIL-Gehäuse durch einen Arduino Nano zu ersetzen. Ich muss zugeben, dass ich für meine Entwicklungen lieber direkt mit den AVR-Prozessoren arbeite. Die  Arduino-Boards waren bisher immer gross, teuer und unhandlich für die Entwicklung von eigenständigen Geräten. Mit der Verfügbarkeit des kleinen und preisgünstigen Arduino Nano-Boards hat sich das aber verändert. Die kleine Platine hat ein komplettes Kernsystem für den ATmega-Prozessor an Board, was den Aufbau vereinfacht. Es bleibt der Nachteil, dass der Arduino im Vergleich zu einem “nackten” ATmega-Prozessor relativ strom-hungrig ist, da die Peripherie, z.B. das USB-Interface und die eingebaute Leuchtdiode, immer unter Spannung stehen. Da der Gewitter-Monitor aber wahrscheinlich nicht mit Batterie-Strom betrieben wird, ist dieser Nachteil verschmerzbar.

Arduino Nano
Der Arduino Nano hat ein komplettes System auf einem kleinen Board: ATmega328 Prozessor, 16 MHz Quarz, USB-Interface, LEDs und Spannungsregler. Sogar ein Reset-Taster ist dabei.

(2) Die zweite Veränderung bezieht sich auf das Graphik-Display. Bei dem vorher verwendeten ST7920 gab es Beschaffungsprobleme für die SPI-Variante. Außerdem fand ich die u8glib-Bibliothek unhandlich. Was lag also näher als eines der gut verfügbaren KS0108-Displays einzusetzen und mit dem Smart-I2C-Interface zu kombinieren, das an anderer Stelle beschrieben ist (siehe Universelles I2C-Interface für Graphik-LCD). Konkret habe ich mich für das 4-Zoll Display (Typ 3) entschieden, dass mit einer Auflösung von 192 x 64 Pixeln bei ansprechender Größe neue Möglichkeiten eröffnet. Das Graphik LCD-Display ist z.B. bei eBay unter dem Stichwort 4″ 192×64 Graphic LCD Modul Display in verschiedenen Farben erhältlich.

(3) Und schließlich war es an der Zeit, die Software komplett zu überarbeiten. Der neue Gewitter-Monitor hat einige neue Funktionen bekommen, speziell zur Analyse des Alarm-Status und der Trend-Entwicklung. Außerdem lässt sich das neue System flexible konfigurieren über ein Menü zur Einstellung der wichtigsten Parameter. Die Software wurde komplett in der Arduino-Entwicklungsumgebung geschrieben und kann mit dem vorhandenen USB-Interface des Arduinos in den Programmspeicher übertragen werden. Ein separater AVR-Programmer ist dafür nicht notwendig.

Die Schaltung

Der neue Gewitter-Monitor besteht aus dem Antennen-Modul, das wie oben beschrieben ohne Veränderung von der vorherigen Version übernommen wurde, und dem Hauptteil, das für die Auswertung und Anzeige zuständig ist. Das Antennen-Modul wird über ein 6-poliges Flachband-Kabel mit dem Hauptteil verbunden.

Gewitter-Monitor Antennen Modul
Schaltung des Antennen Modul mit dem AM-Empfänger IC TA7642 und einer Ferritstab-Antenne. Dieses Modul ist zweimal vorhanden.

Hier arbeitet ein AM-Empfänger mit dem TA7642 und einem Ferritstab als magnetische Antenne. Der Ferritstab hat eine Richtwirkung mit engen aber stark ausgeprägten Minima in Richtung der Ferritstab-Achse. Deshalb werden zwei Empfänger eingesetzt, die senkrecht zueinander stehen. So wird eine lückenlose Abdeckung von 360 Grad erreicht.

Gewitter Monitor Antenne-Modul
Antenne-Modul mit zwei senkrecht zueinander angeordneten Ferritstäben

Der Hauptteil des Gerätes verwendet, wie oben beschrieben, einen Arduino Nano und das Smart-I2C-Graphic-Display.

Schaltung des neuen Gewitter Monitors
Schaltung des neuen Gewitter Monitors

Das Signal vom Antennen-Modul wird mit einem Transistor (BC549C) verstärkt und dann an die Peak-Detection-Schaltung mit dem Operationsverstärker MCP602 geführt. Diese Schaltung folgt ansteigen Flanken sehr schnell und lässt das Signal dann langsam abfallen. So wird es möglich, mit dem naturgemäß relativ langsamen ADC die kurzen Blitz-Impulse sicher zu erfassen. Die Funktionsweise der Peak-Detection lässt sich mit dem Oszilloskop beobachten.

Eingang und Ausgang der Peak-Detection.
Eingang (untere Kurve) und Ausgang (obere Kurve) der Peak-Detection-Schaltung. Die Anregung erfolgt mit einem kurzen Impuls von wenigen µsec und erzeugt eine abfallende Flanke über mehrere msec. Der ADC tastet den Eingang mit einer Periode von 500µsec ab (4 mal pro Raster-Einheit).

Im Gegensatz zum vorherigen Gewitter-Monitor wurde die ADC-Abtast-Frequenz erhöht auf 2000 Hz pro Kanal. Entsprechend konnte die Zeitkonstante der Peak-Detection-Schaltung reduziert werden (10nF statt vorher 100nF). Dadurch erreicht das neue System eine etwa 10-fach höhere zeitliche Auflösung. Ereignisse, die vorher als ein einziger Blitz registriert wurden, können jetzt in mehrere diskrete Blitze aufgelöst werden.

Rechts oben im Schaltplan befindet sich die Spannungsversorgung für das Antennen-Modul. Die Empfangs-Empfindlichkeit der TA7642-ICs lässt sich über die Versorgungsspannung einstellen. Die Schaltung verwendet eine Band Gap-Referenz (TL431), die exakte 2.5V produziert, und einen Emitterfolger mit einem weiteren BC549C. Mit einem Trimmer kann der gewünschte Spannungswert gewählt werden. Ich habe die besten Erfahrungen mit 1.6V gemacht, gemessen am Eingang des Antennenmoduls (vor der Diode).

Für die Bedienung des Geräts sind drei Tasten verfügbar, die mit Select, Down und Up bezeichnet sind. Die Tasten befinden sich zusammen mit den Leuchtdioden für die Anzeige des Alarm-Levels und der Blitz-Erkennung auf einer kleinen Platine, die hier als Front-Panel bezeichnet wird.

Die Spannungsversorgung der Schaltung geschieht über ein externes Stecker-Netzteil, das mindestens 7V liefern sollte. Der Strombedarf ist abhängig von der eingestellten Beleuchtungsstärke des Displays und liegt maximal bei 150mA. Intern arbeitet ein Festspannungsregler. Zwar hat der Arduino einen eigenen 5V-Regler an Board, den man auch zur Versorgung der anderen Schaltungsteile heranziehen könnte. Ich habe mich aber für eine separate Versorgung entschlossen, weil der Regler auf dem Arduino bei größeren Eingangsspannungen vom Stecker-Netzteil (in meinem Fall 12V) und Strömen oberhalb von 100mA durchaus recht warm werden.

Damit ist die Schaltung beschrieben. Durch den Einsatz des integrierten Arduino Nano-Boards ist der Aufbau überschaubar. Dazu trägt auch die Tatsache bei, dass der Audio-Verstärker, der in der vorherige Version vorhanden war, hier weggelassen wurde.  Es hat sich gezeigt, dass beim laufenden System keine Notwendigkeit besteht, das Signal akustisch auszugeben.

Rückseite des Gewitter Monitor v2
Ein Blick hinter die Kulissen: Im Inneren geht es recht aufgeräumt zu. Das Arduino-Board sitzt auf zwei 15-poligen Pfostenleisten, so dass man es im Falle eines Falles leicht austauschen kann. Die USB-Micro Buchse ist gut zugänglich.

Vollständig neue Software

Der Umstieg auf Arduino und das Smart-I2C-GLCD erforderte eine vollständige Überarbeitung der Software. Das Grundprinzip ist aber weitgehend gleich geblieben.

Die Software besteht aus 4 Modulen:

  • GewitterMonitor_v2.ino ist das Hauptprogramm mit den setup() und loop()-Funktion und der Timer-getriebene Interrupt-Routine
  • GewitterMonitor_graphs.ino beinhaltet die Funktionen zur Anzeige auf dem Display
  • GewitterMonitor_stats.ino beherbergt die Statistik-Funktionen
  • GewitterMonitor_config.ino ist für die Verwaltung der Konfiguration zuständig.

Zusätzlich benötigt das Programm die Arduino-Bibliothek für das Graphik-Display glcd_functions. Um erfolgreich zu kompilieren, muss sich der glcd_functions-Folder im librariers-Ordner der Arduino-Sketche befinden – – wie bei Arduino üblich.

File-Struktur im Arduino-Verzeichnis
File-Struktur im Arduino-Verzeichnis

Alle Dateien sind auf der Ressourcen-Seite zum Download verfügbar. Für die glcd_functions-Bibliothek muss darauf geachtet werden, dass die Version vom 21-Feb-2018 (oder neuer) benötigt wird.

Kernstück der Software ist die Interrupt-Routine im Hauptprogramm. Diese Routine wird durch einen Timer-Interrupt 4000 mal pro Sekunde aufgerufen und liest wechselweise die ADC-Kanäle 0 und 1 ein. Das bedeutet, dass die Ausgänge vom Antennen-Modul wie oben beschrieben 2000 mal pro Sekunde abgefragt werden. Wenn sich zwei aufeinanderfolgende Werte um einen bestimmten Wert unterscheiden, wird dieses als Erkennung einer ansteigenden Flanke gewertet und entsprechend ein Blitz registriert. Der Schwellwert zur Blitzerkennung ist ein wichtiger Parameter des Systems, der sich konfigurieren lässt. Kleinere Werte bedeuten höhere Empfindlichkeit aber auch höhere Anfälligkeit gegen Störsignale. Größere Werte bedeuten geringere Empfindlichkeit. Als Default-Wert wird 150 eingesetzt, was bei dem Wertebereich des ADC von 1024 also etwa 15% Ausschlag entspricht. Damit habe ich die besten Erfahrungen gemacht.

Wenn der Trigger für die Blitzerkennung ausgelöst wurde, ermittelt das Programm den Maximalwert des Blitzes. Für jede Minute speichert das Programm die Anzahl der Blitze (flash_cnt) und die Summe der gemessenen Blitz-Maxima (flash_sum) in einem Array ab. Die Werte der letzten 150 Minuten stehen zur Anzeige zur Verfügung.

4 verschiedene Alarm-Level

Der Alarm-Level soll Auskunft geben, wie stark oder möglicherweise gefährlich die derzeitige Gewitter-Aktivität ist. Diese Information mag den interessierten Wetterbeobachter erfreuen oder hilfreich sein, um z.B. ein geplantes Picknick im Freien zu verschieben oder eine windanfällige Markise einzufahren.

Das System kennt 4 verschiedene Alarm-Level. Der Alarm-Level 1 schaltet die Display-Beleuchtung ein. Die Idee ist, dass das Gerät bei geringer Gewitteraktivität selbstständig um Aufmerksamkeit bittet. Die folgenden Alarm-Level 2 bis 4 werden über die drei roten Leuchtdioden als aufsteigende Reihe angezeigt.

Für die Berechnung des Alarm-Levels werden die Daten der letzten 15 Minuten herangezogen, sofern nichts anderes eingestellt wurde. Das Zeitfenster für die Auswertung des Alarm-Levels (alarm window) ist wiederum ein Parameter, den der Anwender konfigurieren kann. Der Default-Wert von 15 Minuten kann zwischen 5 und 60 Minuten gewählt werden.

Die Datenbasis für den Alarm-Level ist das Array mit den Summenwerten der Blitze pro Minute (flash_sum). Das Programm erlaubt zwischen zwei verschiedenen Algorithmen zu wählen. Entweder wird der Durchschnittswert (average) der Summenwerte berechnet, oder es wird der Maximalwert (max) gesucht. Durchschnittswerte sind robuster gegen einzelne Ausreißer, aber sie reagieren auch träger auf Veränderungen. Hier muss man Erfahrung sammeln, um zu schauen, was sich am Besten eignet. Für meinen Einsatz bevorzuge ich die Durchschnittsberechnung. In jedem Fall liefert das Programm einen Wert, entweder Durchschnitt oder Maximum, der dann mit einem Alarm-Schwellwert verglichen wird. Wird der Schwellwert übertroffen, dann wird der Alarm-Level 1 ausgelöst. Die anderen Alarm-Level verhalten sich entsprechend einer geometrischen Reihe:

  • Durchschnitt oder Maximum > Schwellwert: Alarm-Level 1
  • Durchschnitt oder Maximum > 2 * Schwellwert: Alarm-Level 2
  • Durchschnitt oder Maximum > 4 * Schwellwert: Alarm-Level 3
  • Durchschnitt oder Maximum > 8 * Schwellwert: Alarm-Level 4

Man kann leicht erkennen, dass es bei einem Alarm-Level 4 wirklich gewaltig kracht und das Gewitter in direkter Nähe ist.

Der Schwellwert des Alarm-Levels (alarm threshold) lässt sich ebenfalls konfigurieren. Man kann sowohl den gewünschten Algorithmus (Durchschnitt oder Maximum) als auch den Wert selber wählen. Als Default-Wert verwendet das System die Durchschnitts-Methode bei einem Schwellwert von 50.

Ich muss an dieser Stelle erwähnen, dass ich aufgrund der winterlichen Wetterlage bisher noch keine praktische Erfahrung mit den Alarm-Leveln sammeln konnte. Sicherlich wird es im Laufe des Sommers den einen oder anderen Software-Update geben.

Anzeige und Bedienung

Nach diesen Erklärungen sind das Display und die Bedienelemente hoffentlich einleuchtend.

Display und Bedienungselemente
Display und Bedienungselemente

Das 4-Zoll Display macht den Hauptteil der Frontseite aus. Es gibt dort zwei Bereiche: Auf der rechten Seite werden die aktuellen Messdaten für die laufende Minute gezeigt. Count steht für die Anzahl der registrierten Blitze in dieser Minute, und Sum für den Summenwerte über die gemessenen Maxima. Der Sekundenzähler unten in Bild läuft immer von 0 bis 60. Sobald die Minute voll ist, werden die aktuellen Daten übernommen und in den Verlauf eingefügt, der dabei um eine Reihe (= eine Minute) nach links verschoben wird.

Im rückblickenden Verlauf steht jede vertikale Linie für die Summe der Blitze während einer Minute. Am oberen Rand ist die Zeit-Skala angedeutet. Die kleinen Striche stehen für jeweils 15 Minuten, und der etwas größere Strich für eine Stunde. Man muss beachten, dass die vertikale Achse automatisch skaliert wird, um auch den größten gemessenen Wert sinnvoll abbilden zu können. Wenn z.B. der größte gemessene Wert 350 ist, dann wird die Skala bis 500 skaliert, usw.

Die gepunktete vertikale Linie zeigt das Zeitfenster für die Berechnung des Alarm-Levels. Alle Werte zwischen der gepunkteten Linie und dem rechten Rand (der aktuellen Zeit) werden für die Berechnung der Alarm-Level herangezogen. Wenn man im Konfigurations-Menü das Zeitfenster anders einstellet, wandert die gepunktete Linien an ihren neuen Platz.

Oben links im Display wird das Maximum (max) und der Durchschnittswert (avg) für das aktuelle gewählte Zeitfenster angezeigt.

Die restlichen Bedienelemente sind schnell erklärt. Die beiden gelben Leuchtdioden oben leuchten kurz auf (200 msec), sobald ein Blitz registriert wurde. Mit den beiden gelben Tasten lassen sich die Graphik-Bildschirme durchschalten. Man kann entweder die Anzeige der Summen oder die Anzeige der Anzahl der Blitze wählen. Schließlich ist die rote Taste ist für das Konfigurations-Menü vorgesehen.

Die Beleuchtung der Anzeige schaltet sich nach einiger Zeit ab, sofern kein Alarm-Level gesetzt ist. Wenn der neugierige Zeitgenosse trotzdem die aktuellen Daten sehen möchte, genügt ein Druck auf eine der Tasten, womit die Beleuchtung für 2 Minuten aktiviert wird.

Trend-Analyse

Interessanter als der Verlauf der letzten Minuten oder Stunden ist eigentlich das, was kommt. Wird das Gewitter noch stärker werden oder zieht es ab? In die Zukunft schauen kann der Gewitter Monitor natürlich nicht. Aber die Daten der Vergangenheit geben Anhaltspunkte. Zu dem Zweck habe ich eine Trend-Analyse in das Programm aufgenommen. Genaugenommen wird eine lineare Regression nach der Methode der kleinsten Quadrate über die Werte der letzten 45 Minuten (Regressions-Zeitfenster) berechnet und angezeigt. Die resultierende Gerade kann ansteigen oder abfallen, was sich in einem positiven oder negativen Steigungs-Koeffizienten erkennen lässt. Dieser Wert wird angezeigt – allerdings nur, wenn der Korrelations-Koeffizient grösser als 0.5 ist und somit einen sinnvollen Zusammenhang zeigt. Positive Werte bedeuten also, dass die Gewitter-Aktivität in den letzten 45 Minuten zugenommen hat, während negative Werte die Abnahme anzeigen. Wenn die Messwerte durchgängig bei 0 liegen oder wild streuen, so dass die Statistik keinen Trend erkennen lässt, dann wird im Display nur “–.-” angezeigt.

Trend-Analyse mit linearer Regression.
Das Schlimmste ist vorbei! Trend-Analyse mit linearer Regression.

Die Trend-Analyse kann jederzeit mit den Up– oder Down-Tasten dazu geschaltet werden. Die beiden gelben Tasten laufen hintereinander 4 verschiedene Darstellungen durch:

  • Zeitverlauf der Summenwerte (Flash Sum)
  • Zeitverlauf der Summenwerte zusätzlich mit Trend-Analyse (Flash Sum + Trend)
  • Zeitverlauf der Anzahl der Blitze pro Minute (Flash Count)
  • Zeitverlauf der Anzahl der Blitze pro Minute mit Trend-Analyse (Flash Count + Trend)

Auch hier gilt die oben gemachte Bemerkung, dass ich bisher noch keine Erfahrung mit dieser Funktion sammeln konnte. Der Sommer wird möglicherweise Software-Updates bringen.

Konfiguration

Wie oben beschrieben gibt es die Möglichkeit, die wichtigsten System-Parameter zu konfigurieren und an den Aufstellungsort und die Anwender-Vorlieben anzupassen. Ein langer Druck (> 3 sec) auf die rote Select-Taste führt in das Konfigurations-Menü.

Konfigurations-Menü
Konfigurations-Menü

Die Auswahl des gewünschten Parameters erfolgt über die Up– and Down-Tasten. Dann führt ein Druck auf die rote Select-Taste in die Einstellung für den gewählten Wert.

Editieren eines Konfigurations-Wertes
Editieren eines Konfigurations-Wertes

Mit den Up– und Down-Tasten kann der gewählte Werte verändert werden. Ein weiterer Druck auf Select übernimmt den neuen Wert und führt zurück zum Menü. Mit der Anwahl des letzten Eintrags, Exit, verlässt man das Konfigurations-Menü.

Alle Parameter haben Default-Werte, die bei dem ersten Programmstart eingestellt werden. Diese Werte haben sich bisher als eine gute Wahl während des Testbetriebs herausgestellt.

Hier eine kurze Zusammenfassung der Konfigurations-Parameter mit ihren Default-Werten

  • Trigger threshold ist die minimale Schrittgröße zwischen zwei aufeinander folgenden Messwerten, um eine ansteigende Flanke als Blitz zu interpretieren. Kleine Werte erhöhen die Empfindlichkeit aber auch die Anfälligkeit gegenüber Störsignalen.
    Default-Wert: 150
  • Alarm window ist die Anzahl der letzten, verstrichenen Minuten, die zur Berechnung des Alarm-Levels herangezogen werden.
    Default-Wert: 15
  • Alarm threshold selektiert den Algorithmus (Durchschnitt oder Maximum) und den Schwellwert für das Setzen der Alarm-Levels. Alarm-Level 1 wird beim Überschreiten des einfachen Schwellwertes aktiviert, Alam-Level 2 beim doppelten Wert, usw.
    Default-Wert: Avg 50
  • Display light setzt die Helligkeit des Displays. Die Werte reichen von 0 (= 0%, aus) bis 10 (= 100%, maximale Helligkeit).
    Default-Wert: 8
  • Startup screen selektiert die Graphik, die nach dem Einschalten des Geräts gezeigt wird. Mögliche Werte sind:
    Flash Sum
    Flash Sum + Trend
    Flash Count
    Flash Count + Trend
    Default-Wert: Flash Sum

Es bleibt noch zu erwähnen, dass alle Konfigurations-Parameter im EEPROM des Prozessors abgelegt werden. Damit bleiben die eingestellten Parameter auch nach dem Abschalten (oder beim Überwintern im Bastelkeller) erhalten – wie es sich für eine Anwendung dieser Art gehört.

Aufstellung und Einstellung

Zum Schluss ein paar Tips für die Inbetriebnahme. Das Gerät empfängt die magnetische Komponenten der elektromagnetische Schwingungen von Blitzen. Zum Glück sind die magnetischen Störfelder im Haus meist weniger stark ausgeprägt als die elektrischen Felder. Trotzdem reagiert das Gerät empfindlich auf Störsignale von manchen Schaltnetzteilen. Das ist natürlich insbesondere relevant für das Stecker-Netzteil, mit dem das Gerät betrieben wird. Hier gibt es deutliche Unterschiede. Im Zweifelsfall ist es keine schlechte Idee, etwas mehr Geld zu investieren. In meinem Fall steht das Gerät in einer Fensterecke, die sich ein bisschen abseits von den anderen Geräten befindet.

Es kann sinnvoll sein, das Gerät an eine Erdung (z.B. Wasserleitung, Heizung, etc.) anzuschliessen. In meinem Fall konnte ich dadurch den Störpegel deutlich reduzieren und die Empfindlichkeit weiter erhöhen. Das sollte aber nicht unbedingt notwendig sein.

Für die erste Inbetriebnahme sollte die Versorgungsspannung des Antennen-Moduls am Trimpoti auf 1.6V eingestellt werden. Für die Software empfehlen sich die Default-Parameter wie oben beschrieben.  Nach dem Einschalten – und in Abwesenheit von Gewittern – sollten die Blitz-Indikatoren nicht aufleuchten. Wenn es gelegentlich ein Signal gibt, dann ist das normal und kommt wahrscheinlich von technischen Geräten. In jedem Fall sollten die Werte aber deutlich unter der Schwelle für den ersten Alarm-Level liegen.

Zum Testen der Funktion nehme ich eine alte 9V-Batterie, die ich mit einem etwa 30 cm langen Drahtstück ein paar Mal kurz schließe. Wenn ich das in etwa 20 bis 30 cm Entfernung von der Antenne mache, sollte das Gerät dies als Blitze registrieren. Wenn das geklappt hat, ist alles in Ordnung und man kann entspannt auf die nächste gewittrige Wetterlage warten.

Es gibt zwei Optionen, die Empfindlichkeit zu verändern. Zuerst kann man mit dem Trimmpoti die Antennen-Spannungsversorgung vergrößern (höhere Empfindlichkeit) oder verkleinern (geringere Empfindlichkeit). Als zweite Option, und besonders um unerwünschte Störsignale heraus zu filtern, kann der Konfigurations-Parameter Trigger Threshold verkleinert (höhere Empfindlichkeit) oder vergrößert (geringere Empfindlichkeit) werden.  Tatsächlich ist eine sehr große Empfindlichkeit gar nicht wünschenswert. In Sommernächten kann das Gerät Reichweiten von vielen 100 km erreichen. Dann ist es fraglich, ob man sich ein Gewitter in den fernen Alpen anzeigen lassen möchte.

Zum Schluss

Ich hoffe, dass der neuen Gewitter Monitor v2 das Interesse des einen oder anderen Bastlers weckt. Wie immer freue ich mich über Rückmeldungen, Kommentare, Vorschläge oder Ideen. Natürlich bin ich auch gerne bereit, bei der Beschaffung von Komponenten zu unterstützen oder Prozessoren zu programmieren.

Die Software hat ein gewisses Mass an Komplexität erreicht – und wird sicherlich allerlei Fehler enthalten. Auch hierzu sind Rückmeldungen willkommen. Ich gehe davon aus, dass es bei Gelegenheit Updates geben wird, die – Arduino sei Dank – über die USB-Schnittstelle sehr einfach eingespielt werden können.

Die Software für dieses Projekt kann von der Ressourcen-Seite herunter geladen werden.

 

 

 

 

 

 

Nano-Scope: Oszilloskop mit Arduino und Graphic LC-Display

Kürzlich wurden in der Zeitschrift Elektor Mini-Oszilloskope besprochen. Es sind einfache Geräte, die im Kern einen Mikrocontroller mit schnellem Analog-Digital-Converter (ADC) aufweisen und mit einem einfach Display versehen sind. Die Ergebnisse sind durchaus beachtlich. Durch diesen Bericht animiert stellte ich mir die Frage: Kann man so etwas mit ganz einfachen Mitteln machen? Wie weit kommt man mit dem Arduino Nano, basierend auf einem ATmega32 mit 16MHz Taktfrequenz und einem I2C Graphic-LCD?

Die Frage lässt sich nur durch einen Test-Aufbau beantworten. Von Anfang an war klar, dass es kein “konkurrenzfähiges” Produkt werden soll. Die getesteten Mini-Oszilloskope sind so günstig zu kaufen, zum Teil als Bausatz, dass es keinen Sinn macht, ein vergleichbares Gerät selbst zu entwickeln. Hier geht es also um das Prinzip. Und wie immer gibt es dabei viel zu lernen.

Vorüberlegungen

Der ATmega32 hat einen ADC auf dem Chip, der bereits mit einer sample-and-hold Schaltung ausgerüstet und somit für die Erfassung dynamischer Spannungsverläufe gut geeignet ist. Die Grundidee ist ganz einfach: Ein regelmäßiger Timer-Interrupt liest mit Hilfe des ADC den aktuellen Spannungswert am Eingang ein. Mit einer Trigger-Logik wird festgestellt, ob ein (einstellbarer) Schwellwert (trigger threshold) überschritten wurde. Wenn das der Fall ist, erfolgt die Datensammlung in ein Array. Sobald das Array gefüllt ist, wird es als Kurve auf dem Display ausgegeben.

Die Auflösung des ADC beträgt 10 Bit, also Werte von 0 bis 1023, was mehr als genug ist für diese Anwendung. Tatsächlich ist das Display mit seinen vertikalen 64 Pixeln, also 6 Bit, der beschränkende Faktor. Im Programm wird die Auflösung in zwei Stufen reduziert. Zuerst einmal wird nur das höherwertige Byte des ADC ausgelesen. Das lässt sich einfach machen, indem das Flag for left adjusted result (ADLAR) gesetzt wird. Es bleiben also 8 Bit. Diese Auflösung wird für die Trigger-Logik verwendet. Für die Anzeige der Kurve auf dem Display wird der Wert dann noch einmal um 2 Bit nach rechts verschoben, wodurch der verbleibende Wertebereich 0 bis 63 beträgt und somit gut auf das Display passt.

Der ADC arbeitet hier mit der internen Spannungsreferenz des ATmega von knapp 1.1V, wodurch der Messbereich festgelegt ist. Für diese einfache Anwendung habe ich auf eine analoge Verstärkung oder Aufbereitung des Signals, wie es für ein praktisch einsetzbares Oszilloskop unabdingbar wäre,  verzichtet.

Eine kritische Frage ist die errechbare Geschwindigkeit des Daten-Samplings, ein wichtiges Qualitätsmerkmal jedes digitalen Oszilloskops. Der ADC des ATmega ist von einfacher Bauweise. Deshalb sollte man keine zu großen Ansprüche stellen. Der ADC wird mit einem internen Takt versorgt, der mit einem Vorteiler aus dem Systemtakt generiert wird. Das ATMEL Datenblatt empfiehlt Taktraten zwischen 50 und 200 kHz. Außerdem kann man dort erfahren, dass eine Umwandlung 13 Taktzyklen benötigt. Bei 200 kHz würde eine Umwandlung also 65µsec benötigen, was einer Sampling-Frequenz von 15 kHz entspricht. Geht es schneller? Das Datenblatt erwähnt, dass höhere Taktraten möglich sind, wenn man nicht die volle Auflösung von 10 Bit benötigt. Da in dieser Anwendung nur die höheren 8 Bit verwendet werden, habe ich mich für eine Taktrate von 500 kHz entschieden. Dadurch sinkt die Umwandlungszeit auf  26µsec, entsprechend 38kHz. Tatsächlich läuft die Abfrage des ADC mit einem Interrupt von 20kHz, was sich in den Experimenten als eine stabile Frequenz erwiesen hat. Man sollte aber nicht unterschätzen, dass der Prozessor damit unter signifikanter Systemlast steht.

Hardware

Der Aufbau ist minimalistisch und lässt sich schnell auf eine Steckbrett zusammen setzen. Im Zentrum stehen der Arduino Nano und das Graphik-Display, die über den I2C-Bus miteinander verbunden werden. Alle Details zum Graphik-Display sind an anderer Stelle beschrieben (Universelles I2C Interface für Graphik LC-Displays)  Die Stromversorgung kommt über den USB-Port des Arduino. Das zu messende Eingangssignal gelangt über einen Kondensator an den Analog-Port A0 des Arduino. Ein lineares 100kOhm-Poti fügt eine feste Gleichspannung hinzu, womit die vertikale Position eingestellt werden kann. Für die interne ADC Spannungsreferenz wird noch ein 100nF-Kondensator gegen Masse am Referenz-Pin benötigt.

Eine LED am digitalen Port D6 dient als Anzeige des Trigger-Modus. Schließlich gibt es noch vier Tasten an den digitalen Ports D2 bis D5, über die die horizontale Zeitachse und der Trigger-Level eingestellt werden können. Damit ist der Aufbau auch schon beschrieben.

Schaltplan des Arduino Nano-Oszilloskops
Schaltplan des Arduino Nano-Oszilloskops

Software

Der Arduino-Sketch sieht komplizierter aus als er ist. Allerdings habe ich für die Steuerung von Interrupt und ADC nicht die Arduino-Funktionen gewählt, sonder die ATmega-Register direkt angesprochen. So war es möglich, das enge Timing besser unter Kontrolle zu halten. Zum Glück ist all das innerhalb der Arduino-IDE ohne Probleme möglich.

Eine zentrale Funktion ist die Interrupt-Service-Routine (ISR), die mit 20kHz aufgerufen wird. In dieser Routine wir der ADC ausgelesen und gleich wieder gestartet für den nächsten Durchlauf. Darauf folgt die Trigger-Logik und bei gesetztem Trigger das Abspeichern der Daten im Array für die Anzeige. Im zweiten Teil der ISR werden die vier Tasten (im Programm-Code etwas hochtrabend als keyboard bezeichnet) abgefragt und entprellt. Es gibt sogar eine einfache Tasten-Repeat-Funktion.

Die Sampling-Frequenz lässt sich über die Tastatur herunter regeln, was einer langsameren Zeitachse entspricht. Die Tabelle sar_table enthält die möglichen Einstellungen. Die derzeitige Software erlaubt 7 verschiedene Werte. Die ISR läuft in jedem Fall mit 20kHz. Für langsamere Einstellungen werden mehrere ADC-Werte gemittelt wie in der Tabelle angegeben (interrupts per sample).

Einstellbereich der Zeitachse.
Einstellbereich der Zeitachse. Die Werte werden über die Taster angewählt. Der aktuelle Wert für die Dauer eines Rasterfeldes (time per grid) wird in der unteren, rechten Ecke des Displays angezeigt.

Die setup()-Funktion erledigt alle Systemeinstellung und zeichnet die statischen Elemente auf den Bildschirm. Die loop()-Funktion schließlich wartet auf das Signal von der Interrupt-Routine, dass Daten zur Anzeige bereitstehen, und erledigt dieses mit der glcd-Funktion draw_function(). Die Software ergänzt den Kurven-Verlauf mit einem Raster aus gepunkteten Linien. Außerdem werden im Hauptprogramm mögliche Aktivitäten des Keyboards ausgewertet.

Für die Datenanzeige gibt es noch eine erwähnenswerte Besonderheit. Bevor ein neuer Kurvenzug gezeichnet werden kann, muss vorher der bestehende, “alte” Kurvenzug gelöscht werden. In einer ersten Version des Programmes hatte ich dazu den Bildschirm vollständig gelöscht, was allerdings ein deutlich sichtbares Flackern zur Folge hatte. Eleganter geht das mit zwei Datenarrays. Eines enthält die vorherigen, “alten” Daten und wird zum Löschen benutzt, während das zweite die aktuellen, “neuen” Daten hat. Mit jedem Durchlauf wird zwischen den beiden Datenarrays hin- und hergeschaltet. Mit dieser Logik ist die Anzeige weitgehend frei von Flackern.

Anwendung

Die Anzeige ist einfach zu lesen. Die vertikalen, gepunkteten Linien sind das Raster für die Zeitachse. Die horizontale, gepunktete Linie zeigt den aktuelle Trigger-Wert. Am rechten Rand des Bildschirms befinden sich die eingestellten Parameter: der Trigger-Wert und die Zeitachse. Der Wert für die Zeitachse ist die Dauer eines Raster-Feldes in msec.

Zu beachten ist, dass das Gerät nur dann aktiv wird, wenn der Trigger eine ansteigende Flanke erkannt hat. Es gibt also keinen Auto-Modus wie bei anderen Geräten, in dem auch ohne Trigger der Signalverlauf angezeigt wird. Die LED, die bei jedem Trigger kurz aufleuchtet, erweist sich als ein gutes Hilfsmittel.

Anzeige des Nano-Scopes
Anzeige des Arduino Nano Oszilloskops. Der Messbereich liegt zwischen 0 und 1.1V. Die horizontale gestichelte Linie zeigt den aktuellen Trigger-Level, hier 0.1V, und die ms-Angabe am unteren rechten Rand die Zeitdauer eines Rasterfeldes, hier 10 msec.

Fazit

Das kleine Oszilloskop macht sich in der Praxis erstaunlich gut, sofern die vergleichsweise niedrige Abtastrate kein Problem ist. Frequenzen von einigen 100Hz lassen sich sehr gut darstellen. Auch bei 2000Hz (entspricht 10 Datenpunkte pro Schwingung) bekommt man noch einen brauchbaren Eindruck der Signalform. Darüber ist aber kein sinnvolles Arbeiten mehr möglich. Ein schnellerer ADC wäre wünschenswert. Auch die vertikale Auflösung des Displays setzt klare Grenzen. Trotzdem ist es beeindruckend, was mit diesem geringen Materialaufwand und der einfachen Software machbar ist.

Download

Sketch des Arduino Nano Scope: Arduino_Nano_Scope (03-Jan-2018)

 

 

Smart I2C GLCD am Arduino: Ein einfaches Beispiel

Das Smart I2C Graphic LCD kommt inzwischen öfter am Arduino zum Einsatz, wodurch Arduino-Projekte leicht mit graphischen Ausgaben bereichert werden können.

Hier ein einfaches Beispiel: Ein Analogwert – in diesem Fall ein Potentiometer zwischen Masse und Betriebsspannung – wird mit dem ADC ausgelesen und als horizontaler Balken angezeigt.

Horizontale Balken-Anzeige mit dem GLCD
Ein analoger Wert wird als horizontaler Balken angezeigt

Hardware

Für diese Demo verwende ich einen Arduino Nano. Der Aufbau lässt sich schnell auf einer kleinen Lochraster-Platine realisieren. Die Stromversorgung kommt vom USB-Anschluss des Arduinos und versorgt auch gleich das Display mit 5V. Wir brauchen vier Anschlüsse zum Display: +5V, Masse und die beiden I2C-Anschlüsse SCL und SDA. Das Potentiometer wird mit dem analogen Port A0 des Arduino verdrahtet. Damit ist der Aufbau fertig.

Aufbau für das GLCD-Beispiel
Aufbau für das GLCD-Beispiel

Software

Um das GLCD über den Arduino anzusprechen, wird eine Bibliothek gebraucht, die die Graphik-Funktionen bereitstellt. Die glcd-Bibliothek besteht aus 3 Dateien im Ordner glcd_functions:

  • glcd_functions.cpp beinhaltet den Programm-Code für die Graphik-Funktionen. Die Logik ist sehr einfach. Im Prinzip werden die Parameter, z.B. Koordinaten der Linien und Rechtecke, übernommen und mit den entsprechenden Instruktions-Token an die I2C-Schnittstelle gesendet.
  • glcd_functions.h ist die zugehörige Header-Datei und beinhaltet die Definitionen der Graphik-Funktionen
  • keywords.txt listet die Schlüsselwörter der Bibliothek, so dass sie im Arduino-Editor farblich markiert werden

Der Folder glcd_functions mit diesen drei Dateien wird in den Folder libraries der Arduino-Umgebung kopiert. Damit ist alles vorbereitet.

glcd-Bibliothek im Libraries-Folder der Arduino-Umgebung
glcd-Bibliothek im Libraries-Folder der Arduino-Umgebung

Im eigentlichen Programm für diese Beispiel, es ist der Sketch glcd_demo.ino, wird ganz am Anfang die glcd-Bibliothek mit dem entsprechenden #include-Statement eingebunden. Außerdem wird eine Instanz des Graphik-Displays als globale Variable initiiert. Dabei wird die I2C-Adresse, in diesem Fall hexadezimal 20, übergeben. Damit sind alle Graphik-Funktionen verfügbar.

Für den Bar-Graphen gibt es eine Datenstruktur bar_graph, die alle wichtige Daten zusammenfasst, z.B. Koordinaten der linken, oberen Ecke, Länge und Breite, und der aktuell angezeigte Wert. Dazu sind zwei Funktionen vorhanden: draw_bar_graph_frame() zeichnet einen Ramen mit einer Skala von 0 bis 100. Diese Funktion wird ganz am Anfang im setup()-Block aufgerufen. Die zweite Funktion refresh_bar_graph() erzeugt dann den Balken mit dem aktuellen Wert, der vom ADC kommt. Diese Funktion wird im loop()-Block aufgerufen. Schließlich sorgt der Aufruf von delay(100) dafür, dass die Loop etwa 10 mal pro Sekunde durchlaufen wird.

Neben den verschiedenen Zeichenfunktionen zeigt dieses Beispiel auch die Möglichkeiten, die Hintergrund-Beleuchtung zu steuern, wie es z.B. zum Stromsparen bei Batterie-Betrieb notwendig sein kann. Solange der ADC-Wert unverändert bleibt, wird nach Ablauf einer voreingestellten Zeit (verwaltet mi dem Zähler delay_cnt) das Display mit der Funktion dim_on()  dunkel geschaltet. Sobald sich der ADC-Wert verändert, wird die Display-Beleuchtung mit dim_off() wieder auf ihren ursprünglichen Wert zurück gesetzt.

Fazit

Dieses Beispiel soll zeigen, dass das Display sehr einfach in eine Arduino-Anwendung einzubinden ist. Der Aufwand für Hardware und Software ist gering und eröffnet viele Möglichkeiten für ansprechende Darstellungen.

Downloads

  • Arduino-Sketch: adc_bar_graph (28-Dec-2017)
  • Arduino glcd-Bibliothek: siehe Ressourcen, Smart I2C Display, Arduino