【单片机开发】无FIFO的OV7670模组在STM32F1平台上的应用

(一) 背景介绍

其实在很久以前我就一直想搞一下摄像头的移植。当时就在淘宝上买了一个没FIFO,OV7670的模块其实当时自己连什么是FIFO都不知道。就看他便宜然后就买了。结果买回来根本不会用而且没有提供驱动。虽然好像正点原子写了一个驱动但是和F1的接口略有不同,以我当时的水平又不能理解。捣鼓了一段时间后就放弃了,当时在论坛里到处找代码也没找到,虽然有人实现了但也没有给源码。之后一会忙考试,一会有要学点东西就把这个东西给忘了。正好自己好久都没有写驱动代码了,这次正好来练练手。其实本来是准备用STM32F4来实现的F4因为外扩了SDRAM,同时可以提供较高的时钟,可以获得比较好的刷屏效果。但是因为涉及到DCMI接口,所以以后再说吧。

(二)硬件介绍

之前看OV7670的接口发现正点原子的接口对不上买的骑飞电子的模块,标记部分不一样,其实本质上还是一样的。先来看一下他们的照片。

从接口上来看区别就在于SCCB口的描述不一样,行同步信号命名不一样,时钟脚不一样。
总结起来就是

                           SIOC =SCL
                            SIOD=SDA
                            WRST=RESET

其实本质上摄像头是DCMI接口,而F1没有所以驱动会有很大的变化。
所以说有没有FIFO在接口上还是区别很大的。虽然不知道为什么同样是OV7670,是否使用FIFO在接口上就产生了 这么大的区别。但是总的来说接口还是可以分为三个部分。
SCCB
时钟输入和输出
并行数据接口

(三)软件实现:

首先当然先写SCCB接口,这个与I2C区别不大。
SCCB.c
SCCB接口底层实现

#include "sys.h"
#include "sccb.h"
#include "delay.h"


//初始化SCCB接口
//CHECK OK
void SCCB_Init(void)
{											   
	 
 	GPIO_InitTypeDef  GPIO_InitStructure;
 	
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG, ENABLE);	 //使能PB端口时钟
	
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;				 // 端口配置
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 		 //输入
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOG, &GPIO_InitStructure);
 	GPIO_SetBits(GPIOG,GPIO_Pin_13);						 // 输出高

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;				 // 端口配置
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //输输出
 	GPIO_Init(GPIOD, &GPIO_InitStructure);
 	GPIO_SetBits(GPIOD,GPIO_Pin_3);						 // 输出高
 
	SCCB_SDA_OUT();	   
}			 

//SCCB起始信号
//当时钟为高的时候,数据线的高到低,为SCCB起始信号
//在激活状态下,SDA和SCL均为低电平
void SCCB_Start(void)
{
    SCCB_SDA=1;     //数据线高电平	   
    SCCB_SCL=1;	    //在时钟线高的时候数据线由高至低
    delay_us(50);  
    SCCB_SDA=0;
    delay_us(50);	 
    SCCB_SCL=0;	    //数据线恢复低电平,单操作函数必要	  
}

//SCCB停止信号
//当时钟为高的时候,数据线的低到高,为SCCB停止信号
//空闲状况下,SDA,SCL均为高电平
void SCCB_Stop(void)
{
    SCCB_SDA=0;
    delay_us(50);	 
    SCCB_SCL=1;	
    delay_us(50); 
    SCCB_SDA=1;	
    delay_us(50);
}  
//产生NA信号
void SCCB_No_Ack(void)
{
	delay_us(50);
	SCCB_SDA=1;	
	SCCB_SCL=1;	
	delay_us(50);
	SCCB_SCL=0;	
	delay_us(50);
	SCCB_SDA=0;	
	delay_us(50);
}
//SCCB,写入一个字节
//返回值:0,成功;1,失败. 
u8 SCCB_WR_Byte(u8 dat)
{
	u8 j,res;	 
	for(j=0;j<8;j++) //循环8次发送数据
	{
		if(dat&0x80)SCCB_SDA=1;	
		else SCCB_SDA=0;
		dat<<=1;
		delay_us(50);
		SCCB_SCL=1;	
		delay_us(50);
		SCCB_SCL=0;		   
	}			 
	SCCB_SDA_IN();		//设置SDA为输入 
	delay_us(50);
	SCCB_SCL=1;			//接收第九位,以判断是否发送成功
	delay_us(50);
	if(SCCB_READ_SDA)res=1;  //SDA=1发送失败,返回1
	else res=0;         //SDA=0发送成功,返回0
	SCCB_SCL=0;		 
	SCCB_SDA_OUT();		//设置SDA为输出    
	return res;  
}	 
//SCCB 读取一个字节
//在SCL的上升沿,数据锁存
//返回值:读到的数据
u8 SCCB_RD_Byte(void)
{
	u8 temp=0,j;    
	SCCB_SDA_IN();		//设置SDA为输入  
	for(j=8;j>0;j--) 	//循环8次接收数据
	{		     	  
		delay_us(50);
		SCCB_SCL=1;
		temp=temp<<1;
		if(SCCB_READ_SDA)temp++;   
		delay_us(50);
		SCCB_SCL=0;
	}	
	SCCB_SDA_OUT();		//设置SDA为输出    
	return temp;
} 							    
//写寄存器
//返回值:0,成功;1,失败.
u8 SCCB_WR_Reg(u8 reg,u8 data)
{
	u8 res=0;
	SCCB_Start(); 					//启动SCCB传输
	if(SCCB_WR_Byte(SCCB_ID))res=1;	//写器件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;
}		  					    
//读寄存器
//返回值:读到的寄存器值
u8 SCCB_RD_Reg(u8 reg)
{
	u8 val=0;
	SCCB_Start(); 				//启动SCCB传输
	SCCB_WR_Byte(SCCB_ID);		//写器件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();
  	SCCB_Stop();
  	return val;
}

SCCB.h

#ifndef __SCCB_H
#define __SCCB_H
#include "sys.h"

#define SCCB_SDA_IN()  {GPIOG->CRH&=0XFF0FFFFF;GPIOG->CRH|=0X00800000;}
#define SCCB_SDA_OUT() {GPIOG->CRH&=0XFF0FFFFF;GPIOG->CRH|=0X00300000;}

//IO操作函数	 
#define SCCB_SCL    		PDout(3)	 	//SCL
#define SCCB_SDA    		PGout(13) 		//SDA	 

#define SCCB_READ_SDA    	PGin(13)  		//输入SDA    
#define SCCB_ID   			0X42  			//OV7670的ID

///////////////////////////////////////////
void SCCB_Init(void);
void SCCB_Start(void);
void SCCB_Stop(void);
void SCCB_No_Ack(void);
u8 SCCB_WR_Byte(u8 dat);
u8 SCCB_RD_Byte(void);
u8 SCCB_WR_Reg(u8 reg,u8 data);
u8 SCCB_RD_Reg(u8 reg);
#endif

注意:实现了软件SCCB的底层后就是模块的初始化了。
所有接口处理都要特别小心。

OV7670.c
摄像头驱动

#include "sys.h"
#include "ov7670.h"
#include "ov7670cfg.h"	  
#include "delay.h"
#include "usart.h"			 
#include "sccb.h"	
#include "lcd.h"




//VSYNC     PG15		(帧同步信号:out)
//HREF			PD6 		(行同步信号:out)
//XCLK			PA8			(时钟信号:in)
//PCLK			PB4			(像素时钟:out)
//PWDN      	PB3			(功耗选择模式  0工作 1POWER DOWN)正常使用拉低,该引脚与JTDO连接,需先关闭
//RESET			PG14		(复位端:0RESET 1一般模式 )正常使用拉高
//SIOC			PD3
//SIOD			PG13
	


//初始化OV7670
//返回0:成功
//返回其他值:错误代码
u8 OV7670_Init(void)
{
	u8 temp;
	u16 i=0;
	 
 	GPIO_InitTypeDef  GPIO_InitStructure;
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG, ENABLE);	 //使能相关端口时钟
	RCC->APB2ENR|=1<<0;  //开启AF时钟
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_15; 	//PG15 输入 上拉					VSYNC
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOG, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4;			//PB4  端口配置			 PCLK
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 		 //浮空上拉输入
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
 	GPIO_SetBits(GPIOB,GPIO_Pin_4);
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6;  	//PD6		HREF		
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 	GPIO_Init(GPIOD, &GPIO_InitStructure);
	GPIO_SetBits(GPIOD,GPIO_Pin_6);
	
	
 	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;				 //PB3 端口配置			PWDN  正常使用拉低
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
 	GPIO_ResetBits(GPIOB,GPIO_Pin_3);
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_14;  		//PG14  RESET		正常使用拉高
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
 	GPIO_Init(GPIOG, &GPIO_InitStructure);
	GPIO_SetBits(GPIOG,GPIO_Pin_14);	


	GPIO_InitStructure.GPIO_Pin  = 0xff; //PC0~7 输入 上拉			D0~8
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;		//GPIO_Mode_IPU;
 	GPIO_Init(GPIOC, &GPIO_InitStructure);
	

  GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);	//SWD   关闭JTAG
	
	CLK_init_ON();


 	SCCB_Init();        		//初始化SCCB 的IO口
 	if(SCCB_WR_Reg(0x12,0x80))return 1;	//复位SCCB
	delay_ms(50); 
	temp=SCCB_RD_Reg(0x12);
	//读取产品型号
 	temp=SCCB_RD_Reg(0x0b);
	if(temp!=0x73)return 2;  
 	temp=SCCB_RD_Reg(0x0a);	
	if(temp!=0x76)return 2;
	//初始化序列	  对OV7670寄存器进行操作
	for(i=0;i<sizeof(ov7670_init_reg_tbl)/sizeof(ov7670_init_reg_tbl[0])/2;i++)
	{
	   	SCCB_WR_Reg(ov7670_init_reg_tbl[i][0],ov7670_init_reg_tbl[i][1]);
		delay_ms(2);
 	}
   	return 0x00; 	//ok
} 

void CLK_init_ON(void)
{
  
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ; 
    GPIO_Init(GPIOA, &GPIO_InitStructure);		//HSE是高速外部时钟,频率范围为4MHz~16MHz   实测出来为8MHz
    RCC_MCOConfig(RCC_MCO_HSE  );//hsi	Selects the clock source to output on MCO pin.   RCC_MCO_HSE: HSE oscillator clock selected
}
/*void CLK_init_OFF(void)
{
  
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}*/


	
//设置图像输出窗口
//对QVGA设置。
void OV7670_Window_Set(u16 sx,u16 sy,u16 width,u16 height)
{
	u16 endx;
	u16 endy;
	u8 temp; 
	endx=sx+width*2;	//V*2
 	endy=sy+height*2;
	if(endy>784)endy-=784;
	temp=SCCB_RD_Reg(0X03);				//读取Vref之前的值
	temp&=0XF0;
	temp|=((endx&0X03)<<2)|(sx&0X03);
	SCCB_WR_Reg(0X03,temp);				//设置Vref的start和end的最低2位
	SCCB_WR_Reg(0X19,sx>>2);			//设置Vref的start高8位
	SCCB_WR_Reg(0X1A,endx>>2);			//设置Vref的end的高8位

	temp=SCCB_RD_Reg(0X32);				//读取Href之前的值
	temp&=0XC0;
	temp|=((endy&0X07)<<3)|(sy&0X07);
	SCCB_WR_Reg(0X17,sy>>3);			//设置Href的start高8位
	SCCB_WR_Reg(0X18,endy>>3);			//设置Href的end的高8位
}


////////////////////////////////////////////////////////////////////////////
//OV7670功能设置
//白平衡设置
//0:自动
//1:太阳sunny
//2,阴天cloudy
//3,办公室office
//4,家里home
void OV7670_Light_Mode(u8 mode)
{
	u8 reg13val=0XE7;//默认就是设置为自动白平衡
	u8 reg01val=0;
	u8 reg02val=0;
	switch(mode)
	{
		case 1://sunny
			reg13val=0XE5;
			reg01val=0X5A;
			reg02val=0X5C;
			break;	
		case 2://cloudy
			reg13val=0XE5;
			reg01val=0X58;
			reg02val=0X60;
			break;	
		case 3://office
			reg13val=0XE5;
			reg01val=0X84;
			reg02val=0X4c;
			break;	
		case 4://home
			reg13val=0XE5;
			reg01val=0X96;
			reg02val=0X40;
			break;	
	}
	SCCB_WR_Reg(0X13,reg13val);//COM8设置 
	SCCB_WR_Reg(0X01,reg01val);//AWB蓝色通道增益 
	SCCB_WR_Reg(0X02,reg02val);//AWB红色通道增益 
}				  
//色度设置
//0:-2
//1:-1
//2,0
//3,1
//4,2
void OV7670_Color_Saturation(u8 sat)
{
	u8 reg4f5054val=0X80;//默认就是sat=2,即不调节色度的设置
 	u8 reg52val=0X22;
	u8 reg53val=0X5E;
 	switch(sat)
	{
		case 0://-2
			reg4f5054val=0X40;  	 
			reg52val=0X11;
			reg53val=0X2F;	 	 
			break;	
		case 1://-1
			reg4f5054val=0X66;	    
			reg52val=0X1B;
			reg53val=0X4B;	  
			break;	
		case 3://1
			reg4f5054val=0X99;	   
			reg52val=0X28;
			reg53val=0X71;	   
			break;	
		case 4://2
			reg4f5054val=0XC0;	   
			reg52val=0X33;
			reg53val=0X8D;	   
			break;	
	}
	SCCB_WR_Reg(0X4F,reg4f5054val);	//色彩矩阵系数1
	SCCB_WR_Reg(0X50,reg4f5054val);	//色彩矩阵系数2 
	SCCB_WR_Reg(0X51,0X00);			//色彩矩阵系数3  
	SCCB_WR_Reg(0X52,reg52val);		//色彩矩阵系数4 
	SCCB_WR_Reg(0X53,reg53val);		//色彩矩阵系数5 
	SCCB_WR_Reg(0X54,reg4f5054val);	//色彩矩阵系数6  
	SCCB_WR_Reg(0X58,0X9E);			//MTXS 
}
//亮度设置
//0:-2
//1:-1
//2,0
//3,1
//4,2
void OV7670_Brightness(u8 bright)
{
	u8 reg55val=0X00;//默认就是bright=2
  	switch(bright)
	{
		case 0://-2
			reg55val=0XB0;	 	 
			break;	
		case 1://-1
			reg55val=0X98;	 	 
			break;	
		case 3://1
			reg55val=0X18;	 	 
			break;	
		case 4://2
			reg55val=0X30;	 	 
			break;	
	}
	SCCB_WR_Reg(0X55,reg55val);	//亮度调节 
}
//对比度设置
//0:-2
//1:-1
//2,0
//3,1
//4,2
void OV7670_Contrast(u8 contrast)
{
	u8 reg56val=0X40;//默认就是contrast=2
  	switch(contrast)
	{
		case 0://-2
			reg56val=0X30;	 	 
			break;	
		case 1://-1
			reg56val=0X38;	 	 
			break;	
		case 3://1
			reg56val=0X50;	 	 
			break;	
		case 4://2
			reg56val=0X60;	 	 
			break;	
	}
	SCCB_WR_Reg(0X56,reg56val);	//对比度调节 
}
//特效设置
//0:普通模式    
//1,负片
//2,黑白   
//3,偏红色
//4,偏绿色
//5,偏蓝色
//6,复古	    
void OV7670_Special_Effects(u8 eft)
{
	u8 reg3aval=0X04;//默认为普通模式
	u8 reg67val=0XC0;
	u8 reg68val=0X80;
	switch(eft)
	{
		case 1://负片
			reg3aval=0X24;
			reg67val=0X80;
			reg68val=0X80;
			break;	
		case 2://黑白
			reg3aval=0X14;
			reg67val=0X80;
			reg68val=0X80;
			break;	
		case 3://偏红色
			reg3aval=0X14;
			reg67val=0Xc0;
			reg68val=0X80;
			break;	
		case 4://偏绿色
			reg3aval=0X14;
			reg67val=0X40;
			reg68val=0X40;
			break;	
		case 5://偏蓝色
			reg3aval=0X14;
			reg67val=0X80;
			reg68val=0XC0;
			break;	
		case 6://复古
			reg3aval=0X14;
			reg67val=0XA0;
			reg68val=0X40;
			break;	 
	}
	SCCB_WR_Reg(0X3A,reg3aval);//TSLB设置 
	SCCB_WR_Reg(0X68,reg67val);//MANU,手动U值 
	SCCB_WR_Reg(0X67,reg68val);//MANV,手动V值 
}	


/*
//更新LCD显示
void camera_refresh(void)
{
	u32 i,j;
 	u16 color;	 
		
		LCD_Scan_Dir(U2D_L2R);		//从上到下,从左到右 
		LCD_SetCursor(0x00,0x0000);	//设置光标位置 
		LCD_WriteRAM_Prepare();     //开始写入GRAM	
	
		while(OV7670_VSYNC==0);//0-1
    while(OV7670_VSYNC==1);//1-0		只有在VSYNC为低时,才传输数据	

		//240*320=76800    又每个像素用RGB565表示,即每个像素占用两个字节(16位数据)  I2C一次传输8位数据 故一个像素要传2次
		//将读取到的数据按RGB565的处理,保存到一个16位(color)变量中
	for(i=0;i<240;i++)
	{
		while(OV7670_HREF==0);//0-1	只有在HREF为高时,才传输数据
		for(j=0;j<320;j++)		
		{
			
			//读取高8位
			while(OV7670_PCLK==0);			//0-1			数据在PCLK上升沿保持稳定,在此时读取数据	
			color=GPIOC->IDR&0XFF;	//读数据
			while(OV7670_PCLK==1);		//1-0
			//左移高八位
			color<<=8;  
			//读取低8位
			while(OV7670_PCLK==0);			//0-1
			color|=GPIOC->IDR&0XFF;	//读数据
			while(OV7670_PCLK==1);		//1-0
			//显示LCD
			LCD->LCD_RAM=color;    
		}   
		while(OV7670_HREF==1); //1-0 
	}		
	LCD_Scan_Dir(DFT_SCAN_DIR);	//恢复默认扫描方向 
}	
*/



}	
*/

OV7670.h

	#ifndef _OV7670_H
#define _OV7670_H
#include "sys.h"
#include "sccb.h"



//VSYNC     PG15	(帧同步信号:out)
//HREF			PD6 	(行同步信号:out)
//XCLK			PA8		(时钟信号:in)
//PCLK			PB4		(像素时钟:out)
//PWDN      PB3		(功耗选择模式  0工作 1POWER DOWN)正常使用拉低
//RESET			PG14	(复位端:0RESET 1一般模式 )正常使用拉高

#define OV7670_VSYNC 	PGin(15)
#define OV7670_HREF		PDin(6)
#define	OV7670_XCLK		PAin(8)
#define	OV7670_PCLK		PBin(4)
#define	OV7670_PWDN		PBin(3)
#define	OV7670_RESET	PGin(14)

#define OV7670_DATA   GPIO_ReadInputData(GPIOC,0x00FF) 					//数据输入端口
//GPIOC->IDR&0x00FF 
/////////////////////////////////////////
#define CHANGE_REG_NUM 							171			//需要配置的寄存器总数		  
extern const u8 ov7670_init_reg_tbl[CHANGE_REG_NUM][2];		//寄存器及其配置表
	    				 
u8   OV7670_Init(void);		  	   		 

void OV7670_Window_Set(u16 sx,u16 sy,u16 width,u16 height);
void CLK_init_ON(void);
void CLK_init_OFF(void);
void timer_init(void);	



#endif
OV7670cfg.h

//初始化寄存器序列及其对应的值
const u8 ov7670_init_reg_tbl[][2]= 
{   
     //以下为OV7670 QVGA RGB565参数  
  	{0x3a, 0x04},	//
	{0x40, 0x10},  //设置数据位RGB565 00-FF
	//{0x40, 0xd0},
	{0x12, 0x14},//QVGA,RGB输出******************************************

	//输出窗口设置
	{0x32, 0x80},
	{0x17, 0x16},         
	{0x18, 0x04},//5
	{0x19, 0x02},
	{0x1a, 0x7a},//0x7a,
 	{0x03, 0x0a},//0x0a,


	{0x0c, 0x0c},
	{0x15, 0x00},
	{0x3e, 0x00},//10		pclk不分频
	{0x70, 0x00},
	{0x71, 0x01},
	{0x72, 0x11},
	{0x73, 0x09},//
        
	{0xa2, 0x02},//15
	{0x11, 0x04},//内部时钟分频设置,0,不分频.*******************************************************04
	{0x7a, 0x20},  //@0x12
	{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},//@(0xe4)使能AGC				
	{0x00, 0x00},//AGC
        
	{0x10, 0x00},
	{0x0d, 0x00}, 
	{0x14, 0x20},//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, 0x17},//图像输出镜像控制.0x07,  0x37水平镜像+竖直翻转 0x27
	{0x21, 0x02},
	{0x22, 0x91},
	{0x29, 0x07},
	{0x33, 0x0b},
        
	{0x35, 0x0b},//60
	{0x37, 0x1d},
	{0x38, 0x71},
	{0x39, 0x2a},
	{0x3c, 0x78},
        
	{0x4d, 0x40},
	{0x4e, 0x20},
	{0x69, 0x5d},
	{0x6b, 0x40},//PLL*4=48Mhz           40
	{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},//对比度
    {0x57, 0x80},//0x40,  change according to Jim's request	
}; 

//初始化寄存器序列及其对应的值
const u8 ov7670_init_reg_tb2[][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},
	{0x72, 0x11},
	{0x73, 0x00},//

	{0xa2, 0x02},//15
	{0x11, 0x81},//时钟分频设置,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, 0x40},//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
///////////////////////////////////////////////////////////////////////
//以下部分代码由开源电子网网友:duanzhang512 提出
//添加此部分代码将可以获得更好的成像效果,但是最下面一行会有蓝色的抖动.
//如不想要,可以屏蔽此部分代码.然后将:OV7670_Window_Set(12,176,240,320);
//改为:OV7670_Window_Set(12,174,240,320);,即可去掉最下一行的蓝色抖动
//	{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},
///////////////////////////////////////////////////////////////////////
	
};

#endif

这样底层其实就差不多了。

然后再加上一些图像处理函数

#include "PicHandle.h"
#include "lcd.h"


//二值化阈值
#define threshold 32767



int MaxRed=0;										//关于红色的HSV加权最大值
int Red_X,Red_Y;								//记录最红最亮的点的坐标值
float PixelNums;								//最红最亮的点与中心点的像素点数
float Final_Dis;								//记录距离


//float abs(float x)
//{
//    if(x<0) x=0-x;
//    return x;
//}

//
//float sin(float x)
//{

//    const float B = 1.2732395447;
//    const float C = -0.4052847346;
//    const float P = 0.2310792853;              //0.225;
//    float y = B * x + C * x * abs(x);
//    y = P * (y * abs(y) - y) + y;
//    return y;
//}

////
//float cos(float x)
//{
//    const float Q = 1.5707963268;
//    const float PI =3.1415926536;
//    x += Q;

//    if(x > PI)
//    x -= 2 * PI;

//    return( sin(x));
//}

////
//float tan(float y)          //input radian
//{
//    float tan1;
//    tan1=sin(y)/cos(y);
//    return tan1;
//}


/***********************************************
*name:          SqrtByNewton(float x)
*parameter:     float x
*function:      square root
*return:        the result of square root
*reserved:
***********************************************/
float SqrtByNewton(float x)
{

	int temp=0x1fc00000+((*(int *)&x)>>1);
	float val=*(float*)&temp;

	val =(val + x/val) / 2;
	val =(val + x/val) / 2;
	val =(val + x/val) / 2;

	return val;
}



/***********************************************
*name:         	int RGB2HSV(int R,int G,int B)
*parameter:    	int R,int G,int B
*function:     	convert RGB to HSV
*return:       	返回关于HSV对于红色的一个加权
*reserved:
***********************************************/
int RGB2HSV(int R,int G,int B)						//R'G'B变量类型可改。
{
	float maxRGB,minRGB,des;
	float H,S,V;
	int Hi,Si,Vi;
	int RedHSV;

	maxRGB=((R>=G)?R:G);							//the maximum
	maxRGB=((maxRGB>=B)?maxRGB:B);
	minRGB=((R<=G)?R:G);							//the minimum
	minRGB=((minRGB<=B)?minRGB:B);
	des=maxRGB-minRGB;

	V=maxRGB;													//the value of V
	Vi=(int)(V*366/255);							//force to int

	if(maxRGB==0)											//the value of S
		S=0;
	else
		S=1-minRGB/maxRGB;
	Si=(int)(S*360);									//force to int

	if(maxRGB==minRGB)								//the value of H
		H=0;
	else if((maxRGB==R)&&(G>=B))
		H=60*(G-B)/des;
	else if((maxRGB==R)&&(G<B))
		H=60*(G-B)/des+360;
	else if(maxRGB==G)
		H=60*(B-R)/des+120;
	else if(maxRGB==B)
		H=60*(R-G)/des+240;
	else
		;
	if(H<0)
		H=H+360;

	if((H>=0)&&(H<=60))								//对于红色进行加权
		H=60-H;
	else if((H>=300)&&(H<=360))
		H=360-H;
	else
		H=0;
	Hi=(int)H*60;

	if((H>60)&&(H<300))								//the color is not red
		RedHSV=0;
	else											//the color is red
		RedHSV=Hi+Si+Vi;
	return(RedHSV);
}


/***********************************************
*name:
*parameter:
*function:
*return:
*reserved:
***********************************************/
void PicHandle(int sta_y,unsigned int *RGB)
{
	int j;
	int maxRed1;
	int RGB_R,RGB_G,RGB_B;							//the separate value
	float xx,yy,pixelnum;								//the transitional value
	
	for(j=0;j<320;j++)
	{
		RGB_R=(RGB[j]&RGB565_R)>>11;
		RGB_G=(RGB[j]&RGB565_G)>>5;
		RGB_B=(RGB[j]&RGB565_B);
	
		maxRed1=RGB2HSV(RGB_R,RGB_G,RGB_B);
	
		if(MaxRed<maxRed1)
		{
			MaxRed=maxRed1;
			xx=(float)j;
			yy=(float)sta_y;
		}
	}
	pixelnum=(xx-159)*(xx-159)+(yy-119)*(yy-119);		//(159,119)
	pixelnum=SqrtByNewton(pixelnum);
	Red_X=(int)xx;																	//返回x,y值,在屏上做十字标记
	Red_Y=(int)yy;
	PixelNums=pixelnum;
	Drawx(Red_X,Red_Y);
	
	return;
}

//该函数用于计算输入颜色与实际的差距
short int Getsub(u16 RGBReal,u16 RGBCompare)
{
  if((RGBReal-RGBCompare)<0)
  return  (RGBCompare-RGBReal); 
  else
  return  (RGBReal-RGBCompare); 
}	




/***********************************************
*name:          Distance(float pixelnum)
*parameter:     float pixelnum//光斑到中心的像素点
*function:      return the distance/cm
*return:        the distance;unit:cm.
*reserved:
***********************************************/
void Distance(void)
{
    float dis;
    dis=RPC*PixelNums+RO;				
    dis=tan(dis);
    dis=(L2C_H/dis);
		Final_Dis=dis;
    return;
}


//二值化算法
u16 Binary(u16 pixel)
{
        static u16 Gray;
	    u8 R,G,B;
        /*******提取R,G,B值*******/
        R = (pixel&RGB565_R)>>11;
        G = (pixel&RGB565_G)>>5;
        B = (pixel&RGB565_B);
        /*******灰度值计算*******/
        /*******网络上大部分的公式是针对8位的*******/
        /*******这条公式是针对12位的******/
        Gray = (u16)((R*634+G*613+B*232));
        /*******二值化*******/
        if(Gray<threshold)
                Gray = BLACK;
        else if(Gray>=threshold)
                Gray = WHITE;
        return Gray;
}
//移位法进行灰度化
u16 GRB2GRey(u16 pixel)
{
        static u16 Gray;
	    u8 R,G,B;
	    u8 temp;
        /*******提取R,G,B值*******/
        R = (pixel&RGB565_R)>>11;
        G = (pixel&RGB565_G)>>5;
        B = (pixel&RGB565_B);
        temp =(R*28+G*151+B*77)>>8;
        Gray = (u16)(((temp)&0xf8)>>3)|(((temp)&0xfc)<<3)|(((temp)&0xf8)<<8) ;
	    return Gray;
}

//阈值变换
//参数说明:
//LPSTR lpDIBBits:指向源DIB图像指针
//LONG  lWidth:源图像宽度(象素数)
//LONG  lHeight:源图像高度(象素数)
//BYTE  bThre:阈值
//程序说明:
//该函数用来对图像进行阈值变换。对于灰度值小于阈值的象素直接设置
//灰度值为0;灰度值大于阈值的象素直接设置为255。
u16 ThresholdTrans(u16 pixel, u16 bThre)
{

   // 判断是否小于阈值
   if (pixel< bThre)
   {
    // 直接赋值为0
      pixel = 0;
   }
   else
   {
    // 直接赋值为255
       pixel= 255;
    }
    return pixel;
}

然后就差不多了。

main.c

#include "sys.h"
#include "delay.h"
#include "usart.h" 
#include "led.h" 		 	 
#include "lcd.h"  
#include "key.h"     
#include "malloc.h"
#include "sdio_sdcard.h"  
#include "w25qxx.h"    
#include "ff.h"  
#include "exfuns.h"   
#include "text.h"
#include "piclib.h"	
#include "string.h"		
#include "math.h"	 
#include "ov7670.h" 
#include "timer.h" 
#include "PicHandle.h"
 
/*****************************************
KEY0:拍照
KEY1:灰度图,二值化
KEY_UP:将BMP位图上传到电脑
********************************************/
 

 
u16 Row[320];            //用于存储一行像素信息
//u16 Color[240][320];   //用于存储一张图片240*320像素的信息
u16 s_xfact;      //用于记录特征点位置
u16 s_yfact;

//文件名自增(避免覆盖)
//组合成:形如"0:PHOTO/PIC13141.bmp"的文件名
void camera_new_pathname(u8 *pname)
{	 
	u8 res;					 
	u16 index=0;
	while(index<0XFFFF)
	{
		sprintf((char*)pname,"0:PHOTO/PIC%05d.bmp",index);
		res=f_open(ftemp,(const TCHAR*)pname,FA_READ);//尝试打开这个文件
		if(res==FR_NO_FILE)break;		//该文件名不存在=正是我们需要的.
		index++;
	}
}


 int main(void)
 {	 
	u8 res;							 
	u8 *pname;				//带路径的文件名 
	u8 key;					//键值		   
	u8 i;						 
	u8 sd_ok=1;				//0,sd卡不正常;1,SD卡正常.
	u8 stopflag=0;
	u8 effect=0;	    
	u8 flag=0;
	u16 pixcnt=0;				//像素统计
	u16 linecnt=0;				//行数统计 
	delay_init();	    	 //延时函数初始化	  
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为115200
 	LED_Init();		  			//初始化与LED连接的硬件接口
	KEY_Init();					//初始化按键
	LCD_Init();			   		//初始化LCD     
	W25QXX_Init();				//初始化W25Q128
 	my_mem_init(SRAMIN);		//初始化内部内存池
	exfuns_init();				//为fatfs相关变量申请内存  
 	f_mount(fs[0],"0:",1); 		//挂载SD卡 
 	f_mount(fs[1],"1:",1); 		//挂载FLASH. 
	POINT_COLOR=RED;      
	res=f_mkdir("0:/PHOTO");		//创建PHOTO文件夹
	LCD_Scan_Dir(U2D_L2R);									   						    
 	pname=mymalloc(SRAMIN,30);	//为带路径的文件名分配30个字节的内存		    
 	while(pname==NULL)			//内存分配出错
 	{	    			  
	}   											  
	while(OV7670_Init())//初始化OV7670
	{
	}

	delay_ms(1500);	 		 
	OV7670_Window_Set(10,174,240,320);	//设置窗口  			    		    
	LCD_Clear(BLACK);
 	
	
	
	
	while(1)
	{	
		key=KEY_Scan(0);//不支持连按
		switch(key)		
		{		
					case KEY0_PRES:		
				   {
					if(sd_ok)
					{
						LED1=0;	//点亮DS1,提示正在拍照
						camera_new_pathname(pname);//得到文件名		    
						if(bmp_encode(pname,(lcddev.width-240)/2,(lcddev.height-320)/2,240,320,0))//拍照有误
						{	 
						}
					}
					LED1=1;//关闭DS1
					delay_ms(1800);//等待1.8秒钟
					  }break;					
					case KEY1_PRES:
					{
					  flag++;
					  if(flag==3)
					  {flag=0;}				  
					}break;
		  }

			
			
					LCD_SetCursor(0x00,0x0000);	//设置光标位置 
		LCD_WriteRAM_Prepare();     //开始写入GRAM	
		while(OV7670_VSYNC==0);//
		while(OV7670_VSYNC==1);//		只有在VSYNC为低时,才传输数据	
 	
		for(linecnt=0;linecnt<240;linecnt++)		
		{
			while(OV7670_HREF==0);
			for(pixcnt=0;pixcnt<320;pixcnt++)
			{
				while(OV7670_PCLK==0);
				Row[pixcnt]=GPIOC->IDR&0XFF;
				while(OV7670_PCLK==1); 
				Row[pixcnt]<<=8;
				while(OV7670_PCLK==0);  
				Row[pixcnt]|=GPIOC->IDR&0XFF; 
				while(OV7670_PCLK==1);
                				
			} 	
			for(pixcnt=0;pixcnt<320;pixcnt++)
			{
				if(flag==0)LCD->LCD_RAM=Row[pixcnt];
				else if(flag==1)LCD->LCD_RAM=Binary(Row[pixcnt]);//RGB565二值化算法
				else LCD->LCD_RAM=GRB2GRey(Row[pixcnt]);//灰度化算法	
			}
			
			
	   }	   										    
    }
}

(四)效果展示

我尝试了两种数据传输方式,DMA,和直接写发现对速度影响不大,最后还是采用直接传输方式,实测帧率在4-5之间,跟PPT一样。
看一下最后的效果图 。
【单片机开发】无FIFO的OV7670模组在STM32F1平台上的应用_第1张图片
【单片机开发】无FIFO的OV7670模组在STM32F1平台上的应用_第2张图片

【单片机开发】无FIFO的OV7670模组在STM32F1平台上的应用_第3张图片
好了之前的遗憾也算弥补,希望对大家有所帮助。
下面是源码链接:

无FIFO的OV7670在STM32平台应用

你可能感兴趣的:(STM32开发技术总结)