基于MSP430F5438A的OV7670简单驱动程序

        作为一个新手,因为课程设计要求,接触到了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);			
}

猜你喜欢

转载自blog.csdn.net/qm666666/article/details/86484941
今日推荐