OV7670 内部时钟版 驱动代码例程
一、STM32 平台 (HAL库) — DVP 接口驱动
1.1 GPIO 初始化
#ifndef OV7670_H
#define OV7670_H
#include "stm32f4xx_hal.h"
#include <stdint.h>
#define SCCB_SCL_PIN GPIO_PIN_6
#define SCCB_SDA_PIN GPIO_PIN_7
#define SCCB_GPIO_PORT GPIOB
#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
#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 驱动实现
#include "ov7670_stm32.h"
#include "delay.h"
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);
}
static void SCCB_Start(void) {
SCCB_SDA_OUT();
SCCB_SDA_H();
SCCB_SCL_H();
SCCB_Delay();
SCCB_SDA_L();
SCCB_Delay();
SCCB_SCL_L();
}
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();
}
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();
}
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;
}
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); // 等待写入完成
}
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 初始化与配置(内部时钟版)
#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_InitTypeDef GPIO_Init = {0};
__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);
__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);
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_SDA_H();
SCCB_SCL_H();
delay_ms(10);
OV7670_Reset();
OV7670_ConfigVGA_RGB565();
}
void OV7670_ConfigVGA_RGB565(void) {
OV7670_WriteReg(0x12, 0x04); // 输出格式: RGB
OV7670_WriteReg(0x11, 0x01); // 内部时钟分频
OV7670_WriteReg(0x6B, 0x0A); // 启用内部LDO
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 帧采集函数
#include "ov7670_stm32.h"
int OV7670_CaptureFrame(uint8_t *frameBuffer) {
uint32_t pixelCount = 0;
uint32_t timeout = 0;
const uint32_t MAX_PIXELS = 640 * 480;
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;
}
while (pixelCount < MAX_PIXELS) {
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; // 帧结束
}
for (uint16_t col = 0; col < 640; col++) {
while (HAL_GPIO_ReadPin(SYNC_PORT, PCLK_PIN) == GPIO_PIN_RESET);
uint8_t dataH = (uint8_t)(DVP_DATA_PORT->IDR & 0xFF);
while (HAL_GPIO_ReadPin(SYNC_PORT, PCLK_PIN) == GPIO_PIN_SET);
uint8_t dataL = (uint8_t)(DVP_DATA_PORT->IDR & 0xFF);
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) — 完整示例
#include "esp_camera.h"
#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;
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);
esp_camera_fb_return(fb);
delay(100);
}
三、STM32 DCMI 接口 DMA 采集(高效方案)
#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();
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);
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);
HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_CONTINUOUS,
(uint32_t)frameBuffer, 640 * 480);
}
void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) {
}