龙空技术网

「正点原子FPGA连载」第二十九章OV7725照相机实验

正点原子原子哥 44

前言:

此时朋友们对“苹果151系统相机怎么用的”大体比较注重,咱们都需要分析一些“苹果151系统相机怎么用的”的相关资讯。那么小编在网络上搜集了一些对于“苹果151系统相机怎么用的””的相关资讯,希望姐妹们能喜欢,朋友们快快来了解一下吧!

1)摘自【正点原子】领航者 ZYNQ 之嵌入式开发指南

2)实验平台:正点原子领航者ZYNQ开发板

3)平台购买地址:

4)全套实验源码+手册+视频下载:

5)对正点原子FPGA感兴趣的同学可以加群讨论:876744900

6)关注正点原子公众号,获取最新资料

第二十九章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 <stdio.h>2 #include <stdlib.h>3 #include <string.h>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器件ID21 #define DISP_VTC_ID XPAR_VTC_0_DEVICE_ID //VTC器件ID22 #define AXI_GPIO_0_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL端 AXI GPIO 0(lcd_id)器件ID23 #define AXI_GPIO_0_CHANEL 1 //使用AXI GPIO(lcd_id)通道124 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 ID52 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(); //初始化EMIO65 status = ov7725_init(); //初始化ov772566 if(status == 0)67 xil_printf("OV7725 detected successful!\r\n");68 else69 xil_printf("OV7725 detected failed!\r\n");70 71 //获取LCD的ID72 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: %x\r\n",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 //配置VDMA87 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个字节,因此最后乘以391 memset(frame_buffer_addr,0,vd_mode.height*vd_mode.width*FRAME_BUFFER_NUM*3);92 Xil_DCacheFlush();93 //初始化Display controller94 DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);95 //设置VideoMode96 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 picture\n");114 //获取当前读通道操作的帧缓存编号115 rd_index = XAxiVdma_CurrFrameStore(&vdma, XAXIVDMA_READ);116 printf("current read frame is %d\n",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 \n\r",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! \n\r",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图片右侧有一部分黑色区域。

标签: #苹果151系统相机怎么用的