Intel PXA27x平台中的UART接口驱动 (摘录曾慧鹏)

时间:2021-07-20 04:49:03
 

一、铺垫

UART是一种非常古老但是却一直保存在现有计算机系统中的接口,它可以把处理器对数据的并行处理转换成为串行的数据加以传输,这种接口非常简单但是依然特别常用,目前的嵌入式微处理器都Build-In了这种接口。

本人有幸在项目中接触了PXA270处理器,针对其中的UART做过一些研究,想通过本文和大家分享。

PXA270中有三个UART接口,分别叫FFUARTBTUARTSTUART,可以同时进行三个串口通讯。

Intel PXA27x平台中的UART接口驱动 (摘录曾慧鹏)

 

1 FFUART的外部接口

其中FFUART是一个接口最齐全的实现了所有的UART特性的接口,另外两个接口都是FFUART的精简版,意思就是对一些不需要的信号线进行了删减。图1FFUARTFullFunction)的外部接口图,拥有8根符合 16550A 标准的信号线(地线除外)。FFUART可以用来处理任何和UART兼容的通信;接下来是BTUARTBlueTooth),从它的名字可以知道它是推荐给BT(蓝牙)使用的接口,它拥有常用的四根信号线RXD/TXD/CTS/DTR,这四根线分别负责接受发送功能和流控制功能,流控制是蓝牙所需要的,其省略的其他信号线都是modem相关的;接下来的STUARTStandard)最简单,仅仅拥有RXD/TXD也就是收发信号线。

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所示:

 

 Intel PXA27x平台中的UART接口驱动 (摘录曾慧鹏)

 

3 PXA27XUART接口寄存器

所有的寄存器会通过一个名为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作为这个类的子类去实现自己和标准不符的部分或是参数的调整,微软就是这样做的。

Intel PXA27x平台中的UART接口驱动 (摘录曾慧鹏)

4 UART驱动中类的派生关系

从图4中可以看到,描述UART的根类是CSerialpdd,它从CRegistryEdit类派生出来是为了方便对注册表进行读写,因为配置UART驱动的信息往往是保存在注册表中的。

       CPdd16550是一个最重要的类,从名字可以分析得出,这个类实现了16550标准的UART接口。它从CSerialpddCMiniThread派生,从CMiniThread派生的原因是在这个类中要实现对中断的捕获,所以要有监视线程。还有一个重要之处,为了方便操作UART接口中的寄存器,其拥有一个CBulReg16550类型的成员变量,该类封装了对PXA27xUART寄存器的操作,其派生于CReg16550

       接下来是CBulpdd16550,这个类是16550接口在PXA27x中的具体实现,派生于CPdd16550

到这里为止,微软的代码算是完成任务了,接下去是我们OEM要写的代码了。图四最右下脚的三个类就是需要我们在BSPdrivers中去实现的,按照微软的设计,我们必须指明它们派生于CBulpdd16550,微软的MDD层代码会在init的时候去做和我们这部分代码的连接,方法就是从注册表中读该UARTFF/BT/ST,比如是FFUART的话它会用new CBulpdd16550FUART()的方式得到一个CSerialpdd *的指针,然后用这个指针可以协调所有的UART的操作,我们OEM可以很方便的控制UART参数和传输中的数据处理,因为我们仅仅需要去重新实现(修改)父类中的一些虚函数就行了,通常需要实现的虚函数有Init() Open() Close() SetDefaultConfiguration()ReceiveInterruptHandler(…)PIO_ReceiveInterruptHandler(…)等。

 

       现在回过头来看看所有的驱动在文件上面的调用关系吧

 

 

 Intel PXA27x平台中的UART接口驱动 (摘录曾慧鹏)

 

5 UART驱动库之间的调用关系

       5中,在虚框以内的都是微软public下面的代码,包括MDD和部分PDDMs2_serial.dll是我们OEMBSPdrivers目录下编写的代码,它是我们最终的产品,它直接包含了pxa27x_uart.lib

pxa27x_uart.lib开始的代码都是微软的,pxa27x_uart.lib实现了CBulPdd16550这个类,然后它必须包含它的父类的代码如serpddcm.liboo16550.lib等,然后因为它还会用到和pxa27x相关的其他接口如DMAGPIO等,所以它还要包括pxa27x_dma.libpxa27x_xllp.lib

一直忘记强调一点,UART驱动是标准的流驱动,所以其对外的接口肯定是类似COM_Init COM_Open这种的。这个接口属于MDD层的最上端,它们的实现在com_mdd2.lib中,通过并入pxa27x_uart.lib来包含在Ms2_serial.dll中,在Ms2_serial.dlldef文件中,必须指明对这些接口的输出。要具体研究它们的包含关系,请查看它们目录下面的sources文件。

到这里也许你还不是很清楚整个UART驱动的架构,好再来一张连贯点的:

Intel PXA27x平台中的UART接口驱动 (摘录曾慧鹏)

6  UART驱动工作流程

 

 

别急着看图,跟着我的描述看图 :)

WinCE/Magneto启动的时候会到,device.exe会根据注册表设定去调用Ms2_serial.dll暴露出来的函数,首先是DLLENTRY,不去考虑,然后是COM_init,这个甚是重要。

       介绍到COM_init我想偷点懒,网上已经有前辈写好了。

开始引用

COM_Init先分配一个HW_INDEP_INFO结构体,这个结构体是独立于串口硬件的头信息(MDDPDDSER16550都包含自己独特的结构体,具体的结构体定义请参见串口驱动源码),分配之后再初始化结构体中每个成员,初始化结构体后调用 OpenDeviceKey((LPCTSTR)Identifier)打开HLM/Drivers/Active/xx/Key包含的注册表路径,在这里路径一般为HLM/Drivers/BuiltIn/Serial,即串口的驱动程序信息在注册表中所处的位置。COM_Init接着在HLM/Drivers/BuiltIn/Serial下查询DeviceArrayIndexPriority256的值,Priority256指定了驱动程序的优先级,如果没有就用默认的优先级。接下来调用GetSerialObject(DeviceArrayIndex),这个函数由PDD层定义,返回HWOBJ结构体,这个结构体主要包含PDD层和SER16550定义的函数的指针。

也就是说MDD通过调用这个函数才能调用底层实现的函数。接下来的大多数工作都是调用底层函数实现初始化。第一个调用的底层函数SerInit主要设置由用户设置的硬件配置,例如线路控制、波特率。它调用Ser_GetRegistryData函数得到保存在注册表中的硬件信息,Ser_GetRegistryData在内部调用系统提供的DDKReg_GetIsrInfoDDKDDKReg_GetWindowInfo函数得到在HLM/Drivers/BuiltIn/Serial下保存的IRQSysIntrIsrDllIsrHandlerIoBaseIoLenIRQ是逻辑中断号,IsrDll表示当前驱动程序的可安装ISR所在的DLL名称,IsrHandler 表示可安装ISR的函数名称。

引用结束

       之所以说叫MDD就是因为要把它做成和平台无关,要实现platform independent的方法蛮多,最常用的莫过于回调。MDD固定有几个函数,固定的目的就是方便上面来调,它们的指针都被一个叫HW_VTBL的结构题管理了起来,OEMInit最大的任务就是要把它们具体到干活的函数上去,这样说还比较抽象,简单说就是要把上层对UART的指令翻译到我们刚才讲的的那些类的成员函数上去,让下面的类知道要干什么,怎么干。

       具体一点,OEMInit会调用GetSerialObject(DeviceArrayIndex)得到最终干活的那个类的实例的指针,这样MDDPDD就连接起来了。连接起来后OEMInit回去调用类里面的和初始化相关的操作,图7OEM_Init的流程图。

Intel PXA27x平台中的UART接口驱动 (摘录曾慧鹏)

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,看图说话

Intel PXA27x平台中的UART接口驱动 (摘录曾慧鹏)

这个函数会在上层调用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.

别的就不介绍了,最后看一张中断线程的初始化流程图。

Intel PXA27x平台中的UART接口驱动 (摘录曾慧鹏)