ETC VK32

维
VK32 串口扩展 WinCE 驱动说明文档
Rev 1.0
Published: 08-22-2010
VK32 串口扩展 WinCE 驱动说明文档
维
修订记录
修订版本
日期
描述
作者
1.0
08-22-2010
File creation
VKIC
VK32 串口扩展 WinCE 驱动说明文档
维
目 录
修订记录 ...........................................................................................................................................I
1. 概述 .............................................................................................................................................2
2. 串口驱动的实现 .........................................................................................................................2
2.1UART 工作原理..................................................................................................................2
2.2UART 驱动架构..................................................................................................................3
2.3UART 驱动代码分析..........................................................................................................4
3.驱动的使用 ...................................................................................................................................9
4.驱动的移植 .................................................................................................................................10
5.wince5 与 wince6 串口驱动的区别............................................................................................10
6. 总结 ...........................................................................................................................................10
7. 附:在 wince5 中使用 Intallable ISR 时 OEMInterruptHandler 需要注意以下代码: ........10
VK32 串口扩展 WinCE 驱动说明文档
1. 概述
这个驱动是依据 Microsoft 的 16550 驱动原型改写的。MSDN 中有关于 16550 驱动的相关说明。
VK 的驱动与 16550 驱动在中断和寄存器上都与标准 16550 驱动都不兼容,所以改动较大。
2. 串口驱动的实现
串口驱动是一个标准的流驱动,如图所示:
串口驱动本身分为 MDD(mdd.c)层和 PDD(ser16550.c)层。MDD 层对上层的 Device Manager 提
供了标准的流设备驱动接口(COM_xxx),PDD 层实现了 HWOBJ 结构及结构中若干针对于串口硬件
操作的函数指针,这些函数指针将指向 PDD 层中的串口操作函数。DDSI(com_exp.c)是指 MDD
层与 PDD 层的接口,在串口驱动中实际上就是指 HWOBJ,PDD 层会传给 MDD 层一个 HWOBJ 结
构的指针,这样 MDD 层就可以调用 PDD 层的函数来操作串口。
2.1UART 工作原理
虽然串口通讯已经是普遍的标准而且广为大家熟知,但驱动中涉及的部分内容也可能在平时的应
用中并不是很常用到,在这里做一个简单的介绍待后面说明到具体代码的时候可以连贯一些。
串行通讯接口是目前十分流行的通讯接口之一。由于其电气界面的简单性使其在计算机领域的应
用相当的广泛。在这里提到的串行通讯接口主要是指 UART(通用串行)通常的串行连接电气连接上有
3wire 和 9wire 两种。3wire 的接线方式下定义了发送、接收和地三根连接。其用途就如名称一样分
别用于发送、接收。
通常在串行接口控制器上会有两个 FIFO 用作接收和发送的缓冲,当接收到数据后会直接将接收
VK32串口扩展WinCE驱动说明文档 Rev 1.0
2 of 13
到的数据置入该缓冲器,并同时由控制电路向本地总线发出通知,以便让本地总线将缓冲器内的数
据读走,这样在响应(等待和读取)的过程中仍然能通过缓冲器来接收数据。而发送的过程刚刚相反,
本地总线可一直向发送缓冲写入数据直到器填满为止,而无需对每个数据的发送进行等待。这就是
基本的收发流程(这部分逻辑流程相信大家是最熟悉的)。这一点在 3wire 和 9wire 中都是相同的。但
是我们考虑下面的情况,如果接收一方的响应由于某种原因的干扰(如处理器被其他中断服务占用)
的时候可能就来不及相应之前 ReceiveFIFO 就可能被填满了,这样后续发送过来的数据就会丢失,
这样在需要数据可靠传输的情况下串行通讯的弊端也就显示出来了。如需要数据的可靠传输就需要
对数据流的收发进行控制。在 9wire 中将串行连接定义为如下形式。
针号 1 2 3 4 5 6 7 8 9
缩写 DCD RXD TXD DTR GND DSR RTS CTS DELL
功能说明 数据载波检测 接收数据 发送数据 数据终端就绪 信号地 数据设备就绪 请求发
送 清除发送 振铃指示
也就是说在原 3wire 的基础上增加了 DCD,DTR,DSR,RTS,CTS,DELL 六个控制线。其中 RTS/CTS 用
于流控制,另外的 DCD 和 DELL 则留作连接 modem 使用。有了专门的硬件流控制引脚也就使得流
控制成为可能,以完成收发两端的匹配使得数据可以可靠的传输。用 RTS/CTS(请求发送/清除发送)
流控制时,应将通讯两端的 RTS、CTS 线对应相连).在发送端准备发送数据之前设置 RTS(Request to
send)也就使发送请求线,若接收端以作好接收准备,就启动响应的 CTS(Clear to send)引线。这样,
收发双发就进入数据传输状态,在此过程中如若接收端处理数据的速度低于发送端的发送速度,接
收一端还可以设置 CTS 引线恢复原来阻塞得状态以暂时中断数据传输,之后若需要恢复数据传输恢
复 CTS 状态即可。这样 UART 的传输即实现了流控制,保障了数据传输的完备性。
在这里还要说一下软件流控制,虽然硬件已经可以完成流控制的任务但很多少时候受到连线数的
限制不能使用硬件流控制也就设计了专门的软件流控制的方法。现在回到 3 线传输的情景,若接收
端接收数据过程中缓冲器的负载到达某一限制(也就是留出一定的缓冲空间)时接收端向发送端发送
一个特殊的标示位(接收停止位),当发送端收到该标示的时候就停止发送,直到接收端缓冲器低于另
一限制后发送标示(接收许可位)给发送端,这样就可以控制数据流的传输起停。这种软件流控制是在
给缓冲器留余量来完成的,在收发双端处理器速度差很大的时候就不太适用了,就必须要用硬件流
控制。
其他几个引脚都是与 modem 相关的,DSR 数据装置准备好(Data set ready)用于表明 MODEM
处于可以使用的状态。DTR 数据终端准备好(Data terminal ready)表明数据终端可以使用。这两个信
号用于检查 Modem 是否连接。DELL 脚当有电话拨入时 Modem 将会设置这个引脚。DCD 信号是当
Modem 接收到数字载波信号的时候被设置,用于了解 Modem 接收信号的情况。
至于剩下的奇偶效验和停止位设置就只是需要针对寄存器设置无需软件干涉就可以完成了。下
面我们来看具体的驱动程序。
2.2UART 驱动架构
在 wince 中串口的驱动实现是有固定模型的,ce 中的串口模型遵循 ISO/OSI 网络通讯模型(7 层),
就是说串口属于 CE 网络模块的一个部分。其中 rs232 界面(或其它的物理介质)实现网络的物理层,
而驱动和 serialAPI 共同组成数据链路层,其它部分都没有做定义。在典型的应用中,serialAPI 与间接
通过 TAPI 或直接与 ActiveSync 交互,组成 CE 网络的一部分。而红外本身的协议就相对复杂的多,
它有专门的一套模型来描述其使用规则,对红外设备本身了解不多也就不能深入下去。在串口的这
一侧,整个驱动模型也是相当的复杂的,但所幸的是驱动仅仅使用到 SerialAPI 这一层,在这个层次
上串口的行为还是相对简单的。
我们这里仅仅涉及上面所提到的 Serial/irda Driver 这部分。在 wince 提供的驱动例程中串口/红
VK32串口扩展WinCE驱动说明文档 Rev 1.0
3 of 13
外驱动采用分层结构设计,MDD 提供框架性的实现,负责提供 OS 所需的基本实现,并将代码设计
与具体的硬件设计无关。而 PDD 提供了对硬件操作相应的代码。这些代码通过结构 HWOBJ 来相互
联系。对于 MDD+PDD 的整体驱动来看,串口驱动模型是作为 Stream 来实现的。
两者合一以达到实现驱动的目的。DDSI 就是指这两个部分之间的接口,这个接口并非受到强
制的物理/逻辑关系来约束,而是人为的规定的。在涉及到一种特定硬件我们进行针对实现的时候往
往需要的是了解硬件的物理特性和控制逻辑,然后根据 DDSI 的约束就来进行实现。对于这里描述
的驱动模型而言结合关键在于结构指针 HWOBJ 的使用和具体实现。在实际的驱动应用中仅仅需要
实现 HWOBJ 相关的一系列函数,而无需从驱动顶层完全开发。串口驱动模型作为一种常用驱动模
型在 windowsCE 中常常用于串口/红外/USB Client 的具体实现。该驱动模型中对全功能的串口进行
了定义,除了常用的 TX 和 RX 引线定义以外,针对 DTR、RTS 等功能引脚都进行了支持,使得用
该模型设计的串口驱动支持流控制、具备驱动 Modem 等设备的能力。 事实上,如果需要的话完全
可以将该驱动一体化设计(抛开 PDD-MDD 的划分,也就无须 DDSI)。也就是不使用现有的驱动架构
来进行实现。考虑到串口驱动的使用频率和执行效率要求都不是很苛刻的情况下抛弃驱动架构另外
实现的就没有多大必要了。 对于驱动本身而言,串行驱动从功能和实现上相当的简单,确具有相当
全面的成分,对该驱动的分析和了解无疑是学习流式驱动程序很好的典范。
2.3UART 驱动代码分析
在开始具体代码之前我们先来看看,相关的一些结构。 HWOBJ 是相应的硬件设备操作的抽象
集合。结构的定义后的注释与实际的用途有点出入,BandFlags 指定 IST 的启动时间,可选为在初始
化过程启动或是在打开设备的时候起动 ISR.而第二个参数则是指定拦截的具体的系统中断号。最后
一个参数是一个结构,该结构定义了硬件操作的各式行为函数的指针,MDD 正是通过这些函数来访
问具体的 PDD 操作。
typedef struct __HWOBJ {
ULONG
BindFlags; // Flags controlling MDD behaviour. Se above.
DWORD
dwIntID; // Interrupt Identifier used if THREAD_AT_INIT or THREAD_AT_OPEN
PHW_VTBL pFuncTbl;
} HWOBJ, *PHWOBJ;
而 HW_VTBL 则是代表具体硬件操作函数指针的集合,该结构所指向的函数包括了初始化、打开、
关闭、接收、发送、设置 Baudrate 等一系列操作。结构存在就像纽带一样联系着 PDD 中的具体实现
和 MDD 中的抽象操作。PDD 的实现必须遵循 HW_VTBL 中所描述的函数形式,并构造出相应的
HW_VTBL 实例。驱动的编写就是针对这些函数来一一进行实现。
typedef struct __HW_VTBL
{
PVOID
(*HWInit)(ULONG Identifier, PVOID pMDDContext, PHWOBJ pHWObj);
BOOL
(*HWPostInit)(PVOID pHead);
ULONG
(*HWDeinit)(PVOID pHead);
BOOL
(*HWOpen)(PVOID pHead);
ULONG
(*HWClose)(PVOID pHead);
INTERRUPT_TYPE (*HWGetIntrType)(PVOID pHead);
ULONG
(*HWRxIntrHandler)(PVOID pHead, PUCHAR pTarget, PULONG pBytes);
VOID
(*HWTxIntrHandler)(PVOID pHead, PUCHAR pSrc, PULONG pBytes);
VOID
(*HWModemIntrHandler)(PVOID pHead);
VOID
(*HWLineIntrHandler)(PVOID pHead);
VK32串口扩展WinCE驱动说明文档 Rev 1.0
4 of 13
ULONG
BOOL
BOOL
VOID
VOID
VOID
VOID
BOOL
BOOL
VOID
VOID
BOOL
ULONG
VOID
VOID
VOID
VOID
BOOL
BOOL
BOOL
(*HWGetRxBufferSize)(PVOID pHead);
(*HWPowerOff)(PVOID pHead);
(*HWPowerOn)(PVOID pHead);
(*HWClearDTR)(PVOID pHead);
(*HWSetDTR)(PVOID pHead);
(*HWClearRTS)(PVOID pHead);
(*HWSetRTS)(PVOID pHead);
(*HWEnableIR)(PVOID pHead, ULONG BaudRate);
(*HWDisableIR)(PVOID pHead);
(*HWClearBreak)(PVOID pHead);
(*HWSetBreak)(PVOID pHead);
(*HWXmitComChar)(PVOID pHead, UCHAR ComChar);
(*HWGetStatus)(PVOID pHead, LPCOMSTAT lpStat);
(*HWReset)(PVOID pHead);
(*HWGetModemStatus)(PVOID pHead, PULONG pModemStatus);
(*HWGetCommProperties)(PVOID pHead, LPCOMMPROP pCommProp);
(*HWPurgeComm)(PVOID pHead, DWORD fdwAction);
(*HWSetDCB)(PVOID pHead, LPDCB pDCB);
(*HWSetCommTimeouts)(PVOID pHead, LPCOMMTIMEOUTS lpCommTO);
(*HWIoctl)(PVOID pHead, DWORD dwCode,PBYTE pBufIn,DWORD dwLenIn,
PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut);
} HW_VTBL, *PHW_VTBL;交待了上述两个结构以后我们来看看具体的代码,为保障对系统架
构的清晰认识,我们将 MDD 的代码和 PDD 的代码分开进行分析。
MDD 部分
由于串口驱动由 Device.exe 直接调用,所以 MDD 部分是以完整的 Stream 接口给出的. 也就具
备基于 Stream 接口的驱动程序所需的函数实现,包括 COM_Init,COM_Deinit
,COM_Open,COM_Close ,COM_Read ,COM_Write, COM_Seek,, COM_PowerUp, COM_PowerDown,
COM_IOControl 几个基本实现。由于串口发送/接收的信息并不能定位,而仅仅是简单的传送,所以
COM_Seek 仅仅是形式上实现了一下。
COM_Init
COM_Init 是该驱动的初始化函数,在设备管理器加载该驱动后首先调用,用于初始化所需的变
量,硬件设备等资源。该过程分配代表设备硬件实例的数据结构,并通过硬件抽象接口 HWInit 初始
化硬件。同时该函数会调用 InterruptInitialize 为接收内核中的逻辑中断创建相应事件并初始化临界
区。该函数还需要得到硬件缓冲器的物理地址和获知该缓冲器的大小(该冲器最小为 2K)。最后它将
建立相应的缓冲作为接收的中介。下面我们来看这个函数的实现过程。 在函数中定义了两个重要的
变量。pSerialHead 和 pHWHead.前者用于描述相应的串口的状态,后者则是对应硬件的数据抽象。
首先为 pSerialHead 分配空间和初始化链表和临界区等数据并同时为接收和发送中断创建事件。然后
再从注册表中获得当前的注册项值(由于 device.exe 是根据注册表键值来调用驱动的,当前键注册表
项指的就是与上述键值在同一根下的注册项)。得到 DeviceArrayIndex、Priority256 键下的具体值后
就可以调用 GetSerialObject(在 PDD 中实现)来获得具体的 HWObj 对象,并通过该对象来调用硬件初
始化函数了。(由于在这里已经对硬件进行了初始化,之后的返回都需要调用 COM_Deinit 来完成。)
由于硬件初始化(实际的驱动初始化代码)已经得到执行这个时候就只有分配和初始化缓冲的工作需
要做了。所以调用 HWGetRxBufferSize(PDD 代码)来获取 PDD 中设定的缓冲大小,并根据返回的具
VK32串口扩展WinCE驱动说明文档 Rev 1.0
5 of 13
体值分配缓冲。最后如果 BindFlags 被设定为 THREAD_AT_INIT 就再调用 StartDispatchThread 启动
分发线程(实际的 IST)。这样就完成了系统初始化的操作。
COM_Deinit
当驱动被称被卸下的时候该事件启动,用作与 COM_Init 相反的操作。这个过程大致会释放驱动
中所使用的资源,停止期间创建的线程等操作。具体说来,大致为停止在 MDD 中的所有 IST,和释
放内存资源和临界区等系统资源。同时还需调用 HWDeinit 来释放 PDD 中所使用到的系统资源。
COM_Open
COM_Oepn 在 CreateFile 后被调用,用于以读/写模式打开设备,并初始化所需要的空间/资源
等,创建相应的实例,为后面的操作做好准备。这里的代码相对比较容易,下面就简单讲一下。既
然是初始化,肯定就免不了对参数的检查。首先检查通过 COM_Init 返回的 pHead 结构是否有效,
这里虽然没有显式的在这两个函数之间传递参数,而是在设备管理器的内部传递这个参数的。
之后是检查文件系统传递过来的 Open 句柄中的 Open 模式是否有效,这个参数由应用程序产生,通
过文件系统直接传递到驱动。之后就开始初始化的操作,在这里将会建立相应的 HW_OPEN_INFO
实体。下面和为该结构的定义。
typedef struct __HW_OPEN_INFO {
PHW_INDEP_INFO pSerialHead;
// @field Pointer back to our HW_INDEP_INFO
DWORD
AccessCode;
// @field What permissions was this opened with
DWORD
ShareMode;
// @field What Share Mode was this opened with
DWORD
StructUsers;
// @field Count of threads currently using struct.
COMM_EVENTS
CommEvents;
// @field Contains all in…. handling
LIST_ENTRY
llist;
// @field Linked list of OPEN_INFOs
} HW_OPEN_INFO, *PH
结构中的第一个参数指向我们前面提到的 HW_INDEP_INFO 结构,第二个参数为操作权限码,也就
是 READ/WRITE 这类的权限。第三个参数为共享模式,以确定是否支持独占。这两个参数都是与文
件系统的内容对应的。而 CommEvent 则对应于本实例的事件。由于驱动架构支持多个 OPEN 操作实
例的存在,所以这里维护了一个链表来联系这些结构。在这里由于 IST 的启动可以在 COM_Init 和
COM_Open 中进行,还有处理器启动 IST 的内容。准备好 HW_OPEN_INFO 结构后就可以调用
HWOpen(PDD)来进行 PDD 所需的 Open 操作了。Open 操作完成后调用 HWPurgeComm(PDD)来处
理(取消或等待)当前仍在通讯状态的任务。然后重置软件 FIFO 就基本完成了 COM_Open 的动作
了。
事实上这里主要是对所需的数据结构进行处理,对于硬件的具体操作都留给 PDD 去做了,MDD 所
维护的仅仅是一个架构性的代码。Open 操作完成后,驱动就进入了工作状态这个时候。
COM_Close
COM_Close 为与 COM_Open 相对应的操作。这期间的目的是释放 COM_Open 所使用的系统
资源,除此以外如果在 COM_Open 期间创建了相应的 IST 还需要停止该线程,在最后将该
HW_OPEN_INFO 脱链。这样一来驱动状态就得以恢复。当然这期间还做了一写避免线程竞争的处
理,使得代码看起来不是那么简单。
StartDispatchThread/StopDispatchThread
这两个函数都不是 Stream 所需要的标准接口,但却是中断服务程序所需的 IST 启动和关闭的
手段,所以在这里顺便说一下。
StartDispatchThread 函数用于启动 IST,主要的工作为调用 InterruptInitialize 将系统中断与相
VK32串口扩展WinCE驱动说明文档 Rev 1.0
6 of 13
应的事件联系起来。并启动 SerialDispatchThread 作为 IST.其中调用了叫做 InterruptDone 的函数,该
函数会调用 OAL 中的 OEMInterruptDone 来完成中断的响应。
StopDispatchThread 用于与 StartDispatchThread 相反的操作。停止的过程相对要复杂一些,
该函数首先设定当前线程的优先级与分发线程相同,以便于在停止该线程的动作不会比释放内存的
动作快以避免出错。停止的动作是让线程主动完成的,具体的方法是提交表示位 KillRxThread 然后
通过 Sleep 请求调度,待到 IST 自己停止。这个时候由于 IST 已经停止所以在程序的最后调用
InterruptDisable 来屏蔽中断。
SerialDispatchThread/ SerialEventHandler
SerialDispatchThread/ SerialEventHandler 就是串口驱动的中断分发程序(也就是 IST 的所在)。
整个 IST 被分开写成两个部分---循环主体和事件处理程序。循环主体 SerialDispatchThread 内容相对
比 较 简 单 , 反 复 等 待 串 口 事 件 并 调 用 SerialEventHandler 对 具 体 的 中 断 进 行 处 理 , 直 到
pSerialHead->KillRxThread 被设置后退出。SerialEventHandler 为中断处理的具体实现,程序在获得
串口事件后运行,目的在于对中断进行进一步的判断并执行相应的处理。
下面参考两个结构体来完成接受和发送中断服务的分析。我们先来看 RX_BUFFER_INFO 结构。
typedef struct __RX_BUFFER_INFO {
ULONG Read;
/* @field Current Read index. */
ULONG Write;
/* @field Current Write index. */
ULONG Length;
/* @field Length of buffer */
BOOL DataAvail;
/* @field BOOL reflecting existence of data. */
PUCHAR RxCharBuffer;
/* @field Start of buffer */
CRITICAL_SECTION CS;
/* @field Critical section */
} RX_BUFFER_INFO, *PRX_BUFFER_INFO;
用该结构的原因是在驱动内部维护了一个缓冲器用作驱动和应用程序之间的缓冲见下图.
可以看到在硬件内部已经有一个 FIFO 缓冲器,这保障了驱动的数据接收,但由于应用不一定能保障
在驱动获得数据后及时将数据取走,因此在驱动内部维护了另外一个缓冲器。在 RX_BUFFER_FIFO
结构中的 read 成员为 MDD 取数据时在 FIFO 的位置标志,而 PDD 向软件写入数据的位标则对应被
称作 Write,DataAvail 用作表示缓冲器内的数据是否有效。而 RxCharBuffer 则是指向软件 FIFO 的指
针。当收到数据的时候就将 write 标示往上递增,而程序向驱动取数得时候 Read 递增,这样就可以
根据 Read 和 Write 成员来维护这个 FIFO。有了这个基本思路垫底我们接着看 RX 的中断服务具体如
何实现。这间还会涉及到流控制的成分。
接收分支:在接收分支的开始计算软件缓冲器的剩余空间,如果有剩余的空间的话直接调用
HWRxIntrHandler(PDDa 实现)来从硬件缓冲区获取剩余空间大小的数据,若已无剩余空间则建立一
个 16byte 的临时缓冲区,将数据读入该区域,实际上这个缓冲区在后面根本就不会被读取所以这些
数据全部丢失掉了这也就自然需要统计硬件/软件缓冲导致的数据丢失(接收不及时导致)。接下来就
是所谓 XFlow 的流程了,所谓 XFlow 就是我们上面提到的软件流控制。也就是用软件的方法来协调
发送和接收端的收发,保障数据的完整接收。当接收到 XOFF/XON 标记的时候由于这个标记本身不
数据数据而是控制标志,所以要讲后面的数据全部前置一位,覆盖掉 XON/XOFF 的位置。同时还需
要根据标示的具体状态来设置 DCB 结构中的控制标示来控制数据收发流程。如果是 XON 标志,还
需要在处理完接收流程后恢复发送流程。接收的动作会改变 write 标记的位置,这里需要重新计算该
标示。至于硬件流控制的流程中该驱动模型是以缓冲器的 75%为分位点来起停收发的,可用的硬件
连线可以是 DTR,也可以是 RTS(模式相同仅仅是连线不同)
,这里的操作很简单,仅仅是通过计算
缓冲器的存储状态来清除该标志就完成了硬件流控制的操作。由于在此过程中 IST 与其他部分是同
步执行的,所以这个时候如果使用 XFlow 可能还会需要做一次安全检查。这样接收的流程就结束了。
发送分支: 我们同样来看看 TX_BUFFER_INFO 结构,看样子似乎该结构维护了一个和 TX 缓冲类
VK32串口扩展WinCE驱动说明文档 Rev 1.0
7 of 13
似的缓冲区,但事实上这个缓冲区域是不独立存在的,发送的流程因为可以直接使用所需发送的数
据的存储区域来作为发送缓冲,所以这个缓冲没有独立存在的必要。由于使用其它进程的数据区域,
所以这里增加了权限控制项的成分,用于突破进程间的访问限制。
typedef struct __TX_BUFFER_INFO {
DWORD Permissions;
/* @field Current permissions */
ULONG Read;
/* @field Current Read index. */
ULONG Length;
/* @field Length of buffer */
PUCHAR TxCharBuffer;
/* @field Start of buffer */
CRITICAL_SECTION CS;
/* @field Critical section */
} TX_BUFFER_INFO, *PTX_BUFFER_INFO;
下面来看看代码的具体内容。首先是对 OpenCnt 的检查,也就是设备是否被打开。若当会话已经关
闭也就没有必要继续将数据送出了,并同时重置缓冲器的各个标志位。整个流程比较简单,在需要
流控制时设置 RTS 或检查 Xflow 的情况后将数据送入硬件缓冲器.如果在没有数据需要发送的情况下
简单的清除中断标示并发出发送结束事件就可以了。至于 SetProcPermissions 设置的目的在于获得访
问其它线程数据空间的手段。
至于所谓的 Modem 和 Line 的情况则全部交给 PDD 来处理,我们后面再讨论。在这些全部都处理完
了以后如果前面处理了接收,则发出接收(有可用的数据用于接收)的消息,让程序开始接收。后
面 还 跟 进 了 一 个 EvaluateEventFlag 函 数 , 这 个 函 数 用 于 产 生 标 准 的 Communication Events
EV_RXFLAG,而且由于该驱动程序本身支持 mult-open 模式,所以需要将该事件送发到所有的实例
中去。在 ist 期间有一些互锁、临界区的操作,因为不影响流程,同步化的内容这里我没有提。中断
服务的分析大致就是如此,没有涉及到逻辑环节在后面的 PDD 部分再进行讨论。
COM_Read
COM_Read 是获取串口所接收到数据的操作,在前面的 IST 中没有看到对 RX buffer 进行修改
Read 标记的操作,也就是这儿来完成的。该函数有三个参数,第一个参数是从上面的 COM_OPEN
通过设备管理器交换来的,后两个参数与文件系统的使用方法完全一样,一个是接受缓冲指针,另
一个是长度。代码的开始照样是例行公事的参数检查,包括对存取权限,OpenCnt 等。之后计算超
时时间,如果设定了超时读取动作会在超时后返回,不管是否读到了足够长度的数据。随后就是简
单对软件缓冲进行读取的操作了,读取的操作是在 RX_CS 中完成的。下面要处理器的主要就是几种
异常的情形,读取过程中设备被关闭/取消读取和超时。最后在读取的过程中需要处理的就只是流控
制的成本了。首先是软件流的情形,如果缓冲的状态由高于分位点至分位点以下就发出 XON 标记,
启动发送端的发送。而硬件流的情形无论是 RTS 还是 DTR 与软件流的相类似,同样由一个分为点
(50%)来决定发出启动发送端的信号,仅仅是这里使用的具体措施的不同。这些硬件信号的发出都是
由 PDD 来完成的,其中包括 HWSetRTS 和 HWSetDTR(2 选一)。至此 Read 的流程就结束了。
COM_Write
COM_Write 是与 COM_Read 相对应的操作。所传递的参数的形式也是很相似的,仅仅是数据
流向的不同。在程序的开始,同样也是参数检查,内容与 COM_Read 一致。在数据检查完成之后进
入临界区(保障多线程下的独占)将送入的目标地址和长度设置为 TX buffer,待到数据发送完成事件
后调用 DoTxData 来启动发送。这里启动发送的目的在于获得硬件中断维持发送流程。在这里
DoTxData 是作为两种状态来执行的,在通过 COM_Write 的执行的过程中是在 device.exe 所创建的
线程空间内执行的,但由系统中断事件主动启动的过程中属于 IST 本身的的进程空间,这样在
COM_Write 中 调用 DoTxData 之前设 置的 权限 代码 (由 GetCurrentPermissions 获 得 )就 可以 由
TxBufferInfo 传递到 IST 中去使得中断过程也具备了访问缓冲的权限(结合前面说明 IST 的流程)。当
提交中断处理发送后待到 pSerialHead->hTransmitEvent 被设置或是异常或超时后就结束了发送流程,
VK32串口扩展WinCE驱动说明文档 Rev 1.0
8 of 13
在这部分的最后。与 COM_Read 类似需要处理一些异常情况,当然如果使用了硬件流控制还需要在
这里清除掉发送请求信号,当这些状态处理完成以后发送 EV_TXEMPTY 事件通告所有 open 的句柄
发送结束就完成了该部分的流程。
COM_PowerUp/ COM_PowerDown
这两个函数的调用都由 CE 的电源事件来引发,MDD 并没有对这两个函数进行处理,仅仅是
将其传递给 PDD。
COM_IOControl
该函数用于实现向设备发送命令的功能。由于代码本身没有什么流程或逻辑性可言,全都是单
独的实现,下面就用列表的方式大致的说一下这些命令字和其实现。
Command Note
IOCTL_PSL_NOTIFY 在调用驱动的进程退出时产生,并不是串行驱动专有的 IO 命令。这里会调
用 ProcessExiting 函数进行处理。这个函数的内容放到后面来看。
IOCTL_SERIAL_SET_BREAK_ON 中断(暂停)serial 当前的发送或是接收,具体实现在 PDD 中
IOCTL_SERIAL_SET_BREAK_OFF 从中断(暂停)状态恢复,具体实现在 PDD 中
IOCTL_SERIAL_SET_DTR 将 DTR 引线拉高。(直接调用 PDD 实现)
IOCTL_SERIAL_CLR_DTR 将 DTR 引线拉低。(直接调用 PDD 实现)
IOCTL_SERIAL_SET_RTS 将 RTS 引线拉高。(直接调用 PDD 实现)
IOCTL_SERIAL_CLR_RTS 将 RTS 引线拉低。(直接调用 PDD 实现)
IOCTL_SERIAL_SET_XOFF 软件流模式下中止数据发送(Xflow 控制)
IOCTL_SERIAL_SET_XON 软件流模式下启动数据发送(XFlow 控制)
IOCTL_SERIAL_GET_WAIT_MASK 获取当前的事件对象
IOCTL_SERIAL_SET_WAIT_MASK 设置事件对象,这个过程相对比较麻烦,要将当前获得的事
件对象 mask 设置到所有的 Open 实例中,这和前面的 EvaluateEventFlag 过程相似。
IOCTL_SERIAL_WAIT_ON_MASK 等 待 与 提 供 的 事 件 相 同 的 事 件 发 生 , 实 现 实 体 是
WaitCommEvent 后面再讨论。
IOCTL_SERIAL_GET_COMMSTATUS 清除异常并返回当前状态(由 PDD 实现)
IOCTL_SERIAL_GET_MODEMSTATUS 获取 modem 状态(由 PDD 实现)
IOCTL_SERIAL_GET_PROPERTIES 获取通讯****************(由 PDD 实现)
IOCTL_SERIAL_SET_TIMEOUTS 设置超时时间(包含 PDD 实现)
IOCTL_SERIAL_GET_TIMEOUTS 获取超时时间
IOCTL_SERIAL_PURGE 清除制定的发送或接收缓冲内的数据(含 PDD 实现)
IOCTL_SERIAL_SET_QUEUE_SIZE 设置消息队列大小
IOCTL_SERIAL_IMMEDIATE_CHAR 为扩展功能,在发送数据前设置一个标志数
IOCTL_SERIAL_GET_DCB 获取 DCB 数据结构
IOCTL_SERIAL_SET_DCB 设置 DCB 数据结构
IOCTL_SERIAL_ENABLE_IR 启动红外模式(由 PDD 实现)
IOCTL_SERIAL_DISABLE_IR 禁用红外模式(由 PDD 实现)
3.驱动的使用
使用时,将附件解压至 BSP 的 drivers 文件夹中,并修改 drivers 文件夹下的 dirs 文件添加
UARTExpander 文件夹的路径。接着,将驱动文件夹内 platform.reg 和 platform.bib 包含或添加里面的
内容到 BSP 中的 platform.reg 和 platform.bib 中即可。
VK32串口扩展WinCE驱动说明文档 Rev 1.0
9 of 13
4.驱动的移植
若要移植到其它 CPU 平台中,重新实现 vk3268 文件夹内的读写函数即可。至于中断挂钩,不同平
台大致有两种方法,MX31 使用动态申请的方法,如果目标平台也是使用这种方法刚不用更改相关
代码。若是使用另一种方法,则可在注册表中指定中断,并更改 com_exp.c 相应初始化代码即可。
其它的代码都与硬件平台无关。
5.wince5 与 wince6 串口驱动的区别
将此驱动移植至 wince6 时,由于 wince6 已经将串口驱动作了一些优化改动,移植时需要注意。
详细的修改请对比 public 下两个驱动的区别。需要注意的地方有:
1.WINCE6 弱化的 SL_Reset 函数,增加了 SL_PreDeInit;
2.WINCE6 的 isr16550.c 的 isr16550 函数增加了 IOCTL_KLIB_INTERRUPTDONE 的调用;
移植时,建议使用 WINCE6 下的串口驱动为原型,将 WINCE5 驱动中有关 VK 驱动的修改部分
移植到 WINCE6 驱动原型中,会更加方便些。
6. 总结
在驱动实现方面,串口的 MDD 并没有什么特别的之处,在掌握了硬件行为和应用软件行为后很容
易能读懂其间的代码。
7. 附:在 wince5 中使用 Intallable ISR 时 OEMInterruptHandler
需要注意以下代码:
// if IRQ assigned to this GPIO is defined
if (irq != OAL_INTR_IRQ_UNDEFINED)
{
OAL_IRQ_DISABLE(irq);
// First find if IRQ is claimed by chain
sysIntr = NKCallIntChain((UCHAR)irq);
//fix the Intallable ISR error according to the MS documents.
//If the installable ISR returns SYSINTR_NOP,
//the BSP interrupt handler code will re-enable the interrupt and
//return SYSINTR_NOP.
if (sysIntr == SYSINTR_NOP)
{
OAL_IRQ_ENABLE(irq);
return SYSINTR_NOP;
}
VK32串口扩展WinCE驱动说明文档 Rev 1.0
13
10 of
if (sysIntr == SYSINTR_CHAIN || !NKIsSysIntrValid(sysIntr))
{
// IRQ wasn't claimed, use static mapping
sysIntr = OALIntrTranslateIrq(irq);
}
}
else
......
VK32串口扩展WinCE驱动说明文档 Rev 1.0
13
11 of