ESP32-S3 Wi-Fi Controlled ILI9341 TFT Display for Cryptocurrency Prices
View and Edit Project In Cirkit Designer
Circuit Documentation
Summary
This circuit integrates an ESP32-S3 microcontroller with an ILI9341 TFT display. The ESP32-S3 is responsible for fetching cryptocurrency prices from an API and displaying them on the ILI9341 screen. The circuit is designed to connect to WiFi and periodically update the displayed prices.
Component List
1. ILI9341
- Description: A TFT display controller that interfaces with microcontrollers to display graphics and text.
- Purpose: To visually present data fetched from the internet, specifically cryptocurrency prices.
2. ESP32-S3 (beta)
- Description: A powerful microcontroller with integrated WiFi and Bluetooth capabilities.
- Purpose: To handle the logic of fetching data from the internet and controlling the ILI9341 display.
Wiring Details
ILI9341
- VCC: Connected to ESP32-S3 3V3
- GND: Connected to ESP32-S3 GND
- CS: Connected to ESP32-S3 pin 10
- RST: Connected to ESP32-S3 RST
- D/C: Connected to ESP32-S3 pin 9
- MOSI: Connected to ESP32-S3 pin 11
- SCK: Connected to ESP32-S3 pin 12
- MISO: Connected to ESP32-S3 pin 13
- LED: Connected to ESP32-S3 3V3
ESP32-S3 (beta)
- 3V3: Connected to ILI9341 VCC
- GND: Connected to ILI9341 GND
- RST: Connected to ILI9341 RST
- 10: Connected to ILI9341 CS
- 9: Connected to ILI9341 D/C
- 11: Connected to ILI9341 MOSI
- 12: Connected to ILI9341 SCK
- 13: Connected to ILI9341 MISO
Documented Code
Main Code (sketch.ino)
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
// WiFi
#define WIFI_SSID "CirkitWifi"
#define WIFI_PASS ""
// TFT pins (match your wiring)
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST -1 // ILI9341 RST tied to ESP32 RST (no GPIO control)
#define TFT_SCLK 12
#define TFT_MOSI 11
#define TFT_MISO 13
// Route public API calls through Cirkit's data gateway so shared demos
// use cached responses and don't hammer third-party APIs directly.
// This is the avoid rate-limiting in public demos.
String cachedUrl(const char* url) {
String encoded = String(url);
encoded.replace("&", "%26");
return "https://app.cirkitdesigner.com/api/data-gateway/fetch?url=" + encoded;
}
// CoinGecko (one request for multiple coins)
String priceUrl = cachedUrl("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum,solana&vs_currencies=usd");
Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);
WiFiClientSecure tls;
// Simple text writer
static int gY = 34;
inline void tftResetCursor() { gY = 34; }
void tftHeader(const char* title) {
tft.fillScreen(ILI9341_BLACK);
tft.setTextWrap(false);
tft.setTextSize(2);
tft.setTextColor(ILI9341_CYAN, ILI9341_BLACK);
tft.setCursor(4, 4);
tft.print(title);
tft.drawLine(0, 26, tft.width(), 26, ILI9341_DARKGREY);
tftResetCursor();
}
void tftLine(const String& s, uint16_t color = ILI9341_WHITE) {
const int lineH = 20;
if (gY > (tft.height() - lineH)) gY = 34;
tft.fillRect(0, gY, tft.width(), lineH, ILI9341_BLACK);
tft.setCursor(4, gY);
tft.setTextSize(2);
tft.setTextColor(color, ILI9341_BLACK);
tft.print(s);
gY += lineH;
}
static String bodyBuf;
static int httpsGet(const char* url, String& bodyOut) {
HTTPClient https;
https.setUserAgent("ESP32-CRYPTO/1.0");
https.setTimeout(15000);
if (!https.begin(tls, url)) return -1000;
int code = https.GET();
if (code > 0) bodyOut = https.getString();
https.end();
return code;
}
// Minimal JSON extractor: finds "<coin>":{"usd":<number>}
static String getUsd(const String& json, const char* coin) {
String key = String("\"") + coin + String("\"");
int k = json.indexOf(key);
if (k < 0) return "";
int u = json.indexOf("\"usd\":", k);
if (u < 0) return "";
int i = u + 6;
while (i < (int)json.length() && isspace((unsigned char)json[i])) i++;
int j = i;
while (j < (int)json.length()) {
char c = json[j];
if (!(isDigit((unsigned char)c) || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-')) break;
j++;
}
if (j > i) return json.substring(i, j);
return "";
}
static void connectWiFiOnce() {
WiFi.mode(WIFI_STA);
WiFi.setSleep(false);
WiFi.begin(WIFI_SSID, WIFI_PASS);
unsigned long t0 = millis();
const unsigned long TIMEOUT = 20000;
tftLine("Connecting WiFi...");
while (WiFi.status() != WL_CONNECTED && millis() - t0 < TIMEOUT) {
delay(300);
}
if (WiFi.status() == WL_CONNECTED) {
tftLine("WiFi: connected", ILI9341_GREEN);
tftLine("IP: " + WiFi.localIP().toString(), ILI9341_DARKGREY);
} else {
tftLine("WiFi: FAILED", ILI9341_RED);
}
}
static void showPricesOnce() {
if (WiFi.status() != WL_CONNECTED) {
tftLine("No WiFi", ILI9341_RED);
return;
}
bodyBuf = "";
int code = httpsGet(priceUrl.c_str(), bodyBuf);
if (code != 200 || bodyBuf.length() == 0) {
tftLine("Fail code:" + String(code), ILI9341_RED);
return;
}
String btc = getUsd(bodyBuf, "bitcoin");
String eth = getUsd(bodyBuf, "ethereum");
String sol = getUsd(bodyBuf, "solana");
if (!btc.length() && !eth.length() && !sol.length()) {
tftLine("Parse failed", ILI9341_RED);
return;
}
// Draw a price block
const int barH = 26;
if (gY > (tft.height() - 3 * barH)) gY = 34;
auto drawRow = [&](const char* sym, const String& v, uint16_t fg, uint16_t bg) {
tft.fillRect(0, gY, tft.width(), barH, bg);
tft.setCursor(4, gY + 4);
tft.setTextSize(2);
tft.setTextColor(fg, bg);
tft.print(sym);
tft.print(": $");
tft.print(v.length() ? v : String("-"));
gY += barH;
};
drawRow("BTC", btc, ILI9341_YELLOW, ILI9341_DARKGREY);
drawRow("ETH", eth, ILI9341_CYAN, ILI9341_BLACK);
drawRow("SOL", sol, ILI9341_GREEN, ILI9341_DARKGREY);
}
void setup() {
// Prepare SPI before TFT
pinMode(TFT_CS, OUTPUT); digitalWrite(TFT_CS, HIGH);
pinMode(TFT_DC, OUTPUT);
if (TFT_RST >= 0) { pinMode(TFT_RST, OUTPUT); digitalWrite(TFT_RST, HIGH); }
SPI.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, TFT_CS);
tft.begin(8000000);
tft.setRotation(1);
tftHeader("Crypto Prices");
connectWiFiOnce();
// TLS