图
41.2.2 OV7670摄像头模块与开发板连接实物图41.3
软件设计打开上一章的工程,首先在HARDWARE文件夹下新建一个OV7670的文件夹。然后新建如下文件:ov7670.c、sccb.c、ov7670.h、sccb.h、ov7670cfg.h等5个文件,将他们保存在OV7670文件夹下,并将这个文件夹加入头文件包含路径。
本章总共新增了5个文件,代码比较多,我们就不一一列出了,仅挑两个重要的地方进行讲解。首先,我们来看ov7670.c里面的OV7670_Init函数,该函数代码如下:
u8 OV7670_Init(void)
{
u8 temp; u16 i=0;
//设置IO
RCC->APB2ENR|=1<<2; //先使能外设PORTA时钟
RCC->APB2ENR|=1<<3; //先使能外设PORTB时钟
RCC->APB2ENR|=1<<4; //先使能外设PORTC时钟
RCC->APB2ENR|=1<<5; //先使能外设PORTD时钟
RCC->APB2ENR|=1<<8; //先使能外设PORTG时钟
GPIOA->CRH&=0XFFFFFFF0;
GPIOA->CRH|=0X00000008; //PA8 输入
GPIOA->ODR|=1<<8;
GPIOB->CRL&=0XFFF00FFF;
GPIOB->CRL|=0X00033000; //PB3/4 输出
GPIOB->ODR|=3<<3;
GPIOC->CRL=0X88888888; //PC0~7 输入
GPIOC->ODR|=0x00ff;
GPIOD->CRL&=0XF0FFFFFF; //PD6 输出
GPIOD->CRL|=0X03000000;
GPIOD->ODR|=1<<6;
GPIOG->CRH&=0X00FFFFFF;
GPIOG->CRH|=0X33000000;
GPIOG->ODR=7<<14; //PG14/15 输出高
JTAG_Set(SWD_ENABLE);
SCCB_Init(); //初始化SCCB 的IO口
if(SCCB_WR_Reg(0x12,0x80))return 1; //复位SCCB
delay_ms(50);
//读取产品型号
temp=SCCB_RD_Reg(0x0b);
if(temp!=0x73)return 2;
temp=SCCB_RD_Reg(0x0a);
if(temp!=0x76)return 2;
//初始化序列
for(i=0;iSR&0X0001)//溢出中断
{
printf("frame:%dfps",ov_frame); //打印帧率
ov_frame=0;
}
TIM6->SR&=~(1<<0);//清除中断标志位
}
//基本定时器6中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM6_Int_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<4;//TIM6时钟使能
TIM6->ARR=arr; //设定计数器自动重装值//刚好1ms
TIM6->PSC=psc; //预分频器7200,得到10Khz的计数时钟
TIM6->DIER|=1<<0; //允许更新中断
TIM6->CR1|=0x01; //使能定时器3
MY_NVIC_Init(1,3,TIM6_IRQChannel,2);//抢占1,子优先级3,组2
}
这里,我们用到基本定时器TIM6来统计帧率,也就是1秒钟中断一次,打印ov_frame的值,ov_frame用于统计LCD帧率。
再在timer.h里面添加TIM6_Int_Init函数的定义,就完成对timer.c和timer.h的修改了。
在exti.c里面添加EXTI8_Init和EXTI9_5_IRQHandler函数,用于OV7670模块的FIFO写控制,exti.c文件新增部分代码如下:
u8 ov_sta;
//外部中断5~9服务程序
void EXTI9_5_IRQHandler(void)
{
if(EXTI->PR&(1<<8))//是8线的中断
{
if(ov_sta<2)
{
if(ov_sta==0)
{
OV7670_WRST=0; //复位写指针
OV7670_WRST=1; OV7670_WREN=1;//允许写入FIFO
}else
{
OV7670_WREN=0; //禁止写入FIFO
OV7670_WRST=0; OV7670_WRST=1;//复位写指针
}
ov_sta++;
}
}
EXTI->PR=1<<8; //清除LINE8上的中断标志位
}
//外部中断8初始化
void EXTI8_Init(void)
{
Ex_NVIC_Config(GPIO_A,8,RTIR); //任意边沿触发
MY_NVIC_Init(0,0,EXTI9_5_IRQChannel,2); //抢占0,子优先级0,组2
}
因为OV7670的帧同步信号(OV_VSYNC)接在PA8上面,所以我们这里配置PA8作为中端输入,因为STM32的外部中断5~9共用一个中端服务函数(EXTI9_5_IRQHandler),所以在该函数里面,我们需要先判断中断是不是来自中断线8的,然后再做处理。
中断处理部分很简单,通过一个ov_sta来控制OV7670模块的FIFO写操作。当ov_sta=0的时候,表示FIFO存储的数据已经被成功读取了(ov_sta在读完FIFO数据的时候被清零),然后只要OV_VSYNC信号到来,我们就先复位一下写指针,然后ov_sta=1,标志着写指针已经复位,目前正在往FIFO里面写数据。再等下一个OV_VSYNC到来,也就表明一帧数据已经存储完毕了,此时我们设置OV7670_WREN为0,禁止再往OV7670写入数据,此时ov_sta自增为2。其他程序,只要读到ov_sta为2,就表示一帧数据已经准备好了,可以读出,在读完数据之后,程序设置ov_sta为0,则开启下一轮FIFO数据存储。
再在exti.h里面添加EXTI8_Init函数的定义,就完成对exti.c和exti.h的修改了。
最后,打开test.c文件,修改代码如下:
const u8*LMODE_TBL[5]={"Auto","Sunny","Cloudy","Office","Home"}; //5种光照模式
const u8*EFFECTS_TBL[7]={"Normal","Negative","B&W","Redish","Greenish","Bluish",
"Antique"};//7种特效
extern u8 ov_sta; //在exit.c里面定义
extern u8 ov_frame; //在timer.c里面定义
//更新LCD显示
void camera_refresh(void)
{
u32 j; u16 color;
if(ov_sta==2)
{
LCD_Scan_Dir(U2D_L2R); //从上到下,从左到右
LCD_SetCursor(0x00,0x0000); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
OV7670_RRST=0; //开始复位读指针
OV7670_RCK=0;
OV7670_RCK=1;
OV7670_RCK=0;
OV7670_RRST=1; //复位读指针结束
OV7670_RCK=1;
for(j=0;j<76800;j++)
{
OV7670_RCK=0;
color=GPIOC->IDR&0XFF; //读数据
OV7670_RCK=1;
color<<=8;
OV7670_RCK=0;
color|=GPIOC->IDR&0XFF; //读数据
OV7670_RCK=1;
LCD->LCD_RAM=color;
}
EXTI->PR=1<<8; //清除LINE8上的中断标志位
ov_sta=0; //开始下一次采集
ov_frame++;
LCD_Scan_Dir(DFT_SCAN_DIR); //恢复默认扫描方向
}
}
int main(void)
{
u8 key; u8 effect=0; u8 i=0; u8 tm=0;
u8 lightmode=0,saturation=2,brightness=2,contrast=2;
u8 msgbuf[15]; //消息缓存区
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600); //串口初始化为9600
delay_init(72); //延时初始化
OV7670_Init();
LED_Init(); //初始化与LED连接的硬件接口
LCD_Init(); //初始化LCD
usmart_dev.init(72); //初始化USMART
KEY_Init(); //按键初始化
TPAD_Init(72); //触摸按键初始化
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"WarShip STM32");
LCD_ShowString(60,70,200,16,16,"OV7670 TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2012/9/14");
LCD_ShowString(60,130,200,16,16,"KEY0:Light Mode");
LCD_ShowString(60,150,200,16,16,"KEY1:Saturation");
LCD_ShowString(60,170,200,16,16,"KEY2:Brightness");
LCD_ShowString(60,190,200,16,16,"KEY_UP:Contrast");
LCD_ShowString(60,210,200,16,16,"TPAD:Effects");
LCD_ShowString(60,230,200,16,16,"OV7670 Init...");
while(OV7670_Init())//初始化OV7670
{
LCD_ShowString(60,230,200,16,16,"OV7670 Error!!"); delay_ms(200);
LCD_Fill(60,230,239,246,WHITE); delay_ms(200);
}
LCD_ShowString(60,230,200,16,16,"OV7670 Init OK");
delay_ms(1500);
OV7670_Light_Mode(lightmode);
OV7670_Color_Saturation(saturation);
OV7670_Brightness(brightness);
OV7670_Contrast(contrast);
OV7670_Special_Effects(effect);
TIM6_Int_Init(10000,7199); //10Khz计数频率,1秒钟中断
EXTI8_Init(); //使能定时器捕获
OV7670_Window_Set(10,174,240,320); //设置窗口
OV7670_CS=0;
while(1)
{
key=KEY_Scan(0);//不支持连按
if(key)
{
tm=20;
switch(key)
{
case KEY_RIGHT: //灯光模式Light Mode
lightmode++;
if(lightmode>4)lightmode=0;
OV7670_Light_Mode(lightmode);
sprintf((char*)msgbuf,"%s",LMODE_TBL[lightmode]);
break;
case KEY_DOWN: //饱和度Saturation
saturation++;
if(saturation>4)saturation=0;
OV7670_Color_Saturation(saturation);
sprintf((char*)msgbuf,"Saturation:%d",(signed char)saturation-2);
break;
case KEY_LEFT: //亮度Brightness
brightness++;
if(brightness>4)brightness=0;
OV7670_Brightness(brightness);
sprintf((char*)msgbuf,"Brightness:%d",(signed char)brightness-2);
break;
case KEY_UP: //对比度Contrast
contrast++;
if(contrast>4)contrast=0;
OV7670_Contrast(contrast);
sprintf((char*)msgbuf,"Contrast:%d",(signed char)contrast-2);
break;
}
}
if(TPAD_Scan(0))//检测到触摸按键
{
effect++;
if(effect>6)effect=0;
OV7670_Special_Effects(effect);//设置特效
sprintf((char*)msgbuf,"%s",EFFECTS_TBL[effect]);
tm=20;
}
camera_refresh();//更新显示
if(tm) { LCD_ShowString(60,60,200,16,16,msgbuf); tm--;}
i++;
if(i==15) { i=0;LED0=!LED0;} //DS0闪烁.
}
}
此部分代码除了mian函数,还有一个camera_refresh函数,该函数用于将摄像头模块FIFO的数据读出,并显示在LCD上面。main函数则比较简单,我们就不细说了。
前面提到,我们要用USMART来设置摄像头的参数,我们只需要在usmart_nametab里面添加SCCB_WR_Reg和SCCB_RD_Reg这两个函数,就可以轻松调试摄像头了。
41.4 下载验证在代码编译成功之后,我们通过下载代码到ALIENTEK战舰STM32开发板上,得到如图41.4.1所示界面:
图
41.4.1 程序运行效果图随后,进入监控界面。此时,我们可以按不同的按键(KEY0~KEY2、WK_UP、TPAD等),来设置摄像头的相关参数和模式,得到不同的成像效果。 同时,你还可以在串口,通过USMART调用SCCB_WR_Reg函数,来设置OV7670的各寄存器,达到调试测试OV7670的目的,如图41.4.2所示:
图
41.4.2 USMART调试OV7670 从上图还可以看出,LCD显示帧率为8帧左右,则可以推断OV7670的输出帧率则至少是3*8=24帧以上(实际是30帧)。
`