公司“砍掉”FIFO和晶振预算怎么办?教你这样巧开发STM32的OV7670拍照功能

背景

在之前的公司做过一个被压缩成本的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;//100MHzGPIO_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 OUTGPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化  GPIO_InitStructure.GPIO_Pin = (0X1FF<<7);//PE7~15,AF OUTGPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//PF12,FSMC_A6GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//PF12,FSMC_A6GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化 GPIO_PinAFConfig(GPIOD,GPIO_PinSource0,GPIO_AF_FSMC);//PD0,AF12GPIO_PinAFConfig(GPIOD,GPIO_PinSource1,GPIO_AF_FSMC);//PD1,AF12GPIO_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,AF12GPIO_PinAFConfig(GPIOE,GPIO_PinSource7,GPIO_AF_FSMC);//PE7,AF12GPIO_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,AF12GPIO_PinAFConfig(GPIOF,GPIO_PinSource12,GPIO_AF_FSMC);//PF12,AF12GPIO_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=360nsreadWriteTiming.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=54nswriteTiming.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();        //读到0X00lcddev.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();                //读回0X80lcddev.id<<=8;        LCD_WR_REG(0XDC00);        lcddev.id|=LCD_RD_DATA();                //读回0X00                if(lcddev.id==0x8000)lcddev.id=0x5510;//NT35510读回的ID是8000H,为方便区分,我们强制设置为5510if(lcddev.id!=0X5510)                        //也不是NT5510,尝试看看是不是SSD1963{LCD_WR_REG(0XA1);lcddev.id=LCD_RD_DATA();lcddev.id=LCD_RD_DATA();        //读回0X57lcddev.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;//100MHzGPIO_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;//100MHzGPIO_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_HSYNCGPIO_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_D2GPIO_PinAFConfig(GPIOC,GPIO_PinSource9,GPIO_AF_DCMI); //PC9,AF13  DCMI_D3GPIO_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_D6GPIO_PinAFConfig(GPIOE,GPIO_PinSource6,GPIO_AF_DCMI); //PE6,AF13  DCMI_D7DCMI_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,VSYNCDCMI_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;//抢占优先级1NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                //子优先级3NVIC_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_Stream1while (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->DRDMA_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 Streamif(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;//抢占优先级0NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                //子优先级0NVIC_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);                //初始化USMARTPOINT_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();                        //开始写入GRAMDMA_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;        //Vendy=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;                                }          
#elsedatabuf=(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;                         //恒为1hbmp.bmiHeader.biBitCount=16;                 //bmp为16位色bmphbmp.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;
}

因为项目不是现在做的,所以讲起来可能有些乱,各位大神自行索取。
完整项目链接.


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部