OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 10722|回复: 16

【正点原子探索者STM32F407开发板例程连载+教学】第47章 照相机实验

[复制链接]

230

主题

1950

帖子

10

精华

论坛元老

Rank: 8Rank: 8

积分
4562
金钱
4562
注册时间
2010-12-14
在线时间
32 小时
发表于 2014-12-9 12:18:18 | 显示全部楼层 |阅读模式


第四十七章 照相机实验


 

[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文件是由文件头、位图信息头、颜色信息和图形数据等四部分组成。我们先来了解下这几个部分。 

1BMP文件头(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 ;       //说明比特数/象素,其值为1481624、或32

    u32 biCompression ;    //说明图象数据压缩的类型。其值可以是下述值之一:

       //BI_RGB:没有压缩;

       //BI_RLE8:每个象素8比特的RLE压缩编码,压缩格式由2字节组成

    //BI_RLE4:每个象素4比特的RLE压缩编码,压缩格式由2字节组成

      //BI_BITFIELDS:每个象素的比特由指定的掩码决定。

    u32 biSizeImage ;//说明图象的大小,以字节为单位。当用BI_RGB格式时,可设置为

    long  biXPelsPerMeter ;//说明水平分辨率,用象素/米表示

    long  biYPelsPerMeter ;//说明垂直分辨率,用象素/米表示

    u32 biClrUsed ;             //说明位图实际使用的彩色表中的颜色索引数

    u32 biClrImportant ;    //说明对图象显示有重要影响的颜色索引的数目,

//如果是0,表示都重要。

}BITMAPINFOHEADER ;

3、颜色表:颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。

typedef __packed struct

{

    u8 rgbBlue ;       //指定蓝色强度

    u8 rgbGreen ;        //指定绿色强度

    u8 rgbRed ;          //指定红色强度

    u8 rgbReserved ;    //保留,设置为0

}RGBQUAD ;

颜色表中RGBQUAD结构数据的个数由biBitCount来确定:当biBitCount=148时,分别有216256个表项;当biBitCount大于8时,没有颜色表项。 

BMP文件头、位图信息头和颜色表组成位图信息(我们将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 表示位图最多有两种颜色,缺省情况下是黑色和白色,你也可以自己定义这两种颜色。图像信息头装调色板中将有两个调色板项,称为索引0和索引1。图象数据阵列中的每一位表示一个像素。如果一个位是0,显示时就使用索引0RGB值,如果位是1,则使用索引1RGB值。   

biBitCount=16 表示位图最多有65536种颜色。每个像素用16位(2个字节)表示。这种格式叫作高彩色,或叫增强型16位色,或64K色。它的情况比较复杂,当biCompression成员的值是BI_RGB时,它没有调色板。16位中,最低的5位表示蓝色分量,中间的5位表示绿色分量,高的5位表示红色分量,一共占用了15位,最高的一位保留,设为0。这种格式也被称作555 16位位图。如果biCompression成员的值是BI_BITFIELDS,那么情况就复杂了,首先是原来调色板的位置被三个DWORD变量占据,称为红、绿、蓝掩码。分别用于描述红、绿、蓝分量在16位中所占的位置。在Windows 95(或98)中,系统可接受两种格式的位域:555565,在555格式下,红、绿、蓝的掩码分别是:0x7C000x03E00x001F,而在565格式下,它们则分别为:0xF8000x07E00x001F。你在读取一个像素之后,可以分别用掩码“与”上像素值,从而提取出想要的颜色分量(当然还要再经过适当的左右移操作)。在NT系统中,则没有格式限制,只不过要求掩码之间不能有重叠。(注:这种格式的图像使用起来是比较麻烦的,不过因为它的显示效果接近于真彩,而图像数据又比真彩图像小的多,所以,它更多的被用于游戏软件)。

biBitCount=32 表示位图最多有4294967296(232次方)种颜色。这种位图的结构与16位位图结构非常类似,当biCompression成员的值是BI_RGB时,它也没有调色板,32位中有24位用于存放RGB值,顺序是:最高位—保留,红8位、绿8位、蓝8位。这种格式也被成为888 32位图。如果 biCompression成员的值是BI_BITFIELDS时,原来调色板的位置将被三个DWORD变量占据,成为红、绿、蓝掩码,分别用于描述红、绿、蓝分量在32位中所占的位置。在Windows 95(or 98)中,系统只接受888格式,也就是说三个掩码的值将只能是:0xFF00000xFF000xFF。而在NT系统中,你只要注意使掩码之间不产生重叠就行。(注:这种图像格式比较规整,因为它是DWORD对齐的,所以在内存中进行图像处理时可进行汇编级的代码优化(简单))。

通过以上了解,我们对BMP有了一个比较深入的了解,本章,我们采用16BMP编码(因为我们的LCD就是16位色的,而且16BMP编码比24BMP编码更省空间),故我们需要设置biBitCount的值为16,这样得到新的位图信息(BITMAPINFO)结构体:

typedef __packed struct

{

       BITMAPFILEHEADER bmfHeader;

       BITMAPINFOHEADER bmiHeader; 

       u32 RGB_MASK[3];                   //调色板用于存放RGB掩码.

}BITMAPINFO;

其实就是颜色表由3RGB掩码代替。最后,我们来看看将LCD的显存保存为BMP格式的图片文件的步骤:

1)创建BMP位图信息,并初始化各个相关信息

这里,我们要设置BMP图片的分辨率为LCD分辨率、BMP图片的大小(整个BMP文件大小)、BMP的像素位数(16位)和掩码等信息。

2)创建新BMP文件,写入BMP位图信息

我们要保存BMP,当然要存放在某个地方(文件),所以需要先创建文件,同时先保存BMP位图信息,之后才开始BMP数据的写入。

3)保存位图数据。

这里就比较简单了,只需要从LCDGRAM里面读取各点的颜色值,依次写入第二步创建的BMP文件即可。注意:保存顺序(即读GRAM顺序)是从左到右,从下到上。

4)关闭文件。

使用FATFS,在文件创建之后,必须调用f_close,文件才会真正体现在文件系统里面,否则是不会写入的!这个要特别注意,写完之后,一定要调用f_close

BMP编码就介绍到这里。

47.1.2 JPEG编码简介

JPEGJoint Photographic Experts Group)是一个由ISOIEC两个组织机构联合组成的一个专家组,负责制定静态的数字图像数据压缩编码标准,这个专家组开发的算法称为JPEG算法,并且成为国际上通用的标准,因此又称为JPEG标准。JPEG是一个适用范围很广的静态图像数据压缩标准,既可用于灰度图像又可用于彩色图像。

JPEG专家组开发了两种基本的压缩算法,一种是采用以离散余弦变换(Discrete Cosine TransformDCT)为基础的有损压缩算法,另一种是采用以预测技术为基础的无损压缩算法。使用有损压缩算法时,在压缩比为25:1的情况下,压缩后还原得到的图像与原始图像相比较,非图像专家难于找出它们之间的区别,因此得到了广泛的应用。

JPEG压缩是有损压缩,它利用了人的视角系统的特性,使用量化和无损压缩编码相结合来去掉视角的冗余信息和数据本身的冗余信息。

JPEG压缩编码分为三个步骤:

1)使用正向离散余弦变换(Forward Discrete Cosine TransformFDCT)把空间域表示的图变换成频率域表示的图。

2)使用加权函数对DCT系数进行量化,这个加权函数对于人的视觉系统是最佳的。

3)使用霍夫曼可变字长编码器对量化系数进行编码。

这里我们不详细介绍JPEG压缩的过程了,大家可以自行查找相关资料。我们本章要实现的JPEG拍照,并不需要自己压缩图像,因为我们使用的ALIENTEK OV2640摄像头模块,直接就可以输出压缩后的JPEG数据,我们完全不需要理会压缩过程,所以本章我们实现JPEG拍照的关键,在于准确接收OV2640摄像头模块发送过来的编码数据,然后将这些数据保存为.jpg文件,就可以实现JPEG拍照了。

在第四十章,我们定义了一个很大的数组jpeg_buf124KB)来存储JPEG图像数据,但本章,我们要用到内存管理,其他地方也要用到一些数组,所以,肯定无法再定义这么大的数组了。并且这个数组不能使用外部SRAM(实测:DCMI接口使用DMA直接传输JPEG数据到外部SRAM会出现数据丢失,所以DMA接收JPEG数据只能用内部SRAM),所以,我们本章将使用DMA的双缓冲机制来读取,DMA双缓冲读取JPEG数据框图如图47.1.2.1所示:


47.1.2.1 DMA双缓冲读取JPEG数据原理框图

DMA接收来自OV2640JPEG数据流,首先使用M0AR(内存1)来存储,当M0AR满了以后,自动切换到M1AR(内存2),同时程序读取M0AR(内存1)的数据到外部SRAM;当M1AR满了以后,又切回M0AR,同时程序读取M1AR(内存2)的数据到外部SRAM;依次循环(此时的数据处理,是通过DMA传输完成中断实现的,在中断里面处理),直到帧中断,结束一帧数据的采集,读取剩余数据到外部SRAM,完成一次JPEG数据的采集。

这里,M0ARM1AR所指向的内存,必须是内部内存,不过由于采用了双缓冲机制,我们就不必定义一个很大的数组,一次性接收所有JPEG数据了,而是可以分批次接收,数组可以定义的比较小。

最后,将存储在外部SRAMjpeg数据,保存为.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)  指示灯DS0DS1

2)  KEY0KEY1KEY_UP按键

3)  蜂鸣器

4)  串口

5)  TFTLCD模块

6)  SD

7)  SPI FLASH

8)  摄像头模块

这几部分,在之前的实例中都介绍过了,我们在此就不介绍了。需要注意的是:SD卡与DCMI接口有部分IO共用,所以他们不能同时使用,必须分时复用,本章,这部分共用IO我们只有在拍照保存的时候,才切换为SD卡使用,其他时间,都是被DCMI占用的。

47.3 软件设计

打开本章实验工程,由于本章要用到OV2640、蜂鸣器、外部SRAM和定时器等外设,所以,先添加了dcmi.csccb.cov2640.cbeep.csram.ctimer.c等文件到HARDWARE组下。

然后,我们来看下PICTURE组下的bmp.c文件里面的bmp编码函数:bmp_encode,该函数代码如下:

//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)

{                         

       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;       //bmp16位色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首部 

              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;//补充白色的像素

                            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任意指定区域的内容,保存个为16BMP格式,存放在指定位置(由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函数),及时将满了的内存(M0ARM1AR)数据读取到外部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\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,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文件申请内存(最大300KB)

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

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

      {        

              Show_Str(30,190,240,16,"内存分配失败!",16,0); delay_ms(200);   

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

       }  

       while(OV2640_Init())//初始化OV2640

       {

              Show_Str(30,190,240,16,"OV2640错误!",16,0); delay_ms(200);           

           LCD_Fill(30,190,239,206,WHITE); delay_ms(200);         

       }    

      Show_Str(30,190,200,16,"OV2640 正常",16,0);

       delay_ms(2000);

       OV2640_RGB565_Mode();   //JPEG模式

       My_DCMI_Init();                //DCMI配置

       DCMI_DMA_Init((u32)&LCD->LCD_RAM,0,1,DMA_MemoryDataSize_HalfWord,

DMA_MemoryInc_Disable);//DCMI DMA配置 

      OV2640_OutSize_Set(lcddev.width,lcddev.height);

       DCMI_Start(); //启动传输

      while(1)

       {    

              key=KEY_Scan(0);//不支持连按

              if(key)

              {

                     DCMI_Stop(); //停止显示

                     if(key==WKUP_PRES) //缩放 or 1:1显示

                     {

                            scale=!scale; 

                            ……//省略部分代码

                     }else if(sd_ok)//SD卡正常才可以拍照

                     {   

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

                            if(key==KEY0_PRES)  //BMP拍照

                            {

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

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

                            }else if(key==KEY1_PRES)//JPG拍照

                            {

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

                                   res=ov2640_jpg_photo(pname);

                                   if(scale==0)

                                   {                                       

OV2640_ImageWin_Set((1600-lcddev.width)/2,(1200-lcddev.heig

ht)/2,lcddev.width,lcddev.height);//1:1真实尺寸

                                          OV2640_OutSize_Set(lcddev.width,lcddev.height);

                                   }else OV2640_ImageWin_Set(0,0,1600,1200);    //全尺寸缩放

                                   OV2640_OutSize_Set(lcddev.width,lcddev.height);            

                            }

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

                            if(res) Show_Str(30,130,240,16,"写入文件错误!",16,0);//拍照有误

else

                            {

                                   Show_Str(30,130,240,16,"拍照成功!",16,0);

                                   Show_Str(30,150,240,16,"保存为:",16,0);

                                   Show_Str(30+42,150,240,16,pname,16,0);            

                                   BEEP=1; delay_ms(100);//蜂鸣器短叫,提示拍照完成

                            }                       

                     }else //提示SD卡错误

                     {                                    

                            Show_Str(30,130,240,16,"SD卡错误!",16,0);

                            Show_Str(30,150,240,16,"拍照功能不可用!",16,0);                   

                     }         

                     BEEP=0;               //关闭蜂鸣器

                     if(key!=WKUP_PRES)delay_ms(1800);//非尺寸切换,等待1.8秒钟

                     DCMI_Start();      //停止显示 

              }

              delay_ms(10); i++;       

              if(i==20) { i=0; LED0=!LED0;}//DS0闪烁.

       }

}                                                                                                      

此部分代码有点多,main函数里面我们省略了部分代码,以节省篇幅。接下来分别介绍下这些函数。

jpeg_data_process函数,该函数在DCMI帧中断里面被调用,当JPEG拍照时,该函数用于将最后接收到的JPEG数据拷贝到外部SRAMjpeg_data_buf)里面,并标记JPEG数据采集完成,同时该函数也可以启动下一次JPEG采集。

jpeg_dcmi_rx_callback函数,该函数是DCMIDMA传输完成中断,循环读取M0ARM1AR的数据,存放到外部SRAMjpeg_data_buf)里面。

sw_ov2640_modesw_sdcard_mode函数,用于设置SDIODCMI共用的几个IO到底归哪个外设使用,以实现分时复用。

camera_new_pathname函数,则用根据拍照类型(bmp/jpg)创建新文件名,保证文件名和SD卡原有的照片不重复(避免覆盖)。

ov2640_jpg_photo函数,则用于jpg拍照,该函数设置OV2640JPEG输出,然后设置分辨率为1600*1200。为了确保数据完整,jpg拍照选择的是第二帧数据(第一帧可能不完整,直接丢弃)。拍照完成后,偏移到JPEG数据起始标识(0XFF,0XD8),然后保存到SD卡。注意,这里得先切换为SD卡模式,才能进行数据保存,拍照完再切回OV2640模式。

bmp拍照则比较简单,直接在main函数里面调用bmp_encode函数实现,这里我们就不再细说了,至此照相机实验代码编写完成。

最后,本实验可以通过USMART来测试BMP编码函数,将bmp_encode函数添加到USMART管理,即可通过串口自行控制拍照,方便测试。

47.4 下载验证

在代码编译成功之后,我们通过下载代码到ALIENTEK探索者STM32F4开发板上,得到如图47.4.1所示界面:

47.4.1 程序运行效果图

随后,进入监控界面。此时,我们可以按下KEY0KEY1,即可进行bmp/jpg拍照。拍照得到的照片效果如图47.4.2和图47.4.3所示:

47.4.2 拍照样图(bmp拍照样图)

47.4.3 拍照样图(jpg拍照样图)

       最后,我们还可以通过USMART调用bmp_encode函数,实现串口控制bmp拍照,还可以拍成各种尺寸哦(不过必须≤LCD分辨率)!

 

 

实验详细手册和源码下载地址:http://www.openedv.com/posts/list/41586.htm 

正点原子探索者STM32F407开发板购买地址http://item.taobao.com/item.htm?id=41855882779

  


 

第四十七章 照相机实验-STM32F4开发指南-正点原子探索者STM32开发板.pdf

812.3 KB, 下载次数: 824

实验42 照相机实验.zip

1.24 MB, 下载次数: 1065

我是开源电子网?网站管理员,对网站有任何问题,请与我联系!QQ:389063473Email:389063473@qq.com
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

0

主题

4

帖子

0

精华

新手入门

积分
24
金钱
24
注册时间
2014-3-14
在线时间
0 小时
发表于 2014-12-30 10:13:23 | 显示全部楼层
不知道为什么我的开发板按键0的时候拍bmp图像正常,但是按键1拍摄的jpg显示就不对了,屏幕一直都会有图像显示,就是保存在内存卡里的jpg图像是错误的,有点问题。


还有就是spi-flash的字体没有了的话怎么重新放回去呢,现在一直显示不了中文字体
回复 支持 反对

使用道具 举报

0

主题

4

帖子

0

精华

新手入门

积分
24
金钱
24
注册时间
2014-3-14
在线时间
0 小时
发表于 2014-12-30 10:30:19 | 显示全部楼层
回复【2楼】心如止水:
---------------------------------
还有就是单独进行摄像头实验室没有问题的,摄像头实验的时候电脑是可以接收到正确的jpg图像的
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165371
金钱
165371
注册时间
2010-12-1
在线时间
2110 小时
发表于 2014-12-30 22:51:43 | 显示全部楼层
回复【2楼】心如止水:
---------------------------------
因为单张照片超过SRAM大小了,才会出现这个情况,将jpeg分辨率设置小一点就好。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

0

主题

4

帖子

0

精华

新手入门

积分
24
金钱
24
注册时间
2014-3-14
在线时间
0 小时
发表于 2015-1-3 18:44:13 | 显示全部楼层
好像不管用,我把jpeg的分辨率调到320*240,还是出现同样的问题,效果就是

,偶尔会有一两张照片是没有问题的,我看了一下照片的数据,是以FF D8开头FF D9结尾的,就是结尾后面会有很多个0,不过这个应该没问题,想问一下,你们直接使用提供的源程序行不行的,这个问题搞了好久了,一直都搞不定啊
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165371
金钱
165371
注册时间
2010-12-1
在线时间
2110 小时
发表于 2015-1-4 00:43:55 | 显示全部楼层
回复【5楼】心如止水:
---------------------------------
直接用我们提供的代码应该没问题才是。
你试试寄存器版本的例程。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

0

主题

5

帖子

0

精华

新手上路

积分
25
金钱
25
注册时间
2015-1-26
在线时间
0 小时
发表于 2015-1-26 16:42:58 | 显示全部楼层
在综合例程里面拍照显示的是正确的,用“实验42 照相机实验”这个实验的代码拍照存到SD卡里出现乱码情况,原子哥这种情况何解?

回复 支持 反对

使用道具 举报

0

主题

5

帖子

0

精华

新手上路

积分
25
金钱
25
注册时间
2015-1-26
在线时间
0 小时
发表于 2015-1-27 09:19:35 | 显示全部楼层
自顶一下下,有知道怎么解决的高手来搞一下啊
回复 支持 反对

使用道具 举报

0

主题

5

帖子

0

精华

新手上路

积分
25
金钱
25
注册时间
2015-1-26
在线时间
0 小时
发表于 2015-1-27 09:32:05 | 显示全部楼层
用寄存器版本的也不行、、、
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165371
金钱
165371
注册时间
2010-12-1
在线时间
2110 小时
发表于 2015-1-28 00:21:17 | 显示全部楼层
回复【9楼】huijianyan:
---------------------------------
可能是图片数据太多,不够存储了.
参考下综合实验的解决方法.
你用综合实验拍照,应该不存在这个问题.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

0

主题

5

帖子

0

精华

新手上路

积分
25
金钱
25
注册时间
2015-1-26
在线时间
0 小时
发表于 2015-2-3 17:51:32 | 显示全部楼层
回复【10楼】正点原子:
---------------------------------
想知道这个问题根本原因,具体一点的,综合实验没问题,寄存器实验也没有问题,测试几天了,想做一次不为结果,步步为营的项目,原子能不能指点12?
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165371
金钱
165371
注册时间
2010-12-1
在线时间
2110 小时
发表于 2015-2-3 22:59:42 | 显示全部楼层
回复【11楼】huijianyan:
---------------------------------
因为一张1600*1200的JPEG图片,尺寸可能会在80~200KB之间变化(根据拍摄内容不同,JPEG图片大小不同),STM32F4内部只有128KB的连续内存,我们例程是开了120K吧?如果超过120K,势必出错.
所以,综合实验,我们用了一种方法, 内部SRAM只用一点点,利用DMA,并适当处理,把图片数据拷贝到外部SRAM,这样,再打的图片都存放的下.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

13

主题

206

帖子

1

精华

高级会员

Rank: 4

积分
613
金钱
613
注册时间
2014-10-26
在线时间
32 小时
发表于 2015-4-9 23:30:38 | 显示全部楼层
原子哥,我想一直到MINI STM32,但是它没有外部SRAM啊, 那改怎么办?
学习!
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165371
金钱
165371
注册时间
2010-12-1
在线时间
2110 小时
发表于 2015-4-9 23:35:57 | 显示全部楼层
回复【13楼】q27488:
---------------------------------
没办法。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

13

主题

206

帖子

1

精华

高级会员

Rank: 4

积分
613
金钱
613
注册时间
2014-10-26
在线时间
32 小时
发表于 2015-4-12 00:05:37 | 显示全部楼层
回复【14楼】正点原子:
---------------------------------
你的意思是  mini32 无法 实现 照相机?  因为没有外部SRAM缓存数据么?
学习!
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165371
金钱
165371
注册时间
2010-12-1
在线时间
2110 小时
发表于 2015-4-12 23:36:36 | 显示全部楼层
回复【15楼】q27488:
---------------------------------
也可以的,就是不太好实现而已。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

0

主题

12

帖子

0

精华

初级会员

Rank: 2

积分
74
金钱
74
注册时间
2013-12-29
在线时间
7 小时
发表于 2015-6-11 18:11:59 | 显示全部楼层
调用库就出现这种问题,太奇怪了
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-24 17:51

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表