介绍
OV7670 VGA图像传感器内部时钟版本,30万像素(640×480),支持RGB/YCbCr/Raw输出格式,内置12MHz晶振,SCCB控制接口,最高30fps VGA输出,3.3V供电,适用于嵌入式视觉和低端图像采集应用
OV7670 VGA图像传感器内部时钟版本,30万像素(640×480),支持RGB/YCbCr/Raw输出格式,内置12MHz晶振,SCCB控制接口,最高30fps VGA输出,3.3V供电,适用于嵌入式视觉和低端图像采集应用
| 参数 | 值 |
|---|---|
| 像素 | 30万 |
| 功耗 | 60mW(典型) |
| 封装 | 28-pin CSP |
| 帧率 | 30fps@VGA |
| 接口 | 8位并行DVP + SCCB |
| 时钟 | 内置12MHz晶振 |
| 信噪比 | 36dB |
| 分辨率 | 640×480 VGA |
| 供电电压 | 3.3V(内核1.8V内置LDO) |
| 像素尺寸 | 3.6μm×3.6μm |
| 动态范围 | 60dB |
| 输出格式 | RGB565/555/444, YUV422, Raw Bayer |
| 镜头接口 | M12/CS可选 |
# OV7670 内部时钟版 驱动代码例程
## 一、STM32 平台 (HAL库) — DVP 接口驱动
### 1.1 GPIO 初始化
```c
// 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 驱动实现
```c
// 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 初始化与配置(内部时钟版)
```c
// 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 帧采集函数
```c
// 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) — 完整示例
```cpp
// 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 采集(高效方案)
```c
// 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 中已有一帧完整图像
// 可在此处处理或设置标志位通知主循环
}
```
暂无参考文献