ARM技术william hill官网
直播中

正点原子运营官

5年用户 1793经验值
擅长:模拟技术 嵌入式技术 控制/MCU
私信 关注
[资料]

【正点原子FPGA连载】第二十九章OV7725照相机实验-领航者 ZYNQ 之嵌入式开发指南

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/FPGA/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
5)关注正点原子公众号,获取最新资料



第二十九章OV7725照相机实验

在“OV7725摄像头LCD显示实验”中,我们将摄像头采集的图像显示在LCD上。而在“SD卡读BMP图片LCD显示实验”中,我们从SD卡中读取BMP图片,通过LCD显示。本章我们将综合上述两个实验的内容,将OV7725采集的图像以BMP图片的格式存在SD卡中,从而实现拍照功能。
本章包括以下几个部分:
2929.1简介
29.2实验任务
29.3硬件设计
29.4软件设计
29.5下载验证
29.1简介
本次实验在“OV7725摄像头LCD显示实验”的基础上进行,有关OV7725摄像头采集及LCD显示部分的内容,请大家参考前面的章节。除此之外,要将采集的图片以BMP格式存入SD卡,我们还需要了解BMP图片的格式,以及SD卡图片存取的知识。这部分内容请大家参考“SD卡读BMP图片LCD显示实验”。
由上述两个章节的内容我们知道,OV7725摄像头会将采集的图像以RGB888的格式存在DDR显存中。而真彩色的BMP图片中,图像部分同样是RGB888格式。因此,在利用OV7725实现拍照功能时,我们只需要给显存中的图像数据加上BMP文件头,然后再将它们写到SD卡中即可。
29.2实验任务
本章的实验任务是使用OV7725摄像头,实现照相机功能。在拍照时,将摄像头采集的图像以BMP文件的格式存入SD卡中。
29.3硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:




图 29.3.1 系统框图

可以看出,本次实验的系统框图是在“OV7725摄像头LCD显示实验”的基础上添加了SD卡模块。SD卡控制器位于ZYNQ PS端,通过MIO与SD卡连接。
首先修改“OV7725摄像头LCD显示实验”中ZYNQ PS的配置,使能其SD卡控制器,如下图所示:




图 29.3.2 SD卡配置界面

同时我们还需要勾选SD卡控制器的“Card Detect”接口,并将Bank 1的电平设置为“LVCOMS 1.8V”,如下图所示:




图 29.3.3 使能CD接口

有关ZYNQ PS中SD卡控制器的介绍及配置方法请大家参考“SD卡读写TXT文本实验”。
ZYNQ处理系统就配置完成后,重新对设计进行验证,并执行“Generate Output Products”操作。由于SD卡控制器通过MIO连接到PS端的引脚上,因此不需要我们手动进行管脚分配,直接在左侧Flow Navigator导航栏中找点击“Generate Bitstream”,重新生成Bitstream文件
在生成Bitstream之后,我们先在菜单栏选择File > Launch SDK,启动SDK软件,可以看到已经加载了OV7725摄像头实验所创建的工程。
然后在Vivado菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中勾选“Include bitstream”。然后提示是否覆盖之前的文件,点击“Yes”。
29.4软件设计
在重新导出硬件之后,SDK软件中会弹出警告,提示我们硬件信息有改动。然后我们点击“OK”确认,工具重新进行编译。为了能够在软件中读写SD卡中的文件,我们需要设置BSP工程,添加并设置FATFS库。具体的方法请参考“SD卡读写TXT文本实验”软件设计部分。
接下来修改main.c文件。首先我们在文件的开头添加了一些头文件,全局变量,以及函数声明等,代码如下所示:

  • 1 #include
  • 2 #include
  • 3 #include
  • 4 #include "xil_types.h"
  • 5 #include "xil_cache.h"
  • 6 #include "xparameters.h"
  • 7 #include "xgpio.h"
  • 8 #include "xaxivdma.h"
  • 9 #include "xaxivdma_i.h"
  • 10 #include "display_ctrl/display_ctrl.h"
  • 11 #include "ov7725/ov7725_init.h"
  • 12 #include "vdma_api/vdma_api.h"
  • 13 #include "emio_sccb_cfg/emio_sccb_cfg.h"
  • 14 #include "ff.h"
  • 15 #include "xil_cache.h"
  • 16
  • 17 //宏定义
  • 18 #define FRAME_BUFFER_NUM 3 //帧缓存个数
  • 19 #define DYNCLK_BASEADDR XPAR_AXI_DYNCLK_0_BASEADDR //动态时钟基地址
  • 20 #define VDMA_ID XPAR_AXIVDMA_0_DEVICE_ID //VDMA器件ID
  • 21 #define DISP_VTC_ID XPAR_VTC_0_DEVICE_ID //VTC器件ID
  • 22 #define AXI_GPIO_0_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL端 AXI GPIO 0(lcd_id)器件ID
  • 23 #define AXI_GPIO_0_CHANEL 1 //使用AXI GPIO(lcd_id)通道1
  • 24
  • 25 //全局变量
  • 26 XAxiVdma vdma;
  • 27 DisplayCtrl dispCtrl;
  • 28 XGpio axi_gpio_inst; //PL端 AXI GPIO 驱动实例
  • 29 VideoMode vd_mode;
  • 30
  • 31 //文件系统
  • 32 static FATFS fatfs;
  • 33 //BMP图片文件头
  • 34 u8 bmp_head[54] = {
  • 35 0x42,0x4d,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x36,0x0,0x0,0x0,0x28,0x0,
  • 36 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x18,0x0,0x0,0x0,
  • 37 0x0,0x0,0x0,0x0,0x0,0x0,0xc4,0xe,0x0,0x0,0xc4,0x0e,0x0,0x0,0x0,0x0,
  • 38 0x0,0x0,0x0,0x0,0x0,0x0 };
  • 39 //BMP图片各参数偏移地址
  • 40 UINT *bf_size = (UINT *)(bmp_head + 0x2);
  • 41 UINT *bmp_width = (UINT *)(bmp_head + 0x12);
  • 42 UINT *bmp_height = (UINT *)(bmp_head + 0x16);
  • 43 UINT *bmp_size = (UINT *)(bmp_head + 0x22);
  • 44 //BMP图片编号
  • 45 int pic_cnt = 0;
  • 46
  • 47 //抓拍的图片显存地址
  • 48 unsigned int const bmp_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x11000000);
  • 49 //frame buffer的起始地址
  • 50 unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x1000000);
  • 51 //LCD ID
  • 52 unsigned int lcd_id=0;
  • 53
  • 54 //将显存图像以BMP格式写入SD卡
  • 55 void write_sd_bmp(u8 *frame);
  • 56
复制代码

首先在代码的14行,我们包含了ff.h头文件,这样才可以通过调用FATFS库函数来向SD卡中写入BMP图片文件。
然后在代码的31至45行,我们定义了一系列与BMP文件格式相关的变量。包括BMP文件头以及各参数的偏移地址等。这些变量我们在“SD卡读BMP图片LCD显示实验”中有过详细介绍,如果对它们不熟悉的话,请大家参考相应的章节。
另外,我们在内存中指定了一个存储空间,用于存储摄像头抓拍到的图片数据。这个存储空间的起始地址保存在变量bmp_addr中,如程序第48行所示。需要注意的是,这个起始地址不能与VDMA的帧缓存空间有重叠。
最后,在代码的55行,我们声明了一个函数“write_sd_bmp(u8 *frame)”。这个函数可以将抓拍的图片以BMP格式写入SD卡中,它的参数就是变量bmp_addr所指定的地址。然后我们在main函数中将调用这个函数,实现拍照功能。
main函数的代码如下所示:

  • 57 int main(void)
  • 58 {
  • 59 int status = 0;
  • 60 char cmd; //串口输入的拍照指令
  • 61 int rd_index; //VDMA读通道操作的帧缓存编号
  • 62 unsigned int rd_fram_addr; //VDMA读通道操作的帧缓存地址
  • 63
  • 64 emio_init(); //初始化EMIO
  • 65 status = ov7725_init(); //初始化ov7725
  • 66 if(status == 0)
  • 67 xil_printf("OV7725 detected successful!rn");
  • 68 else
  • 69 xil_printf("OV7725 detected failed!rn");
  • 70
  • 71 //获取LCD的ID
  • 72 XGpio_Initialize(&axi_gpio_inst,AXI_GPIO_0_ID);
  • 73 lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);
  • 74 xil_printf("LCD ID: %xrn",lcd_id);
  • 75
  • 76 //根据获取的LCD的ID号来进行video参数的选择
  • 77 switch(lcd_id){
  • 78 case 0x4342 : vd_mode = VMODE_480x272; break; //4.3寸屏,480*272分辨率
  • 79 case 0x4384 : vd_mode = VMODE_800x480; break; //4.3寸屏,800*480分辨率
  • 80 case 0x7084 : vd_mode = VMODE_800x480; break; //7寸屏,800*480分辨率
  • 81 case 0x7016 : vd_mode = VMODE_1024x600; break; //7寸屏,1024*600分辨率
  • 82 case 0x1018 : vd_mode = VMODE_1280x800; break; //10.1寸屏,1280*800分辨率
  • 83 default : vd_mode = VMODE_800x480; break;
  • 84 }
  • 85
  • 86 //配置VDMA
  • 87 run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,
  • 88 frame_buffer_addr,0,0,BOTH);
  • 89 //因摄像头和RGB LCD屏的分辨率不匹配,清空DDR3帧缓存空间
  • 90 //最后一个参数表示清零的字节数,由于RGB888数据格式占用3个字节,因此最后乘以3
  • 91 memset(frame_buffer_addr,0,vd_mode.height*vd_mode.width*FRAME_BUFFER_NUM*3);
  • 92 Xil_DCacheFlush();
  • 93 //初始化Display controller
  • 94 DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
  • 95 //设置VideoMode
  • 96 DisplaySetMode(&dispCtrl, &vd_mode);
  • 97 DisplayStart(&dispCtrl);
  • 98
  • 99 //根据VDMA显存大小给BMP文件头赋值
  • 100 *bmp_width = vd_mode.width;
  • 101 *bmp_height = vd_mode.height;
  • 102 *bmp_size = vd_mode.width * vd_mode.height * 3;
  • 103 *bf_size = *bmp_size + 54;
  • 104 //挂载文件系统
  • 105 f_mount(&fatfs,"",1);
  • 106
  • 107 //根据串口输入的指令控制拍照过程
  • 108 while(1){
  • 109 //用户输入串口指令,指令为单个字符
  • 110 scanf("%c",&cmd);
  • 111 //输入字符'c'时,抓拍摄像头图片
  • 112 if(cmd=='c'){
  • 113 printf("capture picturen");
  • 114 //获取当前读通道操作的帧缓存编号
  • 115 rd_index = XAxiVdma_CurrFrameStore(&vdma, XAXIVDMA_READ);
  • 116 printf("current read frame is %dn",rd_index);
  • 117 //读通道驻停在当前帧
  • 118 XAxiVdma_StartParking(&vdma, rd_index, XAXIVDMA_READ);
  • 119 //并获取当前帧的起始地址
  • 120 rd_fram_addr = frame_buffer_addr + vd_mode.height*vd_mode.width*3*rd_index;
  • 121 //将当前帧的图像拷贝到抓拍图片缓存区域
  • 122 memcpy((void *)bmp_addr,(void *)rd_fram_addr,vd_mode.height*vd_mode.width*3);
  • 123 //结束读通道驻停过程,继续在多帧之间进行切换
  • 124 XAxiVdma_StopParking(&vdma, XAXIVDMA_READ);
  • 125 //将抓拍图片缓存区域中的图像以BMP格式写入SD卡
  • 126 write_sd_bmp((u8 *)bmp_addr);
  • 127 //BMP图片编号累加
  • 128 pic_cnt++;
  • 129 }
  • 130 }
  • 131
  • 132 return 0;
  • 133 }
  • 134
复制代码

main函数主要是在OV7725摄像头LCD显示实验的基础上,增加了拍照相关的代码,如程序99至130行所示。
因为不同尺寸的屏幕对应不同大小的VDMA帧缓存空间,所以我们最终写入SD卡的BMP图片的大小也不一样。因此在代码的99至103行,我们需要根据VDMA的显存大小,来指定BMP文件头中的参数。主要包括BMP图片的分辨率、图像数据的大小,以及BMP文件的大小等参数。其中BMP文件比BMP图像数据要多54个字节,多出来的这部分就是我们给图像数据添加的BMP文件头。
在程序的107至130行,我们通过一个while(1)死循环,来不断判断用户输入的串口指令。当检测到用户输入字符’c’时,就利用摄像头进行拍照并以BMP图片的格式存入SD卡。
由于在OV7725摄像头LCD显示实验中,我们给VDMA定义了三个帧缓存空间,读通道和写通道按照同步锁相机制循环对这三帧显存进行读写操作。因此在拍照时,首先要通过XAxiVdma_CurrFrameStore()函数来判断当前VDMA的读通道正在操作的是哪一个帧缓存空间,如程序第115行所示。由于同步锁相机制会禁止读写通道同时访问同一个帧缓存,因此在检测到正确的用户串口指令时,读通道正在操作的帧缓存中存储的是一幅完整的图像。然后通过XAxiVdma_StartParking( )函数使VDMA读通道驻停在该帧缓存中,也就是说读通道将不会在多个显存中进行切换显示,而是反复读同一帧,此时写通道也无法再对该帧进行写操作。这样就可以保证我们在保存该帧图片时,图片不会被新的摄像头数据给破坏掉,否则我们拍照得到的图片就不再是完整的一帧。
接下来我们根据获取的帧缓存编号rd_index计算出该帧的起始地址,并通过memcpy( )函数将该帧缓存中的图像数据拷贝到BMP图片存储区域。拷贝完成后,用XAxiVdma_StopParking( )函数结束VDMA读通道的驻停过程,这样摄像头就可以正常显示了。当然我们也可以直接操作该帧缓存中的图像数据,但是在VDMA读通道驻停的过程中,LCD上显示的摄像头图案是静止的;由于图片写入SD卡的过程相对较慢,因此我们先将VDMA帧缓存中的数据拷贝出来,以减小对摄像头显示的影响。
最后,在程序的第126行,我们通过write_sd_bmp( )函数将拷贝到BMP图片存储区域中的数据以BMP文件的格式写到SD卡中。写入完成后,BMP图片的编号会加1,该编号将用于对存入SD卡的BMP图片进行命名。
write_sd_bmp( )函数的代码如下所示:

  • 136 //向SD卡中写BMP图片
  • 137 void write_sd_bmp(u8 *frame)
  • 138 {
  • 139 FIL fil; //文件对象
  • 140 UINT bw; //写文件函数返回已写入的字节数
  • 141 char pic_name[20]; //字符串,用于存储BMP文件名
  • 142
  • 143 //打印BMP图片信息(宽/高/图片大小),以及BMP文件大小
  • 144 xil_printf("width = %d, height = %d, size = %d, file size = %d bytes nr",
  • 145 *bmp_width,*bmp_height,*bmp_size,*bf_size);
  • 146
  • 147 //给BMP图片的文件名编号
  • 148 sprintf(pic_name,"picture %04u.bmp",pic_cnt);
  • 149 //打开BMP文件,如果不存在则创建该文件
  • 150 f_open(&fil,pic_name,FA_CREATE_ALWAYS | FA_WRITE);
  • 151
  • 152 //移动文件读写指针到文件开头
  • 153 f_lseek(&fil,0);
  • 154 //写入BMP文件头
  • 155 f_write(&fil,bmp_head,54,&bw);
  • 156 //写入抓拍的图片
  • 157 f_write(&fil,frame,*bmp_size,&bw);
  • 158 //关闭文件
  • 159 f_close(&fil);
  • 160 xil_printf("write %s done! nr",pic_name);
  • 161 }
复制代码

在程序的第148行,我们通过函数sprintf( )给拍摄的图片进行命名,命名格式为“picture + BMP图片编号”。由于每次拍照后BMP图片编号会累加,因此拍摄多张照片时,照片有不同的名称,防止新的照片覆盖之前拍摄的照片。
然后我们调用f_open( )函数在SD卡文件系统中打开BMP图片,使用f_lseek( )函数将读写指针移动到文件开头。接下来连续调用两次f_write( )函数,先将BMP文件头写入打开的BMP文件,然后紧接着写入BMP图片缓存中的摄像头图片。最后通过f_close( )函数关闭文件。
如果大家对调用FATFS库函数进行文件读写不熟悉的话,请参考“SD卡读写TXT文本实验”。
程序设计完成后,按快捷键Ctrl+S保存main.c文件,工具会自动进行编译。编译完成后控制台(Console)中会出现提示信息“Build Finished”,同时在应用工程的Binaries目录下可以看到生成的elf文件。
29.5下载验证
首先我们将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用Mini USB连接线将开发板左侧的USB_UART接口与电脑连接,用于串口通信。除此之外还需要连接OV7725摄像头以及LCD屏幕,并插入SD卡。最后连接开发板的电源,并打开电源开关。
在SDK软件下方的SDK Terminal窗口中点击右上角的加号设置并连接串口。然后下载本次实验硬件设计过程中所生成的BIT文件,来对PL进行配置。最后下载软件程序,下载完成后,LCD会显示OV7725摄像头所采集的图像,另外在SDK Terminal窗口中会打印出LCD屏幕的ID。
然后我们在发送栏输入小写字母“c”,并点击“Send”发送。发送成功后将会触发摄像头拍照,同时打印拍照信息,如下图所示:




图 29.5.1 串口终端中打印的信息

在图 29.5.1中,我们发送了两次拍照指令。第一次拍照时,VDMA读通道正在读帧缓存2,存入SD卡的BMP图片名为“picture 0000.bmp”。第二次拍照时,VDMA读通道正在读帧缓存0,存入SD卡的BMP图片名为“picture 0001.bmp”。
拍照完成后,我们将SD卡取下,并通过读卡器连接到电脑,查看SD卡中的文件,如下图所示:




图 29.5.2 SD卡中存储的BMP图片

从图 29.5.2中可以看到,SD卡中成功写入了两个BMP格式的图片。我们在图片上右击查看其属性,如下图所示:




图 29.5.3 图片属性

从图 29.5.3中可以看到,SD卡中存储的BMP图片位深度为24bit,分辨率与所连接的LCD屏的分辨率一致。最后我们分别双击打开这两幅图片,如下所示:




图 29.5.4 拍摄的BMP图片

从图 29.5.4可以看到,我们拍摄的BMP图片可以正常打开,说明本次实验在领航者ZYNQ开发板上面下载验证成功。需要注意的是,OV7725摄像头输出的图像分辨为640*480。而我们保存的BMP图片的分辨率是与LCD屏保持一致的。本次实验使用的屏幕分辨率为800*480,因此BMP图片右侧有一部分黑色区域。


更多回帖

发帖
×
20
完善资料,
赚取积分