#申请原创# @21小跑堂
在之前的公司做过一个被压缩成本的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 = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
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;
}
因为项目不是现在做的,所以讲起来可能有些乱,这里附上百度网盘的下载链接,因为文件过大,没法上传附件,望谅解。
链接的隐藏是为了获取回复量,有点私心,各位大侠高抬贵手。感激不尽。
|
此文章已获得独家原创/原创奖标签,著作权归21ic所有,未经允许禁止转载。
打赏榜单
21小跑堂 打赏了 100.00 元 2021-03-04 理由:恭喜通过原创文章审核!请多多加油哦!
|