LilyGo T5 4.7” e-paper
T5 4.7”
The LilyGo T5 4.7” (ESP32-S3) is an e-paper development board built around a
4.7” 960x540, 16-level-grayscale ED047TC1 panel driven over a parallel bus (the
ESP32-S3 LCD_CAM peripheral). This BSP wraps the
epdiy library, which owns the panel
timing, waveforms, grayscale rendering, and partial-update handling.
The espp::LilyGoT547 component provides a singleton hardware abstraction that
brings up the e-paper display, an LVGL grayscale (L8) display, the GT911
capacitive touch (as an LVGL input device), the PCF8563 RTC, the BQ27220 battery
fuel gauge, the PCA9535 I/O expander (including the IO48 button), the BOOT
button, the SX1262 LoRa radio, the frontlight, the qwiic connector, and the
microSD card.
epdiy’s e-paper power ICs and the board’s other I2C peripherals share a single internal I2C bus (SDA=39 / SCL=40) that this BSP creates and hands to epdiy, so touch, RTC, battery gauge, I/O expander, and qwiic all live on the same bus.
API Reference
Header File
Classes
-
class LilyGoT547 : public espp::BaseComponent
The LilyGoT547
class provides an interface to the LilyGo T5 4.7” ESP32-S3
e-paper development board.
The board drives an ED047TC1 960x540 16-level-grayscale e-paper panel over a
parallel bus (the ESP32-S3 LCD_CAM peripheral). This BSP wraps Espressif’s
esp-idf-friendly build of the epdiy library, which owns the parallel timing,
waveforms, grayscale rendering and partial-update handling for this exact
board.
The class provides access to the following features:
- 4.7” 960x540 e-paper display (ED047TC1) via epdiy
The class is a singleton and can be accessed using the get() method.
Example
Public Types
-
using button_callback_t = espp::Interrupt::event_callback_fn
Alias for the button callback (an interrupt event handler)
-
using touch_callback_t = std::function<void(const espp::TouchpadData&)>
Alias for the touch callback.
Public Functions
-
bool initialize_display()
Initialize the e-paper display: bring up epdiy for this board (parallel bus + waveforms) and allocate the high-level grayscale framebuffer.
- Returns:
true if the display was successfully initialized
-
void power_on()
Power the display’s high-voltage rails on. Required before an update; the panel draws significant current, so keep it off when idle.
-
void power_off()
Power the display’s high-voltage rails off.
-
void clear()
Clear the whole panel to white (a full refresh; removes ghosting).
-
uint8_t *framebuffer()
Get the epdiy high-level framebuffer (4 bits per pixel, 2 pixels per byte, panel_width x panel_height). Draw into it with epdiy’s drawing functions, then call update().
- Returns:
Pointer to the framebuffer, or nullptr if not initialized
-
void update(EpdDrawMode mode = MODE_GC16)
Push the current framebuffer contents to the panel.
Note
The high-voltage rails must be powered (power_on()) for the update to take effect.
- Parameters:
mode – The epdiy draw mode (default MODE_GC16 for full 16-gray)
-
inline int width() const
Get the current display width (respecting the configured rotation)
- Returns:
Width in pixels
-
inline int height() const
Get the current display height (respecting the configured rotation)
- Returns:
Height in pixels
-
int temperature()
Get the ambient temperature (degrees C) used for the e-paper waveform.
- Returns:
Temperature in degrees Celsius
-
inline espp::I2c *internal_i2c()
Get the internal I2C bus. This is the bus epdiy uses to drive the e-paper power ICs (PCA9555 + TPS65185); this BSP creates it and hands the handle to epdiy so the same bus is shared with the board’s other I2C peripherals (touch, RTC, battery gauge, the qwiic connector). It is created by initialize_display().
- Returns:
Pointer to the espp::I2c, or nullptr if initialize_display() has not been called
-
inline espp::I2c *qwiic_i2c()
Get the I2C bus exposed on the qwiic / STEMMA-QT connector. On this board the qwiic connector is wired to the internal I2C bus (SDA=39/SCL=40), so this returns the same bus as internal_i2c(). Add external devices to it with internal_i2c()->add_device<…>(…).
- Returns:
Pointer to the espp::I2c, or nullptr if initialize_display() has not been called
-
bool initialize_lvgl(int buffer_lines = 60)
Set up LVGL with an 8-bit grayscale (L8) display whose flush writes into the epdiy framebuffer. Each LVGL refresh cycle’s dirty areas are batched into a single panel update (see set_lvgl_update_mode() / full_refresh()), so many small UI changes become one e-paper refresh instead of one per change.
- Parameters:
buffer_lines – Height, in lines, of the LVGL partial draw buffer (double-buffered, allocated in PSRAM). Larger means fewer flushes per refresh at the cost of more memory.
- Returns:
true on success. initialize_display() must have been called first.
-
inline lv_display_t *lvgl_display() const
Get the LVGL display created by initialize_lvgl().
- Returns:
The LVGL display, or nullptr if LVGL has not been initialized
-
inline void set_lvgl_update_mode(EpdDrawMode mode)
Set the epdiy draw mode used for LVGL-driven panel updates. Default is MODE_GC16 (full 16-level grayscale, highest quality but slowest). Use a faster mono mode (e.g. MODE_DU) for snappier black-and-white updates.
- Parameters:
mode – The epdiy draw mode
-
void full_refresh()
Force a full grayscale (MODE_GC16) refresh of the whole panel. Use this to clear ghosting that builds up after repeated partial updates.
-
void set_rotation(EpdRotation rotation)
Set the display rotation. Rotates the e-paper (via epdiy) and resizes the LVGL display to match, so an LVGL UI re-lays-out for the new dimensions. Touch coordinates are transformed to match the rotation automatically.
- Parameters:
rotation – The epdiy rotation (EPD_ROT_LANDSCAPE / EPD_ROT_PORTRAIT / EPD_ROT_INVERTED_LANDSCAPE / EPD_ROT_INVERTED_PORTRAIT)
-
void rotate()
Cycle to the next display rotation.
-
inline EpdRotation rotation() const
Get the current display rotation.
- Returns:
The current EpdRotation
-
bool initialize_button(const button_callback_t &callback = nullptr)
Initialize the BOOT button (GPIO0). The callback fires on press and release.
Note
This wires only the BOOT button (GPIO0). The board’s other buttons are elsewhere: the “IO48”-labelled button is on the PCA9535 expander (see io48_button_pressed()), and the PWR button is handled by the board’s power-management IC (not exposed here).
- Parameters:
callback – Called with each button interrupt event
- Returns:
true on success
-
bool button_state() const
Get the BOOT button state
- Returns:
true if pressed, false otherwise
-
inline espp::Interrupt *interrupts()
Get the interrupts manager (created by initialize_button()).
- Returns:
Pointer to the espp::Interrupt, or nullptr if no button was initialized
-
bool initialize_touch(const touch_callback_t &callback = nullptr)
Initialize the GT911 capacitive touch controller on the internal I2C bus, and register an LVGL input device for it.
Note
The GT911’s reported orientation may not match the panel’s; if touch is mirrored/rotated on hardware, adjust touch_swap_xy / touch_invert_x / touch_invert_y.
- Parameters:
callback – Called (from the touch interrupt) with each new touch state
- Returns:
true on success. initialize_display() must have been called first (it creates the shared I2C bus).
-
inline std::shared_ptr<espp::TouchpadInput> touchpad_input() const
Get the LVGL touchpad input device created by initialize_touch().
- Returns:
The touchpad input, or nullptr if touch was not initialized
-
espp::TouchpadData touchpad_data() const
Get the latest touchpad data (thread-safe copy).
- Returns:
The most recent espp::TouchpadData
-
void touchpad_read(uint8_t *num_touch_points, uint16_t *x, uint16_t *y, uint8_t *btn_state)
Read the latest touchpad data (signature used by espp::TouchpadInput).
-
bool home_button_pressed() const
Get the state of the capacitive home button (the touch key below the display). The GT911 reports it as a key press, separate from finger touch points, so it is available even when num_touch_points is 0.
Note
Requires initialize_touch(). Whether the key is active depends on the GT911 configuration flashed on the board; flagged for hardware verification.
- Returns:
true if the home button is currently pressed
-
bool initialize_rtc()
Initialize the PCF8563 real-time clock on the internal I2C bus.
Note
The PCF8563 is register-compatible with the BM8563, so this uses the espp::Bm8563 driver.
- Returns:
true on success. initialize_display() must have been called first (it creates the shared I2C bus).
-
inline espp::Bm8563 *rtc()
Get the RTC driver (created by initialize_rtc()).
- Returns:
Pointer to the espp::Bm8563, or nullptr if the RTC was not initialized
-
bool initialize_battery()
Initialize the BQ27220 battery fuel gauge on the internal I2C bus.
- Returns:
true on success. initialize_display() must have been called first (it creates the shared I2C bus).
-
inline espp::Bq27220 *battery()
Get the battery fuel gauge (created by initialize_battery()). Query it for voltage, current, state-of-charge, temperature, etc.
- Returns:
Pointer to the espp::Bq27220, or nullptr if not initialized
-
bool initialize_io_expander()
Initialize a driver for the on-board PCA9535 I/O expander on the internal I2C bus.
Warning
This is the SAME physical expander (address 0x20) that epdiy drives for the e-paper power ICs. epdiy owns port 1’s high bits (output enable, mode, power-up, VCOM, wakeup, power-good, INT). The driver is created with auto_init=false so it does not reconfigure the chip; only read inputs / drive port-0 pins epdiy does not use, and always read-modify-write. Reconfiguring epdiy’s bits will break the display’s power sequencing.
- Returns:
true on success. initialize_display() must have been called first.
-
inline espp::Pca9535 *io_expander()
Get the PCA9535 I/O expander driver (created by initialize_io_expander()).
Note
See initialize_io_expander()’s warning about epdiy co-ownership.
- Returns:
Pointer to the espp::Pca9535, or nullptr if not initialized
-
bool io48_button_pressed()
Read the “IO48”-labelled button, which is wired to the PCA9535 expander (pin PC12 = port 1, bit 2 - a pin epdiy does not use). This is not the PWR button (that is handled by the board’s power-management IC).
Note
Requires initialize_io_expander().
- Returns:
true if the button is currently pressed
-
void set_frontlight(bool on)
Turn the e-paper frontlight on or off (BL_EN / GPIO11). The pin is configured as an output on first use.
Note
This is a simple on/off enable (active-high, flagged for hardware verification). For dimming, drive GPIO11 with LEDC PWM instead.
- Parameters:
on – true to enable the frontlight
-
inline bool frontlight_on() const
Get the current frontlight state.
- Returns:
true if the frontlight is on
-
bool shutdown()
Power the board off by putting the BQ25896 PMIC into ship mode (disconnect the battery / BATFET_DIS). The board then draws negligible current and is turned back on by pressing the PWR button.
Note
This only powers the board off when running on battery - if USB power is connected the board stays powered. initialize_display() must have been called first (it creates the shared I2C bus the PMIC is on).
- Returns:
true if the ship-mode command was written successfully
-
bool initialize_sdcard(const SdCardConfig &config)
Initialize the microSD card (SPI). Mounts a FAT filesystem at mount_point (“/sdcard”).
Note
The card is on a dedicated SPI bus (it does not conflict with the e-paper’s parallel bus), so this can be called independently of initialize_display().
- Parameters:
config – The microSD card configuration
- Returns:
true if the card was mounted successfully
-
inline sdmmc_card_t *sdcard() const
Get the mounted microSD card.
- Returns:
Pointer to the sdmmc_card_t, or nullptr if not initialized
-
bool initialize_lora(const espp::Sx126x::RadioConfig &radio_config = {})
Initialize the LoRa radio (SX1262).
Note
The radio shares the SPI bus with the microSD card.
Note
The radio’s DIO1 interrupt is automatically serviced via the shared interrupt manager, so received packets / transmit completion trigger the driver’s callbacks (see espp::Sx126x::set_receive_callback etc.).
- Parameters:
radio_config – The radio (modem) configuration to apply
- Returns:
True if the radio was initialized properly
-
inline std::shared_ptr<espp::Sx126x> lora() const
Get the LoRa radio.
- Returns:
A shared pointer to the LoRa radio driver, or nullptr if initialize_lora() has not succeeded
-
inline const std::string &get_name() const
Get the name of the component
Note
This is the tag of the logger
- Returns:
A const reference to the name of the component
-
inline void set_log_tag(const std::string_view &tag)
Set the tag for the logger
- Parameters:
tag – The tag to use for the logger
-
inline espp::Logger::Verbosity get_log_level() const
Get the log level for the logger
See also
See also
- Returns:
The verbosity level of the logger
-
inline void set_log_level(espp::Logger::Verbosity level)
Set the log level for the logger
See also
See also
- Parameters:
level – The verbosity level to use for the logger
-
inline void set_log_verbosity(espp::Logger::Verbosity level)
Set the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls set_log_level
- Parameters:
level – The verbosity level to use for the logger
-
inline espp::Logger::Verbosity get_log_verbosity() const
Get the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls get_log_level
- Returns:
The verbosity level of the logger
-
inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
Set the rate limit for the logger
See also
Note
Only calls to the logger that have _rate_limit suffix will be rate limited
- Parameters:
rate_limit – The rate limit to use for the logger
Public Static Functions
-
static inline LilyGoT547 &get()
Access the singleton instance of the LilyGoT547 class.
- Returns:
Reference to the singleton instance
-
static inline constexpr auto lora_cs_gpio()
Get the GPIO pin for the LoRa radio chip select.
-
static inline constexpr auto lora_dio1_gpio()
Get the GPIO pin for the LoRa radio DIO1 (interrupt) line.
-
static inline constexpr auto lora_busy_gpio()
Get the GPIO pin for the LoRa radio BUSY line.
-
static inline constexpr auto lora_reset_gpio()
Get the GPIO pin for the LoRa radio reset line.
Public Static Attributes
-
static constexpr int panel_width = 960
Native panel width in pixels.
-
static constexpr int panel_height = 540
Native panel height in pixels.
-
static constexpr char mount_point[] = "/sdcard"
The filesystem mount point for the microSD card.
-
struct SdCardConfig
Configuration for the microSD card.
-
using button_callback_t = espp::Interrupt::event_callback_fn