From 2b4d66ba238475595af8fc492c6fe960bc678daa Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 3 Jul 2025 20:43:16 +0200 Subject: [PATCH] ports/stm32: Refactor MDMA code. Moved to separate file to make omv_csi more readable. Signed-off-by: iabdalkader --- ports/stm32/omv_csi.c | 151 +++--------------------------------------- ports/stm32/stm_dma.c | 134 +++++++++++++++++++++++++++++++++++++ ports/stm32/stm_dma.h | 13 ++++ 3 files changed, 158 insertions(+), 140 deletions(-) diff --git a/ports/stm32/omv_csi.c b/ports/stm32/omv_csi.c index c0c1ae17d..9e24c0e69 100644 --- a/ports/stm32/omv_csi.c +++ b/ports/stm32/omv_csi.c @@ -50,7 +50,6 @@ #if defined(OMV_MDMA_CHANNEL_DCMI_0) #define USE_MDMA (1) -#define MDMA_BUFFER_SIZE (64) #endif #if !defined(DCMIPP) @@ -590,22 +589,12 @@ void DCMI_DMAConvCpltUser(uint32_t addr) { bytes_per_pixel = sizeof(uint8_t); } - // For all non-JPEG and non-transposed modes image capture can be completely offload to MDMA. #if USE_MDMA + // For non-JPEG, non-transposed modes, offload the capture to MDMA. + // Note that MDMA is started here, not in FRAME/VSYNC callbacks, to + // maximize the time before the frame has to be dropped. if (!csi->transpose) { - // NOTE: MDMA is started here, not in FRAME/VSYNC callbacks, to maximize the time before - // the frame has to be dropped. - uint32_t line_width_bytes = fb->u * bytes_per_pixel; - // mdma0 will copy this line of the image to the final destination. - __HAL_UNLOCK(&csi->mdma0); - csi->mdma0.State = HAL_MDMA_STATE_READY; - HAL_MDMA_Start(&csi->mdma0, (uint32_t) src, (uint32_t) dst, - line_width_bytes, 1); - // mdma1 will copy all remaining lines of the image to the final destination. - __HAL_UNLOCK(&csi->mdma1); - csi->mdma1.State = HAL_MDMA_STATE_READY; - HAL_MDMA_Start(&csi->mdma1, (uint32_t) src, (uint32_t) (dst + line_width_bytes), - line_width_bytes, fb->v - 1); + stm_mdma_start(csi, (uint32_t) src, (uint32_t) dst, fb->u * bytes_per_pixel, fb->v); HAL_NVIC_DisableIRQ(DMA2_Stream1_IRQn); return; } @@ -618,7 +607,7 @@ void DCMI_DMAConvCpltUser(uint32_t addr) { } #if USE_MDMA - // Two MDMA channels are used to maximize the time available for each channel to finish the transfer. + // Two MDMA channels are used to maximize the time available to finish the transfer. omv_csi_copy_line(csi, (buffer->offset % 2) ? &csi->mdma1 : &csi->mdma0, src, dst); #else omv_csi_copy_line(csi, NULL, src, dst); @@ -626,125 +615,6 @@ void DCMI_DMAConvCpltUser(uint32_t addr) { } #endif -#if USE_MDMA -// Configures an MDMA channel to completely offload the CPU in copying one line of pixels. -static void omv_csi_mdma_config(omv_csi_t *csi, MDMA_InitTypeDef *init, uint32_t bytes_per_pixel) { - framebuffer_t *fb = csi->fb; - - init->Request = MDMA_REQUEST_SW; - init->TransferTriggerMode = MDMA_REPEAT_BLOCK_TRANSFER; - init->Priority = MDMA_PRIORITY_VERY_HIGH; - init->DataAlignment = MDMA_DATAALIGN_PACKENABLE; - init->BufferTransferLength = MDMA_BUFFER_SIZE; - // The source address is 1KB aligned. So, a burst size of 16 beats (AHB Max) should not break. - // Destination lines may not be aligned however so the burst size must be computed. - init->SourceBurst = MDMA_SOURCE_BURST_16BEATS; - init->SourceBlockAddressOffset = 0; - init->DestBlockAddressOffset = 0; - - if ((csi->pixformat == PIXFORMAT_RGB565 && csi->rgb_swap) || - (csi->pixformat == PIXFORMAT_YUV422 && csi->yuv_swap)) { - init->Endianness = MDMA_LITTLE_BYTE_ENDIANNESS_EXCHANGE; - } else { - init->Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE; - } - - uint32_t line_offset_bytes = (fb->x * bytes_per_pixel) - get_dcmi_hw_crop(csi, bytes_per_pixel); - uint32_t line_width_bytes = fb->u * bytes_per_pixel; - - if (csi->transpose) { - line_width_bytes = bytes_per_pixel; - init->DestBlockAddressOffset = (fb->v - 1) * bytes_per_pixel; - } - - // YUV422 Source -> Y Destination - if ((csi->pixformat == PIXFORMAT_GRAYSCALE) && (csi->mono_bpp == 2)) { - line_width_bytes /= 2; - if (csi->transpose) { - init->DestBlockAddressOffset /= 2; - } - } - - // The destination will be 32-byte aligned, so the line width is broken into the largest - // power of 2. The source may have an offset, further limiting this to a sub power of 2. - for (int i = 3; i >= 0; i--) { - if (!(line_width_bytes % (1 << i))) { - for (int j = IM_MIN(i, 2); j >= 0; j--) { - if (!(line_offset_bytes % (1 << j))) { - init->SourceInc = MDMA_CTCR_SINC_1 | (j << MDMA_CTCR_SINCOS_Pos); - init->SourceDataSize = j << MDMA_CTCR_SSIZE_Pos; - break; - } - } - - init->DestinationInc = MDMA_CTCR_DINC_1 | (i << MDMA_CTCR_DINCOS_Pos); - init->DestDataSize = i << MDMA_CTCR_DSIZE_Pos; - - // Find the burst size we can break the destination transfer up into. - uint32_t count = MDMA_BUFFER_SIZE >> i; - - for (int i = 7; i >= 0; i--) { - if (!(count % (1 << i))) { - init->DestBurst = i << MDMA_CTCR_DBURST_Pos; - break; - } - } - - break; - } - } - - // YUV422 Source -> Y Destination - if ((csi->pixformat == PIXFORMAT_GRAYSCALE) && (csi->mono_bpp == 2)) { - init->SourceInc = MDMA_SRC_INC_HALFWORD; - init->SourceDataSize = MDMA_SRC_DATASIZE_BYTE; - } -} - -static void omv_csi_mdma_enable(omv_csi_t *csi, uint32_t bytes_per_pixel) { - framebuffer_t *fb = csi->fb; - - omv_csi_mdma_config(csi, &csi->mdma0.Init, bytes_per_pixel); - memcpy(&csi->mdma1.Init, &csi->mdma0.Init, sizeof(MDMA_InitTypeDef)); - HAL_MDMA_Init(&csi->mdma0); - - // If we are not transposing the image we can fully offload image capture from the CPU. - if (!csi->transpose) { - // MDMA will trigger on each TC from DMA and transfer one line to the frame buffer. - csi->mdma1.Init.Request = MDMA_REQUEST_DMA2_Stream1_TC; - csi->mdma1.Init.TransferTriggerMode = MDMA_BLOCK_TRANSFER; - // We setup MDMA to repeatedly reset itself to transfer the same line buffer. - csi->mdma1.Init.SourceBlockAddressOffset = -(fb->u * bytes_per_pixel); - } - - HAL_MDMA_Init(&csi->mdma1); - if (!csi->transpose) { - HAL_MDMA_ConfigPostRequestMask(&csi->mdma1, (uint32_t) &DMA2->LIFCR, DMA_FLAG_TCIF1_5); - } -} - -int omv_csi_dma_memcpy(omv_csi_t *csi, void *dma, void *dst, void *src, int bpp, bool transposed) { - framebuffer_t *fb = csi->fb; - MDMA_HandleTypeDef *handle = dma; - - // Drop the frame if MDMA is not keeping up as the image will be corrupted. - if (handle->Instance->CCR & MDMA_CCR_EN) { - csi->drop_frame = true; - return 0; - } - - // If MDMA is still running, HAL_MDMA_Start() will start a new transfer. - __HAL_UNLOCK(handle); - handle->State = HAL_MDMA_STATE_READY; - HAL_MDMA_Start(handle, - (uint32_t) src, - (uint32_t) dst, - transposed ? bpp : (fb->u * bpp), - transposed ? fb->u : 1); - return 0; -} -#endif - #if USE_DCMIPP void omv_csi_update_awb(omv_csi_t *csi, uint32_t n_pixels) { uint32_t avg[3]; @@ -865,9 +735,11 @@ static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { } #if USE_MDMA - // Enable MDMA transfer from the DCMI line buffer for non-JPEG modes. + // Configure MDMA for non-JPEG modes. MDMA will be used to either + // completely offload the transfer, in case of non-transposed mode + // or copy transposed lines. if (csi->pixformat != PIXFORMAT_JPEG) { - omv_csi_mdma_enable(csi, bytes_per_pixel); + stm_mdma_init(csi, bytes_per_pixel, x_crop); } #endif @@ -889,10 +761,9 @@ static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { } #if USE_MDMA } else if ((csi->pixformat != PIXFORMAT_JPEG) && (!csi->transpose)) { - // Start an MDMA transfer, which completely offloads the capture to MDMA. - // DMA to circular mode writing the same line over and over again. + // Special transfer mode that uses DMA in circular mode and MDMA + // to move the lines to the final destination. ((DMA_Stream_TypeDef *) csi->dma.Instance)->CR |= DMA_SxCR_CIRC; - // DCMI will transfer to same line and MDMA will move to final location. HAL_DCMI_Start_DMA(&csi->dcmi, DCMI_MODE_CONTINUOUS, (uint32_t) &_line_buf, line_width_bytes / sizeof(uint32_t)); #endif // USE_MDMA diff --git a/ports/stm32/stm_dma.c b/ports/stm32/stm_dma.c index 66f665729..6bfd4f599 100644 --- a/ports/stm32/stm_dma.c +++ b/ports/stm32/stm_dma.c @@ -186,6 +186,140 @@ uint8_t stm_dma_mpu_region_size(uint32_t size) { return -1; } +#ifdef OMV_MDMA_CHANNEL_DCMI_0 +void stm_mdma_init(omv_csi_t *csi, uint32_t bytes_per_pixel, uint32_t x_crop) { + framebuffer_t *fb = csi->fb; + + stm_mdma_init_channel(csi, &csi->mdma0.Init, bytes_per_pixel, x_crop); + memcpy(&csi->mdma1.Init, &csi->mdma0.Init, sizeof(MDMA_InitTypeDef)); + HAL_MDMA_Init(&csi->mdma0); + + // If we are not transposing the image we can fully offload image capture from the CPU. + if (!csi->transpose) { + // MDMA will trigger on each TC from DMA and transfer one line to the frame buffer. + csi->mdma1.Init.Request = MDMA_REQUEST_DMA2_Stream1_TC; + csi->mdma1.Init.TransferTriggerMode = MDMA_BLOCK_TRANSFER; + // We setup MDMA to repeatedly reset itself to transfer the same line buffer. + csi->mdma1.Init.SourceBlockAddressOffset = -(fb->u * bytes_per_pixel); + } + + HAL_MDMA_Init(&csi->mdma1); + if (!csi->transpose) { + HAL_MDMA_ConfigPostRequestMask(&csi->mdma1, (uint32_t) &DMA2->LIFCR, DMA_FLAG_TCIF1_5); + } +} + +// Configures an MDMA channel to completely offload the CPU in copying one line of pixels. +static void stm_mdma_init_channel(omv_csi_t *csi, + MDMA_InitTypeDef *init, uint32_t bytes_per_pixel, uint32_t x_crop) { + framebuffer_t *fb = csi->fb; + + init->Request = MDMA_REQUEST_SW; + init->TransferTriggerMode = MDMA_REPEAT_BLOCK_TRANSFER; + init->Priority = MDMA_PRIORITY_VERY_HIGH; + init->DataAlignment = MDMA_DATAALIGN_PACKENABLE; + init->BufferTransferLength = MDMA_BUFFER_SIZE; + // The source address is 1KB aligned. So, a burst size of 16 beats + // (AHB Max) should not break. Destination lines may not be aligned + // however so the burst size must be computed. + init->SourceBurst = MDMA_SOURCE_BURST_16BEATS; + init->SourceBlockAddressOffset = 0; + init->DestBlockAddressOffset = 0; + + if ((csi->pixformat == PIXFORMAT_RGB565 && csi->rgb_swap) || + (csi->pixformat == PIXFORMAT_YUV422 && csi->yuv_swap)) { + init->Endianness = MDMA_LITTLE_BYTE_ENDIANNESS_EXCHANGE; + } else { + init->Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE; + } + + uint32_t line_offset_bytes = (fb->x * bytes_per_pixel) - x_crop; + uint32_t line_width_bytes = fb->u * bytes_per_pixel; + + if (csi->transpose) { + line_width_bytes = bytes_per_pixel; + init->DestBlockAddressOffset = (fb->v - 1) * bytes_per_pixel; + } + + // YUV422 Source -> Y Destination + if ((csi->pixformat == PIXFORMAT_GRAYSCALE) && (csi->mono_bpp == 2)) { + line_width_bytes /= 2; + if (csi->transpose) { + init->DestBlockAddressOffset /= 2; + } + } + + // The destination will be 32-byte aligned, so the line width is broken + // into the largest power of 2. The source may have an offset, further + // limiting this to a sub power of 2. + for (int i = 3; i >= 0; i--) { + if (!(line_width_bytes % (1 << i))) { + for (int j = IM_MIN(i, 2); j >= 0; j--) { + if (!(line_offset_bytes % (1 << j))) { + init->SourceInc = MDMA_CTCR_SINC_1 | (j << MDMA_CTCR_SINCOS_Pos); + init->SourceDataSize = j << MDMA_CTCR_SSIZE_Pos; + break; + } + } + + init->DestinationInc = MDMA_CTCR_DINC_1 | (i << MDMA_CTCR_DINCOS_Pos); + init->DestDataSize = i << MDMA_CTCR_DSIZE_Pos; + + // Find the burst size we can break the destination transfer up into. + uint32_t count = MDMA_BUFFER_SIZE >> i; + + for (int i = 7; i >= 0; i--) { + if (!(count % (1 << i))) { + init->DestBurst = i << MDMA_CTCR_DBURST_Pos; + break; + } + } + + break; + } + } + + // YUV422 Source -> Y Destination + if ((csi->pixformat == PIXFORMAT_GRAYSCALE) && (csi->mono_bpp == 2)) { + init->SourceInc = MDMA_SRC_INC_HALFWORD; + init->SourceDataSize = MDMA_SRC_DATASIZE_BYTE; + } +} + +void stm_mdma_start(omv_csi_t *csi, uint32_t src, uint32_t dst, uint32_t line_width, uint32_t line_count) { + // mdma0 will copy this line of the image to the final destination. + __HAL_UNLOCK(&csi->mdma0); + csi->mdma0.State = HAL_MDMA_STATE_READY; + HAL_MDMA_Start(&csi->mdma0, src, dst, line_width, 1); + + // mdma1 will copy all remaining lines of the image to the final destination. + __HAL_UNLOCK(&csi->mdma1); + csi->mdma1.State = HAL_MDMA_STATE_READY; + HAL_MDMA_Start(&csi->mdma1, src, dst + line_width, line_width, line_count - 1); +} + +int omv_csi_dma_memcpy(omv_csi_t *csi, void *dma, void *dst, void *src, int bpp, bool transposed) { + framebuffer_t *fb = csi->fb; + MDMA_HandleTypeDef *handle = dma; + + // Drop the frame if MDMA is not keeping up as the image will be corrupted. + if (handle->Instance->CCR & MDMA_CCR_EN) { + csi->drop_frame = true; + return 0; + } + + // If MDMA is still running, HAL_MDMA_Start() will start a new transfer. + __HAL_UNLOCK(handle); + handle->State = HAL_MDMA_STATE_READY; + HAL_MDMA_Start(handle, + (uint32_t) src, + (uint32_t) dst, + transposed ? bpp : (fb->u * bpp), + transposed ? fb->u : 1); + return 0; +} +#endif // OMV_MDMA_CHANNEL_DCMI_0 + #if defined(GPDMA1) static inline void stm_dma_irq_handler(size_t irqn) { if (dma_handle[irqn] != NULL) { diff --git a/ports/stm32/stm_dma.h b/ports/stm32/stm_dma.h index bf8671a49..81892ae75 100644 --- a/ports/stm32/stm_dma.h +++ b/ports/stm32/stm_dma.h @@ -29,4 +29,17 @@ uint8_t stm_dma_channel_to_irqn(void *dma_channel); uint8_t stm_dma_channel_to_id(void *dma_channel); int stm_dma_set_irq_descr(void *dma_channel, DMA_HandleTypeDef *dma_descr); uint8_t stm_dma_mpu_region_size(uint32_t size); + +#ifdef OMV_MDMA_CHANNEL_DCMI_0 +#include "omv_csi.h" + +#define MDMA_BUFFER_SIZE (64) +#define MDMA_CHAN_TO_INSTANCE(x) \ + (MDMA_Channel_TypeDef *) (MDMA_Channel0_BASE + ((MDMA_Channel1_BASE - MDMA_Channel0_BASE) * x)) + +void stm_mdma_init(omv_csi_t *csi, uint32_t bytes_per_pixel, uint32_t x_crop); +void stm_mdma_init_channel(omv_csi_t *csi, MDMA_InitTypeDef *init, uint32_t bytes_per_pixel, uint32_t x_crop); +void stm_mdma_start(omv_csi_t *csi, uint32_t src, uint32_t dst, uint32_t line_width, uint32_t line_count); +#endif // OMV_MDMA_CHANNEL_DCMI_0 + #endif // __STM_DMA_H__