介绍
OmniVision OV2640 200万像素CMOS图像传感器,支持UXGA(1600x1200)分辨率,内置JPEG压缩引擎,DVP并行接口。内部时钟版本集成PLL,外部提供12-48MHz参考时钟即可工作。广泛用于ESP32-CAM等物联网视觉应用。
OmniVision OV2640 200万像素CMOS图像传感器,支持UXGA(1600x1200)分辨率,内置JPEG压缩引擎,DVP并行接口。内部时钟版本集成PLL,外部提供12-48MHz参考时钟即可工作。广泛用于ESP32-CAM等物联网视觉应用。
| 参数 | 值 |
|---|---|
| 像素 | 200万(UXGA 1600x1200) |
| 封装 | CSP-28 |
| 信噪比 | 40dB |
| 供电电压 | 内核1.3V(内部LDO), 模拟2.8V, I/O 1.8V-3.3V |
| 像素尺寸 | 2.2μm x 2.2μm |
| 动态范围 | 60dB |
| 时钟方案 | 内部PLL,参考时钟12-48MHz |
| 最大帧率 | 15fps@UXGA, 30fps@SVGA(800x600), 60fps@QVGA |
| 特色功能 | 内置JPEG压缩、自动曝光/白平衡/增益 |
| 输出接口 | DVP 8位并行 |
| 传感器尺寸 | 1/4英寸 |
# OV2640 内部时钟版本驱动代码例程
## 一、平台说明
- **主控平台**:ESP32 / ESP32-S3 / STM32F407
- **接口类型**:DVP 8位并行
- **通信协议**:SCCB(页寻址模式)
- **开发环境**:ESP-IDF / STM32CubeIDE
---
## 二、SCCB 驱动(页寻址模式)
```c
#include "stm32f4xx_hal.h"
// OV2640 SCCB地址(注意:不是0x78!)
#define OV2640_I2C_ADDR 0x60 // 7位地址0x30左移1位
I2C_HandleTypeDef hi2c1;
void OV2640_I2C_Init(void)
{
// 标准 I2C 初始化(参考 OV5640 部分的 SCCB_Init)
// 时钟频率: 100kHz
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);
}
// =============================================
// OV2640 写寄存器(8位寄存器地址)
// ⚠ 注意:OV2640寄存器地址是8位,不是16位!
// =============================================
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;
}
// =============================================
// OV2640 页切换(核心!)
// =============================================
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时钟配置
```c
// =============================================
// OV2640 内部PLL时钟配置
// XVCLK=24MHz → 系统时钟≈48MHz
// =============================================
void OV2640_ConfigPLL_Internal(void)
{
printf("配置 OV2640 内部 PLL...\r\n");
// 切换到页0
OV2640_SetPage(0x00);
// 时钟控制寄存器
OV2640_WriteReg(0x11, 0x01); // 时钟控制1: 内部PLL使能
OV2640_WriteReg(0x12, 0x08); // 时钟控制2: PCLK分频
// 切换到页0的时钟域
OV2640_WriteReg(0x2D, 0x00); // 时钟分频控制
// bit[6]: PLL使能
// bit[5:0]: PLL预分频
OV2640_WriteReg(0x2D, 0x40 | 0x03); // PLL使能, 预分频=3
// PLL输出 = XVCLK / 预分频 × 倍频
// = 24MHz / 3 × 8 = 64MHz (典型值)
HAL_Delay(5); // 等待PLL锁定
printf("OV2640 PLL配置完成\r\n");
}
```
---
## 四、完整初始化序列(JPEG模式)
```c
// =============================================
// OV2640 上电与复位
// =============================================
void OV2640_HardwareInit(void)
{
// GPIO初始化 (PWDN: PA8, RESET: PB0)
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);
}
// =============================================
// OV2640 JPEG模式初始化(UXGA 1600x1200)
// =============================================
void OV2640_Init_JPEG_UXGA(void)
{
printf("OV2640 JPEG UXGA 初始化...\r\n");
// ---- 软件复位 ----
OV2640_WriteReg_Page(0x00, 0x12, 0x80);
HAL_Delay(100);
// ---- 页0: 基本配置 ----
OV2640_SetPage(0x00);
// COM7: 输出格式和分辨率
OV2640_WriteReg(0x12, 0x00); // 先清零
OV2640_WriteReg(0x12, 0x40); // COM7: UXGA, Raw RGB
// COM9: 增益上限
OV2640_WriteReg(0x14, 0x28);
// COM10: PCLK不反转, HREF不反转
OV2640_WriteReg(0x15, 0x00);
// ---- 时钟配置(内部PLL)----
OV2640_WriteReg(0x11, 0x01); // 内部PLL使能
OV2640_WriteReg(0x2D, 0x00); // 时钟分频
OV2640_WriteReg(0x2D, 0x43); // PLL使能, 预分频=3
// ---- 分辨率窗口: UXGA 1600x1200 ----
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低
// ---- 输出窗口 = UXGA 1600x1200 ----
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
// ---- 切换到 JPEG 模式 ----
OV2640_WriteReg(0x12, 0x40 | 0x20); // COM7: UXGA + JPEG输出
// ---- 页1: JPEG 压缩配置 ----
OV2640_SetPage(0x01);
// JPEG质量 (0x00~0xFF, 越大质量越高)
OV2640_WriteReg(0x30, 0x80); // JPEG质量中高
// ---- 页0: 自动控制 ----
OV2640_SetPage(0x00);
// AEC/AGC
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)
```c
// ESP32-CAM 使用 OV2640
#include "esp_camera.h"
static camera_config_t camera_config = {
.pin_pwdn = 32,
.pin_reset = -1, // OV2640复位通常与ESP32复位共用
.pin_xclk = 0, // ESP32提供XVCLK (20MHz)
.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, // 20MHz XVCLK
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG, // 利用OV2640内置JPEG
.frame_size = FRAMESIZE_UXGA, // 1600x1200
.jpeg_quality = 10, // 0-63, 越小质量越高
.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)
{
// OV2640 特定设置
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);
// fb->buf 包含完整JPEG数据,可直接保存或传输
esp_camera_fb_return(fb);
}
```
---
## 六、STM32 DCMI JPEG 采集
```c
#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);
// DMA配置
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);
}
// JPEG帧完成回调
void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi)
{
// 获取JPEG数据长度
jpeg_len = JPEG_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_dcmi);
HAL_DCMI_Stop(hdcmi);
}
// 采集一帧JPEG
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相关寄存器 |
暂无参考文献