大侠好,欢迎来到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等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。
大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!