17
17
#include <stdalign.h>
18
18
#include "esp_heap_caps.h"
19
19
#include "freertos/FreeRTOS.h"
20
+ #include "freertos/task.h"
20
21
#include "ll_cam.h"
21
22
#include "cam_hal.h"
22
23
23
24
#if (ESP_IDF_VERSION_MAJOR == 3 ) && (ESP_IDF_VERSION_MINOR == 3 )
24
25
#include "rom/ets_sys.h"
25
26
#else
26
27
#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
27
35
#if CONFIG_IDF_TARGET_ESP32
28
36
#include "esp32/rom/ets_sys.h" // will be removed in idf v5.0
29
37
#elif CONFIG_IDF_TARGET_ESP32S2
@@ -56,6 +64,53 @@ static portMUX_TYPE g_psram_dma_lock = portMUX_INITIALIZER_UNLOCKED;
56
64
#define CAM_LOG_SPAM_EVERY_FRAME 0 /* set to 1 to restore old behaviour */
57
65
#endif
58
66
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
+
59
114
/* Throttle repeated warnings printed from tight loops / ISRs.
60
115
*
61
116
* counter – static DRAM/IRAM uint16_t you pass in
@@ -208,21 +263,67 @@ static void cam_task(void *arg)
208
263
& frame_buffer_event -> buf [frame_buffer_event -> len ],
209
264
& cam_obj -> dma_buffer [(cnt % cam_obj -> dma_half_buffer_cnt ) * cam_obj -> dma_half_buffer_size ],
210
265
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
+ }
211
278
}
279
+
212
280
//Check for JPEG SOI in the first buffer. stop if not found
213
- if (cam_obj -> jpeg_mode && cnt == 0 && cam_verify_jpeg_soi (frame_buffer_event -> buf , frame_buffer_event -> len ) != 0 ) {
214
- ll_cam_stop (cam_obj );
215
- 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
+ }
216
324
}
325
+
217
326
cnt ++ ;
218
- // stop when too many DMA copies occur so the PSRAM
219
- // framebuffer slot doesn't overflow from runaway transfers
220
- if (cnt >= cam_obj -> frame_copy_cnt ) {
221
- ESP_LOGE (TAG , "DMA overflow" );
222
- ll_cam_stop (cam_obj );
223
- cam_obj -> state = CAM_STATE_IDLE ;
224
- continue ;
225
- }
226
327
227
328
} else if (cam_event == CAM_VSYNC_EVENT ) {
228
329
//DBG_PIN_SET(1);
@@ -320,6 +421,9 @@ static esp_err_t cam_dma_config(const camera_config_t *config)
320
421
321
422
cam_obj -> dma_node_cnt = (cam_obj -> dma_buffer_size ) / cam_obj -> dma_node_buffer_size ; // Number of DMA nodes
322
423
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
+ }
323
427
324
428
ESP_LOGI (TAG , "buffer_size: %d, half_buffer_size: %d, node_buffer_size: %d, node_cnt: %d, total_cnt: %d" ,
325
429
(int ) cam_obj -> dma_buffer_size , (int ) cam_obj -> dma_half_buffer_size , (int ) cam_obj -> dma_node_buffer_size ,
@@ -338,6 +442,7 @@ static esp_err_t cam_dma_config(const camera_config_t *config)
338
442
if (cam_obj -> fb_size < cam_obj -> recv_size ) {
339
443
fb_size = cam_obj -> recv_size ;
340
444
}
445
+ fb_size += cam_obj -> dma_half_buffer_size ;
341
446
}
342
447
343
448
/* Allocate memory for frame buffer */
@@ -589,12 +694,38 @@ camera_fb_t *cam_take(TickType_t timeout)
589
694
590
695
if (cam_obj -> jpeg_mode ) {
591
696
/* find the end marker for JPEG. Data after that can be discarded */
592
- 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
+
593
718
if (offset_e >= 0 ) {
594
719
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
+ }
595
724
return dma_buffer ;
596
725
}
597
726
727
+ skip_eoi_check :
728
+
598
729
CAM_WARN_THROTTLE (warn_eoi_miss_cnt ,
599
730
"NO-EOI - JPEG end marker missing" );
600
731
cam_give (dma_buffer );
@@ -605,6 +736,11 @@ camera_fb_t *cam_take(TickType_t timeout)
605
736
dma_buffer -> len = ll_cam_memcpy (cam_obj , dma_buffer -> buf , dma_buffer -> buf , dma_buffer -> len );
606
737
}
607
738
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
+
608
744
return dma_buffer ;
609
745
}
610
746
}
0 commit comments