文档
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已提供 |