ESP32-2432S028 mit 2.8" Touchscreen (Cheap Yellow Display)
Utility Settings und WLAN-Scan

Bevor ihr mit diesem Artikel startet, solltet ihr euch zum Einstieg in die Materie zuerst ein wenig einlesen und die Hardware und das Pinout des TouchScreen-ESP32 ESP32-2432S028R-kennenlernen.

Außerdem solltet ihr euch eine Entwicklungsumgebung mit PlatformIO einrichten, wie in diesem Artikel besprochen. Danach solltet ihr noch den Artikel Programmierung des Displays mit der TFT_eSPI-Library lesen, in dem ihr lernt, wie wir etwas auf dem Display ausgeben.

Sowie den Artikel über die Programmierung des Touchscreen Digitizer XPT2046, der im Cheap Yellow Display steckt.

Damit wäre dann die Hardware-Ansteuerung von Display und Touchscreen bewerkstelligt. Nun kommt als Dialogsystem die LGVL Library obendrauf. In diesem Artikel erfahrt ihr, wie man diese für die weitere Verwendung einbindet und mit der Standard-LVGL-Demo (die, mit dem das Gerät ausgeliefert wurde) testet.

Im darauffolgenden Artikel haben wir dann gelernt, wie man LVGL-Dialoge entwirft und mittels Events steuert. Damit haben wir genügend gelernt, um unsere eigenen Anwendungen mit LVGL-Dialogen zu programmieren.

Doc Cool's Utilities V3


Im vorletzten Artikel habe ich die erste Version von Doc Cool's Utilities vorgestellt. Dies soll eine Sammlung von nützlichen Programmen sein, die über ein Menü aufgerufen werden sollen.

Da ich bemerkt habe, das es auch einige englischsprachige Leser gibt, versuche ich, die Dialog im Programm in (einfachem) Englisch zu halten, damit mehr Leute etwas davon haben, denn die fertige Firmware wird es wieder zum Download geben.

Außerdem spare ich mir, den kompletten Source-Code immer wieder zu wiederholen. Der steht ja schon in den vorhergehenden Artikeln. Ich werde also nur noch auf die neu hinzugekommenen Sachen eingehen. Das macht die ganzen Erklärungen kürzer und übersichtlicher.

Aus technischen Gründen, nämlich damit die WLAN-Antenne und evtl. weitere Antenne nicht von der Hand verdeckt werden und weil es der natürlicher Handhaltung am meisten entspricht, habe ich mich für ein Design der Dialoge im Portrait-Modus, also hochkant, entschieden.

Erweiterung der Touch Kalibrierung


Zu Anfang war es ja so, dass die Bereichswerte für die Größe des Touch-Screens, also wo der eigentlich berührungsempfindliche Bereich losgeht und endet, in der platformio.ini angegeben und damit "hart" einkompiliert wurde. Das ist natürlich unschön, weil man dann für jedes CYD neu kompilieren muss. Darum gab es in den Utilities V2 ja die Möglichkeit, die Daten auf der SD Karte zu speichern und dann beim Start wieder zu laden. So hat jedes CYD seine eigene SD und seine eigenen Einstellungen. Nebenbei erwähnt: das CYD ist nicht wählerisch mit der SD-Karte, es akzeptiert auch die Billig-Micro-SD-Karten für um die 2 Euro das Stück aus China.

Während XPT_XMIN, XPT_XMAX, XPT_YMIN und XPT_YMAX angeben, in welchem Bereich die Touch-Folie anklickbar ist und damit wie groß der auswertenbare Bereich, kann es trotzdem noch sein, dass die Folie ein wenig versetzt aufgeklebt ist und man immer ein paar Pixel daneben klickt. Das wiederum ist bei jedem Display individuell.

Der X-/Y-Versatz (TFT_XDIFF und TFT_YDIFF) ist jetzt nicht so gewaltig, dass man einen halbwegs großen Button nicht mehr treffen kann, aber wer es gerne präzise mag, der kann nur den Versatz pixelgenau einstellen. Dazu dienen die vier neuen Knöpfe, die glaube ich nicht viel Erklärung brauchen. Änderungen an TFT_XDIFF und TFT_YDIFF werden sofort aktiv und sind unmittelbar sichtbar.

Wichtig ist es allerdings, zuerst einmal die XPT_X/Y-Kalibrierung durchzuführen und zu speichern. Erst danach sollte man die TFT_X/Y-Kablibrierung durchführen, die die zuerst genannte Kalibrierung als Grundlage benötigt. Ist alles perfekt, kann man die Einstellungen mit Save speichern.

Damit man nicht nicht selbst eine Falle stellt, sind TFT_XDIFF und TFT_YDIFF nur im Bereich von -10 bis +10 einstellbar.

Sollte trotzdem einmal etwas fatal schiefgehen, das CYD ohne SD-Karte booten, dann werden die gespeicherten Einstellungen nicht geladen und es kann neu kalibriert werden.

Neues Entwicklertool: Raster einblenden für Dialog-Design


Es gibt eine neue "geheime" Entwickler-Option. Während 3 Sekunden lang oben links in die Ecke drücken den Screenshot auslöst, löst 3 Sekunden lang oben rechts in die Ecke drücken nun aus, dass per TFT_eSPI ein Raster auf den derzeitigen Dialog gezeichnet wird.

Dabei wird ein Linien-Raster mit 5 Pixeln Abstand gezeichnet. Das ursprüngliche Bild darunter bleibt dabei erhalten. Die Abstände all2 20 Pixel sind hellgrau und die alle 100 Pixel gelb, so dass sich leicht abzählen lässt, wohin ein Dialogelement verschoben werden muss, wenn es zum Beispiel nicht ganz mittig ist.

Ich glaube, dass wir mir eine gute Hilfe beim zukünfitgen Dialogdesign sein.

Zur Programmierung: Die Linien werden mit den TFT_eSPI-Library-Funktionen drawFastVLine() und drawFastHLine() gezeichnet, die besonders schnell sind:

void drawDesignGrid(){ // Grid mit TFT_eSPI zeichnen, um Dialogpositionen von TOPLEFT aus besser abschätzen zu können uint16_t col; // 5er-Schritte in blau, 20er in grau und 100er in gelb for (int x = 0; x < TFT_HOR_RES; x += 5) { col=TFT_BLUE; if (x % 20 == 0) col=TFT_LIGHTGREY; if (x % 100 == 0) col=TFT_YELLOW; tft.drawFastVLine(x, 0, TFT_VER_RES, col); } for (int y = 0; y < TFT_VER_RES; y += 5) { col=TFT_BLUE; if (y % 20 == 0) col=TFT_LIGHTGREY; if (y % 100 == 0) col=TFT_YELLOW; tft.drawFastHLine(0, y, TFT_HOR_RES, col); } // vertikale, höherwertige Linien wurden unterbrochen, neu zeichnen for (int x = 0; x < TFT_HOR_RES; x += 20) { col=TFT_LIGHTGREY; if (x % 100 == 0) col=TFT_YELLOW; tft.drawFastVLine(x, 0, TFT_VER_RES, col); } }

Neuer Dialog: Settings


In Zukunft sollen natürlich auch Utilities dazu kommen, die das WLAN nutzen, wie etwa Web-Radio. Dazu braucht man natürlich eine Internetverbindung und dazu muss man irgendwo die SSID und das Passwort für den Router eingeben oder hinterlegen.

In den Beispielen müssen diese Logindaten immer als Strings fest im Source-Code eingetragen werden und werden dann "hart" mitkompiliert. Aber das ist natürlich für Utilities, die jeder nur downloaden und dann nutzen können soll ein No-Go.

Irgendwo müssen die WLAN-Logindaten also eingegeben werden. Und damit das nicht jedesmal passieren muss, sollen sie auf der SD-Karte gespeichert und beim Neustart dann automatisch wieder geladen werden. So dass man sie im Normalfall nur einmal eingeben muss, falls man das CYD nur im heimischen WLAN nutzt; aber doch noch ändern kann, um dem Freund sein CYD in dessen WLAN einmal vorzuführen.

Die Eingabemöglichkeit der Login-Daten findet man ab sofort im Dialog "Settings", in dem man zur Zeit auch die Helligkeit der Hintergrundbeleuchtung einstellen kann. Später werden hier noch weitere, notwendig werdende Einstellungen hinzukommen.

Die Benutzung ist ganz einfach: Man tippt in ein Eingabefeld, wobei sich eine Bildschirm-Tastatur im unteren Drittel des Bildschirms öffnet. Verdeckt sie dort dummerweise das Eingabefeld, dann klickt man auf das kleine Tastatur-Symbol unten links in der Tastatur und die Tastatur springt an den oberen Rand, so dass man nun sehen kann, was man eingibt.

Tippt man irgendwo außerhalb des Eingabefeldes, verschwindet die Tastatur auch wieder. Ich habe übrigens das Standard-US-Layout mit für uns vertauschtem Y und Z gelassen, weil ja auch die Benutzerführung in englisch gehalten ist.

Das Passwort-Feld zeigt das Passwort nur mit * an, so dass es keiner lesen kann, der einem kurz über die Schulter blickt. Damit man weiß, was man eingibt, wird der zuletzt eingegebene Buchstabe kurz angezeigt.

Ein Klick auf "Save" speichert die Logindaten dann auf der SD in /settings/wlan.ini und zwar unverschlüsselt. Also auf die SD-Karte gut achtgeben. Das hat den Hintergrund, dass ich ganz gerne mal Sonderzeichen in meinen Passwörtern benutze, die nicht über die Bildschirmtastatur eingegeben werden können. Durch das unverschlüsselte Speichern kann man dieses Problem nämlich folgendermaßen meistern: SD herausnehmen, in PC stecken und dort die .ini-Datei in einem beliebigen Editor editieren mit allen Sonderzeichen, die einem zur Verfügung stehen.

Neben dem Eingabefeld "WLAN SSID" ist ein kleiner Button. Diese kopiert den Inhalt der Zwischenablage in das Eingabefeld. Wie die SSID in die Zwischenablage kommt, lest ihr gleich im Dialog "Scan WLAN".

Vorher noch kurz zur Programmierung: Das Eingabefeld ist neu und wird wie folgt in LVGL definiert:
txt_wlan_ssid = lv_textarea_create(lv_screen_active()); lv_obj_add_event_cb (txt_wlan_ssid, evt_txt_field, LV_EVENT_ALL, NULL); lv_textarea_set_one_line (txt_wlan_ssid, true); lv_textarea_set_password_mode(txt_wlan_ssid, false); lv_textarea_set_max_length (txt_wlan_ssid, sizeof(WLAN_SSID)-1); lv_textarea_set_text (txt_wlan_ssid, WLAN_SSID); lv_obj_set_width (txt_wlan_ssid, TFT_HOR_RES-45); lv_obj_align (txt_wlan_ssid, LV_ALIGN_TOP_LEFT, 0, y);
txt_wlan_ssid ist wieder ein global definierter LVGL-Object-Pointer (lv_obj_t *), weil wir müssen ja auch aus anderen Funktionen darauf zugreifen, um das Feld per Tastatur oder Zwischenablage zu füllen. Über lv_textarea_set_password_mode wird gesteuert, ob die Eingabe oder nur Sternchen angezeigt werden sollen. WLAN_SSID (char[31]) ist eine globale Variable, die zum Login benutzt wird und in die der Wert gespeichert wird, sobald aus Save geklickt wird. Die eingebbare Länge ist eins kürzer als WLAN_SSID, denn für das Zeichenketten-Abschluss-Nullbyte muss ja auch noch Platz sein. Der Rest ist ja schon bekannt.

Der Event, der für alle Textfelder gleich aussehen darf, sieht so aus und regelt das Ein- und Ausblenden der Bildschirmtastatur:
static void evt_txt_field (lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); lv_obj_t *ta = (lv_obj_t *) lv_event_get_target(e); if (code == LV_EVENT_CLICKED || code == LV_EVENT_FOCUSED) { if(Keyboard == NULL) createKeyboard(); lv_keyboard_set_textarea(Keyboard, ta); } else if (code == LV_EVENT_DEFOCUSED) { closeKeyboard(); } else if (code == LV_EVENT_READY) { closeKeyboard(); } }
Die beiden Funktionen für das Öffnen und Schließen der Bildschirmtastatur:
static void createKeyboard() { // Create a keyboard Keyboard = lv_keyboard_create(lv_screen_active()); lv_obj_add_event_cb (Keyboard, evt_keyboard, LV_EVENT_ALL, NULL); lv_obj_set_size (Keyboard, LV_HOR_RES, 100); } static void closeKeyboard() { if (Keyboard == NULL) return; lv_obj_delete (Keyboard); Keyboard = NULL; }
welche wieder einen Event definieren, der das Positionieren der Bildschirmtastatur unten und oben bewerkstelligen:
static void evt_keyboard(lv_event_t *e) { int y; lv_event_code_t code = lv_event_get_code(e); if (code == LV_EVENT_CANCEL) { // The Close button is clicked. // closeKeyboard(); // zwischen oben und unten wechseln y = lv_obj_get_y(Keyboard); // LV_LOG_USER ("Y of Keyboard = %d", y); if ( y > TFT_VER_RES/2) { // ist unten, soll nach oben lv_obj_align(Keyboard, LV_ALIGN_TOP_LEFT, 0, 0); //lv_obj_set_y(Keyboard, 0); } else { lv_obj_set_y(Keyboard, TFT_VER_RES - lv_obj_get_height(Keyboard)); } } }
Normalerweise ist das Tastatur-Icon zum Schließen der Tastatur gedacht. Darum erzeugt es auch einen Close-Event (LV_EVENT_CANCEL). Aber schließen kann man die Tastatur auch einfach durch Fokus-Verlust. Wichtiger ist doch, dass sie nichts verdeckt.

Neues Utility: Scan WLAN


Hat man eine WLAN-SSID wie "CNW", dann ist das leicht zu merken und einzugeben. Was aber, wenn man den Routernamen nicht geändert hat oder ändern kann und die SSID zum Beispiel "FRITZ!Box 6490 Cable" ist? Wer soll sich das denn merken?

Dafür gibt es das neue Scan WLAN-Utility. Kurz erzählt: Scannen, anklicken und damit in die Zwischenablage kopieren und dann in Setting die Zwischenablage wieder unter SSID einfügen. Fertig.

Länger erklärt: Zu Anfang ist die Liste leer. Dann klickt man auf Scan WLAN und dann braucht der ESP32 ein paar Sekunden, das WLAN zu scannen und die Daten zusammen zu suchen. Sobald das geschehen ist, wird die Tabelle gefüllt, und zwar nach dBm sortiert die Geräte mit der besten Signalstärke zuerst: Das Schöne an dieser von LVGL generierten Tabelle ist, dass man komfortabel darin herumscrollen kann, nach rechts für weitere Spalten und nach unten für weitere Geräte. Das funktioniert so gut, dass der kleine Bildschirm fast kein Manko mehr ist.

In der Realität sieht die Tabelle übrigens ein bisschen schöner aus mit wechselnden Hintergrundfarben pro Zeile. Aber da scheint die Funktion der TFT_eSPI-Library zu versagen. Für das wahre Farb-Erlebnis also bitte das Demovideo anschauen oder die Firmware downloaden und selbst ausprobieren. Aber Fotos von dem kleinen Screen zu machen, die gut lesbar sind, ist wirklich schwierig. Da sind die Screenshots für mich einfach praktisch.

Beim Klick auf eine Zelle der Tabelle wird der Inhalt der SSID-Zelle auf dieser Zeile in die Zwischenablage kopiert und kann im Dialog Settings in das SSID-Feld kopiert werden.

Ein Hinweis zu einem Fehler, der auftauchen kann: Macht das CYD nach Klick auf Scan WLAN einfach einen Reset/Reboot, dann noch einmal die serielle Ausgabe beobachten. Wenn da etwas von Error: "Brownout detector was triggered" ("Brownout-Detektor wurde ausgelöst") steht, dann weist das darauf hin, dass nicht genügend Strom zur Verfügung steht. Der Scan-Vorgang braucht nämlich schon einiges an Saft und so manchen alte USB-Schnittstelle am PC oder kleine Powerbank kann die nicht liefern. Das führt dann zu diesem Fehler.

Ursachen dafür können sein: Lösung: Versucht es mit einem anderen, kürzeren USB-Kabel (evtl. mit Datenleitungen für die serielle Kommunikation), einem anderen USB-Anschluss am Computer oder verwendet einen USB-Hub mit externer Stromversorgung.

Nun aber zur Programmierung des Dialoges. Bis auf die Tabelle ist ja alles bekannt. Also beginnen wir mit deren Definition:
if (wlan_table != NULL) lv_obj_delete(wlan_table); // sonst absturz nachdem 3x hintereinander draufgeklickt wlan_table = lv_table_create(lv_screen_active());
wlan_table ist wieder ein Object-Pointer, der mit NULL initialisiert ist. Das heißt, wurde die Tabelle noch nicht gebraucht, haben wir einen Null-Pointer. War hingegen die Tabelle schon einmal gefüllt, dann müssen wir LVGL sagen mit lv_obj_delete sagen, dass er das alte Tabellenobjekt löschen soll. Sonst füllt er das alte sozusagen immer weiter und hat nach ein paar mal keinen Speicher mehr und führt einen Reboot aus.

Je nachdem, wie breit die Spalten sein sollen, setzen wir diese. Es sind ungefähr nochmal 30 Pixel pro Zeile zu dem eigentlichen Überschriftentext hinzu zu addieren:
int p=30; lv_table_set_column_width (wlan_table, col, p+20); ...
Dann werden die Events definiert:
lv_obj_add_event_cb(wlan_table, evt_draw_table, LV_EVENT_DRAW_TASK_ADDED, NULL); lv_obj_add_flag(wlan_table, LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS); lv_obj_add_event_cb(wlan_table, evt_tab_wlan_scan, LV_EVENT_VALUE_CHANGED, NULL);
Der LV_EVENT_DRAW_TASK_ADDED wird beim Erzeugen der Tabelle aufgerufen. Dort können wir uns einklingen und z. B. sagen, dass die ÜBerschrift in gelb sein soll und jede zweite Zeile eine andere Hintergrundfarbe haben soll.

Der LV_EVENT_VALUE_CHANGED dient dazu abzufangen, wenn jemand in die Tabelle klickt, um dann einen Wert in die Zwischenablage zu kopieren. Hinweis: Auch wenn LV_EVENT_CLICKED sehr viel naheliegender ist, ist dass der falsche. Der wird zwar auch bei einem Klick aufgerufen, man kann dort aber nicht die angeklickte Zelle bestimmen!

Danach füllt man einfach die Tabellezellen mit Werten über:
lv_table_set_cell_value (wlan_table, row, col, "Text");
Wobei die Zählung von Zeile (row) und Spalte (col) jeweils bei Null und nicht bei Eins losgehen. Die Überschriftenzeile ist übrigens auch eine ganz normale Zeile. Da sie die erste ist, hat sie den Index 0. Die komplette Überschriftenzeile definiert man dann so.
lv_table_set_cell_value (wlan_table, 0, 0, "Nr."); lv_table_set_cell_value (wlan_table, 0, 1, "SSID"); lv_table_set_cell_value (wlan_table, 0, 2, "dBm"); lv_table_set_cell_value (wlan_table, 0, 3, "Ch."); lv_table_set_cell_value (wlan_table, 0, 4, "Auth.");
Dann muss man den Rest der Tabelle mit Daten füllen und die Tabelle nur noch positionieren:
lv_obj_set_width(wlan_table, TFT_HOR_RES); lv_obj_set_height(wlan_table, 180); lv_obj_align (wlan_table, LV_ALIGN_TOP_MID, 0, 100);
Fertig. Den Rest mit dem Scrolling und allem Weiteren erledigt LVGL für uns. Hier noch die beiden Events. Einmal der für die Umgestaltung, damit unsere Tabelle etwas hübscher ist, die wir im Prinzip für all unsere Tabellen verwenden können, darum auch der generische Name:
static void evt_draw_table (lv_event_t * e) { lv_draw_task_t *draw_task = lv_event_get_draw_task(e); lv_draw_dsc_base_t *base_dsc = (lv_draw_dsc_base_t*) draw_task->draw_dsc; // If the cells are drawn... if(base_dsc->part == LV_PART_ITEMS) { uint32_t row = base_dsc->id1; uint32_t col = base_dsc->id2; // Erste Zeile Überschrift gelb auf dunkel if(row == 0) { lv_draw_label_dsc_t *label_draw_dsc = lv_draw_task_get_label_dsc(draw_task); // Schriftfarbe if(label_draw_dsc) label_draw_dsc->color = lv_color_hex(0xffff00); lv_draw_fill_dsc_t *fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task); // Hintergrundfarbe if(fill_draw_dsc) fill_draw_dsc->color = lv_color_hex(0x080808); } else { // weiß auf mittel lv_draw_label_dsc_t *label_draw_dsc = lv_draw_task_get_label_dsc(draw_task); if(label_draw_dsc) label_draw_dsc->color = lv_color_hex(0xffffff); lv_draw_fill_dsc_t *fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task); if(fill_draw_dsc) fill_draw_dsc->color = lv_color_hex(0x101010); } // jede 2. zeile weiß auf etwas heller if(row != 0 && (row % 2) == 0) { lv_draw_fill_dsc_t *fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task); if(fill_draw_dsc) fill_draw_dsc->color = lv_color_hex(0x181818); } } }
Und dann noch einmal der Event, der bei Klick die SSID in unsere Zwischenablage kopiert:
static void evt_tab_wlan_scan(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); lv_obj_t *obj = (lv_obj_t *) lv_event_get_target(e); if(code == LV_EVENT_VALUE_CHANGED) { uint32_t col; uint32_t row; lv_table_get_selected_cell(obj, &row, &col); // LV_LOG_USER ("changed: row %d, col: %d", row, col); // mit LV_EVENT_VALUE_CHANGED statt clicked geht es // SSID in Textfeld kopieren if (lab_status != NULL) { String txt = lv_table_get_cell_value(obj, row, 1); txt.concat (" copied to clipboard."); lv_label_set_text (lab_status, txt.c_str()); Clipboard = lv_table_get_cell_value(obj, row, 1); } } }
Clipboard ist dabei einfach nur eine globale String-Variable, auf die wir von überall zugreifen können und lab_status das Label links neben dem zurück-Button, dass ich als Statuszeile und zur Anzeige von Bedienhinweisen nutze.

Firmware Download via ESP Web Tools


Du möchtest die Firmware schon einmal ausprobieren, ohne die Entwicklungsumgebung zu installieren oder zu programmieren?

Kein Problem. Schließe einfach dein ESP32 an einen USB-Port deines PC an und klicke auf den deinem Gerät entsprechenden Button:

Die Firmware-Installation funktioniert leider nur mit Google Chrome und Chromium-basierten Browsern wie Microsoft Edge, Opera, Vivaldi oder Brave; jedoch nicht mit Firefox. Bitte benutze eine HTTPS-Verbindung, HTTP funktioniert nicht.

Die Firmware-Installation funktioniert leider nur mit Google Chrome und Chromium-basierten Browsern wie Microsoft Edge, Opera, Vivaldi oder Brave; jedoch nicht mit Firefox. Bitte benutze eine HTTPS-Verbindung, HTTP funktioniert nicht.

Demo und Video

Ich habe wieder ein kleine Demonstrations-Video gemacht, dass die neuen Dialoge in Aktion zeigt:


Mein Fazit zu dieser Programmier-Episode

Bei der Touchscreen-Kalibrierung ist vielleicht ein wenig der Perfektionist in mir durchgegangen, aber jetzt ist das Thema wenigstens abgeschlossen: Genauer geht es nicht mehr.

Ich war überrascht, wie einfach man mit LVGL die virtuelle Tastatur mit einem Eingabefeld verknüpfen kann. Das war super easy und klappt hervorragend.

Auch das Tabelle-Dialogelement ist sehr schön gemacht. Es macht geradezu Spaß, durch die Inhalte zu scrollen - naja, okay, mit einem kapazitiven Display würde das sicher noch eine Spur mehr Spaß machen. Aber es ist toll, wie man mit der Tabelle viele Daten anzeigen kann, wenn auch wegen des kleinen Bildschirm nicht alles auf einmal. Auf das Wie managt LVGL wirklich toll.

Einzig verwirrend ist, dass man das Value-Changed statt des Click-Events benutzen muss, will man herausfinden, welche Zelle angeklickt worden ist. Aber dann geht das auch und ist eine wertvolle Hilfe, Dinge auszuwählen.

Weitere Aussichten

In der nächsten Version und im nächsten Artikel werden wir dann endlich auch den Speaker-Anschluss benutzen und ich werde ein Web-Radio vorstellen und erklären, wie man es programmiert.

Quellen, Literaturverweise und weiterführende Links