| 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;i<sizeof(ov7670_init_reg_tbl)/sizeof(ov7670_init_reg_tbl[0]);i++)        {             SCCB_WR_Reg(ov7670_init_reg_tbl[0],ov7670_init_reg_tbl[1]);               delay_ms(2);       }      return 0x00;   //ok } 此部分代码先初始化OV7670相关的IO口(包括SCCB_Init),然后最主要的是完成OV7670的寄存器序列初始化。OV7670的寄存器特多(百几十个),配置特麻烦,幸好厂家有提供参考配置序列(详见《OV7670 software application note》),本章我们用到的配置序列,存放在ov7670_init_reg_tbl这个数组里面,该数组是一个2维数组,存储初始化序列寄存器及其对应的值,该数组存放在ov7670cfg.h里面。        接下来,我们看看ov7670cfg.h里面ov7670_init_reg_tbl的内容,ov7670cfg.h文件的代码如下: //初始化寄存器序列及其对应的值 const u8 ov7670_init_reg_tbl[][2]=  {         //以下为OV7670 QVGA RGB565参数       {0x3a, 0x04},//        {0x40, 0x10},        {0x12, 0x14},//QVGA,RGB输出        ……省略部分设置        {0x6e, 0x11},//100        {0x6f, 0x9f},//0x9e for advance AWB     {0x55, 0x00},//亮度     {0x56, 0x40},//对比度     {0x57, 0x80},//0x40,  change according to Jim's request    }; 以上代码,我们省略了很多(全部贴出来太长了),我们大概了解下结构,每个条目的第一个字节为寄存器号(也就是寄存器地址),第二个字节为要设置的值,比如{0x3a, 0x04},就表示在0X03地址,写入0X04这个值。        通过这么一长串(110多个)寄存器的配置,我们就完成了OV7670的初始化,本章我们配置OV7670工作在QVGA模式,RGB565格式输出。 在完成初始化之后,我们既可以开始读取OV7670的数据了。        OV7670文件夹里面的其他代码我们就不逐个介绍了,请大家参考光盘该例程源码。        因为本章我们还用到了帧率(LCD显示的帧率)统计和中断处理,所以我们还需要修改timer.c、timer.h、exti.c及exti.h这几个文件。        在timer.c里面,我们新增TIM6_Int_Init和TIM6_IRQHandler两个函数,用于统计帧率,增加代码如下:  u8 ov_frame;        //统计帧数 //定时器6中断服务程序      void TIM6_IRQHandler(void) {                                                              if(TIM6->SR&0X0001)//溢出中断        {                                             printf("frame:%dfps\r\n",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这两个函数,就可以轻松调试摄像头了。 |