Real-Time Image Processing Pipeline : DMA + DCMI Optimization
Building high-performance camera systems for edge AI means mastering the dance between hardware peripherals, memory architecture, and real-time constraints. When developing precision imaging for pet healthcare monitoring, achieving consistent capture rates with zero data loss is the whole game.
The challenge: zero-copy high-speed imaging
Traditional microcontroller camera implementations often struggle with memory bottlenecks and CPU overhead. Our challenge was capturing 640x400 12-bit images at sustained frame rates while processing them in real time for AI inference, under real constraints: 530KB per frame against limited internal SRAM, sub-100ms latency from capture to analysis, zero dropped frames under continuous operation, and edge power budgets.
Architecture: multi-buffer DMA pipeline
The solution centers on a DMA ring buffer architecture that eliminates CPU intervention during capture, using STM32H5's General Purpose DMA (GPDMA) in linked-list mode for seamless buffer management. DCMI (Digital Camera Interface) provides hardware synchronization with the image sensor, 12-bit parallel capture, frame-level interrupt generation, and automatic stop in snapshot mode. GPDMA's linked list gives zero-copy transfers from DCMI to PSRAM, circular buffer management, and interrupt-driven completion signaling. A ring buffer manager on top handles a 12-buffer circular allocation system with a state machine for buffer lifecycle and thread-safe allocation and deallocation.


DMA configuration
The GPDMA configuration leverages STM32H5's linked-list capability to chain multiple buffer transfers with no CPU intervention:
// Configure GPDMA for continuous image capture
hdma_gpdma1_channel7.Init.Request = GPDMA1_REQUEST_DCMI_PSSI;
hdma_gpdma1_channel7.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
hdma_gpdma1_channel7.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_gpdma1_channel7.Init.SrcInc = DMA_SINC_FIXED;
hdma_gpdma1_channel7.Init.DestInc = DMA_DINC_INCREMENTED;
hdma_gpdma1_channel7.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
hdma_gpdma1_channel7.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;Interrupt priorities were tuned carefully to minimize latency: DCMI frame completion at priority 5, DMA transfer complete at priority 4, and the frame processing task at a high FreeRTOS priority. That hierarchy makes sure hardware events get serviced immediately while the RTOS scheduler handles heavy processing efficiently.

Circular buffer management
Each buffer moves through a well-defined state machine: FREE, available for allocation; CAPTURING, DMA actively writing; COPYING, protected during PSRAM transfer; QUEUED_FOR_FLUSH, ready for storage or transmission; and FLUSHING, being written to persistent storage.

typedef enum {
DMA_BUFFER_FREE = 0,
DMA_BUFFER_CAPTURING,
DMA_BUFFER_COPYING,
DMA_BUFFER_QUEUED_FOR_FLUSH,
DMA_BUFFER_FLUSHING
} dma_buffer_state_t;The ring buffer strategically uses external PSRAM to preserve precious internal SRAM: 12 buffers at 530KB each land in PSRAM, while metadata stays in fast internal RAM at under 200 bytes total.
// 12 buffers x 530KB = 6.35MB in PSRAM
PSRAM_ARRAY(uint8_t, dma_ring_buffers[12], 530432)
__attribute__((aligned(32)));
// Metadata remains in fast internal RAM
static dma_buffer_metadata_t buffer_metadata[12]; // 192 bytes totalInterrupt context optimization
The frame completion interrupt handler stays exceptionally lightweight to minimize jitter, storing frame metadata and notifying the processing task without ever blocking:
void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) {
isr_callback_time = xTaskGetTickCount();
pending_frame_buffer = (volatile uint8_t *)camera_handle.frame_buffer;
pending_frame_size = AR0144_BUFFER_SIZE;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(frame_processing_task_handle,
&xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}This design achieves very low interrupt latency by deferring all heavy operations to a dedicated processing task, where AI inference, compression, and network transmission happen safely in task context:
static void frame_processing_task(void *pvParameters) {
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
taskENTER_CRITICAL();
uint8_t *local_buffer = (uint8_t *)pending_frame_buffer;
size_t local_size = pending_frame_size;
pending_frame_buffer = NULL;
taskEXIT_CRITICAL();
if (frame_callback) {
frame_callback(local_buffer, local_size);
}
}
}Performance characteristics
Instrumenting the full pipeline shows hardware capture taking about 33ms, DMA transfer under 1ms at full GPDMA speed, task wake latency of 2 to 5ms from RTOS scheduling, and callback processing varying from 10 to 50ms for inference, adding up to roughly 45 to 89ms end to end. Memory efficiency stayed strong too, with peak external PSRAM allocation around 8MB, under 1KB of internal RAM overhead for metadata, a buffer reuse rate around 99.8%, and zero fragmentation thanks to fixed-size allocations.
Error handling and recovery
Robust error handling keeps operation continuous despite transient failures. On a DMA overrun, the pipeline unlinks and reconfigures the DCMI linked list rather than trying to patch around a corrupted state:

void HAL_DCMI_ErrorCallback(DCMI_HandleTypeDef *hdcmi) {
uint32_t error_flags = hdcmi->ErrorCode;
if (error_flags & HAL_DCMI_ERROR_OVR) {
MX_DCMI_LinkedList_UnLink(&handle_GPDMA1_Channel7);
MX_DCMI_LinkedList_Config();
MX_DCMI_LinkedList_Link(&handle_GPDMA1_Channel7);
}
}Thread-safe buffer allocation prevents race conditions by taking a mutex before searching for the next FREE buffer with wraparound:
int dma_ring_buffer_allocate(uint32_t sequence_id, uint16_t command_id,
uint16_t proximity_mm) {
if (xSemaphoreTake(ring_mutex, pdMS_TO_TICKS(100)) != pdTRUE) {
return -1;
}
for (int i = 0; i < NUM_DMA_BUFFERS; i++) {
int idx = (next_buffer_index + i) % NUM_DMA_BUFFERS;
if (buffer_metadata[idx].state == DMA_BUFFER_FREE) {
buffer_metadata[idx].state = DMA_BUFFER_CAPTURING;
break;
}
}
xSemaphoreGive(ring_mutex);
return allocated_idx;
}Camera sensor integration
The implementation supports continuous streaming mode, eliminating start and stop overhead, with buffer alignment validated before every capture to keep DMA efficient:
HAL_StatusTypeDef Camera_StartCapture(Camera_FrameCallback callback,
uint8_t *buffer, size_t size) {
if (((uint32_t)buffer & 0x3) != 0) {
LOG_ERROR_TAG("DCMI", "Buffer must be 32-bit aligned");
return HAL_ERROR;
}
HAL_StatusTypeDef result = HAL_DCMI_Start_DMA(
camera_handle.hdcmi, DCMI_MODE_SNAPSHOT,
(uint32_t)buffer, size / 4);
}Hardware synchronization through DCMI eliminates software-based frame timing entirely, cutting CPU load and improving accuracy.
Key takeaways
Hardware acceleration is essential, GPDMA's linked-list mode eliminated CPU bottlenecks entirely during capture. Memory architecture matters, strategic use of external PSRAM for buffers while keeping metadata in fast internal RAM optimizes both performance and memory footprint. Interrupt design is critical, keeping ISRs minimal and deferring processing to task context achieves very low interrupt latency. Buffer management complexity pays off, the state machine prevents corruption while maximizing reuse efficiency. And error recovery enables reliability, comprehensive handling and automatic recovery keep the system running continuously in production.
Why it matters at Hoomanely
This real-time imaging pipeline is a core piece of our pet healthcare technology. Combining sensor fusion, edge AI, and machine learning, our biosense AI engine converts continuous health monitoring data into personalized insights that help pet owners catch health issues early. This pipeline's precision and reliability is what makes decoding every moment of a pet's life through clinical-grade intelligence at the edge actually possible.