2525#include "rom/ets_sys.h"
2626#else
2727#include "esp_timer.h"
28+ #include "esp_cache.h"
29+ #include "hal/cache_hal.h"
30+ #include "hal/cache_ll.h"
31+ #include "esp_idf_version.h"
32+ #ifndef ESP_CACHE_MSYNC_FLAG_DIR_M2C
33+ #define ESP_CACHE_MSYNC_FLAG_DIR_M2C 0
34+ #endif
2835#if CONFIG_IDF_TARGET_ESP32
2936#include "esp32/rom/ets_sys.h" // will be removed in idf v5.0
3037#elif CONFIG_IDF_TARGET_ESP32S2
@@ -57,6 +64,53 @@ static portMUX_TYPE g_psram_dma_lock = portMUX_INITIALIZER_UNLOCKED;
5764#define CAM_LOG_SPAM_EVERY_FRAME 0 /* set to 1 to restore old behaviour */
5865#endif
5966
67+ /* Number of bytes copied to SRAM for SOI validation when capturing
68+ * directly to PSRAM. Tunable to probe more of the frame start if needed. */
69+ #ifndef CAM_SOI_PROBE_BYTES
70+ #define CAM_SOI_PROBE_BYTES 32
71+ #endif
72+
73+ /* Number of bytes copied to SRAM for EOI validation when capturing
74+ * directly to PSRAM. Tunable to probe more of the frame tail if needed. */
75+ #ifndef CAM_EOI_PROBE_BYTES
76+ #define CAM_EOI_PROBE_BYTES 32
77+ #endif
78+
79+ /*
80+ * PSRAM DMA may bypass the CPU cache. Always call esp_cache_msync() on the
81+ * SOI probe region so cached reads see the data written by DMA.
82+ */
83+
84+ static inline size_t dcache_line_size (void )
85+ {
86+ #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL (5 , 2 , 0 )
87+ /* cache_hal_get_cache_line_size() added extra argument from IDF 5.2 */
88+ return cache_hal_get_cache_line_size (CACHE_LL_LEVEL_EXT_MEM , CACHE_TYPE_DATA );
89+ #else
90+ /* Older releases only expose the ROM helper, all current targets
91+ * have a 32‑byte DCache line */
92+ return 32 ;
93+ #endif
94+ }
95+
96+ /*
97+ * Invalidate CPU data cache lines that cover a region in PSRAM which
98+ * has just been written by DMA. This guarantees subsequent CPU reads
99+ * fetch the fresh data from PSRAM rather than stale cache contents.
100+ * Both address and length are aligned to the data cache line size.
101+ */
102+ static inline void cam_drop_psram_cache (void * addr , size_t len )
103+ {
104+ size_t line = dcache_line_size ();
105+ if (line == 0 ) {
106+ line = 32 ; /* sane fallback */
107+ }
108+ uintptr_t start = (uintptr_t )addr & ~(line - 1 );
109+ size_t sync_len = (len + ((uintptr_t )addr - start ) + line - 1 ) & ~(line - 1 );
110+ esp_cache_msync ((void * )start , sync_len ,
111+ ESP_CACHE_MSYNC_FLAG_DIR_M2C | ESP_CACHE_MSYNC_FLAG_INVALIDATE );
112+ }
113+
60114/* Throttle repeated warnings printed from tight loops / ISRs.
61115 *
62116 * counter – static DRAM/IRAM uint16_t you pass in
@@ -209,21 +263,67 @@ static void cam_task(void *arg)
209263 & frame_buffer_event -> buf [frame_buffer_event -> len ],
210264 & cam_obj -> dma_buffer [(cnt % cam_obj -> dma_half_buffer_cnt ) * cam_obj -> dma_half_buffer_size ],
211265 cam_obj -> dma_half_buffer_size );
266+ } else {
267+ // stop if the next DMA copy would exceed the framebuffer slot
268+ // size, since we're called only after the copy occurs
269+ // This effectively reduces maximum usable frame buffer size
270+ // by one DMA operation, as we can't predict here, if the next
271+ // cam event will be a VSYNC
272+ if (cnt + 1 >= cam_obj -> frame_copy_cnt ) {
273+ ESP_CAMERA_ETS_PRINTF (DRAM_STR ("cam_hal: DMA overflow\r\n" ));
274+ ll_cam_stop (cam_obj );
275+ cam_obj -> state = CAM_STATE_IDLE ;
276+ continue ;
277+ }
212278 }
279+
213280 //Check for JPEG SOI in the first buffer. stop if not found
214- if (cam_obj -> jpeg_mode && cnt == 0 && cam_verify_jpeg_soi (frame_buffer_event -> buf , frame_buffer_event -> len ) != 0 ) {
215- ll_cam_stop (cam_obj );
216- cam_obj -> state = CAM_STATE_IDLE ;
281+ if (cam_obj -> jpeg_mode && cnt == 0 ) {
282+ if (cam_obj -> psram_mode ) {
283+ /* dma_half_buffer_size already in BYTES (see ll_cam_memcpy()) */
284+ size_t probe_len = cam_obj -> dma_half_buffer_size ;
285+ /* clamp to avoid copying past the end of soi_probe */
286+ if (probe_len > CAM_SOI_PROBE_BYTES ) {
287+ probe_len = CAM_SOI_PROBE_BYTES ;
288+ }
289+ /* Invalidate cache lines for the DMA buffer before probing */
290+ cam_drop_psram_cache (frame_buffer_event -> buf , probe_len );
291+
292+ uint8_t soi_probe [CAM_SOI_PROBE_BYTES ];
293+ memcpy (soi_probe , frame_buffer_event -> buf , probe_len );
294+ int soi_off = cam_verify_jpeg_soi (soi_probe , probe_len );
295+ if (soi_off != 0 ) {
296+ static uint16_t warn_psram_soi_cnt = 0 ;
297+ if (soi_off > 0 ) {
298+ CAM_WARN_THROTTLE (warn_psram_soi_cnt ,
299+ "NO-SOI - JPEG start marker not at pos 0 (PSRAM)" );
300+ } else {
301+ CAM_WARN_THROTTLE (warn_psram_soi_cnt ,
302+ "NO-SOI - JPEG start marker missing (PSRAM)" );
303+ }
304+ ll_cam_stop (cam_obj );
305+ cam_obj -> state = CAM_STATE_IDLE ;
306+ continue ;
307+ }
308+ } else {
309+ int soi_off = cam_verify_jpeg_soi (frame_buffer_event -> buf , frame_buffer_event -> len );
310+ if (soi_off != 0 ) {
311+ static uint16_t warn_soi_bad_cnt = 0 ;
312+ if (soi_off > 0 ) {
313+ CAM_WARN_THROTTLE (warn_soi_bad_cnt ,
314+ "NO-SOI - JPEG start marker not at pos 0" );
315+ } else {
316+ CAM_WARN_THROTTLE (warn_soi_bad_cnt ,
317+ "NO-SOI - JPEG start marker missing" );
318+ }
319+ ll_cam_stop (cam_obj );
320+ cam_obj -> state = CAM_STATE_IDLE ;
321+ continue ;
322+ }
323+ }
217324 }
325+
218326 cnt ++ ;
219- // stop when too many DMA copies occur so the PSRAM
220- // framebuffer slot doesn't overflow from runaway transfers
221- if (cnt >= cam_obj -> frame_copy_cnt ) {
222- ESP_LOGE (TAG , "DMA overflow" );
223- ll_cam_stop (cam_obj );
224- cam_obj -> state = CAM_STATE_IDLE ;
225- continue ;
226- }
227327
228328 } else if (cam_event == CAM_VSYNC_EVENT ) {
229329 //DBG_PIN_SET(1);
@@ -321,6 +421,9 @@ static esp_err_t cam_dma_config(const camera_config_t *config)
321421
322422 cam_obj -> dma_node_cnt = (cam_obj -> dma_buffer_size ) / cam_obj -> dma_node_buffer_size ; // Number of DMA nodes
323423 cam_obj -> frame_copy_cnt = cam_obj -> recv_size / cam_obj -> dma_half_buffer_size ; // Number of interrupted copies, ping-pong copy
424+ if (cam_obj -> psram_mode ) {
425+ cam_obj -> frame_copy_cnt ++ ;
426+ }
324427
325428 ESP_LOGI (TAG , "buffer_size: %d, half_buffer_size: %d, node_buffer_size: %d, node_cnt: %d, total_cnt: %d" ,
326429 (int ) cam_obj -> dma_buffer_size , (int ) cam_obj -> dma_half_buffer_size , (int ) cam_obj -> dma_node_buffer_size ,
@@ -339,6 +442,7 @@ static esp_err_t cam_dma_config(const camera_config_t *config)
339442 if (cam_obj -> fb_size < cam_obj -> recv_size ) {
340443 fb_size = cam_obj -> recv_size ;
341444 }
445+ fb_size += cam_obj -> dma_half_buffer_size ;
342446 }
343447
344448 /* Allocate memory for frame buffer */
@@ -590,12 +694,38 @@ camera_fb_t *cam_take(TickType_t timeout)
590694
591695 if (cam_obj -> jpeg_mode ) {
592696 /* find the end marker for JPEG. Data after that can be discarded */
593- int offset_e = cam_verify_jpeg_eoi (dma_buffer -> buf , dma_buffer -> len );
697+ int offset_e = -1 ;
698+ if (cam_obj -> psram_mode ) {
699+ size_t probe_len = dma_buffer -> len ;
700+ if (probe_len > CAM_EOI_PROBE_BYTES ) {
701+ probe_len = CAM_EOI_PROBE_BYTES ;
702+ }
703+ if (probe_len == 0 ) {
704+ goto skip_eoi_check ;
705+ }
706+ cam_drop_psram_cache (dma_buffer -> buf + dma_buffer -> len - probe_len , probe_len );
707+
708+ uint8_t eoi_probe [CAM_EOI_PROBE_BYTES ];
709+ memcpy (eoi_probe , dma_buffer -> buf + dma_buffer -> len - probe_len , probe_len );
710+ int off = cam_verify_jpeg_eoi (eoi_probe , probe_len );
711+ if (off >= 0 ) {
712+ offset_e = dma_buffer -> len - probe_len + off ;
713+ }
714+ } else {
715+ offset_e = cam_verify_jpeg_eoi (dma_buffer -> buf , dma_buffer -> len );
716+ }
717+
594718 if (offset_e >= 0 ) {
595719 dma_buffer -> len = offset_e + sizeof (JPEG_EOI_MARKER );
720+ if (cam_obj -> psram_mode ) {
721+ /* DMA may bypass cache, ensure full frame is visible */
722+ cam_drop_psram_cache (dma_buffer -> buf , dma_buffer -> len );
723+ }
596724 return dma_buffer ;
597725 }
598726
727+ skip_eoi_check :
728+
599729 CAM_WARN_THROTTLE (warn_eoi_miss_cnt ,
600730 "NO-EOI - JPEG end marker missing" );
601731 cam_give (dma_buffer );
@@ -606,6 +736,11 @@ camera_fb_t *cam_take(TickType_t timeout)
606736 dma_buffer -> len = ll_cam_memcpy (cam_obj , dma_buffer -> buf , dma_buffer -> buf , dma_buffer -> len );
607737 }
608738
739+ if (cam_obj -> psram_mode ) {
740+ /* DMA may bypass cache, ensure full frame is visible to the app */
741+ cam_drop_psram_cache (dma_buffer -> buf , dma_buffer -> len );
742+ }
743+
609744 return dma_buffer ;
610745 }
611746}
0 commit comments