【LabVIEW FPGA图形化】IP集成节点:USB通信

本文介绍了FPGA在USB通信中的应用,包括蔡氏定律的概念,阐述了USB通信的外围电路设计,特别是CY7C68013AUSB控制器的工作原理。接着,详细讲解了LabVIEWFPGAIP集成节点中编写USB通信的网表文件,涉及数据读写的控制逻辑,并给出了Verilog代码示例。最后,讨论了FPGA图形化程序的编写,包括如何构建USB通信线程和数据交互。
摘要由CSDN通过智能技术生成

一、前情提要

上一节内容介绍了图形化FPGA测量数字信号的频率,广泛用于舵机角度控制、直流电机转速控制等运动控制领域,为此,NI在Motion领域重点推广和发展SoftMotion工具包。此外,USB通信现在应用广泛,因为支持热插拔,即插即用,很多采集卡、移动设备、相机里面都集成了USB通信口,FPGA在通信方面也发挥着重要的作用,本节重点关注FPGA的并行通信,本节内容将手把手带大家编写一个USB通信实验的IP集成节点。

【LabVIEW FPGA图形化】 ngc、edf网表文件的编写:LED流水灯

【LabVIEW FPGA图形化】 IP集成节点:按键控制LED

【LabVIEW FPGA图形化】IP集成节点:串口通信

【LabVIEW FPGA图形化】IP集成节点:频率计

二、FPGA蔡氏定律

FPGA有两条蔡氏定律:
1、FPGA不仅仅是FPGA。
2、FPGA的最终目的是做出可用的电路。

FPGA的学习脱离不了硬件电路,FPGA功能的实现也需要基于外围电路,在本节实验中,由于USB协议较复杂,需要配置USB报文及端点信息,FPGA实现难度大,占用资源多,因此FPGA需要USB控制器外设实现通信功能,FPGA只需要进行数据处理和并行发送,大大的节约了硬件资源,完成整个通信功能的不仅仅是FPGA本身,还需要结合外围电路来实现可用的功能。

三、USB外围电路

USB是用差分的方法进行传输的,就是在发送端两条信号线上发送幅值一样但是相位相反的信号,然后在接收端将这两个信号进行减法运算,就会得到相位相同幅值翻倍的信号。将两条信号线进行扭在一起,根据电磁学的原理,可以近似认为两条线受到的干扰信号的幅值和相位是相同的,所以在进行减法运算的时候就会将干扰信号给去除掉,提高其抗干扰的能力。

USB采用差分的结构可以抑制线路中的共模干扰,其串行结构使得它的时钟频率可以做到很高,开发板上通过Cypress CY7C68013A USB2.0控制器芯片实现PC与FPGA间的高速数据通信,CY7C68013A控制器完全符合通用串行总线协议2.0版规范,支持全速(12Mbit/s)以及低速(480Mbit/s)模式。用户通过用USB线连接PC的USB口和开发板的MINI型的USB口(J6)就可以进行USB2.0的数据通信。

FPGA和CY68013A连接图
对于CY68013A这款芯片采用24MHz的外部晶振,内部可通过锁相环倍频至48MHz进行工作,需要说明的是CY68013A里面本质上是8051处理器,需要相应的固件程序以实现通信功能,相关程序将存储与EEPROM中,方便更新。如果用户需要自己做板子的话,可以采用FT232的USB芯片,价格相对便宜。用户可以通过配置 USB 芯片的工作模式,让其工作在 Master 或 Slave 模式。 本节实验模拟的是一个 USB 采集卡,因此,属于 USB 从设备,应该工作在 Slave 模式,PC机工作在 Master 主机模式。
CY68013A实物图

四、LabVIEW FPGA IP集成节点网表文件的编写

USB通信的实现实际上就是控制CY68013A的过程,我们需要针对CY68013A写时序,相关内容可以参考数据手册:
在这里插入图片描述
通过原理图可以发现,USB 芯片的外部晶振是 24MHz,而且有一个大小为 4MByte 的EEPROM 芯片 AT24C32 用于保存固件程序。数据接收和发送引脚公用一组,都是并行 16位的,也就是说,用户可以一次性传输 2 个字节数据或者 1 个 16 位数据给 USB 芯片内部的发送缓冲区 FIFO,或者从 USB 芯片内部接收缓冲区 FIFO 里面读取从 PC 下发的数据。
“USB_FLAGA”、“USB_FLAGB”、“USB_FLAGC”这 3 个输出引脚的含义如下:

input usb_flaga, //CY68013 EP2 FIFO empty indication; 1:not empty; 0: empty
input usb_flagb, //CY68013 EP4 FIFO empty indication; 1:not empty; 0: empty
input usb_flagc, //CY68013 EP6 FIFO full indication; 1:not full; 0: full

通过判断USB_FLAGA和USB_FLAGB这两个引脚是否为高电平,就可以知道USB芯片是否接收到了数据;USB_FLAGC判断发送缓冲区是否满了看要不要继续发送数据。
RTL视图:

USB的RTL视图
在该模块中,我们的数据通过data_in输入,通过输入有效(input_vaild)握手,除了时钟clk和复位reset外,其他的信号都是cypress芯片给出的,比较重要的是其FIFO空满标志位,用于读写控制,输出端除了接收数据的data_out和idle信号外,其他都是与芯片连接的接口。
控制这个芯片的逻辑是

  • 没有数据输入,将一直保持在读取状态,idles始终空闲1
  • 有数据输入,但是输入FIFO没有满,存入数据后仍向上一级反馈idles空闲1
  • 有数据输入,输入FIFO满了,不能存入数据,向上一级反馈忙信号0

从上面的这个逻辑可以看出,usb_cypress_little这个模块是写入优先的,通过idles作为握手信号,告诉上一级应该读还是写,当idles为忙信号0,则USB模块不从上一级读数据,此时开放写入和数据处理等请求。

根据RTL的思路,我们可以编写出USB模块的代码:

`timescale 10ns/1ns
//
// Module Name:    usb_cypress_little
// Description: If the FIFO of EP2 is not empty and 
//              the EP6 is not full, Read the 16bit data from EP2 FIFO
//              and send to EP6 FIFO.  
//
module usb_cypress_little(
    input clk,                       //FPGA Clock Input 50Mhz
    input reset,                         //FPGA Reset input
    input usb_flaga,                       //CY68013 EP2 FIFO empty indication; 1:not empty; 0: empty  外部读信号进来时 查询是否读空
    input usb_flagc,                        //CY68013 EP6 FIFO full indication; 1:not full; 0: full
                                           
    output reg usb_slcs,                   //CY68013 Chipset select
    output reg usb_sloe,                   //CY68013 Data output enable
    output reg usb_slrd,                   //CY68013 READ indication
    input [15:0] usb_port_in,
    output reg usb_slwr,                   //CY68013 Write indication
    output reg [1:0] usb_fifoaddr,         //CY68013 FIFO Address
    output reg usb_fd_en,
    output reg [15:0] usb_port_out,

//    inout [15:0] usb_fd,                   //CY68013 Data 王电令把这个三态门拆成读写 usb_port_in usb_port_out
//                                            //王电令增加读写控制位 数据输入 和输入有效 数据输出和输出有效
//                                            //usb_fd_en 映射到了三态门的方向选择   即新型EIO节点    
//    input usb_flagb,                       //不用EP4
    input rw,
    input [15:0] data_in,
    input in_valid,
    output reg out_vaild,
    output reg [15:0] data_out,
    output reg idles                            //用access_req作为idle信号


    );

//reg[15:0] data_reg;
reg[15:0] data_temp;

reg bus_busy;                              
reg write_req;                            
reg read_req;                            
//reg access_req;

reg [4:0] usb_state; 
reg [4:0] i; 

parameter IDLE=5'd0; 
parameter EP2_RD_CMD=5'd1; 
parameter EP2_RD_DATA=5'd2; 
parameter EP2_RD_OVER=5'd3; 
parameter EP6_WR_CMD=5'd4; 
parameter EP6_WR_OVER=5'd5; 
            

/* Generate USB read/write access request*/
//always @(negedge clk or posedge reset)
//begin
//   if (reset ) begin
//      access_req<=1'b0;
//	end
//   else begin
//      if (usb_flaga & usb_flagc & (bus_busy==1'b0))     //a为高电平说明有数据要接收,c为高电平说明数据不急着读取
//          access_req<=1'b1;                             //busy等于0,说明没有在发送或接收过程中,这一位相当于idle
//      else                                              //USB读写请求
//          access_req<=1'b0;                            //这样的代码是有问题的  只判断有数据要写 不判断有数据要读
//   end                                                 //这就是王电令要分读写的原因,有数据就写,没数据不读
//end
always @(negedge clk or posedge reset)
begin
   if (reset ) begin
      write_req<=1'b0;
      read_req<=1'b0;
	end
   else begin
      if (usb_flaga & (rw==1'b0) &(bus_busy==1'b0))     //a为高电平说明有数据要接收,外部状态允许接收,同时总线不忙
          read_req<=1'b1;                             //busy等于0,说明没有在发送或接收过程中,这一位相当于idle

      else if(usb_flagc & rw & in_valid & (bus_busy==1'b0))                    //发送缓冲区没满 有发送请求 总线不忙
          write_req<=1'b1;
      else  begin                                            //USB读写请求
          read_req<=1'b0;  
          write_req<=1'b0;

        end                         
   end                                             
end

always @(negedge clk or posedge reset)
begin
   if (reset ) begin
    idles<=1'b0;
   end
    else if((bus_busy==1'b0)& (usb_flagc) & (usb_flaga==1'b0) & (rw==1'b0))  //总线不忙 发送缓冲区没满 没有接收请求 没有发送请求
    idles<=1'b1;
    else 
    idles<=1'b0;
end

always @(negedge clk or posedge reset)
begin
   if (reset ) begin
    data_temp<=16'b0;
   end
   else if(in_valid==1'b1)
   data_temp<=data_in;
   else 
   data_temp<=data_temp;
end

/* Generate USB read and write command*/
always @(posedge clk or posedge reset)
  begin
   if (reset) begin
		 usb_fifoaddr<=2'b00;       //fifoaddr为0 表示使用EP2
       usb_slcs<=1'b0;		        //使能 一直为有效
		 usb_sloe<=1'b1;		    //芯片在读写状态时拉低
       usb_slrd<=1'b1;	            //芯片读的时候拉高,不读的时候拉低
		 usb_slwr<=1'b1;            //读为1,写为0,默认在读状态,空闲也在读状态
       usb_fd_en<=1'b0;	            //控制是否从data——reg中读取数据
	    usb_state<=IDLE;		 
   end		                    
   else begin
	     case(usb_state)
        IDLE:begin
			        usb_fifoaddr<=2'b00;	
                 i<=0;
					  usb_fd_en<=1'b0;					  
		           if (read_req==1'b1) begin    //读请求                              
						  usb_state<=EP2_RD_CMD;               //开始读USB EP2 FIFO的数据
						  bus_busy<=1'b1;                       //状态变忙
			        end	
                    else if(write_req==1'b1)  begin  //写请求
                            usb_fifoaddr<=2'b10;
                            usb_state<=EP6_WR_CMD;
                            bus_busy<=1'b1;
                    end
                 else begin
				       bus_busy<=1'b0;		  
						 usb_state<=IDLE;  
					  end	 
        end
        EP2_RD_CMD:begin                               //发送EP2端口FIFO的数据读命令,先拉低OE信号,再拉低RD信号              
			     if(i==2) begin       //延迟两拍
                	usb_slrd<=1'b1;
                	usb_sloe<=1'b0;	                 //OE信号变低					
                  i<=i+1'b1;
              end
              else if(i==8) begin
                	usb_slrd<=1'b0;                       //RD信号变低				
                	usb_sloe<=1'b0;						
                  i<=0;	
						usb_state<=EP2_RD_DATA;  
              end
              else begin
				      i<=i+1'b1;
				  end
        end		  
		  EP2_RD_DATA:begin                             //读取EP2中的数据
              if(i==8) begin
                	usb_slrd<=1'b1;                     //RD信号变高,读取数据				
                	usb_sloe<=1'b0;						
                  i<=0;	
						usb_state<=EP2_RD_OVER;
                        data_out<=usb_port_in;
                        out_vaild<=1'b1;
//			         data_reg<=usb_fd;	                   //读取数据
              end
              else begin
                	usb_slrd<=1'b0;                      		
                	usb_sloe<=1'b0;					  
				      i<=i+1'b1;
				  end
		 end		  
		 EP2_RD_OVER:begin
              if(i==4) begin
                	usb_slrd<=1'b1;                      //OE信号变高,读取数据完成		
                	usb_sloe<=1'b1;						
                  i<=0;	
//			         usb_fifoaddr<=2'b10;				//切换到EP6	,应该在发送的时候切换 这个时候回Idle		
//						usb_state<=EP6_WR_CMD;
						usb_state<=IDLE;
              end
              else begin
                	usb_slrd<=1'b1;                      		
                	usb_sloe<=1'b0;	
                    out_vaild<=1'b0;
                    data_out<=16'b0;				  
				      i<=i+1'b1;
				  end
		 end	
		 EP6_WR_CMD:begin		                              //EP6端口写入数据,slwr变低后,再变高
              if(i==8) begin
                  usb_slwr<=1'b1;
                  i<=0;							
						usb_state<=EP6_WR_OVER;
              end
              else begin
                  usb_slwr<=1'b0;	
						usb_fd_en<=1'b1;                     //数据总线改为输出		
                        usb_port_out<=data_temp;				
				      i<=i+1'b1;
				  end
		 end		  
		 EP6_WR_OVER:begin		                             //EP6写完成                           
              if(i==4) begin
                  usb_fd_en<=1'b0;
                  usb_port_out<=16'b0;
				      bus_busy<=1'b0;
                  i<=0;							
						usb_state<=IDLE;
              end
              else begin		  
				      i<=i+1'b1;
				  end
		 end
       default:usb_state<=IDLE;
       endcase
	 end
  end  
  
//assign usb_fd = usb_fd_en?data_reg:16'bz;       //USB数据总线输入输出改变

endmodule

代码中通过判断总线状态、FIFO缓冲区状态、读写请求信号与上一级通过idle握手,默认设置EP2和EP6作为USB的endpoint(用户可根据需求修改),data_temp用于将data_in的信号同步到当前时钟域下并保持。通过定义6种有限状态使得数据按照时序发送与接收。

parameter IDLE=5'd0; 
parameter EP2_RD_CMD=5'd1; 
parameter EP2_RD_DATA=5'd2; 
parameter EP2_RD_OVER=5'd3; 
parameter EP6_WR_CMD=5'd4; 
parameter EP6_WR_OVER=5'd5; 

用过NI公司的FPGA板卡或者RIO产品的用户应该知道,I/O节点传统FPGA不同,传统FPGA对一些总线共用读写的情况如IIC、单总线,数据传输采用三态门的形式实现,语言形态为 inout。由于 NI FPGA软件提供的 IP Node(类似 DLL 函数调用节点)本身不支持 inout 类型,所以我们将传统的 EIO 节点进行升级改造,将一个 EIO 资源同时拆分为 3 个独立的 I/O 资源:输入、输出、使能。这样做的目的是可以通过“使能”这个开关来控制 I/O 当前是输出状态还是高阻态(输入)。默认情况下,我们将 Xilinx FPGA 芯片的最后一个 BANK 里面的 I/O 引脚,全部封装成 EIO 节点。

在FPGA 程序框图中可以通过拖放各种不同的 I/O 节点来驱动真实的 FPGA 引脚或者外设,这些 I/O 节点称之为 Elemental I/O,简称 EIO。Elemental I/O 是 NI 独创的一种编程模式,最常见的就是网络共享变量。EIO 本质上是把一个对象的全部操作封装到一个节点内部,有点类似 Express VI 的味道。可以通过属性节点的方式对其进行配置。

  • 1)传统 EIO 节点:使用方便,操作简单,易于理解,适用于直接读写 IO,输入输出模式切换速度慢(切换回高阻模式需要延时至少 1个CLK),无法与IP Node节点直接对接。

  • 2)新型 EIO 节点:适用于 inout 数据类型,可与 IP Node 进行结合,输入输出模式切换速度快,一个时钟周期 CLK 内可完成工作模式切换。

    如果用户希望把 FPGA 引脚设置成高阻态,也就是不带输出电流的输入模式,高阻模式最大的优点在于对外部电路不产生任何影响,同时还能读取到引脚外部的电平状态。我们可以利用 FPGA I/O 函数选板里面的 “FPGA I/O 方法节点”和“FPGA I/O 常量”来对 IO 引脚进行动态设置。
    将 IO 方法里面的“设置输出启用”设置为假(F),相当于将 IO 设置成了高阻态,然后再读取就可以了。如果设置为真(T),相当于开启了推出输出模式,输出的电流会比较大一些

    在这里插入图片描述

编写好.v文件后需要修改综合参数,在Xilinx Specific Options中去掉 Add I/O Buffer选项,不添加I/O buffer并综合。

五、FPGA图形化程序编写

将上面程序生成的usb_cypress_little.ngc(要求.v的文件名与模块名字相同) 添加到vi所在的工程目录下,调用IP block节点。

在这里插入图片描述
可以按照上图顺序将节点接口排好,方便连线,将外围接口进行连接通过FIFO交换数据,构成USB通信线程,在下图中对于数据位宽较宽的接口可以采用Boolean作为数据类型,便于数据拼接和拆分。

在这里插入图片描述

上图为USB通信线程,该线程可通过FIFO节点实现跨时钟域的数据交互,该线程最快可采用80M时钟。FIFO节点超时?用于判断FIFO内有无数据,接输入有效,有数据且IP核空闲的时候发送FIFO数据,idle忙或当前时钟已发送数据情况下,切换到读数据分支(假分支),因此需要一个与条件和一个反馈节点,这样可以实现互锁操作,保证每次数据更新与idle错开。

反馈节点(移位寄存器)对于数字电路来说,非常重要,可以简化编程

USB通信线程用于发送和接收数据,在发送数据时作为消费者线程,我们需要匹配一个信号产生线程作为生产者,下图我们用while循环生成了一个斜坡信号通过FIFO_USB_Write发送到上位机。

斜坡信号产生线程

为了方便观察结果,我们将发送的数据,接收的数据,以及数据的数量在前面板进行显示。

在上图中,面板上可以通过一个布尔控件控制信号的产生与暂停,生产者速度输入用于调整数据产生线程的分频系数,发送计数显示当前发送数据的数量,接收到的数据表示从上位机接收到的数据(十进制表示),接收计数表示上位机发送数据的个数。

FPGA图形化前面板
此外,上位机可显示出接收到的斜坡信号。

斜坡信号

在进行开发板与计算机连接前,要确保提前安装并配置好Cypress的驱动程序,采用NI-VISA的USB向导安装好驱动
在这里插入图片描述
这样才能保证上下位机都能接收到数据。

总结

FPGA图形化编程其优势在于逻辑图形化,若不去考虑底层verliog,在顶层设计上FPGA的图形化编程是有优势的,可以更有效率的完成FPGA的设计,本节实验的Verliog网表文件和LabVIEW的vi均开源在 我的资源 如有需要可以下载学习。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值