Embedded & Firmware
Yellow Screen Desk Display
A Pomodoro timer and desk dashboard on the ESP32 "Cheap Yellow Display", with touch input, MP3 alerts from SD over I2S, and a Wi-Fi-synced clock.
- ESP32
- C++
- PlatformIO
- TFT_eSPI
- I2S audio
- XPT2046 touch

What it is
A desk timer and dashboard on the ESP32 “Cheap Yellow Display” board (ESP32 + ST7789 TFT + XPT2046 resistive touch + SD slot). It has two screens:
- Pomodoro timer: set work and break lengths by touch, with a progress bar. At the end of each session an MP3 from the SD card plays over I2S.
- Dashboard: Wi-Fi-synced clock, room temperature, and brightness / colour-temperature sliders for a light. The light is simulated for now; the sliders are a placeholder for a smart-light integration.

Structure
A small state machine switches between the dashboard and the timer. Each screen does its own drawing and touch handling.
| Class | Responsibility |
|---|---|
CYD |
TFT, touch controller and LEDs |
PomodoroManager |
Timer state, timer screen and its touch zones |
AudioManager |
MP3 decode and I2S output |
SDManager |
FAT32 file access |
Touch handling
Touch zones are hard-coded rectangles in screen coordinates. While the alarm plays, any touch stops it (PomodoroManager.cpp):
void PomodoroManager::handleTouch(int16_t x, int16_t y) {
if (m_isAlarmSounding) {
// Stop alarm on any touch
m_audio.stop();
m_isAlarmSounding = false;
// ... transition to next state ...
return;
}
if (m_isRunning) {
// Stop button
if (y >= 190 && y <= 230 && x >= 110 && x <= 210) {
m_isRunning = false;
drawInterface();
}
return;
}
// ... time adjustment and start buttons ...
}
Main loop
Audio playback is non-blocking, so the MP3 decoder has to be fed from the main loop. The UI is serviced every 20 ms, and audioManager.loop() runs on every pass so the I2S buffer doesn’t underrun during playback (main.cpp):
void loop() {
static unsigned long lastUpdate = 0;
unsigned long currentTime = millis();
if (currentTime - lastUpdate >= 20) {
cyd.update();
lastUpdate = currentTime;
}
audioManager.loop();
}