『USB3.0Cypress』Cyusb3014开发(6)控制传输实现

在这里插入图片描述

前言

控制传输实现的原理涉及了比较多的内容,读文之前,应对本专栏以下文章比较熟悉。“USB原理(3)设备连接、传输类型”“USB原理(4)标准请求、传输结构”“Cyusb3014开发(3)FX3内部数据流”“Cyusb3014开发(4)固件代码解析”。在此过程中明确控制传输有什么特点,传输过程分为哪几个阶段,事务、包、域的概念,并由此产生疑问:如何发起控制传输,控制传输的数据是如何打包的?发起控制传输后,固件中的代码是如何处理呢,怎样将其转发到FPGA端呢?FPGA端接收控制传输数据与AN65974接收Bulk传输数据有何异同?带着这些疑问,了解基于Cyusb3014实现控制传输的原理,相信会有醍醐灌顶,茅塞顿开恍然大悟豁然开朗的感觉。

1.控制传输原理

USB传输模型中,所有的传输都是由主机发起,从设备被动响应。控制传输不同于Bulk传输、中断传输、Iso传输,控制传输需要传递参数。在USB连接建立阶段进行描述符枚举过程(进行控制传输),期间会发送很多标准请求。这些标准请求都属于USB命令的一部分,除了标准请求,还有类请求,厂商请求。不同的命令虽然有不同的数据和使用目的,但所有的USB命令结构是一样的。USB命令的结构(偏移量低的先发送)一共5个字段,不同字段含义不同,相互间不同的组合形成了不同的USB命令,其中包含了11个标准命令。厂商请求便是用于自定义控制传输的,本节的任务就是发起控制传输,携带参数(USB命令),具体为打包一个厂商请求的命令帧。
在这里插入图片描述

前面的文章中介绍了事务、包、域的概念,前面提及,包(Packet)是USB系统中信息传输的基本单元(请始终记得这句话),所有数据都是经过打包后在总线上传输的。对于此处的USB命令也不例外,也是打包成包进行传输的。具体怎么回事,从控制传输过程的三个阶段聊起。如下图所示,控制传输过程是分为了三个传输阶段,建立阶段(令牌阶段)、数据传送阶段、握手阶段。当主机发起控制传输时,此为建立阶段,或称为Setup事务。Setup事务与所有事务类型一样是由包(Package)组成的,包有多种类型,具体Setup事务是由令牌包中的Setup包、数据包、以及握手包组成。不同的包是由域字段中的标识域字段标识的。
在这里插入图片描述

对于USB命令,正是在Setup事务阶段完成打包的,Setup事务中的数据包中包含了“自定义的厂商请求命令”,在其中规定了控制传输内容,方向等(即USB命令中的5个字段)。那么需要传输的用户数据,则是在控制传输的第二阶段,数据阶段完成的。也就是说控制传输的参数在第一阶段(建立阶段)的Setup事务中的数据包中打包,而控制传输数据是在第二阶段数据传输阶段进行的。

2.控制传输实现

2.1 上位机实现

从上位机先要发起请求命令,这个请求命令通常是伴随发送(接收)函数一起下发的。使用Qt开发或者自带的上位机软件Control Center均可以实现这个功能。对于Qt端,发起控制传输和Bulk传输确有不同,关于二者的区别,后续讲到Qt篇的时候将会仔细说明,需要在此说明的是,Cypress提供的库cyapi.lib中使用控制端点类中的六个成员属性,来下发配置USB命令中5个字段的具体值,具体是Target、ReqType、Direction、ReqCode、Value、 Index。 也就是说,我们只需要给这五个成员属性赋值,cyapi.lib帮我们完成命令的编码。这里使用Control center完成控制传输的实验,如下图所示。注意这里的字段的具体值要与固件中的设置一致,才能被固件程序响应转发。
在这里插入图片描述

2.2下位机实现

对于FPGA端来说,接收控制传输和Bulk传输的数据并无差别。实现主要取决于固件程序如何将数据转出,关于FPGA的控制时序见后续文章。

2.3固件控制传输相关函数介绍

  • 1.CyU3PUsbRegisterSetupCallback(CyFxSlFifoApplnUSBSetupCB, CyTrue);

/* The fast enumeration is the easiest way to setup a USB connection,
* where all enumeration phase is handled by the library. Only the
* class / vendor requests need to be handled by the application. /
/
This function is used to register a USB setup request handler with the USB driver. The fastEnum parameter specifies whether this setup handler should be used only for unknown setup requests or for all USB setup requests.*/

该函数是一个回调函数,当有request从主机来的时候进入此函数。当设置第二个参数Cyture代表选择fast enumeration模式,这个模式是指库函数将会为你处理一些请求如标准请求,你需要处理的是类/厂商请求。第一个参数则指定了一个入口函数地址,当有类、厂商或者不认识的请求来的时候指定进入此函数地址进行处理。

  • 2.CyFxSlFifoApplnUSBSetupCB(uint32_t setupdat0,uint32_t setupdat1)

/* Callback to handle the USB setup requests. */

该函数是指定的USB请求处理入口,这两个参数中的值代表了USB命令中5个字段的值,具体怎么对应,将在2.5节控制传输的实现中讲到。将在这个函数执行获取控制传输数据的操作。

  • 3.CyU3PUsbGetEP0Data(uint16_t count,uint8_t *buffer,uint16_t *readCount);

/*This function is used to get the OUT data associated with a USB control transfer. */

Count :The length of data to be read in bytes
Buffer:Pointer to buffer where the data should be placed
readCount :Output parameter which will be filled with the actual size of data

该函数将EP0端点的主机OUT数据写入指定的内存空间中,即buffer中,返回实际写入值readCount。当要写回In数据到主机的时候,用到CyU3PUsbSendEP0Data函数。

  • 4.CyU3PDmaChannelSetupSendBuffer (
    CyU3PDmaChannel *handle, //< Handle to the DMA channel to be modified.
    CyU3PDmaBuffer_t *buffer_p //< Pointer to structure containing address, size and status of the DMA buffer to be sent out. */);

/This function initiates the sending of the content of a user provided buffer to the consumer of a DMA channel./

该函数将把指定内存空间中的数据传输到指定的DMA通道上,在使用DMA通道之前,应该先用CyU3PDmaBufferAlloc函数创建一条方向明确的DMA通道。进行发送操作后调用CyU3PDmaChannelWaitForCompletion函数阻塞等待传输完成。

在这里插入图片描述

阅读此小节,请结合数据流中的内容。使用以上1-4函数可以实现收到USB的请求并交给指定函数处理,那么在此处理函数中,可以将控制传输中的数据写入到指定的内存空间中,然后在把该内存空间中的数据转发到指定的DMA通道上,即可完成控制传输从发起到接收、转发在到FPGA的端到端的操作。结合上图,理解为什么选择使用函数4,也就是从指定空间转发到指定DMA通道上面。可以看到,从DMA缓冲区往左也就是数据从缓冲区到GPIF II与之前一样,使用自动DMA通道即可完成,因此FPGA端数据接收不受任何影响。那么对于数据流左侧则完全不一样了,数据从USB总线到了内存中,再由内存到DMA通道,因此DMA通道,缓冲区的大小,UIB套接字等都需要与上图不同的配置才能实现数据流通路。

2.4 控制传输的实现

固件中控制传输的实现基于AN65974做修改,步骤如下:

  • 1.固件程序使用回调机制响应控制请求,在AN65974的工程中,CyFxSlFifoApplnInit函数中调用了回调函数CyU3PUsbRegisterSetupCallback并指定回调处理函数的入口为CyBool_t CyFxSlFifoApplnUSBSetupCB,当上位机下发命令请求时,自动进入这个函数。

  • 2.在CyFxSlFifoApplnUSBSetupCB函数之前,应做以下准备工作。
    第一,自定义请求类型bType。

    #define JY_DE_CODE_TYPE (0xB0)

    第二,在栈区声明一段内存空间,用于存储控制传输数据。

    uint8_t jyEp0Buffer[32];

    第三,在函数CyFxSlFifoApplnStart创建DMA手动通道,用于将数据

//DMA通道为控制传输创建
 
    CyU3PMemSet ((uint8_t *)&dmaCfg, 0, sizeof(dmaCfg));
 
    dmaCfg.size  = 32;//缓冲区大小为32B
 
    dmaCfg.count = 1;//缓冲区个数为1
 
    dmaCfg.prodSckId = CY_U3P_CPU_SOCKET_PROD;//此通道生产者是CPU套接字
 
    dmaCfg.consSckId =CY_FX_CONSUMER_PPORT_SOCKET;//通道消费者是UIB套接字
 
    dmaCfg.dmaMode = CY_U3P_DMA_MODE_BYTE;
 
    dmaCfg.notification = 0;
 
    dmaCfg.cb = NULL;
 
    dmaCfg.prodHeader = 0;
 
    dmaCfg.prodFooter = 0;
 
    dmaCfg.consHeader = 0;
 
    dmaCfg.prodAvailCount = 0;
 
    apiRetStatus = CyU3PDmaChannelCreate (&glChHandleSlFifoUtoP,
 
     CY_U3P_DMA_TYPE_MANUAL_OUT, &dmaCfg);//因需CPU参与数据处理,故创建手动DMA通道
  • 3.在CyFxSlFifoApplnUSBSetupCB函数中做以下几件事。

第一,获取USB厂商请求的五个字段,代码如下,以此分辨用户发起的控制传输。

 /* Decode the fields from the setup request. */
    bReqType = (setupdat0 & CY_U3P_USB_REQUEST_TYPE_MASK);
    bType    = (bReqType & CY_U3P_USB_TYPE_MASK);
    bTarget  = (bReqType & CY_U3P_USB_TARGET_MASK);
    bRequest = ((setupdat0 & CY_U3P_USB_REQUEST_MASK) >> CY_U3P_USB_REQUEST_POS);
    wValue   = ((setupdat0 & CY_U3P_USB_VALUE_MASK)   >> CY_U3P_USB_VALUE_POS);
    wIndex   = ((setupdat1 & CY_U3P_USB_INDEX_MASK)   >> CY_U3P_USB_INDEX_POS);

第二,识别自定义控制传输,并获取控制传输数据到指定的内存空间,并将此空间中的数据转发到指定的DMA通道上。

/***************响应自定义控制请求代码************************/
     if(bType==CY_U3P_USB_VENDOR_RQT)
     {
      isHandled = CyTrue;
      if(bRequest==JY_DE_CODE_TYPE)
      {
      CyU3PUsbGetEP0Data(wLength, &jcEp0Buffer, readout);//获取wLength字节个数据到jcEp0Buffer中
          CyFxaaa(jyEp0Buffer);//my defined
      CyU3PUsbAckSetup ();
       }
     }
  • 4.第3步实现了从指定内存中转发数据到DMA通道上函数的调用,下面补充该函数的实现。
/*************自定义函数 完成DMA从buffer到套接字的传输***************/
void CyFxaaa(uint8_t  *buffer){
CyU3PDmaBuffer_t buf_p;
//用于串口调试打印的状态变量,起到了关键作用
CyU3PReturnStatus_t Info_Debug=CY_U3P_SUCCESS;
buf_p.size = 0x20;    //这里是16进制值 需要注意
buf_p.count = 0x20;
buf_p.buffer = buffer;
buf_p.status = 0;
Info_Debug= CyU3PDmaChannelSetupSendBuffer (&glChHandleSlFifoUtoP, &buf_p);
if(Info_Debug){
     CyU3PDebugPrint(4,"\r\n The status of Send Buffer is %x", Info_Debug);}
Info_Debug=CyU3PDmaChannelWaitForCompletion(&glChHandleSlFifoUtoP,5000);
if(Info_Debug){
    CyU3PDebugPrint(4,"\r\n The status of Send Buffer is %x", Info_Debug);}
}

注意,在使用DMA override模式的时候(上述使用DMA通道的方式即为override方式),禁止使用CyU3PDmaChannelSetXfer。应注释掉,另外做一些描述符上的适配修改,根据自己应用的需求改总线位宽等。

3.传送门

END

🔈文章原创,首发于CSDN论坛。
🔈欢迎点赞❤❤收藏⭐⭐打赏💴💴!
🔈欢迎评论区或私信指出错误❌,提出宝贵意见或疑问❓。


  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FPGA小油条

原创不易,请多支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值