Arduino)

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

文档

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

一、STM32 平台 (HAL库) — DVP 接口驱动

1.1 GPIO 初始化

// ov7670_stm32.h
#ifndef OV7670_H
#define OV7670_H

#include "stm32f4xx_hal.h"
#include <stdint.h>

/* SCCB 引脚定义 - 使用 I2C 软件模拟 */
#define SCCB_SCL_PIN    GPIO_PIN_6
#define SCCB_SDA_PIN    GPIO_PIN_7
#define SCCB_GPIO_PORT  GPIOB

/* DVP 数据引脚定义 */
#define DVP_D0_PIN      GPIO_PIN_0
#define DVP_D1_PIN      GPIO_PIN_1
#define DVP_D2_PIN      GPIO_PIN_2
#define DVP_D3_PIN      GPIO_PIN_3
#define DVP_D4_PIN      GPIO_PIN_4
#define DVP_D5_PIN      GPIO_PIN_5
#define DVP_D6_PIN      GPIO_PIN_6
#define DVP_D7_PIN      GPIO_PIN_7
#define DVP_DATA_PORT   GPIOA

/* 同步信号引脚 */
#define VSYNC_PIN       GPIO_PIN_8
#define HREF_PIN        GPIO_PIN_9
#define PCLK_PIN        GPIO_PIN_10
#define SYNC_PORT       GPIOB

/* OV7670 SCCB 地址 */
#define OV7670_ADDR_W   0x42
#define OV7670_ADDR_R   0x43

void OV7670_Init(void);
void OV7670_Reset(void);
uint8_t OV7670_ReadReg(uint8_t reg);
void OV7670_WriteReg(uint8_t reg, uint8_t val);
void OV7670_ConfigVGA_RGB565(void);
void OV7670_CaptureFrame(uint8_t *frameBuffer);

#endif

1.2 SCCB 驱动实现

// ov7670_sccb.c
#include "ov7670_stm32.h"
#include "delay.h"

/* 软件 I2C 延时(内部时钟版 12MHz,SCL 频率约 100kHz) */
static void SCCB_Delay(void) {
    delay_us(5);
}

static void SCCB_SCL_H(void) {
    HAL_GPIO_WritePin(SCCB_GPIO_PORT, SCCB_SCL_PIN, GPIO_PIN_SET);
}

static void SCCB_SCL_L(void) {
    HAL_GPIO_WritePin(SCCB_GPIO_PORT, SCCB_SCL_PIN, GPIO_PIN_RESET);
}

static void SCCB_SDA_H(void) {
    HAL_GPIO_WritePin(SCCB_GPIO_PORT, SCCB_SDA_PIN, GPIO_PIN_SET);
}

static void SCCB_SDA_L(void) {
    HAL_GPIO_WritePin(SCCB_GPIO_PORT, SCCB_SDA_PIN, GPIO_PIN_RESET);
}

static uint8_t SCCB_SDA_READ(void) {
    return HAL_GPIO_ReadPin(SCCB_GPIO_PORT, SCCB_SDA_PIN);
}

static void SCCB_SDA_OUT(void) {
    GPIO_InitTypeDef GPIO_Init = {0};
    GPIO_Init.Pin = SCCB_SDA_PIN;
    GPIO_Init.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_Init.Pull = GPIO_NOPULL;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(SCCB_GPIO_PORT, &GPIO_Init);
}

static void SCCB_SDA_IN(void) {
    GPIO_InitTypeDef GPIO_Init = {0};
    GPIO_Init.Pin = SCCB_SDA_PIN;
    GPIO_Init.Mode = GPIO_MODE_INPUT;
    GPIO_Init.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(SCCB_GPIO_PORT, &GPIO_Init);
}

/* SCCB 起始信号 */
static void SCCB_Start(void) {
    SCCB_SDA_OUT();
    SCCB_SDA_H();
    SCCB_SCL_H();
    SCCB_Delay();
    SCCB_SDA_L();
    SCCB_Delay();
    SCCB_SCL_L();
}

/* SCCB 停止信号 */
static void SCCB_Stop(void) {
    SCCB_SDA_OUT();
    SCCB_SDA_L();
    SCCB_SCL_H();
    SCCB_Delay();
    SCCB_SDA_H();
    SCCB_Delay();
}

/* 发送一个字节 */
static uint8_t SCCB_WriteByte(uint8_t data) {
    uint8_t i;
    SCCB_SDA_OUT();
    for (i = 0; i < 8; i++) {
        if (data & 0x80)
            SCCB_SDA_H();
        else
            SCCB_SDA_L();
        data <<= 1;
        SCCB_Delay();
        SCCB_SCL_H();
        SCCB_Delay();
        SCCB_SCL_L();
    }
    /* 第9位 - ACK */
    SCCB_SDA_IN();
    SCCB_Delay();
    SCCB_SCL_H();
    SCCB_Delay();
    uint8_t ack = SCCB_SDA_READ();
    SCCB_SCL_L();
    return ack; // 0 = ACK
}

/* 读取一个字节 */
static uint8_t SCCB_ReadByte(uint8_t ack) {
    uint8_t i, data = 0;
    SCCB_SDA_IN();
    for (i = 0; i < 8; i++) {
        data <<= 1;
        SCCB_Delay();
        SCCB_SCL_H();
        SCCB_Delay();
        if (SCCB_SDA_READ())
            data |= 0x01;
        SCCB_SCL_L();
    }
    /* 第9位 */
    SCCB_SDA_OUT();
    if (ack)
        SCCB_SDA_L(); // ACK
    else
        SCCB_SDA_H(); // NACK
    SCCB_Delay();
    SCCB_SCL_H();
    SCCB_Delay();
    SCCB_SCL_L();
    return data;
}

/* OV7670 写寄存器(三阶段写传输) */
void OV7670_WriteReg(uint8_t reg, uint8_t val) {
    SCCB_Start();
    SCCB_WriteByte(OV7670_ADDR_W);   // 设备地址 + 写
    SCCB_WriteByte(reg);             // 寄存器地址
    SCCB_WriteByte(val);             // 数据
    SCCB_Stop();
    delay_ms(1); // 等待写入完成
}

/* OV7670 读寄存器(两阶段读传输) */
uint8_t OV7670_ReadReg(uint8_t reg) {
    uint8_t val;
    SCCB_Start();
    SCCB_WriteByte(OV7670_ADDR_W);   // 设备地址 + 写
    SCCB_WriteByte(reg);             // 寄存器地址
    SCCB_Stop();
    delay_us(10);
    SCCB_Start();
    SCCB_WriteByte(OV7670_ADDR_R);   // 设备地址 + 读
    val = SCCB_ReadByte(0);          // 读取(NACK结束)
    SCCB_Stop();
    return val;
}

1.3 初始化与配置(内部时钟版)

// ov7670_init.c
#include "ov7670_stm32.h"

void OV7670_Reset(void) {
    /* 硬件复位 */
    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);
}

void OV7670_Init(void) {
    /* GPIO 初始化 */
    GPIO_InitTypeDef GPIO_Init = {0};

    /* SCCB 引脚 */
    __HAL_RCC_GPIOB_CLK_ENABLE();
    GPIO_Init.Pin = SCCB_SCL_PIN | SCCB_SDA_PIN;
    GPIO_Init.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_Init.Pull = GPIO_PULLUP;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(SCCB_GPIO_PORT, &GPIO_Init);

    /* DVP 数据引脚 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_Init.Pin = 0xFF; // PA0-PA7
    GPIO_Init.Mode = GPIO_MODE_INPUT;
    GPIO_Init.Pull = GPIO_NOPULL;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DVP_DATA_PORT, &GPIO_Init);

    /* 同步信号引脚 */
    GPIO_Init.Pin = VSYNC_PIN | HREF_PIN | PCLK_PIN;
    GPIO_Init.Mode = GPIO_MODE_INPUT;
    GPIO_Init.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(SYNC_PORT, &GPIO_Init);

    /* RESET 和 PWDN 引脚 */
    GPIO_Init.Pin = GPIO_PIN_12 | GPIO_PIN_13;
    GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init.Pull = GPIO_NOPULL;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &GPIO_Init);

    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); // PWDN = 0, 正常工作

    /* SCCB 总线初始化 */
    SCCB_SDA_H();
    SCCB_SCL_H();
    delay_ms(10);

    /* 复位传感器 */
    OV7670_Reset();

    /* 配置 VGA RGB565 模式 */
    OV7670_ConfigVGA_RGB565();
}

void OV7670_ConfigVGA_RGB565(void) {
    // 内部时钟版:12MHz 晶振 → PCLK = 24MHz (默认分频)
    OV7670_WriteReg(0x12, 0x04);    // 输出格式: RGB

    // 时钟控制 - 使用内部时钟
    OV7670_WriteReg(0x11, 0x01);    // 内部时钟分频
    OV7670_WriteReg(0x6B, 0x0A);    // 启用内部LDO

    // 窗口设置 - VGA 640x480
    OV7670_WriteReg(0x17, 0x16);    // HSTART
    OV7670_WriteReg(0x18, 0x04);    // HSTOP
    OV7670_WriteReg(0x32, 0x80);    // HREF
    OV7670_WriteReg(0x19, 0x02);    // VSTART
    OV7670_WriteReg(0x1A, 0x7A);    // VSTOP
    OV7670_WriteReg(0x03, 0x0A);    // VREF
    OV7670_WriteReg(0x0C, 0x00);    // RGB444 禁用
    OV7670_WriteReg(0x15, 0x00);    // VSYNC 正常
    OV7670_WriteReg(0x3E, 0x00);    // 关闭测试模式

    // 自动控制
    OV7670_WriteReg(0x13, 0xE7);    // AWB/AEC/AGC 全部启用
    OV7670_WriteReg(0x14, 0x18);    // 增益上限 8x

    // 颜色矩阵
    OV7670_WriteReg(0x4F, 0x80);
    OV7670_WriteReg(0x50, 0x80);
    OV7670_WriteReg(0x51, 0x00);
    OV7670_WriteReg(0x52, 0x22);
    OV7670_WriteReg(0x53, 0x5E);
    OV7670_WriteReg(0x54, 0x80);

    // 图像调整
    OV7670_WriteReg(0x70, 0x3A);    // 亮度
    OV7670_WriteReg(0x71, 0x35);    // 对比度
    OV7670_WriteReg(0x72, 0x11);    // 饱和度
    OV7670_WriteReg(0x73, 0xF0);    // 色调

    delay_ms(100); // 等待配置生效
}

1.4 帧采集函数

// ov7670_capture.c
#include "ov7670_stm32.h"

/**
 * 采集一帧 VGA RGB565 图像
 * frameBuffer: 至少 640×480×2 = 614400 字节
 * 返回: 0=成功, -1=超时
 */
int OV7670_CaptureFrame(uint8_t *frameBuffer) {
    uint32_t pixelCount = 0;
    uint32_t timeout = 0;
    const uint32_t MAX_PIXELS = 640 * 480;

    /* 等待帧开始(VSYNC上升沿) */
    timeout = 0xFFFFFF;
    while (HAL_GPIO_ReadPin(SYNC_PORT, VSYNC_PIN) == GPIO_PIN_SET) {
        if (--timeout == 0) return -1;
    }
    timeout = 0xFFFFFF;
    while (HAL_GPIO_ReadPin(SYNC_PORT, VSYNC_PIN) == GPIO_PIN_RESET) {
        if (--timeout == 0) return -1;
    }
    // VSYNC 上升沿,新帧开始

    while (pixelCount < MAX_PIXELS) {
        /* 等待 HREF 有效 */
        while (HAL_GPIO_ReadPin(SYNC_PORT, HREF_PIN) == GPIO_PIN_RESET) {
            if (HAL_GPIO_ReadPin(SYNC_PORT, VSYNC_PIN) == GPIO_PIN_RESET)
                goto frame_end; // 帧结束
        }

        /* 读取一行(640像素) */
        for (uint16_t col = 0; col < 640; col++) {
            /* 等待 PCLK 上升沿 */
            while (HAL_GPIO_ReadPin(SYNC_PORT, PCLK_PIN) == GPIO_PIN_RESET);
            uint8_t dataH = (uint8_t)(DVP_DATA_PORT->IDR & 0xFF);

            /* 等待 PCLK 下降沿 */
            while (HAL_GPIO_ReadPin(SYNC_PORT, PCLK_PIN) == GPIO_PIN_SET);
            uint8_t dataL = (uint8_t)(DVP_DATA_PORT->IDR & 0xFF);

            // RGB565: 第一个字节是高字节,第二个是低字节
            frameBuffer[pixelCount * 2] = dataH;
            frameBuffer[pixelCount * 2 + 1] = dataL;
            pixelCount++;

            if (pixelCount >= MAX_PIXELS) break;
        }
    }

frame_end:
    return (pixelCount >= MAX_PIXELS) ? 0 : -1;
}

二、Arduino 平台 (ESP32) — 完整示例

// ov7670_esp32.ino
// OV7670 内部时钟版 + ESP32 示例
// 使用内部12MHz晶振,无需外部XCLK

#include "esp_camera.h"

// OV7670 引脚配置(ESP32-CAM 兼容)
#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   15
#define XCLK_GPIO_NUM    27
#define SIOD_GPIO_NUM    25
#define SIOC_GPIO_NUM    23

#define Y9_GPIO_NUM      19
#define Y8_GPIO_NUM      36
#define Y7_GPIO_NUM      18
#define Y6_GPIO_NUM      39
#define Y5_GPIO_NUM       5
#define Y4_GPIO_NUM      34
#define Y3_GPIO_NUM      35
#define Y2_GPIO_NUM      32
#define VSYNC_GPIO_NUM   22
#define HREF_GPIO_NUM    26
#define PCLK_GPIO_NUM    21

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

    camera_config_t config;
    config.ledc_channel = LEDC_CHANNEL_0;
    config.ledc_timer   = LEDC_TIMER_0;
    config.pin_d0       = Y2_GPIO_NUM;
    config.pin_d1       = Y3_GPIO_NUM;
    config.pin_d2       = Y4_GPIO_NUM;
    config.pin_d3       = Y5_GPIO_NUM;
    config.pin_d4       = Y6_GPIO_NUM;
    config.pin_d5       = Y7_GPIO_NUM;
    config.pin_d6       = Y8_GPIO_NUM;
    config.pin_d7       = Y9_GPIO_NUM;
    config.pin_xclk     = XCLK_GPIO_NUM;
    config.pin_pclk     = PCLK_GPIO_NUM;
    config.pin_vsync    = VSYNC_GPIO_NUM;
    config.pin_href     = HREF_GPIO_NUM;
    config.pin_sscb_sda = SIOD_GPIO_NUM;
    config.pin_sscb_scl = SIOC_GPIO_NUM;
    config.pin_pwdn     = PWDN_GPIO_NUM;
    config.pin_reset    = RESET_GPIO_NUM;
    config.xclk_freq_hz = 12000000;  // 内部时钟版:12MHz
    config.pixel_format = PIXFORMAT_RGB565;
    config.frame_size   = FRAMESIZE_VGA;  // 640x480
    config.jpeg_quality = 12;
    config.fb_count     = 1;

    // 内部时钟版:XCLK 可配置为低或悬空
    // 若连接 XCLK 引脚,设置为输入模式
    pinMode(XCLK_GPIO_NUM, INPUT);

    esp_err_t err = esp_camera_init(&config);
    if (err != ESP_OK) {
        Serial.printf("摄像头初始化失败: 0x%x\n", err);
        return;
    }

    Serial.println("OV7670 初始化成功!");
    Serial.printf("分辨率: %dx%d\n",
        config.frame_size == FRAMESIZE_VGA ? 640 : 320,
        config.frame_size == FRAMESIZE_VGA ? 480 : 240);
}

void loop() {
    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("获取帧失败");
        delay(100);
        return;
    }

    Serial.printf("帧大小: %u 字节, 时间戳: %lu\n",
        fb->len, fb->timestamp);

    // 处理图像数据...
    // 例如:发送到串口、保存到SD卡、传输到上位机

    esp_camera_fb_return(fb);
    delay(100);
}

三、STM32 DCMI 接口 DMA 采集(高效方案)

// ov7670_dcmi.c  — 使用STM32 DCMI + DMA
// 内部时钟版无需配置XCLK输出

#include "stm32f4xx_hal.h"

DCMI_HandleTypeDef hdcmi;
DMA_HandleTypeDef hdma_dcmi;

static uint8_t frameBuffer[640 * 480 * 2]; // RGB565 VGA

void DCMI_Init_OV7670(void) {
    __HAL_RCC_DCMI_CLK_ENABLE();
    __HAL_RCC_DMA2_CLK_ENABLE();

    /* DCMI 配置 */
    hdcmi.Instance = DCMI;
    hdcmi.Init.SynchroMode   = DCMI_SYNCHRO_HARDWARE;
    hdcmi.Init.PCKPolarity   = DCMI_PCKPOLARITY_RISING;
    hdcmi.Init.VSPolarity    = DCMI_VSPOLARITY_HIGH;
    hdcmi.Init.HSPolarity    = DCMI_HSPOLARITY_HIGH;
    hdcmi.Init.CaptureRate   = DCMI_CR_ALL_FRAME;
    hdcmi.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B;
    HAL_DCMI_Init(&hdcmi);

    /* DMA 配置 - DCMI 数据流 */
    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_CIRCULAR; // 循环模式持续采集
    hdma_dcmi.Init.Priority = DMA_PRIORITY_HIGH;
    HAL_DMA_Init(&hdma_dcmi);

    __HAL_LINKDMA(&hdcmi, DMA_Handle, hdma_dcmi);

    /* 启动 DCMI 连续采集 */
    HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_CONTINUOUS,
        (uint32_t)frameBuffer, 640 * 480);
}

// 帧完成回调
void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) {
    // frameBuffer 中已有一帧完整图像
    // 可在此处处理或设置标志位通知主循环
}

信息

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