/* * Copyright (C) 2023-2024 OpenMV, LLC. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Any redistribution, use, or modification in source or binary form * is done solely for personal benefit and not for any commercial * purpose or for monetary gain. For commercial licensing options, * please contact openmv@openmv.io * * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT * OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * STM32 CSI driver. */ #include #include #include #include #include "py/mphal.h" #include "irq.h" #include "omv_boardconfig.h" #include "unaligned_memcpy.h" #include "omv_gpio.h" #include "omv_i2c.h" #include "omv_csi.h" #include "stm_dma.h" #include "stm_isp.h" #include "stm_pwm.h" #if defined(DCMIPP) #define USE_DCMIPP (1) // NOTE using PIPE1. #define DCMIPP_PIPE (DCMIPP_PIPE1) #endif #if defined(PSSI) #define DCMI_IRQn DCMI_PSSI_IRQn #define DCMI_IRQHandler DCMI_PSSI_IRQHandler #define DMA_PRIORITY_HIGH DMA_HIGH_PRIORITY #endif #if defined(OMV_MDMA_CHANNEL_DCMI_0) #define USE_MDMA (1) #endif #ifndef OMV_CSI_DMA_XFER_PORTS #define OMV_CSI_DMA_XFER_PORTS (0) #endif #ifndef OMV_CSI_DMA_MAX_SIZE #define OMV_CSI_DMA_MAX_SIZE (0xFFFFU) #endif #ifndef OMV_CSI_LINE_ALIGNMENT #define OMV_CSI_LINE_ALIGNMENT (16) #endif typedef enum { CSI_HANDLE_DCMI = 0, CSI_HANDLE_DCMIPP = 1, } csi_handle_t; extern uint8_t _line_buf; // Stores the CSI handle associated with DCMI/DCMIPP. static omv_csi_t *stm_csi_all[2] = { 0 }; #if defined(STM32N6) // Nodes can't be placed in CSI state because they need to be uncacheable. static DMA_NodeTypeDef OMV_ATTR_SECTION(dma_nodes[2], ".dma_buffer"); #endif void DCMI_IRQHandler(void) { omv_csi_t *csi = stm_csi_all[CSI_HANDLE_DCMI]; HAL_DCMI_IRQHandler(&csi->dcmi); } #if USE_DCMIPP void CSI_IRQHandler(void) { omv_csi_t *csi = stm_csi_all[CSI_HANDLE_DCMIPP]; HAL_DCMIPP_CSI_IRQHandler(&csi->dcmipp); } void DCMIPP_IRQHandler(void) { omv_csi_t *csi = stm_csi_all[CSI_HANDLE_DCMIPP]; HAL_DCMIPP_IRQHandler(&csi->dcmipp); } #endif #if USE_MDMA void omv_csi_mdma_irq_handler(void) { omv_csi_t *csi = stm_csi_all[CSI_HANDLE_DCMI]; if (MDMA->GISR0 & (1 << OMV_MDMA_CHANNEL_DCMI_0)) { HAL_MDMA_IRQHandler(&csi->mdma0); } if (MDMA->GISR0 & (1 << OMV_MDMA_CHANNEL_DCMI_1)) { HAL_MDMA_IRQHandler(&csi->mdma1); } } #endif static bool stm_csi_is_active(omv_csi_t *csi) { #if USE_DCMIPP if (csi->mipi_if) { return (DCMIPP->P1FCTCR & DCMIPP_P1FCTCR_CPTREQ); } #endif return (DCMI->CR & DCMI_CR_ENABLE); } static int stm_csi_config(omv_csi_t *csi, omv_csi_config_t config) { if (config == OMV_CSI_CONFIG_INIT) { if (!csi->mipi_if) { // Configure and initialize DMA. if (stm_dma_init(&csi->dma, OMV_CSI_DMA_CHANNEL, OMV_CSI_DMA_REQUEST, DMA_PERIPH_TO_MEMORY, 4, 4, OMV_CSI_DMA_XFER_PORTS, &stm_dma_csi_init, true)) { return OMV_CSI_ERROR_DMA_INIT_FAILED; } #if defined(STM32N6) // Initialize DMA in circular mode. if (stm_dma_ll_init(&csi->dma, &csi->dma_queue, dma_nodes, OMV_ARRAY_SIZE(dma_nodes), OMV_CSI_DMA_LIST_PORTS)) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } #endif // Set DMA IRQ handle stm_dma_set_irq_descr(OMV_CSI_DMA_CHANNEL, &csi->dma); // Configure the DMA IRQ Channel csi->dma_irqn = stm_dma_channel_to_irqn(OMV_CSI_DMA_CHANNEL); NVIC_SetPriority(csi->dma_irqn, IRQ_PRI_DMA21); #if USE_MDMA csi->mdma0.Instance = MDMA_CHAN_TO_INSTANCE(OMV_MDMA_CHANNEL_DCMI_0); csi->mdma1.Instance = MDMA_CHAN_TO_INSTANCE(OMV_MDMA_CHANNEL_DCMI_1); #endif csi->dcmi.Instance = DCMI; csi->dcmi.Init.VSPolarity = csi->vsync_pol ? DCMI_VSPOLARITY_HIGH : DCMI_VSPOLARITY_LOW; csi->dcmi.Init.HSPolarity = csi->hsync_pol ? DCMI_HSPOLARITY_HIGH : DCMI_HSPOLARITY_LOW; csi->dcmi.Init.PCKPolarity = csi->pixck_pol ? DCMI_PCKPOLARITY_RISING : DCMI_PCKPOLARITY_FALLING; csi->dcmi.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE; csi->dcmi.Init.CaptureRate = DCMI_CR_ALL_FRAME; csi->dcmi.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B; csi->dcmi.Init.JPEGMode = DCMI_JPEG_DISABLE; // Link the DMA handle to the DCMI handle __HAL_LINKDMA(&csi->dcmi, DMA_Handle, csi->dma); // Initialize the DCMI HAL_DCMI_DeInit(&csi->dcmi); if (HAL_DCMI_Init(&csi->dcmi) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } // Store CSI handle used for DCMI stm_csi_all[CSI_HANDLE_DCMI] = csi; // Configure and enable DCMI IRQ Channel NVIC_SetPriority(DCMI_IRQn, IRQ_PRI_DCMI); HAL_NVIC_EnableIRQ(DCMI_IRQn); } else { #if USE_DCMIPP // Initialize the DCMIPP csi->dcmipp.Instance = DCMIPP; HAL_DCMIPP_DeInit(&csi->dcmipp); if (HAL_DCMIPP_Init(&csi->dcmipp) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } // Select and configure the DCMIPP source. DCMIPP_CSI_ConfTypeDef scfg = { .NumberOfLanes = DCMIPP_CSI_TWO_DATA_LANES, .DataLaneMapping = DCMIPP_CSI_PHYSICAL_DATA_LANES, .PHYBitrate = (csi->mipi_brate == 850) ? DCMIPP_CSI_PHY_BT_850 : DCMIPP_CSI_PHY_BT_1200, }; if (HAL_DCMIPP_CSI_SetConfig(&csi->dcmipp, &scfg) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } // Configure CSI virtual channel and pipe. DCMIPP_CSI_PIPE_ConfTypeDef csi_pcfg = { .DataTypeMode = DCMIPP_DTMODE_DTIDA, .DataTypeIDA = DCMIPP_DT_RAW10, .DataTypeIDB = DCMIPP_DT_RAW10, }; if (HAL_DCMIPP_CSI_SetVCConfig(&csi->dcmipp, DCMIPP_VIRTUAL_CHANNEL0, DCMIPP_CSI_DT_BPP10) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } if (HAL_DCMIPP_CSI_PIPE_SetConfig(&csi->dcmipp, DCMIPP_PIPE, &csi_pcfg) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } // Store CSI handle used for DCMIPP stm_csi_all[CSI_HANDLE_DCMIPP] = csi; // Configure and enable DCMI IRQ Channel NVIC_SetPriority(DCMIPP_IRQn, IRQ_PRI_DCMI); HAL_NVIC_EnableIRQ(DCMIPP_IRQn); // Configure and enable CSI IRQ Channel NVIC_SetPriority(CSI_IRQn, IRQ_PRI_DCMI); HAL_NVIC_EnableIRQ(CSI_IRQn); #endif } } else if (config == OMV_CSI_CONFIG_DEINIT) { if (!csi->mipi_if) { HAL_NVIC_DisableIRQ(DCMI_IRQn); HAL_DCMI_DeInit(&csi->dcmi); } else { #if USE_DCMIPP HAL_NVIC_DisableIRQ(DCMIPP_IRQn); HAL_DCMIPP_DeInit(&csi->dcmipp); #endif } } else if (config == OMV_CSI_CONFIG_PIXFORMAT) { if (!csi->mipi_if) { DCMI->CR &= ~(DCMI_CR_JPEG_Msk << DCMI_CR_JPEG_Pos); DCMI->CR |= (csi->pixformat == PIXFORMAT_JPEG) ? DCMI_JPEG_ENABLE : DCMI_JPEG_DISABLE; #if defined(STM32N6) // Handle YUV422 Source -> Y Destination using DCMI byte drop. if (csi->pixformat == PIXFORMAT_GRAYSCALE && csi->mono_bpp == 2) { DCMI->CR |= DCMI_CR_BSM_0; } else { DCMI->CR &= ~DCMI_CR_BSM_0; } // Turn on/off byte swapping for RGB/YUV formats. for (size_t i = 0; i < OMV_ARRAY_SIZE(dma_nodes); i++) { if ((csi->pixformat == PIXFORMAT_RGB565 && csi->rgb_swap) || (csi->pixformat == PIXFORMAT_YUV422 && csi->yuv_swap)) { dma_nodes[i].LinkRegisters[NODE_CTR1_DEFAULT_OFFSET] |= DMA_CTR1_DBX; } else { dma_nodes[i].LinkRegisters[NODE_CTR1_DEFAULT_OFFSET] &= ~DMA_CTR1_DBX; } } #endif } else { #if USE_DCMIPP csi->dcmipp.State = HAL_DCMIPP_STATE_READY; // Reset pipes states to allow reconfiguring them. for (size_t i = 0; i < DCMIPP_NUM_OF_PIPES; i++) { csi->dcmipp.PipeState[i] = HAL_DCMIPP_PIPE_STATE_RESET; } // Configure the pixel processing pipeline. if (stm_isp_config_pipeline(&csi->dcmipp, DCMIPP_PIPE, csi->pixformat, csi->raw_output)) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } #endif } } return 0; } // Stop the DCMI from generating more DMA requests, and disable the DMA. static int stm_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { if (!stm_csi_is_active(csi)) { return 0; } if (!csi->mipi_if) { DCMI->CR &= ~DCMI_CR_ENABLE; while (DCMI->CR & DCMI_CR_ENABLE) { ; } #if defined(STM32N6) HAL_DMA_Abort(&csi->dma); #else if (in_irq) { HAL_DMA_Abort_IT(&csi->dma); } else { HAL_DMA_Abort(&csi->dma); } #endif HAL_NVIC_DisableIRQ(csi->dma_irqn); #if USE_MDMA if (!in_irq) { HAL_MDMA_Abort(&csi->mdma0); HAL_MDMA_Abort(&csi->mdma1); } HAL_MDMA_DeInit(&csi->mdma0); HAL_MDMA_DeInit(&csi->mdma1); #endif __HAL_DCMI_DISABLE_IT(&csi->dcmi, DCMI_IT_FRAME); __HAL_DCMI_CLEAR_FLAG(&csi->dcmi, DCMI_FLAG_FRAMERI); } else { #if USE_DCMIPP HAL_DCMIPP_CSI_PIPE_Stop(&csi->dcmipp, DCMIPP_PIPE, DCMIPP_VIRTUAL_CHANNEL0); #endif // USE_DCMIPP } return 0; } static int stm_csi_shutdown(omv_csi_t *csi, int enable) { int ret = 0; if (enable) { ret = omv_csi_config(csi, OMV_CSI_CONFIG_DEINIT); } else { ret = omv_csi_config(csi, OMV_CSI_CONFIG_INIT); } return ret; } static uint32_t stm_clk_get_frequency(omv_clk_t *clk) { if (!clk->tim.Instance) { return 0; } return stm_pwm_get_frequency(&clk->tim, OMV_CSI_TIM_CHANNEL); } static int stm_clk_set_frequency(omv_clk_t *clk, uint32_t frequency) { #if (OMV_CSI_CLK_SOURCE == OMV_CSI_CLK_SOURCE_MCO) // Pass through the MCO1 clock with source input set to HSE (12MHz). // Note MCO1 is multiplexed on OPENMV2/TIM1 only. HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_1); #elif (OMV_CSI_CLK_SOURCE == OMV_CSI_CLK_SOURCE_OSC) // An external oscillator is used for the csi clock. // Configure and enable external oscillator if needed. #elif (OMV_CSI_CLK_SOURCE == OMV_CSI_CLK_SOURCE_TIM) if (stm_pwm_start(&clk->tim, OMV_CSI_TIM, OMV_CSI_TIM_CHANNEL, frequency)) { return OMV_CSI_ERROR_TIM_INIT_FAILED; } #else #error "OMV_CSI_CLK_SOURCE is not set!" #endif // (OMV_CSI_CLK_SOURCE == OMV_CSI_CLK_SOURCE_TIM) return 0; } int omv_csi_set_vsync_callback(omv_csi_t *csi, omv_csi_cb_t cb) { if (cb.fun == NULL) { #if (DCMI_VSYNC_EXTI_SHARED == 0) // Disable VSYNC EXTI IRQ omv_gpio_irq_enable(OMV_CSI_VSYNC_PIN, false); #endif } else { // Enable VSYNC EXTI IRQ omv_gpio_irq_register(OMV_CSI_VSYNC_PIN, cb.fun, cb.arg); omv_gpio_irq_enable(OMV_CSI_VSYNC_PIN, true); } return 0; } // If the image is cropped by more than 1 word in width, align the line start to a word // address to improve copy performance. Do not crop by more than 1 word as this will // result in less time between DMA transfers complete interrupts on 16-byte boundaries. static uint32_t get_dcmi_hw_crop(omv_csi_t *csi, uint32_t bytes_per_pixel) { framebuffer_t *fb = csi->fb; uint32_t byte_x_offset = (fb->x * bytes_per_pixel) % 4; uint32_t width_remainder = (csi->resolution[csi->framesize][0] - (fb->x + fb->u)) * bytes_per_pixel; if (byte_x_offset && (width_remainder >= (4 - byte_x_offset))) { return byte_x_offset; } return 0; } static void stm_csi_frame_event(omv_csi_t *csi, uint32_t pipe) { framebuffer_t *fb = csi->fb; #if USE_MDMA // Clear out any stale flags. DMA2->LIFCR = DMA_FLAG_TCIF1_5 | DMA_FLAG_HTIF1_5; // Re-enable the DMA IRQ to catch the next start line. HAL_NVIC_EnableIRQ(csi->dma_irqn); #endif csi->first_line = false; if (csi->drop_frame) { csi->drop_frame = false; // Reset the buffer's state if the frame was dropped. vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); framebuffer_reset(buffer); return; } // Release the buffer from free queue -> used queue. framebuffer_release(fb, FB_FLAG_FREE | FB_FLAG_CHECK_LAST); if (csi->frame_cb.fun) { csi->frame_cb.fun(csi->frame_cb.arg); } #if defined(STM32N6) // Acquire a buffer from the free queue. vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); if (buffer == NULL) { omv_csi_abort(csi, false, false); } else if (csi->mipi_if) { HAL_DCMIPP_PIPE_SetMemoryAddress(&csi->dcmipp, pipe, DCMIPP_MEMORY_ADDRESS_0, (uint32_t) buffer->data); } else if (csi->one_shot) { HAL_DCMI_Stop(&csi->dcmi); HAL_DCMI_Start_DMA(&csi->dcmi, DCMI_MODE_SNAPSHOT, (uint32_t) buffer->data, csi->dma_size); } #endif // STM32N6 } void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) { stm_csi_frame_event(OMV_CONTAINER_OF(hdcmi, omv_csi_t, dcmi), 0); } #if USE_DCMIPP void HAL_DCMIPP_PIPE_FrameEventCallback(DCMIPP_HandleTypeDef *hdcmi, uint32_t pipe) { stm_csi_frame_event(OMV_CONTAINER_OF(hdcmi, omv_csi_t, dcmipp), pipe); } #endif #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) // This function is called after each transfer is complete, // with a pointer to the buffer that was used. void DCMI_DMAConvCpltUser(DCMI_HandleTypeDef *hdcmi, uint32_t addr) { omv_csi_t *csi = OMV_CONTAINER_OF(hdcmi, omv_csi_t, dcmi); framebuffer_t *fb = csi->fb; // Throttle frames to match the current frame rate. omv_csi_throttle_framerate(csi); if (csi->drop_frame) { #if USE_MDMA if (!csi->transpose) { HAL_NVIC_DisableIRQ(csi->dma_irqn); } #endif return; } // Acquire a buffer from the free queue. vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); if (buffer == NULL) { omv_csi_abort(csi, false, true); return; } if (csi->pixformat == PIXFORMAT_JPEG) { if (csi->jpg_format == 3) { // JPEG MODE 3: Variable line width per frame, with the last line // potentially shorter and no padding. `offset` is incremented once // every max transfer, and the DMA counter holds the total size. buffer->offset += 1; } else if (csi->jpg_format == 4) { // JPEG MODE 4: Fixed width and height per frame. Each line starts // with two bytes indicating valid data length, followed by image // data and optional padding (0xFF). `offset` holds the total size. uint16_t size = __REV16(*((uint16_t *) addr)); if (buffer->offset + size > framebuffer_get_buffer_size(fb)) { buffer->flags |= VB_FLAG_OVERFLOW; return; } unaligned_memcpy(buffer->data + buffer->offset, ((uint16_t *) addr) + 1, size); buffer->offset += size; } return; } #if USE_MDMA // DCMI_DMAConvCpltUser is called with the other MAR register. // So, we have to fix the address in full MDMA offload mode. if (!csi->transpose) { addr = (uint32_t) &_line_buf; } #endif uint32_t bytes_per_pixel = omv_csi_get_src_bpp(csi); uint8_t *src = ((uint8_t *) addr) + (fb->x * bytes_per_pixel) - get_dcmi_hw_crop(csi, bytes_per_pixel); uint8_t *dst = buffer->data; if (csi->pixformat == PIXFORMAT_GRAYSCALE) { bytes_per_pixel = sizeof(uint8_t); } #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) { stm_mdma_start(csi, (uint32_t) src, (uint32_t) dst, fb->u * bytes_per_pixel, fb->v); HAL_NVIC_DisableIRQ(csi->dma_irqn); return; } #endif if (!csi->transpose) { dst += fb->u * bytes_per_pixel * buffer->offset++; } else { dst += bytes_per_pixel * buffer->offset++; } #if USE_MDMA // 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); #endif } #endif // #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { vbuffer_t *buffer = NULL; framebuffer_t *fb = csi->fb; // Configure and re/start the capture if it's not alrady active // and there are no pending buffers (from non-blocking capture). if (!stm_csi_is_active(csi) && !framebuffer_readable(fb)) { // Acquire a buffer from the free queue. if (!(buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK))) { return OMV_CSI_ERROR_FRAMEBUFFER_ERROR; } if (csi->mipi_if) { #if USE_DCMIPP uint32_t bytes_per_pixel = omv_csi_get_dst_bpp(csi); uint32_t line_width = fb->u * bytes_per_pixel; if (!line_width || line_width % OMV_CSI_LINE_ALIGNMENT) { return OMV_CSI_ERROR_INVALID_FRAMESIZE; } // Configure crop DCMIPP_CropConfTypeDef ccfg = { .HStart = fb->x, .VStart = fb->y, .HSize = fb->u, .VSize = fb->v, }; if (HAL_DCMIPP_PIPE_SetCropConfig(&csi->dcmipp, DCMIPP_PIPE, &ccfg) != HAL_OK || HAL_DCMIPP_PIPE_EnableCrop(&csi->dcmipp, DCMIPP_PIPE) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } // Set output pitch if (HAL_DCMIPP_PIPE_SetPitch(&csi->dcmipp, DCMIPP_PIPE, line_width) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } // Start the DCMIPP if (HAL_DCMIPP_CSI_PIPE_Start(&csi->dcmipp, DCMIPP_PIPE, DCMIPP_VIRTUAL_CHANNEL0, (uint32_t) buffer->data, DCMIPP_MODE_CONTINUOUS) != HAL_OK) { return OMV_CSI_ERROR_CAPTURE_FAILED; } #endif // USE_DCMIPP } else { // Setup the size and address of the transfer uint32_t bytes_per_pixel = omv_csi_get_src_bpp(csi); uint32_t x_crop = get_dcmi_hw_crop(csi, bytes_per_pixel); uint32_t line_width = csi->resolution[csi->framesize][0] * bytes_per_pixel; // Shrink the captured pixel count by one word to allow cropping to fix alignment. if (x_crop) { line_width -= 4; } csi->dma_size = line_width * fb->v / 4; // Error out if the transfer size is not compatible with DMA transfer restrictions. if ((!line_width) || (line_width % 4) || #if defined(OMV_LINE_BUF_SIZE) (line_width > (OMV_LINE_BUF_SIZE / 2)) || #endif (!csi->dma_size) || ((line_width * fb->v) % OMV_CSI_LINE_ALIGNMENT)) { return OMV_CSI_ERROR_INVALID_FRAMESIZE; } #if defined(STM32N6) // The N6 DCMI driver currently does not support any of these modes. if (csi->pixformat == PIXFORMAT_JPEG || csi->transpose || fb->x != 0 || fb->u != csi->resolution[csi->framesize][0]) { return OMV_CSI_ERROR_CAPTURE_FAILED; } #endif HAL_DCMI_DisableCrop(&csi->dcmi); if (csi->pixformat != PIXFORMAT_JPEG) { // Vertically crop the image. Horizontal cropping is done in software. HAL_DCMI_ConfigCrop(&csi->dcmi, x_crop, fb->y, line_width - 1, fb->v - 1); HAL_DCMI_EnableCrop(&csi->dcmi); } #if USE_MDMA // 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) { stm_mdma_init(csi, bytes_per_pixel, x_crop); } #endif // Reset the DMA state and re-enable it. #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) OMV_CSI_DMA_CHANNEL->CR &= ~(DMA_SxCR_CIRC | DMA_SxCR_CT | DMA_SxCR_DBM); #endif HAL_NVIC_EnableIRQ(csi->dma_irqn); // HAL_DCMI_Start_DMA and HAL_DCMI_Start_DMA_MB both perform circular transfers, // differing only in size, with an interrupt after every half of the transfer. if ((csi->pixformat == PIXFORMAT_JPEG) && (csi->jpg_format == 3)) { // Start a one-shot transfer to the framebuffer, used only for JPEG mode 3. uint32_t size = framebuffer_get_buffer_size(fb) / 4; csi->dma_size = IM_MIN(size, OMV_CSI_DMA_MAX_SIZE * 2U); csi->one_shot = true; HAL_DCMI_Start_DMA(&csi->dcmi, DCMI_MODE_SNAPSHOT, (uint32_t) buffer->data, csi->dma_size); #if USE_MDMA } else if ((csi->pixformat != PIXFORMAT_JPEG) && (!csi->transpose)) { // 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; csi->one_shot = true; HAL_DCMI_Start_DMA(&csi->dcmi, DCMI_MODE_CONTINUOUS, (uint32_t) &_line_buf, line_width / 4); #endif // USE_MDMA } else { #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) // Start a multibuffer (line by line) transfer. HAL_DCMI_Start_DMA_MB(&csi->dcmi, DCMI_MODE_CONTINUOUS, (uint32_t) &_line_buf, csi->dma_size, fb->v); #else // Handle YUV422 Source -> Y Destination using DCMI byte drop. if (csi->pixformat == PIXFORMAT_GRAYSCALE && csi->mono_bpp == 2) { csi->dma_size /= 2; } // Disable circular mode for transfer sizes less than 64KB. if (csi->dma_size * 4 <= OMV_CSI_DMA_MAX_SIZE) { HAL_DMAEx_List_ClearCircularMode(&csi->dma_queue); } else { HAL_DMAEx_List_SetCircularMode(&csi->dma_queue); } csi->one_shot = true; HAL_DCMI_Start_DMA(&csi->dcmi, DCMI_MODE_SNAPSHOT, (uint32_t) buffer->data, csi->dma_size); #endif } } } // In JPEG mode, enable the end of frame interrupt. if (!csi->mipi_if && csi->pixformat == PIXFORMAT_JPEG) { __HAL_DCMI_ENABLE_IT(&csi->dcmi, DCMI_IT_FRAME); } // One shot DMA transfers must be invalidated. framebuffer_flags_t fb_flags = FB_FLAG_USED | FB_FLAG_PEEK | ((csi->one_shot) ? FB_FLAG_INVALIDATE : 0); // Wait for a frame to be ready. for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_event_handle_nowait()) { if ((buffer = framebuffer_acquire(fb, fb_flags))) { break; } if (flags & OMV_CSI_FLAG_NON_BLOCK) { return OMV_CSI_ERROR_WOULD_BLOCK; } if ((mp_hal_ticks_ms() - start) > OMV_CSI_TIMEOUT_MS) { omv_csi_abort(csi, true, false); return OMV_CSI_ERROR_CAPTURE_TIMEOUT; } } // In JPEG 3 mode, the transfer must be aborted as it waits for data indefinitely. if (!csi->mipi_if && (csi->pixformat == PIXFORMAT_JPEG) && (csi->jpg_format == 3)) { omv_csi_abort(csi, false, false); } // The JPEG in the framebuffer is actually invalid. if (buffer->flags & VB_FLAG_OVERFLOW) { return OMV_CSI_ERROR_JPEG_OVERFLOW; } // Set the framebuffer width/height. fb->w = csi->transpose ? fb->v : fb->u; fb->h = csi->transpose ? fb->u : fb->v; // Set the framebuffer pixel format. switch (csi->pixformat) { case PIXFORMAT_GRAYSCALE: fb->pixfmt = PIXFORMAT_GRAYSCALE; break; case PIXFORMAT_RGB565: fb->pixfmt = PIXFORMAT_RGB565; break; case PIXFORMAT_BAYER: fb->pixfmt = PIXFORMAT_BAYER; fb->subfmt_id = csi->cfa_format; fb->pixfmt = imlib_bayer_shift(fb->pixfmt, fb->x, fb->y, csi->transpose); break; case PIXFORMAT_YUV422: { fb->pixfmt = PIXFORMAT_YUV; fb->subfmt_id = csi->yuv_format; fb->pixfmt = imlib_yuv_shift(fb->pixfmt, fb->x); break; } case PIXFORMAT_JPEG: { int32_t size = 0; if (csi->jpg_format == 4) { // Offset is the total frame size. size = buffer->offset; } else { // HAL_DCMI_Start_DMA splits bigger transfers. if (csi->dma_size > OMV_CSI_DMA_MAX_SIZE) { csi->dma_size /= 2; } // Offset is the number of length-size transfers performed. size = buffer->offset * csi->dma_size * 4; // The DMA counter holds the number of bytes per transfer. if (!csi->mipi_if && __HAL_DMA_GET_COUNTER(&csi->dma)) { // Add in the uncompleted transfer length. size += (csi->dma_size - __HAL_DMA_GET_COUNTER(&csi->dma)) * 4; } } // Clean trailing data after 0xFFD9 at the end of the jpeg byte stream. fb->pixfmt = PIXFORMAT_JPEG; fb->size = jpeg_clean_trailing_bytes(size, buffer->data); break; } default: break; } #if USE_DCMIPP if (csi->raw_output) { float luminance = stm_isp_update_awb(csi, DCMIPP_PIPE, fb->u * fb->v); if (csi->ioctl) { omv_csi_ioctl(csi, OMV_CSI_IOCTL_UPDATE_AGC_AEC, fast_floorf(luminance)); } } #endif // Set the user image. framebuffer_to_image(fb, image); return 0; } int omv_csi_ops_init(omv_csi_t *csi) { // Set CSI ops. csi->abort = stm_csi_abort; csi->config = stm_csi_config; csi->shutdown = stm_csi_shutdown; csi->snapshot = stm_csi_snapshot; // Set CSI clock ops. csi->clk->freq = OMV_CSI_CLK_FREQUENCY; csi->clk->set_freq = stm_clk_set_frequency; csi->clk->get_freq = stm_clk_get_frequency; return 0; }