源码系列:基于 FPGA 的 USB2.0 设计

2020-12-29 16:46:43 浏览数 (1)

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。

今天给大侠带来基于FPGA的USB2.0设计,附源码,获取源码,请在“FPGA技术江湖”公众号内回复“ USB2.0设计源码”,可获取源码文件。话不多说,上货。

2019年9月4日,USB-IF终于正式公布USB 4规范。它引入了Intel此前捐献给USB推广组织的Thunderbolt雷电协议规范,双链路运行(Two-lane),传输带宽因此提升,与雷电3持平,都是40Gbps。需要注意的是,你想要体验最高传输速度,就必须使用经过认证的全新数据线。USB4保留了良好的兼容性,可向下兼容USB 3.2/3.1/3.0、雷电3。除此之外,USB4将只有USB Type-C一种接口,并支持多种数据、显示协议,包括DisplayPort,可以一起充分利用高速带宽,也支持USB PD供电。

比较遗憾的是,USB4的发布时间暂未公布。值得注意的是,此次发布的USB4是规范,而并非USB4.0。在此之前,USB Implementers Forum(USB-IF)计划取消USB 3.0/3.1命名,统一划归为USB 3.2。其中USB 3.0更名USB 3.2 Gen 1(5Gbps),USB 3.1更名USB 3.2 Gen 2(10Gbps),USB 3.2更名为USB 3.2 Gen 2x2(20Gbps)。

以上就是关于USB标准以及命名的讯息,而今天我们要做设计的是USB2.0,USB2.0的设备是按照2.0的电源标准设计的,设备所需的电流小于500mA,而USB3.0接口可以提供更大的电流,所以用在USB3.0接口是安全的,这就是标准向下兼容的道理。反之,USB3.0的设备,是否能够在USB2.0接口使用,就要查看设备的说明书。通常的电源都是电压源,设备与电源的电压必须相等,而电源的输出电流要大于设备的电流,即电源功率要大于设备功率。

现在大部分USB设备(比如USB接口的鼠标、键盘、闪存、U盘等等)都是采用了USB通用驱动,而你的系统有USB通用驱动的话(比如XP就内建了USB通用驱动)就能用。而有些USB设备是需要特殊驱动的,比如某些手机,连接到电脑的USB口,是需要安装驱动才能使用的。下面我们一起动手做一做,了解一下如何设计。

设计原理

USB(Universal Serial Bus2.0,通用串行总线)是一种应用在计算机领域的新型接口技术。USB接口具有传输速度更快,支持热插拔以及连接多个设备的特点。目前已经在各类外部设备中广泛的被采用。USB接口有三种:USB1.1,USB2.0和USB3.2。理论上USB1.1的传输速度可以达到12Mbps,而USB2.0则可以达到速度480Mbps,并且可以向下兼容USB1.1。

本次设计我们选择一款开发设备,一块廉价的开发板,其中的USB芯片是Cypress的FX2LP系列中的CY7C68013A代,详细的介绍大家可以去Cypress的官网查询。下面简述一下设计思路。

FX2的设计架构如下图,内嵌480MBit/s的收发器,锁相环PLL,串行接口引擎SIE——集成了整个USB 2.0协议的物理层。为适应USB 2.0的480MBit/s的速率,FIFO端点可配置成2,3,4个缓冲区。配置用的是“软配置”——USB固件可由USB总线下载,片上不需集成ROM。拥有四个FIFO接口,可工作在内部或外部时钟下。端点和FIFO接口的应用使外部逻辑和USB总线可高速连接。

基于FX2LP的USB开发,包括三部分:固件程序、驱动、上位机软件。

固件程序我们在kiil中写出来,然后配置到我们的芯片中,固件的开发对于FPGA工程师来说是不用写的,是其他工程师配置好芯片后我们直接拿来使用,其主要的配置过程如下图。

先上电复位,然后初始化寄存器变量,然后调用配置函数,打开中断后,判断是否接受到了配置包,如果接收到了就调用TD_POLL()函数,这个是函数是不停的执行扫描端点等。然后判断芯片是否挂起,如果挂起就叫醒芯片,如果没有就一直调用TD_POLL函数,这样就完成所需要的配置。

这里的项目设计要求是要把FX2配置成从FIFO的模式, 配置为单片机工作时钟24M,端点2输出,字节1024,端点6输入,字节1024,信号全设置为低电平有效等。模块驱动时钟配置成内部输出时钟,也就是让FX2给项目设计当作时钟源,输出一个最大的配置时钟48M的时钟。FX2的数据存储区叫端点,有512、1024字节两个存储大小之分。

从FIFO说明

当有一个与FX2芯片相连的外部逻辑只需要利用FX2做为一个USB 2.0接口而实现与主机的高速通讯,而它本身又能够提供满足Slave FIFO要求的传输时序,可以做为Slave FIFO主控制器时,即可考虑用此传输方式。

Slave FIFO传输的示意图如下:

在这种方式下,FX2内嵌的8051固件的功能只是配置Slave FIFO相关的寄存器以及控制FX2何时工作在Slave FIFO模式下。一旦8051固件将相关的寄存器配置完毕,且使自身工作在Slave FIFO模式下后,外部逻辑(如FPGA)即可按照Slave FIFO的传输时序,高速与主机进行通讯,而在通讯过程中不需要8051固件的参与。

FX2系列的有3种封装方式,我们我的开发板用的是56引脚的封装方式的电路图,其电路图如下所示:

端口介绍

IFCLK:FX2输出的时钟,可做为通讯的同步时钟。

SLCS:FIFO的片选信号,外部逻辑控制,当SLCS输出高时,不可进行数据传输。

SLOE:FIFO输出使能,外部逻辑控制,当SLOE无效时,数据线不输出有效数据。

SLRD:FIFO读信号,外部逻辑控制,同步读时,FIFO指针在SLRD有效时的每个IFCLK的上升沿递增。

SLWR:FIFO写信号,外部逻辑控制,同步写时,在SLWR有效时的每个IFCLK的上升沿时数据被写入,FIFO指针递增。

FD[15:0]:数据线。

FIFOADR[1:0]:选择四个FIFO端点的地址线,外部逻辑控制。

FLAG A,B,C端点的空满标志位。

开发驱动大家可以在网上找,然后根据自己系统装上合适的驱动。上位机软件用的是官方的开发工具,只有如下的安装包,然后安装第一个和第二个就好了。

设计代码

总模块usb:

代码语言:javascript复制
module  usb(pi_clk, clk, pi_rst_n, pi_usb_flagb, pi_usb_flagc, pio_usb_data, 
  po_usb_oe_n, po_usb_wr_n, po_usb_address, po_usb_rd_n, led);

  input clk;
  input pi_clk;
  input pi_rst_n;
  input pi_usb_flagb;          
  input pi_usb_flagc;          
  inout [15:0] pio_usb_data;

  output  po_usb_oe_n;          
  output  po_usb_wr_n;          
  output  po_usb_rd_n;          
  output  [1:0] po_usb_address;  
  output led;
  wire po_rst_n;

  delay delay_dut(
    .pi_clk(pi_clk),
    .pi_rst_n(pi_rst_n), 
    .po_rst_n(po_rst_n)
  );

  usb_wr wr_dut(
    .pi_clk(pi_clk),
    .pi_rst_n(po_rst_n),
    .pi_usb_flagb(pi_usb_flagb), 
    .pi_usb_flagc(pi_usb_flagc), 
    .pio_usb_data(pio_usb_data), 
    .po_usb_oe_n(po_usb_oe_n), 
    .po_usb_wr_n(po_usb_wr_n),
    .po_usb_address(po_usb_address),
    .po_usb_rd_n(po_usb_rd_n)
  );

  my_pll  my_pll_inst (
    .areset ( ~ pi_rst_n ),
    .inclk0 ( clk ),
    .c0 ( c0_sig ),
    .locked ( locked_sig )
  );
  
endmodule

读模块usb_rd:

代码语言:javascript复制
module usb_rd(pi_clk, pi_rst_n, pi_usb_flagb, pi_usb_flagc, pio_usb_data, 
  po_usb_oe_n, po_usb_rd_n, po_usb_address, po_usb_wr_n, led);

  input pi_clk;
  input pi_rst_n;
  input pi_usb_flagb;              //端点2标志信号
  input pi_usb_flagc;                    //端点6标志信号
  inout [15:0]  pio_usb_data;            //输入输出端口

  output reg po_usb_oe_n;                //读标志信号
  output reg po_usb_rd_n;                //写使能
  output reg po_usb_wr_n;                //读使能
  output reg [1:0] po_usb_address;       //端点地址选择
  output reg led;

  reg [15:0] temp_data;
  reg [9:0] count;
  reg [2:0] state;

  assign pio_usb_data = (state == 10) ? 1 : 16'hzzzz;    //读数据,可以一直释放数据总线的控制权

  always @ (posedge pi_clk or negedge pi_rst_n)
    if(!pi_rst_n)
      begin
        state <= 0;
        po_usb_oe_n <= 1;
        po_usb_rd_n  <= 1;
        count <= 0;
        po_usb_wr_n <= 1;
        temp_data <= 0;
      end
    else
      case (state)  
        0  :  state <= 1;

        1  :  begin
              po_usb_address <= 2'b00;  //地址指向端点2
              state <= 2;          
            end

        2  :  if(!pi_usb_flagb)      //判断端点2已经满
              begin
                po_usb_rd_n  <= 0;
                state <= 3;
                po_usb_oe_n <= 0;
              end
            else
              state <= 2;

        3  :  begin  
            if(count < 512 - 1)      //接收1024字节的数据
              begin
                count <= count   1'b1;
              end
            else
              begin
                count <= 0;
                state <= 4;
              end
            if (count == 2)
              begin
                temp_data <= pio_usb_data;
              end
            end

        4  :  begin
              po_usb_rd_n  <= 1;
              po_usb_oe_n <= 1;
              state <= 0;
            end

        default:  state <= 0;
      endcase

  always @ (*)
    if(!pi_rst_n)
      led <= 1;
    else if (temp_data == 16'h33ff)
      led <= 0;

endmodule

写模块 usb_wr:

代码语言:javascript复制
module usb_wr(pi_clk, pi_rst_n, pi_usb_flagb, pi_usb_flagc, pio_usb_data, 
  po_usb_oe_n, po_usb_wr_n, po_usb_address, po_usb_rd_n);

  input pi_clk;
  input pi_rst_n;
  input pi_usb_flagb;            //端点2标志信号
  input pi_usb_flagc;            //端点6标志信号
  inout [15:0] pio_usb_data;            //输入输出端口

  output reg po_usb_oe_n;          //读标志信号
  output reg po_usb_wr_n;          //写使能
  output reg po_usb_rd_n;          //读使能
  output reg [1:0] po_usb_address;    //端点地址选择

  reg [15:0] temp_data;
  reg [2:0] state;

  //在状态的3,拿回数据总线控制全,给写入数据
  assign pio_usb_data = (state == 3) ? temp_data : 16'hzzzz;

  always @ (posedge pi_clk or negedge pi_rst_n)
    if(!pi_rst_n)
      begin
        state <= 0;
        po_usb_oe_n <= 1;
        po_usb_wr_n  <= 1;
        temp_data <= 0;
        po_usb_rd_n <= 1;
      end
    else
      case (state)  
        0  :  state <= 1;

        1  :  begin
              po_usb_address <= 2'b10;  //地址指向端点6
              state <= 2;          
            end

        2  :  if(!pi_usb_flagc)      //判断端点6已经空
              begin
                po_usb_wr_n  <= 0;
                state <= 3;
              end
            else
              state <= 2;

        3  :  if(temp_data < 512 - 1)        //发送1024字节的数据
              temp_data <= temp_data   1'b1;
            else
              begin
                temp_data <= 0;
                state <= 4;
              end

        4  :  begin
              po_usb_wr_n  <= 1;
              state <= 0;
            end

        default:  state <= 0;
      endcase

endmodule

延时模块 delay:

代码语言:javascript复制
module delay(pi_clk, pi_rst_n, po_rst_n);

  input pi_clk;
  input pi_rst_n;

  output reg po_rst_n;

  parameter T1ms = 50000;
  reg [15:0] count;

  always @ (posedge pi_clk or negedge pi_rst_n)
    if(!pi_rst_n)
      begin
        count <= 0;
        po_rst_n <= 0;
      end
    else
      begin
        if(count < T1ms - 1)  
          count <= count   1;
        else
          begin
            count <= count;
            po_rst_n <= 1;
          end
      end

endmodule

上位机测试

安装好驱动和下载的上位机软件,然后在下面的界面中,点击”LGEEPROM”按钮,下载我们写好的的.IIC固件。

然后在下面的页面中会出现先选择other endpt xfers选项中会出现4个端点,然后选择写入的端点或者读的端点执行读写操作。

写的端点是6端点,选择这个端点,写入端点是1024个字节,这里设置的是512字节,也就是写入2次就可以写满了,如下图,和我们编写的代码中写入数据值是一样的。

读操作也就是要读端点2,我们先要给端点一个数,然后才能读我们的端点,我们写入图中显示的数,因为我们设计的是读出的数,如果第三个数位33ff ,就让我们的灯亮,值得一说的是,我们上位机显示的时候是把低位显示到了前面,高位显示到了后面,我们一个包是1024字节,后面的数自动补零,读出数据后可以看到我们的led灯亮,验证出我们的设计正确。

以上就是我们的基于FPGA的usb2.0设计了,希望能给各位大侠起到参考学习作用,此次设计到此结束,有缘再见,告辞。

END

后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。

大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!

0 人点赞