Embedded HandbookApps
All sections › III. Peripherals
STM32F407, STM32F429, STM32F746 · level intermediate · 16 min read
Open in the interactive handbook

22б. A camera over DCMI: a frame into memory through DMA

Section 22а outputs a frame from memory to the glass. Here is the reverse path: a frame comes from a camera and lands in memory. The camera outputs bytes on the pixel clock, line by line, and does not wait: miss a byte and the frame is spoiled. They are received by the DCMI block, and DMA puts them into memory. The section covers what signals and modes the DCMI has, how much memory a frame takes and what fits where, what data rate goes over the bus and how DMA with a counter of 65535 transfers receives a frame of 153 600 words.

Prerequisites: reference manual RM0090 (F405/F407 and F427/F429, chapter 15 — DCMI, chapter 10 — DMA, section 2.3 — memory) and RM0385 for the F746 (chapter 17 — DCMI, section 2.3). The frame splitting algorithm is from the functions HAL_DCMI_Start_DMA and DCMI_DMAXferCplt of HAL F4 (Keil pack 2.17.1). The board figures are from the board support packages (BSP): STM324x9I-EVAL (OV2640 camera), STM32746G-Discovery (OV9655) and the Keil example for the MCBSTM32F400 (OVM7690). The calculations were done with a script on a PC, the register-level code was built with armclang for the F407 and F746. QEMU does not model DCMI — this was not run with a camera.

Terms used in the documentation

Term Meaning
DCMI (digital camera interface) the parallel camera input
PIXCLK the pixel clock: the camera puts out data on it
HSYNC, VSYNC line and frame sync pulses
snapshot mode one frame, then capture switches itself off
continuous grab mode continuous capture, frame after frame
crop taking a window out of the frame
embedded synchronization sync codes inside the data stream
overrun the FIFO was not emptied in time
double buffer mode DMA alternates between two memory pointers
current target (CT) the bit "where DMA is writing now"

22б.1 Where DCMI exists and how a camera is connected

Not every chip has DCMI: according to the CMSIS headers in the packs it is on the F407/F417, F427/F429, F437/F439, F446, F469/F479 and on the F745/F746, F750/F756, F765/F767/F769, F777/F779. The F401, F405/F415, F410, F411, F412 and F413/F423, and of the F7 the F722/F723 and F730/F732/F733, do not have it. A camera module is a ready board with a sensor, a lens and a controller; the DCMI only receives: RM0090 says plainly that scaling and any other processing are the camera's business.

There are two sets of wires. The parallel bus goes from the camera to the DCMI: data D0–D13 (8, 10, 12 or 14 bits), PIXCLK, HSYNC and VSYNC. 11, 13, 15 or 17 inputs in total; the DCMI is always a slave, the camera sets the clock. The second bus is I²C: over it the program configures the camera — resolution, pixel format, PIXCLK frequency. The configuration is a long "register — value" table from the sensor documentation: in the BSP for the OV9655 in VGA mode it is 147 pairs, for the OV2640 it is 237.

A camera on DCMI: eight data lines, PIXCLK, HSYNC and VSYNC go from the OV9655 camera to the DCMI, which packs four bytes into a word and holds up to four words in the FIFO; on a DCMI request stream 1 of DMA2 on channel 1 moves the words from the DR register at 0x5005 0028 to SDRAM; a 640×480 RGB565 frame lies there in three parts of 51 200 words at 0xC010 0000, 0xC013 2000 and 0xC016 4000, the pointers M0AR and M1AR alternate, and the free one gets the part after next; the camera is configured separately, over I²C


22б.2 Signals, polarities and packing into words

The camera changes data on one edge of PIXCLK, the DCMI latches it on the other — which one is set by the PCKPOL bit. HSYNC and VSYNC divide the stream into lines and frames. The polarities VSPOL and HSPOL are defined in RM0090 like this: they are the level on the pin when data is not valid — that is, the blanking level, not "the level of the active line". Everything that arrives while HSYNC or VSYNC is at this level, the DCMI discards.

This is how the BSP and the Keil example set them:

Board and camera VSPOL HSPOL PCKPOL
STM324x9I-EVAL, OV2640 low low rising
STM32746G-Discovery, OV9655 high low rising
MCBSTM32F400, OVM7690 high low rising

For the OV2640 and OV9655 the BSPs set VSPOL differently: the bit depends on the camera and its configuration, and it is chosen for your camera, not taken from someone else's example.

Instead of HSYNC and VSYNC the camera can insert sync codes of the form 0xFF 00 00 XY into the stream (the ESS bit). This works only on an 8-bit bus, and then the values 0x00 and 0xFF are forbidden in the data.

Packing. On an 8-bit bus the DCMI assembles a word over four PIXCLK cycles, the first byte in the low bits; on 10, 12 and 14 bits over two cycles, one sample in each half of the word. Only when the word is ready does the DCMI ask DMA to take it. An RGB565 pixel is two cycles, there are two pixels in a word.

The frequency limit is not the bus but the clocking: the PIXCLK period must be greater than 2.5 HCLK periods. At an HCLK of 168 MHz this means PIXCLK below 67.2 MHz, at 216 MHz below 86.4 MHz; RM0090 and RM0385 limit the data rate itself to 54 MB/s.


22б.3 Snapshot and continuous capture

Snapshot (CM = 1). After CAPTURE is set the DCMI waits for the start of a frame, receives exactly one frame and clears CAPTURE itself. If an overrun happened on the way, the frame is lost and CAPTURE is cleared too — so after a snapshot the OVR flag is checked.

Continuous capture (CM = 0). Reception starts from the next VSYNC and goes frame after frame until CAPTURE is cleared; the clearing takes effect at the end of the current frame. The buffer, and whether it is linear or circular, is set by DMA, not by the DCMI. The FCRC field drops frames: all, every second one or one in four.

There are five interrupts: end of line (LINE), end of frame or window (FRAME), overrun (OVR), VSYNC and sync code error (ERR). The DCMI FIFO holds four words, and it has no overrun protection: if DMA has not taken a word in time, the FIFO is reset, and the DCMI waits for the next frame.


22б.4 The CROP window, JPEG and decimation on the F7

The window. DCMI_CWSTRT sets the start of the window: VST is the line number, HOFFCNT is how many PIXCLK cycles to skip in a line. DCMI_CWSIZE is the size: VLINE is lines, CAPCNT is PIXCLK cycles, both "minus one". An RGB565 pixel takes two cycles, and that is the main trap. The window width in cycles must add up to whole words: in the description of the window RM0090 writes that the two low bits of CAPCNT are cleared, and in the register description that CAPCNT is "minus one". There is no contradiction if CAPCNT + 1 is a multiple of four, as in the BSP below: 959 + 1 = 960.

The STM32746G-Discovery has a 480×272 screen, and the OV9655 cannot do that mode. The BSP switches the camera to VGA and cuts out a window: VST 104, HOFFCNT 80, CAPCNT 959 — that is 480 × 2 − 1 — VLINE 271. In registers this is CWSTRT 0x0068 0050 and CWSIZE 0x010F 03BF. The window is 261 120 bytes, 0xFF00 words, as in GetSize of the BSP for 480×272: one DMA transfer. Vertically the window is centred. Horizontally the BSP passes half the difference of the widths in pixels, while the field counts cycles: 80 cycles are 40 pixels. The window starts at pixel 40, and on the right 120 remain; for the centre HOFFCNT 160 is needed.

JPEG (the JPEG bit). The camera compresses the frame itself, VSYNC marks the start and end of the picture, HSYNC serves as a "data valid" signal. There is no window and no sync codes in this mode. The frame size is not known in advance: the buffer is taken with a margin, and how much arrived is computed from NDTR — it decreases with each transfer. If there were not enough bytes at the end to fill a word, the DCMI pads it with zeros. For uncompressed formats the limit is 2048 × 2048 pixels, for JPEG there is no limit.

Decimation on the F7. The F746 has fields in DCMI_CR that the F4 lacks. BSM takes every second byte, one in four or two in four, OEBS selects which one to start with; BSM works only on an 8-bit bus. From YCbCr 4:2:2 only the luma Y remains this way — a frame half the size. LSM and OELS take every second line.


22б.5 How much memory and what data rate

The memory DMA can write in one piece. On the F407 this is SRAM1 and SRAM2 — 128 KB: the 64 KB of CCM memory are not connected to the bus matrix (section 9). On the F429 SRAM3 is added to them — 192 KB. On the F746 it is 320 KB: DTCM, SRAM1 and SRAM2; DMA reaches DTCM through the core's AHBS slave bus (section 11б).

Frame Bytes DMA words F407, 128 KB F429, 192 KB F746, 320 KB
160×120 RGB565 38 400 9 600 yes yes yes
320×240 RGB565 153 600 38 400 no yes yes
480×272 RGB565 261 120 65 280 no no yes
640×480 RGB565 614 400 153 600 no no no
640×480 Y8 307 200 76 800 no no yes

The word counts match GetSize in the BSP: 0x2580, 0x9600, 0xFF00 and 0x25800. A 320×240 frame does not fit into anything on the F407. The Keil example for the MCBSTM32F400 does without a buffer: DMA with address increment switched off writes the DCMI words directly to the data register of an 8080 display at 0x6C00 0002 (sections 22а and 41б). Through the stream FIFO each word goes out as two 16-bit writes — two pixels. VGA in 16 bits fits nowhere except SDRAM: on the 746G-Discovery that is 8 MB from 0xC000 0000.

The data rate. Suppose the camera outputs a PIXCLK of 24 MHz on an 8-bit bus — this is an example, the frequency is set by the camera configuration. Within a line this is 24 MB/s, or six million words per second: one word every 28 HCLK cycles at 168 MHz and every 36 cycles at 216 MHz. The margin is the FIFO: four words are 16 PIXCLK cycles, 0.667 µs, or 112 cycles of a 168 MHz HCLK. That is how long DMA can wait for the bus while others hold it: LTDC, DMA2D, the processor. At the limit of 54 MB/s the margin is 0.296 µs. That is why the BSP gives the camera stream a high priority.

The pure data time of a frame at 24 MHz: 320×240 RGB565 — 6.4 ms, 640×480 — 25.6 ms. Even without blanking, VGA at this frequency gives no more than 39 frames per second.


22б.6 DMA: 65535 words and a double buffer

The DCMI request goes to DMA2, channel 1, stream 1 or 7 (RM0090, table 43; the same in RM0385). The direction is peripheral → memory, both widths are word, address increment only on the memory side. The BSPs of both boards use stream 1 in circular mode and FIFO direct mode.

The NDTR counter is 16-bit: no more than 65535 transfers per setup (section 9). A 640×480 RGB565 frame is 153 600 words. HAL gets around this with a double buffer: if the length is greater than 0xFFFF, HAL_DCMI_Start_DMA halves it until the part is no greater than 0xFFFF, and starts the stream with two pointers M0AR and M1AR. In double-buffer mode DMA itself switches the pointer at the end of each part and continues, and the handler DCMI_DMAXferCplt moves the free pointer two parts ahead. A model of this handler on a PC:

Frame Words Parts Words each Lost
640×480 RGB565 153 600 4 38 400 0
640×480 Y8 76 800 2 38 400 0
800×600 RGB565 240 000 4 60 000 0
window 330×399 RGB565 65 835 2 32 917 1

For VGA the model goes through parts 0, 1, 2, 3, 0, 1, 2, 3 — two frames in a row, as it should. But the division is integer: if the length does not divide by the number of parts, HAL simply loses the remainder, and DMA finishes the "frame" before the camera's frame ends.

At register level the frame can be split into any number of parts, as long as it divides exactly. The functions camera_start and the interrupt handler of the example file split VGA into three parts of 51 200 words:

#define CAM_FB      0xC0100000u         /* с 0xC000 0000 — буфер LTDC платы */
#define VGA_WORDS   FRAME_WORDS(640u, 480u, 2u)
#define PARTS       3u
#define PART        (VGA_WORDS / PARTS)
_Static_assert(VGA_WORDS == 153600u && VGA_WORDS % PARTS == 0u && PART <= 65535u,
               "кадр делится на части без остатка, часть — не больше 65535 слов");

static volatile uint32_t next_part;

void camera_start(void)
{
    dcmi_clocks();
    next_part = 2u;
    dma_dcmi(CAM_FB, CAM_FB + 4u * PART, PART, DMA_SxCR_DBM | DMA_SxCR_TCIE);
    NVIC_EnableIRQ(DMA2_Stream1_IRQn);
    dcmi_on(CR_POL);                    /* CM = 0: кадр за кадром */
    DCMI->CR |= DCMI_CR_CAPTURE;
}

void DMA2_Stream1_IRQHandler(void)      /* часть дописана: свободному указателю — часть через одну */
{
    uint32_t a = CAM_FB + 4u * PART * next_part;

    DMA2->LIFCR = DMA_LIFCR_CTCIF1;
    if (DMA2_Stream1->CR & DMA_SxCR_CT)
        DMA2_Stream1->M0AR = a;         /* DMA пишет по M1 — свободен M0 */
    else
        DMA2_Stream1->M1AR = a;
    next_part = (next_part + 1u) % PARTS;
}

M0AR may be written only while CT = 1, and M1AR only while CT = 0; otherwise RM0090 promises the TEIF error flag and the stream being disabled. Hence the CT check. The parts in the model go 0, 1, 2, 0, 1, 2 — an odd number of parts does not matter: frames simply start alternately with M0 and M1. armclang for the F746 turns the choice of pointer into a single conditional instruction without a branch:

LDR    r3,[r2,#0x20]       ; DMA2_Stream1->CR, r2 — адрес DMA2->LIFCR
MOV    r0,#0x32000         ; часть: 51 200 слов по 4 байта
LSLS   r3,r3,#12           ; бит 19 (CT) — в знак
MOV    r3,#0x2c            ; смещение M0AR от LIFCR
IT     PL
MOVPL  r3,#0x30            ; CT = 0 — M1AR
MOVT   r12,#0xc010         ; CAM_FB = 0xC010 0000
MLA    r0,r1,r0,r12        ; CAM_FB + next_part × 0x32000
STR    r0,[r3,r2]

The handler has the time of a whole part to change the pointer: 51 200 words at a PIXCLK of 24 MHz are 8.533 ms. The frame lies from 0xC010 0000 to 0xC019 6000: the start of SDRAM on the 746G-Discovery is taken by the LTDC buffer (LCD_FB_START_ADDRESS 0xC000 0000; 480×272 in 32 bits is 0x7F800 bytes).

For the F407 the same file has a 160×120 snapshot into internal SRAM in one transfer, the function camera_snapshot. It shows what the fields add up to: NDTR 0x2580, SxCR 0x0202 5400 (channel 1, high priority, words, memory increment), DCMI_CR 0x40A2 (ENABLE, VSPOL, PCKPOL, CM).


22б.7 Pitfalls


22б.8 Summary

Full sources

dcmi22b.c — DCMI at register level: a snapshot on the F407 and continuous capture on the F746.

/* 22б: DCMI по регистрам — снимок QQVGA во внутреннюю SRAM на F407
   и непрерывный VGA в SDRAM на F746 с двойным буфером DMA.
   Собирается armclang: -DSTM32F407xx и -DSTM32F746xx.
   Выводы (GPIO, AF13) и настройка камеры по I²C — вне этого файла. */
#include <stddef.h>
#include <stdint.h>

/* поля DCMI и DMA — RM0090, 15.8 и 10.5; у RM0385 для F7 они те же */
_Static_assert(DCMI_BASE == 0x50050000u && offsetof(DCMI_TypeDef, DR) == 0x28u, "DCMI на AHB2, DR по 0x50050028");
_Static_assert(DCMI_CR_CAPTURE == (1u << 0) && DCMI_CR_CM == (1u << 1) && DCMI_CR_CROP == (1u << 2)
               && DCMI_CR_JPEG == (1u << 3), "CAPTURE, CM, CROP, JPEG — биты 0…3");
_Static_assert(DCMI_CR_PCKPOL == (1u << 5) && DCMI_CR_HSPOL == (1u << 6) && DCMI_CR_VSPOL == (1u << 7),
               "полярности PCKPOL, HSPOL, VSPOL — биты 5, 6, 7");
_Static_assert(DCMI_CR_ENABLE == (1u << 14) && DCMI_CWSTRT_VST_Pos == 16u && DCMI_CWSIZE_VLINE_Pos == 16u,
               "ENABLE — бит 14; строки окна — в старшей половине CWSTRT и CWSIZE");
_Static_assert(DMA_SxCR_CHSEL_Pos == 25u && DMA_SxCR_DBM == (1u << 18) && DMA_SxCR_CT == (1u << 19),
               "канал в битах 27:25, DBM — бит 18, CT — бит 19");

#define FRAME_WORDS(w, h, bytes_per_px) ((w) * (h) * (bytes_per_px) / 4u)

/* VSYNC активен высоким, HSYNC — низким, захват по фронту PIXCLK:
   так у STM32746G-Discovery (OV9655) и у MCBSTM32F400 (OVM7690) */
#define CR_POL (DCMI_CR_PCKPOL | DCMI_CR_VSPOL)

static void dcmi_clocks(void)          /* такты — раньше любой записи в DMA2 и DCMI */
{
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
    RCC->AHB2ENR |= RCC_AHB2ENR_DCMIEN;
    (void)RCC->AHB2ENR;
}

static void dcmi_on(uint32_t cr)
{
    DCMI->CR = cr;                      /* всё настроить до ENABLE */
    DCMI->CR = cr | DCMI_CR_ENABLE;
}

/* DMA2, поток 1, канал 1 — запрос DCMI (RM0090, таблица 43) */
static void dma_dcmi(uint32_t m0, uint32_t m1, uint32_t items, uint32_t extra)
{
    DMA_Stream_TypeDef *s = DMA2_Stream1;

    s->CR = 0;
    while (s->CR & DMA_SxCR_EN) {
    }
    DMA2->LIFCR = DMA_LIFCR_CTCIF1 | DMA_LIFCR_CHTIF1 | DMA_LIFCR_CTEIF1 | DMA_LIFCR_CDMEIF1 | DMA_LIFCR_CFEIF1;
    s->PAR  = (uint32_t)&DCMI->DR;
    s->M0AR = m0;
    s->M1AR = m1;
    s->NDTR = items;                    /* слов, не больше 65535 */
    s->FCR  = 0;                        /* прямой режим, как в BSP */
    s->CR   = (1u << DMA_SxCR_CHSEL_Pos) | DMA_SxCR_PL_1 | DMA_SxCR_MSIZE_1 | DMA_SxCR_PSIZE_1
            | DMA_SxCR_MINC | extra;    /* DIR = 00: периферия → память, слова */
    s->CR  |= DMA_SxCR_EN;
}

#if defined(STM32F407xx)
/* ── снимок 160×120 RGB565: 38 400 байт — во внутреннюю SRAM, одной передачей ── */
#define QQVGA_WORDS FRAME_WORDS(160u, 120u, 2u)
_Static_assert(QQVGA_WORDS == 9600u && QQVGA_WORDS <= 65535u, "QQVGA — 9600 слов, одна передача DMA");

static uint32_t qqvga[QQVGA_WORDS];

int camera_snapshot(void)               /* 0 — кадр в qqvga, -1 — переполнение FIFO */
{
    dcmi_clocks();
    DCMI->ICR = DCMI_ICR_FRAME_ISC | DCMI_ICR_OVR_ISC;
    dma_dcmi((uint32_t)qqvga, 0u, QQVGA_WORDS, 0u);
    dcmi_on(CR_POL | DCMI_CR_CM);       /* CM = 1: один кадр */
    DCMI->CR |= DCMI_CR_CAPTURE;        /* ждёт начала кадра */
    while (DCMI->CR & DCMI_CR_CAPTURE) {
    }                                   /* в конце кадра бит снимается сам */
    return (DCMI->RISR & DCMI_RIS_OVR_RIS) ? -1 : 0;
}

#elif defined(STM32F746xx)
/* ── непрерывно 640×480 RGB565: 614 400 байт — только в SDRAM (0xC000 0000, 8 МБ) ── */
#define CAM_FB      0xC0100000u         /* с 0xC000 0000 — буфер LTDC платы */
#define VGA_WORDS   FRAME_WORDS(640u, 480u, 2u)
#define PARTS       3u
#define PART        (VGA_WORDS / PARTS)
_Static_assert(VGA_WORDS == 153600u && VGA_WORDS % PARTS == 0u && PART <= 65535u,
               "кадр делится на части без остатка, часть — не больше 65535 слов");

static volatile uint32_t next_part;

void camera_start(void)
{
    dcmi_clocks();
    next_part = 2u;
    dma_dcmi(CAM_FB, CAM_FB + 4u * PART, PART, DMA_SxCR_DBM | DMA_SxCR_TCIE);
    NVIC_EnableIRQ(DMA2_Stream1_IRQn);
    dcmi_on(CR_POL);                    /* CM = 0: кадр за кадром */
    DCMI->CR |= DCMI_CR_CAPTURE;
}

void DMA2_Stream1_IRQHandler(void)      /* часть дописана: свободному указателю — часть через одну */
{
    uint32_t a = CAM_FB + 4u * PART * next_part;

    DMA2->LIFCR = DMA_LIFCR_CTCIF1;
    if (DMA2_Stream1->CR & DMA_SxCR_CT)
        DMA2_Stream1->M0AR = a;         /* DMA пишет по M1 — свободен M0 */
    else
        DMA2_Stream1->M1AR = a;
    next_part = (next_part + 1u) % PARTS;
}
#endif

Next: section 22в "Segment LCD: the glass, multiplexing and the LCD controller" — a display that draws almost no current: multiplexing, bias and the built-in LCD controller.

Open in the interactive handbook