学习笔记——基于FPGA的USB通信

USB通信

  USB是一种支持热插拔的高速串行传输总线
  USB体系包括“主机”、“设备”以及“物理连接”三个部分。其中主机是一个提供USB接口及接口管理能力的硬件、软件及固件的复合体,可以是PC,也可以是OTG设备,一个USB系统中仅有一个USB主机;设备包括USB功能设备和USB HUB,最多支持127个设备;物理连接即指的是USB的传输线在USB 2.0系统中,要求使用屏蔽的双绞线。

  从逻辑上可以分为功能层(应用软件)、设备层(电脑上的USB接口)和总线接口(线与两个设备之间的连接)层三个层次。其中功能层完成功能级的描述、定义和行为;设备级则完成从功能级到传输级的转换,把一次功能级的行为转换为一次一次的基本传输; USB总线接口层则处理总线上的Bit流,完成数据传输的物理层实现和总线管理。

   物理上,USB设备通过分层的星型总线连接到HOST,但在逻辑上HUB是透明的,各USB设备和HOST直接连接,和HOST上的应用软件形成一对一的关系。
各应用软件——功能设备对之间的通讯相互独立,应用软件通过USB设备驱动程序(USBD)发起IRQ请求,请求数据传输。主机控制器驱动程序(HCD)接收IRQ(数据传输)请求,并解析成为USB传输和传输事务(Transaction),并对USB系统中的所有传输事务进行任务排定(因为可能同时有多个应用软件发起IRQ请求)。主机控制器(Host Controller)执行排定的传输任务,在同条共享的USB总线上进行数据包的传输。

USB协议

是USB数据最小的单位,由若干位组成(至于是多少位由具体的域决定),域可分为七个类型:

  1. 同步域(SYNC) ,八位,值固定为0000 0001,用于本地时钟与输入同步;
  2. 标识域(PID),山四位标识符+四位标识符反码构成,表明包的类型和格式,这是一个很重要的部分,这里可以计算出,USB的标识码有16种;
  3. 地址域(ADDR) :七位地址,代表了设备在主机上的地址,地址:000 0000被命名为零地址,是任何一个设备第一次连接到主机时,在被主机配置、枚举前的默认地址,山此可以知道为什么一个USB主机只能接127个设备的原因。
  4. 端点域(ENDP) ,四位,由此可知一个USB设备有的端点数量最大为16个。
  5. 帧号域(FRAM),(ADDR+ENDP)11位,每一个顿都有一个特定的顿号,顿号域最大容量0x800,对于同步传输有重要意义(同步传输为四种传输类型之一)
  6. 数据域(DATA) :长度为0~1023字节,在不同的传输类型中,数据域的长度各不相同,但必须为整数个字节的长度
  7. 校验域(CRC):对令牌包和数据包(对于包的分类请看下面)中非PID域进行校验的一种方法。
    在这里插入图片描述
    NRZI编码:0变1不变,6个1补0

由域构成的包有四种类型,分别是令牌包数据包握手包特殊包,前面三种是重要的包,不同的包的域结构不同

  1. 令牌包:可分为输入包、输出包、设置包和帧起始包(注意这里的输入包是用于设置输入命令的,输出包是用来设置输出命令的,而不是放据数的)其中输入包、输出包和设置包的格式都是一样的: SYNC+PID+ADDR+ENDP+CRC5 (五位的校验码)
    帧起始包的格式: SYNC+PID+11位FRAM+CRC5 (五位的校验码)
  2. 数据包:分为DATAO包利IDATA1包,当USB发送数据的时候,当一次发送的数据长度大于相应端点的容量时,就需要把数据包分为好几个包,分批发送,DATAO包和DATA1包交替发送,即如果第一个数据包是DATAO,那第二个数据包就是DATA1。但也有例外情况,在同步传输中(四类传输类型中之一),所有的数据包都是为DATAO,
    格式:SYNC+PID+0-1023字节+CRC16。
  3. 握手包:包括ACK, NAK, STALL 以及NYET 四种,其中ACK表示肯定的应答,成功的数据传输; NAK表示否定的应答,失败的数据传输,要求重新传输; STALL表示功能错误或端点被设置了STALL属性; NYET表示尚未准备好,要求等待。
    格式: SYNC+PID
事务

分别有IN事务、OUT事务和SETUP事务三大事务,每一种事务都由令牌包数据包、握手包三个阶段构成,这里用阶段的意思是因为这些包的发送是有一定的时间先后顺序的,事务的三个阶段如下:

  1. 令牌包阶段:启动一个输入、输出或设置的事务
  2. 数据包阶段:按输入、输出发送相应的数据
  3. 握手包阶段:返回数据接收情况,在同步传输的IN和OUT事务中没有这个阶段,这是比较特殊的。

事务的三种类型如下(以下按三个阶段来说明一个事务) :

  1. IN事务:
      令牌包阶段——主机发送一个PID为IN的输入包给设备,通知设备要往主机发送数据;
      数据包阶段——设备根据情况会作出三种反应(要注意:数据包阶段也不总是传送数据的,根据传输情况还会提前进入握手包阶段);
    1)设备端点正常,设备往入主机里面发出数据包(DATAO与DATA1交替);
    2)设备正在忙,无法往主机发出数据包就发送NAK无效包,IN事务提前结束,到了下一个IN事务才继续;
    3)相应设备端点被禁止,发送错误包STALL包,事务也就提前结束了,总线进入空闲状态。
      握手包阶段——主机正确接收到数据之后就会向设备发送ACK包。
  2. OUT事务:
      令牌包阶段——主机发送一个PID为OUT的输出包给设备,通知设备要接收数据;
      数据包阶段——比较简单,就是主机会给设备送数据,DATAO与DATA1交替;
      握手包阶段——设备根据情况会作出三种反应;
      1)设备端点接收正确,设备往入主机返回ACK,通知主机可以发送新的数据,如果数据包发生了CRC校验错误,将不返回任何握手信息;
      2)设备正在忙,无法接收主机发出数据包就发送NAK无效包,通知主机再次发送数据;
      3)相应设备端点被禁止,发送错误包STALL包,事务提前结束,总线直接进入空闲状态。
  3. SETUP事务:
      令牌包阶段——主机发送一个PID为SETUP的输出包给设备,通知设备要接收数据;
      数据包阶段——比较简单,就是主机会设备送数据,注意,这里只有一个固定为8个字节的DATAO包,这8个字节的内容就是标准的USB设备请求命令(共有11条);
      握手包阶段——设备接收到主机的命令信息后,返回ACK,此后总线进入空闲状态,并准备下一个传输(在SETUP事务后通常是一个IN或OUT事务构成的传输)。
传输

传输由OUT、IN、SETUP事务其中的事务构成,传输有四种类型,中断传输批量传输、同步传输、控制传输,其中中断传输和批量转输的结构一样,同步传输有最简单的结构,而控制传输是最重要的也是最复杂的传输。

  1. 中断传输:由OUT事务和IN事务构成,用于键盘、鼠标等HID设备的数据传输中;
  2. 批量传输:由OUT事务和IN事务构成,用于大容量数据传输,没有固定的传输速率,也不占用带宽,当总线忙时,USB会优先进行其他类型的数据传输,而暂时停止批量转输;
  3. 同步传输:由OUT事务和IN事务构成,有两个特殊地方,第一,在同步传输的IN和OUT事务中是没有返回包阶段的;第二,在数据包阶段所有的数据包都为DATAO;
  4. 控制传输:最重要的也是最复杂的传输,控制传输由三个阶段构成(初始设置阶段、可选数据阶段、状态信息步骤),每一个阶段可以看成一个传输,也就是说控制传输其实是由三个传输构成的,用来于USB设备初次加接到主机之后,主机通过控制传输来交换信息,设备地址和读取设备的描述符,使得主机识别设备,并安装相应的驱动程序,这是每一个USB开发者都要关心的问题。

硬件设计

  USB协议比较麻烦,一般选取厂家封装好的芯片使用。 以FT232为例,设备连接的简化图如下图所示。
在这里插入图片描述

FT232

  用FPGA驱动FT232执行USB协议,故程序设计时,只需设计FPGA驱动程序即可。程序的时序图如下图所示。
在这里插入图片描述
  RD时序必须至少晚于DATA 一个时钟周期。

  程序的信号流图如下图所示。
在这里插入图片描述

module usb_loopback_top (
    input               usb_clk_60m,  //FT232输出的60M时钟 
    input               sys_rst_n,    //系统复位 ,低电平
    
    input               usb_rxf_n,    //FT232H数据FIFO可读指示信号 
    input               usb_txe_n,    //FT232H数据FIFO可写信号
    output              usb_oe_n,     //FT232H数据输出使能
    output              usb_rd_n,     //FT232HFIFO读使能信号
    output              usb_wr_n,     //FT232HFIFO写使能信号
    output              usb_siwu_n,   //send immediate/wake up
    
    inout[7:0]          usb_data      //FT232 数据总线
     );     
     
wire [7:0] fifo_data_in;           //从FT232进到FPGA的数据
wire [7:0] fifo_data_out;          //从FPGA输出到FT232的数据
wire       wr_en;                        //FPGA FIFO写使能
wire       rd_en;                        //FPGA FIFO读使能
wire       full;                         //FPGA FIFO写满信号
wire       empty;                        //FPGA FIFO读空信号

assign usb_siwu_n = 1'b1;          //立即发送,唤醒

//USB 同步FIFO读写
usb_rw u_usb_rw(
    .usb_clk_60m           (usb_clk_60m     ),
    .sys_rst_n             (sys_rst_n       ), 
    .usb_rxf_n             (usb_rxf_n       ), 
    .usb_txe_n             (usb_txe_n       ), 
    .usb_oe_n              (usb_oe_n        ), 
    .usb_rd_n              (usb_rd_n        ), 
    .usb_wr_n              (usb_wr_n        ), 
    .fifo_wr_en            (wr_en           ),
    .fifo_rd_en            (rd_en           ),  
    .empty                 (empty           ),  
    .usb_data              (usb_data        ),
    .fifo_data_in          (fifo_data_in     ),
    .fifo_data_out         (fifo_data_out    )
);

//FPGA FIFO调用
fifo_generator_0 u_fifo_generator_0 (
  .clk(usb_clk_60m),      // input wire clk
  .srst(1'b0),            // input wire srst
  .din(fifo_data_in),     // input wire [7 : 0] din
  .wr_en(wr_en),          // input wire wr_en
  .rd_en(rd_en),          // input wire rd_en
  .dout(fifo_data_out),   // output wire [7 : 0] dout
  .full(full),            // output wire full
  .empty(empty)           // output wire empty
);

endmodule
module usb_rw(
    input               usb_clk_60m,   //FT232输出的60M时钟
    input               sys_rst_n,     //系统复位 ,低电平
    //FT232H                                
    input               usb_rxf_n,     //FT232H数据FIFO可读信号 
    input               usb_txe_n,     //FT232H数据FIFO可写信号
    output reg          usb_oe_n,      //FT232H数据输出使能
    output              usb_rd_n,      //FT232H FIFO读使能信号
    output              usb_wr_n,      //FT232H FIFO写使能信号
    inout    [7:0]      usb_data,      //FT232H 双向数据总线
    //FPGA FIFO
    output              fifo_wr_en,    //FPGA FIFO写使能信号
    output              fifo_rd_en,    //FPGA FIFO读使能
    input               empty,         //FPGA FIFO读空信号
    input    [7:0]      fifo_data_out, //FPGA FIFO数据输出
    output   [7:0]      fifo_data_in  //FPGA FIFO数据输入 
    );
     
localparam IDLE   = 0;                //FT232H 空闲
localparam READ   = 1;                //FT232H 读状态,此时数据从FT232H发送到FPGA
localparam WRITE  = 2;                //FT232H 写状态,此时数据从FPGA发送到FT232H

reg[3:0]  state;                      //读写状态
reg usb_oe_n_d0;                      //usb_oe_n下一拍
 
assign usb_rd_n = ((usb_oe_n_d0 == 0) && (usb_oe_n == 0 ))?1'b0 : 1'b1;	//在usb_oe_n为低且在usb_oe_n下一拍也为低时拉低usb_rd_n,其他时候为高
assign fifo_data_in = (state == READ)? usb_data:8'hzz;		//FT232H读状态,将USB数据总线上的值赋给FIFO数据输入,其他时候赋值为高阻态
assign usb_data = (state == WRITE)? fifo_data_out: 8'hzz;	//在FT232H写状态,将FIFO的数据输出赋值给将USB数据总线,其他时候为高阻态
assign usb_wr_n = ((state == WRITE) && (usb_txe_n == 0 )&& (empty == 0))?1'b0 : 1'b1;	//在FT232H写状态,且usb_txe_n为低,且FPGA FIFO 不空时,使能FT232H FIFO写
assign fifo_wr_en = ((usb_oe_n_d0 == 0) && (usb_rxf_n == 0 ))?1'b1 : 1'b0;				//在usb_oe_n下一拍也为低,且usb_rxf_n也为低时使能FIFO写
assign fifo_rd_en =((state == WRITE) && (usb_txe_n == 0 )&& (empty == 0))?1'b1 : 1'b0;	//在FT232H写状态,且usb_txe_n也为低时,且FPGA FIFO 不空时,使能FIFO读

//产生FT232H数据输出使能usb_oe_n
always@(posedge usb_clk_60m or negedge sys_rst_n)begin
    if(!sys_rst_n)
        usb_oe_n<=1;
    else if (!usb_rxf_n)
        usb_oe_n<=0;
    else if(usb_rxf_n)
        usb_oe_n<=1;
end

//FT232H数据输出使能usb_oe_n打一拍
always@(posedge usb_clk_60m or negedge sys_rst_n)begin
    if(!sys_rst_n)
        usb_oe_n_d0 <= 1'b1;
    else 
        usb_oe_n_d0 <=usb_oe_n;
end

//读写状态跳转
always@(posedge usb_clk_60m or negedge sys_rst_n)begin
    if(!sys_rst_n)
        state <= IDLE;
    else
        case(state)
            IDLE:begin 
                if(usb_rxf_n == 1'b0) //usb_rxf_n拉低,下一时钟进入FT232H读
                    state <= READ;    //usb_txe_n拉低且FPGA FIFO不空进入FT232H写
                else if(usb_txe_n == 1'b0 && empty == 1'b0)
                    state <= WRITE;
            end
            READ: begin              //usb_rxf_n拉高,从FT232H读回到空闲状态
                if(usb_rxf_n == 1'b1)
                    state <= IDLE;
            end
            WRITE: begin            //usb_txe_n拉高或者FPGA FIFO被读空,回到空闲状态
                if(usb_txe_n == 1'b1 || empty == 1'b1)  
                    state <= IDLE;
            end
            default:
                state <= IDLE;
        endcase
end 

endmodule

学习参考正点原子达芬奇FPGA开发板A7系列视频:
https://www.bilibili.com/video/BV1254y1m7HN?spm_id_from=333.337.search-card.all.click

cda备考学习学习笔记——基础知识篇(二)主要涉及了计算机科学与技术领域的基本概念和知识。 首先,它介绍了计算机网络的基础知识。网络是将多台计算机通过通信链路连接起来,使它们能够相互通信和共享资源的系统。笔记中详细介绍了网络的组成、拓扑结构和通信协议等重要内容。 其次,笔记还解释了计算机系统的基本组成。计算机系统由硬件和软件两部分组成,其中硬件包括中央处理器、存储器、输入输出设备等,而软件则分为系统软件和应用软件。笔记详细介绍了各种硬件和软件的功能和作用。 此外,笔记还对数据库管理系统进行了介绍。数据库管理系统是一种用于管理和组织数据的软件系统,它能够实现数据的存储、检索和更新等操作。笔记中详细介绍了数据库的概念、结构和操作等内容。 最后,笔记还包括了算法和数据结构的基础知识。算法是解决问题的一系列步骤和规则,而数据结构则是组织和存储数据的方式。笔记中介绍了常用的算法和数据结构,如排序算法、树和图等。 总之,通过学习CDA备考学习笔记中的基础知识篇(二),我们能够更好地理解计算机网络、计算机系统、数据库管理系统以及算法和数据结构等相关概念和知识。这些基础知识对于我们深入研究计算机科学与技术领域是非常重要的,也为我们日后的学习和工作奠定了坚实的基础。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值