Skip to content

Waveshare ESP32-S3-Touch-LCD-3.5: camera connector support (OV5640/OV2640) - #285

Merged
ThomasFarstrike merged 8 commits into
MicroPythonOS:mainfrom
bitcoin3us:feat/waveshare-3.5-camera
Sep 3, 2026
Merged

ThomasFarstrike merged 8 commits into
MicroPythonOS:mainfrom
bitcoin3us:feat/waveshare-3.5-camera

Conversation

@bitcoin3us

Copy link
Copy Markdown
Contributor

Stacked on #280 (board support, incl. the new st7796 INVON commit) and #281 (ES8311 audio) — only the last commit is new here. Depends on MicroPythonOS/micropython-camera-API#1 for the sccb_i2c_port parameter.

Adds camera support for the 3.5's DVP connector, pins from Waveshare's 03_camera_web_server demo. Three hardware-debugging findings are baked in:

  1. Shared SCCB/I2C bus: the camera's SCCB control lines are the board's main I2C pins (sda=8/scl=7), shared with touch/codec/expander. The camera must reuse the existing bus driver (sda_pin=-1, sccb_i2c_port=0) — with its own second driver on those pins, touch polling garbles SCCB traffic (corrupted sensor config, dead touch buttons during preview).
  2. rotation_degrees=-90: the sensor is mounted 90° CCW relative to the landscape UI (tuned with a real OV5640).
  3. No rgb565_byte_swap: the sensor's RGB565 output is native little-endian, matching LVGL's render format. The "wrong preview colors" seen during bring-up turned out to be the missing INVON in the st7796 driver (see Add board support: Waveshare ESP32-S3-Touch-LCD-3.5 #280) inverting the whole panel, not a camera byte-order issue.

Verified on hardware end to end: preview upright, natural colors, responsive buttons, QR scanning works. Also adds a desktop graphical regression test asserting RGB565 preview color fidelity through the LVGL image blit at 1:1 and scaled sizes (passes on desktop; guards the preview path used by all camera boards).

🤖 Generated with Claude Code

bitcoin3us and others added 8 commits August 29, 2026 21:37
3.5" 320x480 IPS sibling of the supported ESP32-S3-Touch-LCD-2:
ST7796 display over SPI, FT6336 capacitive touch, QMI8658 IMU,
PCF85063 RTC, AXP2101 power management, ES8311 codec, TF slot, and a
camera connector (no camera included).

The notable hardware difference from the LCD-2: the LCD reset and
chip-select lines sit behind a PCA9554 I2C IO expander (EXIO1/EXIO2)
instead of ESP32 GPIOs, so the board file pulses reset and parks CS low
via the expander before bringing up the SPI display bus (cs=-1; the
panel is the only SPI device).

- vendor the st7796 display driver from lvgl_micropython (same package
  glue as the existing st7789)
- add a PCA9554 8-bit IO expander driver alongside the existing TCA9555
- battery status via the AXP2101 PMU (t_watch pattern), not an ADC pin
- auto-detection: PCA9554@0x20 + FT6336@0x38 on sda=8/scl=7, probed last
  so existing boards are unaffected

Pin map cross-checked between the Waveshare wiki/schematic and two
independent community configurations. Not yet verified on hardware:
DMA buffer sizing, IMU mounted orientation, rotation, AXP2101 rail
defaults, and the EXIO pin roles rest on the schematic + one config.

esp32s3 build passes: image 3,618,496 bytes, 51,520 headroom.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wires the existing drivers/codec/es8311.py into the board: speaker
output and microphone input over I2S, with the codec controlled via a
small adapter around the shared lcd_bus I2C wrapper (the driver expects
machine.I2C-style writeto_mem/readfrom_mem_into).

I2S pins from Waveshare's own demo code (01_audio_out /
04_es8311_example): MCLK=12, BCLK=13, LRCK=15, playback data=16,
microphone data=14; codec at I2C 0x18 on the shared sda=8/scl=7 bus.
No speaker-amp enable pin exists in Waveshare's examples, so pop
suppression uses the codec's DAC soft-mute in the AudioManager
on_open/on_close hooks (freenove pattern, minus the amp pin).

DAC volume defaults to 76%, picked by ear on the physical board with a
speaker attached: the driver's 85% default audibly distorts, 70% is
clean but quiet, 76% is the loudest clean setting.

Verified on hardware: WAV playback through an attached speaker works.
Microphone input is registered but untested (no headset to monitor).
Known limitation observed while testing: chaining playbacks with short
(~400ms) gaps can silently wedge the I2S pipeline with
ESP_ERR_INVALID_ARG from stream_wav until reboot — an AudioManager/
stream_wav reopen race worth investigating separately, not specific to
this board.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without INVON the Waveshare ESP32-S3-Touch-LCD-3.5's IPS panel renders
the entire UI in negative. This went unnoticed at first because an
inverted UI reads as a plausible dark theme; camera preview photos
exposed it (inverted photos look like x-rays). Verified on hardware:
sending INVON live flips the panel to correct colors, and a rebuilt
firmware with this init renders launcher and camera preview correctly.

The st7789 driver (used by the ESP32-S3-Touch-LCD-2) already sends
INVON for the same reason; the vendored st7796 init was missing it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…2640)

Adds the camera section to the 3.5 board file, wired per Waveshare's
03_camera_web_server demo: DVP data [45,47,48,46,42,40,39,21], VSYNC=17,
HREF=18, PCLK=41, XCLK=38 @ 20MHz, PWDN/RESET not wired.

The SCCB control bus shares the board's main I2C pins (sda=8/scl=7) with
touch, codec and IO expander. The camera driver must reuse the existing
I2C bus driver (sda_pin=-1, scl_pin=-1, sccb_i2c_port=0) instead of
installing its own: with a second driver on the same pins, concurrent
FT6336 touch polling garbles SCCB traffic — corrupted sensor config and
dead touch buttons during preview. This needs the sccb_i2c_port
parameter from the companion micropython-camera-API change.

rotation_degrees=-90 tuned on hardware with an OV5640: the sensor is
mounted 90 degrees counter-clockwise relative to the landscape UI.

Verified on hardware end to end: preview upright with natural colors
(after the st7796 INVON fix), touch buttons responsive during preview.
Also adds a desktop graphical regression test asserting RGB565 preview
color fidelity through the LVGL image blit at 1:1 and scaled sizes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_270 instead of _90: with the USB cable to the left and the BOOT/RESET
buttons at the bottom — the board's natural desk orientation — the UI
was upside down. Hardware-verified: display upright, touch aligned.
Note the rotation only takes effect at display init; changing it at
runtime updates the driver state but not what the panel shows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With the display orientation changed from _90 to _270, the camera's
mounting offset flips sign: rotation_degrees goes from -90 to 90.
Hardware-verified with an OV5640: preview upright in the new
orientation, colors natural, touch buttons working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bitcoin3us

Copy link
Copy Markdown
Contributor Author

Updated for the orientation change in #280 (_90 → _270): the camera's mounting offset flips sign with it, so rotation_degrees goes from -90 to 90. Re-verified on hardware with the OV5640: preview upright in the new orientation, natural colors, buttons working.

@ThomasFarstrike
ThomasFarstrike merged commit e63810c into MicroPythonOS:main Sep 3, 2026
11 of 15 checks passed
@bitcoin3us

Copy link
Copy Markdown
Contributor Author

Heads-up now that this is merged: the board file passes sccb_i2c_port=0 to the camera constructor, but main still pins the micropython-camera-API submodule at f88b29d, which predates that parameter (it's in MicroPythonOS/micropython-camera-API#1, still open). Until #1 merges and the submodule pointer is bumped, upstream firmware builds will hit a TypeError inside init_cam's try/except on the 3.5 — no crash, the camera just won't initialize. Happy to send the submodule-bump PR the moment #1 lands.

@ThomasFarstrike

Copy link
Copy Markdown
Contributor

Thanks for the heads up!

I did the submodule-bump myself so should be good now!

@bitcoin3us

bitcoin3us commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Follow-up: the submodule bump got reverted by bc767c3 (stale submodule swept into an unrelated commit), so main and the 0.19.0 release pin f88b29d again and the 3.5's Camera app shows "No camera found" (sccb_i2c_port → TypeError). Re-pin PR: #304

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