Firmware for a desk clock / thermometer on the WaveShare ESP32-S3-RLCD-4.2.
README.md is the product spec — layout, update rates and error behaviour are
defined there and should be treated as requirements, not suggestions.
pio run # build
pio run -t upload # flash over USB-C
pio device monitor # 115200 baud
include/secrets.h is gitignored. Copy include/secrets.h.example to it before
the first build; the build fails without it.
Confirmed from the vendor examples in
waveshareteam/ESP32-S3-RLCD-4.2
(02_Example/Arduino/), not from the wiki — the wiki does not publish a pinout.
| Function | Pin / value |
|---|---|
| LCD controller | ST7305, 300 × 400 mono reflective, SPI |
| LCD SCK / MOSI / DC / CS / RST / TE | 11 / 12 / 5 / 40 / 41 / 6 |
| I²C SDA / SCL | 13 / 14 |
| PCF85063 RTC | 0x51 |
| SHTC3 temp/humidity | 0x70 |
| Module | ESP32-S3-WROOM-1-N16R8 — 16 MB flash, 8 MB octal PSRAM |
The panel is native 300 wide × 400 high. The app drives it rotated
(U8G2_R1), so all drawing code works in a 400 × 300 landscape frame. Any
new layout constant belongs in include/config.h, not inline in display.cpp.
board_build.arduino.memory_type = qio_opi in platformio.ini is what selects
octal PSRAM. Dropping it silently disables PSRAM at boot.
lib/ST7305_U8g2/ is vendored verbatim from the vendor's 10_U8G2_Test
example (Apache-2.0). Treat it as third party: fix bugs upstream-style, keep
local changes minimal and commented.
src/main.cpp startup order + the loop; owns the indoor reading
src/display.{h,cpp} the only code that touches the panel
src/rtc.{h,cpp} PCF85063 driver, stores UTC
src/temperature.{h,cpp} SHTC3 driver
src/network.{h,cpp} Wi-Fi association + NTP, writes the RTC
src/outdoor.{h,cpp} HTTP poll of the remote sensor
include/config.h pins, layout, intervals, timezone
The RTC holds UTC, never local time. Presentation goes through
localtime() with TIMEZONE_POSIX, so a daylight-saving change never requires
a write to the chip. Anything that writes the RTC must convert to UTC first.
Boot order is deliberate. Panel → RTC → SHTC3 → first draw → then Wi-Fi.
A useful screen appears in about a second even if the network never associates.
Do not move network::begin() earlier.
Nothing in loop() blocks on the network. network::loop() owns its own
retry timing (WIFI_RETRY_MS); the clock and indoor reading must keep updating
through an outage.
The panel is refreshed only when a rendered string changes, not when a
value changes. display::update() formats everything to text and compares
against what is on screen, which is why a 21.54 → 21.51 drift causes no
refresh. This is the mechanism behind the README's "avoid unnecessary
full-screen refreshes"; keep it if you rework the renderer.
Missing data draws as --.- / --:--, never as a zero or a blank. A stale
outdoor reading keeps its last value on screen with a dashed rule under it —
the README is explicit that the old number is preferable to an error value.
All from U8g2's built-in set; no font files are shipped. FONT_TIME is
logisoso92_tn, which is digits, colon, period and minus only — it cannot
render letters. FONT_TEMP must stay a _tf variant so ° (U+00B0)
resolves, and temperatures must be drawn with drawUTF8, not drawStr.
Everything under README "Future Features", plus: light sleep between refreshes (the "low power consumption" goal is currently unaddressed — the loop spins), partial-region panel updates, and the TE pin.