文档
OV5640 外部时钟版本驱动代码例程
一、平台说明
- 主控平台:FPGA (Xilinx Zynq/Artix-7) + STM32 协处理 / 纯STM32
- 时钟方案:外部精准时钟源直驱 XVCLK(无PLL)
- 开发环境:STM32CubeIDE / Vivado + Vitis
二、外部时钟版本 SCCB 驱动
#include "stm32f4xx_hal.h"
#define OV5640_I2C_ADDR 0x78
#define OV5640_CHIP_ID 0x5640
I2C_HandleTypeDef hi2c1;
extern TIM_HandleTypeDef htim1; // 用于产生XVCLK的定时器
// SCCB 写寄存器
uint8_t OV5640_WriteReg(uint16_t reg, uint8_t data)
{
uint8_t buf[3];
buf[0] = (uint8_t)(reg >> 8);
buf[1] = (uint8_t)(reg & 0xFF);
buf[2] = data;
return HAL_I2C_Master_Transmit(&hi2c1, OV5640_I2C_ADDR, buf, 3, 100);
}
// SCCB 读寄存器
uint8_t OV5640_ReadReg(uint16_t reg)
{
uint8_t addr[2], data = 0;
addr[0] = (uint8_t)(reg >> 8);
addr[1] = (uint8_t)(reg & 0xFF);
HAL_I2C_Master_Transmit(&hi2c1, OV5640_I2C_ADDR, addr, 2, 100);
HAL_I2C_Master_Receive(&hi2c1, OV5640_I2C_ADDR | 0x01, &data, 1, 100);
return data;
}
// 读取芯片ID验证
uint16_t OV5640_ReadChipID(void)
{
uint8_t id_h = OV5640_ReadReg(0x300A);
uint8_t id_l = OV5640_ReadReg(0x300B);
return ((uint16_t)id_h << 8) | id_l;
}
三、STM32 产生 XVCLK(外部时钟版本关键)
外部时钟版本的 XVCLK 由 STM32 定时器 PWM 输出产生,或使用 MCO 时钟输出:
// =============================================
// 方案A:使用 TIM1 CH1 产生精确 XVCLK
// 例如:系统时钟168MHz → 产生24MHz XVCLK
// =============================================
void XVCLK_Init_TIM(void)
{
__HAL_RCC_TIM1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
// PA8 = TIM1_CH1
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);
TIM_OC_InitTypeDef sConfigOC = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0; // 不分频: 168MHz
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 6; // ARR=6 → 168/(6+1)=24MHz
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_PWM_Init(&htim1);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 3; // 50%占空比: (6+1)/2≈3.5→取3
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=24MHz (TIM1_CH1) 已启动\r\n");
}
// =============================================
// 方案B:使用 MCO 时钟输出(更精准)
// MCO1 (PA8) 输出 HSE/HSI/PLL 分频后的时钟
// =============================================
void XVCLK_Init_MCO(void)
{
// 使用 MCO1 从 PA8 输出 HSE(24MHz有源晶振) 直通
HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_1);
// → PA8 输出精准的24MHz时钟
}
// =============================================
// 方案C:FPGA 产生 XVCLK(推荐用于外部时钟版本)
// Vivado约束示例(见下方)
// =============================================
FPGA XVCLK 生成(Verilog,推荐方案)
//==========================================
// ov5640_xclk_gen.v — FPGA产生OV5640 XVCLK
// 外部时钟版本:输出精准低抖动时钟
//==========================================
module ov5640_xclk_gen (
input wire clk_100mhz, // FPGA系统时钟
input wire rst_n,
output reg ov5640_xvclk // OV5640外部时钟
);
// 100MHz → 24MHz: 分频比 = 100/24 ≈ 4.167
// 使用MMCM/PLL更精准,此处以简单分频示意
reg [2:0] counter;
always @(posedge clk_100mhz or negedge rst_n) begin
if (!rst_n) begin
counter <= 3'd0;
ov5640_xvclk <= 1'b0;
end else begin
if (counter == 3'd3) begin // 100/4 = 25MHz (近似24MHz)
counter <= 3'd0;
ov5640_xvclk <= ~ov5640_xvclk;
end else begin
counter <= counter + 1'b1;
end
end
end
endmodule
Vivado MMCM 产生精准24MHz:
# XDC约束 — 使用MMCM产生精准24MHz
create_clock -period 10.000 -name sys_clk [get_ports clk_100mhz]
# MMCM配置输出24MHz到OV5640 XVCLK引脚
set_property PACKAGE_PIN K17 [get_ports ov5640_xvclk]
set_property IOSTANDARD LVCMOS33 [get_ports ov5640_xvclk]
四、外部时钟版本初始化序列(精简版)
// =============================================
// OV5640 外部时钟版本 — 精简上电初始化
// 关键:无PLL配置,XVCLK直驱系统时钟
// =============================================
void OV5640_ExtClk_HardwareInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
// PWDN: PA8, RESET: PB0
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_0;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// ===== 外部时钟版本上电时序 =====
// Step 1: 拉低控制信号
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET); // PWDN=0
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); // RESET=0
// Step 2: 电源已上电,等待稳定
HAL_Delay(5);
// Step 3: ⚠ 关键!先启动XVCLK,再释放PWDN
XVCLK_Init_TIM(); // 或 XVCLK_Init_MCO()
HAL_Delay(1); // 等待时钟稳定
// Step 4: 释放PWDN(外部版本无需PLL锁定,等待缩短)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET); // PWDN=1
HAL_Delay(1); // 外部时钟版本仅需1ms(内部版本需5ms+)
// Step 5: 释放RESET
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); // RESET=1
HAL_Delay(20);
printf("OV5640 外部时钟版本 硬件初始化完成\r\n");
}
// =============================================
// OV5640 外部时钟版本 寄存器初始化
// 关键区别:0x3103=0x03(外部时钟直通)
// 无需配置PLL寄存器0x3034-0x3037
// =============================================
void OV5640_ExtClk_RegInit(void)
{
// --- 软件复位 ---
OV5640_WriteReg(0x3008, 0x82);
HAL_Delay(5);
// --- ⚠ 核心:系统时钟源选择外部时钟直通 ---
OV5640_WriteReg(0x3103, 0x03); // bit[1:0]=11, 选择XVCLK为系统时钟
// 对比内部时钟版本:此寄存器为 0x13 (bit[4]=1使能PLL)
// --- PLL相关寄存器:全部旁路/最低设置 ---
// 外部时钟版本可跳过以下寄存器,写最低值即可
OV5640_WriteReg(0x3034, 0x0A); // PLL charge pump最小(不使用)
OV5640_WriteReg(0x3035, 0x01); // 分频器=1
OV5640_WriteReg(0x3036, 0x00); // PLL倍频=0(禁用PLL)
// --- IO驱动配置 ---
OV5640_WriteReg(0x3017, 0xFF);
OV5640_WriteReg(0x3018, 0xFF);
// --- 输出格式 RGB565 ---
OV5640_WriteReg(0x4300, 0x61);
// --- 分辨率配置 640x480 ---
OV5640_WriteReg(0x3800, 0x00); OV5640_WriteReg(0x3801, 0x00);
OV5640_WriteReg(0x3802, 0x00); OV5640_WriteReg(0x3803, 0x00);
OV5640_WriteReg(0x3804, 0x0A); OV5640_WriteReg(0x3805, 0x3F);
OV5640_WriteReg(0x3806, 0x07); OV5640_WriteReg(0x3807, 0x9F);
OV5640_WriteReg(0x3808, 0x02); OV5640_WriteReg(0x3809, 0x80);
OV5640_WriteReg(0x380A, 0x01); OV5640_WriteReg(0x380B, 0xE0);
// --- 时序配置 ---
OV5640_WriteReg(0x380C, 0x07); OV5640_WriteReg(0x380D, 0x58);
OV5640_WriteReg(0x380E, 0x01); OV5640_WriteReg(0x380F, 0xF0);
// --- PCLK分频(外部时钟版本直接分频XVCLK)---
OV5640_WriteReg(0x3824, 0x04); // PCLK = XVCLK / 4 (当XVCLK=84MHz→PCLK=21MHz)
// XVCLK=24MHz时 → PCLK=6MHz 适合低分辨率
// --- 自动曝光/白平衡 ---
OV5640_WriteReg(0x3A0F, 0x38);
OV5640_WriteReg(0x3A10, 0x30);
OV5640_WriteReg(0x3A11, 0x90);
OV5640_WriteReg(0x3A1B, 0x30);
OV5640_WriteReg(0x3A1E, 0x28);
OV5640_WriteReg(0x3A1F, 0x10);
// --- 图像质量 ---
OV5640_WriteReg(0x5000, 0x06); // 饱和度
OV5640_WriteReg(0x5001, 0x01); // 锐度
printf("OV5640 外部时钟版本 寄存器初始化完成\r\n");
printf("系统时钟 = XVCLK (直通, 无PLL)\r\n");
}
五、完整采集示例
#define FRAME_WIDTH 640
#define FRAME_HEIGHT 480
#define FRAME_SIZE (FRAME_WIDTH * FRAME_HEIGHT * 2)
__attribute__((aligned(4))) uint8_t frame_buffer[FRAME_SIZE];
volatile uint8_t frame_ready = 0;
DCMI_HandleTypeDef hdcmi;
DMA_HandleTypeDef hdma_dcmi;
void DCMI_Init(void)
{
// DCMI配置(与内部时钟版本相同,参见内部时钟版本代码)
// 注意:PCLK极性可能需要根据XVCLK频率调整
__HAL_RCC_DCMI_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;
HAL_DCMI_Init(&hdcmi);
// DMA配置(略,参见内部时钟版本)
}
void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi)
{
frame_ready = 1;
}
int main(void)
{
HAL_Init();
SystemClock_Config();
UART_Init();
SCCB_Init(); // I2C初始化
printf("=== OV5640 外部时钟版本 演示 ===\r\n");
// 1. 硬件上电 + XVCLK启动
OV5640_ExtClk_HardwareInit();
// 2. 验证芯片ID
uint16_t chip_id = OV5640_ReadChipID();
printf("Chip ID: 0x%04X\r\n", chip_id);
if (chip_id != 0x5640)
{
printf("错误: 芯片ID不匹配!\r\n");
while(1);
}
// 3. 寄存器初始化
OV5640_ExtClk_RegInit();
// 4. 启动DCMI
DCMI_Init();
HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_CONTINUOUS,
(uint32_t)frame_buffer, FRAME_SIZE / 4);
printf("采集已启动,XVCLK=外部时钟直驱模式\r\n");
// 主循环
uint32_t frame_count = 0;
while (1)
{
if (frame_ready)
{
frame_ready = 0;
frame_count++;
printf("帧 #%lu 捕获完成, %d字节\r\n", frame_count, FRAME_SIZE);
// 图像处理...
}
}
}
六、时钟频率切换示例
// 动态调整XVCLK频率(外部时钟版本的核心优势)
typedef enum {
OV5640_CLK_12MHZ = 12000000,
OV5640_CLK_24MHZ = 24000000,
OV5640_CLK_48MHZ = 48000000,
OV5640_CLK_84MHZ = 84000000,
} OV5640_XVCLK_Freq;
void OV5640_SetXVCLK(OV5640_XVCLK_Freq freq)
{
// 停止传感器输出
OV5640_WriteReg(0x3008, 0x02); // 软件待机
// 调整TIM1输出的XVCLK频率
switch (freq)
{
case OV5640_CLK_12MHZ:
__HAL_TIM_SET_AUTORELOAD(&htim1, 13); // 168/14=12MHz
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 7);
break;
case OV5640_CLK_24MHZ:
__HAL_TIM_SET_AUTORELOAD(&htim1, 6); // 168/7=24MHz
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 3);
break;
case OV5640_CLK_48MHZ:
__HAL_TIM_SET_AUTORELOAD(&htim1, 2); // 168/3=56MHz≈48MHz
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 1);
break;
case OV5640_CLK_84MHZ:
__HAL_TIM_SET_AUTORELOAD(&htim1, 1); // 168/2=84MHz
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 1);
break;
}
HAL_Delay(1); // 等待时钟稳定
// 恢复传感器工作
OV5640_WriteReg(0x3008, 0x42);
printf("XVCLK切换至 %lu Hz\r\n", freq);
}
七、排错指南
| 症状 | 外部时钟版本特定原因 | 排查方法 |
|---|---|---|
| 无图像/黑屏 | XVCLK未提供或频率不对 | 示波器测XVCLK引脚 |
| 图像有条纹 | XVCLK抖动过大 | 检查时钟源质量,改用有源晶振 |
| 帧率偏差 | XVCLK频率不准 | 频率计精确测量,调整分频 |
| 花屏 | XVCLK占空比偏移 | 示波器检查占空比45-55% |
| SCCB超时 | XVCLK未启动 | 外部版本SCCB也需要XVCLK |
| 寄存器写入无效 | 0x3103未设为外部时钟模式 | 读回0x3103确认=0x03 |