第五四天 2015年09月19日 周六 例程:照相机实验(二)
一、DMA双缓冲模式
1.本章新增加了一个参数:memladdr,根据该值是否非0来判断是否需要使用双缓冲机制,只有在双缓冲机制下,才开启DMA传输完成中断。
2.DMA双缓冲数据最终存储位置(SD卡)追踪
DMA2_Stream1_IRQHandler函数,是DMA传输完成中断
里面通过dcmi_rx_callback回调函数(函数指针,指向jpeg_dcmi_rx_callback函数)
而dcmi_rx_callback进行了回调,如下
[mw_shl_code=c,true] dcmi_rx_callback=jpeg_dcmi_rx_callback;//回调函数,位于u8 ov2640_jpg_photo(u8 *pname)函数中[/mw_shl_code]
jpeg_dcmi_rx_callback函数负责将满了的内存(M0AR或M1AR)数据读取到外部SRAM,如下
[mw_shl_code=c,true]//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=jpeg_buf0;//读取buf0里面的数据
jpeg_data_len+=jpeg_dma_bufsize;//偏移
}else //buf1已满,正常处理buf0
{
for(i=0;i<jpeg_dma_bufsize;i++)pbuf=jpeg_buf1;//读取buf1里面的数据
jpeg_data_len+=jpeg_dma_bufsize;//偏移
}
}[/mw_shl_code]
由上可知,进入中断后判断是哪个内存满引起的,然后进行存储,存入pbuf【】中,由pbuf=jpeg_data_buf+jpeg_data_len;(其中jpeg_data_len随着数据存入在变化),所以数据存入的地址与jpeg_data_buf相关,其在test.c定义
[mw_shl_code=c,true]u32 *jpeg_data_buf; //JPEG数据缓存buf,通过malloc申请内存[/mw_shl_code]
在main函数中,有
[mw_shl_code=c,true]jpeg_data_buf=mymalloc(SRAMEX,300*1024); //为jpeg文件申请内存(最大300KB)[/mw_shl_code]
当数据采集后,会通过FATFS文件系统进行存储,在ov2640_jpg_photo()函数中
[mw_shl_code=c,true]res=f_open(f_jpg,(const TCHAR*)pname,FA_WRITE|FA_CREATE_NEW);//模式0,或者尝试打开失败,则创建新文件
if(res==0)
{
printf("jpeg data size:%d\r\n",jpeg_data_len*4);//串口打印JPEG文件大小
pbuf=(u8*)jpeg_data_buf;//将JPEG暂时存储的数据全部进行存储SD卡中,见下操作
for(i=0;i<jpeg_data_len*4;i++)//查找0XFF,0XD8
{
if((pbuf==0XFF)&&(pbuf[i+1]==0XD8))break;
}
if(i==jpeg_data_len*4)res=0XFD;//没找到0XFF,0XD8
else//找到了
{
pbuf+=i;//偏移到0XFF,0XD8处
res=f_write(f_jpg,pbuf,jpeg_data_len*4-i,&bwr);
if(bwr!=(jpeg_data_len*4-i))res=0XFE;
}
}
jpeg_data_len=0;
f_close(f_jpg); [/mw_shl_code]
至此,完成了数据的存储。(其中f_jpg为FIL*结构体,定义于ff.h中)
3.DMA双缓冲内存分配追踪
DCMI_DMA_Init()函数部分配置如下
[mw_shl_code=c,true]//DCMI DMA配置
//mem0addr:存储器地址0 将要存储摄像头数据的内存地址(也可以是外设地址)
//mem1addr:存储器地址1 当只使用mem0addr的时候,该值必须为0
//memsize:存储器长度 0~65535
//memblen:存储器位宽 0,8位,1,16位,2,32位
//meminc:存储器增长方式,0,不增长;1,增长
void DCMI_DMA_Init(u32 mem0addr,u32 mem1addr,u16 memsize,u8 memblen,u8 meminc)
{
RCC->AHB1ENR|=1<<22; //DMA2时钟使能
while(DMA2_Stream1->CR&0X01);//等待DMA2_Stream1可配置
DMA2->LIFCR|=0X3D<<6*1; //清空通道1上所有中断标志
DMA2_Stream1->FCR=0X0000021;//设置为默认值
DMA2_Stream1-> AR=(u32)&DCMI->DR;//外设地址为 CMI->DR
DMA2_Stream1->M0AR=mem0addr; //mem0addr作为目标地址0
DMA2_Stream1->M1AR=mem1addr; //mem1addr作为目标地址1
DMA2_Stream1->NDTR=memsize; //传输长度为memsize
DMA2_Stream1->CR=0; //先全部复位CR寄存器值
DMA2_Stream1->CR|=0<<6; //外设到存储器模式
DMA2_Stream1->CR|=1<<8; //循环模式
DMA2_Stream1->CR|=0<<9; //外设非增量模式
DMA2_Stream1->CR|=meminc<<10; //存储器增量模式
DMA2_Stream1->CR|=2<<11; //外设数据长度:32位
DMA2_Stream1->CR|=memblen<<13; //存储器位宽,8/16/32bit
DMA2_Stream1->CR|=2<<16; //高优先级
DMA2_Stream1->CR|=0<<21; //外设突发单次传输
DMA2_Stream1->CR|=0<<23; //存储器突发单次传输
DMA2_Stream1->CR|=1<<25; //通道1 DCMI通道
if(mem1addr)//双缓冲的时候,才需要开启[/mw_shl_code]
[mw_shl_code=c,true] ......
[/mw_shl_code]
在ov2640_jpg_photo()函数中进行了调用
[mw_shl_code=c,true]DCMI_DMA_Init((u32)jpeg_buf0,(u32)jpeg_buf1,jpeg_dma_bufsize,2,1);;//DCMI DMA配置(双缓冲模式)[/mw_shl_code]
其中jpeg_buf0,jpeg_buf1在test.c定义如下
[mw_shl_code=c,true]u32 *jpeg_buf0; //JPEG数据缓存buf,通过malloc申请内存
u32 *jpeg_buf1; //JPEG数据缓存buf,通过malloc申请内存[/mw_shl_code]
在main函数中有
[mw_shl_code=c,true]jpeg_buf0=mymalloc(SRAMIN,jpeg_dma_bufsize*4); //为jpeg dma接收申请内存
jpeg_buf1=mymalloc(SRAMIN,jpeg_dma_bufsize*4); //为jpeg dma接收申请内存 [/mw_shl_code]
其中,jpeg_dma_bufsize在test.c中为
[mw_shl_code=c,true]#define jpeg_dma_bufsize 5*1024 //定义JPEG DMA接收时数据缓存jpeg_buf0/1的大小(*4字节)[/mw_shl_code]
二、test.c中函数简介
1.jpeg_data_process函数,当jpeg拍照时,用于将最后接收到的JPEG数据拷贝到SRAM(jpeg_data_buf)里面,并标记JPEG数据采集完成,同时也可以启动下一次JPEG采集
[mw_shl_code=c,true]//处理JPEG数据
//当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.
void jpeg_data_process(void)
{
u16 i;
u16 rlen;//剩余数据长度
u32 *pbuf;
if(ov2640_mode)//只有在JPEG格式下,才需要做处理.
{
if(jpeg_data_ok==0) //jpeg数据还未采集完?
{
DMA2_Stream1->CR&=~(1<<0); //停止当前传输
while(DMA2_Stream1->CR&0X01); //等待DMA2_Stream1可配置
rlen=jpeg_dma_bufsize-DMA2_Stream1->NDTR;//得到剩余数据长度
pbuf=jpeg_data_buf+jpeg_data_len;//偏移到有效数据末尾,继续添加
if(DMA2_Stream1->CR&(1<<19))for(i=0;i<rlen;i++)pbuf=jpeg_buf1;//读取buf1里面的剩余数据
else for(i=0;i<rlen;i++)pbuf=jpeg_buf0;//读取buf0里面的剩余数据
jpeg_data_len+=rlen; //加上剩余长度
jpeg_data_ok=1; //标记JPEG数据采集完按成,等待其他函数处理
}
if(jpeg_data_ok==2) //上一次的jpeg数据已经被处理了
{
DMA2_Stream1->NDTR=jpeg_dma_bufsize;//传输长度为jpeg_buf_size*4字节
DMA2_Stream1->CR|=1<<0; //重新传输
jpeg_data_ok=0; //标记数据未采集
jpeg_data_len=0; //数据重新开始
}
}
}[/mw_shl_code]
在DCMI帧中断里面被调用
[mw_shl_code=c,true]//DCMI中断服务函数
void DCMI_IRQHandler(void)
{
if(DCMI->MISR&0X01)//捕获到一帧图像
{
jpeg_data_process();//jpeg数据处理
DCMI->ICR|=1<<0; //清除帧中断
LED1=!LED1;
ov_frame++;
}
} [/mw_shl_code]
2.jpeg_dcmi_rx_callback函数,是DCMI的DMA传输完成中断,循环读取M0AR和M1AR的数据,存放到外部SRAM(jpeg_data_buf)里面
[mw_shl_code=c,true]//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=jpeg_buf0;//读取buf0里面的数据
jpeg_data_len+=jpeg_dma_bufsize;//偏移
}else //buf1已满,正常处理buf0
{
for(i=0;i<jpeg_dma_bufsize;i++)pbuf=jpeg_buf1;//读取buf1里面的数据
jpeg_data_len+=jpeg_dma_bufsize;//偏移
}
}[/mw_shl_code]
3.sw_ov2640_mode用于设置SDIO和DCMI共用的IO为DCMI模式
[mw_shl_code=c,true]//切换为OV2640模式
void sw_ov2640_mode(void)
{
OV2640_PWDN=0;//OV2640 Power Up
//GPIOC8/9/11切换为 DCMI接口
GPIO_AF_Set(GPIOC,8,13); //PC8,AF13 DCMI_D2
GPIO_AF_Set(GPIOC,9,13); //PC9,AF13 DCMI_D3
GPIO_AF_Set(GPIOC,11,13); //PC11,AF13 DCMI_D4
} [/mw_shl_code]
4.sw_sdcard_mode用于设置SDIO和DCMI共用的IO为SD模式
[mw_shl_code=c,true]//切换为SD卡模式
void sw_sdcard_mode(void)
{
OV2640_PWDN=1;//OV2640 Power Down
//GPIOC8/9/11切换为 SDIO接口
GPIO_AF_Set(GPIOC,8,12); //PC8,AF12
GPIO_AF_Set(GPIOC,9,12); //PC9,AF12
GPIO_AF_Set(GPIOC,11,12); //PC11,AF12
}[/mw_shl_code]
5.camera_new_pathname函数,根据拍照类型(bmp/jpg)创建新文件名,保证文件名和SD卡原有照片不重复(避免覆盖)
[mw_shl_code=c,true]//文件名自增(避免覆盖)
//mode:0,创建.bmp文件;1,创建.jpg文件.
//bmp组合成:形如"0 HOTO/PIC13141.bmp"的文件名
//jpg组合成:形如"0 HOTO/PIC13141.jpg"的文件名
void camera_new_pathname(u8 *pname,u8 mode)
{
u8 res;
u16 index=0;
while(index<0XFFFF)
{
if(mode==0)sprintf((char*)pname,"0 HOTO/PIC%05d.bmp",index);
else sprintf((char*)pname,"0 HOTO/PIC%05d.jpg",index);
res=f_open(ftemp,(const TCHAR*)pname,FA_READ);//尝试打开这个文件
if(res==FR_NO_FILE)break; //该文件名不存在=正是我们需要的.
index++;
}
} [/mw_shl_code]
6.ov2640_jpg_photo函数,用于jpg拍照,该函数设置OV2640为JPEG输出,然后设置分辨率1600*1200,为了确保数据完整,jpg拍照选择的是第二帧数据(第一帧可能不完整,丢弃)。拍照完成后,偏移到JPEG数据起始标识(0xff,0xd8),然后保存到SD卡
[mw_shl_code=c,true]//OV2640拍照jpg图片
//返回值:0,成功
// 其他,错误代码
u8 ov2640_jpg_photo(u8 *pname)
{
FIL* f_jpg;
u8 res=0;
u32 bwr;
u16 i;
u8* pbuf;
f_jpg=(FIL *)mymalloc(SRAMIN,sizeof(FIL)); //开辟FIL字节的内存区域
if(f_jpg==NULL)return 0XFF; //内存申请失败.
ov2640_mode=1;
sw_ov2640_mode(); //切换为OV2640模式
dcmi_rx_callback=jpeg_dcmi_rx_callback;//回调函数
DCMI_DMA_Init((u32)jpeg_buf0,(u32)jpeg_buf1,jpeg_dma_bufsize,2,1);;//DCMI DMA配置(双缓冲模式)
OV2640_JPEG_Mode(); //切换为JPEG模式
OV2640_ImageWin_Set(0,0,1600,1200);
OV2640_OutSize_Set(1600,1200);//拍照尺寸为1600*1200
DCMI_Start(); //启动传输
while(jpeg_data_ok!=1); //等待第一帧图片采集完
jpeg_data_ok=2; //忽略本帧图片,启动下一帧采集
while(jpeg_data_ok!=1); //等待第二帧图片采集完,第二帧,才保存到SD卡去.
DCMI_Stop(); //停止DMA搬运
ov2640_mode=0;
sw_sdcard_mode(); //切换为SD卡模式
res=f_open(f_jpg,(const TCHAR*)pname,FA_WRITE|FA_CREATE_NEW);//模式0,或者尝试打开失败,则创建新文件
if(res==0)
{
printf("jpeg data size:%d\r\n",jpeg_data_len*4);//串口打印JPEG文件大小
pbuf=(u8*)jpeg_data_buf;
for(i=0;i<jpeg_data_len*4;i++)//查找0XFF,0XD8
{
if((pbuf==0XFF)&&(pbuf[i+1]==0XD8))break;
}
if(i==jpeg_data_len*4)res=0XFD;//没找到0XFF,0XD8
else//找到了
{
pbuf+=i;//偏移到0XFF,0XD8处
res=f_write(f_jpg,pbuf,jpeg_data_len*4-i,&bwr);
if(bwr!=(jpeg_data_len*4-i))res=0XFE;
}
}
jpeg_data_len=0;
f_close(f_jpg);
sw_ov2640_mode(); //切换为OV2640模式
OV2640_RGB565_Mode(); //RGB565模式
DCMI_DMA_Init((u32)&LCD->LCD_RAM,0,1,1,0);//DCMI DMA配置
myfree(SRAMIN,f_jpg);
return res;
} [/mw_shl_code]
三、DCMI简单介绍(后补,2015年09月20日 11:24:32)
1.DCMI,数字摄像头接口。是一个同步并行接口,能够接收外部8位、10位、12位、或14位CMOS摄像头模块发出的高速(可达54MB/s)数据流。可支持不同数据格式:YCbCr4:2:2/RGB565逐行视屏和压缩数据(JPEG)。
2.此接口适用于黑白摄像头、X24和X5摄像头,并假定所有预处理(如调整大小)都在摄像头中执行。
3.该接口包含多达14条数据线(D13-D0)和一条像素时钟线(PIXCLK)。像素时钟线的极性可以编程,因此可以在像素时钟的上升沿或下降沿捕获数据。这些数据被放在32位数据寄存器(DCMI_DR)中,然后通过通用DMA进行传输,图像缓冲区由DMA管理,而不是由摄像头接口管理。
4.从摄像头接收的数据可以按行/帧来组织(原始YUB/RGB/拜尔模式),也可以是一系列JPEG图像。
5.数据流可由可选的HSYNC(水平同步)信号和VSYNC(垂直同步)信号硬件同步,或者通过数据流中嵌入的同步码同步。 |