您现在的位置: USB开发网 > USB技术文档 > USB主机驱动及应用
- USB主机驱动及应用

用WinUsb开发USB驱动程序

------分隔线----------------------------

  我们知道,开发USB驱动程序有个选择是使用LibUsb,它为对不太熟悉驱动程序开发的人员提供了一个简便开发USB驱动的方法,可你知道吗,微软也提供了一个类似的工具WinUSB,这可是微软自家的东西哟,是不是很激动,下面来一窥WinUSB到底是什么!

WinUSB简介

  如果您正准备开发一款设备需要与PC通信,用USB接口是个不错的选择。每个USB设备在PC主机上都有对应的一个驱动程序,Windows操作系统为常用标准USB设备类型(如HID设备类、USB接口打印机、优盘等)提供了对应的驱动程序,但如果您开发的USB设备不是标准设备类,那么使用微软的WinUSB将是个不错的选择。

  本文将介绍怎么使用WinUSB。注:WinUSB对环境的要求是Winxp - SP2 以上版本。

传输目的决定传输类型

  每个USB传输发生在PC或其它USB主机与设备端点(endjpoint)之间,设备端点是一个收发数据的缓冲区,每个设备必须支持端点零(endpoint 0),端点零是双向的,设备还可以拥有端点1-15,它们是单向的(IN或OUT)。

  即使从设备里的端点来看,USB协议还定义了端点的方向(针对PC和其它USB主机来说),输入端点(IN endpoint)是设备向PC(或主机)发数据的端点,输出端点(OUT endpoint)是PC向设备发数据的端点,在写设备端代码的时候很容易引起混淆,一定要注意。

  USB是如此有用,一个理由是因为它支持四种传输模式,不同模式支持不同数据长度。WinUSB支持控制传输、批量传输和中断传输。控制传输用端点零,其它类型的传输使用1及1以上的端点。

  控制传输提供了一个结构化的方法来发送请求和数据,接收应答和数据,只有控制传输是双向的。当USB设备插入主机后,主机通过控制端点来取得设备相关信息,这一过程叫做枚举。

  WinUSB可以用控制传输在传输厂商定义的请求,比如你定义的收发开关状态的请求、发送设备操作指令以及读取传输器数据等。

  控制传输有二到三个阶段。为了用于取得最新插入的USB设备设备,主机用控制传输请求设备标准描述符。在Setup阶段,主机发送请求,在Data阶段,设备向主机发送被请求的描述符,在Status阶段,主机分析收到的描述符。在Data阶段,主机也可以用控制传输向设备发送一些信息,接着设备在Status阶段分析这些信息。

  USB主机为控制传输保留了一部分总线带宽,全速设备最低10%,高速设备最低20%。如果总线不是很忙,控制传输和以取得更多保留带宽,但是所有设备必须共享总线带宽 ,所以在一个繁忙的总线上,控制传输可能需要等待。  

  其它类型传输可传输任何目的的数据,传输过程也没有被划分成这么多的阶段。在一个空闲的总线上,批量传输(bulk transfers)是最快的,但批量传输不能保证固定带宽,所以如果是在一个比较忙的总线上,批量传输就会有等待。通常用使用批量传输的设备有打印机、扫描仪等,这类设备可以尽量取得最快的传输速度,但不必保证其传输速度固定。对于中断传输,主机对输入和输出端点都保证有个最大的时间间隔用来访问它们,通常使用中断端点的设备有鼠标、键盘等,它们的特点都是需要尽快地把用户的输入传入主机。

  同步传输(Isochronous transfers)能保证一个固定的传输速率,但不同于其它向种传输模式,同步传输不使用确认机制,接收端收到有错误的数据后无法请求再次发送一次数据。通常使用同步传输的有声音和视频这类流媒体应用,这类应用的特点是用户不会注意或不会介意少量的数据错误或丢包。WinUSB不支持同步传输

使用PIC的固件范例

  我的程序是基于Microchip的PIC18F4550单片机和MPLAB编译器的,我已经在Microchip的 PICDEM FS-USB开发板上测试通过了。一个完整的WinUSB工程以及VB和C#应用程序都放到了我的主页上。

  我的PIC单片机代码使用的是Microchip提供的USB范例,它已经为我们完全了USB最低层的一些代码工作,在此范例的基础上修改可以节省许多宝贵的时间,这也是百合电子工作USB专题站一再提倡的做法。

  除端点零以外的每个端点,设备都提供了一个端点描述符,下面显示了输入输出批量传输和中断传输的端点描述符情况:

  1. // Endpoint descriptors 
  2. 0x07,                    //  Descriptor size in bytes 
  3. USB_DESCRIPTOR_ENDPOINT, // Descriptor type 
  4. _EP01_OUT,               //  Endpoint number and direction 
  5. _BULK,                   //  Transfer type 
  6. 0x40, 0x00,              //  Endpoint size in bytes 
  7. 0x00,                    //  Ignored for bulk endpoint 
  8. 0x07,                    //  Descriptor size in bytes 
  9. USB_DESCRIPTOR_ENDPOINT, // Descriptor type 
  10. _EP01_IN,                //  Endpoint number and direction 
  11. _BULK,                   //  Transfer type 
  12. 0x40, 0x00,              //  Endpoint size in bytes 
  13. 0x00,                    //  Ignored for bulk endpoint 
  14. 0x07,                    //  Descriptor size in bytes 
  15. USB_DESCRIPTOR_ENDPOINT, // Descriptor type 
  16. _EP02_OUT,               //  Endpoint number and direction 
  17. _INT,                    //  Transfer type 
  18. 0x08, 0x00,              //  Endpoint size in bytes 
  19. 0x0A,                    //  Endpoint interval 
  20. 0x07,                    //  Descriptor size in bytes 
  21. USB_DESCRIPTOR_ENDPOINT, // Descriptor type 
  22. _EP02_IN,                //  Endpoint number and direction 
  23. _INT,                    //  Transfer type 
  24. 0x08, 0x00,              //  Endpoint size in bytes 
  25. 0x0A                     //  Endpoint interval 

  USB框架定义的标准结构有利于我们构建可靠的代码并且易于维护例如,USB_DESCRIPTOR_ENDPOINT 是常量0x05,USB协议用它来识别一个端点描述符。

  其它描述符包括设备描述符,它包含设备的VID和PID(即厂商标识ID和产品标识ID)和一个或多个接口描述符,接口描述符指定了接口数量以及此接口下有多少个端点。

批量传输和中断传输

  要读写端点,程序通过访问端点的缓冲描述符,要想和PIC通过USB接口进行通信,必须理解端点缓冲描述符。

  端点缓冲描述符由四个字节组成,它保存有端点最近和下次将下传输的数据。PIC的微控制器核心和USB模块共同拥有这个缓冲区,都能访问它。微控制器核心就是执行程序代码的CPU,USB模块也称作串行接口引擎(SIE),从硬件层面实现USB通信,USB_HANDLE是指向端点缓冲区的指针。

  访问端点缓冲区的关键是UOWN位,当UOWN=0时,微控制器对缓冲区有访问权,程序代码此时可以向缓冲区中读写数据。当UOWN=1时,USB模块对缓冲有访问权。程序代码和以通过判断UOWN的状态来判断是否可以访问缓冲区。

  下面是读取批量传输端点数据的代码:

  1. #define WINUSB_BULK_EP 1 
  2. #define WINUSB_BULK_IN_EP_SIZE 64 
  3. #define WINUSB_BULK_OUT_EP_SIZE 64 
  4. WORD bulk_bytes = 0;  
  5. USB_HANDLE USBWinUsbBulkOutHandle; 
  6. unsigned char winusb_bulk_in_buffer[WINUSB_BULK_IN_EP_SIZE]; 
  7. unsigned char winusb_bulk_out_buffer[WINUSB_BULK_OUT_EP_SIZE]; 
  8. // Set up the endpoint to enable receiving data. 
  9. USBWinUsbBulkOutHandle = USBGenRead(WINUSB_BULK_EP, 
  10.      (BYTE*)&winusb_bulk_out_buffer, WINUSB_BULK_OUT_EP_SIZE); 
  11. if(!USBHandleBusy(USBWinUsbBulkOutHandle)) 
  12.    // The microcontroller  core owns the endpoint.  
  13.    // Check for received  data. 
  14.    bulk_bytes =  USBHandleGetLength(USBWinUsbBulkOutHandle);  
  15.    if (bulk_bytes > 0) 
  16.    { 
  17.       // Data was received.  
  18.       // Copy it to for  sending back to the host. 
  19.       for (count; count <=  bulk_bytes - 1; count++) 
  20.       { 
  21.          winusb_bulk_in_buffer[count] =  
  22.          winusb_bulk_out_buffer[count]; 
  23.       } 
  24.    } 

  记住输出端点是设备接收主机发出的数据。固件范例里的USBGenRead函数可以取得许多有关输出端点的详细信息,此函数的参数里可以指定端点号,接收数据的缓冲区以及最大要接收的字节长度,调用此函数时它自动置UOWN为1以便将控制权交给USB模块,并且返回指向接收缓冲区的指针。

  USB模块接着就能自动将数据发送出去而不需要固件代码的干涉。当端点收到一个带数据的输出令牌后,USB模块保存这些数据到缓冲区,然后置UOWN为零,这样微控制器就能读取这些数据了。  

  为了判断是有数据收到,固件范例里的USBHandleBusy宏首先判断UOWN是否为零,假如是零,USBHandleGetLength宏返回收到的字节数。程序代码就能读取和使用这些数据了。可以把想要向主机发送的数据放入winusb_bulk_in_buffer数组中。读取完数据后,程序代码可以再次调用USBGenRead来接收下一个数据包。

  下面是通过批量传输输出端点向主机发送数据的部分代码:

 

  1. USB_HANDLE USBWinUsbBulkInHandle; 
  2. if (!USBHandleBusy(USBWinUsbBulkInHandle))  
  3.   // The microcontroller core owns the endpoint.  
  4.   // Prepare to send data to the host. 
  5.   USBWinUsbBulkInHandle = USBGenWrite(WINUSB_BULK_EP,  
  6.     (BYTE*)&winusb_bulk_in_buffer, bulk_bytes); 

 

  要发送数据,USBHandleBusy首先检查UOWN位是否为零,如果是,可以调用USBGenWrite来发数据。

  这个函数可以指定端点号,指向要发送数据的指针以及要发送的数据长度,调用此函数开始发送数据,并置UOWN为1,最后返回数据缓冲区指针。

  USB模块接着就能自动将数据发送出去而不需要固件代码的干涉。当端点收到一个带数据的输入令牌后,USB模块自动发送这些数据,然后置UOWN为零,这样程序代码就能接收为发送下一个数据包作准备了。

  在设备端,批量传输和中断传输几基本差不多,除了端点类型外,它们的唯一区别在于主机端,所以要使用中断传输,只需要用中断方式替换每个批量实例,同时设置WINUSB_INTERRUPT_EP为2(或者其它支持中断的端点)、设置WINUSB_INTERRUPT_IN_EP_SIZE 和WINUSB_INTERRUPT_OUT_EP_SIZE的值与端点描述符里的一致。

控制传输

  因为每种传输模式都分成了几个阶段,而控制传输相较于批量和中断传输更为复杂。控制传输第一步是检测收到的请求,在Setup阶段,软件可得知这个请求是指向所整个设备还是特定接口。

  1. // Check the Setup packet to find out if the request is   
  2. // directed to an interface, names the WinUSB interface ID, 
  3. // and is a Vendor request. 
  4. if(SetupPkt.Recipient != RCPT_INTF) return
  5. if(SetupPkt.bIntfID != WINUSB_INTF_ID) return
  6. if(SetupPkt.RequestType != VENDOR) return
  7. // It’s a vendor-specific request to the WinUSB interface. 
  8. // Decode the request and call a routine to handle it. 
  9. switch(SetupPkt.bRequest) 
  10.   case WINUSB_REQUEST_1: 
  11.     // The Data stage is  host-to-device. 
  12.     WinusbControlWriteTransferHandler(); 
  13.     break
  14.   case WINUSB_REQUEST_2: 
  15.     // The Data stage is  device-to-host. 
  16.     WinusbControlReadTransferHandler();             
  17.     break;     
  18. }    

  上面代码能判断两个请求,一个是主机到设备的数据阶段,另一个是设备到主机的数据阶段。WINUSB_REQUEST_1和WINUSB_REQUEST_2都是厂商定义请求。

  我在我把PIC范例代码的控制传输请求部分作了很小的修改就实现了厂商自定义请求,对于请求应答部分代码,我以Get_Descriptor应答为模型实现的。

安装设备

  主机端使用WinUSB来驱动设备。

  Windows利用inf文件来为设备提供相关驱动信息,在WinUSB的inf文件里包含了设备的VID和PID,这个VID和PID应和设备描述符里的值一致,另外还有一个叫GUID的值(128bit),GUID被识别不同的设备类型。

  GUID有几种方法生成,在Visual Studio标准版和更高版本里,选择Tools > Create GUID就可以生成,另外还可以利用微软的GUID生成器(guidgen.exe),或者GUID在线生成器(在网上可以找到)。

  要将我工程里的inf文件用于你自己的项目里,需要修改PID、VID以及GUID,GUID的值在 [Version] 小节的ClassGUID里修改:

 

  1. ClassGUID = {36FC9E60-C465-11CF-8056-444553540000}     
  2. //更改大括号里的值为自己的GUID值    
  3.    
  4. %USB\MyDevice.DeviceDesc% = USB_Install, USB\VID_0925&PID_1456     
  5. //设备VID和PID在inf文件的[Manufacturer]小节里,形式如上   
  6. //上面VID的值是0x0925,PID的值是0x1456,实际应用时改成你设备描述里对应的值 


要安装WinUSB,PC必须有三个dll,在WDK里有这三个文件,你可以将其打包到你的安装软件里,在Vista以上系统你不需要额外提供这三个文件,因为系统已经有这三个文件了

  当设备第一次插入PC后,windows搜索符合PID和VID值的INF文件,必要情况下需要用户定义这个INF文件的位置。

  设置正确安装后,在设备管理器的“通用串行总线控制器”里就能找到此设备,不懂怎么打开设备管理器,网上搜搜。

  Writing Applications

编写应用程序

  我们可以用Visual Basic或C#编写基于应用程序来访问WinUSB为驱动的设备,但是.net 框没有提供访问WinUSB设备的类,因此.net框只能用Windows API或WinUSB API来访问WINUSB设备。

  VB和C#在使用这些API前必须先声明。声明可以参考这些API函数原型(都以C的形式写成)。

  这些API包括:根据GUID找到设备,进而取得设备句柄,取得设备端点数量和类型以及其它一些特性,用批量传输、控制传输或中断传输来和设备通信,如果不会使用这些API,则编程将会变得非常困难。

  通过对此文的介绍,相信你对它有了个简单的认识。

------分隔线----------------------------
联系我们
  • Q Q: 1148374829 点击这里给我发消息
  • 旺旺:jhoneqhsieh 点击这里给我发消息
  • 电话:(0)15923141204
  • 淘宝网店
USB开源项目