今天给大侠带来基于FPGA的USB接口控制器设计(VHDL),由于篇幅较长,分三篇。今天带来第三篇,下篇,FPGA固件开发、USB驱动和软件开发。话不多说,上货。
这里也给出前两篇的超链接:
之前有关于VeriliogHDL实现的USB接口控制器设计,这里放上超链接,仅供各位大侠参考。
导读
2019年9月4日,USB-IF终于正式公布USB4规范。它引入了Intel此前捐献给USB推广组织的Thunderbolt雷电协议规范,双链路运行(Two-lane),传输带宽因此提升,与雷电3持平,都是40Gbps。需要注意的是,你想要体验最高传输速度,就必须使用经过认证的全新数据线。USB4保留了良好的兼容性,可向下兼容USB3.2/3.1/3.0、雷电3。除此之外,USB4将只有USBType-C一种接口,并支持多种数据、显示协议,包括DisplayPort,可以一起充分利用高速带宽,也支持USBPD供电。
现在大部分USB设备(比如USB接口的鼠标、键盘、闪存、U盘等等)都是采用了USB通用驱动,而你的系统有USB通用驱动的话(比如XP就内建了USB通用驱动)就能用。而有些USB设备是需要特殊驱动的,比如某些手机,连接到电脑的USB口,是需要安装驱动才能使用的。下面我们一起动手做一做USB接口控制器设计,了解一下如何设计。
六、FPGA固件开发
6.1固件模块划分
在本例中,固件开发指的就是FPGA开发,也就是使用硬件描述语言(VHDL或者VerilogHDL)编写FPGA内部程序。FPGA的作用就是和PDIUSBD12进行通信,从PDIUSBD12中获取数据并且根据主机的要求发送数据。PDIUSBD12和FPGA之间的通信就是8位数据总线加上若干控制信号(A0、WR_N、RD_N等),只要控制FPGA产生符合PDIUSBD12输入/输出时序的脉冲,即可实现两者之间的通信。
FPGA固件的模块图如图34所示,各个模块的功能如下。
(1)分频器模块
(2)沿控制器模块
PDIUSBD12的读写操作都各自有一个读写控制信号WR_N和RD_N,每次读写操作都在对应的控制信号的下降沿触发,沿控制模块的功能就是可控地产生一个下降沿信号,用于控制读写操作。
(3)输入/输出切换模块
输入/输出切换模块在整个系统中非常重要,因为FPGA芯片和PDIUSBD12芯片之间的数据总线是双向的总线,所以当读写操作之一在进行的时候另一个操作的信号源必须关闭,否则就会造成双驱动,这不但不能得到正确的数据还会损害芯片。输入/输出切换模块的功能就是根据当前的读写状况控制信号源,保证在一个时刻只有一个信号源在驱动总线。
(4)设备收发器模块
这个模块是整个固件的核心模块,它完成的工作包括配置PDIUSBD12芯片、处理PDIUSBD12产生的中断、完成从缓存读取数据,并且根据需要将数据通过PDIUSBD12发送。设备收发器模块完成对每个主机请求的解析工作,此外,还要将解析完成的请求数据传递给请求处理模块。
(5)请求处理模块
请求处理模块的作用是接收设备收发器模块解析完成的主机请求,并且决定如何处理此请求。
模块划分完毕之后就可以使用ISE创建工程了,然后就各个模块分别编写实现代码和测试平台,最后将所有模块整合起来作为一个实体并且对其进行仿真、测试,这样就是一次完整的FPGA开发过程。
6.2自定义包编写
在实际实现各个模块功能之前,首先需要编写两个自定义包,分别是USB包和PDIUSBD12包。
请求类型以及请求的代码定义如下:
鉴于篇幅以及其他原因,以上仅仅介绍USB包和PDIUSBD12包的部分内容作为参考。
6.3分频器模块的实现
分频器模块实现的基本原理就是设计一个工作在系统时钟下的计数器,循环地递减或者递加计数,在某个计数的固定值将输出翻转,即可实现时钟分频的功能。
例如,实验板上的系统时钟是50MHz,而所需的读写周期间隔要求大于500ns,即读写的时钟频率不能高于2MHz,需要将原系统时钟进行至少25倍分频。所以,我们设定一个计数器,工作在系统时钟下,每个系统时钟周期计数减一,减到零后恢复到13,这样,每经过13×2=26个系统时钟周期,计数器的输出会是一个完整的周期。
分频器模块的示意图如图35所示。
实现分频器模块的代码如下:
沿控制模块的功能是提供可控的下降沿输出,实现的方案如下:用一个使能信号CE_N控制输出。输入为分频后的时钟,当CE_N输入为高的时候,输出保持高电平,而当CE_N输入变为低的时候,将时钟接到输出上,这样就能得到连续的下降沿信号(和时钟的下降沿同步)。只要对CE_N进行适当的控制,就能得到需要的下降沿。
沿控制模块的示意图和时序图如图36所示。输入时钟连接到分频器模块的输出时钟上,使能信号控制沿输出信号,只要在某一个时钟周期内将使能信号保持低电平,就可以得到一个下降沿输出。
沿控制模块的实现代码如下:
6.5输入/输出切换模块的实现
由于PDIUSBD12的8位数据线是双向总线,所以当进行读写操作的时候,应该注意避免双驱动。双驱动的意思就是在总线两边同时往总线上加输出信号,这样总线数据就处于一种不定态(用X表示),并且还容易损坏器件。例如,没有处理好双驱动的仿真波形就会如图37所示,这种情况下无法得到正确的数据的。
图37仿真不定态时序图
信号的4种基本状态是高电平(1)、低电平(0)、不定态(X)和高阻态(Z),当一个总线上同时加有两个信号时,组合起来的结果如表35所示。
表35信号状态表
可见,当一个总线上同时有两个驱动的时候,很有可能产生不定态X,但是如果其中一个信号为高阻态Z的话,则是一个确定的状态(即另一个信号的状态)。所以,避免双驱动的基本思想就是根据目前的读写状态关闭某一个驱动源,也就是说将其另一个驱动源输出设置为高阻态。由于读写操作是由各自的控制信号(WR_N、RD_N)控制的,所以可以将这两个信号作为互斥关系的信号来控制总线数据的信号源。例如,当RD_N为低时,要从PDIUSBD12读取数据,就应该关闭FPGA对总线的输出,即将FPGA的总线输出信号变为高阻态Z。反过来也一样,当WR_N为低时,要向PDIUSBD12发送数据,此时PDIUSBD12也会自动关闭它在总线上的输出。以上思想可用公式表示为:
输入/输出切换模块的示意图如图6-38所示。其中左边的总线表示连接到PDIUSBD12的总线,右边的输入、输出总线是在FPGA内部的总线信号,表示在FPGA内部将总线的输入和输出区分开来;RD_N和WR_N信号分别用于读、写控制。
图38输入/输出切换模块的示意图
输入/输出切换模块的实现代码如下:
请求处理模块的功能是根据主机的请求控制设备收发器模块的处理状态。在本例中,请求处理模块实际的功能就是根据目前接收到的主机请求控制设备收发器模块发送数据,所以请求处理模块的实现就是一个简单的状态机。
请求处理模块的示意图如图39所示。时钟信号是由分频器的输出时钟提供;请求类型输入是一个8位端口,它和接收事件输入协同工作,当设备收发器接收到一个请求时,就会将请求代码发送到请求类型输入端口,在接收事件输入端口输出一个时钟周期的低电平,表示一次新的请求处理;命令输出端口和命令中断端口则用于控制设备收发器模块的操作状态。
图39请求处理模块的示意图
请求处理模块的实现代码如下:
6.7设备收发器模块的实现
设备收发器模块是整个固件系统的核心,实现的基本思想是创建一个状态机,将各个处理操作都作为一个状态处理,在每个状态中按照PDIUSBD12的时序要求对其进行数据访问和控制。
设备收发器模块的示意图如图40所示。
由于USB协议很复杂并且PDIUSBD12的控制也比较复杂,所以设备收发器状态机的状态量会较多。根据设备收发器的功能,可以将状态机各个状态的功能分为3类。
·初始化器件:初始化器件就是对PDIUSBD12器件进行配置的状态,需要配置的内容包括设置地址/使能、设置DMA以及设置模式等。
·数据访问:数据访问即实现PDIUSBD12和FPGA之间的数据读写,包括读取中断寄存器、读取前次传输状态、由端点读取数据、由端点发送数据等。
·请求回复:请求回复是指根据各种类型请求的数据格式提取所需要的数据,并且在解析完成后通知请求处理模块。下面详细介绍一下以上3种状态的实现。
1)初始化器件
由于对寄存器配置的命令以及时序都是确定的,所以可以在自定义包中将配置数据定义为常数,例如:
constantD12_CONNECT_DATA:REG8x8:=(D12_COMMAND_SET_DMA,D12_DMA,D12_COMMAND_SET_MODE,D12_MODE_CONFIG,D12_MODE_CLOCK_DIV,others=>X'00');constantD12_CONNECT_DATA_TYPE:REG8x1:=(D12_COMMAND,D12_DATA,D12_COMMAND,D12_DATA,D12_DATA,others=>'0');constantD12_CONNECT_DATA_LENGTH:INTEGER8:=5;上面定义的就是PDIUSBD12的配置参数,第一个常数数组是配置命令和数据,第二个数组表示命令、数据的顺序,最后一个参数是配置参数的总长度。定义的过程是首先向PDIUSBD12发送命令D12_COMMAND_SET_DMA(设置DMA命令),然后发送此命令的数据D12_DMA(D12_DMA定义为0xC0,其意义请参考图23);之后发送设置模式命令和此命令的两个数据。D12_COMMAND_SET_DMA、D12_DMA、D12_COMMAND、D12_DATA等都是已定义的常数,例如:
详细的常数定义请参考PDIUSBD12包的定义文件。这样定义虽然显得复杂,但是便于将数据与格式分离,也便于代码阅读。此外,在调用配置数据时也较为方便,只需要使用一个循环索引变量,依次读取D12_CONNECT_DATA数组和D12_CONNECT_DATA数组的数值,发送给PDIUSBD12即可,代码如下:
--TS_CONNECT状态,对PDIUSBD12进行配置whenTS_CONNECTING=>--handle_step作为循环变量ifhandle_step=D12_CONNECT_DATA_LENGTHthents_state<=TS_IDLE;elsedata_out<=D12ConnectData(handle_step);a0<=D12ConnectDataType(handle_step);wr_n_var:='0';--wr_n_var置为低表示向PDIUSBD12输出endif;handle_step:=handle_step+1;以上代码运行的结果就是经过5个时钟周期,FPGA完成向PDIUSBD12输出的一系列命令以及数据,通过编写测试平台仿真可以看到运行的结果(测试平台的编写将会在下面专门介绍),如图41所示。
图41器件配置仿真时序图
通过上面的时序图可以看出,8位总线上传输的是D12_CONNECT_DATA定义的配置命令和数据,而a0位表明了总线上的是命令还是数据,通过一个下降沿的写信号可以将命令或者数据发送给PDIUSBD12。
2)数据访问状态
数据访问状态的功能简单地说就是中断监测和数据收发。每次系统复位后FPGA会自动配置PDIUSBD12器件,配置完成之后设备收发器模块会处于空闲状态(TS_IDLE)。PDIUSBD12器件在接收到数据包时会通过中断来通知设备收发器,此外,请求处理模块也会通过命令中断信号控制设备收发器模块。所以,中断监测就是在每个时钟周期读取一次PDIUSBD12的中断信号和请求处理模块的命令中断信号,如果发现其中的一个中断信号为低,则转为其他状态。
中断监测的代码如下:
当监测到PDIUSBD12的中断时,设备收发器首先读取中断寄存器,然后就会进入数据收发状态,如果监测到的是请求处理模块的命令中断,则进入的是请求回复状态。请求回复状态包括了发送描述符、发送配置信息等,这些内容将在下面一个小节介绍。数据收发状态包括读取中断寄存器、控制端点数据收发等。读取中断寄存器的流程图如图42所示。
图42中断处理流程图
读取中断寄存器的代码如下:
--读取中断寄存器状态whenTS_READ_IR=>--第一步,发送读取中断寄存器命令ifhandle_step=0thena0<=D12_COMMAND;data_out<=D12_COMMAND_READ_IR;wr_n_var:='0';--第二步,设置读信号为低,读取第一个返回参数,即中断寄存器第一个字节elsifhandle_step=1thena0<=D12_DATA;rd_n_var:='0';--第三步,保存中断寄存器第一个字节并读取第二个返回参数(中断寄存器第二个字节)elsifhandle_step=2then--保存中断寄存器第一个字节ir_0:=data_in;--读取第二个参数a0<=D12_DATA;rd_n_var:='0';--最后,保存第二个参数,进入下一处理状态else--保存中断寄存器第二个字节ir_1:=data_in(0);--根据中断寄存器选择进入下一处理状态ts_state<=GetInterruptHandler(ir_0,ir_1);ih_state<=IH_START;endif;handle_step:=handle_step+1;下面介绍一下控制输出的处理流程。控制输出的输出是相对主机来说的,所以相对于设备来说,就是接收主机的数据。当一次控制输出发生时,设备首先会判断接收到的是不是建立包(SetupPacket),如果是则开始接收下面的数据,否则,接收前次传输所剩余的数据。控制传输的处理流程图如图43所示。
图43控制输出流程图
从上面的流程图可以看出,设备收发器首先要选择控制输出端点,提取建立包的内容,再进行端点是为满还是空的判断。如果控制端点不为空,设备收发器将从缓冲区读出内容并将其保存。之后,它将判断设备请求的有效性,如果是一个有效的请求,设备收发器必须向控制输出端点发送应答建立命令以重新使能下一个建立阶段。
接下来,设备收发器需要证实控制传输是控制读还是写。这可以通过读建立包中bmRequestType的第8位来判断。如果控制传输是一个控制读类型,那就是说器件需要在下一个数据阶段向主机发回数据包。设备收发器会设置一个标志以指示设备现在正处于传输模式,即准备在主机发送请求时进入传输状态(TS_EP0_TRANSMIT)向主机发送数据。
处理流程的各个步骤在设备收发器模块中被划分在两个状态中实现,其中选择端点和读取、保存数据的操作在TS_READ_ENDPOINT状态中实现,其他的内容在TS_EP0_RECEIVE状态中实现。下面是从端点(PDIUSBD12的缓冲)数据读取的实现代码,即TS_READ_ENDPOINT状态的代码,由于篇幅原因,这里只提供部分参考代码。
下面介绍一下控制输入的处理过程。控制输入就是设备向主机发送数据,最为典型的就是设备向主机发送描述符,图44所示是控制输入的流程图。
从控制输入的流程图可以看出,设备收发器首先需要通过读PDIUSBD12的最后处理状态寄存器清零中断标志位。接着设备收发器在确认PDIUSBD12处于传输模式后进行数据包的发送。PDIUSBD12的控制端点只有16字节FIFO,如果传输的长度大于16字节,设备收发器在传输阶段就必须控制数据的数量。设备收发器必须检查要发送到主机的当前和剩余的数据大小,如果剩下的字节数大于16,设备收发器将先发送16字节并继续等待下一次发送。
当下一个数据发送中断来到时,设备收发器将确定剩余的字节是否为零。如果已经没有数据要发送,设备收发器需要发送一个空的包以指示主机数据已经发送完毕。
控制输入是在TS_EP0_TRANSMIT和TS_WRITE_ENDPOINT两个状态中实现的。其中,TS_EP0_TRANSMIT实现的是控制输入流程控制,而TS_WRITE_ENDPOINT的实现和TS_READ_ENDPOINT很类似,只不过是将读取数据换为发送数据。TS_WRITE_ENDPOINT状态的实现代码如下,由于篇幅原因,这里只提供部分参考代码。
图45发送设备描述符仿真波形1
图46发送设备描述符仿真波形2
3)请求回复状态
请求回复状态的功能就是对各个请求作出响应。USB的标准请求已经在前面做了介绍,下面就以获取描述符请求为例介绍一下请求响应的实现方法,其他的标准请求以及厂商请求(获取、设置密码)相对来说比较简单,实现的方法请读者参考源代码。
获取描述符请求是最为重要的请求,因为这在设备枚举过程中是必需的,它是主机了解设备的第一个步。获取描述符请求的处理流程如图47所示。
图47获取描述符处理流程
获取设备描述符请求响应的实现代码如下:
6.8测试平台的编写
上面介绍的是整个FPGA固件系统的实现方法,为了验证设计的正确性,还需要编写一个测试平台对整个系统进行仿真。由于实际情况下FPGA是和PDIUSBD12进行通信,所以在测试平台中需要虚拟一个PDIUSBD12,来实现仿真的目的。
再次,需要处理好总线双驱动的问题。前面介绍的输入/输出选择模块的功能就是在必要的时候关闭总线输出来避免双驱动的发生,同样道理,在测试平台中也应该做到这一点,即当测试平台向FPGA固件系统读取数据时,应该关闭测试平台的总线输出,即将其设置为高阻。实现代码如下:
process(d12_wr,td_index)begin--当FPGA向PDIUSBD12些数据时,总线输出变为高阻ifd12_wr='0'thendata<='ZZZZZZZZ';elsedata<=td(td_index);endif;endprocess;最后,还需要编写一个主流程,在主流程中需要进行系统复位和产生中断信号,代码如下:
七、USB驱动和软件开发
7.1USB驱动编写
以上介绍的是FPGA固件的开发过程,由于本例中设计的不是一个类设备,所以要使设备正常工作,还需要编写专门的驱动程序和软件。由于驱动和软件不是本篇的重点,故下面只简要介绍其编写方法。
1)USB驱动模型
USB体系的主机软件可分为两层,即USB系统软件和客户端驱动程序,如图48所示。
USB系统软件根据功能可以分为USBD和HCD上下两部分,其中HCD为上层提供了主机控制器的抽象以及数据在总线上的传输抽象。USBD为上层的客户端驱动程序提供了USB设备的抽象,并在客户端驱动和所驱动的设备之间提供了数据传输的抽象。
客户端驱动程序从用户的角度来讲相当于传统意义上的驱动程序。不过设备端不同的接口对应不同的驱动程序,如果设备只有一个接口,那么从用户的角度来讲,两者是一样的,客户端驱动程序通过USB系统软件提供的接口与设备交互,而不是通过过去的I/O地址或者端口进行访问。
2)使用DriverStudio开发USB驱动
上面介绍的是USB软件模型,对于驱动开发人员来说,需要编写的就是客户端驱动程序。编写客户端驱动程序需要安装DDK,即WindowsDriverDevelopmentKit,通过DDK我们就能够访问USB系统软件的接口从而实现与设备的交互。但是,如果只使用DDK开发驱动程序的话,会比较复杂,所以可以使用一些驱动开发的专用工具,例如DriverStudio、WinDriver等。本例选用的是DriverStudio2.7进行开发,下面介绍一下开发的基本步骤。安装完DDK以及DriverStudio后,运行DriverStudio的DriverWizard。在第1步中输入驱动工程名称和路径,如图49所示。单击Next按钮进入如图50所示对话框。
图49DriverWizard第1步
图50DriverWizard第2步
第2步选择工程类型WDMDriver,单击Next按钮进入如图51所示对话框。
第3步选择驱动类型WDMFunctionDriver。单击Next按钮进入如图52所示对话框。
图51DriverWizard第3步
第4步比较重要,是选择驱动总线类型,应该选择USB(WDMOnly),并且注意要在USBVendorID和USBProductID中输入和固件中设备描述一致的信息。这里请注意VendorID一定是0x0471,因为使用的是Philips的PDIUSBD12芯片,其VendorID固定为0x0471。单击Next按钮,进入如图53所示对话框。
第5步是端点定义,可以根据需要定义端点的类型(输入输出)、端点号、缓存大小等。
第6步到第9步是一些开发辅助信息的定义,可以保持为默认值,如图54~图57所示。
图54DriverWizard第6步
图55DriverWizard第7步
图56DriverWizard第8步
图57DriverWizard第9步
第10步是设备类的定义,如图58所示。定义打开设备的方式,SymbolicLink表示按照设备名称打开,Interface(WDMOnly)表示按照设备的GUID打开,这里选择使用设备名称打开。
图58DriverWizard第10步
第11步定义的是设备的IO控制接口,也就是驱动和应用程序之间的接口,如图59所示。单击Add按钮可以定义IO控制接口,如图60所示。
图59DriverWizard第11步
图60定义IO控制接口
最后,第12步进行一些额外的设置,如图61所示,可以保持默认值。
图61DriverWizard第十二步
以上便是使用DriveStudio的DriverWizard生成驱动框架的完整过程,现在我们已经有了一个完成了大部分驱动工作的代码框架,只需要增加一些自定义的处理代码即可。
3)使用VisualC++编译驱动
运行VisualC++6.0打开DriverWizard生成的工程文件,可看到在***Device这个类中已经有了很多设备操作的处理函数,例如上电(OnDevicePowerUp)、休眠(OnDeviceSleep)启动(OnDeviceStart)等,可以根据需要修改这些函数,如果没有特殊要求,可以保持默认设置,如图62所示。
图62设备操作处理函数
另外还需要完成的工作就是对上面定义的IO控制接口函数进行处理,其功能就是建立一个厂商请求。由于本次设计的USB设备是一个加密设备,它不是类设备,所以会有一些特定的请求(厂商请求)。为了介绍厂商请求的实现方法,本系统用到了两个厂商请求:设置密码和获取密码。由DriverWizard自动生成的驱动一般都已经包括了标准请求的建立,但是不会包括厂商请求的建立。厂商请求是在IO控制接口函数中建立的,即DriverWizard第11步所定义的两个函数,建立厂商请求的函数主要是BuildVendorRequest函数,其格式如下:
PURBBuildVendorRequest(PUCHARTransferBuffer,ULONGTransferBufferLength,UCHARRequestTypeReservedBits,UCHARRequest,USHORTValue,BOOLEANbIn=FALSE,BOOLEANbShortOk=FALSE,PURBLink=NULLUCHARIndex=0,USHORTFunction=URB_FUNCTION_VENDOR_DEVICE,PURBpUrb=NULL);其中需要开发人员注意的是前6个参数,其意义如下:
·PUCHARTransferBuffe数据缓冲。如果是数据输入,用于存储接收到的数据;如果是数据输出,则是待发送数据的数据源;如果没有数据传输,此参数可是为空(NULL)。
·ULONGTransferBufferLength发送或者接收数据的长度。
·UCHARRequestTypeReservedBit请求类型的位掩码,一般为零。
·UCHARRequest请求代码。
·USHORTValue即USB请求中的wValue位
·BOOLEANbIn=FALSE此参数为TRUE表示数据输出,反之则表示数据输入。
其余的参数可以保持默认。下面就从USBSOFTLOCK_IOCTL_GET_PASSWORD_Handler处理函数为例介绍一下BuildVendorRequest函数的用法,代码如下:
完成厂商请求的编写之后,就可以进行驱动程序编译了。驱动编译默认有两种版本,即Win32Checked和Win32Free,其中前者表示调试版本,而后者表示发布版本,发布版本相对调试版本去掉了大部分调试信息,比较简化。
编译驱动的方法是在VisualC++中打开DriverStudio的工具条CompuwareDriverStudio,如图63所示。
选择合适的编译版本,再单击CompuwareDriverStudio工具条的最后一个按钮即可。请注意不能使用VisualC++本身的编译按钮进行驱动编译。编译成功,如果是Win32Free版本,则会在工程目录的sys\objfre\i386子目录下生成驱动文件USBSoftLock.sys;如果是Win32Checked版本,驱动文件会在工程目录的sys\objchk\i386子目录下。成功编译驱动程序之后,将它和DriverStudio自动生成的.inf文件(在工程目录下)放在同一个目录下,在查找驱动的时候指定这个目录就可以了。
7.2USB软件编写
最后,再简要介绍一下USB软件的编写,即软件对USB设备访问的实现方法。
USB软件通过USB驱动实现对USB设备的访问,编写USB软件必须符合USB驱动定义的接口规范。一般来说,使用DriverWizard生成一个驱动工程后,会同时生成一个***ioctl.h的文件,这个文件就是建立软件和驱动之间通信的桥梁,它定义了访问驱动程序的接口,在编写软件的时候需要将其引用进去。
USB软件的编写一般有下面几个步骤。
1)打开设备
打开设备主要需要调用CreateFile函数,它将设备作为一个文件来处理,代码如下:
BOOLCSoftLock::OpenDevice(){if(m_hDevice!=INVALID_HANDLE_VALUE)returnTRUE;constchar*sLinkName='\\\\.\\USBSoftLockDevice0';m_hDevice=CreateFile(sLinkName,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);returnm_hDevice!=INVALID_HANDLE_VALUE;}2)调用设备IO接口
调用设备IO接口使用DeviceIoControl函数控制设备。这里主要用到两次DeviceIOControl函数,即设置密码和获取密码,它们分别对应驱动中已经定义的IO控制接口函数。例如,设置密码接口函数的调用方法如下:
3)关闭设备
和打开设备对应,关闭设备就是调用CloseHandle函数关闭设备的句柄就可以了,例如:
voidCSoftLock::CloseIfOpen(){if(m_hDevice!=INVALID_HANDLE_VALUE){//Closethehandletothedriverif(!CloseHandle(m_hDevice)){printf('ERROR:CloseHandlereturns%0x.\n',GetLastError());}m_hDevice=INVALID_HANDLE_VALUE;}}USB软件的详细代码请参考源代码中的cube测试程序,它模拟了一个硬件加密设备的工作过程。cube程序运行后会出现一个立方体,使得立方体转动表示正常的程序运行状态。程序运行需要密码,但是密码不是保存在计算机上,而是保存在USB设备上,并且程序运行时需要及时校验密码,一旦密码校验失败(可能是因为密码不正确或者USB设备被移除),程序都会停止运行。方法是首先选择菜单File—>OpenDevice打开USB设备(如图64所示),如果打开设备成功,选择File—>PlayCube,在出现的密码输入框内输入密码,如果密码正确,立方体就会开始转动,并且cube程序在不时地和USB设备之间进行密码校验(可以看到PDIUSBD12的GOODLINK灯会不停的闪,这表示有数据传输)。还可以通过选择File—>SetPassword设置密码,此密码会通过SetPassword请求发送给设备。
总结
首先,对USB协议的了解是最为重要的。虽然PDIUSBD12芯片能够完成很多协议解析工作,但对USB协议的了解程度还是对整个开发过程起到了决定性的作用。USB协议非常的复杂,熟悉USB协议的方法应该是由大到小,即首先了解USB通信的基本原理,比如控制传输、批量传输的原理和特点;然后再了解各个传输的组成,即每个传输首先发送的是什么数据包,然后接受的是什么数据包;最后再去分析每个数据包的格式、意义等。
其次,需要对PDIUSBD12芯片的比较了解,比如它的各个信号引脚的功能、特性,更为重要的是其通信时序和控制命令。
最后,对各种语言以及各种开发工具熟悉也是非常重要的。在本次设计中,需要用到的开发语言很多,包括VHDL、C++(VisualC++);此外,本次设计还用到了多种开发工具,包括EDA开发、驱动开发、软件开发等,只有熟悉这些工具才能够快速的进行开发。USB体系非常庞大,所以编写本章也是为了够帮助读者跨入USB开发的大门,希望读者通过本篇的学习,能够设计出更为完善、高效的USB接口。