diff --git a/src/omv/boards/OPENMV_RT1060/omv_boardconfig.h b/src/omv/boards/OPENMV_RT1060/omv_boardconfig.h index fb59970f4..afb57f306 100644 --- a/src/omv/boards/OPENMV_RT1060/omv_boardconfig.h +++ b/src/omv/boards/OPENMV_RT1060/omv_boardconfig.h @@ -192,7 +192,12 @@ // Camera interface configuration. #define OMV_CSI_BASE (CSI) +#define OMV_CSI_DMA (DMA0) +#define OMV_CSI_DMA_MUX (DMAMUX) +#define OMV_CSI_DMA_CHANNEL_START (4U) +#define OMV_CSI_DMA_CHANNEL_COUNT (2U) #define OMV_CSI_XCLK_FREQUENCY (12000000) +#define OMV_CSI_DMA_MEMCPY_ENABLE (1) #define OMV_CSI_HW_SWAP_ENABLE (1) #define OMV_CSI_D0_PIN (&omv_pin_DCMI_D0) diff --git a/src/omv/ports/mimxrt/sensor.c b/src/omv/ports/mimxrt/sensor.c index 814661e25..811570d80 100644 --- a/src/omv/ports/mimxrt/sensor.c +++ b/src/omv/ports/mimxrt/sensor.c @@ -24,14 +24,21 @@ #include "framebuffer.h" #include "unaligned_memcpy.h" +#define DMA_LENGTH_ALIGNMENT (8) +#define SENSOR_TIMEOUT_MS (3000) + // Sensor struct. sensor_t sensor = {}; extern uint8_t _line_buf[OMV_LINE_BUF_SIZE]; +#if defined(OMV_CSI_DMA) +static edma_handle_t CSI_EDMA_Handle[OMV_CSI_DMA_CHANNEL_COUNT]; +static int src_inc, src_size, dest_inc_size; +#endif + #define CSI_IRQ_FLAGS (CSI_CR1_SOF_INTEN_MASK \ | CSI_CR1_FB2_DMA_DONE_INTEN_MASK \ | CSI_CR1_FB1_DMA_DONE_INTEN_MASK) -//CSI_CR1_RF_OR_INTEN_MASK void sensor_init0() { sensor_abort(true, false); @@ -187,6 +194,39 @@ void sensor_sof_callback() { } } +#if defined(OMV_CSI_DMA) +int sensor_dma_memcpy(void *dma, void *dst, void *src, int bpp, bool transposed) { + // EMDA will not perform burst transfers for anything less than 32-byte chunks made of four 64-bit + // beats. Additionally, the CSI hardware lacks cropping so we cannot align the source address. + // Given this, performance will be lacking on cropped images. So much so that we do not use + // the EDMA for anything less than 4-byte transfers otherwise you get sensor timeout errors. + if (dest_inc_size < 4) { + return -1; + } + + edma_handle_t *handle = dma; + edma_transfer_config_t config; + EDMA_PrepareTransferConfig(&config, + src, // srcAddr + src_size, // srcWidth + src_inc, // srcOffset + dst, // destAddr + transposed ? bpp : dest_inc_size, // destWidth + transposed ? (MAIN_FB()->v * bpp) : dest_inc_size, // destOffset + MAIN_FB()->u * bpp, // bytesEachRequest + MAIN_FB()->u * bpp); // transferBytes + + // Drop the frame if EDMA is not keeping up as the image will be corrupt. + if (EDMA_SubmitTransfer(handle, &config) != kStatus_Success) { + sensor.drop_frame = true; + return 0; + } + + EDMA_TriggerChannelStart(handle->base, handle->channel); + return 0; +} +#endif + void sensor_line_callback(uint32_t addr) { // Throttle frames to match the current frame rate. sensor_throttle_framerate(); @@ -272,8 +312,14 @@ void sensor_line_callback(uint32_t addr) { dst += MAIN_FB()->u * bytes_per_pixel * (buffer->offset - MAIN_FB()->y); } - // Copy line to frame buffer. + #if defined(OMV_CSI_DMA) + // We're using multiple handles to give each channel the maximum amount of time possible to do the line + // transfer. In most situations only one channel will be running at a time. However, if SDRAM is + // backedup we don't have to disable the channel if it is flushing trailing data to SDRAM. + sensor_copy_line(&CSI_EDMA_Handle[buffer->offset % OMV_CSI_DMA_CHANNEL_COUNT], src, dst); + #else sensor_copy_line(NULL, src, dst); + #endif } if (++buffer->offset == resolution[sensor.framesize][1]) { @@ -286,6 +332,46 @@ void sensor_line_callback(uint32_t addr) { } } +#if defined(OMV_CSI_DMA) +static void edma_config(sensor_t *sensor, uint32_t bytes_per_pixel) { + uint32_t line_offset_bytes = MAIN_FB()->x * bytes_per_pixel; + uint32_t line_width_bytes = MAIN_FB()->u * bytes_per_pixel; + + // YUV422 Source -> Y Destination + if ((sensor->pixformat == PIXFORMAT_GRAYSCALE) && (sensor->hw_flags.gs_bpp == 2)) { + line_width_bytes /= 2; + } + + // Destination will be 32-byte aligned. So, we just need to breakup the line width into the largest + // power of 2. Source may have an offset which further limits this to a sub power of 2. + for (int i = 5; i >= 0; i--) { + // 16-byte burst is not supported. + if ((i != 4) && (!(line_width_bytes % (1 << i)))) { + for (int j = i; j >= 0; j--) { + // 16-byte burst is not supported. + if ((j != 4) && (!(line_offset_bytes % (1 << j)))) { + src_inc = src_size = 1 << j; + break; + } + } + + dest_inc_size = 1 << i; + break; + } + } + + if (sensor->transpose) { + dest_inc_size = bytes_per_pixel; + } + + // YUV422 Source -> Y Destination + if ((sensor->pixformat == PIXFORMAT_GRAYSCALE) && (sensor->hw_flags.gs_bpp == 2)) { + src_inc = 2; + src_size = 1; + } +} +#endif + // This is the default snapshot function, which can be replaced in sensor_init functions. int sensor_snapshot(sensor_t *sensor, image_t *image, uint32_t flags) { // Used to restore MAIN_FB's width and height. @@ -315,13 +401,30 @@ int sensor_snapshot(sensor_t *sensor, image_t *image, uint32_t flags) { if (!(CSI->CR18 & CSI_CR18_CSI_ENABLE_MASK)) { framebuffer_setup_buffers(); - vbuffer_t *buffer = framebuffer_get_tail(FB_PEEK); - if (buffer == NULL) { - return SENSOR_ERROR_FRAMEBUFFER_ERROR; - } // Re/configure and re/start the CSI. uint32_t bytes_per_pixel = sensor_get_src_bpp(); uint32_t dma_line_bytes = resolution[sensor->framesize][0] * bytes_per_pixel; + uint32_t length = dma_line_bytes * h; + + // Error out if the transfer size is not compatible with DMA transfer restrictions. + if ((!dma_line_bytes) + || (dma_line_bytes % sizeof(uint64_t)) + || (dma_line_bytes > (OMV_LINE_BUF_SIZE / 2)) + || (!length) + || (length % DMA_LENGTH_ALIGNMENT)) { + return SENSOR_ERROR_INVALID_FRAMESIZE; + } + + #if defined(OMV_CSI_DMA) + // The code below will enable EDMA data transfer from the line buffer for non-JPEG modes. + if (sensor->pixformat != PIXFORMAT_JPEG) { + edma_config(sensor, bytes_per_pixel); + for (int i = 0; i < OMV_CSI_DMA_CHANNEL_COUNT; i++) { + EDMA_CreateHandle(&CSI_EDMA_Handle[i], OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL_START + i); + EDMA_DisableChannelInterrupts(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL_START + i, kEDMA_MajorInterruptEnable); + } + } + #endif if ((sensor->pixformat == PIXFORMAT_RGB565 && sensor->hw_flags.rgb_swap) || (sensor->pixformat == PIXFORMAT_YUV422 && sensor->hw_flags.yuv_swap)) { @@ -353,7 +456,7 @@ int sensor_snapshot(sensor_t *sensor, image_t *image, uint32_t flags) { // Wait for the DMA to finish the transfer. for (mp_uint_t ticks = mp_hal_ticks_ms(); buffer == NULL;) { MICROPY_EVENT_POLL_HOOK - if ((mp_hal_ticks_ms() - ticks) > 3000) { + if ((mp_hal_ticks_ms() - ticks) > SENSOR_TIMEOUT_MS) { sensor_abort(true, false); #if defined(OMV_CSI_FSYNC_PIN)