一、铺垫
UART是一种非常古老但是却一直保存在现有计算机系统中的接口,它可以把处理器对数据的并行处理转换成为串行的数据加以传输,这种接口非常简单但是依然特别常用,目前的嵌入式微处理器都Build-In了这种接口。
本人有幸在项目中接触了PXA270处理器,针对其中的UART做过一些研究,想通过本文和大家分享。
PXA270中有三个UART接口,分别叫FFUART、BTUART和STUART,可以同时进行三个串口通讯。
图1 FFUART的外部接口
其中FFUART是一个接口最齐全的实现了所有的UART特性的接口,另外两个接口都是FFUART的精简版,意思就是对一些不需要的信号线进行了删减。图1是FFUART(FullFunction)的外部接口图,拥有8根符合
FFUART |
BTUART |
STUART |
FF_RXD |
BT_RXD |
ST_RXD |
FF_TXD |
BT_TXD |
ST_TXD |
FF_CTS |
BT_CTS |
|
FF_RTS |
BT_RTS |
|
FF_DSR |
|
|
FF_DTR |
|
|
FF_RI |
|
|
FF_DCD |
|
|
图2 三个UART接口的异同
在我们的项目中FFUART/BTUART/STUART分别用于IrDA/GPS/TMC,接下来我们来关心一下每个UART里面的寄存器的情况,在PXA27X处理器中,每个UART都有一套寄存器,他们的名称和简介如图2所示:
图3 PXA27X的UART接口寄存器
所有的寄存器会通过一个名为BULVERDE_UART_REG的结构体来管理,下面简单来介绍每个寄存器的用途:
Transmit Holding Register (THR) and Receive Holding Register (RHR):
These registers are used to store transmitting and receiving data. The host writes data to THR for transmission, and reads RHR for data received by the UART.
Interrupt Enable Register (IER):
The Interrupt Enable Register is used to enable/disable different types of interrupts supported by the UART. Some of these interrupts are Receive Data Ready, Transmit Empty, Line Status REgister, and Modem Status Register.
FIFO Control Register (FCR)
:FCR is used for enabling the FIFOs, clearing the FIFOs, setting transmitter and receiver trigger levels.
Interrupt Status Register (ISR):
The Interrupt Status Register (ISR) provides the user with four interrupt status bits. Performing a read cycle on the ISR will provide the user with the highest pending interrupt level to be serviced. No other interrupts are acknowledged until the pending interrupt is serviced.
Line Control Register (LCR) and Line Status Register (LSR):
LCR is used to set the data communication format. The word length, number of stop bits,and parity type are selected by writing the appropriate bits to the LCR. Line Status Register (LSR) provides the status of the data transfer between the UART and a remote UART. This register reports framing error, parity error, overrun error, and other FIFOs status.
Divisor Latch Low (DLL) and Divisor Latch High (DLH):
DLL and DLH store the 16-bit divisor for generation of the baud clock in the baud rate generator. DLH stores the most significant part of the divisor; DLL stores the least significant part of the divisor.
为了节约时间俺就不给大家翻译了,相信没人看不懂。
二、驱动分析
串口因为太常用了所以微软早就已经把这些代码实现得很成熟了,不管是做Windows Mobile还是做WinCE哪个版本在%OSROOT%/PUBLIC/COMMON/OAK/DRIVERS/SERIAL路径下面都能找到微软提供的UART驱动源代码。
但是你也别高兴得太早,微软的代码仅仅是从框架上的实现,BSP中还是要有最底层的代码等待你去补充,你补充的内容就是告诉微软上层的代码要如何设置每一个UART的参数,比如用不用FIFO/DMA啊,波特率是多少,数据校验等等情况。微软要做的是把UART通用的特性整合起来,这样以不变应万变,达到代码和数据复用的目的,接下来让我们看看微软是怎么实现UART的代码重用的。
根据上一节的分析,PXA27X的三个UART的寄存器等于三份拷贝,区别仅仅在于它们存放在三个不同的物理地址罢了,这首先就给代码重用提供了机会,学过C++的哥们马上就想到用类的方式来描述标准UART的特性,然后每个UART作为这个类的子类去实现自己和标准不符的部分或是参数的调整,微软就是这样做的。
图4 UART驱动中类的派生关系
从图4中可以看到,描述UART的根类是CSerialpdd,它从CRegistryEdit类派生出来是为了方便对注册表进行读写,因为配置UART驱动的信息往往是保存在注册表中的。
CPdd16550是一个最重要的类,从名字可以分析得出,这个类实现了16550标准的UART接口。它从CSerialpdd和CMiniThread派生,从CMiniThread派生的原因是在这个类中要实现对中断的捕获,所以要有监视线程。还有一个重要之处,为了方便操作UART接口中的寄存器,其拥有一个CBulReg16550类型的成员变量,该类封装了对PXA27x中UART寄存器的操作,其派生于CReg16550。
接下来是CBulpdd16550,这个类是16550接口在PXA27x中的具体实现,派生于CPdd16550。
到这里为止,微软的代码算是完成任务了,接下去是我们OEM要写的代码了。图四最右下脚的三个类就是需要我们在BSP的drivers中去实现的,按照微软的设计,我们必须指明它们派生于CBulpdd16550,微软的MDD层代码会在init的时候去做和我们这部分代码的连接,方法就是从注册表中读该UART是FF/BT/ST,比如是FFUART的话它会用new CBulpdd16550FUART()的方式得到一个CSerialpdd *的指针,然后用这个指针可以协调所有的UART的操作,我们OEM可以很方便的控制UART参数和传输中的数据处理,因为我们仅仅需要去重新实现(修改)父类中的一些虚函数就行了,通常需要实现的虚函数有Init() ;Open(); Close(); SetDefaultConfiguration();ReceiveInterruptHandler(…);PIO_ReceiveInterruptHandler(…)等。
现在回过头来看看所有的驱动在文件上面的调用关系吧
图5 UART驱动库之间的调用关系
图5中,在虚框以内的都是微软public下面的代码,包括MDD和部分PDD。Ms2_serial.dll是我们OEM在BSP的drivers目录下编写的代码,它是我们最终的产品,它直接包含了pxa27x_uart.lib。
从pxa27x_uart.lib开始的代码都是微软的,pxa27x_uart.lib实现了CBulPdd16550这个类,然后它必须包含它的父类的代码如serpddcm.lib和oo16550.lib等,然后因为它还会用到和pxa27x相关的其他接口如DMA和GPIO等,所以它还要包括pxa27x_dma.lib和pxa27x_xllp.lib。
一直忘记强调一点,UART驱动是标准的流驱动,所以其对外的接口肯定是类似COM_Init ,COM_Open这种的。这个接口属于MDD层的最上端,它们的实现在com_mdd2.lib中,通过并入pxa27x_uart.lib来包含在Ms2_serial.dll中,在Ms2_serial.dll的def文件中,必须指明对这些接口的输出。要具体研究它们的包含关系,请查看它们目录下面的sources文件。
到这里也许你还不是很清楚整个UART驱动的架构,好再来一张连贯点的:
图6 UART驱动工作流程
别急着看图,跟着我的描述看图 :)
当WinCE/Magneto启动的时候会到,device.exe会根据注册表设定去调用Ms2_serial.dll暴露出来的函数,首先是DLLENTRY,不去考虑,然后是COM_init,这个甚是重要。
介绍到COM_init我想偷点懒,网上已经有前辈写好了。
开始引用
COM_Init先分配一个HW_INDEP_INFO结构体,这个结构体是独立于串口硬件的头信息(MDD、PDD、SER16550都包含自己独特的结构体,具体的结构体定义请参见串口驱动源码),分配之后再初始化结构体中每个成员,初始化结构体后调用 OpenDeviceKey((LPCTSTR)Identifier)打开HLM/Drivers/Active/xx/Key包含的注册表路径,在这里路径一般为HLM/Drivers/BuiltIn/Serial,即串口的驱动程序信息在注册表中所处的位置。COM_Init接着在HLM/Drivers/BuiltIn/Serial下查询DeviceArrayIndex、Priority256的值,Priority256指定了驱动程序的优先级,如果没有就用默认的优先级。接下来调用GetSerialObject(DeviceArrayIndex),这个函数由PDD层定义,返回HWOBJ结构体,这个结构体主要包含PDD层和SER16550定义的函数的指针。
也就是说MDD通过调用这个函数才能调用底层实现的函数。接下来的大多数工作都是调用底层函数实现初始化。第一个调用的底层函数SerInit主要设置由用户设置的硬件配置,例如线路控制、波特率。它调用Ser_GetRegistryData函数得到保存在注册表中的硬件信息,Ser_GetRegistryData在内部调用系统提供的DDKReg_GetIsrInfoDDK和DDKReg_GetWindowInfo函数得到在HLM/Drivers/BuiltIn/Serial下保存的IRQ、SysIntr、IsrDll、IsrHandler、IoBase、IoLen。IRQ是逻辑中断号,IsrDll表示当前驱动程序的可安装ISR所在的DLL名称,IsrHandler 表示可安装ISR的函数名称。
引用结束
之所以说叫MDD就是因为要把它做成和平台无关,要实现platform independent的方法蛮多,最常用的莫过于回调。MDD固定有几个函数,固定的目的就是方便上面来调,它们的指针都被一个叫HW_VTBL的结构题管理了起来,OEMInit最大的任务就是要把它们具体到干活的函数上去,这样说还比较抽象,简单说就是要把上层对UART的指令翻译到我们刚才讲的的那些类的成员函数上去,让下面的类知道要干什么,怎么干。
具体一点,OEMInit会调用GetSerialObject(DeviceArrayIndex)得到最终干活的那个类的实例的指针,这样MDD和PDD就连接起来了。连接起来后OEMInit回去调用类里面的和初始化相关的操作,图7是OEM_Init的流程图。
图7 OEM_init图释
下面是在Init过程中,我们的那些类所做的事情:
CSerialpdd:
Init the power management thread and functions.
CPdd16550:
Get irq sysintr from REGKEY;
create event and bind interrupt with the event
Map hardware such as MemBase,MemLen.
Create CReg16550 object to operate registers.
CBulpdd16550:
Read the REG setting and configure the work mode (isDMA is32bus isSIR) of the UART by Writing the FCR ABR registers.
CBulpdd16550*UART:
Init the GPIO and E-GPIO pins.
PostInit的过程
CSerialpdd
InitialEnableInterrupt(TRUE);
InitModem
CPdd16550
InitReceive(TRUE);
InitModem(TRUE);
InitLine(TRUE);
CeSetPriority(m_dwPriority256);
ThreadStart()
然后就是COM_OPEN了,看图说话
这个函数会在上层调用CreatFile的时候调用,做一些初始化的动作,具体的工作还是由那些类来实现的,它们分别干如下事情。
CSerialpdd:
Notify the UART system to D0 state.
Set Default DCB Configuration, include baudrate parity/stop bit.
Invoke virtual funcs InitLine(IER LCR),InitReceive(FCR IER),InitXmit(FCR).
CPdd16550:
No open member function. Implement the InitLine InitReceive and Init Xmit.
CBulpdd16550:
Enable UART, initIR and initDMA if need.
CBulpdd16550*UART:
Open the DC power for UART.
别的就不介绍了,最后看一张中断线程的初始化流程图。