feat(esp32-p4-nano): Add display, touch, camera, uSD and audio support - #697
feat(esp32-p4-nano): Add display, touch, camera, uSD and audio support#697finger563 wants to merge 5 commits into
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the function-ev-board LVGL example to the ESP32-P4-NANO: a tabbed GUI (Status / Audio / Camera) that brings up the display, GT911 touch (touch-to-draw), uSD (logs size/free), ES8311 speaker + microphone (record/playback), Ethernet (DHCP client), and the MIPI-CSI camera streaming RGB565 frames into a PSRAM-backed lv_canvas on the Camera tab. Update the component/example READMEs (HTML tables) and the dev-board rst to describe the full peripheral set. Builds for esp32p4 on ESP-IDF 6.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
✅Static analysis result - no issues found! ✅ |
There was a problem hiding this comment.
Pull request overview
Expands the espp::Esp32P4Nano BSP from Ethernet-only to a full-peripheral board abstraction for the Waveshare ESP32-P4-NANO, adding display/touch/camera/audio/uSD support and an LVGL example that exercises all subsystems.
Changes:
- Add BSP implementations for MIPI-DSI display + LVGL flush, GT911 touch (polling/optional interrupt), MIPI-CSI camera capture (esp_video/V4L2), ES8311 audio in/out, and SDMMC microSD.
- Add Kconfig options for panel selection and touch interrupt configuration; update component manifests and docs to reflect new capabilities.
- Replace the example with a tabbed LVGL GUI demonstrating status, audio record/playback, and live camera rendering, plus updated partitioning/sdkconfig defaults.
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| doc/en/dev_boards/waveshare/esp32_p4_nano.rst | Updates board documentation to cover all supported peripherals and requirements. |
| components/esp32-p4-nano/src/video.cpp | Implements MIPI-DSI LCD init and LVGL flush/rotation support. |
| components/esp32-p4-nano/src/touchpad.cpp | Adds GT911 initialization and polling/interrupt touch handling. |
| components/esp32-p4-nano/src/sdcard.cpp | Adds SDMMC + FATFS mounting and card info helpers. |
| components/esp32-p4-nano/src/camera.cpp | Adds esp_video/V4L2 camera init, buffer management, and capture task. |
| components/esp32-p4-nano/src/audio.cpp | Adds ES8311 + I2S playback and microphone capture tasks. |
| components/esp32-p4-nano/README.md | Documents expanded BSP scope and APIs per peripheral. |
| components/esp32-p4-nano/Kconfig.projbuild | Adds panel selection + touch/interrupt task configuration. |
| components/esp32-p4-nano/include/esp32-p4-nano.hpp | Exposes new BSP APIs and board pin/peripheral configuration. |
| components/esp32-p4-nano/idf_component.yml | Extends component metadata + dependencies (display/touch/audio/camera/SD). |
| components/esp32-p4-nano/example/sdkconfig.defaults | Enables PSRAM, MIPI-CSI pipeline options, and larger partitions for the richer example. |
| components/esp32-p4-nano/example/README.md | Documents the new LVGL GUI example and camera feed path. |
| components/esp32-p4-nano/example/partitions.csv | Adds a custom partition table sized for the larger app and storage. |
| components/esp32-p4-nano/example/main/idf_component.yml | Overrides espp/ethernet for the example build when using component manager. |
| components/esp32-p4-nano/example/main/gui.hpp | Introduces a thread-safe LVGL GUI wrapper class. |
| components/esp32-p4-nano/example/main/gui.cpp | Implements the tabbed GUI, camera canvas updates, and drawing overlay. |
| components/esp32-p4-nano/example/main/esp32_p4_nano_example.cpp | New end-to-end example wiring up BSP peripherals and GUI, plus audio record/playback. |
| components/esp32-p4-nano/example/main/CMakeLists.txt | Embeds a WAV asset used for touch click playback. |
| components/esp32-p4-nano/example/CMakeLists.txt | Enables component manager and explicitly lists required component dirs. |
| components/esp32-p4-nano/CMakeLists.txt | Updates component requirements to include new peripheral dependencies. |
…aces) - video: allocate the rotation buffer once (no PSRAM leak on re-init) and log on failure; fix the 180° lv_draw_sw_rotate call to use width-based dims/stride (the swapped hh/ww + h_stride corrupted non-square updates). - audio: honor the Task stop contract in audio_task_callback (finite i2s write timeout + check task_notified) so Task::stop() can join; null-check xStreamBufferCreate and unwind the I2S channel on failure. - touchpad: the polling task now returns true when notified so Task::stop() joins promptly; lock touchpad_data_mutex_ (now mutable) in the touchpad_data() accessor to avoid a torn read. - camera: report the driver's buf.bytesused as the frame length (fall back to the computed RGB565 size) instead of assuming width*height*2. - example: read the WAV sample rate via std::memcpy (avoid an unaligned load / UB), include espp format.hpp for fmt::format, and honor stop in the GUI update task. - docs: match the rst section-underline lengths to their titles. Builds for esp32p4 on ESP-IDF 6.0; cppcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 21 changed files in this pull request and generated 4 comments.
Suppressed comments (5)
components/esp32-p4-nano/src/audio.cpp:55
- Several failure paths return
falseafter allocating/initializing subsystem resources (e.g.,i2s_new_channel,i2s_channel_init_std_mode) without tearing them down. This can leak I2S channel handles and leave hardware in a partially initialized state, which makes retries unreliable. Add a consistent cleanup path (or RAII wrappers) that disables and deletes any created channels/objects before returning on error.
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(audio_i2s_port, I2S_ROLE_MASTER);
chan_cfg.auto_clear = true;
if (i2s_new_channel(&chan_cfg, &audio_tx_handle, &audio_rx_handle) != ESP_OK) {
logger_.error("Failed to create I2S channel");
return false;
}
components/esp32-p4-nano/src/audio.cpp:70
- Several failure paths return
falseafter allocating/initializing subsystem resources (e.g.,i2s_new_channel,i2s_channel_init_std_mode) without tearing them down. This can leak I2S channel handles and leave hardware in a partially initialized state, which makes retries unreliable. Add a consistent cleanup path (or RAII wrappers) that disables and deletes any created channels/objects before returning on error.
if (i2s_channel_init_std_mode(audio_tx_handle, &audio_std_cfg) != ESP_OK) {
logger_.error("Failed to init I2S std mode");
return false;
}
components/esp32-p4-nano/src/audio.cpp:83
- Several failure paths return
falseafter allocating/initializing subsystem resources (e.g.,i2s_new_channel,i2s_channel_init_std_mode) without tearing them down. This can leak I2S channel handles and leave hardware in a partially initialized state, which makes retries unreliable. Add a consistent cleanup path (or RAII wrappers) that disables and deletes any created channels/objects before returning on error.
if (es8311_codec_init(&es8311_cfg) != ESP_OK) {
logger_.error("ES8311 init failed");
return false;
}
components/esp32-p4-nano/example/main/esp32_p4_nano_example.cpp:323
- When
audio_bytesis already populated,load_audio()returnstruewithout settingout_sample_rate. A second caller would see an uninitialized/previous value (potentially 0) and could configure the codec with the wrong sample rate. Store the parsed sample rate in a static alongsideaudio_bytesand always set bothout_sizeandout_sample_rateon all successful returns.
static bool load_audio(size_t &out_size, size_t &out_sample_rate) {
if (!audio_bytes.empty()) {
out_size = audio_bytes.size();
return true;
}
components/esp32-p4-nano/src/touchpad.cpp:104
- Log message capitalization is inconsistent with other messages in these diffs (most start with a capital letter). Consider changing to "Could not update touch driver: {}" for consistency.
logger_.error("could not update touch driver: {}", ec.message());
…acity, teardown) - video: rotate a local copy of LVGL's const area* (no const_cast UB); track the rotation-buffer capacity and skip rotation when a flush area would exceed it (grow the buffer if a later initialize_display() needs more) to prevent overflow. - audio: refuse audio_sample_rate() while the microphone is running (TX/RX share the full-duplex I2S clock); consolidate initialize_audio() failure paths into a single teardown that deletes both I2S channels + the stream buffer. - camera: include <freertos/FreeRTOS.h> + <freertos/task.h> explicitly for vTaskDelay / pdMS_TO_TICKS. Builds for esp32p4 on ESP-IDF 6.0; cppcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 21 changed files in this pull request and generated no new comments.
Suppressed comments (4)
components/esp32-p4-nano/example/main/esp32_p4_nano_example.cpp:125
- The status_task Task callback waits on the condition_variable but always returns false, even when cv.wait_for() returns no_timeout due to Task::stop() notification. That violates the Task callback contract and can cause stop()/destructors to hang waiting for this callback to exit (e.g., if someone later adds teardown paths).
std::unique_lock<std::mutex> lock(m);
cv.wait_for(lock, 100ms);
return false;
components/esp32-p4-nano/src/touchpad.cpp:91
- initialize_touch() starts the polling Task but ignores the return value. If the task cannot be created (e.g., OOM/invalid config), initialize_touch() will still return true and the BSP will report touch initialized even though no updates will occur.
.task_config = {.name = "p4-nano touch",
.stack_size_bytes = CONFIG_ESP32_P4_NANO_TOUCH_TASK_STACK_SIZE}});
touch_task_->start();
}
components/esp32-p4-nano/src/audio.cpp:131
- initialize_audio() sets the speaker PA enable high and flips audio_initialized_ before confirming the audio task actually started. If Task::start() fails, the function returns false but leaves audio_initialized_ true and the PA enabled, making subsequent calls inconsistent and potentially leaving I2S resources active.
set_speaker_enabled(true);
audio_initialized_ = true;
return audio_task_->start();
}
components/esp32-p4-nano/src/video.cpp:64
- initialize_lcd() returns false on several failure paths after partially initializing resources (LDO channel acquired, DSI bus created, IO created, DPI panel created). Because the handles remain set, a later retry can skip creation steps and proceed with a partially-initialized LCD stack, and resources are leaked on failure.
ret = esp_lcd_new_dsi_bus(&bus_config, &lcd_handles_.mipi_dsi_bus);
if (ret != ESP_OK) {
logger_.error("New DSI bus init failed: {}", esp_err_to_name(ret));
return false;
}
- initialize_audio() now checks audio_task_->start() before marking audio initialized / enabling the speaker; on failure it resets the task and runs the fail_audio_init() teardown so no resources are left enabled/allocated. - audio_sample_rate() early-returns if called before initialize_audio() (avoids dereferencing null I2S/stream handles) and now checks the I2S reconfig return codes. Builds for esp32p4 on ESP-IDF 6.0; cppcheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 21 changed files in this pull request and generated no new comments.
Suppressed comments (2)
components/esp32-p4-nano/example/main/esp32_p4_nano_example.cpp:125
- The status task’s Task callback ignores the return value of cv.wait_for() and always returns false. If this task is ever stopped (e.g., if the example is refactored to tear down tasks), Task::stop() can notify the condition_variable but the callback will keep running, preventing stop() from joining promptly.
std::unique_lock<std::mutex> lock(m);
cv.wait_for(lock, 100ms);
return false;
components/esp32-p4-nano/include/esp32-p4-nano.hpp:653
- display_controller_ is initialized to UNKNOWN, so get_display_controller_name()/get_display_controller() report "Unknown" until initialize_lcd() runs even though the default controller is already known at compile time via Kconfig. This makes early logs (e.g. in the example) misleading and forces callers to special-case initialization order.
DisplayController display_controller_{DisplayController::UNKNOWN};
Description
Extends the Waveshare ESP32-P4-NANO BSP (
espp::Esp32P4Nano), previously Ethernet-only, to expose the board's full peripheral set, reusing espp's proven ESP32-P4 patterns (esp32-p4-function-ev-boardfor display/touch/audio/SD,m5stack-tab5for camera):esp_video/V4L2, OV5647 by default (Kconfig-selectable), SCCB on the shared I2C, reset/pwdn not routed (free-run).The example is a tabbed LVGL GUI (Status / Audio / Camera) exercising every subsystem, including the live camera feed rendered into a PSRAM
lv_canvas.Motivation and Context
The BSP only supported Ethernet; this makes the board usable for the display/camera/audio/storage applications it's designed for.
How has this been tested?
esp32p4on ESP-IDF 6.0 (component + example); cppcheck clean.Types of changes
Checklist:
Software
Notes
esp_video/esp_cam_sensor) and overridesespp/ethernetto the in-repo component in the example's manifest only — the shipped BSP manifest stays clean (espp/ethernetis not yet published; upload ordering handles that at release).🤖 Generated with Claude Code