Skip to content

feat(m5stack-tab5): add MIPI-CSI camera support and a live Camera tab#676

Merged
finger563 merged 10 commits into
mainfrom
feat/tab5-camera
Jul 26, 2026
Merged

feat(m5stack-tab5): add MIPI-CSI camera support and a live Camera tab#676
finger563 merged 10 commits into
mainfrom
feat/tab5-camera

Conversation

@finger563

Copy link
Copy Markdown
Contributor

Description

Adds support for the M5Stack Tab5's on-board MIPI-CSI camera (SC202CS) and a
live Camera tab in the example, with a framed/draggable feed and a small
settings overlay.

BSP (espp::M5StackTab5):

  • initialize_camera(callback) brings up the ESP32-P4 camera pipeline (CSI
    receiver → ISP → sensor) via Espressif's esp_video (V4L2) framework: resets
    the sensor (IO expander 0x43 P6), esp_video_init() with the sensor SCCB
    sharing the internal I2C bus, then the V4L2 capture flow (/dev/video0,
    S_FMT RGB565, REQBUFS/mmap, QBUF, STREAMON) and a capture task that
    DQBUFs each frame to the callback and requeues it. Plus stop_camera(),
    camera_width/height(), camera_reset().
  • Each frame is processed by the PPA in one hardware pass: downscale
    (Full / Half / Quarter), rotate to match the current display orientation
    (display rotation + a fixed 90° sensor-mount offset), and mirror / flip on
    request.
  • CameraControls {scale, hmirror, vflip} + set_camera_controls() /
    camera_controls(). The GUI (any thread) posts the desired state under a
    mutex; the capture task applies it, so the PPA scale, mirror/flip and the
    preview-buffer reallocation all happen on one thread.
  • espp::I2c gains a native_bus_handle() accessor so the camera SCCB can
    reuse the internal I2C bus.

Example:

  • A Camera tab showing the live feed in a framed, draggable widget (an
    lv_canvas bound to a PSRAM RGB565 buffer, with a border + rounded corners;
    it can be dragged around the tab, and pans when larger than the tab), plus a
    collapsible gear-button settings overlay (image size + mirror/flip).
  • Pulls in the managed espressif/esp_video + espressif/esp_cam_sensor
    components (the ISP converts the sensor RAW8 → RGB565) and enables the IDF
    component manager. The ISP pipeline controller (auto exposure / white balance)
    is enabled so the feed is correctly white-balanced.

Exposure / white balance / color are left to the ISP auto pipeline (it drives
them every frame), so no manual image-quality controls are exposed.

Motivation and Context

The Tab5 BSP already anticipated the camera (pin defines, a "not yet
implemented" note) but had no camera code. This implements it end-to-end so the
BSP can stream frames and the example can display them, matching the other Tab5
peripherals.

Notes on things worked through on hardware, handled and documented in-code:

  • The camera SCCB shares the internal I2C bus with the touch controller. The
    ISP's auto-exposure writes the sensor over SCCB every frame; the SCCB is
    clocked at 400 kHz so those writes don't starve the (short, fail-fast)
    touch reads.
  • Two benign upstream ISP quirks with the stock SC202CS IPA config (the ISP
    rejects the bad value and keeps the previous one, so the image is unaffected):
    the auto color-correction occasionally exceeds the ISP's ±4.0 CCM limit —
    the per-frame error logging is silenced (which also fixes the frame drops it
    caused); and an occasional ISP: gamma xcoord error from the auto-enhancement
    gamma, which is an ISR early-log that can't be runtime-silenced and is left
    as-is.

How has this been tested?

  • Built the m5stack-tab5 example with ESP-IDF for the ESP32-P4 — clean build,
    no warnings.
  • Flashed to M5Stack Tab5 hardware:
    • Live feed appears on the Camera tab; sensor probes as SC202CS over SCCB.
    • Correct colors (ISP auto white balance) and no CCM error spam / frame drops.
    • Feed stays upright through all four display rotations.
    • Settings overlay expands/collapses; image size switches resolution live;
      mirror / flip work (via the PPA).
    • The feed widget shows its border/frame and can be dragged around the tab.
    • Touch responsiveness improved after raising the SCCB clock to 400 kHz.

Screenshots (if appropriate, e.g. schematic, board, console logs, lab pictures):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update
  • Hardware (schematic, board, system design) change
  • 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.
  • I have updated the .github/workflows/build.yml file to add my new test to the automated cloud build
  • All new and existing tests passed.
  • My code follows the code style of this project.

Bring up the Tab5's on-board camera (SC202CS on MIPI-CSI) via Espressif's
esp_video (V4L2) pipeline and show the live feed, with settings, on a new
Camera tab.

BSP:
- initialize_camera(callback): reset the sensor (IO expander 0x43 P6),
  esp_video_init() with the sensor SCCB sharing the internal I2C bus (at
  400 kHz, so the per-frame auto-exposure sensor writes don't starve the
  shared-bus touch reads), then the V4L2 flow (open /dev/video0, S_FMT RGB565,
  REQBUFS/mmap, QBUF, STREAMON) and a capture task that DQBUFs frames to the
  callback and requeues them. stop_camera(), camera_width/height(),
  camera_reset(). Pulls in managed espressif/esp_video + esp_cam_sensor (the
  ISP converts the sensor RAW8 to RGB565).
- Each frame is run through the PPA in one hardware pass: downscaled (Full /
  Half / Quarter), rotated to match the display orientation (display rotation +
  a fixed 90-deg mount offset), and mirrored / flipped on request.
- CameraControls {scale, hmirror, vflip} + set_camera_controls() /
  camera_controls(): the GUI (any thread) posts the desired state under a mutex
  and the capture task applies it, so the PPA scale, mirror/flip and the
  preview-buffer realloc all happen on one thread. (Exposure / white balance /
  color are left to the ISP auto pipeline; they are driven every frame and are
  not usefully overridable, so no manual controls are exposed.)

espp::I2c: expose the native i2c_master_bus_handle_t so the camera SCCB can
reuse the internal bus.

Example: a Camera tab showing the live feed in a framed, draggable widget (an
lv_canvas bound to a PSRAM RGB565 buffer, with a border and rounded corners,
draggable within the tab), plus a collapsible gear-button settings overlay
(image size + mirror/flip). Enables the IDF component manager for the managed
camera components. The ISP pipeline controller (auto exposure / white balance)
is enabled so the feed is correctly white-balanced.

Two benign upstream quirks, both handled/documented (the ISP rejects the bad
value and keeps the previous one, so the image is unaffected): the stock
SC202CS auto color-correction occasionally exceeds the ISP's +/-4.0 CCM limit
(the per-frame error logging is silenced, which also fixes frame drops), and
occasional 'ISP: gamma xcoord error' from the auto-enhancement gamma (an ISR
early-log that can't be runtime-silenced; left as-is). See camera.cpp and the
example README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 24, 2026 18:54
@finger563 finger563 self-assigned this Jul 24, 2026
@github-actions

github-actions Bot commented Jul 24, 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

Adds end-to-end MIPI-CSI camera support for the M5Stack Tab5 BSP (SC202CS via esp_video/V4L2 + ISP) and extends the example with a live “Camera” tab UI, while also enabling camera-related managed components/config and exposing a native I2C bus handle for SCCB sharing.

Changes:

  • Implemented camera bring-up, V4L2 streaming, and PPA-based downscale/rotate/mirror/flip processing in the Tab5 BSP.
  • Added a live Camera tab to the example (LVGL canvas feed, draggable framed widget, and a settings overlay for scale/mirror/flip).
  • Enabled/declared required dependencies and config (managed esp_video/esp_cam_sensor, sdkconfig defaults) and added I2C native bus handle accessors.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
components/m5stack-tab5/src/camera.cpp New camera pipeline implementation: esp_video init + V4L2 capture + PPA processing + control application + cleanup.
components/m5stack-tab5/README.md Updates BSP documentation to reflect SC202CS camera support and usage.
components/m5stack-tab5/include/m5stack-tab5.hpp Adds camera API, callback type, controls, and camera-related members.
components/m5stack-tab5/idf_component.yml Declares espressif/esp_video and espressif/esp_cam_sensor dependencies for the BSP.
components/m5stack-tab5/example/sdkconfig.defaults Enables esp_video MIPI-CSI device + SC202CS sensor + ISP pipeline controller.
components/m5stack-tab5/example/README.md Documents the new Camera tab behavior and known upstream ISP quirks.
components/m5stack-tab5/example/main/m5stack_tab5_example.cpp Initializes camera and forwards frames into the GUI.
components/m5stack-tab5/example/main/idf_component.yml Enables managed-component fetching for esp_video + esp_cam_sensor in the example.
components/m5stack-tab5/example/main/gui.hpp Adds camera tab initialization and camera frame/control UI plumbing declarations.
components/m5stack-tab5/example/main/gui.cpp Implements Camera tab UI (canvas, drag, overlay controls) and frame copy into PSRAM buffer.
components/m5stack-tab5/example/CMakeLists.txt Adjusts example build to use component manager flow and includes esp_video in components list.
components/m5stack-tab5/CMakeLists.txt Adds esp_video to the BSP component requirements.
components/i2c/include/i2c.hpp Exposes I2c::native_bus_handle() for sharing the internal I2C bus with esp_video SCCB.
components/i2c/include/i2c_master.hpp Exposes I2cMasterBus::native_handle() used by the higher-level I2c accessor.
Comments suppressed due to low confidence (1)

components/m5stack-tab5/src/camera.cpp:283

  • apply_camera_controls() ignores allocate_camera_preview_buffer() failures and clears camera_controls_dirty_. If allocation fails (e.g., PSRAM OOM), the requested scale change is silently dropped and will never be retried.
  // Scale: reallocate the preview buffer if the requested scale changed.
  if (c.scale != camera_active_scale_) {
    allocate_camera_preview_buffer(c.scale);
  }

Comment thread components/m5stack-tab5/src/camera.cpp
Comment thread components/m5stack-tab5/src/camera.cpp Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 25, 2026 01:21
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

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 14 out of 14 changed files in this pull request and generated 4 comments.

Comment thread components/m5stack-tab5/src/camera.cpp
Comment thread components/m5stack-tab5/src/camera.cpp Outdated
Comment thread components/m5stack-tab5/src/camera.cpp
Comment thread components/m5stack-tab5/example/main/gui.cpp
Remaining review findings on top of the Copilot autofix:
- initialize_camera() set camera_initialized_ before the capture task started;
  if start() failed the object stayed 'initialized' and could not be retried.
  Start first, tear down and return false on failure, mark initialized only on
  success.
- If open(/dev/video0) (or a later V4L2 step) failed after esp_video_init()
  succeeded, the esp_video pipeline was left initialized, leaking it and
  blocking a later retry. Track esp_video init state and esp_video_deinit() it
  in stop_camera(); route the open-failure path through stop_camera().
- apply_camera_controls() ignored allocate_camera_preview_buffer() failure and
  cleared the dirty flag, silently dropping a scale change on OOM. Log the
  failure (the current buffer is kept, scale not marked applied).
- The example's Gui allocated a persistent PSRAM canvas buffer but never freed
  it; deinit_ui() (called from ~Gui) only cleaned the LVGL tree. Free the
  buffer there so recreating the Gui does not leak PSRAM.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 20:15

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 14 out of 14 changed files in this pull request and generated 3 comments.

Comment thread components/m5stack-tab5/src/camera.cpp Outdated
Comment thread components/m5stack-tab5/example/main/gui.cpp
Comment thread components/m5stack-tab5/src/camera.cpp Outdated
The camera managed components come in transitively via the m5stack-tab5 BSP,
so the example does not need to re-declare them:
- Remove the redundant example/main/idf_component.yml (esp_video +
  esp_cam_sensor were already declared by the BSP's idf_component.yml and are
  fetched transitively).
- Drop esp_video from the example's COMPONENTS list (m5stack-tab5 REQUIRES it,
  and esp_video REQUIRES esp_cam_sensor, so both are already in the build
  closure).
- Add esp_cam_sensor to the BSP's CMakeLists REQUIRES so it matches the BSP
  manifest and the closure gets both camera components directly from the BSP.

Verified with a clean build: both managed components are still fetched and the
example builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 20:27
finger563 and others added 2 commits July 25, 2026 15:28
- The ISP log-tag silencing (esp_log_level_set to NONE) was never undone, so a
  failed initialize_camera() or a later stop_camera() left those tags muted for
  the rest of the app. Cache the prior levels (esp_log_level_get) before muting
  and restore them in stop_camera().
- set_camera_frame() deleted the canvas and freed the PSRAM buffer before
  confirming the new allocation, blanking the tab on OOM. Allocate the new
  buffer first; on failure keep the current canvas / preview.
- initialize_camera() ignored camera_reset()'s return value, so it would
  continue (and let esp_video probe a possibly-held-in-reset sensor) even if the
  IO-expander write failed. Treat a reset failure as an init error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

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 13 out of 13 changed files in this pull request and generated 5 comments.

Comment thread components/m5stack-tab5/src/camera.cpp
Comment thread components/m5stack-tab5/src/camera.cpp Outdated
Comment thread components/m5stack-tab5/src/camera.cpp Outdated
Comment thread components/m5stack-tab5/src/camera.cpp
Copilot AI review requested due to automatic review settings July 25, 2026 20:43

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 13 out of 13 changed files in this pull request and generated 5 comments.

Comment thread components/m5stack-tab5/src/camera.cpp
Comment thread components/m5stack-tab5/src/camera.cpp Outdated
Comment thread components/m5stack-tab5/src/camera.cpp Outdated
Comment thread components/m5stack-tab5/src/camera.cpp
Comment thread components/m5stack-tab5/src/camera.cpp Outdated
…ding)

- The capture task treated every VIDIOC_DQBUF failure as 'no frame yet' and
  ignored VIDIOC_QBUF's result. Now only EAGAIN sleeps/retries; any other DQBUF
  error, or a QBUF failure (which would drain the queue and stall), is logged
  and stops the task rather than spinning silently.
- The camera task read the display rotation via LVGL (lv_display_get_rotation)
  from its own thread, which is not thread-safe. The display flush already reads
  the rotation on the GUI thread, so cache it there into an atomic and have the
  camera task read that instead.
- stop_camera() restored the muted ISP log tags after esp_video_deinit(), hiding
  any teardown diagnostics; restore them before deinit.
- apply_camera_controls() cleared the dirty flag before applying the scale, so
  an OOM scale change was dropped, never retried. Apply mirror/flip (which
  cannot fail) first, and on a failed scale realloc re-queue the request (with a
  rate-limited warning) so it retries when memory frees.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 21:12

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 14 out of 14 changed files in this pull request and generated 6 comments.

Comment thread components/m5stack-tab5/src/camera.cpp Outdated
Comment thread components/m5stack-tab5/src/camera.cpp
Comment thread components/m5stack-tab5/src/camera.cpp
Comment thread components/m5stack-tab5/example/main/gui.cpp
Comment thread components/m5stack-tab5/example/README.md Outdated
Comment thread components/m5stack-tab5/example/CMakeLists.txt
…, docs)

- VIDIOC_REQBUFS may allocate fewer buffers than requested; use the count it
  actually returns (require >=1, capped to the array) and iterate that instead
  of assuming CAMERA_BUFFER_COUNT.
- Include errno / strerror in the camera bring-up failure logs (open, QUERYCAP,
  S_FMT, REQBUFS, QUERYBUF, mmap, QBUF, STREAMON) for easier field debugging.
- stop_camera() now resets camera_width_/height_ and the preview dims to 0 so
  camera_width()/height() honor their documented '0 when not initialized'
  contract after a stop or failure.
- Gui::deinit_ui() only nulled camera_canvas_; lv_obj_clean() deletes the whole
  tree, so a camera frame arriving during teardown could build objects under a
  freed camera_tab_ (use-after-free). Add a ui_ready_ flag (checked in
  set_camera_frame) and null all the tab/overlay pointers on teardown.
- Enable the IDF component manager explicitly in the example CMakeLists (it is
  on by default, but the env could disable it), and fix the example README to
  point at the BSP's idf_component.yml where the camera deps are now declared.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 23:04

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 14 out of 14 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

components/m5stack-tab5/src/camera.cpp:87

  • This comment claims the shared internal I2C bus runs other devices at 1 MHz, but the Tab5 BSP configures its internal_i2c_ clock at 100 kHz (and device configs use that value). That mismatch is confusing when debugging bus contention issues.
  // bus-hold time. (The bus itself runs the other devices at 1 MHz; the I2C
  // master reprograms the clock per transaction.)

Comment thread components/m5stack-tab5/src/camera.cpp
Comment thread components/m5stack-tab5/example/main/gui.hpp
@finger563
finger563 merged commit 3ca9d1b into main Jul 26, 2026
131 of 132 checks passed
@finger563
finger563 deleted the feat/tab5-camera branch July 26, 2026 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants