[STM32F4]公司“砍掉”FIFO和晶振预算怎么办?教你这样巧开发STM32

本文详细介绍了使用STM32F407配合OV7670摄像头进行图像采集的全过程,包括OV7670的初始化、DCMI接口配置、DMA传输、LCD显示以及JPEG数据处理。通过DCMI捕获图像,然后通过DMA将数据传输到内存,最终保存为BMP文件。整个过程中涉及到了GPIO、DMA、中断和LCD初始化等多个方面,是STM32嵌入式系统中摄像头应用的一个实例。

在之前的公司做过一个被压缩成本的0V7670无FIFO无晶振的拍照项目,主控使用STM32F407,确实节约了成本,但是没有FIFO确实很麻烦,因为FIFO可以暂存图像数据,有这颗芯片可以降低单片机对高速IO的限制,还节省CPU资源,但是没有也只能搞下去。
接线:
说起OV7670必须说接线,特别是阉割版本的模块,不然真的会头大,在接线这一步自己就因为接错,导致我耽误了很长时间。
因为没有FIFO,所以这里使用F407的DCMI接口。
因为是测试,所以直接用杜邦线连接,这个线简直头疼,太多太乱。这里给一下我的接线表和我的实物图。这里已经是能再LCD上面显示图片了。

 
另外还有两个接口,一个是POWER DOWN控制信号(PG9),一个是复位控制信号(PG15)。

PWDN    |    输入         |    POWER DOWN模式选择           0:工作               1:POWER DOWN

RESET   |   输入         |    初始化所有寄存器到默认值         0:RESET 模式  1:一般模式


外设初始化:
用到摄像头,还要拍照,这一快屏幕是一定少不了的,先从初始化LCD屏幕开始。

复制
//初始化lcd

//该初始化函数可以初始化各种ILI93XX液晶,但是其他函数是基于ILI9320的!!!

//在其他型号的驱动芯片上没有测试! 

void LCD_Init(void)

{         

        vu32 i=0;

        

  GPIO_InitTypeDef  GPIO_InitStructure;

        FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;

  FSMC_NORSRAMTimingInitTypeDef  readWriteTiming; 

        FSMC_NORSRAMTimingInitTypeDef  writeTiming;

        

        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOD|RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOF|RCC_AHB1Periph_GPIOG, ENABLE);//使能PD,PE,PF,PG时钟  

  RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC,ENABLE);//使能FSMC时钟  

        

 

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PB15 推挽输出,控制背光

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式

  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉

  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化 //PB15 推挽输出,控制背光

        

  GPIO_InitStructure.GPIO_Pin = (3<<0)|(3<<4)|(7<<8)|(3<<14);//PD0,1,4,5,8,9,10,14,15 AF OUT

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出

  GPIO_InitStructure.GPIO_OType = G100MHz

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉

  GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化  

        

  GPIO_InitStructure.GPIO_Pin = (0X1FF<<7);//PE7~15,AF OUT

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出

  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉

  GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化  



        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//PF12,FSMC_A6

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出

  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉

  GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化  



        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//PF12,FSMC_A6

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出

  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉

  GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化 



  GPIO_PinAFConfig(GPIOD,GPIO_PinSource0,GPIO_AF_FSMC);//PD0,AF12

  GPIO_PinAFConfig(GPIOD,GPIO_PinSource1,GPIO_AF_FSMC);//PD1,AF12

  GPIO_PinAFConfig(GPIOD,GPIO_PinSource4,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOD,GPIO_PinSource5,GPIO_AF_FSMC); 

  GPIO_PinAFConfig(GPIOD,GPIO_PinSource8,GPIO_AF_FSMC); 

  GPIO_PinAFConfig(GPIOD,GPIO_PinSource9,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOD,GPIO_PinSource10,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOD,GPIO_PinSource14,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOD,GPIO_PinSource15,GPIO_AF_FSMC);//PD15,AF12

 

  GPIO_PinAFConfig(GPIOE,GPIO_PinSource7,GPIO_AF_FSMC);//PE7,AF12

  GPIO_PinAFConfig(GPIOE,GPIO_PinSource8,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOE,GPIO_PinSource9,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOE,GPIO_PinSource10,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOE,GPIO_PinSource12,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOE,GPIO_PinSource13,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOE,GPIO_PinSource14,GPIO_AF_FSMC);

  GPIO_PinAFConfig(GPIOE,GPIO_PinSource15,GPIO_AF_FSMC);//PE15,AF12

 

  GPIO_PinAFConfig(GPIOF,GPIO_PinSource12,GPIO_AF_FSMC);//PF12,AF12

  GPIO_PinAFConfig(GPIOG,GPIO_PinSource12,GPIO_AF_FSMC);





  readWriteTiming.FSMC_AddressSetupTime = 0XF;         //地址建立时间(ADDSET)为16个HCLK 1/168M=6ns*16=96ns        

  readWriteTiming.FSMC_AddressHoldTime = 0x00;         //地址保持时间(ADDHLD)模式A未用到        

  readWriteTiming.FSMC_DataSetupTime = 60;                        //数据保存时间为60个HCLK        =6*60=360ns

  readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;

  readWriteTiming.FSMC_CLKDivision = 0x00;

  readWriteTiming.FSMC_DataLatency = 0x00;

  readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;         //模式A 

    



        writeTiming.FSMC_AddressSetupTime =9;              //地址建立时间(ADDSET)为9个HCLK =54ns 

  writeTiming.FSMC_AddressHoldTime = 0x00;         //地址保持时间(A                

  writeTiming.FSMC_DataSetupTime = 8;                 //数据保存时间为6ns*9个HCLK=54ns

  writeTiming.FSMC_BusTurnAroundDuration = 0x00;

  writeTiming.FSMC_CLKDivision = 0x00;

  writeTiming.FSMC_DataLatency = 0x00;

  writeTiming.FSMC_AccessMode = FSMC_AccessMode_A;         //模式A 



 

  FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;//  这里我们使用NE4 ,也就对应BTCR[6],[7]。

  FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; // 不复用数据地址

  FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;// FSMC_MemoryType_SRAM;  //SRAM   

  FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;//存储器数据宽度为16bit   

  FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;// FSMC_BurstAccessMode_Disable; 

  FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;

        FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable; 

  FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;   

  FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;  

  FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;        //  存储器写使能

  FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;   

  FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Enable; // 读写使用不同的时序

  FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable; 

  FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming; //读写时序

  FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &writeTiming;  //写时序



  FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置



  FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);  // 使能BANK1 

                

         delay_ms(50); // delay 50 ms 

         LCD_WriteReg(0x0000,0x0001);

        delay_ms(50); // delay 50 ms 

          lcddev.id = LCD_ReadReg(0x0000);   

           if(lcddev.id<0XFF||lcddev.id==0XFFFF||lcddev.id==0X9300)//读到ID不正确,新增lcddev.id==0X9300判断,因为9341在未被复位的情况下会被读成9300

        {        

                 //尝试9341 ID的读取                

                LCD_WR_REG(0XD3);                                   

                lcddev.id=LCD_RD_DATA();        //dummy read         

                 lcddev.id=LCD_RD_DATA();        //读到0X00

                  lcddev.id=LCD_RD_DATA();           //读取93                                                                   

                 lcddev.id<<=8;

                lcddev.id|=LCD_RD_DATA();          //读取41                                       

                 if(lcddev.id!=0X9341)                //非9341,尝试是不是6804

                {        

                         LCD_WR_REG(0XBF);                                   

                        lcddev.id=LCD_RD_DATA();         //dummy read          

                         lcddev.id=LCD_RD_DATA();           //读回0X01                           

                         lcddev.id=LCD_RD_DATA();         //读回0XD0                                   

                          lcddev.id=LCD_RD_DATA();        //这里读回0X68 

                        lcddev.id<<=8;

                          lcddev.id|=LCD_RD_DATA();        //这里读回0X04          

                        if(lcddev.id!=0X6804)                //也不是6804,尝试看看是不是NT35310

                        { 

                                LCD_WR_REG(0XD4);                                   

                                lcddev.id=LCD_RD_DATA();//dummy read  

                                lcddev.id=LCD_RD_DATA();//读回0X01         

                                lcddev.id=LCD_RD_DATA();//读回0X53        

                                lcddev.id<<=8;         

                                lcddev.id|=LCD_RD_DATA();        //这里读回0X10         

                                if(lcddev.id!=0X5310)                //也不是NT35310,尝试看看是不是NT35510

                                {

                                        LCD_WR_REG(0XDA00);        

                                        lcddev.id=LCD_RD_DATA();                //读回0X00         

                                        LCD_WR_REG(0XDB00);        

                                        lcddev.id=LCD_RD_DATA();                //读回0X80

                                        lcddev.id<<=8;        

                                        LCD_WR_REG(0XDC00);        

                                        lcddev.id|=LCD_RD_DATA();                //读回0X00                

                                        if(lcddev.id==0x8000)lcddev.id=0x5510;//NT35510读回的ID是8000H,为方便区分,我们强制设置为5510

                                        if(lcddev.id!=0X5510)                        //也不是NT5510,尝试看看是不是SSD1963

                                        {

                                                LCD_WR_REG(0XA1);

                                                lcddev.id=LCD_RD_DATA();

                                                lcddev.id=LCD_RD_DATA();        //读回0X57

                                                lcddev.id<<=8;         

                                                lcddev.id|=LCD_RD_DATA();        //读回0X61        

                                                if(lcddev.id==0X5761)lcddev.id=0X1963;//SSD1963读回的ID是5761H,为方便区分,我们强制设置为1963

                                        }

                                }

                        }

                 }          

        } 

        if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510||lcddev.id==0X1963)//如果是这几个IC,则设置WR时序为最快

        {

                //重新配置写时序控制寄存器的时序                                                                        

                FSMC_Bank1E->BWTR[6]&=~(0XF<<0);//地址建立时间(ADDSET)清零          

                FSMC_Bank1E->BWTR[6]&=~(0XF<<8);//数据保存时间清零

                FSMC_Bank1E->BWTR[6]|=3<<0;                //地址建立时间(ADDSET)为3个HCLK =18ns           

                FSMC_Bank1E->BWTR[6]|=2<<8;         //数据保存时间(DATAST)为6ns*3个HCLK=18ns

        }else if(lcddev.id==0X6804||lcddev.id==0XC505)        //6804/C505速度上不去,得降低

        {

                //重新配置写时序控制寄存器的时序                                                                        

                FSMC_Bank1E->BWTR[6]&=~(0XF<<0);//地址建立时间(ADDSET)清零          

                FSMC_Bank1E->BWTR[6]&=~(0XF<<8);//数据保存时间清零

                FSMC_Bank1E->BWTR[6]|=10<<0;        //地址建立时间(ADDSET)为10个HCLK =60ns           

                FSMC_Bank1E->BWTR[6]|=12<<8;         //数据保存时间(DATAST)为6ns*13个HCLK=78ns

        }

         printf(" LCD ID:%x\r\n",lcddev.id); //打印LCD ID   

 

这里参考了正点原子的LCD的初始化,我说贴上的代码没有具体型号的屏幕初始化,因为代码太长,会占用我的篇幅,在我的上面贴的代码最后是打印LCD ID,打印完成后会进行if()语句的判断,判断属于哪个型号的LCD屏幕,再进行初始化,大家有需要可以参考正点原子的初始化。在初始化完成后会对LCD进行清屏。因为使用摄像头会占用大量的内存资源,所有我在板子上贴了一片外部SARM芯片,同样需要对其进行初始化。此过程已放入LCD初始化中。

复制
 LCD_Display_Dir(0);                //默认为竖屏

        LCD_LED=1;                                //点亮背光

        LCD_Clear(WHITE);

exfuns初始化:
在使用摄像头拍照结束后,会将.bmp文件存入内存卡中,所以这里需要初始化exfuns,为fatfs相关变量申请内存 。

复制
u8 exfuns_init(void)

{

        u8 i;

        for(i=0;i<_VOLUMES;i++)

        {

                fs[i]=(FATFS*)mymalloc(SRAMIN,sizeof(FATFS));        //为磁盘i工作区申请内存        

                if(!fs[i])break;

        }

        file=(FIL*)mymalloc(SRAMIN,sizeof(FIL));                //为file申请内存

        ftemp=(FIL*)mymalloc(SRAMIN,sizeof(FIL));                //为ftemp申请内存

        fatbuf=(u8*)mymalloc(SRAMIN,512);                                //为fatbuf申请内存

        if(i==_VOLUMES&&file&&ftemp&&fatbuf)return 0;  //申请有一个失败,即失败.

        else return 1;        

}

OV7670初始化:
初始化OV7670主要是初始化POWER DOWN控制信号(PG9)和复位控制信号(PG15),以及SCCB的接口,该接口是串行摄像机控制总线协议的英文名简称,相当于一个简易的I2C协议,同时也会初始化QVGA的分辨率。

复制
//初始化OV7670

//返回0:成功

//返回其他值:错误代码

u8 OV7670_Init(void)

{

u16 i=0;

u16 reg=0;

u8 temp=0;

//设置IO 

GPIO_InitTypeDef GPIO_InitStructure;



RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);

//GPIOG9,15初始化设置

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_15;//PG9,15推挽输出

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //推挽输出

GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz

GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉

GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化

OV7670_PWDN=0;        //POWER ON

delay_ms(10);

OV7670_RST=0;        //复位OV7670

delay_ms(10);

OV7670_RST=1;        //结束复位 

SCCB_Init(); //初始化SCCB 的IO口        

SCCB_WR_Reg(0X12, 0x80);        //软复位OV7670

delay_ms(50); 

LED0=0;

//初始化 OV7670,采用QVGA分辨率(320*240) 

for(i=0;i<sizeof(ov7670_init_reg_tbl)/sizeof(ov7670_init_reg_tbl[0]);i++)

{

SCCB_WR_Reg(ov7670_init_reg_tbl[i][0],ov7670_init_reg_tbl[i][1]);

} 

return 0x00; //ok

}



//初始化SCCB接口 

void SCCB_Init(void)

{                                

  GPIO_InitTypeDef  GPIO_InitStructure;



  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);//使能GPIOD时钟

  //GPIOF9,F10初始化设置

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;//PD6,7 推挽输出

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;  //PD6,7 推挽输出

  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉

  GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化

 

        GPIO_SetBits(GPIOD,GPIO_Pin_6|GPIO_Pin_7);

        SCCB_SDA_OUT();           

}        

DCMI配置:

这里是一个重点,因为我的OV7670不带FIFO和晶振,而7670的CMOS芯片的时钟可以高达24M,而单片机的IO速度不够,退而求其次,使用DCMI的接口,勉强让其跑起来,当然,如果是ARM9或者DSP图像处理芯片就另说了,人家内存大,带camera接口,但是价格也感人。
DCMI接口是一个同步并行接口,能够接收外部 8 位、 10 位、 12 位或 14 位 CMOS 摄像头模块发出的高速数据流。可支持不同的数据格式: YCbCr4:2:2/RGB565 逐行视频和压缩数据 (JPEG)。  

DCMI可以接收54M的数据流,高达14根数据线和一条像素时钟线PIXCLK,且像素时钟的极性可编程,可以自定义在上升沿还是下降沿捕捉数据。

复制
//DCMI初始化

void My_DCMI_Init(void)

{

  GPIO_InitTypeDef  GPIO_InitStructure;

        NVIC_InitTypeDef NVIC_InitStructure;



        

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOA B C E 时钟

        RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI,ENABLE);//使能DCMI时钟

  //PA4/6初始化设置

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_6;//PA4/6   复用功能输出

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能输出

  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉

  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化

        

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_6;// PB6/7   复用功能输出

  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化

        

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_11;//PC6/7/8/9/11 复用功能输出

  GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化        



  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6;//PE5/6  复用功能输出 

  GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化        



        GPIO_PinAFConfig(GPIOA,GPIO_PinSource4,GPIO_AF_DCMI); //PA4,AF13  DCMI_HSYNC

        GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_DCMI); //PA6,AF13  DCMI_PCLK  

         GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_DCMI); //PB7,AF13  DCMI_VSYNC 

         GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_DCMI); //PC6,AF13  DCMI_D0  

         GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_DCMI); //PC7,AF13  DCMI_D1 

        GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_DCMI); //PC8,AF13  DCMI_D2

        GPIO_PinAFConfig(GPIOC,GPIO_PinSource9,GPIO_AF_DCMI); //PC9,AF13  DCMI_D3

        GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_DCMI);//PC11,AF13 DCMI_D4 

        GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_DCMI); //PB6,AF13  DCMI_D5 

        GPIO_PinAFConfig(GPIOE,GPIO_PinSource5,GPIO_AF_DCMI); //PE5,AF13  DCMI_D6

        GPIO_PinAFConfig(GPIOE,GPIO_PinSource6,GPIO_AF_DCMI); //PE6,AF13  DCMI_D7



        

        DCMI_DeInit();//清除原来的设置 

 

 

  DCMI_InitStructure.DCMI_CaptureMode=DCMI_CaptureMode_Continuous;//连续模式

        DCMI_InitStructure.DCMI_CaptureRate=DCMI_CaptureRate_All_Frame;//全帧捕获

        DCMI_InitStructure.DCMI_ExtendedDataMode= DCMI_ExtendedDataMode_8b;//8位数据格式  

        DCMI_InitStructure.DCMI_HSPolarity = DCMI_HSPolarity_Low;//HSYNC 低电平有效

        DCMI_InitStructure.DCMI_PCKPolarity= DCMI_PCKPolarity_Falling;//PCLK 上升沿有效

        DCMI_InitStructure.DCMI_SynchroMode= DCMI_SynchroMode_Hardware;//硬件同步HSYNC,VSYNC

        DCMI_InitStructure.DCMI_VSPolarity=DCMI_VSPolarity_High;//VSYNC 低电平有效

        DCMI_Init(&DCMI_InitStructure);

        printf("start DCMI_IT_FRAME\r\n");

        DCMI_ITConfig(DCMI_IT_FRAME,ENABLE);//开启帧中断 

        //DCMI_ITConfig(DCMI_IT_LINE,ENABLE); //开启行中断

        //DCMI_ITConfig(DCMI_IT_VSYNC,ENABLE); //开启场中断        

        DCMI_Cmd(ENABLE);        //DCMI使能

        printf("ENABLE DCMI_IT_FRAME OK\r\n");

  NVIC_InitStructure.NVIC_IRQChannel = DCMI_IRQn;

        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级1

        NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                //子优先级3

        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能

        NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器、

        printf("My_DCMI_Init OK\r\n");

} 

DCMI的初始化中我们使用连续模式的全帧捕捉,在中断中我们选择帧中断,方便获取一个满屏的图像数据。
DCMI获取到的数据会保存在32位的数据寄存器DCMI_DR中,之后我们便可以通过DMA进行传输,图像的缓冲由DMA进行管理,不是由DCMI接管。
DCMI的DMA配置:

复制
void DCMI_DMA_Init(u32 DMA_Memory0BaseAddr,u32 DMA_Memory1BaseAddr,u16 DMA_BufferSize,u32 DMA_MemoryDataSize,u32 DMA_MemoryInc)

{ 

        DMA_InitTypeDef  DMA_InitStructure;

        NVIC_InitTypeDef NVIC_InitStructure;

        

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 

        DMA_DeInit(DMA2_Stream1);//等待DMA2_Stream1

        while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置 

        

  /* 配置 DMA Stream */

  DMA_InitStructure.DMA_Channel = DMA_Channel_1;  //通道1 DCMI通道 

  DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&DCMI->DR;//外设地址为:DCMI->DR

  DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;//DMA 存储器0地址

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设到存储器模式

  DMA_InitStructure.DMA_BufferSize = DMA_BufferSize;//数据传输量 



  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc;//存储器增量模式

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//外设数据长度:32位

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize;//存储器数据长度 

  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式 

  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级

  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; //FIFO模式        

  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//使用全FIFO 

  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外设突发单次传输

  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输

  DMA_Init(DMA2_Stream1, &DMA_InitStructure);//初始化DMA Stream

                

        if(DMA_Memory1BaseAddr)

  {

                DMA_DoubleBufferModeCmd(DMA2_Stream1,ENABLE);//双缓冲模式

          DMA_MemoryTargetConfig(DMA2_Stream1,DMA_Memory1BaseAddr,DMA_Memory_1);//配置目标地址1

        }        

        DMA_ITConfig(DMA2_Stream1,DMA_IT_TC,ENABLE);//开启传输完成中断

        

        NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream1_IRQn;

        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级0

        NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                //子优先级0

        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能

        NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器、        

} 



开始工作:
在启动MCU后需要初始化相关配置。

复制
  LCD_Init();                                        //LCD初始化  

        FSMC_SRAM_Init();                        //初始化外部SRAM.

        W25QXX_Init();                                //初始化W25Q128 

        exfuns_init();                                //为fatfs相关变量申请内存  

//  

//        KEY_Init();                                        //按键初始化 

//        SD_Init();

        //TIM3_Int_Init(10000-1,8400-1);//10Khz计数,1秒钟中断一次

        TIM1_PWM_Init();



        my_mem_init(SRAMIN);                //初始化内部内存池 

        my_mem_init(SRAMEX);                //初始化内部内存池  

        my_mem_init(SRAMCCM);                //初始化CCM内存池 

         usmart_dev.init(84);                //初始化USMART

         POINT_COLOR=RED;//设置字体为红色          

                LCD_ShowString(30,130,200,16,16,"OV7670 00089"); 

                printf("init ov\n");

        while(OV7670_Init())//初始化OV7670

        {

                LCD_ShowString(30,130,240,16,16,"OV7670 ERR");

                delay_ms(200);

          LCD_Fill(30,130,239,170,WHITE);

                delay_ms(200);

        }

        LCD_ShowString(30,130,200,16,16,"OV7670 OK"); 

        printf("init ov ok\n");

        delay_ms(1500);        



        jpeg_buf0=mymalloc(SRAMIN,jpeg_dma_bufsize*4);        //为jpeg dma接收申请内存        

        jpeg_buf1=mymalloc(SRAMIN,jpeg_dma_bufsize*4);        //为jpeg dma接收申请内存        

        jpeg_data_buf=mymalloc(SRAMEX,300*1024);                //为jpeg文件申请内存(最大300KB)

         pname=mymalloc(SRAMIN,30);//为带路径的文件名分配30个字节的内存         

         while(pname==NULL||!jpeg_data_buf)        //内存分配出错

         {            

                LCD_ShowString(30,190,240,16,16,"内存分配失败!");

                        printf("jpeg_data_buf ERROR!!!!!!!!!!!!!!!!!!!!!!! ok\n");

                delay_ms(200);                                  

                LCD_Fill(30,190,240,146,WHITE);//清除显示             

                delay_ms(200);                                  

        }

    printf("init mymalloc ok    \n");

        My_DCMI_Init();                        //DCMI配置

//        DCMI_DMA_Init((u32)&LCD->LCD_RAM,0,10,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);//DCMI DMA配置  

//        DCMI_Start();                 //启动传输



        printf("init My_DCMI_Init ok\n");

        dcmi_rx_callback=jpeg_dcmi_rx_callback;//回调函数

        //DCMI_DMA_Init((u32)&LCD->LCD_RAM,0,10,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);//DCMI DMA配置  

        DCMI_DMA_Init((u32)jpeg_buf0,(u32)jpeg_buf1,jpeg_dma_bufsize,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Enable);//DCMI DMA配置(双缓冲模式)

        printf("init DCMI_Start");

        DCMI_Start();                         //启动传输 

  OV7670_Window_Set(12,176,240,320);//OV7670设置输出窗口        

        //        sw_ov2640_mode();                //切换为OV2640模式

//while(1){

//        if(sd_ok==1){

        printf("start DCMI_Start ok ");

        dcmi=1;

        while(jpeg_data_ok!=1);        //等待第一帧图片采集完

        dcmi=0;

        DCMI_Stop(); //停止DMA搬运

        printf("DCMI STOP");

        

        sw_sdcard_mode();        //切换为SD卡模式

        printf("SWICCT SDCARD SUCCESS");

//                if(KEY0==0)        //BMP拍照

//                {

  f_mount(fs[0],"0:",1);                 //挂载SD卡  

        res=f_mkdir("0:/PHOTO");                //创建PHOTO文件夹

        if(res!=FR_EXIST&&res!=FR_OK)         //发生了错误

        {                 

                printf("SD CARD ERROR");

                LCD_ShowString(30,190,200,16,16,"内存分配失败!");

                delay_ms(200);                                  

                sd_ok=0;          

        } 

        printf("SD CARD RIGHT");

        camera_new_pathname(pname,0);//得到文件名        

        printf("CREAT PNAME SUCCESS");

        res=bmp_encode(pname,0,0,lcddev.width,lcddev.height,0);

        printf("BMP SAVE SUCCESS");

                delay_ms(200);


此过程皆在main函数中完成,初始化DCMI之前必须先初始化内存并为jpeg dma接收申请和分配内存。
然后启动DMA,启动DCMI,进行图像采集。

复制
//DCMI,启动传输

void DCMI_Start(void)

{ 

        LCD_Scan_Dir(U2D_L2R);                   //从上到下,从左到右        

        LCD_Set_Window(0,0,240,320); //LCD设置显示窗口,如果改变了分辨率,这里需要更改

        LCD_SetCursor(0,0);  

        LCD_WriteRAM_Prepare();                        //开始写入GRAM

        DMA_Cmd(DMA2_Stream1, ENABLE);//开启DMA2,Stream1 

        DCMI_CaptureCmd(ENABLE);//DCMI捕获使能  

}

设置OV7670输出窗口        

复制
//设置图像输出窗口

//对QVGA设置。

void OV7670_Window_Set(u16 sx,u16 sy,u16 width,u16 height)

{

        u16 endx;

        u16 endy;

        u8 temp; 

        endx=sx+width*2;        //V

         endy=sy+height*2;

        if(endy>784)endy-=784;

        temp=SCCB_RD_Reg(0X03);                                //读取Vref之前的值

        temp&=0XF0;

        temp|=((endx&0X03)<<2)|(sx&0X03);

        SCCB_WR_Reg(0X03,temp);                                //设置Vref的start和end的最低2位

        SCCB_WR_Reg(0X19,sx>>2);                        //设置Vref的start高8位

        SCCB_WR_Reg(0X1A,endx>>2);                        //设置Vref的end的高8位



        temp=SCCB_RD_Reg(0X32);                                //读取Href之前的值

        temp&=0XC0;

        temp|=((endy&0X07)<<3)|(sy&0X07);

        SCCB_WR_Reg(0X32,temp);

        SCCB_WR_Reg(0X17,sy>>3);                        //设置Href的start高8位

        SCCB_WR_Reg(0X18,endy>>3);                        //设置Href的end的高8位

}

此时DCMI启动,会抓拍照片,当捕获到一帧完整数据会触发DCMI的帧中断,进入中断函数之后会进行JPEG的数据处理,在处理JPEG数据时会停止DMA的传输,并将数据保存到pbuf[]数组。

复制
//DCMI中断服务函数

void DCMI_IRQHandler(void)

{

        if(DCMI_GetITStatus(DCMI_IT_FRAME)==SET)//捕获到一帧图像

        {

                //DCMI_Stop(); //停止DMA搬运



                DCMI_ClearITPendingBit(DCMI_IT_FRAME);//清除中断        

                if (dcmi == 1)

                        //printf("dcmi == 1 \r\n");                        

                        jpeg_data_process();

                //ov_frame++;

                

        }

        

} 



//处理JPEG数据

//当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.

void jpeg_data_process(void)

{

        u16 i;

        u16 rlen;//剩余数据长度

        u32 *pbuf;



        if(jpeg_data_ok==0)        //jpeg数据还未采集完?

        {

                DMA_Cmd(DMA2_Stream1,DISABLE);                //停止当前传输

                while(DMA_GetCmdStatus(DMA2_Stream1) != DISABLE);        //等待DMA2_Stream1可配置 

                rlen=jpeg_dma_bufsize-DMA_GetCurrDataCounter(DMA2_Stream1);//得到剩余数据长度        

                pbuf=jpeg_data_buf+jpeg_data_len;//偏移到有效数据末尾,继续添加

                if(DMA2_Stream1->CR&(1<<19))

                        for(i=0;i<rlen;i++)

                                pbuf[i]=jpeg_buf1[i];//读取buf1里面的剩余数据

                else 

                        for(i=0;i<rlen;i++)

                                pbuf[i]=jpeg_buf0[i];//读取buf0里面的剩余数据 

                jpeg_data_len+=rlen;                        //加上剩余长度

                jpeg_data_ok=1;                                 //标记JPEG数据采集完按成,等待其他函数处理

        }

        if(jpeg_data_ok==2)        //上一次的jpeg数据已经被处理了

        { DMA_SetCurrDataCounter(DMA2_Stream1,jpeg_dma_bufsize);//传输长度为jpeg_buf_size*4字节

                DMA_Cmd(DMA2_Stream1,ENABLE); //重新传输

                jpeg_data_ok=0;                                        //标记数据未采集

                jpeg_data_len=0;                                //数据重新开始

        }

                        printf("jpeg_data_process success \r\n");                

}

当判断一帧数据采集完成,会启动DMA传输,传输完成会触发DMA中断,最后模式切换到SD卡模式,进行图片的保存。

复制
void DMA2_Stream1_IRQHandler(void)

{        

        if(DMA_GetFlagStatus(DMA2_Stream1,DMA_FLAG_TCIF1)==SET)//DMA2_Steam1,传输完成标志

        {  

                 DMA_ClearFlag(DMA2_Stream1,DMA_FLAG_TCIF1);//清除传输完成中断

     dcmi_rx_callback();        //执行摄像头接收回调函数,读取数据等操作在这里面处理  

                 

        }                                                                                             

}



//jpeg数据接收回调函数

void jpeg_dcmi_rx_callback(void)

{ 

        u16 i;

        u32 *pbuf;

        pbuf=jpeg_data_buf+jpeg_data_len;//偏移到有效数据末尾

        if(DMA2_Stream1->CR&(1<<19))//buf0已满,正常处理buf1

        { 

                for(i=0;i<jpeg_dma_bufsize;i++)pbuf[i]=jpeg_buf0[i];//读取buf0里面的数据

                jpeg_data_len+=jpeg_dma_bufsize;//偏移

        }else //buf1已满,正常处理buf0

        {

                for(i=0;i<jpeg_dma_bufsize;i++)pbuf[i]=jpeg_buf1[i];//读取buf1里面的数据

                jpeg_data_len+=jpeg_dma_bufsize;//偏移 

        }         

}

我这里保存的是BMP格式,所以有一个转码处理,这里也贴一下。

复制
//BMP编码函数

//将当前LCD屏幕的指定区域截图,存为16位格式的BMP文件 RGB565格式.

//保存为rgb565则需要掩码,需要利用原来的调色板位置增加掩码.这里我们已经增加了掩码.

//保存为rgb555格式则需要颜色转换,耗时间比较久,所以保存为565是最快速的办法.

//filename:存放路径

//x,y:在屏幕上的起始坐标  

//mode:模式.0,仅仅创建新文件的方式编码;1,如果之前存在文件,则覆盖之前的文件.如果没有,则创建新的文件.

//返回值:0,成功;其他,错误码.  

u8 bmp_encode(u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 mode)

{                

        u32 i=0,num=0;

        u32* pbuf;

        u16* pbuf_Data;

        FIL* f_bmp;

        u16 bmpheadsize;                        //bmp头大小                    

         BITMAPINFO hbmp;                        //bmp头         

        u8 res=0;

        u16 tx,ty;                                           //图像尺寸

        u16 *databuf;                                //数据缓存区地址                   

        u16 pixcnt;                                           //像素计数器

        u16 bi4width;                               //水平像素字节数           

        if(width==0||height==0)return PIC_WINDOW_ERR;        //区域错误

        if((x+width-1)>lcddev.width)return PIC_WINDOW_ERR;                //区域错误

        if((y+height-1)>lcddev.height)return PIC_WINDOW_ERR;        //区域错误 

          

#if BMP_USE_MALLOC == 1        //使用malloc        

        databuf=(u16*)pic_memalloc(1024);                //开辟至少bi4width大小的字节的内存区域 ,对240宽的屏,480个字节就够了.

        if(databuf==NULL)return PIC_MEM_ERR;                //内存申请失败.

        f_bmp=(FIL *)pic_memalloc(sizeof(FIL));        //开辟FIL字节的内存区域 

        if(f_bmp==NULL)                                                                //内存申请失败.

        {                 

                pic_memfree(databuf);

                return PIC_MEM_ERR;                                

        }          

#else

        databuf=(u16*)bmpreadbuf;

        f_bmp=&f_bfile;

#endif              

        bmpheadsize=sizeof(hbmp);//得到bmp文件头的大小   

        mymemset((u8*)&hbmp,0,sizeof(hbmp));//置零空申请到的内存.            

        hbmp.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);//信息头大小

        hbmp.bmiHeader.biWidth=width;                 //bmp的宽度

        hbmp.bmiHeader.biHeight=height;         //bmp的高度

        hbmp.bmiHeader.biPlanes=1;                         //恒为1

        hbmp.bmiHeader.biBitCount=16;                 //bmp为16位色bmp

        hbmp.bmiHeader.biCompression=BI_BITFIELDS;//每个象素的比特由指定的掩码决定。

         hbmp.bmiHeader.biSizeImage=hbmp.bmiHeader.biHeight*hbmp.bmiHeader.biWidth*hbmp.bmiHeader.biBitCount/8;//bmp数据区大小

                                    

        hbmp.bmfHeader.bfType=((u16)'M'<<8)+'B';//BM格式标志

        hbmp.bmfHeader.bfSize=bmpheadsize+hbmp.bmiHeader.biSizeImage;//整个bmp的大小

           hbmp.bmfHeader.bfOffBits=bmpheadsize;//到数据区的偏移



        hbmp.RGB_MASK[0]=0X00F800;                         //红色掩码

        hbmp.RGB_MASK[1]=0X0007E0;                         //绿色掩码

        hbmp.RGB_MASK[2]=0X00001F;                         //蓝色掩码



        if(mode==1)res=f_open(f_bmp,(const TCHAR*)filename,FA_READ|FA_WRITE);//尝试打开之前的文件

         if(mode==0||res==0x04)res=f_open(f_bmp,(const TCHAR*)filename,FA_WRITE|FA_CREATE_NEW);//模式0,或者尝试打开失败,则创建新文件                   

         if((hbmp.bmiHeader.biWidth*2)%4)//水平像素(字节)不为4的倍数

        {

                bi4width=((hbmp.bmiHeader.biWidth*2)/4+1)*4;//实际要写入的宽度像素,必须为4的倍数.        

        }else bi4width=hbmp.bmiHeader.biWidth*2;                //刚好为4的倍数         

         if(res==FR_OK)//创建成功

        {

                res=f_write(f_bmp,(u8*)&hbmp,bmpheadsize,&bw);//写入BMP首部  



                pixcnt=0;

                pbuf=(u32*)jpeg_data_buf;

//                pbuf_Data = (u32*)jpeg_data_buf;



        for (i = 16; i < 32; i++)

        {

                pbuf[i]=0x0;

//                pbuf_Data=pbuf&0x0000FFFF;



        }

                f_write(f_bmp,(u32*)pbuf,jpeg_data_len,&bw);

                

                f_close(f_bmp);

        }

//         if(res==FR_OK)//创建成功

//        {

//                res=f_write(f_bmp,(u8*)&hbmp,bmpheadsize,&bw);//写入BMP首部  

//                for(ty=y+height-1;hbmp.bmiHeader.biHeight;ty--){

//                        

//                }

//                pixcnt=0;

//                pbuf=(u32*)jpeg_data_buf;

//                f_write(f_bmp,(u32*)pbuf,jpeg_data_len*4,&bw);

//                

//                f_close(f_bmp);

//        }                

#if BMP_USE_MALLOC == 1        //使用malloc        

        pic_memfree(databuf);         

        pic_memfree(f_bmp);                 

#endif        

        return res;

}

因为项目不是现在做的,所以讲起来可能有些乱,这里附上百度网盘的下载链接,因为文件过大,没法上传附件,望谅解。
链接的隐藏是为了获取回复量,有点私心,各位大侠高抬贵手。感激不尽。
---------------------
作者:呐咯密密
链接:https://bbs.21ic.com/icview-3071062-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值