OpenEdv-开源电子网

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

[国产FPGA] 《ATK-DFPGL22G 之FPGA开发指南》第五十四章 基于OV7725的以太网视频传输实验

[复制链接]

1118

主题

1129

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4671
金钱
4671
注册时间
2019-5-8
在线时间
1224 小时
发表于 2024-1-10 10:59:22 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2024-1-8 16:05 编辑

第五十四章  基于OV7725的以太网视频传输实验

1)实验平台:正点原子 ATK-DFPGL22G开发板

2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-PGL22G.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)FPGA技术交流QQ群:435699340

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

在OV7725摄像头RGB_LCD显示实验中,成功地在LCD屏上实时显示出了摄像头采集的图像。本章将使用ATK-DFPGL22G FPGA开发板实现将OV7725摄像头采集的视频通过网口传输到电脑的上位机实时显示。
本章分为以下几个章节:
1.1        简介
1.2        实验任务
1.3        硬件设计
1.4        程序设计
1.5        下载验证

1.1 简介
在“OV7725摄像头RGB_LCD显示实验”中对OV7725的视频传输时序、SCCB协议以及寄存器的配置信息等内容作了详细的介绍,如果大家对这部分内容不是很熟悉的话,请参考“OV7725摄像头RGB_LCD显示实验”中的OV7725简介部分。

1.2 实验任务
本节实验任务是使用ATK-DFPGL22G开发板及OV7725摄像头实现图像采集,并通过开发板上的以太网接口发送给上位机实时显示。

1.3 硬件设计
ATK-DFPGL22G开发板上有一个摄像头扩展接口,该接口可以用来连接OV7725/OV5640等摄像头模块。由于SCCB接口通信需要接上拉电阻,因此,将CMOS_SCL信号和CMOS_SDA信号连接上拉电阻,原理图如图 54.3.1所示:                             
image001.png
图 54.3.1 摄像头扩展接口原理图
ATK-OV7725是正点原子推出的一款高性能30W像素高清摄像头模块。该模块通过2*9排针(2.54mm间距)同外部连接,我们将摄像头的排针直接插在开发板上的摄像头接口即可,模块外观如图 54.3.2所示:

image003.png
图 54.3.2 ATK-OV7725摄像头模块实物图
我们在前面说过,OV7725在RGB565模式中只有高8位数据是有效的即D[9:2],而我们的摄像头排针上数据引脚的个数是8位。实际上,摄像头排针上的8位数据连接的就是OV7725传感器的D[9:2],所以我们直接使用摄像头排针上的8位数据引脚即可。
需要注意的是,由图 54.3.1可知,摄像头扩展口的第18个引脚定义为CMOS_PWDN,而我们的OV7725摄像头模块的PWDN引脚固定为低电平,也就是一直处于正常工作模式。OV7725摄像头模块的第18个引脚定义为SGM_CTRL,这个引脚是摄像头驱动时钟的选择引脚。OV7725摄像头模块内部自带晶振的,当SGM_CTRL引脚为低电平时,选择使用摄像头的外部时钟,也就是FPGA需要输出时钟给摄像头;当SGM_CTRL引脚为高电平时,选择使用摄像头的晶振提供时钟。本次实验将SGM_CTRL引脚驱动为高电平,这样就不用为摄像头提供驱动时钟,即不用在CMOS_XCLK引脚上输出时钟。
由于以太网与摄像头引脚数目较多,且在前面相应的章节中已经给出它们的管脚列表,这里就不重复列出了。

1.4 程序设计
OV7725在VGA(分辨率为640*480)帧模式下,以RGB565格式输出最高帧率可达60Hz,每秒钟输出的数据量达到60*640*480*16bit = 294912000bit = 281.25Mbit。我们FPGA开发板上的PHY芯片类型为千兆以太网,理论上最大传输速率为1000Mbit/s,加上帧头、CRC校验以及帧间隙带来的额外开销,实际上能达到的最大传输速率比理论上最大传输速率低。尽管实际传输速率低于1000Mbit/s,但实时传输OV7725摄像头的图像完全没有压力,可以满足带宽要求,因此本次实验不需要通过片外存储器缓存图像,仅将图像数据先经过FIFO进行缓存,然后通过以太网接口进行发送即可。
根据实验任务,我们可以大致规划出系统的控制流程:时钟模块用于为IIC驱动模块、以太网顶层模块和开始传输控制模块提供驱动时钟。I2C驱动模块和I2C配置模块用于初始化OV7725图像传感器;摄像头采集模块负责采集摄像头图像数据,并且把图像数据连接至图像数据封装模块,图像数据封装模块将输入的图像数据进行位拼接,并添加图像的帧头和行场分辨率;以太网顶层模块实现以太网数据的收发;开始传输控制模块控制以太网顶层模块开始/停止发送数据。
OV7725的以太网视频传输系统框图如下图所示:

image005.png
图 54.4.1 系统框图
FPGA顶层模块(ov7725_udp_pc)例化了以下七个模块:时钟模块(clk_wiz_0)、I2C驱动模块(i2c_dri)、I2C配置模块(i2c_ov7725_rgb565_cfg)、摄像头图像采集模块(cmos_capture_data)、开始传输控制模块(start_transfer_ctrl)、图像数据封装模块(img_data_pkt)和以太网顶层模块模块(eth_top)。
时钟模块(clk_wiz_0):时钟IP核模块通过调用PLL IP核来实现,总共输出2个时钟,频率分别为50Mhz和200Mhz时钟。50Mhz时钟作为I2C驱动模块的驱动时钟;200Mhz时钟作为IDELAYCTRL源语的参考时钟。
I2C驱动模块(i2c_dri):I2C驱动模块负责驱动OV7725 SCCB接口总线,用户可根据该模块提供的用户接口可以很方便的对OV7725的寄存器进行配置,该模块和“EEPROM读写实验”章节中用到的I2C驱动模块为同一个模块,有关该模块的详细介绍请大家参考“EEPROM读写实验”章节。
I2C配置模块(i2c_ov7725_rgb565_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出OV7725的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对OV7725传感器的初始化。
摄像头图像采集模块(cmos_capture_data):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成16位数据信号,完成对OV7725传感器图像的采集。
开始传输控制模块(start_transfer_ctrl):该模块解析以太网顶层模块接收到的数据,如果收到1个字节的ASCII码“1”,则表示以太网开始传输图像数据;如果收到1个字节的ASCII码“0”,则表示以太网停止传输图像数据。
图像数据封装模块(img_data_pkt):图像数据封装模块负责将输入16位的图像数据,拼接成32位数据,以及添加图像数据的帧头和行场分辨率。该模块控制着以太网发送模块发送的字节数,单次发送一行图像数据的字节数,模块内部例化了一个异步FIFO模块,用于缓存待发送的图像数据。
以太网顶层模块(eth_top):以太网顶层模块实现以太网通信的收发功能,有关该模块的详细介绍请大家参考“以太网UDP测试实验”章节。
顶层模块部分代码如下:
  1. 1  module ov7725_udp_pc(
  2. 2       input             sys_clk     , //系统时钟
  3. 3       input             sys_rst_n   , //系统复位信号,低电平有效
  4. 4       //以太网接口
  5. 5       input             eth_rxc     , //RGMII接收数据时钟
  6. 6       input             eth_rx_ctl  , //RGMII输入数据有效信号
  7. 7       input       [3:0  eth_rxd     , //RGMII输入数据
  8. 8       output            eth_txc     , //RGMII发送数据时钟   
  9. 9       output            eth_tx_ctl  , //RGMII输出数据有效信号
  10. 10      output      [3:0  eth_txd     , //RGMII输出数据         
  11. 11      output            eth_rst_n   , //以太网芯片复位信号,低电平有效   
  12. 12
  13. 13      //摄像头接口
  14. 14      input             cam_pclk    , //cmos 数据像素时钟
  15. 15      input             cam_vsync   , //cmos 场同步信号
  16. 16      input             cam_href    , //cmos 行同步信号
  17. 17      input     [7:0   cam_data    , //cmos 数据
  18. 18      output            cam_rst_n   , //cmos 复位信号,低电平有效
  19. 19      output            cam_sgm_ctrl, //cmos 时钟选择信号, 1:使用摄像头自带的晶振
  20. 20      output            cam_scl     , //cmosSCCB_SCL线
  21. 21      inout             cam_sda       //cmos SCCB_SDA线   
  22. 22 );
  23. 23
  24. 24 //parameter define
  25. 25 //开发板MAC地址00-11-22-33-44-55
  26. 26 parameter  BOARD_MAC = 48'h00_11_22_33_44_55;     
  27. 27 //开发板IP地址192.168.1.10
  28. 28 parameter  BOARD_IP = {8'd192,8'd168,8'd1,8'd10};  
  29. 29 //目的MAC地址ff_ff_ff_ff_ff_ff
  30. 30 parameter  DES_MAC  = 48'hff_ff_ff_ff_ff_ff;   
  31. 31 //目的IP地址192.168.1.102     
  32. 32 parameter  DES_IP   = {8'd192,8'd168,8'd1,8'd102};
  33. 33
  34. 34 parameter  SLAVE_ADDR = 7'h21         ;  //OV7725的器件地址7'h21
  35. 35 parameter  BIT_CTRL  = 1'b0          ;  //OV7725的字节地址为8位 0:8位 1:16位
  36. 36 parameter  CLK_FREQ  = 26'd50_000_000;  //i2c_dri模块的驱动时钟频率
  37. 37 parameter  I2C_FREQ  = 18'd250_000  ;  //I2C的SCL时钟频率,不超过400KHz
复制代码
在代码的第24至32行定义了四个参量:开发板MAC地址BOARD_MAC,开发板IP地址 BOARD_IP,目的MAC地址DES_MAC(这里指PC MAC地址),目的IP地址 DES_IP(PC IP地址)。开发板的MAC地址和IP地址是我们随意定义的,只要不和目的MAC 地址和目的IP地址一样就可以,否则会产生地址冲突。目的MAC地址这里写的是公共MAC 地址(48'hff_ff_ff_ff_ff_ff),也可以修改成电脑网口的MAC地址,DES_IP是对应电脑以太网的IP地址,这里定义的四个参数是向下传递的,需要修改MAC地址或者IP地址时直接在这里修改即可,而不用在以太网顶层模块里面修改。
在代码的第34行定义了OV7725的器件地址,其器件地址为7’h21;第35行定义了寄存器地址的位宽,BIT_CTRL=0表示地址位宽为8位,BIT_CTRL=1表示地址位宽为16位。因为OV7725的地址位宽为8位,所以BIT_CTRL设置为0。第36行和第37行分别定义了i2c_dri模块的驱动时钟频率和I2C的SCL时钟频率。
  1. 70 assign  rst_n = sys_rst_n & locked;
  2. 71 //不对摄像头硬件复位,固定高电平
  3. 72 assign  cam_rst_n = 1'b1;
  4. 73 //cmos 时钟选择信号, 1:使用摄像头自带的晶振
  5. 74 assign  cam_sgm_ctrl = 1'b1;
  6. 75
  7. 76 //例化PLL
  8. 77 clk_wiz_0 u_clk_wiz_0
  9. 78     (
  10. 79      .clk_out1    (clk_50m),  
  11. 80      .clk_out2    (clk_200m),
  12. 81      .reset       (~sys_rst_n),  
  13. 82      .locked      (locked),      
  14. 83      .clk_in1     (sys_clk)
  15. 84      );      
  16. 85
  17. 86 //I2C配置模块   
  18. 87 i2c_ov7725_rgb565_cfg u_i2c_cfg(
  19. 88      .clk           (i2c_dri_clk),
  20. 89      .rst_n         (rst_n),
  21. 90      .i2c_done      (i2c_done),
  22. 91      .i2c_exec      (i2c_exec),
  23. 92      .i2c_data      (i2c_data),
  24. 93      .init_done     (cam_init_done)
  25. 94      );   
  26. 95
  27. 96 //I2C驱动模块
  28. 97 i2c_dri
  29. 98     #(
  30. 99      .SLAVE_ADDR  (SLAVE_ADDR),               //参数传递
  31. 100     .CLK_FREQ    (CLK_FREQ  ),              
  32. 101     .I2C_FREQ    (I2C_FREQ  )               
  33. 102     )
  34. 103   u_i2c_dri(
  35. 104     .clk         (clk_50m   ),   
  36. 105     .rst_n       (rst_n     ),   
  37. 106     //i2cinterface
  38. 107    .i2c_exec    (i2c_exec  ),   
  39. 108     .bit_ctrl    (BIT_CTRL  ),   
  40. 109     .i2c_rh_wl   (1'b0),                    //固定为0,只用到了IIC驱动的写操作   
  41. 110     .i2c_addr    (i2c_data[15:8]),   
  42. 111     .i2c_data_w  (i2c_data[7:0]),   
  43. 112     .i2c_data_r  (),   
  44. 113     .i2c_done    (i2c_done  ),   
  45. 114     .scl         (cam_scl   ),   
  46. 115     .sda         (cam_sda   ),   
  47. 116     //userinterface
  48. 117     .dri_clk     (i2c_dri_clk)               //I2C操作时钟
  49. 118 );
  50. 119
  51. 120 //摄像头数据采集模块
  52. 121 cmos_capture_datau_cmos_capture_data(
  53. 122
  54. 123     .rst_n              (rst_n & cam_init_done),
  55. 124     .cam_pclk           (cam_pclk),   
  56. 125     .cam_vsync          (cam_vsync),
  57. 126     .cam_href           (cam_href),
  58. 127     .cam_data           (cam_data),           
  59. 128     .cmos_frame_vsync   (cmos_frame_vsync),
  60. 129     .cmos_frame_href    (),
  61. 130     .cmos_frame_valid   (img_data_en),     
  62. 131     .cmos_frame_data    (img_data)            
  63. 132     );
复制代码
OV7725摄像头配置模块和IIC驱动模块实现对OV7725摄像头的初始化,在初始化完成后拉高cam_init_done信号,此时开始通过摄像头数据采集模块接收摄像头输出的图像数据(如程序中第123行代码所示),将输入的8位数据转换成16位RGB565数据。
  1. 134 //开始传输控制模块  
  2. 135 start_transfer_ctrl u_start_transfer_ctrl(
  3. 136     .clk                (eth_rx_clk),
  4. 137     .rst_n              (rst_n),
  5. 138     .udp_rec_pkt_done   (udp_rec_pkt_done),
  6. 139     .udp_rec_en         (udp_rec_en      ),
  7. 140     .udp_rec_data       (udp_rec_data    ),
  8. 141     .udp_rec_byte_num   (udp_rec_byte_num),
  9. 142
  10. 143     .transfer_flag      (transfer_flag)      //图像开始传输标志,1:开始传输 0:停止传输
  11. 144     );      
  12. 145      
  13. 146 //图像封装模块     
  14. 147 img_data_pkt u_img_data_pkt(   
  15. 148     .rst_n              (rst_n),              
  16. 149   
  17. 150     .cam_pclk           (cam_pclk),
  18. 151     .img_vsync          (cmos_frame_vsync),
  19. 152     .img_data_en        (img_data_en),
  20. 153     .img_data           (img_data),
  21. 154     .transfer_flag      (transfer_flag),            
  22. 155     .eth_tx_clk         (eth_tx_clk     ),
  23. 156     .udp_tx_req         (udp_tx_req     ),
  24. 157     .udp_tx_done        (udp_tx_done    ),
  25. 158     .udp_tx_start_en    (udp_tx_start_en),
  26. 159     .udp_tx_data        (udp_tx_data    ),
  27. 160     .udp_tx_byte_num    (udp_tx_byte_num)
  28. 161     );  
  29. 162
  30. 163 //以太网顶层模块   
  31. 164 eth_top  #(
  32. 165     .BOARD_MAC     (BOARD_MAC),              //参数例化
  33. 166     .BOARD_IP      (BOARD_IP ),         
  34. 167     .DES_MAC       (DES_MAC  ),         
  35. 168     .DES_IP        (DES_IP   )         
  36. 169     )         
  37. 170     u_eth_top(         
  38. 171     .sys_rst_n       (rst_n     ),           //系统复位信号,低电平有效           
  39. 172     .clk_200m        (clk_200m),
  40. 173     //以太网RGMII接口            
  41. 174     .eth_rxc         (eth_rxc   ),          //RGMII接收数据时钟
  42. 175     .eth_rx_ctl      (eth_rx_ctl),          //RGMII输入数据有效信号
  43. 176     .eth_rxd         (eth_rxd   ),          //RGMII输入数据
  44. 177     .eth_txc         (eth_txc   ),           //RGMII发送数据时钟   
  45. 178     .eth_tx_ctl      (eth_tx_ctl),           //RGMII输出数据有效信号
  46. 179     .eth_txd         (eth_txd   ),          //RGMII输出数据        
  47. 180     .eth_rst_n       (eth_rst_n ),           //以太网芯片复位信号,低电平有效
  48. 181
  49. 182     .gmii_rx_clk     (eth_rx_clk),
  50. 183     .gmii_tx_clk     (eth_tx_clk),      
  51. 184     .udp_tx_start_en(udp_tx_start_en),
  52. 185     .tx_data         (udp_tx_data),
  53. 186     .tx_byte_num     (udp_tx_byte_num),
  54. 187     .udp_tx_done     (udp_tx_done),
  55. 188     .tx_req          (udp_tx_req ),
  56. 189     .rec_pkt_done    (udp_rec_pkt_done),
  57. 190     .rec_en          (udp_rec_en      ),
  58. 191     .rec_data        (udp_rec_data    ),
  59. 192     .rec_byte_num    (udp_rec_byte_num)
  60. 193     );
  61. 194
  62. 195 endmodule
复制代码
在代码的第135行至144行例化了开始传输控制模块,由该模块端口可知,输入端口信号为以太网接收到的数据,而输出信号为图像开始传输标志(transfer_flag)。transfer_flag信号用于控制以太网发送图像数据的开始和停止,连接至图像数据封装模块。
在代码的第147行至161行例化了图像数据封装模块,该模块输入的端口信号为摄像头图像数据,而输出的udp_tx_start_en(以太网开始发送信号)和udp_tx_byte_num(发送的字节数)连接至以太网顶层模块的以太网发送控制端口,从而控制以太网的UDP发送模块开始发送图像数据。
I2C配置模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,代码如下所示:
  1. 1  module i2c_ov7725_rgb565_cfg(  
  2. 2       input               clk      ,  //时钟信号
  3. 3       input               rst_n    ,  //复位信号,低电平有效
  4. 4      
  5. 5       input               i2c_done ,  //I2C寄存器配置完成信号
  6. 6       output  reg         i2c_exec ,  //I2C触发执行信号  
  7. 7       output  reg  [15:0  i2c_data ,  //I2C要配置的地址与数据(高8位地址,低8位数据)
  8. 8       output  reg         init_done   //初始化完成信号
  9. 9       );
  10. 10
  11. 11 //parameter define
  12. 12 parameter  REG_NUM = 7'd70   ;       //总共需要配置的寄存器个数
  13. 13
  14. 14 //reg define
  15. 15 reg    [9:0  start_init_cnt;       //等待延时计数器
  16. 16 reg    [6:0  init_reg_cnt  ;       //寄存器配置个数计数器
  17. 17
  18. 18 //*****************************************************
  19. 19 //**                    main code
  20. 20 //*****************************************************
  21. 21
  22. 22 //cam_scl配置成250khz,输入的clk为1Mhz,周期为1us,1023*1us = 1.023ms
  23. 23 //寄存器延时配置
  24. 24 always @(posedge clk or negedge rst_n) begin
  25. 25      if(!rst_n)
  26. 26          start_init_cnt <= 10'b0;   
  27. 27      else if((init_reg_cnt == 7'd1) && i2c_done)
  28. 28          start_init_cnt <= 10'b0;
  29. 29      else if(start_init_cnt < 10'd1023) begin
  30. 30          start_init_cnt <= start_init_cnt + 1'b1;                  
  31. 31      end
  32. 32 end
  33. 33
  34. 34 //寄存器配置个数计数   
  35. 35 always @(posedge clk or negedge rst_n) begin
  36. 36      if(!rst_n)
  37. 37          init_reg_cnt <= 7'd0;
  38. 38      else if(i2c_exec)   
  39. 39          init_reg_cnt <= init_reg_cnt + 7'b1;
  40. 40 end         
  41. 41
  42. 42 //i2c触发执行信号  
  43. 43 always @(posedge clk or negedge rst_n) begin
  44. 44      if(!rst_n)
  45. 45          i2c_exec <= 1'b0;
  46. 46      else if(start_init_cnt == 10'd1022)
  47. 47          i2c_exec <= 1'b1;
  48. 48      //只有刚上电和配置第一个寄存器增加延时
  49. 49      else if(i2c_done && (init_reg_cnt != 7'd1) && (init_reg_cnt < REG_NUM))
  50. 50          i2c_exec <= 1'b1;
  51. 51      else
  52. 52          i2c_exec <= 1'b0;   
  53. 53 end
  54. 54
  55. 55 //初始化完成信号
  56. 56 always @(posedge clk or negedge rst_n) begin
  57. 57      if(!rst_n)
  58. 58          init_done <= 1'b0;
  59. 59      else if((init_reg_cnt == REG_NUM) && i2c_done)  
  60. 60          init_done <= 1'b1;  
  61. 61 end        
  62. 62     
  63. 63 //配置寄存器地址与数据
  64. 64 always @(posedge clk or negedge rst_n) begin
  65. 65      if(!rst_n)
  66. 66          i2c_data <= 16'b0;
  67. 67      else begin
  68. 68          case(init_reg_cnt)
  69. 69              //先对寄存器进行软件复位,使寄存器恢复初始值
  70. 70              //寄存器软件复位后,需要延时1ms才能配置其它寄存器
  71. 71              7'd0  : i2c_data <= {8'h12, 8'h80}; //COM7BIT[7]:复位所有的寄存器
  72. 72              7'd1  : i2c_data <= {8'h3d, 8'h03}; //COM12 模拟过程直流补偿
  73. 73              7'd2  : i2c_data <= {8'h15, 8'h00}; //COM10href/vsync/pclk/data信号控制
  74. 74              7'd3  : i2c_data <= {8'h17, 8'h23}; //HSTART水平起始位置
  75. 75              7'd4  : i2c_data <= {8'h18, 8'ha0}; //HSIZE 水平尺寸
  76. 76              7'd5  : i2c_data <= {8'h19, 8'h07}; //VSTRT 垂直起始位置
  77. 77              7'd6  : i2c_data <= {8'h1a, 8'hf0}; //VSIZE 垂直尺寸            
  78. 78              7'd7  : i2c_data <= {8'h32, 8'h00}; //HREF 图像开始和尺寸控制,控制低位
  79. 79              7'd8  : i2c_data <= {8'h29, 8'ha0}; //HOutSize水平输出尺寸
  80. 80              7'd9  : i2c_data <= {8'h2a, 8'h00}; //EXHCH 虚拟像素MSB
  81. 81              7'd10 : i2c_data <= {8'h2b, 8'h00}; //EXHCL 虚拟像素LSB
  82. 82              7'd11 : i2c_data <= {8'h2c, 8'hf0}; //VOutSize垂直输出尺寸
  83. 83              7'd12 : i2c_data <= {8'h0d, 8'h41}; //COM4PLL倍频设置(multiplier)
  84. 84                                                 //Bit[7:6]:  0:1x 1:4x 2:6x 3:8x
  85. 85              7'd13 : i2c_data <= {8'h11, 8'h00}; //CLKRC 内部时钟配置
  86. 86                                        //Freq= input_clk* multiplier/[(CLKRC[5:0]+1)*2]
  87. 87              7'd14 : i2c_data <= {8'h12, 8'h06}; //COM7 输出VGA RGB565格式                                    
  88. 88              7'd15 : i2c_data <= {8'h0c, 8'h10}; //COM3Bit[0]: 0:图像数据 1:彩条测试
  89. 89              //DSP 控制
  90. 90              7'd16 : i2c_data <= {8'h42, 8'h7f}; //TGT_B 黑电平校准蓝色通道目标值
  91. 91              7'd17 : i2c_data <= {8'h4d, 8'h09}; //FixGain模拟增益放大器
  92. 92              7'd18 : i2c_data <= {8'h63, 8'hf0}; //AWB_Ctrl0自动白平衡控制字节0
  93. 93              7'd19 : i2c_data <= {8'h64, 8'hff}; //DSP_Ctrl1DSP控制字节1
  94. 94              7'd20 : i2c_data <= {8'h65, 8'h00}; //DSP_Ctrl2DSP控制字节2
  95. 95              7'd21 : i2c_data <= {8'h66, 8'h00}; //DSP_Ctrl3DSP控制字节3
  96. 96              7'd22 : i2c_data <= {8'h67, 8'h00}; //DSP_Ctrl4DSP控制字节4   
  97. 97              //AGC AEC AWB        
  98. 98              //COM8 Bit[2]:自动增益使能 Bit[1]:自动白平衡使能 Bit[0]:自动曝光功能
  99. 99              7'd23 : i2c_data <= {8'h13, 8'hff}; //COM8
  100. 100             7'd24 : i2c_data <= {8'h0f, 8'hc5}; //COM6
  101. 101             7'd25 : i2c_data <= {8'h14, 8'h11};  
  102. 102             7'd26 : i2c_data <= {8'h22, 8'h98};
  103. 103             7'd27 : i2c_data <= {8'h23, 8'h03};  
  104. 104             7'd28 : i2c_data <= {8'h24, 8'h40};
  105. 105             7'd29 : i2c_data <= {8'h25, 8'h30};  
  106. 106             7'd30: i2c_data <= {8'h26, 8'ha1};      
  107. 107             7'd31: i2c_data <= {8'h6b, 8'haa};
  108. 108             7'd32: i2c_data <= {8'h13, 8'hff};  
  109. 109             //matrix sharpness brightness contrast UV
  110. 110            7'd33 : i2c_data <= {8'h90, 8'h0a}; //EDGE1 边缘增强控制1
  111. 111             //DNSOff 降噪阈值下限,仅在自动模式下有效
  112. 112             7'd34 : i2c_data <= {8'h91, 8'h01}; //DNSOff
  113. 113             7'd35 : i2c_data <= {8'h92, 8'h01}; //EDGE2 锐度(边缘增强)强度上限
  114. 114             7'd36 : i2c_data <= {8'h93, 8'h01}; //EDGE3 锐度(边缘增强)强度下限
  115. 115             7'd37 : i2c_data <= {8'h94, 8'h5f}; //MTX1 矩阵系数1
  116. 116             7'd38 : i2c_data <= {8'h95, 8'h53}; //MTX1 矩阵系数2
  117. 117             7'd39 : i2c_data <= {8'h96, 8'h11}; //MTX1 矩阵系数3
  118. 118             7'd40 : i2c_data <= {8'h97, 8'h1a}; //MTX1 矩阵系数4
  119. 119             7'd41 : i2c_data <= {8'h98, 8'h3d}; //MTX1 矩阵系数5
  120. 120             7'd42 : i2c_data <= {8'h99, 8'h5a}; //MTX1 矩阵系数6
  121. 121             7'd43 : i2c_data <= {8'h9a, 8'h1e}; //MTX_Ctrl矩阵控制
  122. 122             7'd44 : i2c_data <= {8'h9b, 8'h3f}; //BRIGHT亮度
  123. 123             7'd45 : i2c_data <= {8'h9c, 8'h25}; //CNST 对比度            
  124. 124             7'd46 : i2c_data <= {8'h9e, 8'h81};
  125. 125             7'd47 : i2c_data <= {8'ha6, 8'h06}; //SDE 特殊数字效果控制
  126. 126             7'd48 : i2c_data <= {8'ha7, 8'h65}; //USAT"U"饱和增益
  127. 127             7'd49 : i2c_data <= {8'ha8, 8'h65}; //VSAT"V"饱和增益            
  128. 128             7'd50 : i2c_data <= {8'ha9, 8'h80}; //VSAT"V"饱和增益   
  129. 129             7'd51 : i2c_data <= {8'haa, 8'h80}; //VSAT"V"饱和增益
  130. 130             //伽马控制
  131. 131             7'd52 : i2c_data <= {8'h7e, 8'h0c};
  132. 132             7'd53 : i2c_data <= {8'h7f, 8'h16};
  133. 133             7'd54 : i2c_data <= {8'h80, 8'h2a};
  134. 134             7'd55 : i2c_data <= {8'h81, 8'h4e};
  135. 135             7'd56 : i2c_data <= {8'h82, 8'h61};
  136. 136             7'd57 : i2c_data <= {8'h83, 8'h6f};
  137. 137             7'd58 : i2c_data <= {8'h84, 8'h7b};
  138. 138             7'd59 : i2c_data <= {8'h85, 8'h86};   
  139. 139             7'd60 : i2c_data <= {8'h86, 8'h8e};
  140. 140             7'd61 : i2c_data <= {8'h87, 8'h97};
  141. 141             7'd62 : i2c_data <= {8'h88, 8'ha4};
  142. 142             7'd63 : i2c_data <= {8'h89, 8'haf};
  143. 143             7'd64 : i2c_data <= {8'h8a, 8'hc5};
  144. 144             7'd65 : i2c_data <= {8'h8b, 8'hd7};
  145. 145             7'd66 : i2c_data <= {8'h8c, 8'he8};
  146. 146             7'd67 : i2c_data <= {8'h8d, 8'h20};
  147. 147            
  148. 148             7'd68 : i2c_data <= {8'h0e, 8'h65}; //COM5
  149. 149             7'd69 : i2c_data <= {8'h09, 8'h00}; //COM2  Bit[1:0] 输出电流驱动能力
  150. 150             //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
  151. 151             default:i2c_data <= {8'h1C, 8'h7F}; //MIDH 制造商ID 高8位
  152. 152         endcase
  153. 153     end
  154. 154 end
  155. 155
  156. 156 endmodule
复制代码
在代码的第12行定义了总共需要配置的寄存器的个数,如果增加或者删减了寄存器的配置,需要修改此参数。
图像传感器刚开始上电时电压有可能不够稳定,所以程序中的23行至32行定义了一个延时计数器(start_init_cnt)等待传感器工作在稳定的状态。当计数器计数到预设值之后,开始第一次配置传感器即软件复位,目的是让所有的寄存器复位到默认的状态。从前面介绍的OV7725的特性可知,软件复位需要等待1ms的时间才能配置其它的寄存器,因此发送完软件复位命令后,延时计数器清零,并重新开始计数。当计数器计数到预设值之后,紧接着配置剩下的寄存器。只有软件复位命令需要1ms的等待时间,其它寄存器不需要等待时间,直接按照程序中定义的顺序发送即可。
在程序的83至86行,说明了关于摄像头输出时钟的寄存器配置,摄像头的地址0x0d配置成0x41,表示PLL倍频设置设为了4倍频,摄像头的地址0x11配置成0x00,而摄像头的输入时钟为12M,所以根据第86行的公式可得到摄像头的输出时钟为24M。
CMOS图像数据采集模块的代码如下所示:
  1. 1  module cmos_capture_data(
  2. 2       input                rst_n            ,  //复位信号   
  3. 3       //摄像头接口                           
  4. 4       input                cam_pclk         ,  //cmos 数据像素时钟
  5. 5       input                cam_vsync        ,  //cmos 场同步信号
  6. 6       input                cam_href         ,  //cmos 行同步信号
  7. 7       input  [7:0         cam_data         ,                     
  8. 8       //用户接口                              
  9. 9       output               cmos_frame_vsync ,  //帧有效信号   
  10. 10      output               cmos_frame_href  ,  //行有效信号
  11. 11      output               cmos_frame_valid ,  //数据有效使能信号
  12. 12      output       [15:0  cmos_frame_data     //有效数据        
  13. 13      );
  14. 14
  15. 15 //寄存器全部配置完成后,先等待10帧数据
  16. 16 //待寄存器配置生效后再开始采集图像
  17. 17 parameter  WAIT_FRAME = 4'd10    ;            //寄存器数据稳定等待的帧个数            
  18. 18                                   
  19. 19 //reg define                     
  20. 20 reg             cam_vsync_d0     ;
  21. 21 reg             cam_vsync_d1     ;
  22. 22 reg             cam_href_d0      ;
  23. 23 reg             cam_href_d1      ;
  24. 24 reg    [3:0   cmos_ps_cnt      ;            //等待帧数稳定计数器
  25. 25 reg    [7:0   cam_data_d0      ;            
  26. 26 reg    [15:0  cmos_data_t      ;            //用于8位转16位的临时寄存器
  27. 27 reg             byte_flag        ;            //16位RGB数据转换完成的标志信号
  28. 28 reg             byte_flag_d0     ;
  29. 29 reg             frame_val_flag   ;            //帧有效的标志
  30. 30
  31. 31 wire            pos_vsync        ;            //采输入场同步信号的上升沿
  32. 32
  33. 33 //*****************************************************
  34. 34 //**                    main code
  35. 35 //*****************************************************
  36. 36
  37. 37 //采输入场同步信号的上升沿
  38. 38 assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0;
  39. 39
  40. 40 //输出帧有效信号
  41. 41 assign  cmos_frame_vsync = frame_val_flag  ? cam_vsync_d1  :  1'b0;
  42. 42
  43. 43 //输出行有效信号
  44. 44 assign  cmos_frame_href = frame_val_flag  ? cam_href_d1   :  1'b0;
  45. 45
  46. 46 //输出数据使能有效信号
  47. 47 assign  cmos_frame_valid = frame_val_flag  ? byte_flag_d0  :  1'b0;
  48. 48
  49. 49 //输出数据
  50. 50 assign  cmos_frame_data = frame_val_flag  ? cmos_data_t   :  1'b0;
  51. 51         
  52. 52 always @(posedge cam_pclk or negedge rst_n) begin
  53. 53      if(!rst_n) begin
  54. 54          cam_vsync_d0 <= 1'b0;
  55. 55          cam_vsync_d1 <= 1'b0;
  56. 56          cam_href_d0 <= 1'b0;
  57. 57          cam_href_d1 <= 1'b0;
  58. 58      end
  59. 59      else begin
  60. 60          cam_vsync_d0 <= cam_vsync;
  61. 61          cam_vsync_d1 <= cam_vsync_d0;
  62. 62          cam_href_d0 <= cam_href;
  63. 63          cam_href_d1 <= cam_href_d0;
  64. 64      end
  65. 65 end
  66. 66
  67. 67 //对帧数进行计数
  68. 68 always @(posedge cam_pclk or negedge rst_n) begin
  69. 69      if(!rst_n)
  70. 70          cmos_ps_cnt <= 4'd0;
  71. 71      else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))
  72. 72          cmos_ps_cnt <= cmos_ps_cnt + 4'd1;
  73. 73 end
  74. 74
  75. 75 //帧有效标志
  76. 76 always @(posedge cam_pclk or negedge rst_n) begin
  77. 77      if(!rst_n)
  78. 78          frame_val_flag <= 1'b0;
  79. 79      else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)
  80. 80          frame_val_flag <= 1'b1;
  81. 81      else;   
  82. 82 end            
  83. 83
  84. 84 //8位数据转16位RGB565数据        
  85. 85 always @(posedge cam_pclk or negedge rst_n) begin
  86. 86      if(!rst_n) begin
  87. 87          cmos_data_t <= 16'd0;
  88. 88          cam_data_d0 <= 8'd0;
  89. 89          byte_flag <= 1'b0;
  90. 90      end
  91. 91      else if(cam_href) begin
  92. 92          byte_flag <= ~byte_flag;
  93. 93          cam_data_d0 <= cam_data;
  94. 94          if(byte_flag)
  95. 95              cmos_data_t <= {cam_data_d0,cam_data};
  96. 96          else;   
  97. 97      end
  98. 98      else begin
  99. 99          byte_flag <= 1'b0;
  100. 100         cam_data_d0 <= 8'b0;
  101. 101     end   
  102. 102 end        
  103. 103
  104. 104 //产生输出数据有效信号(cmos_frame_valid)
  105. 105 always @(posedge cam_pclk or negedge rst_n) begin
  106. 106     if(!rst_n)
  107. 107         byte_flag_d0 <= 1'b0;
  108. 108     else
  109. 109         byte_flag_d0 <= byte_flag;  
  110. 110 end
  111. 111        
  112. 112 endmodule
复制代码
CMOS图像采集模块第17行定义了参数WAIT_FRAME(寄存器数据稳定等待的帧个数),这个参数是设置摄像头初始化完成后,等待摄像头输出稳定数据的帧数,此处等待10帧,即通过采集场同步信号的上升沿来统计帧数,计数器计数超过10次之后产生数据有效的标志,开始采集图像。在程序的第84行开始的always块实现了8位数据转16位数据的功能。需要注意的是摄像头的图像数据是在像素时钟(cam_pclk)下输出的,因此摄像头的图像数据必须使用像素钟来采集,否则会造成数据采集错误。
开始传输控制模块的代码如下:
  1. 1 module start_transfer_ctrl(
  2. 2      input                clk                ,   //时钟信号
  3. 3      input                rst_n              ,   //复位信号,低电平有效
  4. 4      input                udp_rec_pkt_done   ,   //GMII接收时钟
  5. 5      input                udp_rec_en         ,   //UDP单包数据接收完成信号
  6. 6      input        [31:0  udp_rec_data       ,   //UDP接收的数据使能信号
  7. 7      input        [15:0  udp_rec_byte_num   ,   //UDP接收的数据
  8. 8                                                  //UDP接收到的字节数
  9. 9      output  reg          transfer_flag          //图像开始传输标志,1:开始传输 0:停止传输
  10. 10     );   
  11. 11     
  12. 12 //parameter define
  13. 13 parameter  START = "1";  //开始命令
  14. 14 parameter  STOP  = "0";  //停止命令
  15. 15
  16. 16 //*****************************************************
  17. 17 //**                   main code
  18. 18 //*****************************************************
  19. 19
  20. 20 //解析接收到的数据
  21. 21 always @(posedge clk or negedge rst_n) begin
  22. 22     if(!rst_n)
  23. 23         transfer_flag <= 1'b0;
  24. 24     else if(udp_rec_pkt_done && udp_rec_byte_num == 1'b1) begin
  25. 25         if(udp_rec_data[31:24 == START)         //开始传输
  26. 26             transfer_flag <= 1'b1;
  27. 27         else if(udp_rec_data[31:24 == STOP)     //停止传输
  28. 28             transfer_flag <= 1'b0;
  29. 29     end
  30. 30 end
  31. 31
  32. 32 endmodule
复制代码
开始传输控制模块的代码比较简单,当接收到UDP的数据包,并且这个数据包的有效字节个数为1时,开始对接收到的数据进行判断。如果这个数据等于ASCII码的“1”时,此时将transfer_flag赋值为1,表示以太网开始发送图像数据;如果这个数据等于ASCII码的“0”时,此时将transfer_flag赋值为0,表示以太网停止发送图像数据。
图像封装模块的代码如下:
  1. 1  module img_data_pkt(
  2. 2       input                rst_n          ,   //复位信号,低电平有效
  3. 3       //图像相关信号
  4. 4       input                cam_pclk       ,   //像素时钟
  5. 5       input                img_vsync      ,   //帧同步信号
  6. 6       input                img_data_en    ,   //数据有效使能信号
  7. 7       input        [15:0  img_data       ,   //有效数据
  8. 8      
  9. 9       input                transfer_flag  ,   //图像开始传输标志,1:开始传输 0:停止传输
  10. 10      //以太网相关信号
  11. 11      input                eth_tx_clk     ,   //以太网发送时钟
  12. 12      input                udp_tx_req     ,   //udp发送数据请求信号
  13. 13      input                udp_tx_done    ,   //udp发送数据完成信号                              
  14. 14      output  reg           udp_tx_start_en,   //udp开始发送信号
  15. 15      output       [31:0  udp_tx_data    ,   //udp发送的数据
  16. 16      output  reg  [15:0  udp_tx_byte_num    //udp单包发送的有效字节数
  17. 17      );   
  18. 18      
  19. 19 //parameter define
  20. 20 parameter  CMOS_H_PIXEL = 16'd640;  //图像水平方向分辨率
  21. 21 parameter  CMOS_V_PIXEL = 16'd480;  //图像垂直方向分辨率
  22. 22 //图像帧头,用于标志一帧数据的开始
  23. 23 parameter  IMG_FRAME_HEAD = {32'hf0_5a_a5_0f};
复制代码
程序中第20行至23行定义了三个参数,分别是CMOS_H_PIXEL(图像水平方向分辨率)、CMOS_V_PIXEL(图像垂直方向分辨率)和IMG_FRAME_HEAD(图像帧头)。我们在用网口传输图像数据时,一次发送一行图像数据。在发送一帧图像的第一行数据时,在一行数据的开头添加图像的帧头和图像的行场分辨率,共8个字节,图像的帧头是32'hf0_5a_a5_0f,共占用4个字节;而图像的行场分辨率占用4个字节,本次实验传输的图像分辨率为640*480。
这里我们来详细说明下在一帧的第一行添加帧头的原因。在使用UDP传输数据时,有时会出现数据丢包的现象。这里说的丢包现象并不是开发板没有把数据发送出去,而是电脑接收端系统繁忙或者其它的原因没有接收到数据。为了降低丢包对视频显示带来的影响,我们为每帧图像添加一个帧头,上位机解析到帧头之后,接下来将接收到的数据重新放到图像显示区域的起始位置,保证了在视频传输过程中,即使出现丢包的现象,视频也能恢复到正常显示的画面。图像帧头的值要尽量选择图像数据不容易出现的值,否则很容易把图像数据当成帧头,程序中把图像帧头设置为{32'hf0_5a_a5_0f},图像中出现的像素数据和帧头相同的概率极低。除此之外,上位机软件按照帧头为{32'hf0_5a_a5_0f}来解析数据,所以帧头的值不可以随意修改。
  1. 25 reg             img_vsync_d0    ;  //帧有效信号打拍
  2. 26 reg             img_vsync_d1    ;  //帧有效信号打拍
  3. 27 reg             neg_vsync_d0    ;  //帧有效信号下降沿打拍
  4. 28                                 
  5. 29 reg             wr_sw           ;  //用于位拼接的标志
  6. 30 reg    [15:0  img_data_d0     ;  //有效图像数据打拍
  7. 31 reg             wr_fifo_en      ;  //写fifo使能
  8. 32 reg    [31:0  wr_fifo_data    ;  //写fifo数据
  9. 33
  10. 34 reg             img_vsync_txc_d0;  //以太网发送时钟域下,帧有效信号打拍
  11. 35 reg             img_vsync_txc_d1;  //以太网发送时钟域下,帧有效信号打拍
  12. 36 reg             tx_busy_flag    ;  //发送忙信号标志
  13. 37                                 
  14. 38 //wire define                  
  15. 39 wire            pos_vsync       ;  //帧有效信号上升沿
  16. 40 wire            neg_vsync       ;  //帧有效信号下降沿
  17. 41 wire            neg_vsynt_txc   ;  //以太网发送时钟域下,帧有效信号下降沿
  18. 42 wire   [9:0   fifo_rdusedw    ;  //当前FIFO缓存的个数
  19. 43
  20. 44 //*****************************************************
  21. 45 //**                    main code
  22. 46 //*****************************************************
  23. 47
  24. 48 //信号采沿
  25. 49 assign neg_vsync = img_vsync_d1 & (~img_vsync_d0);
  26. 50 assign pos_vsync = ~img_vsync_d1 & img_vsync_d0;
  27. 51 assign neg_vsynt_txc = ~img_vsync_txc_d1 & img_vsync_txc_d0;
  28. 52
  29. 53 //对img_vsync信号延时两个时钟周期,用于采沿
  30. 54 always @(posedge cam_pclk or negedge rst_n) begin
  31. 55      if(!rst_n) begin
  32. 56          img_vsync_d0 <= 1'b0;
  33. 57          img_vsync_d1 <= 1'b0;
  34. 58      end
  35. 59      else begin
  36. 60          img_vsync_d0 <= img_vsync;
  37. 61          img_vsync_d1 <= img_vsync_d0;
  38. 62      end
  39. 63 end
  40. 64
  41. 65 //寄存neg_vsync信号
  42. 66 always @(posedge cam_pclk or negedge rst_n) begin
  43. 67      if(!rst_n)
  44. 68          neg_vsync_d0 <= 1'b0;
  45. 69      else
  46. 70          neg_vsync_d0 <= neg_vsync;
  47. 71 end
复制代码
程序中第54行至63行在cam_pclk时钟下对img_vsync信号延时两拍,用于采该信号的上升沿和下降沿。程序中第66行至71行对下降沿延时一拍,我们后面的处理中会用到这些信号。
  1. 73 //对wr_sw和img_data_d0信号赋值,用于位拼接
  2. 74 always @(posedge cam_pclk or negedge rst_n) begin
  3. 75      if(!rst_n) begin
  4. 76          wr_sw <= 1'b0;
  5. 77          img_data_d0 <= 1'b0;
  6. 78      end
  7. 79       else if(neg_vsync)
  8. 80          wr_sw <= 1'b0;
  9. 81      else if(img_data_en) begin
  10. 82          wr_sw <= ~wr_sw;
  11. 83          img_data_d0 <= img_data;
  12. 84      end   
  13. 85 end
复制代码
程序中第74行至85行代码对wr_sw和img_data_d0信号进行赋值,用于位拼接。这是由于输入的图像数据为16位,而以太网顶层模块的用户数据端口是32位,因此这里需要对输入的数据进行寄存,用于将16位拼接成32位。
  1. 87 //将帧头和图像数据写入FIFO
  2. 88 always @(posedge cam_pclk or negedge rst_n) begin
  3. 89      if(!rst_n) begin
  4. 90          wr_fifo_en <= 1'b0;
  5. 91          wr_fifo_data <= 1'b0;
  6. 92      end
  7. 93      else begin
  8. 94          if(neg_vsync) begin
  9. 95              wr_fifo_en <= 1'b1;
  10. 96              wr_fifo_data <= IMG_FRAME_HEAD;               //帧头
  11. 97          end
  12. 98          else if(neg_vsync_d0) begin
  13. 99              wr_fifo_en <= 1'b1;
  14. 100             wr_fifo_data <= {CMOS_H_PIXEL,CMOS_V_PIXEL};  //水平和垂直方向分辨率
  15. 101         end
  16. 102         else if(img_data_en && wr_sw) begin
  17. 103             wr_fifo_en <= 1'b1;
  18. 104             wr_fifo_data <= {img_data_d0,img_data};       //图像数据位拼接,16位转32位
  19. 105           end
  20. 106         else begin
  21. 107             wr_fifo_en <= 1'b0;
  22. 108             wr_fifo_data <= 1'b0;        
  23. 109         end
  24. 110     end
  25. 111 end
复制代码
本次模块中例化了异步FIFO,我们需要将一帧的帧头、图像行场分辨率和拼接后的数据都写入FIFO中。需要注意的是,只有一帧图像的第一行图像才需要添加帧头和图像行场分辨率,而其余行的图像是不需要添加的,因此我们需要根据img_vsync(帧同步信号)来判断什么时候输入的数据是第一行的图像。
img_vsync作为帧同步信号,该信号的下降沿之后输入的数据,就是一帧的第一行数据。因此我们在img_vsync的下降沿将帧头IMG_FRAME_HEAD写入FIFO,在下一个时钟周期将行场分辨率{CMOS_H_PIXEL,CMOS_V_PIXEL}写入FIFO中。当img_data_en信号和wr_sw同时为高时,将拼接后的32位数据写入FIFO中即可。
  1. 113 //以太网发送时钟域下,对img_vsync信号延时两个时钟周期,用于采沿
  2. 114 always @(posedge eth_tx_clk or negedge rst_n) begin
  3. 115     if(!rst_n) begin
  4. 116         img_vsync_txc_d0 <= 1'b0;
  5. 117         img_vsync_txc_d1 <= 1'b0;
  6. 118     end
  7. 119     else begin
  8. 120         img_vsync_txc_d0 <= img_vsync;
  9. 121         img_vsync_txc_d1 <= img_vsync_txc_d0;
  10. 122     end
  11. 123 end
复制代码
由于cam_pclk和eth_tx_clk为异步时钟,因此不能直接在eth_tx_clk时钟域下直接采集neg_vsync信号(该信号在cam_pclk时钟域下产生),因此这里在eth_tx_clk时钟域下重新对img_vsync信号进行打拍和采沿。
  1. 125 //控制以太网发送的字节数
  2. 126 always @(posedge eth_tx_clk or negedge rst_n) begin
  3. 127     if(!rst_n)
  4. 128         udp_tx_byte_num <= 1'b0;
  5. 129     else if(neg_vsynt_txc)
  6. 130         udp_tx_byte_num <= {CMOS_H_PIXEL,1'b0} + 16'd8;
  7. 131     else if(udp_tx_done)   
  8. 132         udp_tx_byte_num <= {CMOS_H_PIXEL,1'b0};
  9. 133 end
复制代码
我们只在一帧的第一行添加了帧头和行场分辨率,因此只有在发送第一行图像数据时,发送的UDP字节数为1288(640*2+8),而其余行单包发送的字节数为1280。
  1. 135 //控制以太网发送开始信号
  2. 136 always @(posedge eth_tx_clk or negedge rst_n) begin
  3. 137     if(!rst_n) begin
  4. 138         udp_tx_start_en <= 1'b0;
  5. 139         tx_busy_flag <= 1'b0;
  6. 140     end
  7. 141     //上位机未发送“开始”命令时,以太网不发送图像数据
  8. 142     else if(transfer_flag == 1'b0) begin
  9. 143         udp_tx_start_en <= 1'b0;
  10. 144         tx_busy_flag <= 1'b0;        
  11. 145     end
  12. 146     else begin
  13. 147         udp_tx_start_en <= 1'b0;
  14. 148         //当FIFO中的个数满足需要发送的字节数时
  15. 149         if(tx_busy_flag == 1'b0 && fifo_rdusedw >= udp_tx_byte_num[15:2]) begin
  16. 150             udp_tx_start_en <= 1'b1;                     //开始控制发送一包数据
  17. 151             tx_busy_flag <= 1'b1;
  18. 152         end
  19. 153         else if(udp_tx_done || neg_vsynt_txc)
  20. 154             tx_busy_flag <= 1'b0;
  21. 155     end
  22. 156 end
复制代码
当上位机发送“开始”命令后,transfer_flag信号为高电平,此时以太网开始传输图像数据。当以太网发送空闲时,且FIFO中的字节数达到待发送的字节数时,此时拉高udp_tx_start_en信号,开始控制以太网顶层模块读取FIFO,并将FIFO中的数据发送给上位机。
  1. 158 //异步FIFO
  2. 159 async_fifo_1024x32b async_fifo_1024x32b_inst (
  3. 160   .rst(pos_vsync| (~transfer_flag)), // FIFO复位控制
  4. 161   .wr_clk(cam_pclk),                  // FIFO写时钟
  5. 162   .rd_clk(eth_tx_clk),                // FIFO读时钟
  6. 163   .din(wr_fifo_data),                // FIFO写数据
  7. 164   .wr_en(wr_fifo_en),                // FIFO写使能
  8. 165   .rd_en(udp_tx_req),                 // FIFO读使能
  9. 166   .dout(udp_tx_data),                 // FIFO读数据
  10. 167   .full(),                       
  11. 168   .empty(),                 
  12. 169   .rd_data_count(fifo_rdusedw),       // FIFO读侧数据个数
  13. 170   .wr_rst_busy(),      
  14. 171   .rd_rst_busy()     
  15. 172 );   
  16. 173
  17. 174 endmodule
复制代码
程序中第158行至172行代码例化了异步FIFO,存储深度为1024,数据位宽为32位。值得一提的是,我们帧同步的上升沿和transfer_flag信号作为FIFO的复位信号,避免上位机在传输图像中途发送停止命令,FIFO没有被清空的情况。
下图为图像数据封装模块采集过程中在线抓取的波形图,在img_vsync的下降沿,依次将图像帧头和行场分辨率写入fifo(如下图的wr_fifo_en和wr_fifo_data),帧头为32’hf0_5a_a5_0f,行分辨率为16’h0280(640),场分辨率为32’h01e0(32’h480)。
image007.png
图 54.4.2 在线抓取的波形图

1.5 下载验证
编译工程并生成比特流.sbit文件后,此时将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,将网线一端连接开发板的网口,另一端连接电脑的网口,然后将OV7725摄像头连接开发板上的摄像头接口,注意镜头方向朝外,最后连接电源线,如下图所示。
image009.png
图 54.5.1 开发板硬件连接示意图
接下来我们打开电源开关,并下载程序。程序下载完成后,此时开发板上还看不到现象,我们接下来打开正点原子UDP传输视频显示的上位机,位于资料盘(A盘)/6_软件资料/1_软件/video_transfer文件夹下,如下图所示:
image011.png
图 54.5.2 打开上位机软件
双击video_transfer.exe即可打开软件,如果软件无法打开,先双击安装上图中的vc_redist.x64.exe,安装完成后再打开video_transfer.exe。
打开后的界面如下图所示。
image013.png
图 54.5.3 上位机设置界面
按照上图的界面进行设置,这些设置和程序中定义的参数和代码是对应的。设置完成后,点击“打开”按钮,此时上位机会通过网口向开发板发送ASCII码“1”,开发板收到开始命令后,会开始传输图像数据,如下图所示,如果图像画面显示有模糊不聚焦的现象可以通过旋转摄像头镜头进行聚焦调试,正常显示如下图所示:
image015.png
图 54.5.4 上位机实时显示画面
由上图可知,开发板传输的图像分辨率是640*480,帧率约为30fps,如果需要停止显示画面的话,只需要点击上方的“关闭”按钮即可。需要注意的是,上位机显示的画面受电脑性能的影响,如果电脑性能较差的话,上位机可能会出现卡顿和崩溃的现象。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

GMT+8, 2024-11-25 09:19

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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