作为一个新手,因为课程设计要求,接触到了OV7670。因为课程设计要求使用MSP430F5438A驱动OV7670,而网上大多数都是使用STM32进行驱动。上网查找了很多资料,也仔细看过网上大佬发的相关寄存器的帖子,最后成功使用430单片机完成了设计。本着来源于网络,回馈于网络的原则,在此介绍一下,自己使用OV7670的一点经验。水平有限,偏颇之处,还请包涵!
首先简单的介绍一下所使用的摄像头OV7670。 OV7670是OV(OmniVision)公司生产的一颗1/6寸的CMOS VGA图像传感器。该传感器体积小、工作电压低,提供单片VGA摄像头和影像处理器的所有功能。通过SCCB 总线控制,可以输出整帧、子采样、取窗口等方式的各种分辨率8位影像数据。该产品VGA图像最高达到30帧/秒。用户可以完全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、度、色度等都可以通过SCCB接口编程。OmmiVision 图像传感器应用独有的传感器技术,通过减少或消除光学或电子缺陷如固定图案噪声、托尾、浮散等,提高图像质量,得到清晰的稳定的彩色图像。其具有高灵敏度、低电压适合嵌入式应用,标准的SCCB接口以及支持RGB565格式输出。因为摄像头的像素时钟非常高,直接通过MSP430的IO口读取数据非常困难,也是十分占耗CPU。采用带FIFO模块的OV7670,通过对FIFO的读取,就可以轻松的读取摄像头采集到图像数据。从而满足速度要求,节省CPU。本次采用的OV7670自带有源晶振,不需要外部再提供时钟。一个FIFO芯片的容量是384K字节,可以存储两帧QVGA的图像数据,所以本次设计采用QVGA模式 RGB565格式传输图像数据。接下来介绍具体的驱动程序。
管脚分配如图所示:
宏定义:
//SCL-P1.3,SDA-P1.6
#define SCCB_SIC_H() P1OUT|=BIT3
#define SCCB_SIC_L() P1OUT&=~BIT3
#define SCCB_SID_H() P1OUT|=BIT6
#define SCCB_SID_L() P1OUT&=~BIT6
#define SCCB_SID_IN P1DIR &= ~BIT6
#define SCCB_SID_OUT P1DIR |= BIT6
#define SCCB_SID_STATE P1IN&BIT6
#define OE_L P4OUT &= ~BIT3
#define OE_H P4OUT |= BIT3
#define RCLK_L P4OUT &= ~BIT4
#define RCLK_H P4OUT |= BIT4
#define WEN_L P4OUT &= ~BIT5
#define WEN_H P4OUT |= BIT5
#define WRST_L P4OUT &= ~BIT6
#define WRST_H P4OUT |= BIT6
#define RRST_L P4OUT &= ~BIT7
#define RRST_H P4OUT |= BIT7
//像素存储
#define piexl_w 320
#define piexl_h 240
OV7670初始化程序:
unsigned char ov7670_init(void)
{
unsigned int i=0;
unsigned char temp;
//VSYNC-P1.0
//上拉输入,外部中断
P1DIR &= ~BIT0;
P1REN |= BIT0;
P1OUT |= BIT0;//上拉输入
//FIFO数据输入引脚
//D0-D3--P6.4-P6.7,D4-D7--P7.4--P7.7
//上拉输入
P6DIR &= 0x0f;
P6REN |= 0xf0;
P6OUT |= 0xf0;
P7DIR &= 0x0f;
P7REN |= 0xf0;
P7OUT |= 0xf0;
//OE-P4.3,RCLK-P4.4,WEN-P4.5,WRST-P4.6,RRST-P4.7
//输出
P4DIR |= 0xf8;
P4OUT |= 0xf8;
SCCB_init();
//读写寄存器函数出现错误
if(wr_Sensor_Reg(0x12,0x80)!= 0 ) //Reset SCCB
{
return 1;//错误返回
}
delay_ms(50);
if(rd_Sensor_Reg(0x0b, &temp) != 0)//读ID
{
return 2 ;//错误返回
}
if(temp==0x73)//OV7670
{
for(i=0;i<OV7670_REG_NUM;i++)
{
if(wr_Sensor_Reg(OV7670_reg[i][0],OV7670_reg[i][1]) != 0)
{
return 3;//错误返回
}
}
}
return 0; //ok
}
写寄存器操作函数如下:
//功能:读OV7660寄存器
//返回:0-成功 其他失败
unsigned char rd_Sensor_Reg(unsigned char regID,unsigned char *regDat)
{
//通过写操作设置寄存器地址
startSCCB();
if(SCCBwriteByte(0x42)==0)//写地址
{
return 1;//错误返回
}
delay_us(100);
if(SCCBwriteByte(regID)==0)//积存器ID
{
return 2;//错误返回
}
delay_us(100);
stopSCCB();//发送SCCB 总线停止传输命令
delay_us(100);
//设置寄存器地址后,才是读
startSCCB();
if(SCCBwriteByte(0x43)==0)//读地址
{
return 3;//错误返回
}
delay_us(100);
*regDat=SCCBreadByte();//返回读到的值
noAck();//发送NACK命令
stopSCCB();//发送SCCB 总线停止传输命令
return 0;//成功返回
}
读寄存器操作如下:
//功能:读OV7660寄存器
//返回:0-成功 其他失败
unsigned char rd_Sensor_Reg(unsigned char regID,unsigned char *regDat)
{
//通过写操作设置寄存器地址
startSCCB();
if(SCCBwriteByte(0x42)==0)//写地址
{
return 1;//错误返回
}
delay_us(100);
if(SCCBwriteByte(regID)==0)//积存器ID
{
return 2;//错误返回
}
delay_us(100);
stopSCCB();//发送SCCB 总线停止传输命令
delay_us(100);
//设置寄存器地址后,才是读
startSCCB();
if(SCCBwriteByte(0x43)==0)//读地址
{
return 3;//错误返回
}
delay_us(100);
*regDat=SCCBreadByte();//返回读到的值
noAck();//发送NACK命令
stopSCCB();//发送SCCB 总线停止传输命令
return 0;//成功返回
}
简单的SCCB总线控制协议如下:
/*
-----------------------------------------------
功能: 初始化SCCB端口,SCL-P1.3,输出,SCL-P1.3,输出
参数: 无
返回值: 无
-----------------------------------------------
*/
void SCCB_init(void)
{
//SDA-P1.6,上拉输入
P1DIR &= ~BIT6;
P1REN |= BIT6;
P1OUT |= BIT6;
//SCL-P1.3,输出
P1DIR |= BIT3;
P1OUT |= BIT3;
SCCB_SID_OUT;
}
/*
-----------------------------------------------
功能: start命令,SCCB的起始信号
参数: 无
返回值: 无
-----------------------------------------------
*/
void startSCCB(void)
{
SCCB_SID_H(); //数据线高电平
SCCB_SIC_H(); //在时钟线高的时候数据线由高至低
delay_us(50);
SCCB_SID_L();
delay_us(50);
SCCB_SIC_L(); //时钟恢复低电平,单操作函数必要
}
/*
-----------------------------------------------
功能: stop命令,SCCB的停止信号
参数: 无
返回值: 无
-----------------------------------------------
*/
void stopSCCB(void)
{
SCCB_SID_L();
delay_us(50);
SCCB_SIC_H();
delay_us(50);
SCCB_SID_H();
delay_us(50);
}
/*
-----------------------------------------------
功能: noAck,用于连续读取中的最后一个结束周期
参数: 无
返回值: 无
-----------------------------------------------
*/
void noAck(void)
{
delay_us(50);
SCCB_SID_H();
SCCB_SIC_H();
delay_us(50);
SCCB_SIC_L();
delay_us(50);
SCCB_SID_L();
delay_us(50);
}
/*
-----------------------------------------------
功能: 写入一个字节的数据到SCCB
参数: 写入数据
返回值: 发送成功返回1,发送失败返回0
-----------------------------------------------
*/
unsigned int SCCBwriteByte(unsigned int m_data)
{
unsigned char j,tem;
for(j=0;j<8;j++) //循环8次发送数据
{
if(m_data&0x80)
{
SCCB_SID_H();
}
else
{
SCCB_SID_L();
}
m_data<<=1;
delay_us(50);
SCCB_SIC_H();
delay_us(50);
SCCB_SIC_L();
}
SCCB_SID_IN;/*设置SDA为输入*/
delay_us(50);
SCCB_SIC_H();
delay_us(50);
if(SCCB_SID_STATE){tem=0;} //SDA=1发送失败,返回0}
else {tem=1;} //SDA=0发送成功,返回1
SCCB_SIC_L();
SCCB_SID_OUT;/*设置SDA为输出*/
return tem;
}
/*
-----------------------------------------------
功能: 一个字节数据读取并且返回
参数: 无
返回值: 读取到的数据
-----------------------------------------------
*/
unsigned char SCCBreadByte(void)
{
unsigned char read,j;
read = 0x00;
SCCB_SID_IN;/*设置SDA为输入*/
for(j=8;j>0;j--) //循环8次接收数据
{
delay_us(50);
SCCB_SIC_H();
read=read<<1;
if(SCCB_SID_STATE)
{
read++;
}
delay_us(50);
SCCB_SIC_L();
}
SCCB_SID_OUT;/*设置SDA为输出*/
return read;
}
图像采集最重要的就是FIFO模块如何存储图像数据以及单片机如何读取FIFO模块中的图像数据。其具体的实现步骤如下:
摄像头模块存储图数据的过程为:等待OV7670同步信号、FIFO写指针复位、FIFO写使能、等待第二个OV7670同步信号、FIFO写禁止。
在存储完一帧图像以后,就可以开始读取图像数据了。读取过程为:FIFO读指针复位、给FIFO读时钟(FIFO_RCLK)、读取第一个像素高字节、给FIFO读时钟、读取第一个像素低字节、给FIFO读时钟、读取第二个像素高字节、循环读取剩余像素、结束。
因此使用一个外部中断(设计中为P1.0),来捕获帧同步信号,在中断服务函数中开始将OV7670的图像数据存储在FIFO芯片中。然后在一个帧同步信号到来之后,关闭数据存储。这样一帧数据就存储完成。中断服务函数源码如下:
//FIFO模块存储摄像头数据
#pragma vector = PORT1_VECTOR
__interrupt void PORT1_B0_ISR(void)
{
if(P1IV == 2)
{
WRST_L;//开始复位写指针
WRST_H;//写指针复位结束
if(ov_sta == 0)
{
WEN_H;
ov_sta = 1;
}
else if(ov_sta == 1)
{
WEN_L;
ov_sta = 2;
}
}
P1IFG = 0; //清除标志位
}
当FIFO芯片中一帧图像数据存储完毕就可以在主函数中进行430对FIFO芯片中数据的读取,特别注意,在读取FIFO芯片中的图像数据时,CS位要置低,否则输入管脚为高阻态。根据时序要求编写程序,源码如下:
/* OE AL422 FIFO的输出使能引脚 ,OE为低电平时,允许数据输出 ,
高电平时,数据输出高阻态*/
OE_L;
if(ov_sta == 2)//读数据
{
P1IE &= ~BIT0;//关外部中断
//设置图像分辨率
OV7670_Window_Set(180,10,piexl_w,piexl_h);
RRST_L;//开始复位读指针
RCLK_L;
RCLK_H;
RCLK_L;
RRST_H;//读指针复位结束
RCLK_H;
for(unsigned int p=0;p < piexl_h;p++)//传输图像piexl_w*piexl_h
{
for(unsigned int j=0;j < piexl_w;j++)
{
RCLK_L;
FIFO_1 = P6IN&0xf0;
FIFO_2 = P7IN&0xf0;
FIFO_data = (FIFO_1>>4)|FIFO_2;
data_fifo[0] = FIFO_data;//读取高字节
RCLK_H;
RCLK_L;
FIFO_1 = P6IN&0xf0;//读取低字节
FIFO_2 = P7IN&0xf0;
FIFO_data = (FIFO_1>>4)|FIFO_2;
data_fifo[1] = FIFO_data;
RCLK_H;
}
}
ov_sta = 0;
P1IE |= BIT0;
}
因为采用的是MSP430F5438a单片机,以QVGA模式传输图像数据还是很多,速度很慢。为降低传输的图像数据量,提高数据,借鉴了网上一个设置图像分辨率的函数,源码如下:
//QVGA 分辨率设置
void OV7670_Window_Set(unsigned int sx,unsigned int sy,unsigned int width,unsigned int height)
{
unsigned int endx;
unsigned int endy;
unsigned char temp;
endx=(sx+width*2)%784; // sx:HSTART endx:HSTOP
endy=sy+height*2; // sy:VSTRT endy:VSTOP
rd_Sensor_Reg(0x32,&temp);
temp&=0Xc0;
temp|=((endx&0X07)<<3)|(sx&0X07);
wr_Sensor_Reg(0X032,temp);
wr_Sensor_Reg(0X17,sx>>3);
wr_Sensor_Reg(0X18,endx>>3);
rd_Sensor_Reg(0x03,&temp);
temp&=0Xf0;
temp|=((endy&0X03)<<2)|(sy&0X03);
wr_Sensor_Reg(0X03,temp);
wr_Sensor_Reg(0X19,sy>>2);
wr_Sensor_Reg(0X1A,endy>>2);
}