OV2640 外部时钟版本驱动代码例程

知识库
知识库文档
/firmware/传感器/OV2640 外部时钟版本/OV2640 外部时钟版本驱动代码例程.md

文档

OV2640 外部时钟版本驱动代码例程

一、平台说明

  • 主控平台:FPGA (Xilinx/Intel) + 协处理MCU / STM32F407
  • 时钟方案:外部精准时钟直驱 XVCLK(PLL旁路)
  • 开发环境:Vivado + Vitis / STM32CubeIDE

二、SCCB 基础驱动

#include "stm32f4xx_hal.h"

#define OV2640_I2C_ADDR  0x60

I2C_HandleTypeDef hi2c1;

void OV2640_I2C_Init(void)
{
    __HAL_RCC_I2C1_CLK_ENABLE();
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 100000;
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    HAL_I2C_Init(&hi2c1);
}

uint8_t OV2640_WriteReg(uint8_t reg, uint8_t data)
{
    uint8_t buf[2] = {reg, data};
    return HAL_I2C_Master_Transmit(&hi2c1, OV2640_I2C_ADDR, buf, 2, 100);
}

void OV2640_SetPage(uint8_t page)
{
    OV2640_WriteReg(0xFF, page);
}

三、外部时钟版本核心:XVCLK生成

STM32 定时器方案

// =============================================
// 外部时钟版本 XVCLK 由 TIM1 产生
// 系统时钟168MHz → 产生48MHz XVCLK
// =============================================
TIM_HandleTypeDef htim1;

void XVCLK_ExtClk_48MHz_Init(void)
{
    __HAL_RCC_TIM1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_8;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 0;
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim1.Init.Period = 2;              // 168/(2+1)=56MHz(≈48MHz可用)
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_PWM_Init(&htim1);

    TIM_OC_InitTypeDef sConfigOC = {0};
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 1;                // 50%占空比
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);

    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
    printf("XVCLK=56MHz (TIM1) 已启动 [外部时钟版本]\r\n");
}

FPGA Verilog 方案(推荐)

//==========================================
// ov2640_xclk_gen.v
// FPGA MMCM → 48MHz 精准 XVCLK
//==========================================
module ov2640_xclk_gen (
    input  wire       clk_100mhz,
    input  wire       rst_n,
    output wire       ov2640_xvclk,
    output wire       mmcm_locked
);

    // MMCM IP核配置:100MHz → 48MHz
    clk_wiz_0 mmcm_inst (
        .clk_in1    (clk_100mhz),
        .clk_out1   (ov2640_xvclk),   // 48MHz, 50% duty
        .reset      (~rst_n),
        .locked     (mmcm_locked)
    );

endmodule

XDC 约束:

set_property PACKAGE_PIN K17 [get_ports ov2640_xvclk]
set_property IOSTANDARD LVCMOS33 [get_ports ov2640_xvclk]
set_property SLEW FAST [get_ports ov2640_xvclk]

四、外部时钟版本初始化序列

// =============================================
// OV2640 外部时钟版本 — 硬件初始化
// 关键:PLL旁路,XVCLK直驱系统时钟
// =============================================
void OV2640_ExtClk_HardwareInit(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_8;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    // 上电时序
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);  // PWDN=0
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);  // RESET=0
    HAL_Delay(5);

    // ⚠ 先启动XVCLK
    XVCLK_ExtClk_48MHz_Init();
    HAL_Delay(1);

    // 释放PWDN(外部版本仅需1ms)
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
    HAL_Delay(1);  // 外部版本:1ms足够(内部版本需5ms)

    // 释放RESET
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_Delay(20);
}

// =============================================
// OV2640 外部时钟版本 — 寄存器初始化
// ⚠ PLL全部旁路!
// =============================================
void OV2640_ExtClk_RegInit(void)
{
    printf("OV2640 外部时钟版本 寄存器初始化...\r\n");

    // ---- 软件复位 ----
    OV2640_SetPage(0x00);
    OV2640_WriteReg(0x12, 0x80);
    HAL_Delay(100);

    // ---- ⚠ 核心:时钟控制 = 外部时钟直通 ----
    OV2640_WriteReg(0x11, 0x00);  // 时钟控制1: PLL禁用! bit[0]=0
    // 对比内部版本: 0x11=0x01 (PLL使能)

    OV2640_WriteReg(0x2D, 0x03);  // 时钟分频: PLL旁路, 预分频=3
    // bit[6]=0: PLL不使能
    // bit[5:0]=000011: 分频=3
    // SCLK = XVCLK / 分频 = 48MHz / 3 = 16MHz

    // ---- 输出格式 JPEG + UXGA ----
    OV2640_WriteReg(0x12, 0x40 | 0x20);  // COM7: UXGA + JPEG

    // ---- 分辨率窗口 ----
    OV2640_WriteReg(0x17, 0x00); OV2640_WriteReg(0x18, 0x00);
    OV2640_WriteReg(0x19, 0x06); OV2640_WriteReg(0x1A, 0x40);
    OV2640_WriteReg(0x03, 0x00); OV2640_WriteReg(0x04, 0x00);
    OV2640_WriteReg(0x05, 0x04); OV2640_WriteReg(0x06, 0xB0);
    OV2640_WriteReg(0x32, 0x06); OV2640_WriteReg(0x33, 0x40);

    // ---- 输出窗口 ----
    OV2640_WriteReg(0x51, 0x06); OV2640_WriteReg(0x52, 0x40);
    OV2640_WriteReg(0x53, 0x04); OV2640_WriteReg(0x54, 0xB0);

    // ---- JPEG 质量(页1)----
    OV2640_SetPage(0x01);
    OV2640_WriteReg(0x30, 0x80);

    // ---- 自动控制(页0)----
    OV2640_SetPage(0x00);
    OV2640_WriteReg(0x13, 0x07);  // AEC/AGC/AWB 使能
    OV2640_WriteReg(0x7C, 0x00);  // 亮度
    OV2640_WriteReg(0x7D, 0x20);  // 对比度

    printf("OV2640 外部时钟版本初始化完成 (SCLK=XVCLK/3)\r\n");
}

五、完整采集示例

#define FRAME_BUF_SIZE  (200 * 1024)
uint8_t frame_buffer[FRAME_BUF_SIZE];
volatile uint32_t frame_len = 0;
volatile uint8_t frame_done = 0;

DCMI_HandleTypeDef hdcmi;
DMA_HandleTypeDef hdma_dcmi;

void DCMI_JPEG_Init(void)
{
    __HAL_RCC_DCMI_CLK_ENABLE();
    __HAL_RCC_DMA2_CLK_ENABLE();

    hdcmi.Instance = DCMI;
    hdcmi.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE;
    hdcmi.Init.PCKPolarity = DCMI_PCKPOLARITY_FALLING;
    hdcmi.Init.VSPolarity = DCMI_VSPOLARITY_LOW;
    hdcmi.Init.HSPolarity = DCMI_HSPOLARITY_LOW;
    hdcmi.Init.CaptureRate = DCMI_CR_ALL_FRAME;
    hdcmi.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B;
    hdcmi.Init.JPEGMode = DCMI_JPEG_ENABLE;
    HAL_DCMI_Init(&hdcmi);

    hdma_dcmi.Instance = DMA2_Stream1;
    hdma_dcmi.Init.Channel = DMA_CHANNEL_1;
    hdma_dcmi.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_dcmi.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_dcmi.Init.MemInc = DMA_MINC_ENABLE;
    hdma_dcmi.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_dcmi.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_dcmi.Init.Mode = DMA_NORMAL;
    hdma_dcmi.Init.Priority = DMA_PRIORITY_VERY_HIGH;
    HAL_DMA_Init(&hdma_dcmi);
    __HAL_LINKDMA(&hdcmi, DMA_Handle, hdma_dcmi);
}

void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi)
{
    frame_len = FRAME_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_dcmi);
    frame_done = 1;
    HAL_DCMI_Stop(hdcmi);
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    UART_Init();

    printf("=== OV2640 外部时钟版本 演示 ===\r\n");

    // 1. I2C初始化
    OV2640_I2C_Init();

    // 2. 硬件上电 + XVCLK
    OV2640_ExtClk_HardwareInit();

    // 3. 寄存器初始化(PLL旁路)
    OV2640_ExtClk_RegInit();

    // 4. DCMI初始化
    DCMI_JPEG_Init();

    printf("OV2640 外部时钟版本就绪, 开始采集...\r\n");

    uint32_t fps_count = 0;
    uint32_t last_time = HAL_GetTick();

    while (1)
    {
        frame_done = 0;
        frame_len = 0;

        HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_SNAPSHOT,
                           (uint32_t)frame_buffer, FRAME_BUF_SIZE / 4);

        uint32_t timeout = 2000;
        while (!frame_done && timeout-- > 0) HAL_Delay(1);

        if (frame_done && frame_len > 0)
        {
            fps_count++;
            printf("帧 #%lu: %lu字节 JPEG\r\n", fps_count, frame_len);
        }
        else
        {
            printf("帧超时!\r\n");
        }

        // 每秒统计
        if (HAL_GetTick() - last_time >= 1000)
        {
            printf("FPS: %lu\r\n", fps_count);
            fps_count = 0;
            last_time = HAL_GetTick();
        }
    }
}

六、XVCLK频率与帧率对照表参考

// 外部时钟版本:帧率由XVCLK决定
typedef struct {
    uint32_t xvclk_hz;
    uint8_t  pclk_div;    // 0x2D分频系数
    uint32_t pclk_hz;
    float    fps_uxga;    // 1600x1200
    float    fps_svga;    // 800x600
    float    fps_qvga;    // 320x240
} OV2640_ClkConfig;

const OV2640_ClkConfig clk_table[] = {
    // XVCLK      DIV  PCLK      UXGA    SVGA    QVGA
    { 12000000,   1,   12000000, 3.8,    13.2,   52.8 },
    { 24000000,   3,    8000000, 2.5,    8.8,    35.2 },
    { 48000000,   3,   16000000, 5.1,    17.6,   70.4 },
    { 56000000,   4,   14000000, 4.4,    15.4,   61.6 },
    { 72000000,   4,   18000000, 5.7,    19.8,   79.2 },
    { 96000000,   5,   19200000, 6.1,    21.1,   84.5 },
};

void OV2640_ExtClk_SelectConfig(int idx)
{
    XVCLK_SetFrequency(clk_table[idx].xvclk_hz);
    OV2640_SetPage(0x00);
    OV2640_WriteReg(0x2D, clk_table[idx].pclk_div);  // PLL旁路+分频
    printf("配置: XVCLK=%luHz, PCLK=%luHz, UXGA≈%.1ffps\r\n",
           clk_table[idx].xvclk_hz,
           clk_table[idx].pclk_hz,
           clk_table[idx].fps_uxga);
}

七、常见问题

问题 解决方案
图像噪点严重 XVCLK抖动过大,改用FPGA MMCM/有源晶振
帧率不对 XVCLK频率不准,用频率计校准
无图像 检查0x11 bit[0]=0, 0x2D bit[6]=0
数据错位 XVCLK占空比超出45-55%,调整PWM
SCCB无应答 确认I2C地址0x60,XVCLK已提供

信息

路径
/firmware/传感器/OV2640 外部时钟版本/OV2640 外部时钟版本驱动代码例程.md
更新时间
2026/5/26