(1.西安现代控制技术研究所,西安 710065;2.中国人民解放军93811部队 保障部装备质量控制与安全监察中心,兰州 730000)
PCI-1751卡是一块基于PCI总线的拥有48路并行I/O的板卡。由于可以同时控制多路电平输入输出,该板卡广泛于工业交流/直流I/O设备监控、继电器和开关控制、并行数据传输、感应TTL信号逻辑、驱动LED指示器等环境。同时PCI-1751板卡上也集成了2个8254定时器/计数器,也可用于一些高精度定时计数的功能场景。
RTX操作系统作为Windows系统的扩展系统,受到许多高校研究单位的青睐。而很多板卡在出厂时都不提供RTX驱动程序,PCI-1751板卡也不例外。因此,本文结合研华科技公司的PCI-1751板卡,介绍RTX系统下板卡驱动的编写调试方法及一些经验,以求RTX驱动程序的开发被更多探索。
寄存器是板卡上的具有特定功能的内存存储。用户可以不了解板卡内部的具体硬件实现,但只要能理解其意义并通过地址访问到寄存器,即可实现板卡功能,故称为板卡与用户之间的软件接口。
因此,RTX下PCI板卡驱动的开发实质就是利用RTX系统函数操纵板卡上的寄存器。
开发驱动的用户需要关心两类寄存器,即PCI配置寄存器与板卡功能寄存器。
1.2.1 PCI配置寄存器
PCI配置寄存器是每块板卡寄存器的“目录”,是PCI协议预定义的256字节的内存[3]。在该寄存器中,标识了该板卡的所有有用信息,其具体内容如表1所示。
表1 PCI配置空间
1)DeviceID与VendorID:
每类板卡独一无二的属性[1],用户在遍历计算机系统中的所有板卡时,可根据这两个值,来判断该板卡是否存在于该计算机系统。
2)基地址寄存器:
用于存放寄存器映射的基地址。基地址是板卡功能寄存器的起始地址,用户可以根据基地址和偏移地址计算板卡上所有功能寄存器的地址。
1.2.2 板卡功能寄存器
板卡功能寄存器是板卡功能的软件接口,用户只需对这些寄存器置数取数,即可完成与之对应的功能。
板卡在出厂硬件手册都会附带寄存器功能说明及地址分布,这些地址都是从基地址开始有规律累加的,每个寄存器相对基地址的累加量称作偏移地址。因此,只要找到了板卡I/O基地址,所有寄存器的地址都可以很容易的推算出来。
在图1所示的计算机环境中,PCI-1751的内存基地址为0xFEBFF400,这与板卡PCI配置空间的基地址寄存器2中的值所吻合,我们可得到其基地址存放在基地址寄存器2中。感兴趣的读者可以通过该方法自己动手验证基地址是否存放于PCI配置寄存器中的基地址寄存器2中。
图1 板卡资源对话框
研华PCI-1751接口卡是一块具有48路并行DI/O输入输出卡,同时该板卡也携带3个定时器/计数器,可以完成高精度的定时计数功能。
该板卡借鉴了8255芯片的设计思路,实现了两块8255芯片的mode 0模式,共具有24x2=48路DI/O通道。同时该板卡的I/O驱动能力远超出于普通的8255芯片。
同时,PCI-1751板卡提供了断电保护功能,当所在机器遭遇突发断电又瞬时恢复的情况,板卡可以保持之前保存的通道输出值。
PCI-1751板卡上两个增强的8255芯片的48路DI/O通道被分为6个组,分别为PA0、PB0、PC0(PC0H、PC0L)、PA1、PB1、PC1(PC1H、PC1L)。每个通道组可以单独配置输入输出方向,PC0和PC1组高低字节也可单独配置,互不影响。
PCI-1751的寄存器地址列表如表2所示。
板卡上的硬件跳线可以强制配置I/O口输入输出方向。当跳线配置为软件配置模式时,需要在使用前先写入控制字。控制寄存器的偏移地址为0和7,对应Port0和Port1,其内容格式如表3所示。
表2 PCI-1751寄存器地址
表3 Port0、Port1配置寄存器
对于Port0、Port1配置寄存器,写1为输入方向,写0为输出方向。例如,只想配置PC0通道组为输入通道,其他通道均为输出通道,则应将控制字0x09(00001001B)写入偏移地址为3的寄存器中。
配置好输入/输出方向后,对相应的通道寄存器进行读/写即可完成输入/输出操作。例如读取PC0通道组,只需读取base+2的寄存器值即可。
PCI-1751板卡上携带三块8254计数器芯片,定时器连接关系如图2所示。
板卡在设计时,为了提供更多的灵活性,Timer1的CLK引脚可以通过跳线连接到外部信号源CLK1,亦可连接到Timer0的输出端。当Timer1的时钟源连接到Timer0的输出端时,相当于Timer0与Timer1串联形成一个32位的计数器。
板卡内部的定时器晶振频率为10 MHz,使用Timer0与Timer1进行定时,最大定时频率为10 MHz/2=5 MHz;最小定时频率为10 MHz/65 536/65 536=0.002 328 Hz。
图2 定时器/计数器结构图
在表2所示的寄存器列表中,特别值得关注的是偏移地址为32的中断控制/状态寄存器。该寄存器在写入时作为控制寄存器,读取时作为状态寄存器,他们使用相同的偏移地址。
1751板卡将PC00、PC04、Timer1、PC10、PC14、Timer2的输出引入到板卡的中断电路中。中断控制寄存器决定了中断源的选择、中断触发模式等设置,中断状态寄存器显示当前中断配置与触发状态,其定义如表4所示。
表4 中断控制/状态寄存器
其中:F是中断标志,作为状态寄存器时,该位表示中断是否发生;作为控制寄存器,写0是对中断标志的清除。E是上升沿/下降沿的配置,1为上升沿,0为下降沿。M1M0是中断源的选择,具体示意如表5所示。
表5 中断模式配置
例如,在本文的第四章节所介绍的实验中,欲检测PC00的上升沿中断,需将0x05写入中断控制寄存器中即可完成配置;中断服务函数中,就是通过该寄存器的D3位即可检测PC00中断是否来临。
有了这些知识储备,即可开始进行板卡驱动的开发。
在本文示例的驱动程序中,主要提供关于DI/O操作的几个重要函数,分别为打开板卡函数、中断配置函数、配置通道组函数、读通道组函数、写通道组函数、等待中断函数。通过这些函数,该板卡可以完成多路电平的输入输出以及上升沿/下降沿中断采集的功能。
PC机可能存在很多板卡,因此在打开板卡函数的实现中,主要操作为根据DeviceID和VendorID搜索PCI-1751板卡是否存在。如果搜寻到板卡,则保存板卡的I/O映射基地址,方便后续读写板卡内部寄存器时使用。
示例代码如下:
for ( bus=0; bFlag; bus++ )
for(deviceNumber=0;deviceNumber for(functionNumber=0;functionNumber { bytesWritten = RtGetBusDataByOffset(…) if(( PciData->VendorID == vendorID ) && ( PciData->DeviceID == deviceID )) base_add = base_add_register[2];//get add } 这三层循环会遍历所在计算机系统中的所有板卡。通过RTX系统提供的接口RtGetBusDataByOffset,可以获得PCI配置空间的内存指针,即表1所示的内存区域,将该内存中的DeviceID和VendorID成员与PCI-1751板卡的进行对比,即可验证当前所遍历板卡是否为1751板卡。如果找到,保存I/O映射基地址。 对于PCI-1751板卡而言,DeviceID为0x1751,VendorID为0x13FE。 打开中断函数内部完成两个操作。 首先根据用户需求,对中断控制寄存器进行配置,其次使用RTX提供的API函数RtAttachInterruptVector对PCI中断进行挂接响应。 完成上述两个设置之后,板卡上被使能的中断就可以触发中断服务函数。 中断寄存器的配置示例代码如下: IntCmd = IntMode<<(port*4); RtWritePortUchar(BaseAdd+32, (UCHAR)IntCmd); 其中,IntMode对应表5中的中断模式选择,取值0~3;port定义为Port口编号,Port0为0,Port1为1。 在配置通道组函数中,主要操作就是对欲使用的通道组的控制字进行设置,然后将控制字写入对应的寄存器中。 示例代码如下: dirsetting=PA<<4+PCH<<3+PB<<1+PCL; RtWritePortUchar(BaseAdd+(port+1)*4-1, (UCHAR)dirsetting); 在形参列表中,PA、PCH、PB、PCL是通道组输入输出方向,定义为输出传0,输入传1;port定义为Port口编号,Port0为0,Port1为1。 读取通道组,就是读取指定通道对应的寄存器。 示例代码如下: if (channel >= 3) channel += 1; cResult=RtReadPortUchar(BaseAdd+channel); 在形参列表中,channel代表I/O口编号,定义为PortA0、PortB0、PortC0、PortA1、PortB1、PortC1依次为0~5。 输出通道组,就是向指定通道对应的寄存器上写值。 示例代码如下: if (channel >= 3) channel += 1; RtWritePortUchar(BaseAdd+channel, (UCHAR)value); 在形参列表中,channel代表I/O口编号,定义为PortA0、PortB0、PortC0、PortA1、PortB1、PortC1依次为0~5。 3.6.1 原理解析 对于像Windows、RTX这样的多任务操作系统,每个任务对应一个运行的进程,每个运行的进程中又可以包含很多线程。如果没有同步机制,所有的线程会任意运行。然而,多个线程可能会要求同一个资源,这就需要同步处理。 等待中断函数就使用到了同步机制。调用等待函数后,其内部的等待同步对象的函数,例如WaitForSingleObject函数,就会处于等待状态,对于用户,其表征为“卡死”状态,只有当中断触发后,中断服务函数内部对该同步对象使能后,等待同步对象的函数才会释放线程占有权,等待中断函数才能继续运行下去。 RTX操作系统提供的等待信号量的函数为RtWaitForSingleObject,形参和用法兼容Windows操作系统函数。形参1是信号量的句柄,形参2是等待时间,当形参2传入INFINITE时,永久等待,直至信号量有效。等待中断函数就是利用永久等待信号量来实现的。 3.6.2 函数实现 等待中断函数内部对两个port口,3类中断进行等待。当用户调用该函数时,先清空对应信号量,然后等待信号量,此时该函数处于阻塞状态。 中断服务函数检测到中断触发后,将对应信号量激活。等待中断函数才能继续进行,达到了“卡死”等待的作用。 这里对port0口的PC00中断进行示意。 IntCmd = 0x01;//中断源,对应表5 RtWritePortUchar(BaseAdd+32, (UCHAR)IntCmd);//写中断控制寄存器 RtWaitForSingleObject(hInterHandle[0], INFINITE); Printf(“PC00 Int found/n”);//中断到达了 return 0; 在中断服务函数内部,其核心代码如下: temp1=RtReadPortUchar(base+32);//得到中断状态寄存器 if (temp1 & 0x08)//对比表4中的D3位 RtSetEvent(hInterHandle[0]); 对于板卡驱动性能的测试,这里使用了一个“自发自收”的闭环测试模型,即板卡PA00自己产生上升沿,板卡PC00采集该上升沿,通过对比上升沿产生前后的时间间隔来衡量驱动程序的性能。测试流程如图3所示。 图3 驱动测试流程 板卡硬件上用导线连接引脚1与引脚19,即PA00引脚与PC00引脚。 软件上将PA口配置为输出方向,PC口设置为输入方向,这样PA00的电压会被PC00实时采集。 I/O口方向设置好后,使PA00口先输出低电平,再输出高电平,等待PC00口检测到该上升沿触发中断。 Windows与RTX的测试程序均按照图3所示流程进行编写,具体流程如下: 1)打开板卡,配置PA口为输出方向,PC口为输入方向; 2)配置中断控制寄存器,使能PC00上升沿中断; 3)记录当前时刻t1; 4)PA00输出低电平; 5)PA00输出高电平; 6)等待PC00上升沿中断,记录中断触发时刻t2; 7)计算“闭环”时间t2-t1; 8)程序结束。 本次试验使用研华610L原装机箱作为测试硬件环境,系统环境为Windows XP SP3+RTX8.1,编译器使用Visual Studio 6.0。 Windows与RTX实验程序均按照4.2节中的流程开发,t1和t2通过系统函数获取,t2与t1的差值作为最终考核指标。 实验100次取平均值作为测试最终结果,Windows驱动与RTX驱动的“闭环”测试结果如表6所示。 表6 驱动测试结果 ms 通过平均值的对比可以看到,RTX驱动程序相比Windows驱动,响应时间缩短了68%,性能提升相当明显。 同时,通过极值对比可以看到,RTX驱动的闭环时间相对稳定,波动保持在0.003 ms之内;Windows驱动的闭环时间相对不稳定,波动在0.008 ms之内。 本次实验表明,无论在响应时间方面,还是在稳定性方面,RTX驱动的性能都处在领先地位,对于追求实时、稳定的环境而言,RTX驱动无疑是首选。 由于系统设计出发点的不同,无论是在线程调度算法、线程优先级定义、定时器精度方面,Windows系统均不是RTX对手。因此,Windows驱动的落败也是在预料之中的。 这也表明,RTX可以对一个单一的低成本的平台进行扩展,使其满足一个广泛的嵌入式应用程序的要求。之所以很多高校和研究所广泛使用RTX,确实是有一定依据的。 本文介绍了PCI-1751接口卡在RTX实时系统下驱动程序的编写方法,出色的实现了板卡提供的DI/O功能、中断采集功能,可以满足绝大多数工业、生产、仿真的实时性要求。同时对于其他类型的接口卡,亦可借鉴本文中列举的方法和框架进行开发驱动。对于PCI-1751板卡的定时器/计数器等功能,由于篇幅所限未能介绍,感兴趣的读者可以参考本文的思路,自己探索尝试。3.2 打开中断——EnableInterrupt_1751
3.3 配置通道组——SetPortDirection_1751
3.4 读通道组——ReadPort_1751
3.5 写通道组——WritePort_1751
3.6 等待中断——WaitPortInterrupt_1751
4 RTX驱动程序测试
4.1 测试原理
4.2 测试方法
4.3 测试环境与考核指标
4.4 测试结果与分析
5 结束语