ESP32 Börsen-Ticker mit 2,4" TFT-Display
Dieses Projekt verwandelt ein ESP32-Entwicklungsboard und ein 2,4" TFT-Display in einen Börsen-Ticker für den Schreibtisch. Das Gerät holt sich Aktienkurse über WLAN aus dem Internet und zeigt sie fortlaufend an: Firmenname, aktueller Kurs, die Veränderung zum Vortag und darunter der Verlauf des Handelstages als Kurve.
Benötigte Komponente
- ESP32 NodeMcu Entwicklungsboard
- 2,4" TFT LCD Display Modul
- Jumper-Kabel Buchse-Buchse
- Micro-USB-Kabel
- 1x Kabelverbinder
- Optional: 3D-gedrucktes Gehäuse
Software & Zugänge
- Arduino IDE 2.x
- ESP32-Boardpaket für die Arduino IDE
- Bibliotheken: Adafruit GFX, Adafruit ILI9341, ArduinoJson
- Kostenloser Account bei finnhub.io
- Kostenloser Account bei twelvedata.com
- 2,4-GHz-WLAN-Netzwerk
Downloads und mehr
Welche Börsen-API ist die richtige?
Der Ticker benötigt zwei verschiedene Datenquellen: einen aktuellen Kurs, der möglichst oft aktualisiert wird, und eine Reihe historischer Werte für die Verlaufskurve.
Unsere Wahl fällt auf die Kombination aus Finnhub und Twelve Data:
Finnhub liefert den Live-Kurs. 60 Anfragen pro Minute reichen für mehrere Symbole im Minutentakt problemlos aus.
Twelve Data liefert die Kurshistorie. Mit 800 Anfragen pro Tag lässt sich bei einer Aktualisierung alle vier Minuten reihum der Intraday-Verlauf stabil anzeigen.
Funktionsweise des Tickers
- Der ESP32 verbindet sich mit dem WLAN.
- Jede Minute fragt er bei Finnhub die aktuellen Kurse aller hinterlegten Symbole ab.
Alle vier Minuten holt er bei Twelve Data den Intraday-Verlauf eines Symbols – reihum, um das Tageslimit einzuhalten.
Die Daten werden auf dem per SPI angeschlossenen TFT-Display dargestellt. Alle acht Sekunden wechselt die Anzeige zum nächsten Wert.
zum Vergrößern auf Bilder klicken
Schritt 1 | API-Keys holen
Finnhub (Live-Kurse)
- Auf finnhub.io gehen, Get free API key klicken und registrieren. Keine Zahlungsdaten nötig.
- Der Key steht danach im Dashboard (oben rechts) und sieht ungefähr so aus: cq7f2h9r01qk3v8b1p2g.
Direkt im Browser testen (DEIN_KEY ersetzen)
https://finnhub.io/api/v1/quote?symbol=AAPL&token=DEIN_KEY
Die Antwort: {"c":214.29,"d":1.87,"dp":0.8801,"h":215.15,"l":211.83,"o":212.41,"pc":212.42,"t":1721764801}
| Feld | Bedeutung |
| c | current – aktueller Kurs |
| d | delta – Veränderung zum Vortagesschluss, absolut |
| dp | delta percent – Veränderung in Prozent |
| h / l | Tageshoch / Tagestief |
| o | Eröffnungskurs |
| pc | previous close – Schlusskurs des Vortags |
| t | Zeitstempel (Unix) |
Twelve Data (Kursverlauf)
- Auf twelvedata.com registrieren und den Basic-Plan wählen – kostenlos, ohne Zahlungsdaten.
- Der API-Key steht im Dashboard.
Auch diesen im Browser testen:
https://api.twelvedata.com/time_series?symbol=AAPL&interval=15min&outputsize=26&order=ASC&apikey=DEIN_KEY
Zurück kommt:
(Aktienkurs) anzeigen / einklappen
{
"meta": {
"symbol": "AAPL",
"interval": "15min"
},
"values": [
{
"datetime": "2026-07-24 09:30:00",
"open": "212.41",
"high": "213.02",
"low": "212.20",
"close": "212.88",
"volume": "1840233"
}
],
"status": "ok"
}
Uns interessiert nur close aus jedem Eintrag – 26 Werte im 15-Minuten-Takt ergeben genau einen US-Handelstag von 6,5 Stunden. order=ASC sorgt dafür, dass der älteste Wert zuerst kommt, dann können wir die Kurve direkt von links nach rechts zeichnen.
Schritt 2 | Arduino IDE vorbereiten
ESP32-Boardpaket installieren (detailierte Anleitung)
- Datei → Einstellungen öffnen.
- Bei Zusätzliche Boardverwalter-URLs eintragen: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
- Werkzeuge → Board → Boardverwalter öffnen, nach esp32 suchen, esp32 by Espressif Systems installieren.
- Board auswählen: ESP32 Dev Module.
Treiber prüfen
Unser Board nutzt einen CP2102 als USB-Seriell-Wandler. Windows 11, macOS und die meisten Linux-Distributionen erkennen ihn automatisch. Erscheint unter Werkzeuge → Port kein Eintrag, installiere den CP2102-Treiber von Silicon Labs.
Bibliotheken installieren
Werkzeuge → Bibliotheken verwalten, dann suchen und installieren:
Beim Installieren der Adafruit-ILI9341-Bibliothek fragt die IDE, ob Abhängigkeiten mitinstalliert werden sollen – hier „Install all" wählen. WiFi, HTTPClient, SPI und WiFiClientSecure bringt das ESP32-Boardpaket bereits mit.
Schritt 3 | Display verkabeln
Beide Bauteile arbeiten mit 3,3 V – ein Pegelwandler ist nicht nötig. Zieh vor dem Verkabeln immer das USB-Kabel ab. Das Display hat oben eine Stiftleiste mit neun Pins. Die vier Pins an der Unterseite gehören zum microSD-Slot und bleiben in diesem Projekt frei.
| Display-Pin | ESP32-Pin | Funktion |
|---|---|---|
| VCC | 3V3 | Versorgung |
| GND | GND | Masse |
| CS | GPIO 15 | Chip Select |
| RESET | GPIO 4 | Reset |
| DC (auch RS) | GPIO 2 | Data / Command |
| SDI (MOSI) | GPIO 23 | Daten zum Display |
| SCK | GPIO 18 | Takt |
| LED | 3V3 | Hintergrundbeleuchtung |
| SDO (MISO) | GPIO 19 | Daten vom Display |
GPIO 23, 18 und 19 sind die Hardware-SPI-Pins des ESP32 (VSPI). Die Adafruit-Bibliothek nutzt sie automatisch – im Sketch geben wir nur CS, DC und RESET an.
Zwei Pins, eine Quelle
VCC und LED brauchen beide 3,3 V, der ESP32 hat aber nur einen 3V3-Pin. Dafür sind die Stromschienen des Breadboards da – ein Kabel vom 3V3-Pin auf die rote Schiene, von dort zwei Kabel zum Display. Beide Pins sind schlicht parallele Verbraucher an derselben Quelle. GND gibt es am ESP32 zweimal, kann aber genauso über die blaue Schiene laufen. Wenn direkt verkabelt wird, muss ein Y-Kabel hergestellt werden. Alternativ kannst du auch einen Kabelverbinder verwenden.
Check vor dem Einschalten
- Sitzt VCC wirklich auf 3V3 und nicht auf VIN/5V?
- Ist GND verbunden?
- Sitzen alle Stecker fest? Wackelkontakte auf dem SPI-Bus erzeugen die kuriosesten Fehlerbilder.
Schritt 4 | Kurztest - Läuft das Display?
Bevor WLAN und APIs dazukommen, prüfen wir isoliert die Anzeige:
Code anzeigen / einklappen
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 4
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
void setup() {
tft.begin();
tft.setRotation(3); // Querformat 320 x 240; bei 1 steht das Bild kopf
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_GREEN);
tft.setTextSize(3);
tft.setCursor(20, 100);
tft.print("Display laeuft!");
}
void loop() {}
Steht der grüne Text im Querformat auf dem Schirm? Dann ist die Hardware in Ordnung.
Falls nicht, spring kurz zur Fehlerbehebung am Ende.
Schritt 5 | Der Ticker-Sketch
Das Programm komplett in einen neuen Sketch kopieren und oben die markierten Zeilen anpassen: WLAN-Name, WLAN-Passwort, die beiden API-Keys und deine Watchlist.
Code anzeigen / einklappen
/* ============================================================
ESP32 Börsen-Ticker mit Kursverlauf
Display : 2,4" TFT ILI9341 (Adafruit_GFX / Adafruit_ILI9341)
Kurse : finnhub.io – Live-Kurs, 60 Anfragen/Minute
Verlauf : twelvedata.com – Intraday-Kerzen für die Grafik
Anleitung: Roboter-Bausatz.de
============================================================ */
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <time.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSansBold24pt7b.h>
// ---------- Display-Pins ----------
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 4
// MOSI 23, SCK 18, MISO 19 = Hardware-SPI, wird automatisch genutzt
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// ============ HIER ANPASSEN =================================
const char* WIFI_SSID = "MEIN_WLAN";
const char* WIFI_PASS = "MEIN_PASSWORT";
const char* FINNHUB_KEY = "MEIN_FINNHUB_KEY";
const char* TWELVE_KEY = "MEIN_TWELVEDATA_KEY";
struct Symbol { const char* ticker; const char* name; };
// Deine Watchlist – Zeilen ergänzen oder löschen, der Rest passt sich an
const Symbol watchlist[] = {
{ "AAPL", "Apple" },
{ "MSFT", "Microsoft" },
{ "NVDA", "NVIDIA" },
{ "AMZN", "Amazon" },
{ "TSLA", "Tesla" }
};
// ============================================================
const uint8_t ANZAHL = sizeof(watchlist) / sizeof(watchlist[0]);
// Zeitzone Deutschland inkl. automatischer Sommerzeit
const char* TZ_INFO = "CET-1CEST,M3.5.0/02,M10.5.0/03";
const char* NTP_SERVER = "pool.ntp.org";
const uint32_t KURS_INTERVALL = 60000; // 1 min – Live-Kurse (Finnhub)
const uint32_t CHART_INTERVALL = 240000; // 4 min – ein Chart (Twelve Data)
const uint32_t WECHSEL_INTERVALL = 8000; // 8 s – nächster Wert der Liste
#define MAX_PUNKTE 26 // Datenpunkte im Chart (26 x 15 min = 1 Handelstag)
// Laufzeitdaten, werden vom Programm gefüllt
struct Daten {
float kurs;
float diff;
float proz;
bool gueltig;
float verlauf[MAX_PUNKTE];
uint8_t punkte;
};
Daten daten[ANZAHL]; // global -> startet automatisch mit Nullen
// ---------- Farben (RGB565) ----------
#define FARBE_BG 0x0862 // fast schwarz
#define FARBE_BAR 0x1105 // Kopfzeile
#define FARBE_TEXT 0xFFFF // weiß
#define FARBE_GRAU 0x7C52 // Sekundärtext
#define FARBE_LINIE 0x39C7 // Hilfslinie im Chart
#define FARBE_GRUEN 0x2610 // im Plus
#define FARBE_ROT 0xE267 // im Minus
#define FARBE_GRUEN_BG 0x09C5 // Füllfläche unter der Kurve
#define FARBE_ROT_BG 0x38A2
// ---------- Layout ----------
#define BREITE 320
#define HOEHE 240
#define KOPF_H 28
#define CHART_X 12
#define CHART_Y 152
#define CHART_W 296
#define CHART_H 60
uint32_t tKurse = 0;
uint32_t tChart = 0;
uint32_t tWechsel = 0;
uint8_t anzeigeIndex = 0; // aktuell angezeigter Wert
uint8_t chartIndex = 0; // als nächstes zu aktualisierender Chart
int letzteSekunde = -1;
// ===========================================================
void setup() {
Serial.begin(115200);
delay(200);
tft.begin();
tft.setSPISpeed(40000000); // 40 MHz; bei Bildstörungen auf 24000000 senken
tft.setRotation(3); // Querformat 320 x 240 (1 = um 180 Grad gedreht)
statusText("Verbinde mit WLAN ...");
wifiVerbinden();
configTzTime(TZ_INFO, NTP_SERVER);
statusText("Hole Kurse ...");
kurseAktualisieren();
statusText("Lade Charts ...");
for (uint8_t i = 0; i < ANZAHL; i++) {
chartAktualisieren(i);
delay(1000); // Twelve Data erlaubt 8 Anfragen pro Minute
}
tft.fillScreen(FARBE_BG);
kopfzeileZeichnen();
wertZeichnen(anzeigeIndex);
tKurse = tChart = tWechsel = millis();
}
// ===========================================================
void loop() {
uint32_t jetzt = millis();
// Live-Kurse aller Symbole
if (jetzt - tKurse >= KURS_INTERVALL) {
tKurse = jetzt;
kurseAktualisieren();
wertZeichnen(anzeigeIndex);
}
// Reihum einen Chart nachladen
if (jetzt - tChart >= CHART_INTERVALL) {
tChart = jetzt;
chartAktualisieren(chartIndex);
if (chartIndex == anzeigeIndex) wertZeichnen(anzeigeIndex);
chartIndex = (chartIndex + 1) % ANZAHL;
}
// Zum nächsten Wert blättern
if (jetzt - tWechsel >= WECHSEL_INTERVALL) {
tWechsel = jetzt;
anzeigeIndex = (anzeigeIndex + 1) % ANZAHL;
wertZeichnen(anzeigeIndex);
}
uhrzeitZeichnen();
delay(50);
}
// ===========================================================
// WLAN
// ===========================================================
void wifiVerbinden() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
uint8_t versuche = 0;
while (WiFi.status() != WL_CONNECTED && versuche < 40) {
delay(500);
Serial.print(".");
versuche++;
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Verbunden. IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("WLAN-Verbindung fehlgeschlagen.");
statusText("Kein WLAN!");
}
}
// ===========================================================
// Live-Kurse von Finnhub
// ===========================================================
void kurseAktualisieren() {
if (WiFi.status() != WL_CONNECTED) {
wifiVerbinden();
if (WiFi.status() != WL_CONNECTED) return;
}
for (uint8_t i = 0; i < ANZAHL; i++) {
String url = String("https://finnhub.io/api/v1/quote?symbol=")
+ watchlist[i].ticker + "&token=" + FINNHUB_KEY;
WiFiClientSecure client;
client.setInsecure(); // ohne Zertifikatsprüfung – hier ausreichend
HTTPClient https;
https.setTimeout(8000);
if (https.begin(client, url)) {
int code = https.GET();
if (code == HTTP_CODE_OK) {
JsonDocument doc;
DeserializationError fehler = deserializeJson(doc, https.getString());
if (!fehler) {
daten[i].kurs = doc["c"] | 0.0f;
daten[i].diff = doc["d"] | 0.0f;
daten[i].proz = doc["dp"] | 0.0f;
daten[i].gueltig = (daten[i].kurs > 0);
Serial.printf("%-6s %8.2f (%+.2f%%)\n", watchlist[i].ticker, daten[i].kurs, daten[i].proz);
} else {
Serial.printf("JSON-Fehler bei %s: %s\n", watchlist[i].ticker, fehler.c_str());
}
} else {
Serial.printf("Finnhub HTTP %d bei %s\n", code, watchlist[i].ticker);
}
https.end();
}
delay(250);
}
}
// ===========================================================
// Kursverlauf von Twelve Data
// ===========================================================
void chartAktualisieren(uint8_t i) {
if (WiFi.status() != WL_CONNECTED) return;
String url = String("https://api.twelvedata.com/time_series?symbol=")
+ watchlist[i].ticker
+ "&interval=15min&outputsize=" + String(MAX_PUNKTE)
+ "&order=ASC&apikey=" + TWELVE_KEY;
WiFiClientSecure client;
client.setInsecure();
HTTPClient https;
https.setTimeout(10000);
if (!https.begin(client, url)) {
Serial.println("TwelveData: Verbindung fehlgeschlagen");
return;
}
int code = https.GET();
if (code != HTTP_CODE_OK) {
Serial.printf("TwelveData HTTP %d bei %s\n", code, watchlist[i].ticker);
https.end();
return;
}
// Komplett einlesen statt direkt aus dem Stream zu parsen: Twelve Data
// antwortet je nach Größe mit "chunked transfer encoding", woran ein
// direkt lesender Parser scheitern würde.
String antwort = https.getString();
https.end();
// Filter: nur "close" je Eintrag sowie Status und Fehlermeldung behalten
JsonDocument filter;
filter["values"][0]["close"] = true;
filter["status"] = true;
filter["message"] = true;
JsonDocument doc;
DeserializationError fehler = deserializeJson(doc, antwort, DeserializationOption::Filter(filter));
if (fehler) {
Serial.printf("Chart-JSON-Fehler bei %s: %s\n", watchlist[i].ticker, fehler.c_str());
return;
}
// Wichtig: Twelve Data meldet Fehler oft mit HTTP 200 im JSON-Body
const char* status = doc["status"] | "";
if (strcmp(status, "ok") != 0) {
Serial.printf("TwelveData meldet Fehler bei %s: %s\n", watchlist[i].ticker, doc["message"] | "unbekannt");
return;
}
uint8_t k = 0;
for (JsonObject eintrag : doc["values"].as<JsonArray>()) {
if (k >= MAX_PUNKTE) break;
daten[i].verlauf[k++] = eintrag["close"].as<float>();
}
daten[i].punkte = k;
Serial.printf("Chart %s: %d Punkte\n", watchlist[i].ticker, k);
}
// ===========================================================
// Anzeige
// ===========================================================
void kopfzeileZeichnen() {
tft.fillRect(0, 0, BREITE, KOPF_H, FARBE_BAR);
textAn(12, 19, "MARKT-TICKER", &FreeSans9pt7b, FARBE_TEXT);
}
void wertZeichnen(uint8_t i) {
char puffer[32];
tft.fillRect(0, KOPF_H, BREITE, HOEHE - KOPF_H, FARBE_BG);
// Firmenname und Börsenkürzel
textAn(12, 52, watchlist[i].name, &FreeSans12pt7b, FARBE_TEXT);
textAn(12, 74, watchlist[i].ticker, &FreeSans9pt7b, FARBE_GRAU);
if (!daten[i].gueltig) {
textAn(12, 118, "keine Daten", &FreeSans12pt7b, FARBE_GRAU);
punkteZeichnen(i);
return;
}
uint16_t farbe = (daten[i].diff >= 0) ? FARBE_GRUEN : FARBE_ROT;
// Kurs, groß
snprintf(puffer, sizeof(puffer), "%.2f", daten[i].kurs);
textAn(12, 122, puffer, &FreeSansBold24pt7b, FARBE_TEXT);
// Veränderung, farbig
snprintf(puffer, sizeof(puffer), "%+.2f (%+.2f%%)", daten[i].diff, daten[i].proz);
textAn(12, 146, puffer, &FreeSans12pt7b, farbe);
// Dreieck als Richtungspfeil
if (daten[i].diff >= 0)
tft.fillTriangle(292, 92, 278, 116, 306, 116, farbe);
else
tft.fillTriangle(292, 116, 278, 92, 306, 92, farbe);
chartZeichnen(i);
punkteZeichnen(i);
}
// ---------- Der Kursverlauf ----------
void chartZeichnen(uint8_t i) {
tft.fillRect(CHART_X, CHART_Y, CHART_W, CHART_H, FARBE_BG);
uint8_t n = daten[i].punkte;
if (n < 2) {
textAn(CHART_X, CHART_Y + 34, "Verlauf nicht verfuegbar", &FreeSans9pt7b, FARBE_GRAU);
return;
}
// Wertebereich bestimmen – der Vortagesschluss soll immer sichtbar sein,
// damit die Nulllinie nicht aus dem Bild läuft
float vortag = daten[i].kurs - daten[i].diff;
float minWert = daten[i].verlauf[0];
float maxWert = daten[i].verlauf[0];
for (uint8_t k = 1; k < n; k++) {
if (daten[i].verlauf[k] < minWert) minWert = daten[i].verlauf[k];
if (daten[i].verlauf[k] > maxWert) maxWert = daten[i].verlauf[k];
}
if (vortag < minWert) minWert = vortag;
if (vortag > maxWert) maxWert = vortag;
float spanne = maxWert - minWert;
if (spanne < 0.0001f) spanne = 1.0f; // Division durch null vermeiden
// Gestrichelte Linie auf Höhe des Vortagesschlusses
int yVortag = CHART_Y + CHART_H - 1 - (int)((vortag - minWert) / spanne * (CHART_H - 1));
for (int x = CHART_X; x < CHART_X + CHART_W; x += 6) {
tft.drawFastHLine(x, yVortag, 3, FARBE_LINIE);
}
// Bildschirmkoordinaten aller Punkte berechnen
int px[MAX_PUNKTE], py[MAX_PUNKTE];
for (uint8_t k = 0; k < n; k++) {
px[k] = CHART_X + (int)((float)k * (CHART_W - 1) / (n - 1));
py[k] = CHART_Y + CHART_H - 1 - (int)((daten[i].verlauf[k] - minWert) / spanne * (CHART_H - 1));
}
uint16_t linie = (daten[i].diff >= 0) ? FARBE_GRUEN : FARBE_ROT;
uint16_t flaeche = (daten[i].diff >= 0) ? FARBE_GRUEN_BG : FARBE_ROT_BG;
// Durchgang 1: Fläche unter der Kurve füllen
for (uint8_t k = 1; k < n; k++) {
for (int x = px[k - 1]; x <= px[k]; x++) {
int y = py[k - 1];
if (px[k] != px[k - 1]) {
y += (py[k] - py[k - 1]) * (x - px[k - 1]) / (px[k] - px[k - 1]);
}
tft.drawFastVLine(x, y, CHART_Y + CHART_H - y, flaeche);
}
}
// Durchgang 2: Kurve darüber zeichnen
for (uint8_t k = 1; k < n; k++) {
tft.drawLine(px[k - 1], py[k - 1], px[k], py[k], linie);
}
}
void punkteZeichnen(uint8_t aktiv) {
int x = BREITE / 2 - ((ANZAHL - 1) * 14) / 2;
for (uint8_t k = 0; k < ANZAHL; k++) {
tft.fillCircle(x + k * 14, 228, 3, (k == aktiv) ? FARBE_TEXT : FARBE_GRAU);
}
}
void uhrzeitZeichnen() {
struct tm zeit;
if (!getLocalTime(&zeit, 0)) return;
if (zeit.tm_sec == letzteSekunde) return; // nur einmal pro Sekunde neu
letzteSekunde = zeit.tm_sec;
char puffer[12];
strftime(puffer, sizeof(puffer), "%H:%M:%S", &zeit);
// Eingebaute Schrift: kann den Hintergrund gleich mitfüllen -> kein Flackern
tft.setFont();
tft.setTextSize(2);
tft.setTextColor(FARBE_TEXT, FARBE_BAR);
tft.setCursor(BREITE - 88 - 12, 6);
tft.print(puffer);
tft.setTextSize(1);
}
// ---------- Kleine Helfer ----------
void textAn(int x, int grundlinie, const char* txt, const GFXfont* font, uint16_t farbe) {
tft.setFont(font);
tft.setTextSize(1);
tft.setTextColor(farbe);
tft.setCursor(x, grundlinie); // bei GFX-Schriften ist y die Grundlinie
tft.print(txt);
}
void statusText(const char* txt) {
tft.fillScreen(FARBE_BG);
textAn(20, 125, txt, &FreeSans12pt7b, FARBE_TEXT);
}
Schritt 6: Hochladen und starten
- Werkzeuge → Board: ESP32 Dev Module
- Werkzeuge → Port: den Port deines Boards wählen
- Upload Speed: 921600 (bei Problemen auf 115200 reduzieren)
- Auf Hochladen klicken.
Bleibt der Upload bei Connecting........___ hängen: BOOT-Taste am ESP32 gedrückt halten, bis der Upload anläuft. Manche Boards brauchen diesen Anstupser. Danach wird folgendes der Reihe nach sichtbar:
- Verbinde mit WLAN …"
- Hole Kurse …"
- Lade Charts …" (dauert wegen der Wartezeit zwischen den Anfragen etwa fünf Sekunden)
- und schließlich den ersten Wert mit Kurve
Parallel den Seriellen Monitor bei 115200 Baud öffnen. Dort läuft mit, welche Kurse und wie viele Chartpunkte abgerufen wurden – ideal zur Fehlersuche.
Optional | 3D-gedrucktes Gehäuse
Das Gehäuse besteht aus 3 Teilen und benötigt 4 5x2mm Magnete. Den Download gibts hier.
Fehlerbehebung
| Symptom | Ursache & Lösung |
|---|---|
| Display bleibt schwarz | Hintergrundbeleuchtung prüfen: liegt LED an 3V3? Danach CS, DC und RESET kontrollieren. |
| Display bleibt weiß | Meist DC oder RESET vertauscht. Prüfe, ob die #define-Werte oben im Sketch zur Verkabelung passen. |
| Bild verzerrt, gestreift oder mit Artefakten | tft.setSPISpeed() auf 24000000 reduzieren. Lange oder billige Jumper-Kabel vertragen 40 MHz oft nicht. |
| Farben vertauscht (Rot / Blau) | Manche Module verwenden BGR statt RGB. Abhilfe: nach tft.begin() die Zeile tft.sendCommand(ILI9341_MADCTL, (uint8_t[]) {0x28}, 1); einfügen und den Wert variieren. |
| Bild steht auf dem Kopf | tft.setRotation() in setup() zwischen 1 und 3 wechseln – beides ist Querformat, die beiden Werte unterscheiden sich um 180°. Welcher passt, hängt davon ab, ob die Stiftleiste des Displays links oder rechts sitzt. (0 und 2 ergeben Hochformat, dafür müsstest du das Layout anpassen.) |
FreeSans12pt7b nicht gefunden |
Die Adafruit-GFX-Bibliothek ist nicht (vollständig) installiert. Über den Bibliotheksverwalter nachinstallieren. |
redeclared as different kind of entity |
Eine eigene Variable trägt denselben Namen wie eine Funktion der Standardbibliothek. Berüchtigte Kandidaten: index, time, link, div, remove, y0/y1. Variable umbenennen, z. B. anzeigeIndex statt index. |
| WLAN verbindet nicht | Der ESP32 kann kein 5 GHz. Prüfe, ob dein Router ein 2,4-GHz-Netz anbietet und das Passwort exakt stimmt. |
| Finnhub HTTP 401 | API-Key falsch oder noch nicht aktiv. Im Browser testen (siehe Schritt 1). |
| Finnhub HTTP 429 | Zu viele Anfragen. KURS_INTERVALL erhöhen oder Watchlist verkleinern. |
| Finnhub HTTP 403 | Das Symbol gehört zu einer Börse außerhalb des Free-Tarifs. Bei US-Aktien bleiben. |
| TwelveData HTTP 429 / „API credits“ | Das Tageslimit von 800 ist erreicht. CHART_INTERVALL erhöhen – bei 6 Minuten sind es nur noch 240 Abrufe am Tag. |
| Chart zeigt „Verlauf nicht verfuegbar“ | Der Abruf ist fehlgeschlagen – das ist kein Ladezustand, alle Charts werden beim Start geholt. Serieller Monitor verrät die Ursache. Häufig: falscher Twelve-Data-Key oder aufgebrauchte Credits. Achtung, Twelve Data meldet beides mit HTTP 200 und "status":"error" im Body. Die Test-URL aus Schritt 1 im Browser aufrufen, dort steht der Grund im Klartext. |
| Chart ist eine flache Linie | Normal außerhalb der Handelszeiten – es gibt dann keine neuen Kurse. |
| Kurs ist 0,00 | Symbolschreibweise prüfen. Beide APIs erwarten exakt das Börsenkürzel, z. B. BRK.B statt BRK-B. |
| Upload schlägt fehl | BOOT-Taste beim Verbindungsaufbau gedrückt halten. Kein Port sichtbar? CP2102-Treiber installieren und ein echtes Datenkabel verwenden. |
| ESP32 startet ständig neu | Meist die Stromversorgung: USB-Ports an Monitoren oder Hubs liefern oft zu wenig. Netzteil mit mindestens 1 A nutzen. |