feat(lilygo-t5-47): Add BSP for the LilyGo T5 4.7" ESP32-S3 e-paper board - #682
feat(lilygo-t5-47): Add BSP for the LilyGo T5 4.7" ESP32-S3 e-paper board#682finger563 wants to merge 1 commit into
Conversation
|
✅Static analysis result - no issues found! ✅ |
…oard Board support package (espp::LilyGoT547 singleton) for the LilyGo T5 4.7" (ESP32-S3) e-paper board, wrapping the epdiy library for the ED047TC1 960x540 16-level-grayscale panel. Features: - E-paper display via epdiy (LCD_CAM parallel bus): power on/off, clear, a grayscale framebuffer, and partial/full updates. - LVGL 9 integration: an L8 grayscale display whose flush batches each refresh cycle's dirty areas into a single panel update; selectable update mode (GC16 grayscale vs fast mono DU) and touch-aware display rotation. - GT911 capacitive touch (polled - the panel's controller config is query-mode) as an LVGL input device, plus the capacitive home button. - PCF8563 RTC (via the register-compatible espp::Bm8563 driver). - BQ27220 battery fuel gauge. - PCA9535 I/O expander (the same chip epdiy drives) including the IO48 button. - Frontlight, BOOT button, qwiic bus accessor, microSD (SPI), and power-off via BQ25896 ship mode. - All on-board I2C peripherals share epdiy's bus (100 kHz), handed to epdiy via epd_init_with_config so there is a single shared I2C master. Adds two reusable espp components used by this BSP: - bq27220: TI BQ27220 I2C battery fuel-gauge driver. - pca9535: PCA9535 / PCA9555 16-bit I2C GPIO-expander driver. The example is an interactive LVGL GUI (Gui class): a live RTC clock, a toggle-able stats panel (battery / touch / IO48- and home-button state), and buttons for frontlight, rotation, stats, full-refresh, and power-off. The panel update mode follows the stats panel (GC16 while shown for legibility, DU while hidden for a snappy clock). Docs pages and CI example-build entries added for all three components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d8b7b92 to
1763406
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new ESPP Board Support Package (BSP) for the LilyGo T5 4.7" ESP32-S3 e-paper board, plus two new reusable I2C driver components (BQ27220 fuel gauge and PCA9535/9555 I/O expander). This integrates epdiy for the ED047TC1 panel, provides LVGL 9 support, and documents/build-tests the new components in CI.
Changes:
- Added
espp::LilyGoT547BSP component (display via epdiy, LVGL flush integration, touch, RTC, battery gauge, IO expander, frontlight, SD card, and ship-mode shutdown). - Added new standalone components:
bq27220(fuel gauge) andpca9535(I/O expander), each with examples and docs. - Updated Sphinx/Doxygen docs and GitHub Actions CI build matrix to include the new examples.
Reviewed changes
Copilot reviewed 52 out of 52 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| doc/en/io_expander/pca9535.rst | Adds PCA9535/9555 documentation page. |
| doc/en/io_expander/pca9535_example.md | Adds docs include page for the PCA9535 example. |
| doc/en/io_expander/index.rst | Adds PCA9535 to IO expander docs index. |
| doc/en/dev_boards/lilygo/lilygo_t5_47.rst | Adds BSP documentation page for LilyGo T5 4.7". |
| doc/en/dev_boards/lilygo/lilygo_t5_47_example.md | Adds docs include page for the LilyGo T5 4.7" example. |
| doc/en/dev_boards/lilygo/index.rst | Adds the new LilyGo T5 4.7" page to the LilyGo boards index. |
| doc/en/battery/index.rst | Adds BQ27220 to battery docs index. |
| doc/en/battery/bq27220.rst | Adds BQ27220 documentation page. |
| doc/en/battery/bq27220_example.md | Adds docs include page for the BQ27220 example. |
| doc/Doxyfile | Adds new components’ headers and examples to Doxygen inputs/paths. |
| components/pca9535/README.md | PCA9535 component README. |
| components/pca9535/include/pca9535.hpp | Implements the PCA9535/9555 I2C expander driver. |
| components/pca9535/idf_component.yml | Component Manager manifest for pca9535. |
| components/pca9535/example/sdkconfig.defaults | Example default sdkconfig for pca9535. |
| components/pca9535/example/README.md | PCA9535 example README. |
| components/pca9535/example/main/pca9535_example.cpp | PCA9535 example application. |
| components/pca9535/example/main/Kconfig.projbuild | Kconfig options for PCA9535 example I2C pins/hardware. |
| components/pca9535/example/main/CMakeLists.txt | Example main component registration. |
| components/pca9535/example/CMakeLists.txt | Example project CMake (local components). |
| components/pca9535/CMakeLists.txt | pca9535 component build definition. |
| components/lilygo-t5-47/src/touch.cpp | GT911 bring-up and polling task + LVGL input integration. |
| components/lilygo-t5-47/src/sdcard.cpp | SPI bus init + FAT mount for microSD. |
| components/lilygo-t5-47/src/rtc.cpp | RTC bring-up using Bm8563 driver for PCF8563-compatible chip. |
| components/lilygo-t5-47/src/power.cpp | Ship-mode shutdown via PMIC register write. |
| components/lilygo-t5-47/src/lvgl.cpp | LVGL initialization + flush that writes to epdiy framebuffer and batches updates. |
| components/lilygo-t5-47/src/lilygo-t5-47.cpp | Core display initialization and basic display operations. |
| components/lilygo-t5-47/src/io_expander.cpp | PCA9535 bring-up (shared with epdiy) + IO48 button helper. |
| components/lilygo-t5-47/src/frontlight.cpp | Frontlight GPIO control. |
| components/lilygo-t5-47/src/buttons.cpp | BOOT button interrupt wiring (shared ISR service). |
| components/lilygo-t5-47/src/battery.cpp | BQ27220 bring-up on internal I2C bus. |
| components/lilygo-t5-47/README.md | BSP README including features and pin mapping. |
| components/lilygo-t5-47/include/lilygo-t5-47.hpp | BSP public API header and singleton interface. |
| components/lilygo-t5-47/idf_component.yml | Component Manager manifest for lilygo-t5-47 (includes epdiy git dependency). |
| components/lilygo-t5-47/example/sdkconfig.defaults | Example default sdkconfig (PSRAM/cache line/fonts/partition). |
| components/lilygo-t5-47/example/README.md | BSP example README describing the LVGL GUI behavior and hardware expectations. |
| components/lilygo-t5-47/example/main/lilygo-t5-47_example.cpp | BSP example app wiring up display, LVGL GUI, peripherals, and polling loop. |
| components/lilygo-t5-47/example/main/gui.hpp | GUI class interface + LVGL update task structure. |
| components/lilygo-t5-47/example/main/gui.cpp | GUI implementation (layout, buttons, update loop, refresh behavior). |
| components/lilygo-t5-47/example/main/CMakeLists.txt | Example main component registration. |
| components/lilygo-t5-47/example/CMakeLists.txt | Example project CMake (component manager enabled to fetch epdiy). |
| components/lilygo-t5-47/CMakeLists.txt | BSP component build definition and requirements. |
| components/bq27220/README.md | BQ27220 component README. |
| components/bq27220/include/bq27220.hpp | Implements the BQ27220 fuel gauge driver (little-endian standard commands). |
| components/bq27220/idf_component.yml | Component Manager manifest for bq27220. |
| components/bq27220/example/sdkconfig.defaults | Example default sdkconfig for bq27220. |
| components/bq27220/example/README.md | BQ27220 example README. |
| components/bq27220/example/main/Kconfig.projbuild | Kconfig options for BQ27220 example I2C pins/hardware. |
| components/bq27220/example/main/CMakeLists.txt | Example main component registration. |
| components/bq27220/example/main/bq27220_example.cpp | BQ27220 example application. |
| components/bq27220/example/CMakeLists.txt | Example project CMake (local components). |
| components/bq27220/CMakeLists.txt | bq27220 component build definition. |
| .github/workflows/build.yml | Adds CI build matrix entries for the new examples. |
Comments suppressed due to low confidence (2)
doc/Doxyfile:146
- The new pca9535 example entry is out of order in EXAMPLE_PATH (it appears between mcp23x17 and mt6701), despite the file’s note to keep entries alphabetical. Please move it to the correct location in the list to keep maintenance simple.
This issue also appears on line 340 of the same file.
doc/Doxyfile:343
- The new pca9535 header entry is out of order in Doxyfile INPUT (it is listed before mt6701/odrive_ascii). The header list is intended to be alphabetical; please reorder the local block so pca9535 is placed correctly.
| lv_init(); | ||
| lv_tick_set_cb(lvgl_tick_ms); | ||
|
|
||
| const int w = width(); | ||
| const int h = height(); | ||
| if (buffer_lines <= 0 || buffer_lines > h) { | ||
| buffer_lines = h; | ||
| } | ||
| // L8 = 1 byte per pixel. Two partial draw buffers in PSRAM. | ||
| const size_t buffer_bytes = static_cast<size_t>(w) * buffer_lines; | ||
| lvgl_buffer0_ = | ||
| static_cast<uint8_t *>(heap_caps_malloc(buffer_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); | ||
| lvgl_buffer1_ = | ||
| static_cast<uint8_t *>(heap_caps_malloc(buffer_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); | ||
| if (lvgl_buffer0_ == nullptr || lvgl_buffer1_ == nullptr) { | ||
| logger_.error("Could not allocate LVGL draw buffers ({} bytes each)", buffer_bytes); | ||
| return false; | ||
| } | ||
|
|
||
| lvgl_display_ = lv_display_create(w, h); | ||
| lv_display_set_color_format(lvgl_display_, LV_COLOR_FORMAT_L8); | ||
| lv_display_set_buffers(lvgl_display_, lvgl_buffer0_, lvgl_buffer1_, buffer_bytes, | ||
| LV_DISPLAY_RENDER_MODE_PARTIAL); | ||
| lv_display_set_user_data(lvgl_display_, this); | ||
| lv_display_set_flush_cb(lvgl_display_, &LilyGoT547::lvgl_flush_cb); | ||
| lv_display_set_default(lvgl_display_); |
| auto p0_pins = pca9535.get_pins(espp::Pca9535::Port::PORT0, ec); | ||
| if (ec) { | ||
| fmt::print("get_pins failed: {}\n", ec.message()); | ||
| return false; | ||
| } | ||
| // toggle the output on port 1 | ||
| output = ~output; | ||
| pca9535.set_pins(espp::Pca9535::Port::PORT1, output, ec); | ||
| if (ec) { | ||
| fmt::print("set_pins failed: {}\n", ec.message()); | ||
| return false; | ||
| } | ||
| fmt::print("{:.3f}, {:#x}, {:#x}\n", seconds, p0_pins, output); | ||
| // NOTE: sleeping in this way allows the sleep to exit early when the | ||
| // task is being stopped / destroyed | ||
| { | ||
| std::unique_lock<std::mutex> lk(m); | ||
| cv.wait_for(lk, 500ms); | ||
| } | ||
| // don't want to stop the task | ||
| return false; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 52 out of 52 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (3)
components/lilygo-t5-47/src/lvgl.cpp:43
- If either LVGL draw-buffer allocation fails, the function returns without freeing the other buffer, leaking PSRAM and leaving the object in a partially-initialized state for subsequent calls.
lvgl_buffer0_ =
static_cast<uint8_t *>(heap_caps_malloc(buffer_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
lvgl_buffer1_ =
static_cast<uint8_t *>(heap_caps_malloc(buffer_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
if (lvgl_buffer0_ == nullptr || lvgl_buffer1_ == nullptr) {
logger_.error("Could not allocate LVGL draw buffers ({} bytes each)", buffer_bytes);
return false;
}
doc/en/dev_boards/lilygo/lilygo_t5_47_example.md:1
- The docs include path is incorrect for this file's location (doc/en/dev_boards/lilygo/...). As written it resolves under doc/en/, so the rendered page will fail to include the example README.
components/lilygo-t5-47/include/lilygo-t5-47.hpp:207 - The header comment says the touch callback is invoked from an interrupt, but the implementation polls GT911 from a Task and calls the callback from that polling task. This matters for callback expectations (blocking/logging is OK in a task but not in an ISR).
/// Initialize the GT911 capacitive touch controller on the internal I2C bus,
/// and register an LVGL input device for it.
/// \param callback Called (from the touch interrupt) with each new touch state
/// \return true on success. initialize_display() must have been called first
/// (it creates the shared I2C bus).
| void Gui::set_label_if_changed(lv_obj_t *label, std::string &cache, std::string_view text) { | ||
| if (!label) { | ||
| return; | ||
| } | ||
| if (cache == text) { | ||
| return; // no change - avoid a needless e-paper refresh | ||
| } | ||
| cache = std::string(text); | ||
| std::lock_guard<std::recursive_mutex> lock(mutex_); | ||
| lv_label_set_text(label, cache.c_str()); | ||
| } |
| internal_i2c_ = std::make_unique<espp::I2c>(espp::I2c::Config{ | ||
| .port = I2C_NUM_0, | ||
| .sda_io_num = internal_i2c_sda, | ||
| .scl_io_num = internal_i2c_scl, | ||
| .sda_pullup_en = GPIO_PULLUP_ENABLE, | ||
| .scl_pullup_en = GPIO_PULLUP_ENABLE, | ||
| .clk_speed = 100 * 1000, // the board (and epdiy) run this bus at 100 kHz | ||
| .log_level = espp::Logger::Verbosity::WARN, | ||
| }); | ||
| EpdI2cConfig epd_i2c_config = {.bus_handle = internal_i2c_->native_bus_handle()}; | ||
| EpdInitConfig epd_init_config = {.i2c = &epd_i2c_config}; | ||
|
|
Description
Adds a Board Support Package for the LilyGo T5 4.7" (ESP32-S3) e-paper board
(
espp::LilyGoT547), wrapping the epdiylibrary for the ED047TC1 960×540 16-level-grayscale panel, plus two reusable
driver components it depends on.
BSP features (
components/lilygo-t5-47/):grayscale framebuffer, and partial/full updates.
L8grayscale display whose flush batches each refreshcycle's dirty areas into a single panel update; selectable update mode
(
MODE_GC16grayscale vs fast monoMODE_DU) and touch-aware displayrotation.
config is query-mode), plus the capacitive home button.
espp::Bm8563driver).BQ25896 ship mode.
epd_init_with_config()so there is a single shared I2C master.New reusable components:
components/bq27220/— TI BQ27220 I2C battery fuel-gauge driver.components/pca9535/— PCA9535 / PCA9555 16-bit I2C GPIO-expander driver.Example: an interactive LVGL GUI (
Guiclass) with a live RTC clock, atoggle-able stats panel (battery / touch / IO48- and home-button state), and
buttons for frontlight, rotation, stats, full-refresh, and power-off; the update
mode follows the stats panel (GC16 while shown for legibility, DU while hidden
for a snappy clock).
Docs pages and CI example-build entries are included for all three components.
Motivation and Context
There was no espp BSP for the LilyGo T5 4.7" e-paper board. E-paper on the
ESP32-S3 (parallel LCD_CAM timing, waveforms, grayscale, partial updates) is
non-trivial, so this wraps the well-supported epdiy library and exposes the whole
board — display, LVGL, touch, RTC, battery, I/O expander, frontlight, microSD,
and power management — behind a single easy-to-use component, matching the other
espp BSPs. The
bq27220andpca9535drivers are broadly useful on their own andare factored out as standalone components.
How has this been tested?
idf.py build— the BSPexample (
esp32s3), thebq27220example (esp32), and thepca9535example(
esp32s3). Added to.github/workflows/build.yml.touch stays aligned);
0x55 gauge, 0x5D touch, 0x68 TPS, 0x6B PMIC).
Screenshots (if appropriate, e.g. schematic, board, console logs, lab pictures):
Console (I2C bus scan on boot):
Scanning internal I2C bus...
I2C device found at 0x20 (PCA9535 expander)
I2C device found at 0x51 (PCF8563 RTC)
I2C device found at 0x55 (BQ27220 gauge)
I2C device found at 0x5D (GT911 touch)
I2C device found at 0x68 (TPS65185)
I2C device found at 0x6B (BQ25896 PMIC)
Types of changes
Checklist:
Software
.github/workflows/build.ymlfile to add my new test to the automated cloud build github action.