Auf dem ESP32 mit Touchscreen kannst du nicht nur geometrische Figuren zeichnen, sondern mit etwas Geschick und Zeit auch Dashboards. In diesem Beitrag möchte ich dir daher nun zeigen, wie eine Bildschirmtastatur programmiert wird.
Die hier vorgestellte Bildschirmtastatur wird in einem weiteren Beitrag benötigt, jedoch finde ich das es ein Eigner Beitrag wert ist, weil dieses kleine Feature kannst du für viele Anwendungsfälle verwenden.
Inhaltsverzeichnis
- Wie soll die Bildschirmtastatur am ESP32 mit Touchscreen aussehen?
- Programmieren der Bildschirmtastatur in der Arduino IDE
- Ausgangsstruktur für das Programm
- Schritt 1 – Struktur für die GUI Elemente anlegen
- Schritt 2 – Anlegen der Schaltflächen und speichern im Array
- Schritt 3 – Zeichnen der Schaltflächen
- Schritt 4 – Zeichnen des Eingabefeldes
- Schritt 5 – Anzeigen / Zeichnen der Bildschirmtastatur
- Schritt 6 – Aktionen an der Bildschirmtastatur am ESP32 behandeln
- Schritt 7 – Einbinden der Bildschirmtastatur in die Loop
- Wie geht es nun weiter mit der ESP32 Touchscreen Bildschirmtastatur?
Wie soll die Bildschirmtastatur am ESP32 mit Touchscreen aussehen?
Eine Bildschirmtastatur kennst du bestimmt schon von deinem Handy und genau von dort nehme ich mir eine Vorlage.
Aus dieser Vorlage streiche ich jedoch die Funktion für die Smileys und füge für die Sonderzeichen eine zusätzliche Taste ein.
Hier jetzt ein Bild wie das Ergebnis aussieht, wenn du diesen Beitrag durcharbeitest oder dir ganz bequem vom GitHub Repository StefanDraeger / ESP32_Development_Board_ESP32-2432S028R dir den Code herunterlädst und ausführst.
Programmieren der Bildschirmtastatur in der Arduino IDE
Fangen wir jetzt an und programmieren die Bildschirmtastatur. Wie man geometrische Figuren zeichnet und auf ein Touch Event reagiert, habe ich dir bereits in den nachfolgenden Beiträgen gezeigt.
- ESP32 Development Board mit 2,8 Zoll Touch Display: Programmieren für Anfänger
- ESP32 Development Board: Touchfunktion programmieren
Ausgangsstruktur für das Programm
Zunächst das kleine Programm, welches wir als Basis nutzen möchten:
#define LGFX_USE_V1 #include <LovyanGFX.hpp> #include "lgfx_ESP32_2432S028.h" //Größe der Zeichenfläche definieren #define MAX_X 319 #define MAX_Y 239 //Felder für die ermittelte Position //bei einem klick uint16_t x = 0, y = 0; //Instanz des Displays static LGFX lcd; void setup(void) { //beginn der seriellen Kommunikation mit //115200 Baud Serial.begin(115200); //beginn der Kommunikation mit dem Display lcd.init(); //drehen des Displays lcd.setRotation(1); //füllen des Displays mit der Farbe Schwarz lcd.fillScreen(lcd.color332(0,0,0)); //eine kleine Pause von 100ms. delay(100); } void loop() { if (lcd.getTouch(&x, &y) == 1) { if (x > MAX_X || y > MAX_Y) { return; } } //eine Pause von 50 Millisekunden. delay(50); }
Zusätzlich benötigst du noch die Datei „lgfx_ESP32_2432S028.h“ welche du vom GitHub Repository OttoMeister/ARDUINO_ESP32-2432S028R herunterladen kannst. Nachfolgend eine ZIP-Datei mit dem Code und der benötigten Datei.
Schritt 1 – Struktur für die GUI Elemente anlegen
Zunächst erstellen wir die Strukturen für die Schaltflächen und das Eingabefeld, denn schließlich wollen wir unseren Code effizient und optimiert aufbauen.
Auf der Tastatur findest du zwei verschiedene Buttons, zum einen für die Buchstaben, Zahlen und Sonderzeichen sowie die Funktionstasten, Löschen (BACK), Umschalten (SHIFT), Leerzeichen (SPACE), Schließen (X) und OK. Dafür benötigen wir nun zwei Strukturen.
Schritt 1.1 – Struktur für die Funktionstasten
struct ButtonFunction { uint8_t backgroundcolor; //Hintergrundfarbe uint8_t textcolor; //Textfarbe int coord_x; //X Koordinate der Schaltfläche int coord_y; //Y Koordinate der Schaltfläche int width; //Breite int height; //Höhe int roundEdge; //Wert ffür die Rundung der Ecke String caption; //Text };
Schritt 1.2 – Struktur für die Schaltflächen Buchstaben, Zahlen & Sonderzeichen
Die Buttons haben alle eine gleiche Größe & Farbe und daher entfallen hier die Attribute für backgroundcolor, textcolor, width & height.
struct Button { int coord_x; //X Koordinate der Schaltfläche int coord_y; //Y Koordinate der Schaltfläche String caption1; //Text normal String caption2; //Text umgeschaltet };
Schritt 1.3 – Struktur für das Eingabefeld
Das Eingabefeld hat zusätzlich ein Attribut für eine Rahmenfarbe.
struct InputField { uint8_t backgroundcolor; //Hintergrundfarbe uint8_t bordercolor; //Rahmenfarbe uint8_t textcolor; //Textfarbe int coord_x; //X Koordinate int coord_y; //Y Koordinate int width; //Breite int height; //Höhe };
Schritt 2 – Anlegen der Schaltflächen und speichern im Array
Wie erwähnt schreiben wir unseren Code optimiert, dazu reduzieren wir Redundanzen im Code, wo es geht. Die Schaltflächen werden dazu gleich im Array erzeugt, was jetzt nicht so gut lesbar ist, aber die Codezeilen reduziert (Die Belegung des Speichers bleibt dieselbe.).
Da die Zeilen der Tastatur nicht gleichartig aufgebaut sind, habe ich ein zusätzliches Array implementiert, in welchem die Anzahl der Schaltflächen pro Zeile definiert sind.
//Wieviele Schaltflächen sollen pro Zeile angezeigt werden? int rowButtonCount[4] = {10, 10, 9, 7}; //Maximale Anzahl der Schaltflächen pro Zeile const int NUM_BUTTONS = 10; //Anzahl der Zeilen const int NUM_ROWS = 4; Button keyboard[NUM_ROWS][NUM_BUTTONS] = { { { 0, 0, "1", "!" }, { 0, 0, "2", "\"" }, { 0, 0, "3", ":" }, { 0, 0, "4", "$" }, { 0, 0, "5", "%" }, { 0, 0, "6", "&" }, { 0, 0, "7", "/" }, { 0, 0, "8", "(" }, { 0, 0, "9", ")" }, { 0, 0, "0", "=" } }, { { 0, 0, "Q", "q" }, { 0, 0, "W", "w" }, { 0, 0, "E", "e" }, { 0, 0, "R", "r" }, { 0, 0, "T", "t" }, { 0, 0, "Z", "z" }, { 0, 0, "U", "u" }, { 0, 0, "I", "i" }, { 0, 0, "O", "o" }, { 0, 0, "P", "p" } }, { { 0, 0, "A", "a" }, { 0, 0, "S", "s" }, { 0, 0, "D", "d" }, { 0, 0, "F", "f" }, { 0, 0, "G", "g" }, { 0, 0, "H", "h" }, { 0, 0, "J", "j" }, { 0, 0, "K", "k" }, { 0, 0, "L", "l" } }, { { 0, 0, "Y", "y" }, { 0, 0, "X", "x" }, { 0, 0, "C", "c" }, { 0, 0, "V", "v" }, { 0, 0, "B", "b" }, { 0, 0, "N", "n" }, { 0, 0, "M", "m" }} };
Dasselbe machen wir auch für die Schaltflächen der Sonderzeichen:
//Anzahl der Schaltflächen pro Zeile const int NUM_BTN_SYMBOL = 5; //Anzahl der Zeilen const int NUM_BTN_SYMBOL_ROWS = 4; Button symbols[NUM_BTN_SYMBOL_ROWS][NUM_BTN_SYMBOL] = { {{ 0, 0, "+", "" }, { 0, 0, "-", "" }, { 0, 0, "/", "" }, { 0, 0, "#", "" }, { 0, 0, "\'", "" }}, {{ 0, 0, "_", "" }, { 0, 0, ".", "" }, { 0, 0, ":", "" }, { 0, 0, ",", "" }, { 0, 0, ";", "" }}, {{ 0, 0, "<", "" }, { 0, 0, ">", "" }, { 0, 0, "|", "" }, { 0, 0, "?", "" }, { 0, 0, "!", "" }}, {{ 0, 0, "{", "" }, { 0, 0, "}", "" }, { 0, 0, "[", "" }, { 0, 0, "]", "" }, { 0, 0, "~", "" }} };
Schritt 3 – Zeichnen der Schaltflächen
Die Schaltflächen haben wir zuvor in einem Array abgelegt und den „Style“ in einem weiteren Array definiert, mit diesen Informationen läßt sich nun recht einfach die Schaltfläche zeichnen.
Wie du sicherlich gesehen hast, sind die Koordinaten jeweils mit 0 vorbelegt, diese Koordinaten werden beim Zeichnen berechnet und an die Schaltfläche gespeichert.
//Zeichnen eines Buttons void drawButton(int row, int btnIndex, Button &button, int offset_X) { int x = start_X + offset_X; //Für den ersten Button die Berechnung der neuen X Koordinate überspringen if (btnIndex > 0) { x = start_X + (BTN_WIDTH + BTN_MARGIN_HORIZONTAL) + offset_X; } // Y Koordinate vom durchlauf zuvor verwenden int y = start_Y; lcd.fillRoundRect(x, y, BTN_WIDTH, BTN_HEIGHT, 2, LIGHT_GREY); lcd.setTextColor(WHITE, LIGHT_GREY); lcd.setCursor(x + 7, y + 2); lcd.print(isShiftActive ? button.caption2 : button.caption1); //Speichern der Koordinaten am Button button.coord_x = x; button.coord_y = y; //Speichern der Koordinaten an den Feldern für den nächsten durchlauf start_X = x; start_Y = y; }
Die Funktionstasten sind ähnlich, jedoch haben diese zusätzliche Attribute welche behandelt werden müssen.
//Zeichnen einer Funktionstaste void drawFunctionButton(ButtonFunction &button) { //Wenn die Shift-Taste oder die Symbol-Taste betätigt wurde, dann soll die jeweilige //Taste mit dunkelgrauem hintergrund dargestellt werden. if ((button.caption == "SHIFT" && isShiftActive) || (button.caption == "#" && isSymbolActive)) { lcd.fillRoundRect(button.coord_x, button.coord_y, button.width, button.height, BTN_ROUNDEDGE, DARKER_GREY); lcd.setTextColor(button.textcolor, button.backgroundcolor); } else { lcd.fillRoundRect(button.coord_x, button.coord_y, button.width, button.height, BTN_ROUNDEDGE, button.backgroundcolor); lcd.setTextColor(button.textcolor, button.backgroundcolor); } //Die Leertaste ist deutlich länger und damit der Text zentriert angezeigt wird, muss hier eine If-Bedingung erfolgen. if (button.caption == "Space") { lcd.setCursor(button.coord_x + 50, button.coord_y + 3); } else { lcd.setCursor(button.coord_x + 6, button.coord_y + 3); } lcd.print(button.caption); }
Schritt 4 – Zeichnen des Eingabefeldes
Das Eingabefeld ist eigentlich nur ein Rechteck in welchem ein Text geschrieben wird. Daher ist das Zeichnen von diesem recht einfach.
void drawInputField(InputField inputField) { lcd.drawRect(inputField.coord_x, inputField.coord_y, inputField.width, inputField.height, inputField.bordercolor); lcd.fillRect(inputField.coord_x + 1, inputField.coord_y + 1, inputField.width - 2, inputField.height - 2, BK_GREY); lcd.setTextColor(inputField.textcolor, inputField.backgroundcolor); lcd.setCursor(inputField.coord_x + 4, inputField.coord_y + 5); int maxTextLength = 15; int textLength = inputText.length(); if (textLength > maxTextLength) { lcd.print(inputText.substring(textLength - maxTextLength, textLength)); } else { lcd.print(inputText); } }
Da keine TrueType Font zur Verfügung steht habe ich zur ermittlung der maximalen Eingabe den Buchstaben „W“ genutzt, es wurde ermittelt das maximal 15 Zeichen in dieses Eingabefeld passt.
struct InputField { uint8_t backgroundcolor; //Hintergrundfarbe uint8_t bordercolor; //Rahmenfarbe uint8_t textcolor; //Textfarbe int coord_x; //X Koordinate int coord_y; //Y Koordinate int width; //Breite int height; //Höhe };
Schritt 5 – Anzeigen / Zeichnen der Bildschirmtastatur
Die Funktion showKeyboard wird genutzt um die Bildschirmtastatur zu zeichnen und wird auch aufgerufen wenn die SHIFT / Symbol-Taste betätigt wird.
//Zeigt die Bildschirmtastatur an, wird ebenso verwendet um //die Bildschirmtastatur neu zu zeichnen. void showKeyboard() { //Die Bildschirmtastatur ist sichtbar! keyboardIsVisible = true; //Koordinaten der Bildschirmtastatur start_X = DEFAULT_START_X; start_Y = DEFAULT_START_Y; //Hintergrundfarbe lcd.fillScreen(BK_GREY); //Rahmen zeichnen lcd.drawRoundRect(30, 30, 250, 155, 10, DARK_GREY); //Zeichnen der Funktionstasten drawFunctionButton(closeBtn); drawFunctionButton(backBtn); //Wenn die Symbol-Taste aktiviert ist, dann soll //die Shift-Taste nicht gezeichnet werden if (!isSymbolActive) { drawFunctionButton(shiftBtn); } drawFunctionButton(symbolsBtn); drawFunctionButton(spaceBtn); drawFunctionButton(okBtn); //Zeichnen des Eingabefeldes drawInputField(inputField); //Wenn nicht die Symbol-Taste aktiviert ist, //dann soll die normale Tastatur gezeichnet werden. if (!isSymbolActive) { //Schleifen über die Arrays for (int row = 0; row < NUM_ROWS; row++) { start_X = DEFAULT_START_X; //Berechnen der X Koordinate für Zeilen größer als 1 if (row > 0) { start_Y = DEFAULT_START_Y + (row * (BTN_HEIGHT + BTN_MARGIN_VERTIKAL)); } //Schleife über die Schaltflächen im Array, zusätzlich wird hier aus dem Array //rowButtonCount die maximale Anzahl entnommen for (int btnIndex = 0; btnIndex < rowButtonCount[row]; btnIndex++) { int offset_X = 0; //Berechnen eines Offsets if (rowButtonCount[row] < 10 && btnIndex == 0) { //Abhängig von der Anzahl der Schaltflächen soll die Zeile eingerückt werden. switch (rowButtonCount[row]) { case 9: offset_X = BTN_WIDTH / 2; break; case 7: offset_X = BTN_WIDTH * 1.5 + BTN_MARGIN_HORIZONTAL; break; } } //Zeichnen der Bildschirmtastatur drawButton(row, btnIndex, keyboard[row][btnIndex], offset_X); } } } else { //Wenn die Symbol-Taste aktiviert wurde, dann sollen die Sonderzeichen gezeichnet werden //Die Sonderzeichen sind als Block von 5x4 abgelegt. for (int row = 0; row < NUM_BTN_SYMBOL_ROWS; row++) { start_X = DEFAULT_START_X; if (row > 0) { start_Y = DEFAULT_START_Y + (row * (BTN_HEIGHT + BTN_MARGIN_VERTIKAL)); } for (int btnIndex = 0; btnIndex < NUM_BTN_SYMBOL; btnIndex++) { int offset_X = 0; if (btnIndex == 0) { offset_X = 60; } drawButton(row, btnIndex, symbols[row][btnIndex], offset_X); } } } }
Schritt 6 – Aktionen an der Bildschirmtastatur am ESP32 behandeln
Wie man eine Aktion am Touchscreen am ESP32 behandelt habe ich dir bereits im Beitrag ESP32 Development Board: Touchfunktion programmieren erläutert, hier nutzen wir den Code und wandeln diesen lediglich etwas ab.
//Aktionen an der Bildschirmtastatur behandeln //Als Parameter werden die geklickten Koordinaten am Display erwartet. void handleKeyboard(int x, int y) { //Felder ob die komplette Bildschirmtastatur neugezeichnet werden soll, oder //nur das Eingabefeld. bool redrawKeyboard = false; bool redrawInputField = false; //prüfen ob die Symbold-Taste aktiviert ist. //Hintergrund: Es wird geprüft ob sich an einer X/Y Koordinate eine Schaltfläche //befindet. Da die Bildschirmtastatur jedoch zweischichtig ist, kommt es hier zu //Überlagerungen von Schaltflächen. if (!isSymbolActive) { for (int row = 0; row < NUM_ROWS; row++) { for (int btnIndex = 0; btnIndex < NUM_BUTTONS; btnIndex++) { Button button = keyboard[row][btnIndex]; if (checkCoordButton(button, x, y)) { redrawInputField = true; addSignToInputField(isShiftActive ? button.caption2 : button.caption1); } } } } else { for (int row = 0; row < NUM_BTN_SYMBOL_ROWS; row++) { for (int btnIndex = 0; btnIndex < NUM_BTN_SYMBOL; btnIndex++) { Button button = symbols[row][btnIndex]; if (checkCoordButton(button, x, y)) { addSignToInputField(button.caption1); redrawInputField = true; } } } } //Prüfen der Funktionstasten if (checkCoordButton(closeBtn, x, y)) { //Hier wird die Schaltfläche X behandelt. } else if (checkCoordButton(shiftBtn, x, y)) { //Wenn die Shift-Taste betätigt wurde dann soll der //boolsche Wert umgekert werden isShiftActive = !isShiftActive; //Auf jedenfall soll dann auch die Symboltaste als deaktiviert gelten isSymbolActive = false; //neuzeichnen der Bildschirmtastatur, entweder werden nun //die kleinen Buchstaben oder die großen angezeigt. redrawKeyboard = true; } else if (checkCoordButton(symbolsBtn, x, y)) { isSymbolActive = !isSymbolActive; isShiftActive = false; redrawKeyboard = true; } else if (checkCoordButton(spaceBtn, x, y)) { //Ein Leerzeichen dem Eingabefeld hinzufügen. addSignToInputField(" "); } else if (checkCoordButton(okBtn, x, y)) { //Hier wird die Schaltfläche OK behandelt. } else if (checkCoordButton(backBtn, x, y)) { //Taste zum löschen des letzten Zeichens im Eingabefeld //Wird jedoch nur ausgehührt wenn die Länge des Feldes größer 0 ist. if (inputText.length() > 0) { inputText = inputText.substring(0, inputText.length() - 1); //nur das Eingabefeld neuzeichnen redrawInputField = true; } } if (redrawKeyboard) { showKeyboard(); } else if (redrawInputField) { drawInputField(inputField); } }
Schritt 7 – Einbinden der Bildschirmtastatur in die Loop
In der Funktion loop müssen wir nun nurnoch eine kleine If-Bedingung implementieren in welcher zunächst geprüft wird ob die Bildschirmtastatur sichtbar ist und wenn dieses so ist, dann soll der Klick bzw. die Koordinaten ausgewertet werden.
void loop() { long currentMillis = millis(); if (lcd.getTouch(&x, &y) == 1 && (lastClick + CLICK_DELAY < currentMillis)) { lastClick = currentMillis; if (x > MAX_X || y > MAX_Y) { return; } //Nur wenn die Bildschirmtastatur sichtbar ist, soll diese behandelt werden. if(keyboardIsVisible){ //Aufrufen der Funktion mit den geklickten Koordinaten. handleKeyboard(x, y); } } //eine Pause von 50 Millisekunden. delay(50); }
Wie geht es nun weiter mit der ESP32 Touchscreen Bildschirmtastatur?
Die hier vorgestellte Bildschirmtastatur am ESP32 mit Touchscreen bietet eine vielzahl an Möglichkeiten. Im nächsten Beitrag möchte ich dir einen kleinen Wecker vorstellen welcher mit Hilfe dieser Bildschirmtastatur konfiguriert werden kann.
Du könntest mit dieser auch einen Chatclient programmieren, wobei ich die Eingabe mit dem Stift nicht sehr bequem für viel Text empfinde.
Hallo Stefan,
schöner Artikel zu Thema Touchscreen.
Hast Du auch schon mal versucht auf externe 14″ Monitor oder größer zu programmieren.
Hintergrund meiner Frage ist, ich will das momentan analoge Gleisstellpult (Schalter, Taster, LED’s, LCD-Displays, Fahrreglerpoti, etc.) meiner Modellbahn auf einem Touchscreen abbilden. Dieser soll dann die Anzeigefunktionalitäten (Blockbelegung, Signalsstellungen, Weichenstellungen, Systemmeldungen, etc.) und Eingabemöglichkeiten (Button anstelle von Schalter und Taster etc.) beinhalten!
Kannst Du da helfen?
Die Steuerung erfolgt momentan mit Arduino Mega R3, 48 Relais und 24 Belegtmelder (auf Strommesbasis), IO-Erweiterungen 5 x MCP23017. Die Anlage fährt klassisch analog im Handbetrieb oder Vollautomatik.
Jetzt sind Erweiterungen auf dem Gleisstellpult nur schwer bis gar nicht möglich ohne einen Anbau oder Neubau des Gleisstellpultes.
Wenn Du mir deine Mailadresse schickst kann ich dir ja mal ein Foto schicken zur besseren Beurteilung des mechanischen Aufwandes.
Viele Grüße und Dank im voraus
Falk