STM32F103实现二维码识别项目详解——OV7670篇
无线手持二维码识别项目是中山大学电子与信息工程学院(微电子学院)的工程应用训练课程的设计要求。项目是基于STM32F103开发的,开源代码:stm32f103zet6-qrcode-detect,bilibili:基于STM32F1的无线手持二维码识别项目。CSDN总目录:基于STM32F103的二维码识别项目。项目移植了Zbar库进行二维码识别,而非使用二维码识别模块。
此篇文章讲述OV7670FIFO模块相关的设计。
文章目录
一、 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脉冲,不过我们不需要这个。
下面简单概括 (用人话说) 一下图像输入和输出的过程:
图像写入过程:
-
等待帧同步VSYNC信号拉高:两个帧同步信号产生(拉高)说明一帧图片输出完成,中间拉低的时候在传数据。
-
FIFO复位写指针:WRST先置0再置1。
-
FIFO写允许:WR置1。
-
等待帧同步VSYNC信号再拉高。
-
FIFO写禁止:WR置0。
图像读出过程:
-
FIFO读指针复位:RRST先置0再置1。
-
输出使能:OE置0。
-
RCK。
-
读数据(RGB565有16位,一次只能读8位,先读高字节再读低字节)。
-
RCK。
-
读数据(两次读完才表示一个像素)。
-
重复RCK和读数据的过程直到全部读完。
-
读指针复位,输出失能(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.