WinCE串口驱动分析
扫描二维码
随时随地手机看文章
串行通讯接口是目前十分流行的通讯接口之一,串口通讯也已是普遍的标准而被大家广为熟悉。
基本架构
在WinCE中,串口的驱动实现是有固定模型的,其串口模型遵循ISO/OSI网络通讯模型。在典型的应用中,serialAPI与间接通过TAPI或直接与ActiveSync交互,组成CE网络的一部分。其实整个驱动模型是相当复杂的,好在驱动仅仅使用到SerialAPI这一层,在这个层次上串口的行为相对比较简单。在WinCE中,串口驱动模型是作为Stream来实现的(即:流设备驱动),其架构如图所示:
串口驱动本身分为MDD层和PDD层。
MDD提供框架性的实现,负责提供OS所需的基本实现,它对上层的设备管理器,提供了标准的流设备驱动接口(COM_xxx);而PDD提供了对硬件操作相应的代码,它实现了HWOBJ结构及结构中若干针对于串口硬件操作的函数指针。
DDSI是指MDD与PDD两个部分之间的接口,这个接口是人为的规定的,在串口驱动中实际上就是指HWOBJ,PDD层会传给MDD层一个HWOBJ的结构指针,这样MDD层就可以调用PDD层的函数来操作串口。在实际的驱动应用中仅仅需要实现HWOBJ相关的一系列函数,而无需从驱动顶层完全开发。
通常的串行连接有3wire和9wire两种。3wire的接线方式下定义了发送、接收和地三根连接。而在9wire中将串行连接定义为如下形式。
针号 |
1 |
2 |
3 |
4 |
[!--empirenews.page--]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使用。有了专门的硬件流控制引脚也就使得流控制成为可能,以完成收发两端的匹配使得数据可以可靠的传输,即实现了流控制,保障了数据传输的完备性。
其他几个引脚都是与modem相关的,DSR数据装置准备好用于表明MODEM处于可以使用的状态。
函数分析
1、HWOBJ
HWOBJ是相应的硬件设备操作的抽象集合,实现了对串口硬件的操作,并在MDD层被调用。
typedef struct __HWOBJ {
ULONG BindFlags;.
DWORD dwIntID;
PHW_VTBL pFuncTbl;
} HWOBJ, *PHWOBJ;
其中,BandFlags用于控制MDD层指定IST的启动时间,MDD正是通过这些函数来访问具体的PDD操作。
dwInitID是系统的中断号。
pFuncTbl则是指向一个PHW_VTBL结构,该结构中包含一个函数指针列表,这些函数指针指向串口硬件操作函数,用于操作串口。
2、MDD
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_Oepn在CreateFile后被调用,用于以读/写模式打开设备,并初始化所需要的空间/资源等,创建相应的实例。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函数返回的Handle。pSourceBytes指向一个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控制,他会被应用层的一些串口函数调用来获得或者设置串口的状态。
3、PDD
实际上,在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层的函数主要是实现了对串口硬件的操作,函数不少,可以参考以下列表:
序号 |
函数[!--empirenews.page--] |
说明 |
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给等待的线程。
switch(xxxx){
...
case IOCTL_SERIAL_WAIT_ON_MASK:
{ if(dwBufOutSize < sizeof(DWORD) ||
WaitForSingleObject(g_hEventComm,INFINITE) == WAIT_TIMEOUT)
{ *pBytesReturned = 0;
return FALSE;
} else {
InterlockedExchange(reinterpret_cast
*pBytesReturned = sizeof(DWORD);
return TRUE;
}
}...
多串口扩展
在WinCE应用环境中,对扩展的多串口的编程方法,实际上与标准的串口应用程序完全一样。[!--empirenews.page--]
注意在打开串口号大于9的串口时,需要使用“\\$device\\COMxx”,而不是通常的“COMx:”。
考虑到共享中断的异步特性,各个串口可能同时请求中断,从而产生极高的中断频率,所以建议客户把低波特率的串口通道,如9600bps或以下的波特率,配置在扩展串口上,以均衡CPU对各个硬件设备的开销;相应地把需要使用高波特率的通道配置到嵌入式主板自带的串口通道上,比如:EM9360的COM2-COM7,这些串口均配置有独立的硬件中断。
在WinCE标准的串口驱动程序中,为每个串口分配了2KB的接收数据缓冲区,所以各个串口上层处理线程可参考buffer的深度,采用合适的响应方式,以最大限度的避免线程空转所带来的CPU时间的无谓消耗。
WinCE下的驱动开发减少了开发者的工作量,不过其中还存在很多细节性的问题,动手实践中就能慢慢体会到了。