feat(bsp): add Waveshare ESP32-P4-Module-Dev-Kit board support - #740
feat(bsp): add Waveshare ESP32-P4-Module-Dev-Kit board support#740finger563 wants to merge 8 commits into
Conversation
…ule-DEV-KIT Add a new BSP component for the Waveshare ESP32-P4-Module-DEV-KIT (the ESP32-P4-Module — P4 + 16 MB flash + 32 MB PSRAM + on-module ESP32-C6 — on a carrier board with RJ45 Ethernet / PoE option, MIPI-DSI + MIPI-CSI FFCs, TF-card slot, ES8311 codec + NS4150B PA with on-board mic, USB 2.0 OTG HS, and an RTC backup-battery connector). The espp::Esp32P4ModuleDevKit singleton mirrors the esp32-p4-eth / esp32-p4-nano BSPs (the board shares their pinout: IP101GRI RMII on the same EMAC pins, ES8311 on I2S 9-13 with PA on GPIO53, I2C on 7/8, 4-bit SDMMC on 39-44, 2-lane DSI with Kconfig-selected panel + polled GT911 touch, 2-lane CSI camera via esp_video). Pinout verified against the Waveshare schematic and Waveshare's official example repo. WiFi/BT (ESP32-C6 over SDIO, esp_hosted default pins) is documented but intentionally not managed by the BSP, matching the other P4 BSPs. Includes the full example (LVGL Gui class with Status/Audio/Camera tabs), docs (dev_boards rst + Doxyfile), and CI wiring (build matrix + component upload list). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds a new BSP component for the Waveshare ESP32-P4-Module-DEV-KIT, along with documentation, an LVGL-based example app, and CI wiring to build/publish the new component.
Changes:
- Introduce
components/esp32-p4-module-dev-kitBSP with display/touch/camera/audio/SD/Ethernet bring-up. - Add a full-featured example (LVGL tab UI + camera feed + audio record/playback) with board-specific sdkconfig/partitions.
- Publish docs + Doxygen integration and update GitHub Actions build/upload matrices.
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| doc/en/dev_boards/waveshare/index.rst | Adds the new board page to the Waveshare docs toctree. |
| doc/en/dev_boards/waveshare/esp32_p4_module_dev_kit.rst | New board documentation page describing peripherals and usage. |
| doc/en/dev_boards/waveshare/esp32_p4_module_dev_kit_example.md | Includes the example README into the docs site. |
| doc/Doxyfile | Adds the new BSP header and example to Doxygen inputs/examples. |
| components/esp32-p4-module-dev-kit/include/esp32-p4-module-dev-kit.hpp | Public BSP API + pin mappings + configuration docs. |
| components/esp32-p4-module-dev-kit/src/esp32-p4-module-dev-kit.cpp | Ethernet initialization implementation. |
| components/esp32-p4-module-dev-kit/src/video.cpp | MIPI-DSI LCD init, LVGL flush/rotation, and brightness/backlight I2C handling. |
| components/esp32-p4-module-dev-kit/src/touchpad.cpp | GT911 touch init (polled or interrupt) + coordinate conversion. |
| components/esp32-p4-module-dev-kit/src/camera.cpp | esp_video/V4L2 camera bring-up + capture task + teardown. |
| components/esp32-p4-module-dev-kit/src/audio.cpp | ES8311 + I2S playback and microphone capture tasks. |
| components/esp32-p4-module-dev-kit/src/sdcard.cpp | SDMMC mount + on-chip LDO power control + capacity/free query. |
| components/esp32-p4-module-dev-kit/CMakeLists.txt | Registers the BSP component and its required dependencies. |
| components/esp32-p4-module-dev-kit/Kconfig.projbuild | Adds Kconfig options for panel selection and touch task/interrupt settings. |
| components/esp32-p4-module-dev-kit/README.md | BSP overview and supported-peripheral matrix. |
| components/esp32-p4-module-dev-kit/idf_component.yml | Component Manager manifest with deps/version constraints. |
| components/esp32-p4-module-dev-kit/example/CMakeLists.txt | Example project wiring + explicit component manager enable + component dirs. |
| components/esp32-p4-module-dev-kit/example/sdkconfig.defaults | Enables PSRAM, camera pipeline, partition sizing, and target settings. |
| components/esp32-p4-module-dev-kit/example/partitions.csv | Custom 16MB flash partition table for the example. |
| components/esp32-p4-module-dev-kit/example/README.md | Example documentation (GUI + subsystems + build steps). |
| components/esp32-p4-module-dev-kit/example/main/CMakeLists.txt | Embeds click.wav and registers example sources. |
| components/esp32-p4-module-dev-kit/example/main/idf_component.yml | Example-only override to use in-repo espp/ethernet. |
| components/esp32-p4-module-dev-kit/example/main/gui.hpp | LVGL GUI class interface and threading model. |
| components/esp32-p4-module-dev-kit/example/main/gui.cpp | Implements the LVGL UI, camera canvas, and touch trail overlay. |
| components/esp32-p4-module-dev-kit/example/main/esp32_p4_module_dev_kit_example.cpp | Example app integrating display/touch/audio/mic/camera/SD/Ethernet. |
| .github/workflows/build.yml | Adds the new example to the CI build matrix. |
| .github/workflows/upload_components.yml | Adds the new component to the publish workflow list. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
✅Static analysis result - no issues found! ✅ |
- video: make the DPI trans-done callback ISR-safe (cache a raw lv_display_t* and call lv_display_flush_ready() directly; no shared_ptr access) since the JD9365/DMA2D path invokes it from the DMA2D ISR - video: propagate JD9365 panel power/backlight I2C write failures and abort LCD init (the sequence must complete before any DSI traffic) - audio: derive the ES8311 init-time samples enum from the requested sample rate and reject unsupported rates instead of hard-coding 48k - audio/camera: make initialize_microphone()/initialize_camera() idempotent (warn + return true), matching the other initialize_* methods - audio/camera: silence unused cv parameters in task callbacks - example: cache the WAV sample rate so load_audio() also reports it on the early-return (already-loaded) path Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dback - camera: tear down the capture pipeline (STREAMOFF/munmap/close/ esp_video_deinit) on fatal capture errors via a new idempotent teardown_camera_pipeline() helper, called from the camera task itself (no self-join) and from stop_camera(), so the driver is not left wedged and initialize_camera() can be called again to recover - example gui: bound the pending touch-point queue (drop-oldest at a small cap, rate-limited warn on drops) so it cannot grow without limit if the GUI task stalls - README: list the default/bundled JD9365 panel in the supported- peripherals table, consistent with the Kconfig panel choices - touchpad: capitalize the update-failure log message for consistency - sdcard: store sd_pwr_ctrl_handle_ as the concrete sd_pwr_ctrl_handle_t (with <sd_pwr_ctrl.h>) instead of void*, with an ownership comment, matching the esp32-p4-wifi6-dev-kit BSP Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 27 changed files in this pull request and generated 6 comments.
Suppressed comments (4)
Previously missed (3) — in code that hasn't changed since the last review.
doc/Doxyfile:112
- Doxyfile example entries must remain alphabetical.
esp32-p4-function-ev-boardsorts beforeesp32-p4-module-dev-kit, so swap these entries.
This issue also appears on line 285 of the same file.
$(PROJECT_PATH)/components/esp32-p4-module-dev-kit/example/main/esp32_p4_module_dev_kit_example.cpp \
components/esp32-p4-module-dev-kit/example/main/esp32_p4_module_dev_kit_example.cpp:355
- The embedded
click.wavis 16-bit stereo at 44.1 kHz, but this loader forwards its interleaved samples unchanged toplay_audio(), whose contract and I2S slot are 16-bit mono. The left/right words will be played sequentially as mono samples, doubling the clip duration and altering its waveform. Either replace the asset with a mono WAV or parse the channel count and downmix/deinterleave before storingaudio_bytes.
uint32_t sample_rate = 0;
std::memcpy(&sample_rate, &audio_bytes[24], sizeof(sample_rate));
components/esp32-p4-module-dev-kit/src/touchpad.cpp:91
- The polling task's start result is ignored, so an allocation/configuration failure still makes
initialize_touch()report success even though no touch updates will occur. Becausetouch_driver_remains set, later retries also return success without starting a task. Check the result and clear the partially initialized polling state on failure.
touch_task_->start();
doc/Doxyfile:285
- Doxyfile input entries must remain alphabetical.
esp32-p4-function-ev-boardsorts beforeesp32-p4-module-dev-kit, so swap these entries.
$(PROJECT_PATH)/components/esp32-p4-module-dev-kit/include/esp32-p4-module-dev-kit.hpp \
…back - video: gate the legacy (IDF < 6) use_dma2d flag on the JD9365 controller, matching the IDF 6 path (DMA2D corrupts RGB565 on ILI9881C / EK79007) - manifest: raise the minimum ESP-IDF to 5.4 (the BSP uses the new I2C master API via I2c::native_bus_handle() for the camera SCCB) - camera: make camera_width_/camera_height_ std::atomic<uint16_t> (read from other tasks while the camera task can reset them in fatal teardown) - camera: publish camera_initialized_ BEFORE Task::start() so an immediate fatal teardown's flag-clear cannot be overwritten - touchpad: use the three-arg Task callback with the notified flag as the cv wait predicate so spurious wakeups don't kill the polling task; also check the poll task's start() result and unwind on failure - example gui: same predicate-wait fix for the LVGL update task - example: downmix the embedded stereo click.wav to mono for play_audio()'s 16-bit mono contract (and reject non-16-bit / >2-channel data) - doc: restore alphabetical order of the P4 Doxyfile entries Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed the 4 suppressed review comments in c821969:
|
…dback - video: query rotation from the display being flushed (flush callback arg) and from the BSP-managed LVGL display in the rotated-geometry helpers, instead of lv_display_get_default() - audio: extend the init failure teardown to stop the codec, drop the ES8311 register-access functions and I2C device handle, and reset all audio state so a retry starts clean; cover the i2s_new_channel() failure path too - idf_component.yml: use https:// instead of git:// for the repository URL - Kconfig/docs: correct the panel facts per Waveshare's DSI-TOUCH lineup (5" = HX8394 720x1280, 7" = ILI9881C 720x1280, 8"/10.1" = JD9365 800x1280); clarify that the BSP's ILI9881C option is the 800x1280 (10.1") panel, not Waveshare's 7" 720x1280 ILI9881C Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 27 changed files in this pull request and generated 3 comments.
Suppressed comments (6)
Previously missed (4) — in code that hasn't changed since the last review.
components/esp32-p4-module-dev-kit/include/esp32-p4-module-dev-kit.hpp:612
- Multiplying before dividing can produce a partial frame for supported rates: 8 kHz and 32 kHz yield 533 and 2133 bytes.
audio_task_callback()then writes that full odd-sized buffer despite requiring 16-bit alignment, shifting sample framing on successive transfers. Divide the sample count first so the result is always a whole stereo frame (the same correction is documented incomponents/m5stack-cardputer/include/m5stack-cardputer.hpp:830-836).
static constexpr int calc_audio_buffer_size(int sample_rate) {
return sample_rate * NUM_CHANNELS * NUM_BYTES_PER_CHANNEL / UPDATE_FREQUENCY;
components/esp32-p4-module-dev-kit/example/main/gui.hpp:106
- The GUI task can read/invoke
record_callback_fromon_clicked()whileapp_mainassigns it here after the task has already started. A user pressing Record during peripheral bring-up therefore races on thestd::function. The event reader already runs undermutex_; acquire the same mutex in this setter.
This issue also appears on line 110 of the same file.
void set_record_callback(audio_button_callback_t callback) {
record_callback_ = std::move(callback);
}
components/esp32-p4-module-dev-kit/include/esp32-p4-module-dev-kit.hpp:704
- This member stays
UNKNOWNuntilinitialize_lcd()callsapply_panel_params(), so the public “configured panel” getters returnUnknownbefore LCD initialization; the new example does exactly that atesp32_p4_module_dev_kit_example.cpp:56. Initialize it fromdefault_controller_so the Kconfig selection is observable immediately.
DisplayController display_controller_{DisplayController::UNKNOWN};
components/esp32-p4-module-dev-kit/src/touchpad.cpp:165
- This conversion uses whichever LVGL display is globally default, rather than the BSP display whose dimensions are used by the formulas. In a multi-display app this applies the wrong rotation, and before any default display exists it may pass null to LVGL. Load
lvgl_display_as the rotation helpers do and fall back to rotation 0 until initialization.
auto rotation = lv_display_get_rotation(lv_display_get_default());
components/esp32-p4-module-dev-kit/src/audio.cpp:305
- After standard mode has been initialized, an RX enable failure returns without undoing that initialization. A later
initialize_microphone()retry callsi2s_channel_init_std_mode()again on an already-initialized channel and cannot recover; the task-start failure below leaves the same partial state plus the codec ADC started. Add a shared microphone-init cleanup path that disables/deinitializes or recreates RX, restores codec decode-only state, clears the callback/buffer, and use it for both failures.
if (i2s_channel_enable(audio_rx_handle) != ESP_OK) {
logger_.error("Failed to enable I2S RX channel");
return false;
components/esp32-p4-module-dev-kit/example/main/gui.hpp:110
- Like the record setter, this can modify
play_callback_concurrently with the running GUI task reading it fromon_clicked(). Guard the assignment withmutex_to avoid a data race during startup.
void set_play_callback(audio_button_callback_t callback) { play_callback_ = std::move(callback); }
…back - camera: make stop_camera() safe to call from the frame callback (deferred in-task stop; the capture task tears down the pipeline and exits itself instead of destroying a still-joinable thread), and reap a leftover exited task in initialize_camera() - audio: divide-first in calc_audio_buffer_size() so 8/32 kHz rates yield whole stereo frames instead of partial-frame buffer sizes - audio: consolidated microphone-init failure cleanup (disable RX, restore codec decode-only state, drop task/callback/buffer) so a retry can succeed; RX std-mode init is tracked and reconfigured rather than re-initialized - display: initialize display_controller_ from the Kconfig selection so the panel getters are meaningful before initialize_lcd() - touchpad: use the BSP's own LVGL display (not the global default) for the rotation mapping, falling back to rotation 0 before display init - example: load the click clip before the touch task starts (unsynchronized vector access), serialize all play_audio()/clear_audio() producers behind a mutex (single-writer stream buffer), and guard the Gui callback setters with the GUI mutex Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed all 6 suppressed review comments in 7e43336 (all were valid; none rebutted):
Example builds clean ( |
…back - initialize_sdcard(): warn and return true when already initialized, matching the idempotent initialize_* convention - audio_task_callback(): capture i2s_channel_write() result into a local bytes_written and log (rate-limited) on error/partial write, mirroring the esp32-p4-wifi6-dev-kit playback path Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…edback - audio: cache volume()/mute() before initialize_audio() and apply the cached state on init instead of calling into the ES8311 driver with no I2C hooks installed (matches the wifi6-dev-kit BSP pattern) - video: bound the rotation scratch buffer by what lv_draw_sw_rotate() actually writes (alignment-padded destination stride x destination height) instead of ww*hh pixels; track capacity in bytes and allocate with stride-padding headroom - video: drop misleading IRAM_ATTR from the task-context flush() callback - example: embed click.wav with EMBED_FILES (binary asset; no appended NUL) instead of EMBED_TXTFILES - example: build as C++23, set before project.cmake so it applies to all components Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| static constexpr int NUM_CHANNELS = 2; | ||
| static constexpr int NUM_BYTES_PER_CHANNEL = 2; | ||
| static constexpr int UPDATE_FREQUENCY = 60; | ||
| static constexpr int calc_audio_buffer_size(int sample_rate) { | ||
| // NOTE: divide the rate by the update frequency FIRST so the result is | ||
| // always a whole number of frames. Multiplying first yields a partial | ||
| // frame for rates that are not a multiple of the update frequency (e.g. | ||
| // 8 kHz -> 533 bytes), and reading/writing partial samples shifts the | ||
| // I2S sample framing on every transfer - heard as loud static. | ||
| return (sample_rate / UPDATE_FREQUENCY) * NUM_CHANNELS * NUM_BYTES_PER_CHANNEL; | ||
| } |
Summary
New BSP component
esp32-p4-module-dev-kit(espp::Esp32P4ModuleDevKit) for the Waveshare ESP32-P4-Module-Dev-Kit (ESP32-P4-Module with on-module ESP32-C6, on a carrier with Ethernet/DSI/CSI/audio/SD), with example, docs, and CI wiring.Schematic-verified finding: the carrier is pin-for-pin identical to the existing esp32-p4-nano / esp32-p4-eth BSPs, so this BSP mirrors that proven code:
espp::Ethernet, DHCP client/server).doc/en/dev_boards/waveshare/, Doxyfile entries,build.ymlmatrix +upload_components.ymlentries.Panel facts (per Waveshare's DSI-TOUCH lineup for its ESP32-P4 boards): 5" = HX8394 720x1280, 7" = ILI9881C 720x1280, 8" and 10.1" = JD9365 800x1280. The BSP's ILI9881C option is the 800x1280 (10.1"-class, Function-EV-Board style) panel shared with the p4-nano/p4-eth BSPs — Waveshare's 5" (HX8394), 7" (720x1280 ILI9881C), and 8" (different JD9365 init) panels are not supported, as noted in the Kconfig help.
Needs hardware verification
DSI panel timings and I2C backlight controller (inherited from p4-nano), live Ethernet bring-up.
Testing
idf.py set-target esp32p4 && idf.py buildclean (~1.5 MB app, 64% slot free).🤖 Generated with Claude Code