User reported the ESP32-CAM + ILI9341 live preview at ~1 frame/min.
Profile: 80×60 preview pushes 9600 SPI bytes per drawRGBBitmap, and
each byte was emitting a full {type:'spi_event'} JSON message over
the worker→backend→WS→frontend pipeline. Per-byte overhead ~150-200µs
in Python (json.dumps + sys.stdout.write+flush dominates) plus
asyncio + WS dispatch. Net: 1.5-2 sec/frame minimum, much worse with
GIL contention.
Fix: buffer MOSI bytes in the worker and emit a single base64-encoded
`spi_batch` message when CS goes HIGH (transaction ended) or the
buffer crosses 4 KiB. ~9600 events/frame collapse to ~3 messages.
backend/app/services/esp32_worker.py:_on_spi_event
- Add _spi_byte_buf bytearray + threading.Lock
- On op==0x00 (byte): append; flush early if buf >= 4096
- On op==0x01 (CS change): flush buffer, then emit the CS event
via the legacy spi_event channel (ePaper / custom chips that
observe CS still get it).
frontend/src/simulation/Esp32Bridge.ts
- New 'spi_batch' message handler decodes b64 and replays each
byte through the existing onSpiByte callback. Parts that
subscribed via simulator.spi.onByte don't notice the protocol
change. The 'spi_event' branch still handles CS changes plus
legacy single-byte payloads for backwards compat.
Now that 38 KB/frame is cheap, restore preview to 160×120 + JPEG
quality 0.35 in the gallery example. Real measured speedup: ~50× on
the QVGA preview demo. Real hardware was never affected — it runs
SPI at 80 MHz and pushes the bitmap in ~4 ms either way.
PSRAM emulation is unrelated to this bottleneck and was left untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User reported the live preview "looks slow" after the JPEG decode fix.
Diagnosis: each tft.drawRGBBitmap pushes width × height × 2 bytes over
SPI, and every byte takes a full QEMU → worker → backend (WS) → frontend
round-trip. At 160×120 that's 38 400 messages per frame; the bus
saturates at ~0.2 fps perceived.
Two changes shrink the per-frame SPI bandwidth:
1. Preview 160×120 → 80×60 (and JPG_SCALE_2X → JPG_SCALE_4X).
38 400 bytes/frame → 9 600 bytes/frame. Already 4× faster.
2. Status bar redraw throttled to every 10th frame instead of every
frame. The text writes (printf, fillRect, fillCircle) account for
another ~1-2 KB of SPI traffic per loop iteration. Skipping 9 of
every 10 redraws frees up a chunk more bandwidth without losing
the headline numbers (fps, frame counter) — they just refresh
once a second instead of 5x/sec.
Also dropped the trailing `delay(20)` — we don't need an artificial
throttle, the SPI bus is the throttle.
Real-hardware effect: zero. ESP32 SPI runs at 80 MHz; a full
160×120 bitmap pushes in ~4 ms either way.
Applied in two places:
- examples/esp32-cam-lcd-preview/esp32-cam-lcd-preview.ino
- frontend/src/data/examples.ts (in-app gallery copy)
Long-term plan: batch SPI bytes at the worker level (one WS message
per N bytes instead of per byte) — that's a deeper change in
qemu-lcgamboa + Esp32Bridge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First end-user demo of the new ESP32-CAM emulation capability —
shows how to wire an ESP32-CAM to an ILI9341 320×240 SPI TFT,
decode JPEG frames from the emulated webcam with jpg2rgb565()
(built into esp32-camera/conversions, header now exposed by the
Velxio compile template), and render the resulting RGB565 bitmap
to the screen at ~10 fps.
Two sketches in the same example folder:
- esp32-cam-lcd-preview.ino — full demo: decode JPEG → render the
bitmap (160×120 centered in the TFT) + status bar with fps,
frame counter, decode-fail counter, live pulse dot.
- esp32-cam-lcd-status.ino — companion that doesn't decode the
JPEG; instead it shows a status dashboard (frame counter, byte
histogram, JPEG header hex dump). Useful when the source JPEG
exceeds the deliverable byte budget and jpg2rgb565 fails on the
truncation.
diagram.json wires the two parts using velxio-esp32-cam +
wokwi-ili9341 part types over VSPI:
ILI9341 ↔ ESP32-CAM
CS ↔ GPIO 15, RST ↔ GPIO 2, D/C ↔ GPIO 14,
MOSI ↔ GPIO 13, SCK ↔ GPIO 12
libraries.txt lists Adafruit GFX + Adafruit ILI9341. esp_camera.h
and img_converters.h ship with arduino-esp32 — no extra install.
Adds examples/README.md as the index for future demos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>