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,发送不同长度的数据该如何操作?(附与赛普拉斯官方工程师交流截图)
- 官方定义小于1024字节的数据都是短包。发送短包使用xferdata直接发送即可;
- 传输小于16KB的数据X,且X%1024 != 0,那么直接发送即可;
- 如果小于16KB的数据X,且X%1024==0,那么需要发送零包;
- 如果X==16KB,也是直接发送即可;
- 如果X大于16KB,且X%16KB==0,那么直接发送即可;
- 如果X大于16KB,且X%16KB != 0,那么需要分成整包和短包,或者发送数据包然后紧跟着发送一个零包。