Text, Bitmap und Grafik auf dem LCD mit dem Mikrocontroller


Es gehört auch zum guten Ton bzw. zur guten Optik eine kleine LCD als Anzeige für das Mikrocontroller Projekt zu verwenden. Als Beispiel das Projekt MPPT-II welches einige Herausforderungen stellte. Z.B. der Text-Font, welcher Micropython zur Verfügung stellt, ist mehr schlecht als recht. Es gibt ausschließlich einen 8x8-Bit-Font welcher kaum lesbar ist und wenn man ihn hoch-skaliert, sieht er ziemlich hässlich aus. Deshalb wurden für die Messwerte ein eigener Font implementiert wie hier mit dem Source-Code in der Zip-Datei dokumentiert.

Die Beispiele in C und Python, welche die Display Hersteller mitliefern, sind leider sehr aufwändig programmiert. Klar, es soll ja für verschiedene Mikrocontroller und diverse Displays funktionieren. Da sind aber auch Tabellen mit Text Fonts und Grafiken dabei ohne Aufschluss darüber wie diese entstanden sind; Sie sind einfach magisch da. Und dass ein Bildchen in ein Array gepackt eine 2MB großen C-Datei benötigt, ist fast unglaublich; jedenfalls unpraktisch und langsam in der Entwicklungs-IDE.

Hier sind Beispiele, wie man eigene Text-Fonts, Bitmaps und Grafiken einfach und schnell implementieren kann.

Die 3 Grafik-Arten

Es gibt (zumindest hier) 3 Arten von Grafiken:

 

Text Font und Bitmap als Grafik erstellen

Gut geeignet für eine Text-Font Bitmap ist Gimp. Dazu wird ein neues Bild mit entsprechender Größe definiert und dann ein Text mit allen Buchstaben eingefügt. Der Hintergrund muss weiß sein und die Schriftfarbe Schwarz. Beim schreiben des Textes sollte "Kanten glätten" ausgeschaltet werden um scharfe und regelmäßige Buchstaben zu erhalten. Die Schriftart "Liberation-Mono-Bold" eignet sich gut. Die Text-Größe muss entsprechend der gewünschten Größe eingestellt werden. Nach dem Einfügen können noch einzelne Korrekturen nach Wunsch vorgenommen werden, z.B. mit dem Stift, scharfe Kante, ein Pixel groß, Farben schwarz und weiß. Insbesondere kleine Schriften sind teilweise etwas unschön. z.B. könnte man den Punkt in der Null entfernen. Oder die Höhe etwas vergrößern und die wenigen Zeichen anpassen welche über den Rand ragen (z.B. Klammern, j, g, y, ; usw). Auch die Abstände müssen genau sein. Beim 14x9 Pixel Font muss jeder Buchstabe im Feldanfang beginnen, also bei 0, 9, 18, 27 usw. Die fertige Grafik wird dann als .png-Datei exportiert in der entsprechenden Pixel-Größe.

Bei einer Bitmap Grafik zählt nur eine gesamte beliebige Größe. Das Vorgehen sonst ist aber genau wie beim Text-Font.

Bitmap Muster Anordnung

Die Bitmaps sind als Muster angeordnet in einem Array von Integer-Worten (Wort = eine Anzahl Bit bzw. Bytes, z.B. 16-Bit = 2 Byte, 32-Bit = 4Byte). In der Vertikalen sind die einzelnen Bits der Werte, in der Horizontalen fortlaufende Worte. Da die Bit-Breite der Worte oft vom Rechner abhängt, gibt es in der Regel C-Typen mit entsprechender Bitbreite, namentlich uint16_t und uint32_t. Bitmaps bis 16 Bit verwenden also uint16_t, sonst uint32_t. Mehr als 32-Bit geht meist auch, ist aber für Text Fonts auf einem Mikrocontroller normalerweise nicht nötig. Hier ein Beispiel des 14x9 Pixel Fonts.

Um einen Text aufs Display zu schreiben, verwenden wir also eine Funktion, welche den Text verarbeitet (Puts()) und die einzelnen Buchstaben an eine Funktion gibt, welche diese dann schreibt (Putc()). Der Buchstabe wird mit einem Zeiger im Array adressiert, also Zeiger = 9 x Buchstabe. Dann werden die Worte des Arrays einzeln an eine Funktion gegeben, welche das Muster dekodiert und aufs Display bringt (Bitmap()). Dazu wird eine Funktion verwendet, welche die Pixel schreibt (PutPixel()).
PutsXY("Text", Font, X, Y, Fg, Bg, Attr) -> PutcXY('Char', Font, X, Y, Fg, Bg, Attr) -> Bitmap(Map, X, Y, Fg, Bg, Attr) -> PutPixel(X, Y, Color).
Letztere kann dann in den Framebuffer oder direkt aufs Display schreiben.
Dabei ist der Font ein Index, welcher in einer Tabelle auf das Font-Array zeigt. So kann ein Font aus mehreren ausgewählt werden.
X, Y ist die Display-Position. Die Funktionen PutsXY() und PutcXY geben den neuen X-Wert zurück. So kann man Texte und Buchstaben aneinander reihen.
Man kann auch noch Text Arrtibute verarbeiten. So kann ein Text unterstrichen werden, indem immer das letzte Bit gesetzt wird. Oder es können Abstände zwischen den Zeichen eingefügt oder entfernt werden.
PutsXY könnte zusätzlich den Text Links, Mitte oder Rechts ausrichten.
PutBitmap kann außerdem Transparent (nur Vordergrund) oder Noch (nur Hintergrund) schreiben. So ist eine Vielzahl von Optionen für einen Font möglich.

Bei einer Bitmap Grafik gehen wir ähnlich vor. Bei der Codierung werden aber zusätzlich die Dimensionen am Anfang des Arrays eingetragen. So muss man nicht nicht um die Größe kümmern sondern kann einfach den Zeiger auf das Array und die X-Y Position an eine Funktion übergeben.
PutBitmap(Array, X, Y, Fg, Bg, Attr) -> Bitmap(Map, X, Y, Fg, Bg, Attr) -> PutPixel(X, Y, Color).
Für die Attribute gibt es hier aber nur den Transparent und Noch Modus.

Bitmap Bild in ein Array umwandeln

Wir verwenden also das Beispiel mit dem 14x9 Pixel Text Font, welcher mit Gimp erstellt wurde und als Datei _font_14x09.png exportiert. Erst wandeln wir sie in eine unformatierte monochrom Datei. Dazu verwenden wir "convert" aus dem Tool "Imagemagick" mit dem Kommando:
convert -depth 8 +antialias -threshold 50% _font_14x09.png _font_14x09.gray
Das Tool erstellt damit die Datei _font_14x09.gray, welche wir nun mit dem Python-Script gray2c.py lesen und daraus ein C-Array erstellen mit dem Kommando:
./gray2c.py -w=855 -s=16 _font_14x09.gray
Die Breite -w=n und Höhe -s=n kann man mit dem exiftool auslesen bzw. ist von Gimp bei der Erstellung bekannt. Die Bitbreite -s=n muss auf die nächsten 4  aufgerundet werden damit die Anzahl Hex-Ziffern bestimmt werden kann.
Nun erhalten wir die Datei _font_14x09.arr. Sie enthält die C-Definition für das Array _font_14x09[]. Man kann sie in .h umbenennen oder mehrere Fonts, Bitmaps und Grafiken in einer .h Datei zusammenfassen.

Nun auch noch das Beispiel mit der Bitmap-Grafik BitmapCool.png. Erst konvertieren mit:
convert -depth 8 +antialias -threshold 50% BitmapCool.png BitmapCool.gray
Dann wieder mit gray2c.py:
./gray2c.py -w=120 -s=20 -h BitmapCool.gray
Der Schalter -h dient dazu die Dimension in den Header von BitmapCool.arr zu schreiben, sonst ist alles gleich.

Man kann es auch einfacher mit den Shell-Scripts Font_convert und Bitmap_convert erstellen. Diese lesen die Dimension mit exiftool und stellen die richtigen Parameter ein. Beispiel:
./Font_convert _font_14x09.png
./Bitmap_convert BitmapCool.png

Möchte man die Daten für ein Python Programm nutzen, kann man einfach die Array Definition anpassen.

Farb-Grafik erstellen

Für eine Grafik kann man z.B. LibreOffice Draw verwenden. Dazu zur Begrenzung ein Rechteck in entsprechender Größe platzieren. Z.B. für eine 160x128 Pixel Grafik würde man ein Rechteck von 160x128mm erstellen und das Raster auf 1mm Stellen. Danach können beliebige Elemente gezeichnet werden (Farbige Boxen, Kreise, Texte, Logos usw.). Am Ende kann das Begrenzungsrechteck entfernt werden oder die Farben entsprechend eingestellt dass es unsichtbar wird (außer man will es sehen können).

Dann alles markieren (Ctrl-A), exportieren als .png Datei (Option "Auswahl" markieren), die Skalierung wählen (in unserem Fall 160x128 Pixel) und dann speichern bzw. exportieren.

Das fertige .png-Bild kann nochmals z.B. mit Gimp überprüft und wenn nötig auch noch korrigiert werden.

Farbgrafik Codierung

Auch die Farbgrafik ist in einem Array angeordnet. Wir verwenden hier 8-Bit pro Pixel, also 8-Bit Farben mit folgender Anordnung:

Um die 3 Farben Rot, Grün und Blau in ein Byte mit 8-Bit zu packen, werden meist einfach 2-3 Bit pro Farbe genommen, also 3-Bit Rot, 3-Bit Grün, 2-Bit Blau. Rot und Grün hätten dann zwar 8 Stufen, Blau hätte aber nur 4 Stufen, was unausgewogen ist. Deshalb wird hier eine andere Methode verwendet. Für Rot und Blau werden je 6-Stufen verwendet, für Grün 7-Stufen. Wir nehmen an, dass wir ein Bild mit 24-Bit Farben haben, also pro Farbe ein Byte mit dem Wert 0...255. Also reduzieren wir erst auf die 6 bzw. 7 Farbstufen:
Red = int(Red * (6.0 / 255.5));
Grn = int(Grn * (7.0 / 255.5));
Blu = int(Blu * (6.0 / 255.5));

Der Teiler 255.5 wird verwendet um korrekt gerundete Werte zu erhalten. Danach werden die Farben codiert mit:
Pixelfarbe = (36 * Grn) + (6 * Red) + Blu
Somit haben wir den Wert 0 für schwarz und 251 für weiß.

Zum Dekodieren auf die 6-7 Stufen verwenden wir dann:
Grn = Pixelfarbe / 36;
Pixelfarbe -= 36 * Grn;
Red = Pixelfarbe / 6;
Pixelfarbe -= 6 * Red;
Blu = Pixelfarbe;
Danach werden die Farben für das Display auf 6-7 Bit skaliert (16-Bit RGB):
Color = ((Red * 6) << 11) | ((Grn * 10) << 5) | (Blu * 6);

Die Farbwerte 252-255 werden nicht benötigt. Dies erlaubt Steuerwerte einzufügen und eine rudimentäre Daten-Komprimierung zu verwenden. Angenommen, unsere Grafik ist eine Zeichnung mit Flächen und Linien wie das Beispiel-Testbild. Das heißt, es kommen immer wieder mehrere Pixel derselben Farbe hintereinander vor. So wird der Steuerwert 255 für eine Wiederholung verwendet. Die auf den Wert 255 folgende Zahl ist die Anzahl Wiederholungen der letzten Farbe. Also z.B. die Wertefolge 0, 255, 19 bedeutet, Pixelfarbe schwarz, 19 mal wiederholt, also 20 schwarze Pixel.

Im weiteren wird der Wert 254 als Markierung für das Ende der Grafik verwendet.

Und schlussendlich sind die ersten 2 Byte im Array die Dimension des Bildes (H x W), also z.B. 128, 160 bedeutet das Bild ist 128 Pixel hoch und 160 Pixel breit. Sollen Bilder mit Dimensionen von mehr als 255 codiert werden, müsste man die Dimensionen anders codieren, z.B. mit je 2 Byte. Oder als Parameter an die Funktion mit übergeben.

Mit dieser Methode wird das gezeigte Testbild in der C-Source Datei lediglich 24kB groß und belegt für die 20480 Pixel (160x128) nur 5748 Byte Speicher.

Farbgrafik in ein Array umwandeln

Ähnlich wie mit den Bitmaps wandeln wir das Bild erst in eine unformatierte Datei um, diesmal mit Farbe und je ein Byte für Rot, Grün und Blau. Wir verwenden als Beispiel das Textbild.png, erstellt mit LibreOffice Draw:
convert -depth 8 +antialias Testbild.png Testbild.rgb
Danach erstellen wir das C-Array mit rgb2c.py:
./rgb2c.py -w=160 -h=128 Testbild.rgb
Die Breite -w=n und Höhe -h=n kann man mit dem exiftool auslesen bzw. ist von Zeichnungsprogramm bei der Erstellung bekannt.
Man kann es auch einfacher mit den Shell-Scripts Graphic_convert erstellen. Dieses liest die Dimension mit exiftool und stellt die richtigen Parameter ein. Beispiel:
./Graphic_convert Testbild.png
Die Ausgabedatei Testbild.arr enthält die C-Definition für das Array Testbild[]. Man kann sie in .h umbenennen oder mehrere Fonts, Bitmaps und Grafiken in einer .h Datei zusammenfassen.

Applikations-Interface

Hier nochmals alle Scripts und Bilder in einer Zip-Datei. Die Stripts müssen nach dem Kopieren ausführbar gemacht werden (chmod +x scriptname, auch *.py).

In der Zip-Datei ist auch ein Verzeichnis mit einem Beispiel-Source-Code für ein Application-Interface. Voraussetzungen:
Die Datei lcd_api.c enthält Funktionen welche Texte, Logos und Bilder aufs Display schreiben. Sie nutzen die Funktion lcd_Pixel(x, y, color) (Oben als PutPixel(X, Y, Color) beschrieben), welche zur Verfügung stehen muss und eine entsprechende Deklaration inkludiert sein muss. Des weiteren muss die Display Auflösung in den globalen Variablen tftHeight und tftWidth gespeichert und deklariert sein.

Grundsätzlich gibt es 2 Möglichkeiten zum Beschreiben des Displays:

Der Beispiel-Code ist rudimentär und ohne Optionen. Es ist dem Anwender überlassen, was und wie genau das implementiert wird. Interessierte können mir eine Nachricht zukommen lassen um die entsprechenden Source-Dateien für das hier gezeigte 160x128 TFT Display zu bekommen. Im Beispielcode sind folgende Dateien enthalten:
fonts.h        Header mit den Font Tabellen
BitmapCool.h  
Bild und Header der Bitmap
Testbild.h     Bild und Header des Testbildes
CMakeLists.txt
Benötigt der Compiler und Linker
lcd_api.c/-.h 
Application Interface Beispiel Code
lcd.c          Demo Beispiel Code




© 2022-2026 by Stefan Ludescher