第四十七章 照相机实验
[mw_shl_code=c,true]1.硬件平台:正点原子探索者STM32F407开发板
2.软件平台:MDK5.1
3.固件库版本:V1.4.0[/mw_shl_code]
上一章,我们学习了图片解码,本章我们将学习
BMP&JPEG编码,结合前面的摄像头实验,实现一个简单的照相机。本章分为如下几个部分:
47.1 BMP&JPEG编码简介
47.2 硬件设计
47.3 软件设计
47.4 下载验证
47.1 BMP&JPEG编码简介
本章,我们要实现的照相机,支持
BMP图片格式的照片和
JPEG图片格式的照片,这里简单介绍一下这两种图片格式的编码。
47.1.1 BMP编码简介
上一章,我们学习了各种图片格式的解码。本章,我们介绍最简单的图片编码方法:
BMP图片编码。通过前面的了解,我们知道
BMP文件是由文件头、位图信息头、颜 {MOD}信息和图形数据等四部分组成。我们先来了解下这几个部分。
1、
BMP文件头(
14字节):
BMP文件头数据结构含有
BMP文件的类型、文件大小和位图起始位置等信息。
//BMP文件头
typedef __packed struct
{
u16 bfType ;
//文件标志
.只对
'BM',用来识别
BMP位图类型
u32 bfSize ;
//文件大小
,占四个字节
u16 bfReserved1 ; //保留
u16 bfReserved2 ; //保留
u32 bfOffBits ;
//从文件开始到位图数据
(bitmap data)开始之间的偏移量
}BITMAPFILEHEADER ;
2、位图信息头(
40字节):
BMP位图信息头数据用于说明位图的尺寸等信息。
typedef __packed struct
{
u32
biSize ; //说明
BITMAPINFOHEADER结构所需要的字数。
long biWidth ; //说明图象的宽度,以象素为单位
long biHeight ; //说明图象的高度,以象素为单位
u16 biPlanes ; //为目标设备说明位面数,其值将总是被设为
1
u16 biBitCount ; //说明比特数
/象素,其值为
1、
4、
8、
16、
24、或
32
u32
biCompression ; //说明图象数据压缩的类型。其值可以是下述值之一:
//BI_RGB:没有压缩;
//BI_RLE8:每个象素
8比特的
RLE压缩编码,压缩格式由
2字节组成
//BI_RLE4:每个象素
4比特的
RLE压缩编码,压缩格式由
2字节组成
//BI_BITFIELDS:每个象素的比特由指定的掩码决定。
u32
biSizeImage ;//说明图象的大小
,以字节为单位。当用
BI_RGB格式时
,可设置为
0
long biXPelsPerMeter ;//说明水平分辨率,用象素
/米表示
long biYPelsPerMeter ;//说明垂直分辨率,用象素
/米表示
u32
biClrUsed ; //说明位图实际使用的彩 {MOD}表中的颜 {MOD}索引数
u32 biClrImportant
; //说明对图象显示有重要影响的颜 {MOD}索引的数目,
//如果是
0,表示都重要。
}BITMAPINFOHEADER ;
3、颜 {MOD}表:颜 {MOD}表用于说明位图中的颜 {MOD},它有若干个表项,每一个表项是一个
RGBQUAD类型的结构,定义一种颜 {MOD}。
typedef __packed struct
{
u8
rgbBlue ; //指定蓝 {MOD}强度
u8
rgbGreen ; //指定绿 {MOD}强度
u8
rgbRed ; //指定红 {MOD}强度
u8
rgbReserved ; //保留,设置为
0
}RGBQUAD ;
颜 {MOD}表中
RGBQUAD结构数据的个数由
biBitCount来确定:当
biBitCount=1、
4、
8时,分别有
2、
16、
256个表项;当
biBitCount大于
8时,没有颜 {MOD}表项。
BMP文件头、位图信息头和颜 {MOD}表组成位图信息(我们将
BMP文件头也加进来,方便处理),
BITMAPINFO结构定义如下
:
typedef __packed struct
{
BITMAPFILEHEADER
bmfHeader;
BITMAPINFOHEADER
bmiHeader;
RGBQUAD
bmiColors[1];
}BITMAPINFO;
4、位图数据:位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数
:
当
biBitCount=1时,
8个像素占
1个字节
;
当
biBitCount=4时,
2个像素占
1个字节
;
当
biBitCount=8时,
1个像素占
1个字节
;
当
biBitCount=16时,
1个像素占
2个字节
;
当
biBitCount=24时,
1个像素占
3个字节
;
当
biBitCount=32时,
1个像素占
4个字节
;
biBitCount=1 表示位图最多有两种颜 {MOD},缺省情况下是黑 {MOD}和白 {MOD},你也可以自己定义这两种颜 {MOD}。图像信息头装调 {MOD}板中将有两个调 {MOD}板项,称为索引
0和索引
1。图象数据阵列中的每一位表示一个像素。如果一个位是
0,显示时就使用索引
0的
RGB值,如果位是
1,则使用索引
1的
RGB值。
biBitCount=16 表示位图最多有
65536种颜 {MOD}。每个像素用
16位(
2个字节)表示。这种格式叫作高彩 {MOD},或叫增强型
16位 {MOD},或
64K {MOD}。它的情况比较复杂,当
biCompression成员的值是
BI_RGB时,它没有调 {MOD}板。
16位中,最低的
5位表示蓝 {MOD}分量,中间的
5位表示绿 {MOD}分量,高的
5位表示红 {MOD}分量,一共占用了
15位,最高的一位保留,设为
0。这种格式也被称作
555 16位位图。如果
biCompression成员的值是
BI_BITFIELDS,那么情况就复杂了,首先是原来调 {MOD}板的位置被三个
DWORD变量占据,称为红、绿、蓝掩码。分别用于描述红、绿、蓝分量在
16位中所占的位置。在
Windows 95(或
98)中,系统可接受两种格式的位域:
555和
565,在
555格式下,红、绿、蓝的掩码分别是:
0x7C00、
0x03E0、
0x001F,而在
565格式下,它们则分别为:
0xF800、
0x07E0、
0x001F。你在读取一个像素之后,可以分别用掩码“与”上像素值,从而提取出想要的颜 {MOD}分量(当然还要再经过适当的左右移操作)。在
NT系统中,则没有格式限制,只不过要求掩码之间不能有重叠。(注:这种格式的图像使用起来是比较麻烦的,不过因为它的显示效果接近于真彩,而图像数据又比真彩图像小的多,所以,它更多的被用于游戏软件)。
biBitCount=32 表示位图最多有
4294967296(2的
32次方
)种颜 {MOD}。这种位图的结构与
16位位图结构非常类似,当
biCompression成员的值是
BI_RGB时,它也没有调 {MOD}板,
32位中有
24位用于存放
RGB值,顺序是:最高位—保留,红
8位、绿
8位、蓝
8位。这种格式也被成为
888 32位图。如果
biCompression成员的值是
BI_BITFIELDS时,原来调 {MOD}板的位置将被三个
DWORD变量占据,成为红、绿、蓝掩码,分别用于描述红、绿、蓝分量在
32位中所占的位置。在
Windows 95(or 98)中,系统只接受
888格式,也就是说三个掩码的值将只能是:
0xFF0000、
0xFF00、
0xFF。而在
NT系统中,你只要注意使掩码之间不产生重叠就行。(注:这种图像格式比较规整,因为它是
DWORD对齐的,所以在内存中进行图像处理时可进行汇编级的代码优化(简单))。
通过以上了解,我们对
BMP有了一个比较深入的了解,本章,我们采用
16位
BMP编码(因为我们的
LCD就是
16位 {MOD}的,而且
16位
BMP编码比
24位
BMP编码更省空间),故我们需要设置
biBitCount的值为
16,这样得到新的位图信息(
BITMAPINFO)结构体:
typedef __packed struct
{
BITMAPFILEHEADER
bmfHeader;
BITMAPINFOHEADER
bmiHeader;
u32
RGB_MASK[3]; //调 {MOD}板用于存放
RGB掩码
.
}BITMAPINFO;
其实就是颜 {MOD}表由
3个
RGB掩码代替。最后,我们来看看将
LCD的显存保存为
BMP格式的图片文件的步骤:
1)创建BMP位图信息,并初始化各个相关信息
这里,我们要设置
BMP图片的分辨率为
LCD分辨率、
BMP图片的大小(整个
BMP文件大小)、
BMP的像素位数(
16位)和掩码等信息。
2)创建新BMP文件,写入BMP位图信息
我们要保存
BMP,当然要存放在某个地方(文件),所以需要先创建文件,同时先保存
BMP位图信息,之后才开始
BMP数据的写入。
3)保存位图数据。
这里就比较简单了,只需要从
LCD的
GRAM里面读取各点的颜 {MOD}值,依次写入第二步创建的
BMP文件即可。注意:保存顺序(即读
GRAM顺序)是从左到右,从下到上。
4)关闭文件。
使用
FATFS,在文件创建之后,必须调用
f_close,文件才会真正体现在文件系统里面,否则是不会写入的!这个要特别注意,写完之后,一定要调用
f_close。
BMP编码就介绍到这里。
47.1.2 JPEG编码简介
JPEG(
Joint Photographic Experts Group)是一个由
ISO和
IEC两个组织机构联合组成的一个专家组,负责制定静态的数字图像数据压缩编码标准,这个专家组开发的算法称为
JPEG算法,并且成为国际上通用的标准,因此又称为
JPEG标准。
JPEG是一个适用范围很广的静态图像数据压缩标准,既可用于灰度图像又可用于彩 {MOD}图像。
JPEG专家组开发了两种基本的压缩算法,一种是采用以离散余弦变换(
Discrete Cosine Transform,
DCT)为基础的有损压缩算法,另一种是采用以预测技术为基础的无损压缩算法。使用有损压缩算法时,在压缩比为
25:1的情况下,压缩后还原得到的图像与原始图像相比较,非图像专家难于找出它们之间的区别,因此得到了广泛的应用。
JPEG压缩是有损压缩,它利用了人的视角系统的特性,使用量化和无损压缩编码相结合来去掉视角的冗余信息和数据本身的冗余信息。
JPEG压缩编码分为三个步骤:
1)使用正向离散余弦变换(
Forward Discrete Cosine Transform,
FDCT)把空间域表示的图变换成频率域表示的图。
2)使用加权函数对
DCT系数进行量化,这个加权函数对于人的视觉系统是最佳的。
3)使用霍夫曼可变字长编码器对量化系数进行编码。
这里我们不详细介绍
JPEG压缩的过程了,大家可以自行查找相关资料。我们本章要实现的
JPEG拍照,并不需要自己压缩图像,因为我们使用的
ALIENTEK OV2640摄像头模块,直接就可以输出压缩后的
JPEG数据,我们完全不需要理会压缩过程,所以本章我们实现
JPEG拍照的关键,在于准确接收
OV2640摄像头模块发送过来的编码数据,然后将这些数据保存为
.jpg文件,就可以实现
JPEG拍照了。
在第四十章,我们定义了一个很大的数组
jpeg_buf(
124KB)来存储
JPEG图像数据,但本章,我们要用到内存管理,其他地方也要用到一些数组,所以,肯定无法再定义这么大的数组了。并且这个数组不能使用外部
SRAM(实测:
DCMI接口使用
DMA直接传输
JPEG数据到外部
SRAM会出现数据丢失,所以
DMA接收
JPEG数据只能用内部
SRAM),所以,我们本章将使用
DMA的双缓冲机制来读取,
DMA双缓冲读取
JPEG数据框图如图
47.1.2.1所示:
图
47.1.2.1 DMA双缓冲读取
JPEG数据原理框图
DMA接收来自
OV2640的
JPEG数据流,首先使用
M0AR(内存
1)来存储,当
M0AR满了以后,自动切换到
M1AR(内存
2),同时程序读取
M0AR(内存
1)的数据到外部
SRAM;当
M1AR满了以后,又切回
M0AR,同时程序读取
M1AR(内存
2)的数据到外部
SRAM;依次循环(此时的数据处理,是通过
DMA传输完成中断实现的,在中断里面处理),直到帧中断,结束一帧数据的采集,读取剩余数据到外部
SRAM,完成一次
JPEG数据的采集。
这里,
M0AR,
M1AR所指向的内存,必须是内部内存,不过由于采用了双缓冲机制,我们就不必定义一个很大的数组,一次性接收所有
JPEG数据了,而是可以分批次接收,数组可以定义的比较小。
最后,将存储在外部
SRAM的
jpeg数据,保存为
.jpg/.jpeg存放在
SD卡,就完成了一次
JPEG拍照。
47.2 硬件设计
本章实验功能简介:开机的时候先检测字库,然后检测SD卡根目录是否存在PHOTO文件夹,如果不存在则创建,如果创建失败,则报错(提示拍照功能不可用)。在找到SD卡的PHOTO文件夹后,开始初始化OV2640,在初始化成功之后,就一直在屏幕显示OV2640拍到的内容。当按下KEY_UP按键的时候,可以选择缩放,还是1:1显示,默认缩放。按下KEY0,可以拍bmp图片照片(分辨率为:LCD辨率)。按下KEY1可以拍JPEG图片照片(分辨率为UXGA,即1600*1200)。拍照保存成功之后,蜂鸣器会发出“滴”的一声,提示拍照成功。DS0还是用于指示程序运行状态,DS1用于提示DCMI帧中断。
所要用到的硬件资源如下:
1) 指示灯
DS0和
DS1
2) KEY0、
KEY1和
KEY_UP按键
3) 蜂鸣器
4) 串口
5) TFTLCD模块
6) SD卡
7) SPI FLASH
8) 摄像头模块
这几部分,在之前的实例中都介绍过了,我们在此就不介绍了。需要注意的是:
SD卡与
DCMI接口有部分
IO共用,所以他们不能同时使用,必须分时复用,本章,这部分共用
IO我们只有在拍照保存的时候,才切换为
SD卡使用,其他时间,都是被
DCMI占用的。
47.3 软件设计
打开本章实验工程,由于本章要用到
OV2640、蜂鸣器、外部
SRAM和定时器等外设,所以,先添加了
dcmi.c、
sccb.c、
ov2640.c、
beep.c、
sram.c和
timer.c等文件到
HARDWARE组下。
然后,我们来看下
PICTURE组下的
bmp.c文件里面的
bmp编码函数:
bmp_encode,该函数代码如下:
//BMP编码函数
//将当前
LCD屏幕的指定区域截图
,存为
16位格式的
BMP文件
RGB565格式
.
//保存为
rgb565则需要掩码
,需要利用原来的调 {MOD}板位置增加掩码
.这里我们增加了掩码
.
//保存为
rgb555格式则需要颜 {MOD}转换
,耗时间比较久
,所以保存为
565是最快速的办法
.
//filename:存放路径
//x,y:在屏幕上的起始坐标
//mode:模式
.0,仅创建新文件
;1,如果存在文件
,则覆盖该文件
.如果没有
,则创建新的文件
.
//返回值
:0,成功
;其他
,错误码
.
u8 bmp_encode(u8 *filename,u16 x,u16 y,u16
width,u16 height,u8 mode)
{
FIL*
f_bmp; u8 res=0;
u16
bmpheadsize; //bmp头大小
BITMAPINFO hbmp; //bmp头
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位 {MOD}
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; //红 {MOD}掩码
hbmp.RGB_MASK[1]=0X0007E0; //绿 {MOD}掩码
hbmp.RGB_MASK[2]=0X00001F; //蓝 {MOD}掩码
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首部
for(ty=y+height-1;hbmp.bmiHeader.biHeight;ty--)
{
pixcnt=0;
for(tx=x;pixcnt!=(bi4width/2);)
{
if(pixcnt<hbmp.bmiHeader.biWidth)databuf[pixcnt]=LCD_ReadPoint(tx,ty);
//读取坐标点的值
else
databuf[pixcnt]=0Xffff;//补充白 {MOD}的像素
.
pixcnt++;
tx++;
}
hbmp.bmiHeader.biHeight--;
res=f_write(f_bmp,(u8*)databuf,bi4width,&bw);//写入数据
}
f_close(f_bmp);
}
#if BMP_USE_MALLOC == 1 //使用
malloc
pic_memfree(databuf);
pic_memfree(f_bmp);
#endif
return
res;
}
该函数实现了对
LCD屏幕的任意指定区域进行截屏保存,用到的方法就是
47.1.1节我们所介绍的方法,该函数实现了将
LCD任意指定区域的内容,保存个为
16位
BMP格式,存放在指定位置(由
filename决定)。注意,代码中的
BMP_USE_MALLOC是在
bmp.h定义的一个宏,用于设置是否使用
malloc,本章我们选择使用
malloc。
在
jpeg拍照的时候,我们使用了双缓冲机制,且用到了
DMA传输完成中断,这里我们需要修改
dcmi.c里面的
DCMI_DMA_Init函数,并添加
DMA传输完成中断服务函数,代码如下:
//DCMI DMA配置
//memaddr:存储器地址
将要存储摄像头数据的内存地址
(也可以是外设地址
)
//DMA_BufferSize:存储器长度
0~65535
//DMA_MemoryDataSize:存储器位宽
//DMA_MemoryInc:存储器增长方式
@defgroup DMA_memory_incremented_mode
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){}//等待可配置
/* 配置
DMA Stream */
DMA_InitStructure.DMA_Channel = DMA_Channel_1; //通道
1 DCMI通道
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&DCMI->DR;//外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;//存储器
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;
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寄存器、
}
}
void (*dcmi_rx_callback)(void);//DCMI DMA接收回调函数
//DMA2_Stream1中断服务函数
(仅双缓冲模式会用到
)
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(); //执行摄像头接收回调函数
,读取数据等操作在这里面处理
}
}
这里,
DCMI_DMA_Init函数,和第四十章相比增加了一个参数:
DMA_Memory1BaseAddr,用于设置
DMA的第二个内存地址,同时根据该值是否为非
0,来判断是否需要使用双缓冲机制,只有在双缓冲机制下,才开启
DMA传输完成中断。
DMA2_Stream1_IRQHandler函数,是
DMA传输完成中断,里面通过
dcmi_rx_callback回调函数(函数指针,指向
jpeg_dcmi_rx_callback函数),及时将满了的内存(
M0AR或
M1AR)数据读取到外部
SRAM。
最后我们来看看
main.c文件源码:
u8 ov2640_mode=0; //工作模式
:0,RGB565模式
;1,JPEG模式
#define jpeg_dma_bufsize 5*1024 //定义
JPEG DMA接收时数据缓存大小
(*4字节
)
volatile u32 jpeg_data_len=0; //buf中的
JPEG有效数据长度
(*4字节
)
volatile u8 jpeg_data_ok=0; //JPEG数据采集完成标志
//0,数据没有采集完
;
//1,数据采集完了
,但是还没处理
;
//2,数据已经处理完成了
,可以开始下一帧接收
u32 *jpeg_buf0; //JPEG数据缓存
buf,通过
malloc申请内存
u32 *jpeg_buf1; //JPEG数据缓存
buf,通过
malloc申请内存
u32 *jpeg_data_buf; //JPEG数据缓存
buf,通过
malloc申请内存
//处理
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数据还未采集完
?
{
DMA_Cmd(DMA2_Stream1,DISABLE); //停止当前传输
while(DMA_GetCmdStatus(DMA2_Stream1)
!= DISABLE);//等待可配置
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=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数据已经被处理了
{
DMA_SetCurrDataCounter(DMA2_Stream1,jpeg_dma_bufsize);
//传输长度为
jpeg_buf_size*4字节
DMA_Cmd(DMA2_Stream1,ENABLE);//重新传输
jpeg_data_ok=0; //标记数据未采集
jpeg_data_len=0; //数据重新开始
}
}
}
//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;//偏移
}
}
//切换为
OV2640模式(
GPIOC8/9/11切换为
DCMI接口)
void sw_ov2640_mode(void)
{
OV2640_PWDN=0;//OV2640
Power Up
GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_DCMI);
GPIO_PinAFConfig(GPIOC,GPIO_PinSource9,GPIO_AF_DCMI);
GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_DCMI);
}
//切换为
SD卡模式(
GPIOC8/9/11切换为
SDIO接口)
void sw_sdcard_mode(void)
{
OV2640_PWDN=1;//OV2640
Power Down
GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_SDIO);
//PC8,AF12
GPIO_PinAFConfig(GPIOC,GPIO_PinSource9,GPIO_AF_SDIO);//PC9,AF12
GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_SDIO);
}//文件名自增(避免覆盖)
//mode:0,创建
.bmp文件
;1,创建
.jpg文件
.
//bmp组合成
:形如
"0HOTO/PIC13141.bmp"的文件名
//jpg组合成
:形如
"0HOTO/PIC13141.jpg"的文件名
void camera_new_pathname(u8 *pname,u8 mode)
{
u8
res; u16 index=0;
while(index<0XFFFF)
{
if(mode==0)sprintf((char*)pname,"0HOTO/PIC%05d.bmp",index);
else
sprintf((char*)pname,"0HOTO/PIC%05d.jpg",index);
res=f_open(ftemp,(const
TCHAR*)pname,FA_READ);//尝试打开这个文件
if(res==FR_NO_FILE)break; //该文件名不存在
=正是我们需要的
.
index++;
}
}
//OV2640拍照
jpg图片
//返回值
:0,成功
// 其他
,错误代码
u8 ov2640_jpg_photo(u8 *pname)
{
FIL*
f_jpg; u8* pbuf;
u8
res=0; u32 bwr; u16 i;
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,
DMA_MemoryDataSize_Word,DMA_MemoryInc_Enable);//双缓冲模式
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
",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,DMA_MemoryDataSize_HalfWord,
DMA_MemoryInc_Disable);//DCMI DMA配置
myfree(SRAMIN,f_jpg);
return
res;
}
int main(void)
{
u8
res; u8 i;
u8
*pname; //带路径的文件名
u8
key; //键值
u8
sd_ok=1; //0,sd卡不正常
;1,SD卡正常
.
u8 scale=1; //默认是全尺寸缩放
u8
msgbuf[15]; //消息缓存区
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组
2
……
//省略部分代码
while(font_init())
//检查字库
{
LCD_ShowString(30,50,200,16,16,"Font
Error!"); delay_ms(200);
LCD_Fill(30,50,240,66,WHITE);
delay_ms(200); //清除显示
}
……
//省略部分代码
res=f_mkdir("0:/PHOTO"); //创建
PHOTO文件夹
if(res!=FR_EXIST&&res!=FR_OK)
//发生了错误
{
Show_Str(30,150,240,16,"SD卡错误
!",16,0); delay_ms(200);
Show_Str(30,170,240,16,"拍照功能将不可用
!",16,0); sd_ok=0;
}
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文
---------------------------------
可能是图片数据太多,不够存储了.
参考下综合实验的解决方法.
你用综合实验拍照,应该不存在这个问题.
---------------------------------
想知道这个问题根本原因,具体一点的,综合实验没问题,寄存器实验也没有问题,测试几天了,想做一次不为结果,步步为营的项目,原子能不能指点12?
---------------------------------
因为一张1600*1200的JPEG图片,尺寸可能会在80~200KB之间变化(根据拍摄内容不同,JPEG图片大小不同),STM32F4内部只有128KB的连续内存,我们例程是开了120K吧?如果超过120K,势必出错.
所以,综合实验,我们用了一种方法, 内部SRAM只用一点点,利用DMA,并适当处理,把图片数据拷贝到外部SRAM,这样,再打的图片都存放的下.
一周热门 更多>