STM32F103实现二维码识别项目详解——OV7670篇

无线手持二维码识别项目是中山大学电子与信息工程学院(微电子学院)的工程应用训练课程的设计要求。项目是基于STM32F103开发的,开源代码:stm32f103zet6-qrcode-detect,bilibili:基于STM32F1的无线手持二维码识别项目。CSDN总目录:基于STM32F103的二维码识别项目。项目移植了Zbar库进行二维码识别,而非使用二维码识别模块。

此篇文章讲述OV7670FIFO模块相关的设计。

文章目录

  • 一、 OV7670简介
  • 二、 接口定义
  • 三、 SCCB时序
  • 四、 OV7670读写原理
  • 一、 OV7670简介

    OV7670是OV(OmniVision)公司生产的一颗1/6寸的CMOS VGA图像传感器。该传感器体积小、工作电压低,提供单片VGA摄像头和影像处理器的所有功能。通过SCCB 总线控制,可以输出整帧、子采样、取窗口等方式的各种分辨率8位影像数据。该产品VGA图像最高达到30帧/秒。用户可以完全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、度、色度等都可以通过SCCB接口编程。OmmiVision 图像传感器应用独有的传感器技术,通过减少或消除光学或电子缺陷如固定图案噪声、托尾、浮散等,提高图像质量,得到清晰的稳定的彩色图像。

    OV7670 的特点有:
    ● 高灵敏度、低电压适合嵌入式应用
    ● 标准的SCCB接口,兼容IC 接口
    ● 支持 RawRGB、RGB(GBR4:2:2,RGB565/RGB555/RGB444),YUV(4:2:2)和 YCbCr(4:2:2)输出格式
    ●支持VGA、CIF,和从CIF到40*30的各种尺寸输出
    ● 支持自动曝光控制、自动增益控制、自动白平衡、自动消除灯光条纹、自动黑电平校准等自动控制功能。同时支持色饱和度、色相、伽马、锐度等设置。
    ● 支持闪光灯
    ● 支持图像缩放

    OV7670的功能框图如下图所示:

    二、 接口定义

    下面先简单介绍一下各个接口的意思。

    ● VSYNC:每次在摄像头写出一帧图之前拉高,表示开始输出。

    ● RCK:每读取一个像素数据给一个上升沿。

    ● WR:拉高表示允许图像写入FIFO。

    ● OE:电源使能

    ● WRST:写复位,回到最开始的位置准备写入FIFO。

    ● RRST:读复位,从最开始的地方开始准备读取FIFO。

    ● SIOC与SIOD:I2C通信。

    二、 寄存器的读写

    对于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},
    	{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
    
    //写寄存器
    //返回值: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时序

    寄存器的读写以及对像素数据的读取都是通过SCCB实现的。SCCB(Serial Camera Control Bus)是OmniVision公司公布的串行相机总线协议。OV开头的相机模块例如OV7670都使用SCCB协议。而事实上,SCCB就是改编版的IIC,完全可以按照IIC来理解。

    总线空闲状态:SIO_D为高电平,SIO_C为高电平;
    起始位:当SIO_C为高电平时,SIO_D出现下降沿,产生一个起始位;
    结束位:当SIO_C为高电平时,SIO_D出现上升沿,产生一个结束位;
    数据有效性:SIO_D线上的数据必须在时钟的高电平时期保持稳定,这时候数据接收方才可以接收数据;数据线的高或低电平状态只有在SIO_C线的时钟信号是低电平时才能改变,这个时候数据发送方才向SIO_D线上发送数据。
    总结:总线空闲状态、起始位、结束位、数据有效性 与I2C一样。


    写时序
    SCCB的写时序和I2C的写时序一样,只不过响应位ACK变成了不关心位X:

    ACK = 0
    NO ACK = 1
    X:Don’t care

    I2C:Start + 器件地址(ACK) + 寄存器地址(ACK) + 写数据(ACK)+ 停止位
    SCCB:Start + 器件地址(X) + 寄存器地址(X) + 写数据(X)+ 停止位

    //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的读时序相当于在I2C读时序的寄存器地址和第二个器件地址中加了一个停止位和起始位。

    I2C:Start + 器件地址(ACK) + 寄存器地址(ACK) + 器件地址(ACK) + 读数据(NO ACK)+ Stop
    SCCB:Start2 + 器件地址(X) + 寄存器地址(X) + Stop1 + Start1 + 器件地址(X) + 读数据(NO ACK)+ Stop2

    //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;
    } 		
    

    四、 OV7670读写原理

    OV7670的图像数据输出(通过D[7:0])就是在PCLK,VSYNC和HREF/HSYNC的控制下进行的。我们看帧输出时序,如下图所示:

    每一帧数据前会有一个VSYNC的脉冲信号,代表这帧数据开始传输,每一行数据都会有一HREF脉冲,不过我们不需要这个。

    下面简单概括 (用人话说) 一下图像输入和输出的过程:

    图像写入过程

    1. 等待帧同步VSYNC信号拉高:两个帧同步信号产生(拉高)说明一帧图片输出完成,中间拉低的时候在传数据。

    2. FIFO复位写指针:WRST先置0再置1。

    3. FIFO写允许:WR置1。

    4. 等待帧同步VSYNC信号再拉高。

    5. FIFO写禁止:WR置0。

    图像读出过程

    1. FIFO读指针复位:RRST先置0再置1。

    2. 输出使能:OE置0。

    3. RCK。

    4. 读数据(RGB565有16位,一次只能读8位,先读高字节再读低字节)。

    5. RCK。

    6. 读数据(两次读完才表示一个像素)。

    7. 重复RCK和读数据的过程直到全部读完。

    8. 读指针复位,输出失能(OE置1)。

    	//核心代码
    	if(ov_sta)//有帧中断更新?
    	{
    		OV7670_RRST=0;				//开始复位读指针 
    		OV7670_RCK_L;
    		OV7670_RCK_H;
    		OV7670_RCK_L;
    		OV7670_RRST=1;				//复位读指针结束 
    		OV7670_RCK_H;
    		for(i=0;i<240;i++)   //此种方式可以兼容任何彩屏,但是速度很慢
    		{
    			for(j=0;j<320;j++)
    			{
    				OV7670_RCK_L;
    				color=GPIOF->IDR&0XFF;	//读数据
    				OV7670_RCK_H; 
    				color<<=8;  
    				OV7670_RCK_L;
    				color|=GPIOF->IDR&0XFF;	//读数据
    				OV7670_RCK_H; 
    				LCD_DrawFRONT_COLOR(i,j,color);
    			}
    		}
    	}
    

    作者:Lanthanesthai.

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32F103实现二维码识别项目详解——OV7670篇

    发表回复