USBGPS 开放源代码项目  

 

项目维护人: 鲁郁    

ENGLISH



项目简介

硬件原理

固件

设备驱动

应用软件

下载

开发文档

开发心得与致谢


设备驱动

首先我要声明, 我并不是一个设备驱动程序的专家,相反在这个领域我只是个初学者. 我完成的这个驱动仅仅具有最基本的功能: 和PC通信. 在目前的版本里,没有超时处理, 没有崩溃恢复,也没有功率控制, 而这些都是一个专业设备驱动程序所必须的. 如果我想把这些功能都完善起来, 那么我就得花多得多的时间. 如果你想学习更多关于驱动开发, 我这里有一些好的参考书.

开发一个设备驱动, 开发者必须遵循操作系统为驱动程序规定好的一套规范. 驱动是用C语言写的,但如果你没有理解这些规范, 不要期待你能看的懂驱动的源代码--- 甚至你是个C语言的大师,也不例外. 调试驱动更是一场真正的梦魇: 在用户模式, 如果程序有BUG,最多程序运行不正常,或给出错误的运行结果; 但在内核模式, 一个小小的BUG就能将你的计算机置于死地. 我已经记不清为了完成这个简单的驱动, 我重起了多少次机器. 因为目前我只写了WINDOWS下的WDM驱动, 所以在这我只说一说我所理解的部分. 当然我将直接跳到跟这个项目相关的部分, 我假定你已经有了WDM驱动的一些基本概念, 象IRP, IO manager, FDO, URB...等等.

要理解驱动,你必须理解硬件和固件, 道理很简单: 驱动希望访问的资源是由硬件和固件提供的. 通常对一个USB外设来说,其资源包括配置,接口和端点. 在USBGPS项目中,我的硬件接口板只有一个配置资源和一个接口资源, 所以我只需要谈谈端点资源. 在我的硬件接口板上, 有5个端点:
Endpoint 0
端点0被用来配置整个设备. 它是双向的, 即, PC<--->硬件. 通过这个端点, PC完成所有必须的USB事件, 象CLEAR_FEATURE, GET_DESCRIPTOR, GET_STATUS,SET_CONFIGURATION...等等. 所有这些事件都由操作系统发起, 所以端点0没有出现在我的驱动源程序了.
Endpoint 1
端点1用来发送一些系统命令给硬件, 例如读取USBN9603寄存器的值, 读取C51的寄存器的值, 控制C51的计时器...等等. 大部分的命令都是为了调试准备的. 这个端点是从PC到硬件的, 既, PC--->硬件.
Endpoint 2
端点2的目的是和端点1结合在一起的:PC通过端点1发送命令给硬件, 端点2将命令的结果发送给PC(如果该命令需要结果返回的话). 所以端点2的方向是从硬件到PC, 既, PC<---硬件. 端点2和端点1结合在一起, 为我在开发阶段提供了一个调试的通道. 同时它们也提供了一种控制GP2021的手段, 但这个方法并不是实时运行的方案, 而仅仅是用来测试GP2021的硬件接口工作正常与否.
Endpoint 5
端点5是用来向GP2021实时地发送控制指令. 其方向是从PC到硬件. 指令被组织成BUFFER3的格式, 关于这一点, 我在固件文档里有详细的描述. 当硬件接口板接受到这些指令, 就会将指令写到相应的GP2021寄存器里.
Endpoint 6
端点6被硬件用来实时地发送4次(3.6毫秒)采集的6个通道的数据给PC. 数据传输方向是从硬件到PC, PC<----硬件. 固件将这些数据组织成BUFFER1(或BUFFER2)的格式, 关于这些格式,我在固件文档里有详细描述.
现在我们对硬件资源有了一个清晰的理解, 随之产生一个问题:怎样来访问这些资源? 这个问题的解答就需要设备驱动了. 写USB设备驱动的程序员比较幸运, 因为USB设备没有设置IO口, 设置中断,以及DMA的麻烦. USB设备驱动通过URB来访问所有资源, 下面这段代码摘自我的程序, 演示如何产生一个URB:

URB urb ;

pch = (PUCHAR) ioBuffer;
UsbBuildInterruptOrBulkTransferRequest(&urb,
           sizeof( _URB_BULK_OR_INTERRUPT_TRANSFER),
           pdx->Pipe1,
           pch,
           NULL,
           cbin,
           USBD_SHORT_TRANSFER_OK,
           NULL);
          
这里pdx->Pipe1指示了端点号码(是由管道体现出来的), pch 是一个指针, 指向一块欲发送的数据缓冲区. 关于其他参数的意义, 请参看参考书目. For the meaning of other arguments, please read the reference.

用户模式的程序通过I/O Control 操作来调用驱动, 具体是用微软WIN32 API 函数DeviceIoControl来完成. 我没有提供ReadFile() 或 WriteFile()函数, 因为我发现DeviceIoControl()函数已经足够完成所有功能了. 例如, 在ioctls.h里我定义了如下的I/O 控制码:
#define IOCTL_USBGPS_BULK_WRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)
这个控制码的处理在Control.cpp给出, 如下所示:
NTSTATUS DispatchControl(PDEVICE_OBJECT fdo, PIRP Irp){
           ...
           ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
          
           switch (code)
           { // process request
           ...
          
           case IOCTL_USBGPS_BULK_WRITE: // code == 0x803
           { // IOCTL_USBGPS_USBGPS_WRITE
           if(ioBuffer && cbin >= 0 && cbout >= 0)
           {
           URB urb ;
          
           pch = (PUCHAR) ioBuffer;
           UsbBuildInterruptOrBulkTransferRequest(&urb,
           sizeof( _URB_BULK_OR_INTERRUPT_TRANSFER),
           pdx->Pipe1,
           pch,
           NULL,
           cbin,
           USBD_SHORT_TRANSFER_OK,
           NULL);
           status = SendAwaitUrb(fdo,&urb);
          
           if(NT_SUCCESS(status))
           info = cbin;
           else
           info = 0;
          
           // TODO insert code here to handle this IOCTL, which uses METHOD_BUFFERED
           }
           break;
           }
           ...
           }


从这段代码, 我们可以看出驱动并不需要理解传送的缓冲区的数据结构定义. 驱动只负责将这块缓冲区打包进URB, 然后将URB传送给USB控制器. 上层的用户程序和下层的固件应该理解数据结构的定义, 才能做后续的处理. 所以驱动只是上层的用户程序和下层的固件相互交谈的媒介而已.

设备驱动的编译器开发环境是Win2000 DDK.

一些有用的参考书目



Welcome to USBGPS Opensource Project Homepage
© Copyright 2004    Email:   Yu Lu