When `psram_mode` is active, `cam_take()` used to copy only 32 bytes
from PSRAM and scan backward for the JPEG EOI (FF D9). Because DMA
copies in dma_node_buffer_size length, the true EOI often
falls earlier and was missed, leading to repeated "NO-EOI" resets.
This change:
- Drops the 32-byte SRAM copy; scan directly in PSRAM after cache
invalidation.
- Searches the last dma_node_buffer_size **plus 1 byte** (marker_len-1),
starting 1 byte before the final block to catch split EOIs.
- Scans **forward** so we pick the earliest EOI in the current tail and
avoid stale markers from a larger previous frame.
- Uses a fast byte-hunt for 0xFF with a 2-byte verification for "FF D9".
SRAM DMA mode keeps the simple backward check (we only accept EOI at
offset 0 there), so no extra complexity or perf work is added.
Net effect: robust EOI detection without garbage tails and fewer
spurious "NO-EOI" resets in PSRAM captures.
- Do this check only in psram_mode
- Improve code comment, to make clear that it will stop early on copy operations
- Increase fb size by one dma_half_buffer_size in psram_mode
Rationale:
In psram_mode we will be called after each memory copy operation is completed, by
the CAM_IN_SUC_EOF event. Since we cannot predict at this point, if the camera
will copy another dma_half_buffer_size into the fb, we need to abort here,
if there's not enough size in the fb for the next copy operation.
This assumption means, we will abort even if this is indeed the last
copy operation the camera did, and the next event will be a CAM_VSYNC,
notifying us, that the copy operation is completed.
Since we cannot predict this, we need one dma_half_buffer_size more
space in the fb in psram_mode to allow images of the same size in
psram_mode as in the regular mode.
Invalidate data-cache lines for the image captured via dma (in psram_mode)
before the buffer is handed off to the application. This ensures the the
CPU will read the correct data from the PSRAM instead of cached segments
from the previous image stored in this buffer.
Other work:
- The cache invalidation was refactored into `cam_drop_psram_cache()`
Performance consideration:
On an ESP32-S3 @ 240 MHz with 32-byte cache lines:
|------------------------------------------------|
| Image size | Lines flushed | Cycles | Time |
|------------|---------------|--------|----------|
| 100 KiB | 3 200 | 16 000 | 66.7 µs |
| 300 KiB | 9 600 | 48 000 | 200 µs |
|________________________________________________|
- Added a configurable probe length for PSRAM JPEG validation via CAM_EOI_PROBE_BYTES
- For PSRAM mode, CAM_EOI_PROBE_BYTES is copied from the first DMA block into a
stack buffer and validated by cam_verify_jpeg_eoi() to verify that it's a valid
jpeg image.
- Reading from PSRAM directly with cam_verify_jpeg_eoi() is here avoided due to
latency of small operations done by cam_verify_jpeg_eoi().
- Added a configurable probe length for PSRAM JPEG validation via CAM_SOI_PROBE_BYTES
- For PSRAM mode, CAM_SOI_PROBE_BYTES is copied from the first DMA block into a
stack buffer and validated by cam_verify_jpeg_soi() to verify that it's a valid
jpeg image before continuing the capture.
- Reading from PSRAM directly with cam_verify_jpeg_soi() is here avoided due to
latency of small operations done by cam_verify_jpeg_soi().
Previously there is no limit how much data would be transferred
via DMA from the camera.
Added a DMA overflow detection block in cam_task() so the
driver stops if the number of frame copies exceeds the
is reaching the maximum amount of storage allocated in
PSRAM.
Add a Kconfig switch (CONFIG_CAMERA_PSRAM_DMA) to enable camera to PSRAM
DMA copy on ESP32-S2 and ESP32-S3 targets, in favor of removing the
undocumented "set XCLK=16MHz to enable PSRAM DMA" heuristic.
cam_hal.c:
- Select psram_mode = true when CONFIG_CAMERA_PSRAM_DMA && target is S2/S3.
- Otherwise psram_mode = false.
- Log whether PSRAM DMA mode is enabled.
Kconfig:
- New bool CAMERA_PSRAM_DMA (default n) under Camera configuration.
Runtime control:
- New public API:
- esp_camera_set_psram_mode(bool enable)
- esp_camera_get_psram_mode(void)
- esp_camera_reconfigure(const camera_config_t *cfg)
README/test:
- Remove stale 16MHz XCLK comments.
- Add short note describing CONFIG_CAMERA_PSRAM_DMA.
Changes:
* Use `%zu` in _STREAM_PART for size_t-safe Content-Length formatting.
* Fix `part_buf` type: `char part_buf[64]` (was array of char pointers).
* initialize local JPEG buffer vars: `jpg_buf_len`/`jpg_buf`
* drop leading underscores on them for consistency with other locals
* Bound `snprintf()` by `sizeof(part_buf)`; capture `int hlen`.
* Detect and log header truncation with required/available sizes; fail gracefully.
* Break out of loop on JPEG compression failure to avoid invalid buffer use.
* Send header chunk only when formatting succeeds.
* Guard FPS calculation against divide-by-zero; compute once as `fps`.
* Remove unsafe/needless pointer cast.
* Minor style cleanups for readability.
SCCB_Install_Device() rejected new devices only when device_count > MAX_DEVICES.
When device_count == MAX_DEVICES the function still proceeded to install the
device and wrote to devices[device_count], i.e. devices[MAX_DEVICES], which is
one element past the end of the devices[] array (valid indices 0..MAX_DEVICES-1).
This off-by-one results in a buffer overflow / write outside the designated
memory area and then increments device_count to MAX_DEVICES+1.
Change the guard to `device_count >= MAX_DEVICES` so we refuse installation once
the array is full and prevent the out-of-bounds write/read chain.
The old code always wrote BRIGHT even if enabling ISPCTRL5 failed,
while also overwriting the first error code. It also used
`if (ret != 0)` to log/update status, so we reported success when
the write actually failed.
Guard the BRIGHT write behind a successful ISPCTRL5 write and only log /
update `sensor->status.brightness` when both writes succeed. This keeps
the original error, and prevents false success reports.
* Mark all currently unused PLL parameters with (void) to remove
-Wunused-parameter noise.
* After a successful read_reg(), reject any value > 0xFF and return
-ERANGE. Ensures only 8-bit data is written to PLL1CFG.
The old code stored the raw return value of read_reg() in an 8-bit
variable and wrote it straight back to PLL1CFG. A negative I²C/SCCB
error therefore became 0xFF (or similar), silently corrupting the
sensor’s clock tree while still returning “success”.
* Read PLL1CFG into an int (`ret`) and return immediately if `ret < 0`.
* Cast to uint8_t only after the error check, then proceed with the
masked write.
This propagates read failures to the caller and guarantees we never
write garbage to the PLL register under fault conditions.
* Replace ESP_LOGx in ISRs/tight loops with CAM_WARN_THROTTLE(counter,msg)
→ uses ROM-resident ets_printf(); ~300 B less stack per hit.
* At CONFIG_LOG_DEFAULT_LEVEL < 2 the macro compiles to a no-op,
so *all* warning code is dropped from the binary.
* First miss logs immediately, then every 100th; counter wraps at 10 000
(reset to 1 to skip the “first miss” banner after wrap).
* New static uint16_t counters per call-site keep totals without globals.
* Kconfig switch CAM_LOG_SPAM_EVERY_FRAME (=0) restores old per-frame debug.
No functional change to capture path—just less stack, fewer cycles,
smaller image, and a much quieter UART.
If DMA returns a frame shorter than two bytes, the previous code
did:
dptr = inbuf + length - 2;
which under-flows the pointer and produces undefined behaviour.
Behaviour for valid frames (length ≥ 2) is unchanged; damaged or
empty buffers are now discarded safely.
and (2) over-reading the last 2 bytes
Changes:
* Store SOI as a 3-byte array (0xFF D8 FF)
* Early-exit when length < 3 to avoid over-reading
* calculate end index correctly, to avoid over-reading