目录

硬件选择

一、OV7670核心特性

1. 基本参数

2. 关键优势

二、硬件接口详解

1. 引脚定义与连接

三、SCCB协议基础

1. SCCB与I2C的关系

2. SCCB信号线

四、图像时序基础概念

1. 图像数据的组成

2. 关键时序信号

 五、实操开始

六、实际效果演示


OV7670 是一款由 OmniVision 推出的低成本、低功耗CMOS图像传感器,广泛应用于嵌入式视觉、智能家居、机器人视觉等领域。本文将从 硬件结构核心特性寄存器配置实战开发 进行全面解析,帮助开发者快速上手OV7670。

硬件选择

  • 主控:STM32F103C8T6
  • 摄像头:无FIFO的OV7670
  • 软件:KEIL5,山外多功能助手
  • USB转TTL模块

一、OV7670核心特性

1. 基本参数

  • 分辨率:支持 VGA(640x480) 到 QQVGA(160x120) 多档分辨率。
  • 输出格式:YUV、RGB565/555、GRB 4:2:2 等格式,默认常用 RGB565
  • 帧率:最高 30fps@VGA,可调。
  • 接口:SCCB(兼容I2C)配置接口,8位并行数据输出。
  • 功耗:工作电流约 20mA@3.3V,低功耗模式支持待机。

2. 关键优势

  • 低成本:适合预算有限的嵌入式项目。
  • 灵活性:寄存器可编程配置,支持多种图像处理功能。
  • 易集成:直接输出数字信号,无需外部ADC。

二、硬件接口详解

1. 引脚定义与连接

引脚名称 功能描述 典型连接方式
SIO_C SCCB时钟线 MCU的I2C_SCL
SIO_D SCCB数据线 MCU的I2C_SDA
VSYNC 垂直同步信号 外部中断引脚
HREF 行同步信号 GPIO输入
PCLK 像素时钟输出 定时器捕获引脚
D0-D7 8位数据总线 连续GPIO或硬件接口
XCLK 外部时钟输入(12/24MHz) MCU/PWM输出

三、SCCB协议基础

1. SCCB与I2C的关系

  • 协议兼容性:SCCB协议与I2C高度相似,但存在关键差异:
    • 数据有效性:SCCB数据在时钟线(SCL)低电平时变化(I2C在高电平时稳定)。
    • 停止条件:SCCB写操作无需发送停止信号(STOP),而I2C必须发送。
    • 读操作:SCCB读操作需发送两次START信号,I2C只需一次。
  • 典型应用:SCCB专用于OmniVision摄像头模块(如OV7670、OV2640等)。

2. SCCB信号线

  • SIO_C(SCL):串行时钟线,由主控制器(MCU)生成。
  • SIO_D(SDA):串行数据线,支持双向通信。

SCCB时序图

 图像时序是摄像头传感器输出图像数据的核心机制,决定了图像如何被采集、传输和处理。本文以 OV7670摄像头模块 为例,深入解析其图像时序的工作原理、关键信号及实战应用中的注意事项,帮助开发者掌握图像数据捕获的核心技术。

四、图像时序基础概念

1. 图像数据的组成

  • 帧(Frame):一张完整图像的所有像素集合。
  • 行(Line):图像的一行像素。
  • 像素(Pixel):图像的最小单元,包含颜色和亮度信息。

2. 关键时序信号

  • VSYNC(垂直同步信号):标志一帧图像的开始和结束。
  • HREF(行同步信号):标志一行像素的开始和结束。
  • PCLK(像素时钟):控制每个像素数据的传输节奏。
  • 线(D0-D7):传输像素数据(如RGB565格式)。

这个图片是一些关键时序,在OV7670的手册中会有相对应的。

 五、实操开始

根据上面的了解相信你已经对OV7670有了不错的理解,现在我们就开始实际操作吧。

1. SCCB部分代码
#include "stm32f10x.h"
#include "Delay.h"
#include "sccb.h"
#include "GPIOLIKE51.h"
 
void SCCB_GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10|GPIO_Pin_11;
    GPIO_Init(GPIOB,&GPIO_InitStruct);
}
 
void SCCB_SDA_IN(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE );
    GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Pin=GPIO_Pin_11;
    GPIO_Init(GPIOB,&GPIO_InitStruct);
}
 
void SCCB_SDA_OUT(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE );
    GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Pin=GPIO_Pin_11;
    GPIO_Init(GPIOB,&GPIO_InitStruct );
}

//开始信号
void SCCB_Start(void)
{
    SCCB_SDA=1;       
    SCCB_SCL=1;	    
    delay_us(100);
    SCCB_SDA=0;
    delay_us(100);	 //并不是必须为50us,不要太短即可
    SCCB_SCL=0;	    
}

//结束信号
void SCCB_Stop(void)
{
    SCCB_SDA=0;
    delay_us(100);	 
    SCCB_SCL=1;	
    delay_us(100); 
    SCCB_SDA=1;	
    delay_us(100);
} 
//写数据
uint8_t SCCB_WR_Byte(u8 dat)
{
	uint8_t j,res;	 
	for(j=0;j<8;j++) //循环发送bit7-bit0
	{
		if(dat&0x80)
			SCCB_SDA=1;	
		else
			SCCB_SDA=0;
		dat<<=1;
		delay_us(100);
		SCCB_SCL=1;	
		delay_us(100);
		SCCB_SCL=0;		   
	}			 
	SCCB_SDA_IN();		//设置SDA为输入
	delay_us(100);
	SCCB_SCL=1;			//将SCL置1,此时如果数据已被从机接收,从机将把SDA置0
	delay_us(100);
	if(SCCB_READ_SDA)
		res=1;  //SDA置1,说明从机没有成功接收数据
	else
		res=0;         //发送成功
	SCCB_SCL=0;		 
	SCCB_SDA_OUT();		//设置SDA为输出,为下一个相的输出作准备  
	return res;  
}	

//写数据到寄存器
uint8_t SCCB_WR_Reg(uint8_t reg,uint8_t data)
{
	uint8_t res=0;
	SCCB_Start(); 					//启动传输的标志
	if(SCCB_WR_Byte(SCCB_ID))res=1;	//写入OV7670传感器ID	  
	delay_us(100);
  	if(SCCB_WR_Byte(reg))res=1;		//写寄存器地址
	delay_us(100);
  	if(SCCB_WR_Byte(data))res=1; 	//写要向寄存器写入的数据
  	SCCB_Stop();	                //结束传输的标志
  	return	res;
}		

//应答信号
void SCCB_No_Ack(void)
{
	delay_us(100);
	SCCB_SDA=1;	
	SCCB_SCL=1;	
	delay_us(100);
	SCCB_SCL=0;	
	delay_us(100);
	SCCB_SDA=0;	
	delay_us(100);
}

//读数据
uint8_t SCCB_RD_Byte(void)
{
	uint8_t temp=0,j;    
	SCCB_SDA_IN();		//设置主机SDA连接的IO口为输入
	for(j=8;j>0;j--) 	//循环读取bit7-bit0
	{		     	  
		delay_us(100);
		SCCB_SCL=1;
		temp=temp<<1;
		if(SCCB_READ_SDA)temp++;   //SCCB_READ_SDA是从IO口读到的数据
		delay_us(100);
		SCCB_SCL=0;
	}	
	SCCB_SDA_OUT();		//将主机连接SDA的IO口设置为输出  
	return temp;
}

//读寄存器数据
uint8_t SCCB_RD_Reg(u8 reg)
{
	uint8_t val=0;
    //对应两相写操作
	SCCB_Start(); 				//启动传输
	SCCB_WR_Byte(SCCB_ID);  		  
	delay_us(100); 
  	SCCB_WR_Byte(reg);	  
	delay_us(100);
	SCCB_Stop();             //结束传输
	delay_us(100); 
	//对应两相读操作
	SCCB_Start();            //启动传输
	SCCB_WR_Byte(SCCB_ID|0X01);	 
	delay_us(100);
  	val=SCCB_RD_Byte();		 
  	SCCB_No_Ack();            //读取完8bit数据后的应答
  	SCCB_Stop();              //结束传输
  	return val;
}

这部分代码是OV7670的通信代码,通过这段代码可以配置OV7670的各个寄存器。

2、OV7670的初始化代码

#include "stm32f10x.h"
#include "ov7670.h"
#include "sccb.h"
#include "Delay.h"
#include "mco.h"
#include "usart.h"
#include "stdio.h"
#include "QDTFT_demo.h"

uint8_t OV7670_VS(void)
{
	return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_12);
}
uint8_t OV7670_HREF(void)
{
	return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_11);
}
uint8_t OV7670_PCLK(void)
{
	return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15);
}

// GPIO 初始化函数
void GPIO_Init_OV7670(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    // 启用 GPIOA 和 GPIOB 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);

    // 配置 GPIOA(数据引脚:PA0-PA7)为浮空输入模式(IN_FLOATING)
    GPIO_InitStructure.GPIO_Pin = OV7670_DATA_PIN; // 数据引脚 PA0-PA7
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置为浮空输入模式,用于接收数据
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚工作速度为 50 MHz
    GPIO_Init(OV7670_DATA_PORT, &GPIO_InitStructure);

    // 配置 GPIOA(控制引脚:VSYNC, HREF, PCLK)为上拉输入(IPU)
    GPIO_InitStructure.GPIO_Pin = OV7670_HSYNC_PIN | OV7670_VSYNC_PIN | OV7670_PCLK_PIN; 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置频率为 50 MHz
    GPIO_Init(OV7670_DATA_PORT, &GPIO_InitStructure);
}

//设置图像获取窗口大小函数
void OV7670_config_window(unsigned int startx,unsigned int starty,unsigned int width, unsigned int height){
	unsigned int endx;
	unsigned int endy;// "v*2"必须
	unsigned char temp_reg1, temp_reg2;
	unsigned char temp=0;
	
	endx=(startx+width);
	endy=(starty+height+height);// "v*2"必须
	temp_reg1 = SCCB_RD_Reg(0x03);
	temp_reg1 &= 0xf0;
	temp_reg2  = SCCB_RD_Reg(0x32);
	temp_reg2 &= 0xc0;
	
	// Horizontal
	temp = temp_reg2|((endx&0x7)<<3)|(startx&0x7);
	SCCB_WR_Reg(0x32, temp );
	temp = (startx&0x7F8)>>3;
	SCCB_WR_Reg(0x17, temp );
	temp = (endx&0x7F8)>>3;
	SCCB_WR_Reg(0x18, temp );
	
	// Vertical
	temp =temp_reg1|((endy&0x3)<<2)|(starty&0x3);
	SCCB_WR_Reg(0x03, temp );
	temp = starty>>2;
	SCCB_WR_Reg(0x19, temp );
	temp = endy>>2;
	SCCB_WR_Reg(0x1A, temp );
}

//OV7670 传感器寄存器、初始化相关设置
const u8 ov7670_init_reg_tbl[][2]= 
{   
	/*以下为OV7670 QVGA RGB565参数  */
	{0x3a, 0x04},//dummy
	{0x40, 0xd0},//565   
	{0x12, 0x14},//QVGA,RGB输出

	//输出窗口设置
	{0x32, 0x80},//HREF control	bit[2:0] HREF start 3 LSB	 bit[5:3] HSTOP HREF end 3LSB
	{0x17, 0x16},//HSTART start high 8-bit MSB         
	{0x18, 0x04},//5 HSTOP end high 8-bit
	{0x19, 0x02},
	{0x1a, 0x7b},//0x7a,
	{0x03, 0x06},//0x0a,帧竖直方向控制

	{0x0c, 0x00},
	{0x15, 0x00},//0x00
	{0x3e, 0x00},//10
	{0x70, 0x3a},
	{0x71, 0x35},//0x80 调试彩条
	{0x72, 0x11},
	{0x73, 0x00},//

	{0xa2, 0x02},//15
	{0x11, 0x00},//时钟分频设置,0,不分频.
	{0x7a, 0x20},
	{0x7b, 0x1c},
	{0x7c, 0x28},

	{0x7d, 0x3c},//20
	{0x7e, 0x55},
	{0x7f, 0x68},
	{0x80, 0x76},
	{0x81, 0x80},

	{0x82, 0x88},
	{0x83, 0x8f},
	{0x84, 0x96},
	{0x85, 0xa3},
	{0x86, 0xaf},

	{0x87, 0xc4},//30
	{0x88, 0xd7},
	{0x89, 0xe8},
	{0x13, 0xe0},
	{0x00, 0x00},//AGC

	{0x10, 0x00},
	{0x0d, 0x00},//全窗口, 位[5:4]: 01 半窗口,10 1/4窗口,11 1/4窗口 
	{0x14, 0x28},//0x38, limit the max gain
	{0xa5, 0x05},
	{0xab, 0x07},

	{0x24, 0x75},//40
	{0x25, 0x63},
	{0x26, 0xA5},
	{0x9f, 0x78},
	{0xa0, 0x68},

	{0xa1, 0x03},//0x0b,
	{0xa6, 0xdf},//0xd8,
	{0xa7, 0xdf},//0xd8,
	{0xa8, 0xf0},
	{0xa9, 0x90},

	{0xaa, 0x94},//50
	{0x13, 0xe5},
	{0x0e, 0x61},
	{0x0f, 0x4b},
	{0x16, 0x02},

	{0x1e, 0x27},//图像输出镜像控制.0x07
	{0x21, 0x02},
	{0x22, 0x91},
	{0x29, 0x07},
	{0x33, 0x0b},

	{0x35, 0x0b},//60
	{0x37, 0x1d},
	{0x38, 0x71},
	{0x39, 0x2a},
	{0x3c, 0x78},

	{0x4d, 0x40},
	{0x4e, 0x20},
	{0x69, 0x00},
	{0x6b, 0x60},//PLL*4=48Mhz
	{0x74, 0x19},
	{0x8d, 0x4f},

	{0x8e, 0x00},//70
	{0x8f, 0x00},
	{0x90, 0x00},
	{0x91, 0x00},
	{0x92, 0x00},//0x19,//0x66

	{0x96, 0x00},
	{0x9a, 0x80},
	{0xb0, 0x84},
	{0xb1, 0x0c},
	{0xb2, 0x0e},

	{0xb3, 0x82},//80
	{0xb8, 0x0a},
	{0x43, 0x14},
	{0x44, 0xf0},
	{0x45, 0x34},

	{0x46, 0x58},
	{0x47, 0x28},
	{0x48, 0x3a},
	{0x59, 0x88},
	{0x5a, 0x88},

	{0x5b, 0x44},//90
	{0x5c, 0x67},
	{0x5d, 0x49},
	{0x5e, 0x0e},
	{0x64, 0x04},
	{0x65, 0x20},

	{0x66, 0x05},
	{0x94, 0x04},
	{0x95, 0x08},
	{0x6c, 0x0a},
	{0x6d, 0x55},


	{0x4f, 0x80},
	{0x50, 0x80},
	{0x51, 0x00},
	{0x52, 0x22},
	{0x53, 0x5e},
	{0x54, 0x80},

	//{0x54, 0x40},//110

	{0x09, 0x03},//驱动能力最大

	{0x6e, 0x11},//100
	{0x6f, 0x9f},//0x9e for advance AWB
	{0x55, 0x00},//亮度
	{0x56, 0x40},//对比度 0x40
	{0x57, 0x40},//0x40,  change according to Jim's request
	

	{0x6a, 0x40},
	{0x01, 0x40},
	{0x02, 0x40},
	{0x13, 0xe7},
	{0x15, 0x00},  
	
		
	{0x58, 0x9e},
	
	{0x41, 0x08},
	{0x3f, 0x00},
	{0x75, 0x05},
	{0x76, 0xe1},
	{0x4c, 0x00},
	{0x77, 0x01},
	{0x3d, 0xc2},	
	{0x4b, 0x09},
	{0xc9, 0x60},
	{0x41, 0x38},
	
	{0x34, 0x11},
	{0x3b, 0x02}, 

	{0xa4, 0x89},
	{0x96, 0x00},
	{0x97, 0x30},
	{0x98, 0x20},
	{0x99, 0x30},
	{0x9a, 0x84},
	{0x9b, 0x29},
	{0x9c, 0x03},
	{0x9d, 0x4c},
	{0x9e, 0x3f},
	{0x78, 0x04},
	
	{0x79, 0x01},
	{0xc8, 0xf0},
	{0x79, 0x0f},
	{0xc8, 0x00},
	{0x79, 0x10},
	{0xc8, 0x7e},
	{0x79, 0x0a},
	{0xc8, 0x80},
	{0x79, 0x0b},
	{0xc8, 0x01},
	{0x79, 0x0c},
	{0xc8, 0x0f},
	{0x79, 0x0d},
	{0xc8, 0x20},
	{0x79, 0x09},
	{0xc8, 0x80},
	{0x79, 0x02},
	{0xc8, 0xc0},
	{0x79, 0x03},
	{0xc8, 0x40},
	{0x79, 0x05},
	{0xc8, 0x30},
	{0x79, 0x26}, 
	{0x09, 0x00},
}; 

这部分代码是OV7670的初始化代码,通过这段代码可以进行摄像头的初始化。

3、OV7670的时钟信号代码

//使用PWM给OV7670提供一个24MHz的时钟信号
void TIM1_Config(void) {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    // 使能 TIM1 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // 配置 GPIOA Pin 8 作为 TIM1 CH1
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 定时器时基配置
    TIM_TimeBaseStructure.TIM_Period = 3 - 1; // 产生 24MHz 时钟信号 (72MHz/3 = 24MHz)
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    // PWM 配置
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 1; // 50% 占空比
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM1, &TIM_OCInitStructure);

    TIM_CtrlPWMOutputs(TIM1, ENABLE); // 使能 PWM 输出
    TIM_Cmd(TIM1, ENABLE); // 使能定时器
}

六、实际效果演示

完成以上步骤后就可以使用摄像头来获取图像数据了

下面部分就是效果图

这部分是把图像显示在PC端的山外助手上的 

 这部分是把图像显示在LCD上,可以看到图像的像素并不高,并不是所设置的320*240,是因为C8T6的SRAM只有20K,而一张完整的320*240的大小是150K远远超过了SRAM的大小,所以程序中所获取的图像大小是128*70的,所以会小许多。

发这篇文章主要是因为目前网上关于使用STM32F103C8T6来获取无FIFO的OV7670的图像的教程太少了,所以分享出来给大家,本程序没有使用到DMA,所以图像的显示是十分慢的,几乎5秒左右才会显示一张新图片,后续会加入DMA,这样子图像的显示方面应该会有大幅度的提升,如果有什么不对的都可以联系我进行修改,毕竟博主也是找了不是的资料才将图像显示出来,也是十分的不容易,如果对你有帮助的话,请给我点个赞吧!!!

紧急通告:由于博主的问题,程序还有些Bug没有搞定,所以在这里给大家说说首先是7670引脚连接问题复位以及PWDN引脚,复位引脚直接给个3.3V的电源就行了,PWDN直接接地这两个在手册上都有说明,然后就是在获取图像函数里,如果不使用串口发送到PC端就需要把串口发送那部分的代码注释掉,不然就会导致图像无法获取成功(这个问题博主也不知道为什么,有知道的也可以告诉博主一声),自此感谢大家的支持。

以下附上程序源码
链接: https://pan.baidu.com/s/1IkRv8weTruOkuHlC5VxIEw?pwd=pkmn 提取码: pkmn

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐