ports/stm32: Refactor MDMA code.

Moved to separate file to make omv_csi more readable.

Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
This commit is contained in:
iabdalkader 2025-07-03 20:43:16 +02:00
parent 85179136ab
commit 2b4d66ba23
3 changed files with 158 additions and 140 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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__