Skip to content

cam_hal: shrink ISR stack, silence spam, strip logs at low levels #765

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

RubenKelevra
Copy link

Description

Requires #758, #759 and #760 to be merged first, thus containing their commits here.

  • 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.
  • 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.

Related

Crashes reported by @turenkomv here due to excessive stack usage.

Checklist

Before submitting a Pull Request, please ensure the following:

  • 🚨 This PR does not introduce breaking changes.
  • All CI checks (GH Actions) pass.
  • Documentation is updated as needed.
  • Tests are updated or added as necessary.
  • Code is well-commented, especially in complex areas.
  • Git history is clean — commits are squashed to the minimum necessary.

               – prevents stack-overflow in cam_task

The old cam_take() used recursion to retry if
* no JPEG EOI was found or
* a NULL frame pointer was returned (GDMA glitch on ESP32-S3).

Under heavy loss conditions this could overflow the cam_task stack
and reboot the whole system.

* Re-wrote cam_take() as a single while-loop – stack depth is now
  constant and independent of retry count.
* Added strict timeout tracking (`remaining = timeout - elapsed`);
  function can never block longer than the caller’s timeout.
* ESP32-S3 only
  * capped GDMA reset storms to 3 attempts (`MAX_GDMA_RESETS`)
  * logs one “giving up” warning, then yields (`vTaskDelay(1)`)
    to avoid busy-spin after hardware is wedged.
* Non-S3 targets
  * emit early `ESP_LOGW` when a NULL frame ever appears,
    then yield one tick per loop to prevent CPU thrash.
* Maintained existing JPEG-EOI trimming and YUV→GRAY memcpy paths;
  behaviour unchanged on successful frames.
* Inline comment links to esp32-camera commit 984999f / issue espressif#620
  for future context.
              and (2) over-reading the last 2 bytes

Changes:
* Store SOI as a 3-byte array (0xFF D8 FF) and use sizeof() everywhere.
* Early-exit when length < 3 to avoid over-reading
* calculate end index correctly, to avoid over-reading
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.
* 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.
@RubenKelevra
Copy link
Author

RubenKelevra commented Jul 7, 2025

@turenkomv feel free to test. The goal here is to be less crashy without increasing the standard stack size.

@turenkomv
Copy link

turenkomv commented Jul 7, 2025

Without increasing the stack size, I’m getting a crash like this:

[19:51:57]Guru Meditation Error: Core  1 panic'ed (Unhandled debug exception). 
[19:51:57]Debug exception reason: Stack canary watchpoint triggered (framebuffer_tas) 
[19:51:57]Core  1 register dump:
[19:51:57]PC      : 0x4037ef9e  PS      : 0x00060836  A0      : 0x820494aa  A1      : 0x3fcb4780
WARNING Decoded 0x4037ef9e: xPortSetInterruptMaskFromISR at C:\Users\turen\.platformio\packages\framework-espidf\components\freertos\FreeRTOS-Kernel\portable\xtensa\include\freertos/portmacro.h:552
 (inlined by) xPortEnterCriticalTimeout at C:\Users\turen\.platformio\packages\framework-espidf\components\freertos\FreeRTOS-Kernel\portable\xtensa/port.c:478
[19:51:57]A2      : 0x3fc9ae6c  A3      : 0xffffffff  A4      : 0x00000007  A5      : 0x3fcb47be  
[19:51:57]A6      : 0x00000066  A7      : 0x00000064  A8      : 0x00060820  A9      : 0x3fcb4780
[19:51:57]A10     : 0x00060823  A11     : 0x00000000  A12     : 0x00060820  A13     : 0x00060223
[19:51:57]A14     : 0x00000000  A15     : 0x0000abab  SAR     : 0x00000018  EXCCAUSE: 0x00000001
[19:51:57]EXCVADDR: 0x00000000  LBEG    : 0x40056fc5  LEND    : 0x40056fe7  LCOUNT  : 0xffffffff


[19:51:57]Backtrace: 0x4037ef9b:0x3fcb4780 0x420494a7:0x3fcb47b0 0x420495cd:0x3fcb47d0 0x4200f9ed:0x3fcb47f0 0x42016ada:0x3fcb48d0 0x420a5a61:0x3fcb4910 0x40384606:0x3fcb4940 0x4201a84a:0x3fcb4990 0x42019d6a:0x3fcb49d0 0x4200bc76:0x3fcb49f0
WARNING Found stack trace! Trying to decode it
WARNING Decoded 0x4037ef9b: xPortSetInterruptMaskFromISR at C:\Users\turen\.platformio\packages\framework-espidf\components\freertos\FreeRTOS-Kernel\portable\xtensa\include\freertos/portmacro.h:552
 (inlined by) xPortEnterCriticalTimeout at C:\Users\turen\.platformio\packages\framework-espidf\components\freertos\FreeRTOS-Kernel\portable\xtensa/port.c:478
WARNING Decoded 0x420494a7: vPortEnterCritical at C:\Users\turen\.platformio\packages\framework-espidf\components\freertos\FreeRTOS-Kernel\portable\xtensa\include\freertos/portmacro.h:567
 (inlined by) find_key at C:\Users\turen\.platformio\packages\framework-espidf\components\pthread/pthread_local_storage.c:77
WARNING Decoded 0x420495cd: pthread_setspecific at C:\Users\turen\.platformio\packages\framework-espidf\components\pthread/pthread_local_storage.c:199
WARNING Decoded 0x4200f9ed: esphome::logger::Logger::check_and_set_task_log_recursion_(bool) at C:\Projects\m5stack-camera-1\.esphome\build\m5stack-camera-1/src/esphome/components/logger/logger.h:298
 (inlined by) esphome::logger::Logger::check_and_set_task_log_recursion_(bool) at C:\Projects\m5stack-camera-1\.esphome\build\m5stack-camera-1/src/esphome/components/logger/logger.h:287    
 (inlined by) esphome::logger::Logger::log_vprintf_(unsigned char, char const*, int, char const*, __va_list_tag) at C:\Projects\m5stack-camera-1\.esphome\build\m5stack-camera-1/src/esphome/components/logger/logger.cpp:35
WARNING Decoded 0x42016ada: esphome::esp_idf_log_vprintf_(char const*, __va_list_tag) at C:\Projects\m5stack-camera-1\.esphome\build\m5stack-camera-1/src/esphome/core/log.cpp:56
WARNING Decoded 0x420a5a61: esp_log_writev at C:\Users\turen\.platformio\packages\framework-espidf\components\log/log.c:210
WARNING Decoded 0x40384606: esp_log_write at C:\Users\turen\.platformio\packages\framework-espidf\components\log/log.c:220
WARNING Decoded 0x4201a84a: cam_take at C:\Projects\m5stack-camera-1\.esphome\build\m5stack-camera-1/managed_components/espressif__esp32-camera/driver/cam_hal.c:541 (discriminator 1)
WARNING Decoded 0x42019d6a: esp_camera_fb_get at C:\Projects\m5stack-camera-1\.esphome\build\m5stack-camera-1/managed_components/espressif__esp32-camera/driver/esp_camera.c:384
WARNING Decoded 0x4200bc76: esphome::esp32_camera::ESP32Camera::framebuffer_task(void*) at C:\Projects\m5stack-camera-1\.esphome\build\m5stack-camera-1/src/esphome/components/esp32_camera/esp32_camera.cpp:412

Increasing the stack size for framebuffer_task as shown here helps avoid the crash:
https://github.com/esphome/esphome/blob/dev/esphome/components/esp32_camera/esp32_camera.cpp#L43

  xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task,
                          "framebuffer_task",  // name
                          8192,                // stack size
                          nullptr,             // task pv params
                          1,                   // priority
                          nullptr,             // handle
                          1                    // core
  );

After that, the device runs stably — no more crashes — but still doesn’t receive any frames.

@RubenKelevra
Copy link
Author

@turenkomv

thanks!

Sorry for the delayed response. I'll wait for further development that I got the hardware, which arrived in my country today. So given the usual delay it should be here on monday.

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