DIY ELECTRONICSTR

← All projects

Exchange Rate Display (ESP32 + LCD)

Beginner20 min

The 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.

Get it on Google Play

What you need

Wiring steps

  1. 1

    Connect the LCD's power (GND and VCC)

    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. 2

    Connect the I2C wires (SDA and SCL)

    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

Run the circuit in your browser before buying a part: start the sketch on Wokwi and watch the Serial Monitor.

// 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

SERIAL MONITOR · 115200 BAUD
Connecting to MyHomeWiFi
.....
Connected
30.10.2026  USD 49.8644  EUR 55.8153  GBP 67.0171

If a wire is loose

SERIAL MONITOR · 115200 BAUD
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

In the app, tick off every wire, match the parts to your inventory and pick up where you left off.

Get it on Google Play