Skip to content

feat(esp32-p4-nano): Add display, touch, camera, uSD and audio support - #697

Open
finger563 wants to merge 5 commits into
mainfrom
feat/esp32-p4-nano-peripherals
Open

feat(esp32-p4-nano): Add display, touch, camera, uSD and audio support#697
finger563 wants to merge 5 commits into
mainfrom
feat/esp32-p4-nano-peripherals

Conversation

@finger563

Copy link
Copy Markdown
Contributor

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-board for display/touch/audio/SD, m5stack-tab5 for camera):

  • Display — MIPI-DSI (2-lane, LDO ch3), ILI9881C 10.1" (default) or EK79007 7" via Kconfig.
  • Touch — GT911 on the shared internal I2C bus (polled).
  • Camera — MIPI-CSI via esp_video/V4L2, OV5647 by default (Kconfig-selectable), SCCB on the shared I2C, reset/pwdn not routed (free-run).
  • uSD/TF — 4-bit SDMMC (CLK=43/CMD=44/D0–D3=39–42) on the on-chip LDO.
  • Audio — ES8311 codec + NS4150B amp: speaker out + full-duplex mic over I2S.
  • Shared internal I2C bus (SDA=7/SCL=8) for codec, touch, and camera SCCB.

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?

  • Builds for esp32p4 on ESP-IDF 6.0 (component + example); cppcheck clean.
  • Pinouts taken from Waveshare's authoritative BSP header. Hardware verification still pending (I can only compile) — flagged in-code: backlight (an unidentified I2C controller → brightness is a best-effort no-op, panel comes up backlight-on), camera reset/sensor (OV5647, reset=NC), and the panel default (ILI9881C, switchable to EK79007 via Kconfig).

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Documentation Update
  • Software change

Checklist:

  • My change requires a change to the documentation.
  • I have added / updated the documentation related to this change via either README or WIKI

Software

  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My code follows the code style of this project.

Notes

  • The board's "RTC" is the ESP32-P4 internal RTC (the connector is only a coin-cell backup), so no RTC driver is added.
  • The example enables the component manager (for esp_video/esp_cam_sensor) and overrides espp/ethernet to the in-repo component in the example's manifest only — the shipped BSP manifest stays clean (espp/ethernet is not yet published; upload ordering handles that at release).

🤖 Generated with Claude Code

finger563 and others added 2 commits August 5, 2026 19:04
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>
Copilot AI lite review requested due to automatic review settings August 6, 2026 00:27
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

✅Static analysis result - no issues found! ✅

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread components/esp32-p4-nano/src/video.cpp
Comment thread components/esp32-p4-nano/src/touchpad.cpp
Comment thread components/esp32-p4-nano/example/main/gui.cpp Outdated
Comment thread components/esp32-p4-nano/src/audio.cpp
Comment thread components/esp32-p4-nano/example/main/esp32_p4_nano_example.cpp
…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>
Copilot AI review requested due to automatic review settings August 6, 2026 01:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 false after 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 false after 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 false after 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_bytes is already populated, load_audio() returns true without setting out_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 alongside audio_bytes and always set both out_size and out_sample_rate on 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());

Comment thread components/esp32-p4-nano/src/video.cpp
Comment thread components/esp32-p4-nano/src/video.cpp
Comment thread components/esp32-p4-nano/src/camera.cpp
Comment thread components/esp32-p4-nano/src/audio.cpp
…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>
Copilot AI review requested due to automatic review settings August 6, 2026 02:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copilot AI review requested due to automatic review settings August 6, 2026 04:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants