OV2640 内部时钟版本驱动代码例程
一、平台说明
- 主控平台:ESP32 / ESP32-S3 / STM32F407
- 接口类型:DVP 8位并行
- 通信协议:SCCB(页寻址模式)
- 开发环境:ESP-IDF / STM32CubeIDE
二、SCCB 驱动(页寻址模式)
#include "stm32f4xx_hal.h"
#define OV2640_I2C_ADDR 0x60 // 7位地址0x30左移1位
I2C_HandleTypeDef hi2c1;
void OV2640_I2C_Init(void)
{
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);
}
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, ®, 1, 100);
HAL_I2C_Master_Receive(&hi2c1, OV2640_I2C_ADDR | 0x01, &data, 1, 100);
return data;
}
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时钟配置
void OV2640_ConfigPLL_Internal(void)
{
printf("配置 OV2640 内部 PLL...\r\n");
OV2640_SetPage(0x00);
OV2640_WriteReg(0x11, 0x01); // 时钟控制1: 内部PLL使能
OV2640_WriteReg(0x12, 0x08); // 时钟控制2: PCLK分频
OV2640_WriteReg(0x2D, 0x00); // 时钟分频控制
OV2640_WriteReg(0x2D, 0x40 | 0x03); // PLL使能, 预分频=3
HAL_Delay(5); // 等待PLL锁定
printf("OV2640 PLL配置完成\r\n");
}
四、完整初始化序列(JPEG模式)
void OV2640_HardwareInit(void)
{
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);
}
void OV2640_Init_JPEG_UXGA(void)
{
printf("OV2640 JPEG UXGA 初始化...\r\n");
OV2640_WriteReg_Page(0x00, 0x12, 0x80);
HAL_Delay(100);
OV2640_SetPage(0x00);
OV2640_WriteReg(0x12, 0x00); // 先清零
OV2640_WriteReg(0x12, 0x40); // COM7: UXGA, Raw RGB
OV2640_WriteReg(0x14, 0x28);
OV2640_WriteReg(0x15, 0x00);
OV2640_WriteReg(0x11, 0x01); // 内部PLL使能
OV2640_WriteReg(0x2D, 0x00); // 时钟分频
OV2640_WriteReg(0x2D, 0x43); // PLL使能, 预分频=3
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低
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
OV2640_WriteReg(0x12, 0x40 | 0x20); // COM7: UXGA + JPEG输出
OV2640_SetPage(0x01);
OV2640_WriteReg(0x30, 0x80); // JPEG质量中高
OV2640_SetPage(0x00);
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)
#include "esp_camera.h"
static camera_config_t camera_config = {
.pin_pwdn = 32,
.pin_reset = -1,
.pin_xclk = 0,
.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,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_UXGA,
.jpeg_quality = 10,
.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)
{
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);
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);
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);
}
void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi)
{
jpeg_len = JPEG_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_dcmi);
HAL_DCMI_Stop(hdcmi);
}
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相关寄存器 |