ESP32)

知识库
知识库文档
/firmware/传感器/OV7670 外部时钟版/OV7670 外部时钟版 驱动代码例程 (STM32/ESP32).md

文档

OV7670 外部时钟版 驱动代码例程

一、STM32 平台 — 外部时钟生成 + DVP 采集

1.1 MCO 时钟输出配置(提供 XCLK)

// ov7670_ext_clk_stm32.c
// 使用 STM32F4 MCO1 输出 24MHz 时钟给 OV7670 XCLK

#include "stm32f4xx_hal.h"

/**
 * 配置 MCO1 (PA8) 输出 24MHz 供 OV7670 外部时钟版使用
 * 需先配置系统时钟为 HSE/PLL 提供时钟源
 */
void MCO1_Output_24MHz(void) {
    GPIO_InitTypeDef GPIO_Init = {0};

    __HAL_RCC_GPIOA_CLK_ENABLE();

    /* PA8 = MCO1, 推挽输出, 高速 */
    GPIO_Init.Pin = GPIO_PIN_8;
    GPIO_Init.Mode = GPIO_MODE_AF_PP;
    GPIO_Init.Pull = GPIO_NOPULL;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_Init.Alternate = GPIO_AF0_MCO;
    HAL_GPIO_Init(GPIOA, &GPIO_Init);

    /*
     * MCO1 时钟源选择:
     * - 若系统时钟 168MHz,用 HSE 24MHz → 直接输出 24MHz
     * - HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_1);
     */
    HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_1);
    // 输出: HSE 24MHz ÷ 1 = 24MHz XCLK
}

/**
 * 备选方案: 使用 TIM1 CH1 输出 PWM 产生 24MHz
 * 适合没有 MCO 的 MCU(如 STM32F1)
 */
void TIM1_PWM_24MHz_Init(void) {
    TIM_OC_InitTypeDef sConfigOC = {0};
    GPIO_InitTypeDef GPIO_Init = {0};

    __HAL_RCC_TIM1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /* PA8 = TIM1_CH1 */
    GPIO_Init.Pin = GPIO_PIN_8;
    GPIO_Init.Mode = GPIO_MODE_AF_PP;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_Init.Alternate = GPIO_AF1_TIM1;
    HAL_GPIO_Init(GPIOA, &GPIO_Init);

    /* TIM1: 72MHz → 24MHz = 每周期3个计数 */
    TIM_HandleTypeDef htim1;
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 0;           // 不分频
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim1.Init.Period = 2;              // ARR=2, 0-1-2 = 3拍 = 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 = 1;                // 50%占空比 (1/2)
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
}

1.2 外部时钟版初始化

// ov7670_ext_init.c
#include "ov7670_stm32.h"  // 复用之前定义的 SCCB 和 DVP 函数

/* 外部时钟版初始化 */
void OV7670_ExtClk_Init(uint32_t xclk_freq_hz) {
    GPIO_InitTypeDef GPIO_Init = {0};

    // ... GPIO初始化同内部时钟版 ...

    /* 关键:先启动外部时钟,再复位传感器 */
    if (xclk_freq_hz == 24000000) {
        MCO1_Output_24MHz();
    }
    delay_ms(10); // 等待时钟稳定

    /* 硬件复位 */
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); // PWDN=0
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); // RESET=0
    delay_ms(10);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);   // RESET=1
    delay_ms(100);

    /* 软复位 */
    OV7670_WriteReg(0x12, 0x80);
    delay_ms(100);

    /* 根据XCLK频率配置 */
    uint8_t clkrc_val;
    switch (xclk_freq_hz) {
        case 12000000:
            clkrc_val = 0x01;  // PCLK = 24MHz
            break;
        case 24000000:
            clkrc_val = 0x01;  // PCLK = 48MHz
            break;
        case 48000000:
            clkrc_val = 0x03;  // PCLK = 48MHz (÷4)
            break;
        default:
            clkrc_val = 0x01;
    }

    /* 外部时钟模式配置 */
    OV7670_WriteReg(0x11, clkrc_val);   // 时钟分频
    OV7670_WriteReg(0x6B, 0x00);        // 外部时钟模式,不用内部LDO

    /* 后续配置同内部时钟版... */
    OV7670_ConfigVGA_RGB565();
}

1.3 动态帧率调节

/**
 * 外部时钟版的优势:可动态调整帧率
 * 通过修改寄存器0x11改变PCLK分频比
 */
void OV7670_SetFrameRate(uint8_t fps_code) {
    static const uint8_t clkrc_table[] = {
        0x01, // ~30fps (XCLK=24MHz, PCLK=48MHz)
        0x03, // ~15fps (XCLK=24MHz, PCLK=24MHz)
        0x07, // ~7.5fps
        0x0F, // ~3.75fps
    };

    if (fps_code < sizeof(clkrc_table)) {
        OV7670_WriteReg(0x11, clkrc_table[fps_code]);
        delay_ms(50); // 等待帧率切换稳定
    }
}

二、ESP32 平台 — 外部时钟输出

// ov7670_ext_esp32.ino
// ESP32 为 OV7670 外部时钟版提供 XCLK

#include "esp_camera.h"
#include "driver/ledc.h"

// XCLK 输出引脚
#define XCLK_GPIO_NUM    27

/**
 * 使用 ESP32 LEDC 生成 24MHz XCLK
 * ESP32 外设限制:最高约 40MHz(占空比可能不够精确)
 * 推荐使用 20MHz 以下以确保稳定性
 */
void setupXCLK_24MHz(void) {
    ledc_timer_config_t timer_conf = {
        .speed_mode = LEDC_HIGH_SPEED_MODE,
        .duty_resolution = LEDC_TIMER_2_BIT,  // 2位分辨率 для 高速
        .timer_num = LEDC_TIMER_0,
        .freq_hz = 24000000,  // 24MHz
        .clk_cfg = LEDC_AUTO_CLK,
    };
    ledc_timer_config(&timer_conf);

    ledc_channel_config_t ch_conf = {
        .gpio_num = XCLK_GPIO_NUM,
        .speed_mode = LEDC_HIGH_SPEED_MODE,
        .channel = LEDC_CHANNEL_0,
        .timer_sel = LEDC_TIMER_0,
        .duty = 2,   // 50%占空比 (2^2 / 2 = 2)
        .hpoint = 0,
    };
    ledc_channel_config(&ch_conf);
}

void setup() {
    Serial.begin(115200);
    Serial.println("OV7670 外部时钟版 初始化...");

    // 1. 先启动 XCLK
    setupXCLK_24MHz();
    delay(10);

    // 2. 初始化摄像头
    camera_config_t config;
    config.ledc_channel = LEDC_CHANNEL_0;
    config.ledc_timer   = LEDC_TIMER_0;
    config.pin_d0       = 32; // Y2
    config.pin_d1       = 35; // Y3
    config.pin_d2       = 34; // Y4
    config.pin_d3       = 5;  // Y5
    config.pin_d4       = 39; // Y6
    config.pin_d5       = 18; // Y7
    config.pin_d6       = 36; // Y8
    config.pin_d7       = 19; // Y9
    config.pin_xclk     = XCLK_GPIO_NUM;  // 关键:XCLK由ESP32输出
    config.pin_pclk     = 21; // PCLK
    config.pin_vsync    = 22; // VSYNC
    config.pin_href     = 26; // HREF
    config.pin_sscb_sda = 25; // SIOD
    config.pin_sscb_scl = 23; // SIOC
    config.pin_pwdn     = -1;
    config.pin_reset    = 15;
    config.xclk_freq_hz = 24000000;  // 外部时钟版:24MHz
    config.pixel_format = PIXFORMAT_RGB565;
    config.frame_size   = FRAMESIZE_VGA;
    config.jpeg_quality = 12;
    config.fb_count     = 2;

    esp_err_t err = esp_camera_init(&config);
    if (err != ESP_OK) {
        Serial.printf("初始化失败: 0x%x\n", err);
        return;
    }
    Serial.println("OV7670 外部时钟版 就绪!");
}

void loop() {
    camera_fb_t *fb = esp_camera_fb_get();
    if (fb) {
        Serial.printf("帧: %u字节\n", fb->len);
        esp_camera_fb_return(fb);
    }
    delay(50);
}

三、多传感器同步采集(外部时钟版核心优势)

// ov7670_multi_sync.c
// 3个OV7670外部时钟版同步采集方案

#define CAM_COUNT 3

typedef struct {
    I2C_HandleTypeDef *hi2c;    // 独立I2C总线
    uint8_t frameBuffer[640*480*2];
    uint8_t frameReady;
} OV7670_Camera;

OV7670_Camera cameras[CAM_COUNT];

/**
 * 多摄像头同步初始化
 * 所有摄像头共用 XCLK,各自独立 SCCB 和 DVP
 */
void MultiCam_Init(void) {
    // 1. 启动共用的 24MHz XCLK(连接至所有摄像头)
    MCO1_Output_24MHz();
    delay_ms(10);

    // 2. 依次初始化每个摄像头
    for (int i = 0; i < CAM_COUNT; i++) {
        cameras[i].hi2c = &hi2c_list[i]; // 各自独立的 I2C
        cameras[i].frameReady = 0;

        // 单独复位
        HAL_GPIO_WritePin(reset_ports[i], reset_pins[i], GPIO_PIN_RESET);
        delay_ms(5);
        HAL_GPIO_WritePin(reset_ports[i], reset_pins[i], GPIO_PIN_SET);
        delay_ms(100);

        // 配置(可用同一配置,通过独立SCCB写入)
        OV7670_WriteReg_Ex(cameras[i].hi2c, 0x12, 0x80); // 软复位
        delay_ms(100);
        OV7670_ConfigVGA_RGB565_Ex(cameras[i].hi2c);
    }

    // 3. 所有摄像头已同步(共用XCLK确保帧起始对齐)
    // 误差 < 1 pixel clock cycle
}

信息

路径
/firmware/传感器/OV7670 外部时钟版/OV7670 外部时钟版 驱动代码例程 (STM32/ESP32).md
更新时间
2026/5/26