欢迎访问ic37.com |
会员登录 免费注册
发布采购

WinCE串口驱动分析

日期:2012-6-18标签: (来源:互联网)

串行通讯接口是目前十分流行的通讯接口之一,串口通讯也已是普遍的标准而被大家广为熟悉。

基本架构

WinCE中,串口的驱动实现是有固定模型的,其串口模型遵循ISO/OSI网络通讯模型。在典型的应用中,serialAPI与间接通过TAPI或直接与ActiveSync交互,组成CE网络的一部分。其实整个驱动模型是相当复杂的,好在驱动仅仅使用到SerialAPI这一层,在这个层次上串口的行为相对比较简单。在WinCE中,串口驱动模型是作为Stream来实现的(即:流设备驱动),

串口驱动本身分为MDD层和PDD层。

MDD提供框架性的实现,负责提供OS所需的基本实现,它对上层的设备管理器,提供了标准的流设备驱动接口(COM_xxx);而PDD提供了对硬件操作相应的代码,它实现了HWOBJ结构及结构中若干针对于串口硬件操作的函数指针。

DDSI是指MDDPDD两个部分之间的接口,这个接口是人为的规定的,在串口驱动中实际上就是指HWOBJPDD层会传给MDD层一个HWOBJ的结构指针,这样MDD层就可以调用PDD层的函数来操作串口。在实际的驱动应用中仅仅需要实现HWOBJ相关的一系列函数,而无需从驱动顶层完全开发。

通常的串行连接有3wire9wire两种。3wire的接线方式下定义了发送、接收和地三根连接。而9wire中将串行连接定义为如下形式。

针号

1

2

3

4

5

6

7

8

9

缩写

DCD

RXD

TXD

DTR

GND

DSR

RTS

CTS

DELL

功能说明

数据载波检测

接收数据

发送数据

数据终端就绪

信号地

数据设备就绪

请求发送

清除发送

振铃指示

这就是在原3wire的基础上增加了DCDDTRDSRRTSCTSDELL六个控制线。其中RTS/CTS用于流控制,另外的DCDDELL则留作连接modem使用。有了专门的硬件流控制引脚也就使得流控制成为可能,以完成收发两端的匹配使得数据可以可靠的传输,即实现了流控制,保障了数据传输的完备性。

其他几个引脚都是与modem相关的,DSR数据装置准备好用于表明MODEM处于可以使用的状态。

函数分析

1HWOBJ

HWOBJ是相应的硬件设备操作的抽象集合,实现了对串口硬件的操作,并在MDD层被调用。

typedef struct __HWOBJ {

ULONG BindFlags;.

DWORD dwIntID;

PHW_VTBL pFuncTbl;

} HWOBJ, *PHWOBJ;

其中,BandFlags用于控制MDD指定IST的启动时间,MDD正是通过这些函数来访问具体的PDD操作。

dwInitID是系统的中断号。

pFuncTbl则是指向一个PHW_VTBL结构,该结构中包含一个函数指针列表,这些函数指针指向串口硬件操作函数,用于操作串口。

2MDD

MDD层向上提供了流设备接口,用于管理串口,Device.exe直接调用。

? COM_Init (ULONG Identifier)

它是该驱动的初始化函数,通过硬件抽象接口HWInit初始化硬件。如果驱动被设备管理器加载,参数Identifier包含一个注册表键值在“HKEY_LOCAL_MACHINE\Drivers\Active”的路径下。

? COM_Deinit(void)

当驱动被称被卸下的时候该事件启动,用作与COM_Init相反的操作。停止在MDD中的所有IST,释放内存资源和临界区等系统资源。

? COM_Open(HANDLE pContext, DWORD AccessCode, DWORD ShareMode)

COM_OepnCreateFile后被调用,用于以读/写模式打开设备,并初始化所需要的空间/资源等,创建相应的实例。Open操作完成后,驱动就进入了工作状态。

? COM_Close(DWORD pContext)

COM_Close释放COM_Open所使用的系统资源,停止IST线程,恢复驱动状态。

? COM_Read(HANDLE pContext, PUCHAR pTargetBuffer,

ULONG BufferLength, PULONG pBytesRead)

COM_Read是获取串口所接收到数据的操作,在前面的IST中没有看到对RX buffer进行修改Read标记的操作,也就是这儿来完成的。

? COM_Write(HANDLE pContext, PUCHAR pSourceBytes,

ULONG NumberOfBytes)

COM_Write是与COM_Read相对应的操作,是写串口数据的。应用程序调用WriteFile函数写串口的时候,该函数被调用。在程序的开始,同样也是参数检查,内容与COM_Read一致。其中pContext参数是COM_Open函数返回的HandlepSourceBytes指向一个Buffer,该Buffer包含要写入串口的数据。NumberOfBytes表示要写入串口的数据的大小。

? COM_PowerUp/ COM_PowerDown (HANDLE pContext)

这两个函数的调用都由CE的电源事件来引发,MDD并没有对这两个函数进行处理,仅仅是将其传递给PDD

? COM_IOControl (DWORD dwOpenData, DWORD dwCode, PBYTE pBufIn, DOWRD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut)

该函数主要实现了一些串口的IO控制,他会被应用层的一些串口函数调用来获得或者设置串口的状态。

3PDD

实际上,在PDD层的主要工作就2个:一是控制硬件;二是和上层打好关系。先说上层接口,上层用了GetSerialHead()来获得接口,所以PDD里面要实现GetSerialHead()的函数,并且将接口返回给上层。

GetSerialObject( DWORD DeviceArrayIndex )

{

PHWOBJ pSerObj;

pSerObj=(PHWOBJ)LocalAlloc( LPTR ,sizeof(HWOBJ) );

if ( !pSerObj )

return (NULL);

pSerObj->BindFlags = THREAD_IN_PDD;

pSerObj->dwIntID = DeviceArrayIndex;

pSerObj->pFuncTbl = (HW_VTBL *) & IoVTbl;

return (pSerObj);

}

PDD层的函数主要是实现了对串口硬件的操作,函数不少,可以参考以下列表:

序号

函数

说明

1

GetSerialObject

返回一个指向HWOBJ结构的指针,该结构包含了相关硬件接口函数的函数指针

2

HWClearBreak

清除串口中断状态,用于串口从中断状态恢复

3

HWClearDTR

设置串口的DTR管脚为低

4

HWClearRTS

设置串口的RTS管脚为低

5

HWClose

关闭由HWInit函数初始化的设备

6

HWDisableIR

禁用串口的红外模式

7

HWEnableIR

启用串口的红外模式

8

HWGetCommProperties

重新获得当前串口设备的硬件属性

9

HWGetIntrType

获得当前的中断类型

10

HWGetModemStatus

获得Modem的状态

11

HWGetRxBufferSize

获得串口硬件接收Buffer的大小

12

HWGetRxStart

返回硬件接收Buffer的起始位置

13

HWGetStatus

获得硬件状态信息

14

HWInit

初始化串口硬件设备

15

HWIoctl

执行I/O控制

16

HWLineIntrHandler

线路状态信息中断处理函数

17

HWOpen

打开串口设备

18

HWPowerOff

串口硬件进入Suspend模式

19

HWPowerOn

串口硬件从Suspend模式恢复到工作模式

20

HWSetDCB

设置串口硬件设备信息

21

HWSetDTR

设置串口的DTR管脚为高

22

HWSetRTS

设置串口的RTS管脚为高

23

HWPurgeComm

清除串口硬件buffer的信息

24

HWPutBytes

通过写数据到硬件中来直接发送数据

25

HWReset

复位串口硬件

26

HWRxIntrHandler

接收数据中断处理函数

27

HWSetBreak

设置串口为中断状态,停止发送接收数据

28

HWTxIntrHandler

串口发送中断处理函数

非独占式串口驱动

用过串口进行过开发的兄弟们都知道,串口驱动是一个典型的独占设备。简单点来说,就是在成功地调用CreateFile打开串口之后,没有通过CloseHandle进行关闭,是无论如何都不能再次调用CreateFile来再次打开相同的串口,这样做可以避免产生数据丢失,也避免获取数据的线程是反复读取。

但是现在的嵌入式设备功能都非常多,需要非独占式串口驱动,也就是虚拟串口驱动。它主要是处理数据的分发,可以和具体的硬件分开,优势也很明显,可以不用理会具体的硬件规格,只要采用的是WinCE系统,虚拟串口驱动就能正常工作。

在设计驱动的时候需要注意,同一时间只能有一个进程对外输出数据,其余进程只能在该进程输出完毕之后才能进行。当然,程序不应该主动调用ReadFile来轮询获取数据,而是通过WaitCommEvent进行检测。为了不丢失数据,缓冲大小一定要等于或大于READ_BUFFER_LENGTH

在写代码的时候需要注意一点,WaitCommEvent函数只能被一个线程调用,同一时间只有唯一的一个线程通过WaitCommEvent函数进入等待状态。因此对于IOCTL_SERIAL_WAIT_ON_MASK控制码的处理,可以通过调用WaitForSingleObject进行线程等待。这时虚拟串口驱动会额外开放一个线程,该线程主要是通过调用WaitCommEvent来获取原生串口的状态,当状态有通知时,再发送event给等待的线程。