2.3.5 函数库i8250Sio
从图2.13可以看出,函数库ttyDrv与底层函数库的接口形式为一个通用的结构接口SIO_CHAN,该接口中保存了一些硬件操作的函数指针,显而易见,对于底层函数库来说,实现结构SIO_CHAN中定义的几个函数指针对应的函数则是其最根本的目的。从下面即将分析的i8250Sio库来看,它也正是这么做的。
如图2.16。和图2.12相比,指针pSioChan从SIO_CHAIN的结构变成了I8250_CHAN,从SIO_CHAN和I8250_CHAN的对比来看,I8250_CHAN结构定义一开始就完全包含了SIO_CHAN结构中的元素,因此在内存中SIO_DRV_FUNCS函数对应的相对地址也都是完全一样的,因此可以用SIO_CHAN结构指针指向I8250_CHAN结构变量而不会出现混乱,这里依然采取了多态的思想,不过将I8250_CHAN结构定义成下列格式可以使多态的概念更加清晰。
typedef struct
{
SIO_CHAIN sioChain;
STATUS (*getTxChar) ();
STATUS (*putRcvChar) ();
void * getTxArg;
......
}I8250_CHAN;
图2.16 函数ttyDrv与i8250控制器函数库的接口
这里不再详细分析硬件操作的详细内容,重点还是放在函数库的结构分析上,如有有需要了解具体的硬件操作可以参考i8250控制芯片的数据手册。
下面还是从函数分析出发,了解函数库i8250Sio的内部结构。i8250Sio库函数主要分为三类:第一类是提供了图2.16所示函数接口的5个函数;第二类则是第一类函数的支撑函数;第三类则是中断处理函数。
1. static int i8250CallbackInstall
(
SIO_CHAN * pSioChan,
int callbackType,
STATUS (*callback)(),
void * callbackArg
)
该函数的作用主要是为结构I8250_CHAN中的函数指针getTxChar和putRcvChar执行函数及其参数。
getTxChar函数是为了从发送buffer中取出一个待发送的字符,如tyITx函数;putRcvChar函数是为了向接收buffer中存放一个刚接收的字符,如tyIRd函数。
2. void i8250HrdInit
(
I8250_CHAN * pChan
)
该函数主要完成结构I8250_CHAN中变量SIO_DRV_FUNCS * pDrvFuncs的赋值(参考图2.16),有了这个函数,通用接口就能和具体的串口通信芯片的控制函数连接起来了。此外这个函数还调用函数i8250InitChannel完成了串口通信芯片硬件的初始化。
3. static void i8250InitChannel
(
I8250_CHAN * pChan
)
主要设置串口通道的属性,如波特率、数据位、停止位、校验位等等。通过写入到相应的寄存器可以完成对属性的修改,注意在修改寄存器的时候要关闭中断,防止这时候出现的中断导致通信状态的不确定性。最后在设置完毕后打开中断。
注意,这里的options选项只是对通道状态的一个记录,并不对实际的寄存器进行控制。
4. LOCAL STATUS i8250Hup
(
I8250_CHAN * pChan
)
清除RTS(请求发送)和TDR(数据准备就绪)信号,此时不会发送数据。
5. LOCAL STATUS i8250Open
(
I8250_CHAN * pChan
)
设置RTS(请求发送)和TDR(数据准备就绪)信号,表明做好收发准备。
6. LOCAL STATUS i8250ModeSet
(
I8250_CHAN * pChan,
UINT newMode
)
填写波特率寄存器,设置波特率。
7. LOCAL STATUS i8250ModeSet
(
I8250_CHAN * pChan,
UINT newMode
)
修改通道模式设定。该驱动支持中断和轮询两种模式,并允许在两种模式下进行动态切换。
在中断模式下,接收和发送都会产生中断。如果modem控制选项有效时,CTS信号为false则不会产生发送中断。
8. LOCAL STATUS i8250OptsSet
(
I8250_CHAN * pChan,
UINT options
)
设定串口选项,不但要修改pChan->options,还要修改i8250相应的寄存器以确保一致。这些选项包括:CLOCAL, HUPCL, CREAD, CSIZE, PARENB, PARODD。
CSIZE:设置通信通道一个字节的长度,分别为5/6/7/8比特;
STOPB:可根据要求设置一个或者两个停止位;
PARENB:校验功能开启;
PARODD:奇偶校验选项,PARENB有效时该选项有效;
CLOCAL:modem status lines有效/无效。Modem status lines主要包括CTS(收到“允许发送”)、DSR(收到“数据准备就绪”)、RI(收到“振铃指示”)、RLSD(收到“载波检测”)。
9. static int i8250Ioctl
(
I8250_CHAN * pChan,
int request,
int arg
)
提供给上层调用的设备控制函数,上层函数通过调用该函数可以完成波特率设置/读取、模式设置/读取、通道选项的设置/读取以及modem control line的设置/取消等功能。这些功能主要是前面的几个函数实现的。
10. void i8250Int
(
I8250_CHAN * pChan
)
中断处理程序。8250内部包含四种类型的中断,按优先级的高低依次为:接收出错(奇偶错、溢出错、帧格式错和终止字符)中断、数据接收寄存器“满”中断、数据发送寄存器“空”中断、MODEM输入状态改变中断。8250的四级中断的优先权,是按照串行通信过程中事件的紧迫程度安排的,而且固定不变,用户可以通过中断允许寄存器进行控制。8250中断允许寄存器某一位为“1”,则对应的中断被允许,否则,被禁止。
当8250产生中断时,那么在该中断标识被清空之前所有的同级别或低优先级的中断都不会输出。清除中断标识的方法如下:
接收出错(奇偶错、溢出错、帧格式错和终止字符)中断:读取状态寄存器(LSR)即可清除;
数据接收寄存器“满”中断:从接收寄存器中读取数据即可清除;
数据发送寄存器“空”中断:向发送寄存器中写入数据或者读取IIR寄存器。
MODEM输入状态改变中断:读取modem状态寄存器即可清除中断。
如果中断处理程序在返回前没有清除掉所有的中断信号,那么将会阻碍UART向CPU发送进一步中断请求。
从代码中观察中断处理程序的逻辑是比较简单的,就是检查寄存器IIR,根据中断的类型做不同的处理即可。当中断处理完毕后并不能马上就退出,可能其中还有更低级别的中断在上一轮的检查中没有发现,因此在处理完上一轮之后需要重新检查有没有更低级别的中断,如果有的话要继续处理。此外程序中还设置了一个门限值,40,即最多进行40次检查,超过40次之后直接退出。以防止在该中断处理程序中无限循环的情况。
注意,这个函数使用了函数指针getTxChar以及putRcvChar,getTxChar函数的作用是从发送buffer中取出一个字符,putRcvChar的作用是向接收buffer中存放一个字符。ttyDrv函数库中的ttyDrvCreate函数将这两个函数指针分别设置为函数库tyLib中的tyITx和tyIRd函数(参见图2.15)。
11. LOCAL int i8250Startup
(
I8250_CHAN * pChan
)
该函数的作用是在工作在终端模式下时控制发送中断寄存器做好准备,使得在串口发送端空闲的时候发送中断信号,这样才能驱动中断处理程序发送数据。也就是说该函数并不发送数据,只是驱动了中断处理程序的运行。
当设置忽略modem状态时(CLOCAL有效),该函数设置中断允许寄存器当发送寄存器为空时产生中断通知CPU可以发送数据。
当设置非忽略modem状态时,该函数检查信号线CTS,如果CTS为允许发送,则设置中断允许寄存器的TBE位和modem状态中断位;否则只是设置modem状态中断位。这种模式增加了通信握手协议,首先只有对方允许发送才有可能发送,因此在处理的时候如果发现发送允许,则设置发送寄存器空中断有效可以控制数据发送。当然,无论是否发送允许,都要检查modem状态,因此modem状态中断是必须有效的。
12. LOCAL int i8250PRxChar
(
I8250_CHAN * pChan,
char * pChar
)
轮询的方法接收数据。首先检查lst寄存器(只检查一次),如果lst寄存器表明收到了新的数据则直接读取接收寄存器,否则就退出了。
13. LOCAL int i8250PTxChar
(
I8250_CHAN * pChan,
char outChar
)
轮询的方法发送数据,如果通信线路允许发送,且本地发送数据寄存器为空,则向发送数据寄存器写入数据。
2.3.6 函数库sysSerial
I8250Sio函数库实现了8250控制芯片的底层控制,并为上层与硬件无关的函数库提供了接口。但是还有一个任务没有完成,就是中断处理程序的安装,这部分功能主要在函数库sysSerial中完成。
sysSerial.c文件中定义了I8250_CHAN i8250Chan[N_UART_CHANNELS]变量,每个变量对应一个串口通道,此外它还定义了每个8250控制器的地址等参数,这些参数保存在结构I8250_CHAN_PARAS变量devParas。这个文件定义了三个函数。
1. sysSerialHwInit (void)
这个函数主要完成两件事情,一是给每个I8250_CHAN 结构变量i8250Chan[i]赋值,主要确定了各个寄存器地址、对应的中断向量、以及寄存器读写函数i8250Chan[i].outByte及i8250Chan[i].inByte;另一方面该函数调用函数i8250HrdInit给每个8250控制器进行初始化。
2. sysSerialHwInit2 (void)
这个函数的功能比较简单,一是调用函数intConnect安装每个8250控制器的中断处理程序,二是调用函数sysIntEnablePIC设置中断允许。
3. SIO_CHAN * sysSerialChanGet
(
int channel
)
根据返回i8250Chan[channel],也就是说根据通道号返回其结构变量。注意这里使用了类似于面向对象中的多态的概念,i8250Chan[channel]的结构原本为i8250_CHAIN,这里强制将其转变为结构SIO_CHAIN。
2.3.7 函数库usrSerial
vxWroks的代码通常遵循一个规范,就是函数库名以usr开头的函数库为用户库已进行配置的函数库,串口驱动也不例外。vxWorks提供了一个对串口驱动进行配置的接口,即函数库usrSerial。这个函数库只有两个函数,
1. static char * itos (int val)
经整数转换为字符串输出。如整数1234,函数将其转换为字符串“1234”。
2. STATUS usrSerialInit (void)
串口初始化。主要包含两个功能,一是调用函数ttyDevCreate初始化系统中的每个串口;并将其定义为终端控制接口。
到目前为止,整个串口驱动的结构都已经分析完毕了。当然这仅仅是一个静态的分析,通过对串口驱动结构的静态分析,可以了解vxWorks操作系统中串口驱动的结构组成。下面将从vxWorks操作系统中串口驱动程序的启动和应用过程对串口驱动程序进行动态的说明。