开启辅助访问
 找回密码
 立即注册

一天一台设计实例-FPGA和USB(二)

林东 回答数3 浏览数751
USB2.0协议的FPGA应用



用FPGA实现USB协议的工作量很大,而且复杂度很高,一般应用时很少直接使用FPGA实现USB协议,所以本次应用USB时是利用USB2.0的PHY芯片Cypress厂家的CY7C68013芯片。下面在针对在使用过程中需要用到的几个概念进行解释,如下。
2.4.2.1 USB协议简要概念
USB2.0设备组成
每一台USB设备由一台或多个配置来控制其行为,使用多配置原因是对操作系统的支持;一台配置是由接口(Interface)组成;接口则是由管道(Pipe)组成;管道是和USB设备的端点(Endpoint)对应,端点都是输入输出成对的。在固件编程中,USB设备、配置、接口和管道都来描述符来报告其属性。
一台端点(Endpoint)建立一台管道。管道的端点总是成对出现,即In Endpoint和Out Endpoint。端点0默认为控制管道,其它端点可以配置成数据管道。一台具体的端点,只能工作在一种传输模式下。
In Endpoint:由device向Host发送数据的端点。
Out Endpoint:由Host向device发送数据的端点。
USB2.0传输速度
USB1.0和USB1.1版本中,只支持1.5Mb/s的低速(low-speed)模式和12Mb/s的全速模式。在USB2.0种,又加入了速度更快(480Mb/s)的高速模式。而USB3.0的最大传输带宽高达5.0Gbps(625MB/s)。
USB可扩展设备
USB1.1规定最多为4层,USB2.0规定最多为6层。理论上,一台USB主控制器最多可接127个设备,这是因为协议规定每个USB设备具有一台7 bit的地址(取值范围0~127),而地址0是保留给未初始化的设备使用。
USB传输类型
虽然USB定义了数据在总线上传输的基本单位是包,但是我们还不能随意地使用包来传输数据,必须按照一定的关系把这些不同的包组织成事务才能传输数据。
事务通常由两个或者三个包组成:令牌包,数据包和握手包。
令牌包用来启动一台事务,总是由主机发送。
数据包传送数据,可以从主机到设备,也可以从设备到主机,方向由令牌包来制定。
握手包的发送者通常为数据接收者,当数据接收正确后,发送握手包。设备也可以使用NAK握手包来表示数据还未准备好。
USB协议规定了4种传输类型:批量传输、等时传输(同步传输),中断传输和控制传输。其中,批量传输、等时传输、中断传输每传输一次数据都是一台事务;控制传输包括三个过程,建立过程和状态过程分别是一台事务,数据过程则可能包含多个事务。4种数据传输的相关特性(仅限USB1.1协议)如下表。
表Chapter‑1 USB4种数据传输的相关特性

传输模式中断传输Interrupt批量传输Bulk等待传输ISO控制传输Control
传输速率/Mbps12(1.5 低速)12121.5/12
数据的最大长度/Byte1~64(1~8 低速)8/16/32/641~10231~64(1~8 低速)
数据周期性没有没有
发送错误重传
应用设备鼠标键盘打印机语音-
可得到的最大宽度/Mbps6.762(0.051 低速)9.72810.240-

批量传输使用批量事务传输数据。一台批量事务由三个阶段:令牌包阶段,数据包阶段和握手包阶段。每个阶段都是一台独立的包。批量传输通常用于数据量大,对数据的实时性要求不高的场合。
USB2.0 数据帧
USB2.0和USB1.1规范的最大不同就是数据帧。在USB1.1规范中,USB数据采用每毫秒一台数据帧的方式进行数据传输,在毫秒数据帧的开始,USB主机首先产生帧开始(SOF)数据包,并传输当前数据帧号,后面是传输数据。对于USB2.0规范,为了支持480Mbps高速传输速度,USB2.0提出了微帧的概念,每毫秒数据帧又包含8个微帧。
USB2.0 端点缓冲区
表Chapter‑2 USB2.0端点缓冲区

传输类型USB1.1数据包大小USB2.0数据包大小
控制传输8,16,32,6464
批量传输8,16,32,64512
中断传输1~641024
等时传输10231024

2.4.2.2  CY7C68013与FPGA



图Chapter‑1 CY7C68013内部构造
CY7C68013特点:

  • 支持USB2.0,内部包括USB2.0收发器、串行接口引擎(SIE)以及增强型51内核;
  • 灵活配置,可“软配置”RAM,取代了传统51的RAM和ROM,程序可以通过以下方式下载:通过USB口下载;通过外部E2PROM装载;外界存储设备(仅128引脚支持)
  • 模式灵活,可设置为主从模式,主模式下可对外部FIFO、存储器、ATAn接口设备进行高速读写操作,从模式下外部主控器(例如DSP、MCU)可把GPIF端口当作FIFO进行高速读写操作。
  • 支持与外设通过并行8位或者16位总线传输
官方资料AN61345 提供了一台示例项目,用以通过从设备 FIFO 接口将 FX2LP 链接至 FPGA。示例实现中描述的接口为各个应用执行高速度的 USB 链接事项,如数据采集、工业控制和监控以及图像处理。



图Chapter‑2 官方DEMO示例
可以通过两个不同的模式将 FX2LP 链接至 FPGA。这两个模式分别为通用可编程接口( GPIF)模式和从设备 FIFO模式。
官方硬件链接图如下:



图Chapter‑3 官方推荐硬件链接图
表Chapter‑3 硬件链接中引脚说明

引脚名称说明
SLRDSLRD 引脚应由主设备激活,用以从 FIFO 读取数据。
SLWRSLWR 引脚应该由主设备激活,以将数据写入到 FIFO 内。
SLOE是指 FIFO 输出驱动器的使能信号。
FIFOADR[1:0]这些信号用于选择有效的端点。
FD[15:0]16 位数据总线
FLAGA/FLAGB/FLAGC/FLAGDFIFO 使用这些标志来表示各种状态(满、空、可编程)。
IFCLK是指与从设备 FIFO 接口同步的时钟。在本应用笔记所提供的设计中,该时钟的频率被配置为 48 MHz,并由链接至 FX2LP 的 FPGA 生成。
CLKOUTFX2LP 的 CLKOUT 引脚可以提供的时钟频率分别为 12、 24 或 48 MHz

在使用过程中或者硬件设计时尽量以官方说明为主,尤其一些引脚最好都链接上,这样在使用过程中以应对后期不同应用。
2.4.2.3  CY7C68013读写时序

  • 同步Slave FIFO写
同步Slave FIFO写的标准链接图如下:



图Chapter‑4 同步Slave FIFO端口链接示意图
同步Slave FIFO写的标准时序如下:
IDLE:当写事件发生时,进状态1;
状态1:使FIFOADR[1:0]指向IN FIFO,进状态2;
状态2:如FIFO满,在本状态等待,否则进状态3;
状态3:驱动数据到数据线上,使SLWR有效,持续一台IFCLK周期,进状态4;
状态4:如需传输更多的数,进状态2,否则进状态IDLE。
状态跳转示意图如下:



图Chapter‑5 同步Slave FIFO状态转移示意图
几种情况的时序图示意如下(FULL,EMPTY,SLWR,PKTEND均假定低有效):



图Chapter‑6 同步Slave FIFO时序图
图示FIFO中本来没有数据,外部逻辑写入第一台数据时的情况。



图Chapter‑7同步Slave FIFO写入第一台数据时序图
图示假定FX2设定包大小为512字节,外部逻辑向FIFO端点中写入的数据达512字节时的情况。此时FX2硬件自动将已写入的512字节打成一包准备进行传输,这个动作就和在普通传输中,FX2固件向FIFO端点中写入512字节后,把512这个数写入EPxBC中一样,只不过这个过程是由硬件自动完成的。在这里可以看出“FX2固件不参与数据传输过程”的含义了。外部逻辑只须按上面的时序图所示的时序向FIFO端点中一台一台字节(或字)地写数,写到一定数量,FX2硬件自动将数据打包传输,这一切均不需固件的参与,由此实现高速数据传输。



图Chapter‑8 同步Slave FIFO端点被写满时的情况
下图是同步Slave FIFO写入时序:



图Chapter‑9 同步Slave FIFO写入时序
逻辑时序设计中,数据应该在IFCLK上升沿写入。同时注意SLWR、DATA之间的时序关系。

  • 同步Slave FIFO读:
同步Slave FIFO读的标准链接图如下:



图Chapter‑10 同步Slave FIFO读的标准链接图
同步Slave FIFO读的标准时序如下:
IDLE:当读事件发生时,进状态1;
状态1:使FIFOADR[1:0]指向OUT FIFO,进状态2;
状态2:使SLOE有效,如FIFO空,在本状态等待,否则进状态3;
状态3:从数据线上读数,使SLRD有效,持续一台IFCLK周期,以递增FIFO读指针,进状态4;
状态4:如需传输更多的数,进状态2,否则进状态IDLE。
状态跳转示意图如下:



图Chapter‑11 同步Slave FIFO读状态跳转示意图
单个和突发读取时序:



图Chapter‑12 同步SLAVE FIFO 同步读取序列和时序图



图Chapter‑13 同步Slave FIFO同步事件序列图
从上图所示,FPGA应该在IFCLK上升沿处采集数据。

  • 异步Slave FIFO写:
异步Slave FIFO写的标准链接图如下:



图Chapter‑14 异步Slave FIFO写的标准链接图
异步Slave FIFO写的标准时序如下:
IDLE:当写事件发生时,进状态1;
状态1:使FIFOADR[1:0]指向IN FIFO,进状态2;
状态2:如FIFO满,在本状态等待,否则进状态3;
状态3:驱动数据到数据线上,使SLWR有效,再无效,以使FIFO写指针递增,进状态4;
状态4:如需传输更多的数,进状态2,否则进状态IDLE。
状态跳转示意图如下:



图Chapter‑15 异步Slave FIFO状态跳转示意图
几种情况的时序图示意如下(FULL,EMPTY,SLWR,PKTEND均假定低有效):



图Chapter‑16 异步Slave FIFO几种情况的时序图示意
图示FIFO中本来没有数据,外部逻辑写入第一台数据时的情况。



图Chapter‑17 Slave FIFO 异步写时序
数据必须在SLWR解除沿前Tsfd出目前总线上,当SLWR上升沿时,数据将被写进FIFO中,同时更新FIFO的指针。

  • 异步Slave FIFO读:
异步Slave FIFO读的标准链接图如下:



图Chapter‑18 异步Slave FIFO读的标准链接图
异步Slave FIFO读的标准时序如下:
IDLE:当读事件发生时,进状态1;
状态1:使FIFOADR[1:0]指向OUT FIFO,进状态2;
状态2:如FIFO空,在本状态等待,否则进状态3;
状态3:使SLOE有效,使SLRD有效,从数据线上读数,再使SLRD无效,,以递增FIFO读指针,再使SLOE无效,进状态4;
状态4:如需传输更多的数,进状态2,否则进状态IDLE。
状态跳转示意图如下:



图Chapter‑19 异步Slave FIFO读状态跳转示意图
几种情况的时序图示意如下(FULL,EMPTY,SLRD,SLOE均假定低有效):



图Chapter‑20 异步Slave FIFO读几种情况的时序图示意
图示正常情况时的时序。



图Chapter‑21 Slave FIFO异步读时序
Data总线在SLRD下降沿时被触发更新,有一定时间的延迟,所以采用异步读取的方式,应该在SLRD上升沿处采集数据。

  • 同步与异步读写的引脚差异
表Chapter‑4 同步与异步读写的引脚差异

同步读异步读同步写异步写
IFCLKIFCLK
FIFOADR[1:0]FIFOADR[1:0]FIFOADR[1:0]FIFOADR[1:0]
EMPTYEMPTYFULLFULL
SLOESLOESLWRSLWR
SLRDSLRDFD[15:0]FD[15:0]
FD[15:0]FD[15:0]PKTENDPKTEND
SLCSSLCSSLCSSLCS

2.4.2.4  CY7C68013驱动及下载
CysuiteUSB_3_4_7软件下载链接:
http://www.waveshare.net/w/upload/7/79/CySuiteUSB_3_4_7_B204.7z
下载完成后解压安装,详见下图:


http://www.waveshare.net/study/data/attachment/portal/201609/22/113337ybl0lo6e8hjshuew.png
点击NEXT。











1、安装完CysuiteUSB_3_4_7,我们接入模块,在未加载驱动前,状态如下:



2、我们打开软件,看看可以如何加载驱动:





3、这里面涉及到模块的VID和PID,具体如何看呢,详见下图:







4、接着就是修改驱动文件上的VID和PID了,我们打开安装路径下的驱动目录,如下:



5、找到适合自个操作系统的驱动文件,比如我的是win7 64位,那么,进入../wlh/x64,以记事本方式打开cyusb.inf,然后修改替换并保存:







补充说明:把上图对应的操作系统前的“;”注释去掉,再保存
6、重新加载驱动文件



















7、通常情况,驱动到这里加载就能正常识别了。But...,部分64位的操作系统有个强制数字签名比较烦球(比如我的这个机子上的就有这种情况),那么可能会有类似下面的情况出来:





8、那么大家伙可以重启按F8,临时禁用数字签名,不知道如何禁用的同学可以百度下(http://jingyan.bAIdu.com/article/3d69c5518ca3fdf0cf02d7c9.html)
9、禁用后重启,识别正常,打开软件,正常识别!




http://www.waveshare.net/study/data/attachment/portal/201609/22/113337ybl0lo6e8hjshuew.png
关于 Win8 哈希值不在文件中的安装错误解决方案:
在 Win8-64 系统下安装 USB驱动出现了如下问题:



由于 Win8 强制要求驱动程序签名,而Cypress 的驱动未经过签名,因此驱动安装不成功。出现这种情况的,网上有很多解决方案,这里例举网友晴天/zt小猪在 Win8-64 位操作系统上测试验证的解决方案,如下:
1. 按快捷键 win+R 打开运行命令
2. (请先看完后面的再操作!!)运行输入 shutdown.exe /r /o /f /t 00
3. 点击确定
4. 系统将重启
5. 重启后点击疑难解答
6. 选择高级选项
7. 选择 windows 开机设置
8. 点击重启
9. 选择高级选项
10.选择“禁用强制驱动程序”(F7 键)
11. 然后装驱动就可以了
完成以上操作后, 再次安装 USB 驱动程序,成功的界面如下所示:



烧写模块固件:(说明此方法仅适用于擦除了EEPROM的模块,如何擦除详见用户手册)
把模块通过USB口链接到电脑。
打开软件CyConsole,Device选择模块,然后点击“Lg EEPROM”,
打开Slave_FIFO.iic文件,详细操作如下。











按模块上的RST按钮,查看电脑“设备管理器”中能否正确识别到,有则固件下载成功。
CY7C68013想要正常工作,就得给他编写好相应的固件,然后再固化到其内部,当然PC也是需要安装相应的驱动的,固件的编写主要是确定IN和OUT端点,以及一些标志信号。



固件只需要改这些参数,这里我都写好了,大家就不需要再改了,很容易看出我设置的时钟是48MHz,,然后设置
EP2为OUT端点,512字节,4缓冲,bulk (注意OUT,IN都是相对PC来说的,OUT表示PC--->cy7c68013a,IN则相反)
EP6为IN端点,512字节,4缓冲,bulk
flag_a 为EP2的EF,也就是空标志信号,为低时表示空,也就是没有数据过来,为高则表示有数据来了
flag_d 为EP6的FF,也就是满标志信号,为低时表示写满了,这时再去写就是无效写了,为高则表示没有写满,可以继续写。



写好的固件所在文件夹:固件源码什么的都在Firmware文件中



按照上诉步骤进行烧写就可以了,接下来就是FPGA端进行FIFO的读写。
2.4.2.5  FPGA驱动CY7C68013
驱动CY7C68013比较简单,因为大部分协议都由外围PHY完成,所以只需要对FIFO进行操作即可,详细的实现原理可以参考后面章节。
代码Chapter‑1 FPGA驱动CY7C68013代码

//****************************************************************************//  
//# @Author: 碎碎思  
//# @Date:   2019-08-14 20:31:26  
//# @Last Modified by:   zlk  
//# @WeChat Official Account: OpenFPGA  
//# @Last Modified time: 2019-08-14 20:43:45  
//# Description:   
//# @Modification History: 2019-08-14 20:43:45  
//# Date                By             Version             Change Description:   
//# ========================================================================= #  
//# 2019-08-14 20:43:45  
//# ========================================================================= #  
//# |                                                                       | #  
//# |                                OpenFPGA                               | #  
//****************************************************************************//  
//flag_a,flag_d都是默认低电平有效的,flag_a是ep2的EF信号,flag_d是ep6的FF信号  
//flag_a为低时表示ep2里的FIFO为空,也就是没有数据过来,为高则表示非空,表示有数据过来了  
//flag_d为低时表示ep6里的FIFO写满了,这时cypress芯片就会自个打包数据然后去发送pc,为高则表示没有写满,表示可以往ep6的FIFO里写数据  
module  usb(  
        input                   CLCOK                     ,  
        input                   rst_n                   ,  
        //usb interface  
        input                   flag_d                  ,  
        input                   flag_a                  ,  
        output  reg             slwr                    ,  
        output  reg             slrd                    ,  
        output  reg             sloe                    ,  
        output  wire            pktend                  ,  
        output  wire            ifclk                   ,  
        output  reg  [ 1: 0]    FIFO_addr               ,  
        inout   wire [15: 0]    usb_data                ,  
        //receive cmd from pc  
        output  wire            cmd_flag                ,  
        output  wire  [15: 0]   cmd_data                  
);  
//=====================================================================\  
// ********** Define Parameter and Internal Signals *************  
//=====================================================================/  
parameter   CNT_END     =       256                             ;   
parameter   IDLE        =       3'b001                          ;  
parameter   READ        =       3'b010                          ;  
parameter   WRITE       =       3'b100                          ;  
reg     [ 2: 0]                 state_c  /*synthesis noprune*/  ;  
reg     [ 2: 0]                 state_n  /*synthesis noprune*/  ;   
//cnt  
reg     [ 7: 0]                 cnt                             ;  
wire                            add_cnt                         ;  
wire                            end_cnt                         ;   
//======================================================================  
// ***************      Main    Code    ****************  
//======================================================================  
assign  ifclk       =       ~CLCOK;  
assign  pktend      =       1'b1;  
assign  usb_data    =       (state_c[2] == 1'b1) ? {cnt,8'h00} : 16'hzzzz;//先发送低字节,然后再发送高字节  

//cnt  产生写usb数据,从0-255  
always @(posedge CLCOK or negedge rst_n)begin  
if(!rst_n)begin  
        cnt <= 0;  
    end  
else if(add_cnt)begin  
if(end_cnt)  
            cnt <= 0;  
else
            cnt <= cnt + 1;  
    end  
else begin  
        cnt <= 0;  
    end  
end  

assign  add_cnt     =       state_c[2];         
assign  end_cnt     =       add_cnt && cnt == 256-1;     

//state_c  
always@(posedge CLCOK or negedge rst_n)begin  
if(!rst_n)begin  
        state_c <= IDLE;  
    end  
else begin  
        state_c <= state_n;  
    end  
end  

//state_n  
always@(*)begin  
case(state_c)  
        IDLE:begin  
if(flag_a == 1'b1)begin //ep2非空,说明有数据来了,进入读状态  
                state_n = READ;  
            end  
else if(flag_d == 1'b1)begin  //flag_d是EP6的FF,低有效,为高时表示该FIFO目前空闲,往ep6写数据  
                state_n = WRITE;  
            end  
else begin  
                state_n = state_c;  
            end  
        end  
        READ:begin  
if(flag_a == 1'b0)begin //flag_a位低,说明数据已经读完了,进入空闲状态  
                state_n = IDLE;  
            end  
else begin  
                state_n = state_c;  
            end  
        end  
        WRITE:begin  
if(flag_d == 1'b0)begin  //flag_d为低,说明写满了,进入空闲状态,这时cypress自个就会把这些数据打包好,然后发送给pc,  
                state_n = IDLE;  
            end  
else begin  
                state_n = state_c;  
            end  
        end  
default:begin  
            state_n = IDLE;  
        end  
    endcase  
end  

//FIFO_addr,sloe  
always  @(posedge CLCOK or negedge rst_n)begin  
if(rst_n == 1'b0)begin  
        FIFO_addr   =   2'b10;  
        sloe        =   1'b1;  
    end  
else if(state_n[1])begin  
        FIFO_addr   =   2'b00;  
        sloe        =   1'b0;  
    end  
else begin  
        FIFO_addr   =   2'b10;  
        sloe        =   1'b1;  
    end  
end  

//slwr  
always  @(posedge CLCOK or negedge rst_n)begin  
if(rst_n==1'b0)begin  
        slwr    <=  1'b1;  
    end  
else if(state_n[2])begin  
        slwr    <=  1'b0;  
    end  
else begin  
        slwr    <=  1'b1;  
    end  
end  

//slrd  
always  @(posedge CLCOK or negedge rst_n)begin  
if(rst_n==1'b0)begin  
        slrd    <=  1'b1;  
    end  
else if(state_n[1])begin  
        slrd    <=  1'b0;  
    end  
else begin  
        slrd    <=  1'b1;  
    end  
end  

//cmd_flag  
assign  cmd_flag    =   state_c[1] && flag_a;  
assign  cmd_data    =   usb_data;  

reg     [ 9: 0]                 cnt0    /*synthesis noprune*/   ;  
wire                            add_cnt0                        ;  
wire                            end_cnt0                        ;  
//cnt0  用来标记读的数据的个数,注意数据是16位的  
always @(posedge CLCOK or negedge rst_n)begin  
if(!rst_n)begin  
        cnt0 <= 0;  
    end  
else if(add_cnt0)begin  
if(end_cnt0)  
            cnt0 <= 0;  
else
            cnt0 <= cnt0 + 1;  
    end  
else begin  
        cnt0 <= 0;  
    end  
end  

assign  add_cnt0        =       cmd_flag;         
assign  end_cnt0        =       add_cnt0 && cnt0 == 1024-1;  

endmodule  

接下来就是利用官方程序进行调试。



图Chapter‑22 官方USB调试工具使用示例
下面给出写的signal tap 的调试截图
写是一次写512个字节数据,0-255,注意usb的FIFO是一次发送16位的,也就是2个字节。先发送低字节,然后再发送高字节,这我直接把低字节给赋值为0了





图Chapter‑23 signal tap写调试设置





图Chapter‑24 signal tap调试结果
后面局部放大图,注意只有在flag_d为高时,slwr为低才是有效写,否则就是无效写,因为当flag_d为低时,表示写满了,这时FIFO就会弃之后的写数据了。



PC端接受到的数据要2个字节一起读,因为usb是16位发送的,可以看出接受到的数据的确是0000-00FF
注意:pc接受数据按我标的编码顺序执行







图Chapter‑25 上位机接收数据结果
下面轮到“读”出场了。



图Chapter‑26 signal tap读调试设置
pc发送数据按1-->2-->3的步骤,可以看出我们发送了12 34 56 78 这4个字节。





图Chapter‑27 USB上位机写操作
注意这里我是设置了cmd_flag标志信号的,只有cmd_flag为高时的cmd_data的数据才是有效的,也就是pc发送过来的数据。
下面针对数据格式简单解释一下。



Alter 的FIFO ip 是可以读写位宽不一致的,具体看下面的图:



由上图可以看出这个和USB是一样的格式,都是先发低字节,然后再发高字节。或者说先接受低字节,然后再接受高字节。
使用道具 举报
| 来自广东 用Deepseek满血版问问看
cheng98583 | 未知
你的实测速度能到多少?我做的最大才20MBps,根本到不了手册给的速度
用Deepseek满血版问问看
回复
使用道具 举报
26idc | 来自广东
发送数据时怎么连续不间断的发送?buck trans是发送的重复的数据,如果我想发送递增数据该怎么办呢?
回复
使用道具 举报
Jamesonline | 来自北京
很不错
回复
使用道具 举报
快速回复
您需要登录后才可以回帖 登录 | 立即注册

当贝投影