/* Set this device to response on default address */
SetDeviceAddress(0);
}要配置下。就是你开启的那些端点了。
4 Windows XP下USB设备驱动的开发
这部分是最麻烦的,至少对我来说是这样的,好像已经到了计算机专业范畴,而我对这些非常不熟悉。学习期间第一个方案用的是LABVIEW,它附带一个工具叫VISA,可以直接配置成USB驱动,然后再在LABVIEW里面直接调用,很方便。在研究LABVIEW的时候发现里面有很多DEMO,而且对中文的支持非常好,很多文档都是官方直接出中文文档。(好像半导体大额们对中国这块市场都越来越重视了,前几天看到AVR大部分的文档也都是直接官方翻译的中文文档。)学习了软件里面附带的入门教程,大概也能编一些简单的程序了,调试USB的时候发现里面有几个USB的demo,配合VISA生成的驱动居然直接接收到了下面传上来的数据,这对我们来说无异于是一次莫大的鼓励。后来导师推荐我试试另一种方法,就是用Driverstudio+VC来开发驱动和编写界面。为了学习一下USB的驱动开发,于是在第三个星期的时候开始windows下驱动的开发。老实说吃了很多苦头,到现在虽然功能基本实现了,但是对于这一块我还是只知道皮毛。DS里面的那些类和函数也没怎么搞透,驱动能开发出来主要还是借鉴了DS附带的example 里面的USB BULK 程序。
下面大概讲下我遇到过的问题给后来人一点借鉴:
(1)是安装顺序的问题,因为DriverStudio要嵌入到VC中,以后的代码要用DDK来编写所以三个软件要很好的兼容才可以。
第一步:安装Microsoft Visual C++6.0;
第二步:安装Microsoft Windows XP DDK;
第三步:安装DriverStudio 3.2驱动程序开发工具包。
VC6.0最好安装英文版的,可以减少不知名的错误。安装完成后DS3.2会嵌入到VC中,在上面多一个标题:
DriverStudio选项卡下面第三项DDK Build setting要设置成C:\WINDDK\2600(如果DDK安装在C盘),然后要编译DriverStudio安装目录下DriverStudio\DriverWorks\source\VdwLibs.dsw,以得到vdw_wdm.lib这个库文件。编译的时候会出现错误,因为用VC打开vdwlibs.dsw工程文件后,有两个工程,要先将VdwLibs工程设为当前Active Project,然后在工具栏上单击右键选择“组建”,在弹出的编译工具栏中配置一下编译平台的设置:选择Win32 WDM Checked平台(因为我们用的XP),然后编译就应该可以了。
(2)是利用向导生成驱动程序框架
DS有个DriverWizard可以生成驱动程序的大体框架。
第一步:选择开发环境VC6.0,命名。
第二步:选择WDM Driver不用修改。
第三步:选择WDM Function Driver
第四步:选择USB并设置好PID 和 VID 要与固件里面的设置相同
第五步:设置端点,以及缓冲区。也要和固件里面相对应。
接下午几步可以根据自己来设置,也可以默认。完成后会生成一个工程目录,里面包含了两个工程,一个是驱动,是我们需要的。另一个是测试程序,好像没有用。当你编译驱动的时候,会提示有错误,这是因为DS里面的一个BUG,选择project-setting,左边两个工程选驱动那个,点右边LINK选项卡,删除Object/library module 项的ntstrsafe.lib。再编译就能通过了。
(3)接下去就是对刚才生成的框架进行研究,添加一些代码以使驱动完整。
首先来了解下生成的框架。
(1)DriverEntry()该例程是当系统检测到与驱动程序支持的设备时被调用的。
(2)AddDevice() 该例程为系统添加一个设备。
(3)Unload() 该例程为系统卸载设备。
以上几个例程都存在于XXXXDriver.cpp是设备的基本操作可以直接使用生成的代码,不用修改。
在另一个文件XXXXDevice.cpp中包含了具体应用函数,比如read和write,但是这个文件也存在这一个BUG,在开头部分有m_Pipe1_in.Initialize(m_Lower, 0x81, 64); 和m_Pipe2_out.Initialize(m_Lower, 3, 64);本次定义了两个端点,一个是叫m_Pipe1_in,0x81表示这个管道的属性是入(in),缓冲区是64.下面一个是m_Pipe2_out表示是出(out),缓冲区是64.向导生成的代码里面漏掉了“0x”这两个字符。
关键部分代码的添加
上位机应用程序,主要是通过两种方式来控制驱动,一个是DeviceIoControl()函数,另一个是ReadFile()函数和WriteFile()函数。本次使用的是后者。只要在Read(KIrp I),和Write(KIrp I)里面添加相应的代码就可以。这方面可以参考DS3.2附带的example里面的那个usbbulk例程。
首先简述一下完成一次驱动调用所要做的具体工作。应用程序想对USB设备进行I/O操作,它需调用Windows API函数比如readfile(),I/O管理器将此请求构造成一个合适的I/O请求包(IRP)并把它传递给USB设备驱动程序。USB设备驱动程序接收到这个IRP后,根据IPR中包含的具体操作代码构造相应USB请求块(URB),并把它放到一个新的IRP中,然后传递给USB底层驱动程序(中间层或总线驱动程序)。USB底层驱动程序根据IRP中所含的URB执行相应的操作,并把操作的结果返回给USB设备驱动程序。USB设备驱动程序接收到此返回的IRP后,将操作结果通过IRP返还给I/O管理器,最后I/O管理器将此IRP操作结果传回给应用程序,至此应用程序对设备的一次I/O操作完成。当上位机调用的ReadFile()的时候,USB设备驱动程序要根据IPR中包含的具体操作代码构造相应USB请求块(URB),这个URB的生成就在这里实现,比如例程里面的
PURB pUrb = m_Pipe1_in.BuildBulkTransfer(
Mem, // Where is data coming from?
dwTotalSize, // How much data to read?
TRUE, // direction (TRUE = IN)
NULL // Link to next URB
);
主要是通过这个函数来实现。函数实现了之后会有不同的返回值,然后打印出不同的信息。这些信息可以在DS调试工具Monitor中看到。
5 驱动开发的过程大概就是这样了。下面是应用程序的开发。主要两步,先打开设备,然后读写数据。
(1)打开设备
Windows下面有很多针对驱动调用的API函数,要调用一个USB设备首先就要打开这个设备,其对应的API函数为CreateFile,在本次驱动中DS自动生成了一个OpenByInterface.c文件,在该文件里面对这个CreateFile函数进行了封装,其参数如下
HANDLE OpenByInterface(
GUID* pClassGuid, // points to the GUID that identifies the interface class
DWORD instance, // specifies which instance of the enumerated devices to open
PDWORD pError // address of variable to receive error status
)
所以在主程序中只要调用OpenByInterface函数就可以了。本次中具体实现的代码如下:
if(g_hUsbDevice==INVALID_HANDLE_VALUE)
{
g_hUsbDevice=OpenByInterface(
&g_UsbGuid, // points to the GUID that identifies the interface class
0, // specifies which instance of the enumerated devices to open
&Error // address of variable to receive error status
);
if(g_hUsbDevice==INVALID_HANDLE_VALUE)
{
MessageBox("打开设备失败!",NULL,MB_OK | MB_ICONHAND);
}
else
{
MessageBox("打开设备成功!",NULL,MB_OK | MB_ICONASTERISK);
}
}
其中GUID* pClassGuid是对应的设备的GUID,具体定义如下:
GUID g_UsbGuid=GUID_DEVINTERFACE_USB;//打开设备的GUID
其中GUID_DEVINTERFACE_USB是在DS生成的Intrface.h文件中定义的,
#define GUID_DEVINTERFACE_USB \
{ 0x6C8CFFA6, 0xCAB8, 0x45B1, { 0xAA, 0xEE, 0x1E, 0xF4, 0xDF, 0x79, 0xF8, 0xDF } }
这个ID保证了每个USB驱动的唯一性,调用紊乱的情况的出现。OpenByInterface这个函数有一个返回值,通过的改变可以确认设备是否打开成功,然后通过MessageBox函数跳出不同的反馈信息。
(2)读写数据
Windows中读取数据的API函数有两种,一种是DeviceIoControl,一种是ReadFile、WriteFile函数。前者一个函数可以读也可以写,后者把读写分开来,这次使用的是后者。
ReadFile的具体实现如下:
ReadFile(
g_hUsbDevice, //我们的设备HANDLE hFile
DataBuffer,//输入缓冲,无lpBuffer
64, //输出字节数nNumberOfBytesToRead,
&BytesReturned, //实际读取到的字节数lpNumberOfBytesRead,
NULL
)
g_hUsbDevice是对应我们的设备,DataBuffer是我们定义的数组,读取到的数据就存在这个数组中,64是一次读取的字节数,BytesReturned是实际读取到的字节数,最后一个可以默认为NULL。如果成功读取到了数据,数组DataBuffer中的数据就会更新,然后可以做任意处理。
写数据基本和读取数据类似。