- Muchas notas - Fran Acién

20260522 - STM32 OV5640 DCMI JPEG

This document explains how to configure the camera OV5640 on STM32H747 using DCMI and JPEG.

CubeMX configuration

For interfacing the camera is necessary to configure:

  • DCMI
  • SDRAM, that is configure using BSP and not with CubeMX
  • I2C
  • DMA
  • GPIO configuration
  • MPU Configuration
  • (Optional) USART 1
  • (Optional) SDMMC
  • (Optional) LittleFS

System architecture

DCMI Configuration

Enable DCMI on Cortex-M7 Domain.

On parameter settings:

  • Pixel clock polarity: OV5640_POLARITY_PCLK_HIGH Configured by the library in stm32-ov5640/ov5640.c:458
  • Vertical synchronization polarity: OV5640_POLARITY_VSYNC_HIGH. Configured by the library stm32-ov5640/ov5640.c:458
  • Horizontal synchronization polarity: OV5640_POLARITY_HREF_HIGH Configured by the library stm32-ov5640/ov5640.c:458
  • Frequency of frame capture: All frames are captured
  • Jpeg mode: Enabled

Nvic settings:

  • DMA2 stream 7 global interrupt: Enabled
  • DCMI global interrupt: Enabled

Gpio settings are automatically configured.

DMA

Configuration:

  • DMA request: DCMI
  • Stream: DMA2 Stream 7
  • Direction: Peripheral To Memory
  • Priority: High
  • Mode: Circular. But depends on the resolution
  • Increment address: Peripheral disabled, Memory enabled
  • Use FIFO: Enabled
  • Threshold: Full
  • Data width: Peripheral word, Memory word
  • Burst size: Peripheral single, Memory 4 increment. Better performance as described in 6.4.5 in STM32 DCMI manual for cameras

I2C Configuration

Enable I2C4 on Cortex-M7 Domain.

Its configured by default.

Paremeter settings:

  • Custom timing: Disabled
  • I2C Speed Mode: Standard Mode
  • I2C Speed Freq (KHz): 100
  • Analog filter: Enabled

GPIO settings:

  • PD12: I2C4_SCL
  • PD13: I2C4_SDA

GPIO Configuration

This is specific for each board. The DCMI module will configure all the gpios, except the DCMI_PWR_EN, that is necessary to configure by hand. In my STM32H757i-Disco the DCMI_PWR_EN is set in the PJ14.

To configure go to GPIO > GPIO > PJ14:

  • Pin context: M7
  • GPIO Output Level: Low
  • GPIO Mode: Output Push Pull
  • GPIO Pull-up/Pull-down: No pull-up and no pull down
  • Maximum output speed: Low
  • User Label: DCMI_PWR_EN

MPU Configuration

We need to add the sdram configuration on the MPU. For this go to Cortex_M7 > Parameter settings > Unit region 1:

  • MPU Region: Enabled
  • MPU Region base address: 0xD0200000
  • MPU Region Size: 32 MB
  • MPU SubRegion Disable: 0x0
  • MPU TEX field level: level 0
  • MPU Access Permission: All access permitted
  • MPU Instruction Access: Enable
  • MPU Shareable permission: Disable
  • MPU Cacheable Permission: Disable
  • MPU Bufferable Permission: Disable

(Optional) USART 1

Enable USART 1 on M7 with the next configurations:

  • Mode: Asynchronous

(Optional) SDMMC

Enable SDMMC for M7 with Mode SD 1 Bit.

(Optional) LittleFS

Enable the FS for accessing the SD card as a fat. Middleware and software packs > FATFS_M7 > Enable for M7 > SD Card

CubeIDE configuration

In the CubeIDE is necessary to import the camera drivers, and the files necessary to enable the SDRAM using the BSP.

Copy your BSP folder from your CubeMX to your CubeIDE. In my linux machines is in ~/STM32Cube/Repository/STM32Cube_FW_H7_V1.13.0/Drivers/BSP/. Copy that folder into your PROJECT_PATH/Drivers/

Open your project on STM32CubeIDE, and configure the next on your CM7 Project:

  • Create a (Logic) Folder in Drivers > BSP > Components. Its necessary to import the source files of the drivers, on that folder you go to Create new file, advanced, Link to file in the file system. And link the next files:
    • PROJECT_PATH/Drivers/BSP/Components/is42s32800j/is42s32800j.c: The SDRAM driver
    • PROJECT_PATH/Drivers/BSP/Components/ov5640/ov5640_reg.c: Part of the camera driver
    • PROJECT_PATH/Drivers/BSP/Components/ov5640/ov5640.c: Part of the camera driver
  • Create a (Logic) Folder in Drivers > BSP > STM32H747I_DISCO. Its necessary to import the source files of the BSP, on that folder you go to Create new file, advanced, Link to file in the file system. And link the next files:
    • PROJECT_PATH/Drivers/BSP/STM32H747I-DISCO/stm32h747i_discovery_sdram.c: The BSP for the sdram
    • PROJECT_PATH/Drivers/BSP/STM32H747I-DISCO/stm32h747i_discovery.c: The BSP source
  • Add the new include paths to your CM7 project:
    • Go to CM7 Project > Properties > C/C++ Build > Settings > MCU/MPU GCC Compiler > Include paths. And add:
      • ../../Drivers/BSP/Components/is42s32800j
      • ../../Drivers/BSP/STM32H747I-DISCO
      • ../../Drivers/BSP/Components/ov5640
  • Copy the file PROJECT_PATH/Drivers/BSP/STM32H747I-DISCO/stm32h747i_eval_conf_template.h into your project in CM7/Core/Inc/stm32h747i_eval_conf.h
  • Copy the file PROJECT_PATH/Drivers/BSP/Components/is42s32800j/is42s32800j_conf_template.h into your project in CM7/Core/Inc/is42s32800j_conf.h
  • Modify your stm32h7xx_hal_conf.h in order to enable SDRAM, by uncommenting #define HAL_SDRAM_MODULE_ENABLED

From main.c:

//#define DUAL_CORE_BOOT_SYNC_SEQUENCE

/* USER CODE BEGIN Private defines */
#define CAMERA_FRAME_BUFFER               0xD0200000
#define QVGA_RES_X      320
#define QVGA_RES_Y      240
/* USER CODE END Private defines */

/* USER CODE START PV */
static volatile uint8_t frame_captured = 0;
static OV5640_Object_t CameraObj;
/* USER CODE END PV */

/* USER CODE BEGIN 0 */
static void UART_Print(const char *str)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)str, (uint16_t)strlen(str), HAL_MAX_DELAY);
}

static int32_t OV5640_IO_Init(void)      { return OV5640_OK; }
static int32_t OV5640_IO_DeInit(void)    { return OV5640_OK; }
static int32_t OV5640_IO_GetTick(void)   { return (int32_t)HAL_GetTick(); }

static int32_t OV5640_IO_WriteReg(uint16_t addr, uint16_t reg,
                                   uint8_t *pData, uint16_t len)
{
  if (HAL_I2C_Mem_Write(&hi2c4, addr, reg, I2C_MEMADD_SIZE_16BIT,
                         pData, len, 1000) != HAL_OK)
    return OV5640_ERROR;
  return OV5640_OK;
}

static int32_t OV5640_IO_ReadReg(uint16_t addr, uint16_t reg,
                                  uint8_t *pData, uint16_t len)
{
  if (HAL_I2C_Mem_Read(&hi2c4, addr, reg, I2C_MEMADD_SIZE_16BIT,
                        pData, len, 1000) != HAL_OK)
    return OV5640_ERROR;
  return OV5640_OK;
}

void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi_cb)
{
  (void)hdcmi_cb;
  frame_captured = 1;
}

static int32_t Camera_Init(void)
{
  OV5640_IO_t  CameraIO;
  uint32_t     camera_id = 0;
  char         buf[256];

  CameraIO.Init      = OV5640_IO_Init;
  CameraIO.DeInit    = OV5640_IO_DeInit;
  CameraIO.Address   = 0x78U;   /* 7-bit 0x3C shifted left */
  CameraIO.WriteReg  = OV5640_IO_WriteReg;
  CameraIO.ReadReg   = OV5640_IO_ReadReg;
  CameraIO.ModifyReg = NULL;
  CameraIO.GetTick   = OV5640_IO_GetTick;

  if (OV5640_RegisterBusIO(&CameraObj, &CameraIO) != OV5640_OK)
    return -1;

  if (OV5640_ReadID(&CameraObj, &camera_id) != OV5640_OK)
    return -1;

  if (camera_id != 0x5640)
    return -1;

  if (OV5640_Init(&CameraObj, OV5640_R320x240, OV5640_JPEG) != OV5640_OK)
    return -1;

  if (OV5640_Start(&CameraObj) != OV5640_OK)
    return -1;

  return 0;
}

static int32_t Camera_CaptureSnapshot(uint32_t *out_jpeg_offset,
                                       uint32_t *out_jpeg_size)
{
  const uint32_t CAPTURE_TIMEOUT_MS = 3000U;

  *out_jpeg_offset = 0;
  *out_jpeg_size   = 0;
  frame_captured   = 0;

  __HAL_DCMI_ENABLE_IT(&hdcmi, DCMI_IT_FRAME);

  /* Allocate the full DMA budget upfront – the actual JPEG size is unknown
   * before compression, so we hand the DMA the worst-case word count for a
   * YUV422 frame at the configured resolution.                              */
  uint32_t dma_size_words = (QVGA_RES_X * QVGA_RES_Y * 2U) / 4U;

  /* Start DCMI in snapshot mode – one frame then stop automatically */
  if (HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_SNAPSHOT,
                          (uint32_t)CAMERA_FRAME_BUFFER,
                          dma_size_words) != HAL_OK)
  {
    UART_Print("[CAM] HAL_DCMI_Start_DMA failed\r\n");
    return -1;
  }

  uint32_t t_start = HAL_GetTick();

  /* Spin until the DCMI frame-complete ISR sets the flag or we time out */
  while (frame_captured == 0)
  {
    if ((HAL_GetTick() - t_start) >= CAPTURE_TIMEOUT_MS)
    {
      HAL_DCMI_Stop(&hdcmi);
      UART_Print("[CAM] Capture timeout\r\n");
      frame_captured = 0;
      return -1;
    }
  }

  uint32_t remaining_words = __HAL_DMA_GET_COUNTER(hdcmi.DMA_Handle);
  HAL_DCMI_Stop(&hdcmi);
  frame_captured = 0;

  /* Total bytes written by DMA into the frame buffer */
  uint32_t dma_bytes = (dma_size_words - remaining_words) * 4U;

  /* --------------------------------------------------------------------- *
   * Locate JPEG SOI marker (0xFF 0xD8) and EOI marker (0xFF 0xD9)         *
   * The OV5640 may prepend padding bytes before the actual JPEG stream.   *
   * --------------------------------------------------------------------- */
  uint8_t *buf_ptr = (uint8_t *)CAMERA_FRAME_BUFFER;
  uint32_t soi_offset = 0;
  uint8_t  soi_found  = 0;

  for (uint32_t i = 0; i + 1U < dma_bytes; i++)
  {
    if (buf_ptr[i] == 0xFFU && buf_ptr[i + 1U] == 0xD8U)
    {
      soi_offset = i;
      soi_found  = 1;
      break;
    }
  }

  if (!soi_found)
  {
    UART_Print("[CAM] JPEG SOI marker (0xFFD8) not found\r\n");
    return -1;
  }

  /* Search for EOI marker (0xFF 0xD9) starting just after SOI */
  uint8_t  eoi_found  = 0;
  uint32_t eoi_offset = 0;

  for (uint32_t i = soi_offset + 2U; i + 1U < dma_bytes; i++)
  {
    if (buf_ptr[i] == 0xFFU && buf_ptr[i + 1U] == 0xD9U)
    {
      /* EOI is the two-byte sequence itself; include both bytes */
      eoi_offset = i + 2U;
      eoi_found  = 1;
      break;
    }
  }

  if (!eoi_found)
  {
    UART_Print("[CAM] JPEG EOI marker (0xFFD9) not found\r\n");
    return -1;
  }

  *out_jpeg_offset = soi_offset;
  *out_jpeg_size   = eoi_offset - soi_offset;

  char dbg[80];
  snprintf(dbg, sizeof(dbg),
           "[CAM] JPEG markers found: offset=%lu size=%lu bytes\r\n",
           (unsigned long)soi_offset, (unsigned long)*out_jpeg_size);
  UART_Print(dbg);

  return 0;


}

static int32_t SD_SaveJPEG(uint32_t jpeg_offset, uint32_t jpeg_size)
{
  static uint32_t file_index = 0;
  char     filename[32];
  FRESULT  res;
  FIL      file;
  UINT     bw;
  char     buf[64];

  if (jpeg_size == 0)
  {
    UART_Print("[SD] Zero bytes to write, skipping\r\n");
    return -1;
  }

  res = f_mount(&SDFatFS, (TCHAR const *)SDPath, 1);
  if (res != FR_OK)
  {
    snprintf(buf, sizeof(buf), "[SD] f_mount failed (%d)\r\n", res);
    UART_Print(buf);
    return -1;
  }

  /* Find the next unused filename */
  do
  {
    file_index++;
    if (file_index > 9999U)
    {
      UART_Print("[SD] File index overflow\r\n");
      f_mount(NULL, (TCHAR const *)SDPath, 0);
      return -1;
    }
    snprintf(filename, sizeof(filename), "IMG_%04lu.JPG", (unsigned long)file_index);
  }
  while (f_stat(filename, NULL) == FR_OK);

  res = f_open(&file, filename, FA_CREATE_NEW | FA_WRITE);
  if (res != FR_OK)
  {
    snprintf(buf, sizeof(buf), "[SD] f_open failed (%d)\r\n", res);
    UART_Print(buf);
    f_mount(NULL, (TCHAR const *)SDPath, 0);
    return -1;
  }

  /* Write only the valid JPEG payload: from SOI (0xFFD8) to EOI (0xFFD9)
   * inclusive, skipping any padding bytes the DMA may have prepended.    */
  const uint8_t *jpeg_start = (const uint8_t *)CAMERA_FRAME_BUFFER + jpeg_offset;
  res = f_write(&file, jpeg_start, jpeg_size, &bw);
  f_close(&file);
  f_mount(NULL, (TCHAR const *)SDPath, 0);

  if (res != FR_OK || bw != jpeg_size)
  {
    snprintf(buf, sizeof(buf), "[SD] Write error (%d), wrote %u/%lu bytes\r\n",
             res, (unsigned)bw, (unsigned long)jpeg_size);
    UART_Print(buf);
    return -1;
  }

  snprintf(buf, sizeof(buf), "[SD] Saved %s (%lu bytes)\r\n",
           filename, (unsigned long)jpeg_size);
  UART_Print(buf);
  return 0;
}


/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
if(BSP_SDRAM_Init(0) != BSP_ERROR_NONE){
    while(1){}
}
Camera_Init();
HAL_Delay(1000);
uint32_t jpeg_offset = 0;
uint32_t jpeg_size   = 0;
if (Camera_CaptureSnapshot(&jpeg_offset, &jpeg_size) == 0)
    SD_SaveJPEG(jpeg_offset, jpeg_size);
/* USER CODE END 2 */

Decoding the video

I use the next command to parse the image:

ffmpeg -f mjpeg -framerate 11 -i VID_0030.MJP -c copy output.avi