利用 STM32 DCMI 与 DMA 双缓冲实现 OV2640 帧同步采集

一、为什么需要无 CPU 干预采集?

在嵌入式视觉系统中,图像数据量大(如 800x600 分辨率,RGB565 格式,一帧约 960KB),若由 CPU 逐像素搬运,将占用大量主频资源,导致其他任务(如算法处理、通信)无法及时响应。STM32 的 DCMI(Digital Camera Interface)接口专为摄像头设计,配合 DMA 双缓冲机制,可实现数据从摄像头到内存的全硬件搬运,CPU 仅在帧完成中断中切换缓冲区并处理图像,真正实现零拷贝、低延迟采集。

二、硬件原理:DCMI 与 DMA 双缓冲

1. DCMI 接口核心特性

  • 支持 8/10/12/14 位并行数据输入,兼容 OV2640 的 8 位 YCbCr/RGB 输出。
  • 内置同步信号(VSYNC、HSYNC、PIXCLK)检测,可配置极性。
  • 支持帧捕获模式(连续或单帧),通过硬件状态机自动完成帧同步。

2. DMA 双缓冲(乒乓缓冲)

DMA 双缓冲使用两个内存缓冲区(Buffer A 和 Buffer B),当 DMA 正在填充 Buffer A 时,CPU 可处理 Buffer B 中的数据;反之亦然。通过硬件自动切换,避免数据覆盖和等待,实现流水线式处理。

3. 帧同步流程

  • 摄像头输出 VSYNC 信号,DCMI 检测到帧起始。
  • DCMI 将像素数据按行写入 DMA,DMA 将数据搬运至当前缓冲区。
  • 一帧传输完成,DMA 产生传输完成中断,硬件自动切换到另一缓冲区。
  • CPU 在中断中处理已满的缓冲区,同时新数据写入另一缓冲区。

三、CubeMX 配置步骤

1. 初始化 DCMI

  • 在 CubeMX 中启用 DCMI,配置数据线(如 D0-D7)、同步信号(VSYNC、HSYNC、PIXCLK)。
  • 设置像素时钟极性(通常为上升沿采样)、同步信号极性(低有效)。
  • 选择捕获模式为连续模式,数据格式为 RGB565(8 位模式)。

2. 配置 DMA

  • 添加 DMA 请求,选择 DCMI 的 RX 通道。
  • 设置 DMA 模式为循环模式(Circular),数据宽度为半字(16 位),内存地址递增。
  • 启用 DMA 双缓冲模式,指定两个内存缓冲区地址。

3. 生成代码并调整

  • 在生成的 main.c 中,启动 DCMI 和 DMA 传输。
  • 实现 DMA 传输完成中断回调函数,处理帧数据。

四、关键代码实现

1. 缓冲区定义与初始化

#define IMG_WIDTH  800
#define IMG_HEIGHT 600
#define IMG_SIZE   (IMG_WIDTH * IMG_HEIGHT * 2)  // RGB565 每像素 2 字节

uint8_t buffer_a[IMG_SIZE] __attribute__((aligned(32)));
uint8_t buffer_b[IMG_SIZE] __attribute__((aligned(32)));

// 在 main 中初始化
HAL_DCMI_Start_DMA(&hdcmi, DMA_MODE_CIRCULAR, (uint32_t)buffer_a, (uint32_t)buffer_b, IMG_SIZE);

2. DMA 中断回调处理

// 在 stm32f4xx_it.c 中调用 HAL_DCMI_IRQHandler
void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi)
{
    // 判断当前 DMA 使用的缓冲区(通过 DMA 的 NDTR 寄存器或自定义标志)
    if (dma_current_buffer == buffer_a) {
        // 处理 buffer_b 中的图像数据
        process_image(buffer_b, IMG_SIZE);
    } else {
        process_image(buffer_a, IMG_SIZE);
    }
    // 切换标志(注意:DMA 已自动切换,此处仅用于逻辑判断)
    dma_current_buffer = (dma_current_buffer == buffer_a) ? buffer_b : buffer_a;
}

3. 图像处理函数示例(伪代码)

void process_image(uint8_t *buf, uint32_t size)
{
    // 例如:转换为灰度图、边缘检测等
    // 注意:处理时间必须小于一帧传输时间,否则会丢帧
    // 可在此处添加帧计数、时间戳等
}

五、注意事项与常见坑点

  • 缓冲区对齐:DMA 要求缓冲区地址按 4 字节对齐,建议使用 __attribute__((aligned(32)))memalign
  • DMA 循环模式:必须设置为循环模式,否则 DMA 传输一次后停止,无法连续采集。
  • 中断优先级:DCMI 帧中断应设置为高优先级,避免被其他中断阻塞导致丢帧。
  • 处理时间控制:图像处理必须在下一帧传输完成前结束,否则缓冲区会被覆盖。可通过帧率测量或双缓冲状态标志检测。
  • 同步信号极性:OV2640 的 VSYNC/HSYNC 极性可通过寄存器配置,需与 DCMI 设置匹配,否则帧同步失败。
  • 像素时钟频率:确保 PIXCLK 不超过 DCMI 最大频率(如 STM32F4 为 54MHz),否则数据采样错误。
  • 内存带宽:高分辨率高帧率时,DMA 占用总线带宽,可能影响其他外设,需合理规划。

六、总结

通过 DCMI + DMA 双缓冲,STM32 可高效完成图像采集,CPU 负载极低,适用于实时视觉处理、运动检测等场景。本文提供的配置和代码可直接移植到多数 STM32 系列(如 F4/H7),开发者只需根据具体型号调整时钟和引脚即可。掌握该技术,是迈向高性能嵌入式视觉系统的重要一步。