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

 

Pointer auf Funktionen: Neue Software für das Graphik-Display

Das Graphik-Display „Smart I2C-GLCD“ hat inzwischen eine ganze Reihe Anwendungsgebiete gefunden. Dabei sind einige neue Funktionen hinzugekommen, z.B. das Zeichnen von gepunkteten Linien oder die Ausgabe von negativen Zahlen. Durch den größeren Funktionsumfang war der Instruktions-Interpreter aber nicht mehr effizient und musste verbessert werden.

Zur Erinnerung: Das Graphik-Display empfängt Instruktionen über das I2C-Interface. Das erste Byte jeder Datensendung ist das Command-Token und bestimmt, welche Funktion ausgeführt werden soll. Z.B steht 17 für das Zeichnen einer Linie oder 32 für die Ausgabe eines Zeichens. Im bisherigen Programm gab es eine Switch/Case-Konstruktion, die sich durch die Liste der möglichen Instruktion-Tokens hindurch hangelte, bis der passende Token gefunden wurde. Dort wurde dann in die entsprechende Graphik-Funktion verzweigt. Das funktionierte gut mit 5 oder 10 möglichen Tokens, macht aber keinen Sinn mehr für die derzeitige Liste von über 30.

Wie geht es besser? Die Idee ist ganz einfach: Eine Tabelle, die für jeden Token einen Zeiger auf die zugehörige Graphik-Funktion. Mit Hilfe dieser Tabelle kann der Instruktions-Token als Index genutzt werden, um ohne Schleifen direkt den jeweiligen Tabellen-Eintrag zu finden.

Allerdings benötigt der Instruktionsinterpreter nicht nur den Zeiger auf die zugehörige Funktion, sondern auch die Anzahl der Parameter, die zum Ausführen der Funktion benötigt werden. Die Graphik-Funktion draw_char() braucht nur einen Parameter, eben das zu zeichnende Zeichen, während die Funktion draw_line() 5 Parameter benötigt, die Koordinaten des Start- und Endpunktes und der Modus. So ergibt sich eine Tabelle mit zwei Spalten: dem Zeiger auf die zugehörige Graphik-Funktion und der Anzahl der Parameter.

Tabelle mit Zeigern auf Funktionen
Tabelle mit Zeigern auf Funktionen

Die Sprache C macht den Umgang mit Zeigern nicht gerade einfach. Auch nach vielen Jahren Programmierpraxis ist die zugehörige Syntax immer wieder verwirrend. Zum Glück konnte ich noch meinen alten „Kernighan & Ritchie“ im Regal finden (meine Ausgabe ist von 1986). Dieses immer wieder erstaunliche Buch zeigt die zugehörige Syntax.

Kernighan & Ritchie, Ausgabe 1986
Kernighan & Ritchie, Ausgabe 1986

Und so ist der neue Instruktions-Interpreter entstanden. Das Kernstück, die zweidimensionale Tabelle, wird mit Hilfe einer Struktur definiert.

Die Formulierung void (*cmd_func)(void) ist ein Platzhalter für die Zeiger auf die jeweiligen Graphik-Funktion. Die Instruktions-Tabelle ist ein Array von der oben definierten Struktur. Sie wird gleich mit den entsprechenden Einträgen initialisiert:

Natürlich müssen die Funktionen, die in der Tabelle auftauchen, entsprechend definiert sein. Hier sind die Funktions-Prototypen gezeigt, die üblicherweise in einer #include-Datei stehen:

Im Instruktions-Interpreter wird das Command-Token über das I2C-Interface eingelesen, der Wertebereich überprüft, und wenn der passt die entsprechende Funktion aufgerufen.

parm_buffer[] ist ein globales Array, in dem der aktuelle Command-Token und die zugehörigen Parameter abgelegt werden. Dieses Array wird von den Graphik-Funktionen genutzt, um die jeweilige Aktion zu steuern. Als Beispiel wird hier die Funktion cmd_draw_line() gezeigt, die die zugehörige Graphik-Funktion zum Zeichnen einer Linie aufruft:

Nach einigen Versuchen funktioniert der neue Instruktions-Interpreter ganz hervorragend. Das Programm ist damit deutlich kürzer und effizienter geworden.

Die neue Software ist bei den Ressourcen zu finden unter Smart I2C Display, Firmware