
Exchange Rate Display (ESP32 + LCD)
Beginner20 minThe ESP32 downloads the rates list the Central Bank of Turkey (TCMB) publishes every workday and shows the selling rates of the dollar, the euro and the pound on a 16x2 LCD. The screen turns its page every four seconds: first USD and EUR, then GBP and the rates' date; the list is refreshed every half hour. It teaches downloading a web page over HTTPS, finding a value inside the text that comes back, and updating an LCD without flicker. The same circuit as the Internet Clock: if you have built it, just upload the code. Install "LiquidCrystal I2C" from the Library Manager.

What you need
1 × ESP32 Development Board (ESP32-WROOM-32)
1 × 16x2 Character LCD Display (with I2C module)
4 × Jumper Wire - Female/Female
Wiring steps
- 1
Connect the LCD's power (GND and VCC)

Do not put the ESP32 on the breadboard: a 30-pin board covers nearly all of it. In this project the board lies on the table and the LCD is connected straight to its pins with four female-female jumper wires. Hold the board with its USB socket facing you; the pin names are printed on the board next to the pins. On the 4-pin header of the I2C module on the back of the LCD, connect GND to the GND pin on the ESP32's right edge, just above 3V3, and VCC to the VIN pin at the bottom of the left edge. VIN gives 5V while the ESP32 is plugged into USB; the LCD's text is clear at 5V.
- ESP32 GND (next to 3V3) → LCD GND (black wire)
- ESP32 VIN (the USB's 5V) → LCD VCC (red wire)
- 2
Connect the I2C wires (SDA and SCL)

Connect the LCD's SDA pin to the ESP32's D21 pin and its SCL pin to D22. These are the pins the ESP32 uses for I2C; if you use other pins, change SDA_PIN and SCL_PIN in the code too. Before you upload the code, write your home Wi-Fi network's name and password into WIFI_NAME and WIFI_PASSWORD in the code, minding capital letters. The ESP32 only joins 2.4 GHz networks; if your router broadcasts only on 5 GHz, it cannot see the network. If you have built the Internet Clock or the Wi-Fi Scanner, the circuit is the same; just upload the new code.
- ESP32 D21 → LCD SDA (yellow wire)
- ESP32 D22 → LCD SCL (green wire)
Arduino sketch
Download the sketch (.ino)Try it on Wokwi
// Install from the Library Manager:
// "LiquidCrystal I2C"
// Board: "ESP32 Dev Module"
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Your Wi-Fi network's name and password
const char* WIFI_NAME = "Network_Name";
const char* WIFI_PASSWORD = "Password";
// The Central Bank's daily rates list
const char* RATES_URL = "https://www.tcmb.gov.tr/kurlar/today.xml";
// Rates come out on workdays at 15:30;
// a look every half hour is enough (ms)
const unsigned long FETCH_EVERY = 30UL * 60 * 1000;
// If they fail, try again after this
const unsigned long RETRY_AFTER = 60000;
// Time between screen pages (ms)
const unsigned long PAGE_EVERY = 4000;
// The ESP32's I2C pins
const int SDA_PIN = 21;
const int SCL_PIN = 22;
// Ask about the password after this (ms)
const unsigned long WIFI_HINT_AFTER = 15000;
// I2C address: 0x27 on most, 0x3F on some
LiquidCrystal_I2C lcd(0x27, 16, 2);
// The last selling rates read; 0: none
float usd = 0;
float eur = 0;
float gbp = 0;
// The rates' date, e.g. "30.10.2026"
String rateDate = "";
unsigned long nextFetch = 0;
unsigned long lastPage = 0;
int page = 0;
// Writes a line and blanks the rest
// (no flicker)
void printLine(int row, const String& text) {
lcd.setCursor(0, row);
lcd.print(text);
for (int n = text.length(); n < 16; n++) {
lcd.print(' ');
}
}
// Finds a currency's selling rate
// (ForexSelling); 0 if it is missing
float sellingRate(const String& xml, const char* code) {
int at = xml.indexOf(String("CurrencyCode=\"") + code + "\"");
if (at < 0) return 0;
int start = xml.indexOf("<ForexSelling>", at);
if (start < 0) return 0;
start += 14;
int end = xml.indexOf('<', start);
return xml.substring(start, end).toFloat();
}
// Downloads the list; if it fails, the
// old rates stay on screen
bool fetchRates() {
WiFiClientSecure client;
// The certificate is not checked: only
// a public list is being read
client.setInsecure();
HTTPClient http;
http.begin(client, RATES_URL);
// Ask for it plain: to the ESP32's own
// request TCMB answers with gzip
http.setAcceptEncoding("identity");
int status = http.GET();
if (status != 200) {
Serial.print("Could not get rates, HTTP ");
Serial.println(status);
http.end();
return false;
}
String xml = http.getString();
http.end();
// The list gives its date at the top:
// Tarih="30.10.2026"
int at = xml.indexOf("Tarih=\"");
if (at >= 0) {
int end = xml.indexOf('"', at + 7);
rateDate = xml.substring(at + 7, end);
}
float newUsd = sellingRate(xml, "USD");
if (newUsd == 0) {
// Not the rates list: its start shows
// what went wrong
Serial.print("Unexpected answer (");
Serial.print(xml.length());
Serial.print(" bytes): ");
Serial.println(xml.substring(0, 160));
return false;
}
usd = newUsd;
eur = sellingRate(xml, "EUR");
gbp = sellingRate(xml, "GBP");
Serial.print(rateDate);
Serial.print(" USD ");
Serial.print(usd, 4);
Serial.print(" EUR ");
Serial.print(eur, 4);
Serial.print(" GBP ");
Serial.println(gbp, 4);
return usd > 0;
}
// "USD/TL 49.8644": exactly 16 chars
String rateLine(const char* code, float rate) {
char text[17];
snprintf(text, sizeof text, "%s/TL %8.4f", code, rate);
return String(text);
}
void showPage() {
if (usd == 0) {
printLine(0, "No rates yet");
printLine(1, "Will try again");
return;
}
if (page == 0) {
printLine(0, rateLine("USD", usd));
printLine(1, rateLine("EUR", eur));
} else {
printLine(0, rateLine("GBP", gbp));
printLine(1, "TCMB " + rateDate);
}
}
void setup() {
Serial.begin(115200);
Wire.begin(SDA_PIN, SCL_PIN);
lcd.init();
lcd.backlight();
lcd.print("Waiting for WiFi");
Serial.print("Connecting to ");
Serial.println(WIFI_NAME);
WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (millis() - start > WIFI_HINT_AFTER) {
// No luck: check name, password
lcd.setCursor(0, 1);
lcd.print("Name/password?");
}
}
Serial.println();
Serial.println("Connected");
printLine(0, "Getting rates");
printLine(1, "");
}
void loop() {
// At start-up, then every half hour
if ((long)(millis() - nextFetch) >= 0) {
bool ok = fetchRates();
nextFetch = millis() + (ok ? FETCH_EVERY : RETRY_AFTER);
showPage();
}
// Two pages in turn: USD and EUR, then
// GBP and the rates' date
if (millis() - lastPage > PAGE_EVERY) {
lastPage = millis();
page = 1 - page;
showPage();
}
}
What the Serial Monitor shows
Connecting to MyHomeWiFi ..... Connected 30.10.2026 USD 49.8644 EUR 55.8153 GBP 67.0171
If a wire is loose
Connecting to MyHomeWiFi ..... Connected Could not get rates, HTTP -1
What you will see
A one-time ESP32 set-up in the Arduino IDE: in File → Preferences, write https://espressif.github.io/arduino-esp32/package_esp32_index.json into "Additional boards manager URLs"; in Tools → Board → Boards Manager search for "esp32" and install "esp32 by Espressif Systems". Then pick "ESP32 Dev Module" and the ESP32's port under Tools → Board, and install "LiquidCrystal I2C" from the Library Manager (a warning about the "AVR architecture" does not matter, it works on the ESP32 too). Upload the code and open the Serial Monitor at 115200 baud. The LCD first says "Waiting for WiFi"; once it has joined the network it says "Getting rates", and within a few seconds "USD/TL 49.8644" shows on the top line and the euro's rate on the bottom one. Every four seconds it turns to a second page: the pound and the rates' date ("TCMB 30.10.2026"). The figures are the TCMB's forex selling rates; banks and exchange offices charge a little differently. The TCMB announces rates on workdays at 15:30: at weekends and on holidays the last workday's rates show, and the date says so. The list is read again every half hour; if that fails, the rates on screen stay and it tries again a minute later. If the Serial Monitor says "Could not get rates", check the internet connection. If the dots in the Serial Monitor never end and the LCD's bottom line says "Name/password?", check the network's name and password, and make sure it is a 2.4 GHz network. If the LCD's light is on but there is no text, turn the blue trimmer on the I2C module with a small screwdriver; if it still shows nothing, change 0x27 in the code to 0x3F. For another currency, write its code, like sellingRate(xml, "CHF"): the list has close to 20 of them.
Similar projects
Internet Clock (ESP32 + LCD)Beginner20 minA desk clock that takes the time from the internet and never needs setting: the ESP32 joins your home Wi-Fi, gets Turkey's time from a time server (NTP) and shows the date, the day and the time with its seconds on the LCD. After a power cut it finds the time again by itself. Ideal as a first ESP32 project: no soldering, no breadboard, just four wires.Open project
RFID Attendance (ESP32 + LCD)Intermediate40 minThe name of whoever scans their card appears on the LCD and is logged as "in" or "out"; the screen counts how many people are inside. For a club, class or workshop register: it shows the number of a card it does not know, and you add that number with a name to the list in the code. No breadboard needed; the RC522 and the LCD connect straight to the ESP32 with female-female wires.Open project
Wi-Fi Scanner (ESP32 + LCD)Beginner20 minA pocket scanner for the Wi-Fi networks around you: the ESP32 scans for networks, sorts them from the strongest to the weakest and shows each one's name, its signal strength (in dBm and as bars) and whether it is locked on the LCD, one after another; the Serial Monitor gets the full list with channels and encryption. Plug it into a power bank and walk around the house to find where the router reaches best. It joins no network and needs no password. The same four wires as the Internet Clock; it teaches scanning for networks, sorting an array and laying out a screen line with snprintf.Open project
In the app, tick off every wire, match the parts to your inventory and pick up where you left off.
