现在介绍在windows XP下开发虚拟串口的方法。
可以开发一个虚拟串口,将读写请求传递给USB驱动,这样就可以利用现成的串口调试工具向USB设备读取了。
1、DDK串口开发框架DDK对串口驱动提供了专门接口。
只要编写的驱动满足这些接口,并按照串口标准的命名方法,不管是真实的串口设备,还是虚拟设备,Windows操作系统都会认为这个设备是一个标准的串口设备。
用标准的串口调试工具都可以与这个设备进行通信。
1、1 串口驱动的入口函数本章的实例程序是在HelloWDM驱动的基础上修改而来,入口函数依然是DriverEntry,在DriverEntry函数中指定各种IRP的派遣函数,以及AddDevice 例程、卸载例程等。
[cpp]view plaincopy1./************************************************************************2.* 函数名称:DriverEntry3.* 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象4.* 参数列表:5. pDriverObject:从I/O管理器中传进来的驱动对象6. pRegistryPath:驱动程序在注册表的中的路径7.* 返回值:返回初始化驱动状态8.*************************************************************************/9.#pragma INITCODE10.extern"C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,11. IN PUNICODE_STRING pRegistryPath)12.{13. KdPrint(("Enter DriverEntry\n"));14.15. pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;16. pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;17. pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloWDMDispatchControlp;18. pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloWDMCreate;19. pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloWDMClose;20. pDriverObject->MajorFunction[IRP_MJ_READ] = HelloWDMRead;21. pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMWrite;22. pDriverObject->DriverUnload = HelloWDMUnload;23.24. KdPrint(("Leave DriverEntry\n"));25.return STATUS_SUCCESS;26.}其中在AddDevice例程中,需要创建设备对象,这些都是和以前的HelloWDM驱动程序类似。
在创建完设备对象后,需要将设备对象指定一个符号链接,该符号链接必须是COM开头,并接一下数字,如本例就采用了COM7。
因为COM1和COM2在有些计算机中有时会被占用,因此,当该设备对象在指定符号链接时,应该避免采用这些名称。
[cpp]view plaincopy1./************************************************************************2.* 函数名称:HelloWDMAddDevice3.* 功能描述:添加新设备4.* 参数列表:5. DriverObject:从I/O管理器中传进来的驱动对象6. PhysicalDeviceObject:从I/O管理器中传进来的物理设备对象7.* 返回值:返回添加新设备状态8.*************************************************************************/9.#pragma PAGEDCODE10.NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,11. IN PDEVICE_OBJECT PhysicalDeviceObject)12.{13. PAGED_CODE();14. KdPrint(("Enter HelloWDMAddDevice\n"));15.16. NTSTATUS status;17. PDEVICE_OBJECT fdo;18. UNICODE_STRING devName;19. RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");20. status = IoCreateDevice(21. DriverObject,22.sizeof(DEVICE_EXTENSION),23. &(UNICODE_STRING)devName,24. FILE_DEVICE_UNKNOWN,25. 0,26. FALSE,27. &fdo);28.if( !NT_SUCCESS(status))29.return status;30. PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;31. pdx->fdo = fdo;32. pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);33. UNICODE_STRING symLinkName;34. RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\COM7");35.36. pdx->ustrDeviceName = devName;37. pdx->ustrSymLinkName = symLinkName;38. status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName);39.40.if( !NT_SUCCESS(status))41. {42. IoDeleteSymbolicLink(&pdx->ustrSymLinkName);43. status = IoCreateSymbolicLink(&symLinkName,&devName);44.if( !NT_SUCCESS(status))45. {46.return status;47. }48. }49.// 设置为缓冲区设备50. fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;51. fdo->Flags &= ~DO_DEVICE_INITIALIZING;52.53. KdPrint(("Leave HelloWDMAddDevice\n"));54.return STATUS_SUCCESS;55.}在创建完符号链接后,还不能保证应用程序能找出这个虚拟的串口设备,还需要进一步修改注册表。
具体位置是HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM,可以在这里加入新项目。
本例的项目名是MyWDMDevice,类型为REG_SZ,内容是COM7。
在上述步骤后,即在AddDevice例程中创建COM7的符号链接,并且在注册表进行相应设置,系统会认为有这个串口驱动,用任何一个串口调试软件,都可以枚举到该串口。
1、2 应用程序与串口驱动的通信其实对于一个真实的串口驱动,或者这个介绍的虚拟串口驱动,都需要遵循一组接口。
这组接口由微软事先定义好了,只要符合这组接口,windows就会认为这是一个串口设备。
这里所指的接口就是应用程序发的IO控制码和读写命令,因此对于串口驱动只要对这些IRP的派遣函数编写适当,就能实现一个串口驱动。
首先用IRPTrace看一下,需要对哪些IRP进行处理,笔者加载本章已经介绍的虚拟串口驱动,并用IRPTrace 拦截其IRP处理信息,在打开串口工具后,会发现IRPTrace立刻跟踪到若干个IO控制码,如下所示:下面依次解释这些IO控制码,理解这些IO控制码,并处理好这些控制码,是编写串口驱动的核心。
关于这些IO控制码在ntddser.h文件中,都有相应的定义,并且还有相应的数据结构定义。
(1)IOCTL_SERIAL_SET_QUEUE_SIZE这个控制码是应用程序向驱动请求设置串口驱动内部的缓冲区大小,它是向驱动传递SEARIAL_QUEUE_SIZE 数据结构来进行设置的,对于虚拟串口驱动来说,这是不需要关心的。
用IRPTrace可以看出,串口调试工具会向驱动发送的请求是0x400大小的缓冲区大小。
(2)IOCTL_SERIAL_GET_BAUD_RATE串口调试工具会接着向驱动发送IOCTL_SERIAL_GET_BAUD_RATE命令,这主要是询问驱动这个设备的波特率。
驱动应该回应应用程序SEARIAL_BAUD_RATE数据结构,来通知波特率的数值。
(3)IOCTL_SERIAL_GET_LINE_CONTROL串口调试工具接着向驱动发送IOTCL_SERIAL_GET_LINE_CONTROL命令,这主要是为了返回串口的行控制信息,行控制信息用SERIAL_LINE_CONTROL数据结构表示。
[cpp]view plaincopy1.typedef struct _SERIAL_LINE_CONTROL {2.UCHAR StopBits;3.UCHAR Parity;4.UCHAR WordLength;5. } SERIAL_LINE_CONTROL,*PSERIAL_LINE_CONTROL;其中StopBits是停止位,可以是STOP_BIT_1、STOP_BITS_1_5、STOP_BITS_2等取值。