搜索
bottom↓
回复: 0

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

[复制链接]

出0入234汤圆

发表于 2020-8-15 11:39:44 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-10-24 10:54 编辑

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
QQ群头像.png
5)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png


第二十九章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硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:
阿莫论坛发帖领航者专用1593.png

图 29.3.1 系统框图

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

图 29.3.2 SD卡配置界面

同时我们还需要勾选SD卡控制器的“Card Detect”接口,并将Bank 1的电平设置为“LVCOMS 1.8V”,如下图所示:
阿莫论坛发帖领航者专用1909.png

图 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. 1   #include <stdio.h>
  2. 2   #include <stdlib.h>
  3. 3   #include <string.h>
  4. 4   #include "xil_types.h"
  5. 5   #include "xil_cache.h"
  6. 6   #include "xparameters.h"
  7. 7   #include "xgpio.h"
  8. 8   #include "xaxivdma.h"
  9. 9   #include "xaxivdma_i.h"
  10. 10  #include "display_ctrl/display_ctrl.h"
  11. 11  #include "ov7725/ov7725_init.h"
  12. 12  #include "vdma_api/vdma_api.h"
  13. 13  #include "emio_sccb_cfg/emio_sccb_cfg.h"
  14. 14  #include "ff.h"
  15. 15  #include "xil_cache.h"
  16. 16  
  17. 17  //宏定义
  18. 18  #define FRAME_BUFFER_NUM   3                          //帧缓存个数
  19. 19  #define DYNCLK_BASEADDR    XPAR_AXI_DYNCLK_0_BASEADDR //动态时钟基地址
  20. 20  #define VDMA_ID            XPAR_AXIVDMA_0_DEVICE_ID   //VDMA器件ID
  21. 21  #define DISP_VTC_ID        XPAR_VTC_0_DEVICE_ID       //VTC器件ID
  22. 22  #define AXI_GPIO_0_ID      XPAR_AXI_GPIO_0_DEVICE_ID  //PL端  AXI GPIO 0(lcd_id)器件ID
  23. 23  #define AXI_GPIO_0_CHANEL  1                          //使用AXI GPIO(lcd_id)通道1
  24. 24  
  25. 25  //全局变量
  26. 26  XAxiVdma     vdma;
  27. 27  DisplayCtrl  dispCtrl;
  28. 28  XGpio        axi_gpio_inst;   //PL端 AXI GPIO 驱动实例
  29. 29  VideoMode    vd_mode;
  30. 30  
  31. 31  //文件系统
  32. 32  static FATFS fatfs;
  33. 33  //BMP图片文件头
  34. 34  u8 bmp_head[54] = {
  35. 35       0x42,0x4d,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x36,0x0,0x0,0x0,0x28,0x0,
  36. 36       0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x18,0x0,0x0,0x0,
  37. 37       0x0,0x0,0x0,0x0,0x0,0x0,0xc4,0xe,0x0,0x0,0xc4,0x0e,0x0,0x0,0x0,0x0,
  38. 38       0x0,0x0,0x0,0x0,0x0,0x0 };
  39. 39  //BMP图片各参数偏移地址
  40. 40  UINT *bf_size    = (UINT *)(bmp_head + 0x2);
  41. 41  UINT *bmp_width  = (UINT *)(bmp_head + 0x12);
  42. 42  UINT *bmp_height = (UINT *)(bmp_head + 0x16);
  43. 43  UINT *bmp_size   = (UINT *)(bmp_head + 0x22);
  44. 44  //BMP图片编号
  45. 45  int  pic_cnt = 0;
  46. 46  
  47. 47  //抓拍的图片显存地址
  48. 48  unsigned int const bmp_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x11000000);
  49. 49  //frame buffer的起始地址
  50. 50  unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x1000000);
  51. 51  //LCD ID
  52. 52  unsigned int lcd_id=0;
  53. 53  
  54. 54  //将显存图像以BMP格式写入SD卡
  55. 55  void write_sd_bmp(u8 *frame);
  56. 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函数的代码如下所示:
  1. 57  int main(void)
  2. 58  {
  3. 59      int status = 0;
  4. 60      char cmd;                   //串口输入的拍照指令
  5. 61      int  rd_index;              //VDMA读通道操作的帧缓存编号
  6. 62      unsigned int rd_fram_addr;  //VDMA读通道操作的帧缓存地址
  7. 63  
  8. 64      emio_init();             //初始化EMIO
  9. 65      status = ov7725_init();  //初始化ov7725
  10. 66      if(status == 0)
  11. 67          xil_printf("OV7725 detected successful!\r\n");
  12. 68      else
  13. 69          xil_printf("OV7725 detected failed!\r\n");
  14. 70  
  15. 71      //获取LCD的ID
  16. 72      XGpio_Initialize(&axi_gpio_inst,AXI_GPIO_0_ID);
  17. 73      lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);
  18. 74      xil_printf("LCD ID: %x\r\n",lcd_id);
  19. 75  
  20. 76      //根据获取的LCD的ID号来进行video参数的选择
  21. 77      switch(lcd_id){
  22. 78          case 0x4342 : vd_mode = VMODE_480x272; break;  //4.3寸屏,480*272分辨率
  23. 79          case 0x4384 : vd_mode = VMODE_800x480; break;  //4.3寸屏,800*480分辨率
  24. 80          case 0x7084 : vd_mode = VMODE_800x480; break;  //7寸屏,800*480分辨率
  25. 81          case 0x7016 : vd_mode = VMODE_1024x600; break; //7寸屏,1024*600分辨率
  26. 82          case 0x1018 : vd_mode = VMODE_1280x800; break; //10.1寸屏,1280*800分辨率
  27. 83          default : vd_mode = VMODE_800x480; break;
  28. 84      }
  29. 85  
  30. 86      //配置VDMA
  31. 87      run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,
  32. 88                              frame_buffer_addr,0,0,BOTH);
  33. 89      //因摄像头和RGB LCD屏的分辨率不匹配,清空DDR3帧缓存空间
  34. 90      //最后一个参数表示清零的字节数,由于RGB888数据格式占用3个字节,因此最后乘以3
  35. 91      memset(frame_buffer_addr,0,vd_mode.height*vd_mode.width*FRAME_BUFFER_NUM*3);
  36. 92      Xil_DCacheFlush();
  37. 93      //初始化Display controller
  38. 94      DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
  39. 95      //设置VideoMode
  40. 96      DisplaySetMode(&dispCtrl, &vd_mode);
  41. 97      DisplayStart(&dispCtrl);
  42. 98  
  43. 99      //根据VDMA显存大小给BMP文件头赋值
  44. 100     *bmp_width  = vd_mode.width;
  45. 101     *bmp_height = vd_mode.height;
  46. 102     *bmp_size   = vd_mode.width * vd_mode.height * 3;
  47. 103     *bf_size    = *bmp_size + 54;
  48. 104     //挂载文件系统
  49. 105     f_mount(&fatfs,"",1);
  50. 106
  51. 107     //根据串口输入的指令控制拍照过程
  52. 108     while(1){
  53. 109         //用户输入串口指令,指令为单个字符
  54. 110         scanf("%c",&cmd);
  55. 111         //输入字符'c'时,抓拍摄像头图片
  56. 112         if(cmd=='c'){
  57. 113             printf("capture picture\n");
  58. 114             //获取当前读通道操作的帧缓存编号
  59. 115             rd_index = XAxiVdma_CurrFrameStore(&vdma, XAXIVDMA_READ);
  60. 116             printf("current read frame is %d\n",rd_index);
  61. 117             //读通道驻停在当前帧
  62. 118             XAxiVdma_StartParking(&vdma, rd_index, XAXIVDMA_READ);
  63. 119             //并获取当前帧的起始地址
  64. 120             rd_fram_addr = frame_buffer_addr + vd_mode.height*vd_mode.width*3*rd_index;
  65. 121             //将当前帧的图像拷贝到抓拍图片缓存区域
  66. 122             memcpy((void *)bmp_addr,(void *)rd_fram_addr,vd_mode.height*vd_mode.width*3);
  67. 123             //结束读通道驻停过程,继续在多帧之间进行切换
  68. 124             XAxiVdma_StopParking(&vdma, XAXIVDMA_READ);
  69. 125             //将抓拍图片缓存区域中的图像以BMP格式写入SD卡
  70. 126             write_sd_bmp((u8 *)bmp_addr);
  71. 127             //BMP图片编号累加
  72. 128             pic_cnt++;
  73. 129         }
  74. 130     }
  75. 131
  76. 132     return 0;
  77. 133 }
  78. 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( )函数的代码如下所示:
  1. 136 //向SD卡中写BMP图片
  2. 137 void write_sd_bmp(u8 *frame)
  3. 138 {
  4. 139     FIL     fil;            //文件对象
  5. 140     UINT    bw;             //写文件函数返回已写入的字节数
  6. 141     char    pic_name[20];   //字符串,用于存储BMP文件名
  7. 142
  8. 143     //打印BMP图片信息(宽/高/图片大小),以及BMP文件大小
  9. 144     xil_printf("width = %d, height = %d, size = %d, file size = %d bytes \n\r",
  10. 145             *bmp_width,*bmp_height,*bmp_size,*bf_size);
  11. 146
  12. 147     //给BMP图片的文件名编号
  13. 148     sprintf(pic_name,"picture %04u.bmp",pic_cnt);
  14. 149     //打开BMP文件,如果不存在则创建该文件
  15. 150     f_open(&fil,pic_name,FA_CREATE_ALWAYS | FA_WRITE);
  16. 151
  17. 152     //移动文件读写指针到文件开头
  18. 153     f_lseek(&fil,0);
  19. 154     //写入BMP文件头
  20. 155     f_write(&fil,bmp_head,54,&bw);
  21. 156     //写入抓拍的图片
  22. 157     f_write(&fil,frame,*bmp_size,&bw);
  23. 158     //关闭文件
  24. 159     f_close(&fil);
  25. 160     xil_printf("write %s done! \n\r",pic_name);
  26. 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”发送。发送成功后将会触发摄像头拍照,同时打印拍照信息,如下图所示:
阿莫论坛发帖领航者专用19590.png

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

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

图 29.5.2 SD卡中存储的BMP图片

从图 29.5.2中可以看到,SD卡中成功写入了两个BMP格式的图片。我们在图片上右击查看其属性,如下图所示:
阿莫论坛发帖领航者专用110002.png

图 29.5.3 图片属性

从图 29.5.3中可以看到,SD卡中存储的BMP图片位深度为24bit,分辨率与所连接的LCD屏的分辨率一致。最后我们分别双击打开这两幅图片,如下所示:
阿莫论坛发帖领航者专用110179.png

图 29.5.4 拍摄的BMP图片

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

阿莫论坛20周年了!感谢大家的支持与爱护!!

阿莫论坛才是最爱国的,关心国家的经济、社会的发展、担心国家被别国牵连卷入战争、知道珍惜来之不易的和平发展,知道师夷之长,关注世界的先进文化与技术,也探讨中国文化的博大精深,也懂得警惕民粹主义的祸国殃民等等等等,无不是爱国忧民的表现。(坛友:tianxian)
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-17 05:30

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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