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

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

文档

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

一、平台说明

  • 主控平台:ESP32 / ESP32-S3 / STM32F407
  • 接口类型:DVP 8位并行
  • 通信协议:SCCB(页寻址模式)
  • 开发环境:ESP-IDF / STM32CubeIDE

二、SCCB 驱动(页寻址模式)

#include "stm32f4xx_hal.h"

// OV2640 SCCB地址(注意:不是0x78!)
#define OV2640_I2C_ADDR  0x60  // 7位地址0x30左移1位

I2C_HandleTypeDef hi2c1;

void OV2640_I2C_Init(void)
{
    // 标准 I2C 初始化(参考 OV5640 部分的 SCCB_Init)
    // 时钟频率: 100kHz
    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);
}

// =============================================
// OV2640 写寄存器(8位寄存器地址)
// ⚠ 注意:OV2640寄存器地址是8位,不是16位!
// =============================================
uint8_t OV2640_WriteReg(uint8_t reg, uint8_t data)
{
    uint8_t buf[2];
    buf[0] = reg;    // 8位寄存器地址
    buf[1] = data;   // 8位数据
    return HAL_I2C_Master_Transmit(&hi2c1, OV2640_I2C_ADDR, buf, 2, 100);
}

uint8_t OV2640_ReadReg(uint8_t reg)
{
    uint8_t data = 0;
    HAL_I2C_Master_Transmit(&hi2c1, OV2640_I2C_ADDR, &reg, 1, 100);
    HAL_I2C_Master_Receive(&hi2c1, OV2640_I2C_ADDR | 0x01, &data, 1, 100);
    return data;
}

// =============================================
// OV2640 页切换(核心!)
// =============================================
void OV2640_SetPage(uint8_t page)
{
    OV2640_WriteReg(0xFF, page);
}

// =============================================
// 便捷函数:跨页写寄存器
// =============================================
void OV2640_WriteReg_Page(uint8_t page, uint8_t reg, uint8_t data)
{
    OV2640_SetPage(page);
    OV2640_WriteReg(reg, data);
}

三、内部PLL时钟配置

// =============================================
// OV2640 内部PLL时钟配置
// XVCLK=24MHz → 系统时钟≈48MHz
// =============================================
void OV2640_ConfigPLL_Internal(void)
{
    printf("配置 OV2640 内部 PLL...\r\n");

    // 切换到页0
    OV2640_SetPage(0x00);

    // 时钟控制寄存器
    OV2640_WriteReg(0x11, 0x01);  // 时钟控制1: 内部PLL使能
    OV2640_WriteReg(0x12, 0x08);  // 时钟控制2: PCLK分频

    // 切换到页0的时钟域
    OV2640_WriteReg(0x2D, 0x00);  // 时钟分频控制
    // bit[6]: PLL使能
    // bit[5:0]: PLL预分频

    OV2640_WriteReg(0x2D, 0x40 | 0x03);  // PLL使能, 预分频=3
    // PLL输出 = XVCLK / 预分频 × 倍频
    //         = 24MHz / 3 × 8 = 64MHz (典型值)

    HAL_Delay(5);  // 等待PLL锁定

    printf("OV2640 PLL配置完成\r\n");
}

四、完整初始化序列(JPEG模式)

// =============================================
// OV2640 上电与复位
// =============================================
void OV2640_HardwareInit(void)
{
    // GPIO初始化 (PWDN: PA8, RESET: PB0)
    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;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    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(10);

    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);    // PWDN=1
    HAL_Delay(5);  // 内部时钟版本需等待PLL锁定

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);    // RESET=1
    HAL_Delay(20);
}

// =============================================
// OV2640 JPEG模式初始化(UXGA 1600x1200)
// =============================================
void OV2640_Init_JPEG_UXGA(void)
{
    printf("OV2640 JPEG UXGA 初始化...\r\n");

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

    // ---- 页0: 基本配置 ----
    OV2640_SetPage(0x00);

    // COM7: 输出格式和分辨率
    OV2640_WriteReg(0x12, 0x00);  // 先清零
    OV2640_WriteReg(0x12, 0x40);  // COM7: UXGA, Raw RGB

    // COM9: 增益上限
    OV2640_WriteReg(0x14, 0x28);

    // COM10: PCLK不反转, HREF不反转
    OV2640_WriteReg(0x15, 0x00);

    // ---- 时钟配置(内部PLL)----
    OV2640_WriteReg(0x11, 0x01);  // 内部PLL使能
    OV2640_WriteReg(0x2D, 0x00);  // 时钟分频
    OV2640_WriteReg(0x2D, 0x43);  // PLL使能, 预分频=3

    // ---- 分辨率窗口: UXGA 1600x1200 ----
    OV2640_WriteReg(0x17, 0x00);  // HSTART高
    OV2640_WriteReg(0x18, 0x00);  // HSTART低
    OV2640_WriteReg(0x19, 0x06);  // HSTOP高
    OV2640_WriteReg(0x1A, 0x40);  // HSTOP低  → 1600
    OV2640_WriteReg(0x03, 0x00);  // VSTART高
    OV2640_WriteReg(0x04, 0x00);  // VSTART低
    OV2640_WriteReg(0x05, 0x04);  // VSTOP高
    OV2640_WriteReg(0x06, 0xB0);  // VSTOP低  → 1200
    OV2640_WriteReg(0x32, 0x06);  // HREF高
    OV2640_WriteReg(0x33, 0x40);  // HREF低
    OV2640_WriteReg(0x2A, 0x00);  // VREF高
    OV2640_WriteReg(0x2B, 0x00);  // VREF低

    // ---- 输出窗口 = UXGA 1600x1200 ----
    OV2640_WriteReg(0x51, 0x06);  // OUTW高
    OV2640_WriteReg(0x52, 0x40);  // OUTW低 → 1600
    OV2640_WriteReg(0x53, 0x04);  // OUTH高
    OV2640_WriteReg(0x54, 0xB0);  // OUTH低 → 1200

    // ---- 时序 ----
    OV2640_WriteReg(0x55, 0x00);  // HTS
    OV2640_WriteReg(0x56, 0x00);  // HTS
    OV2640_WriteReg(0x57, 0x00);  // VTS

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

    // ---- 页1: JPEG 压缩配置 ----
    OV2640_SetPage(0x01);

    // JPEG质量 (0x00~0xFF, 越大质量越高)
    OV2640_WriteReg(0x30, 0x80);  // JPEG质量中高

    // ---- 页0: 自动控制 ----
    OV2640_SetPage(0x00);

    // AEC/AGC
    OV2640_WriteReg(0x13, 0x07);  // COM8: AEC/AGC/AWB使能

    // 亮度/对比度
    OV2640_WriteReg(0x7C, 0x00);  // 亮度
    OV2640_WriteReg(0x7D, 0x20);  // 对比度

    // 特效
    OV2640_WriteReg(0x7F, 0x00);  // 特效控制: 正常

    printf("OV2640 JPEG UXGA 初始化完成\r\n");
}

// =============================================
// 切换分辨率示例
// =============================================
void OV2640_SetResolution(uint16_t width, uint16_t height)
{
    OV2640_SetPage(0x00);

    if (width == 1600 && height == 1200)
    {
        OV2640_WriteReg(0x12, 0x40 | 0x20);  // UXGA + JPEG
    }
    else if (width == 800 && height == 600)
    {
        OV2640_WriteReg(0x12, 0x00 | 0x20);  // SVGA + JPEG
    }
    else if (width == 640 && height == 480)
    {
        OV2640_WriteReg(0x12, 0x80 | 0x20);  // VGA + JPEG
    }
    else if (width == 320 && height == 240)
    {
        OV2640_WriteReg(0x12, 0xC0 | 0x20);  // QVGA + JPEG
    }

    printf("OV2640 分辨率切换: %dx%d\r\n", width, height);
}

五、ESP32 平台驱动(Arduino/ESP-IDF)

// ESP32-CAM 使用 OV2640
#include "esp_camera.h"

static camera_config_t camera_config = {
    .pin_pwdn       = 32,
    .pin_reset      = -1,      // OV2640复位通常与ESP32复位共用
    .pin_xclk       = 0,       // ESP32提供XVCLK (20MHz)
    .pin_sccb_sda   = 26,
    .pin_sccb_scl   = 27,
    .pin_d7         = 35,
    .pin_d6         = 34,
    .pin_d5         = 39,
    .pin_d4         = 36,
    .pin_d3         = 21,
    .pin_d2         = 19,
    .pin_d1         = 18,
    .pin_d0         = 5,
    .pin_vsync      = 25,
    .pin_href       = 23,
    .pin_pclk       = 22,

    .xclk_freq_hz   = 20000000,    // 20MHz XVCLK
    .ledc_timer     = LEDC_TIMER_0,
    .ledc_channel   = LEDC_CHANNEL_0,

    .pixel_format   = PIXFORMAT_JPEG,  // 利用OV2640内置JPEG
    .frame_size     = FRAMESIZE_UXGA,  // 1600x1200

    .jpeg_quality   = 10,         // 0-63, 越小质量越高
    .fb_count       = 2,
    .fb_location    = CAMERA_FB_IN_PSRAM,
    .grab_mode      = CAMERA_GRAB_WHEN_EMPTY,
};

void OV2640_ESP32_Init(void)
{
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK)
    {
        printf("OV2640 初始化失败: 0x%x\n", err);
        return;
    }

    sensor_t *s = esp_camera_sensor_get();
    if (s != NULL)
    {
        // OV2640 特定设置
        s->set_vflip(s, 1);          // 垂直翻转(根据安装方向)
        s->set_hmirror(s, 0);        // 水平镜像
        s->set_brightness(s, 0);     // 亮度 -2~2
        s->set_contrast(s, 0);       // 对比度 -2~2
        s->set_saturation(s, 0);     // 饱和度 -2~2
        s->set_quality(s, 10);       // JPEG质量
    }

    printf("OV2640 ESP32-CAM 初始化成功\n");
}

void OV2640_ESP32_Capture(void)
{
    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb)
    {
        printf("帧获取失败\n");
        return;
    }

    printf("JPEG帧: %zu字节, %dx%d\n", fb->len, fb->width, fb->height);

    // fb->buf 包含完整JPEG数据,可直接保存或传输

    esp_camera_fb_return(fb);
}

六、STM32 DCMI JPEG 采集

#define JPEG_BUF_SIZE  (200 * 1024)  // 200KB JPEG缓冲
uint8_t jpeg_buffer[JPEG_BUF_SIZE];
volatile uint32_t jpeg_len = 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;  // ⚠ JPEG模式使能!
    hdcmi.Init.ByteSelectMode = DCMI_BSM_ALL;
    HAL_DCMI_Init(&hdcmi);

    // DMA配置
    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;  // JPEG用普通模式
    hdma_dcmi.Init.Priority = DMA_PRIORITY_VERY_HIGH;
    HAL_DMA_Init(&hdma_dcmi);

    __HAL_LINKDMA(&hdcmi, DMA_Handle, hdma_dcmi);
}

// JPEG帧完成回调
void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi)
{
    // 获取JPEG数据长度
    jpeg_len = JPEG_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_dcmi);
    HAL_DCMI_Stop(hdcmi);
}

// 采集一帧JPEG
uint8_t* OV2640_CaptureJPEG(uint32_t *len)
{
    jpeg_len = 0;

    HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_SNAPSHOT,
                       (uint32_t)jpeg_buffer, JPEG_BUF_SIZE / 4);

    // 等待帧完成(或使用中断)
    uint32_t timeout = 1000;
    while (jpeg_len == 0 && timeout-- > 0) {
        HAL_Delay(1);
    }

    if (jpeg_len > 0)
    {
        *len = jpeg_len;
        return jpeg_buffer;
    }

    *len = 0;
    return NULL;
}

七、常见问题

问题 解决方案
页寄存器忘记切换 每次操作前确保 0xFF 寄存器设置正确
读取全0xFF I²C地址错误,OV2640用0x60非0x78
JPEG数据不完整 增大DMA缓冲区,检查DCMI JPEG模式配置
图像颜色偏绿 白平衡配置问题,检查AWB相关寄存器

信息

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