USB 之基于FX3Qt开发上位机(十七)2022-04-15

  • Post author:
  • Post category:其他


1.开发准备

QT官方没有与USB通信相关的封装好的类,cypress官方提供了两个做上位机用的库,一个基于C#的动态链接库CyUSB.dll,另一个是基于C++的CyAPI.lib库。本次开发使用CyAPI.lib,QT+CyUsb3.sys+USB3.0方案实现基于windows10的上位机USB接口配置。这个方案需要安装cypress官方提供的驱动CyUsb3.sys,这个驱动在安装官方套件EZ-USB FX3 SDK后,会在C:\Program Files (x86)\Cypress\EZ-USB FX3 SDK\1.3\driver\bin\win7\x64路径下。另外要注意安装的时候需要将连接线插到PC端的USB3.0接口。

mingw,是Minimalist GNU on Windows 的缩写。它实际上是将经典的开源 C语言 编译器 GCC 移植到了Windows 下,并且包含了 WindowsAPI ,因此可以将源代码编译生成 Windows下的可执行程序。MSVC是微软提供的编译器。cypress提供的官方库在QT中需要使用MSVC的编译器,静态库是以.lib或者以.a结尾的文件,.lib结尾的是MSVC编译器使用的,.a结束的是minGW编译器使用的。安装MSVC的编译器需要安装对应版本的VS,以及windows SDK,这样才能使用MSVC的编译器。本次开发使用minGW编译器,因此需要将官方的库进行重新编译。

minGW版本CyAPI

需要注意,添加了CyAPI库只能使用release模式,不能使用debug模式。另外赛普拉斯提供了CyAPI的使用说明,在SDK安装路径下可以找到,名字为CyAPI.chm。

2.开发环境搭建

具体操作步骤如下:

①新建QT工程,将Cypress文件夹下面的所有文件复制到工程目录下。

②右击工程->选择添加库->外部库->选择刚刚复制到工程目录下的libmingw64cyapi.a文件->选中静态->下一步->完成。可以看到在.pro文件中多出了下面的代码。

③右击工程->选择添加现有文件->选中刚刚复制到工程目录的所有.h文件(共7个)

④添加头文件:

#include<Windows.h>

#include"CyAPI.h"

#include<qdebug.h>

#pragma comment(lib,"SetupAPI.lib")

#pragma comment(lib,"User32.lib")

#pragma comment(lib,"legacy_stdio_definitions.lib")

⑤在.pro文件添加:

LIBS+=-L$$PWD -lSetupAPI64

⑥将项目构建模式切换为release模式。

⑦创建USB设备对象,并调用查看属性与方法进行测试开发环境是否配置成功。

 CCyUSBDevice* pUSB = new CCyUSBDevice;

    int nDeviceCount = pUSB->DeviceCount();

    qDebug()<<"nDeviceCount:"<<nDeviceCount;

    for (int nIdx = 0; nIdx < pUSB->DeviceCount(); nIdx++)

    {

        pUSB->Open(nIdx);

        qDebug()<<"pUSB->DeviceName"<<pUSB->DeviceName;

        qDebug()<<"pUSB->VendorID"<<pUSB->VendorID;

        qDebug()<<"pUSB->ProductID"<<pUSB->ProductID;

}

3.CyAPI简单应用

CCyUSBDevice* pUSB = new CCyUSBDevice; //实例化一个CCyUSBDevice对象;

pUSB->Open(index);//打开设备 

CCyUSBEndPoint *bulkinendpt =pUSB->EndPointOf(addr_in); //自定义端点对象,其值为与 下位机协商好的输入的端点地址,即addr_in=0x81。

bool flag_Recieve=bulkinendpt->XferData(inbuf, len);//使能接收 inbuf指向接收缓冲区,len 返回实际接收字节

CCyUSBEndPoint *bulkoutendpt = pUSB->EndPointOf(addr_out); //使用端点1.out传输

bool flag_Transmit= bulkoutendpt->XferData(outbuf, len);//使能发送 outbuf指向发送缓冲 区,len返回实际发送字节

以上实现了简单的Bulk传输的数据收发。还有几个常用的属性与方法介绍如下:

pUSB->DeviceName//product字符串描述符属性

pUSB->VendorID//vendorID描述符字段属性

pUSB->ProductID//ProductID描述符字段属性

pUSB->DeviceCount();//返回当前在线设备数量

另外该库支持通过上位机烧写固件,对应的几种启动方式均有烧写方法,有需要可以研究。

4.控制传输实现

Cypress提供的库cyapi.lib中使用控制端点类中的六个成员属性,来下发配置USB命令中5个字段的具体值,具体是Target、ReqType、Direction、ReqCode、Value、 Index。 也就是说,我们只需要给这五个成员属性赋值,cyapi.lib帮我们完成命令的编码。控制端点类的这六个成员属性如下:

Target:其有效参数成员为:TGT_DEVICE, TGT_INTFC, TGT_ENDPT 和TGT_OTHER。一般不必关注,固定为:TGT-DEVICE。

ReqType:其有效参数成员为:REQ_STD,REQ_CLASS和REQ_VENDOR。当传输自定义请求时,应该用REQ_VENDOR。

Direction:其有效参数成员有:DIR_TO_DEVICE(表明传输方向为:主机到usb设备(out))和DIR_FROM_DEVICE(表示传输方向为:usb设备到主机(in))。

ReqCode: 八位二进制码值,不同的码值表示USB芯片应该执行的特定功能和命令。

固件中通过下面代码获取到的bRequest值即为此处的ReqCode。这是自定义请求码值,只需上位机按照固件中的配置下发即可。它是除0x00-0x0c和0xa0-0xaf以外的其他值。

 bRequest = ((setupdat0 & CY_U3P_USB_REQUEST_MASK) >> CY_U3P_USB_REQUEST_POS);

Value:其值由ReqCode决定,这里的设定值将传给setupdat的【2:3】位。

Index:其值由ReqCode决定,这里的设定值将传给setupdat的【4:5】位。

基本上,要对USB芯片(固件)进行自定义命令(vendor)传输,通常都是用控制传输进行的。使用控制传输in或者out方式都可以实现对固件的自定义命令传输。我们一般采用out方式实现自定义命令的传输。控制传输的函数有:异步传输函数begindataxfer()和同步传输函数xferdata()以及Write()函数(out传输)和Read()函数(in传输)。控制传输请尽量用同步传输函数xferdata()而不是异步传输函数begindataxfer()。

示例:

 CCyUSBDevice *USBDevice = new CCyUSBDevice(NULL);

// Get a handle to the control endpoint

CCyControlEndPoint *ept = USBDevice->ControlEndPt;

ept->Target    = TGT_DEVICE;

ept->ReqType   = REQ_VENDOR;

ept->Direction = DIR_TO_DEVICE;

ept->ReqCode   = 0x05;

ept->Value     = 1;

ept->Index     = 0;

unsigned char  buf[512];

ZeroMemory(buf, 512);

LONG buflen = 512;

ept->XferData(buf, buflen);

5.同步传输实现

一个同步传输接收实现实例如下:

 CCyUSBDevice *USBDevice = new CCyUSBDevice();

 CCyIsocEndPoint *IsoIn = USBDevice->IsocInEndPt;

if (IsoIn) {

   LONG   bufSize = 4096;

   PUCHAR buffer = new UCHAR[bufSize];

   CCyIsoPktInfo *isoPktInfos;

   int pkts;

  // Allocate the IsoPktInfo objects, and find-out how many were allocated

   isoPktInfos = IsoIn->CreatePktInfos(bufSize, pkts);

  // Request the transfer and identify how much data got transferred

   if (IsoIn->XferData(buffer, bufSize, isoPktInfos)) {

    LONG recvdBytes = 0;

    for (int i=0; i<pkts; i++) {

      if (isoPktInfos[i].Status == 0) {

         recvdBytes += isoPktInfos[i].Length;

       }

     }

   }

  // Free the buffer and IsoPktInfo array

  delete [] buffer;

  delete [] isoPktInfos;

 }

比起BULK传输的实现,在调用xferdata函数时,多了一个可选参数

isoPktInfos





这个参数可以通过


CreatePktInfos


来实例化,其中的lenth属性表示发送的时长,status属性表示数据发送的状态,为0代表



USBD_STATUS_SUCCESS









即所有发送的数据均有效,如果为其他值,可在




Windows Driver Development Kit (DDK)




中查找相应错误值含义。另外注意


在《Cypress开发环境》中写到:maxPktSize=wMaxPacketSize*(1+bMaxBurst)(1+bmAttributes)其中,bmAttributes字段是确定同步端点服务间隔中的最大数据包数(最大为2);bMaxBurst代表一次突发中的数据包个数。对于同步端点,此值可以为0~1024,计算该值的意义在于,同步传输缓冲区的长度(现版本由系统直接设定)以及端点传输的长度应该是8倍的maxPktSize!

6.发送不同长度数据包

FPGA篇讲到对于FPGA来说,短包的时序是在发送数据的最后一个数据字拉低pktend信号;ZLP的时序是slwr信号置高的时候,pktend信号拉低代表了一个零长度包,用以完成特殊长度数据包的发送。对于上位机来说,发送短包就是直接使用函数Xferdata(buf,len)和发送长包数据无区别,将len参数改为0调用Xferdata代表发送了零包。利用短包和零包完成不同长度数据包的有效发送,使下位机标识下行数据通道的FLAG标志能够正常工作,从而确保FPGA正确接收数据。

缓冲区的大小为16KB,发送不同长度的数据该如何操作?(附与赛普拉斯官方工程师交流截图)

  1. 官方定义小于1024字节的数据都是短包。发送短包使用xferdata直接发送即可;
  2. 传输小于16KB的数据X,且X%1024 != 0,那么直接发送即可;
  3. 如果小于16KB的数据X,且X%1024==0,那么需要发送零包;
  4. 如果X==16KB,也是直接发送即可;
  5. 如果X大于16KB,且X%16KB==0,那么直接发送即可;
  6. 如果X大于16KB,且X%16KB != 0,那么需要分成整包和短包,或者发送数据包然后紧跟着发送一个零包。