处带通滤波器的通带大于100 khz即可。由于是信道模拟,为了尽可能的达到模拟的效果,设计中以牺牲硬件的方式在中频处理数字信号。此时带通滤波并不是要滤出基带信号,而仅仅是实现正交变换,所以通带只要大于100 khz,保证基带信号通过。实际设计过程中,取2 m,便于滤波器的设计。带通滤波器的设计方法如下:
(1) 设计一个fir低通滤波器,其通带为要求实现的通带的1/2。
(2) 根据下面两个公式将数字低通滤波器的系数转换成i、q带通滤波器系数
(3-12)
(3-13)
上式中,
= 阶低通滤波器的系数;
=带通信号的中心频率;
=滤波器系数的个数;
=采样周期;
3.2.2.2 数字解调设计
首先要设计低通滤波器。systemview[22]是功能强大的滤波器设计工具,本文采用它完成低通数字滤波器的设计。如图3-10,3-11所示。
图3-10中的参数设置中,取了20%的通带,带内纹波0.05 db,根据相关参数,滤波器阶数为59。图3-11为设计的数字低通滤波器系数。因此,公式(3-12)、(3-13)中的参数 就是图3.5中生成的系数, =10 mhz, =59,t=40 ns.。然后由公式(3-12)、(3-13)即可得出 , 。
图3-10 低通滤波器参数设置 图3-11 低通数字滤波器系数
图3-12 正交解调结果
求得 , 后,在ccs中编写程序。由于数据流量为25 m,所以每个数据的处理时间最多40 ns。每个数据要做一次滤波,包含118次乘法和118次加法,再加上数据的读进读出,而tms320c6416t只有2个乘法单元和6个逻辑单元,如果不优化是不能完成算法的。实际程序采用了前面提到的优化方法:循环展开、指令打包、采用内联函数,压缩寄存器生命周期等。最后优化的结果是34.5 ns处理1个数据,处理结果如图3-12所示。
从结果可以看出,平行分量和垂直分量的相位差为:
2.83571-(-1.87626)=4.71197=1.4998666344014361305658144529968 pi
可知iq相位几乎为90度,误差为-0.024005807741496498153398460580861度,精度非常高,而他们的幅度分别为:
i: 606115 q: 608607
误差为:(2492)/(607361)=0.0041近似为0.4%。
计算 每个点需要时间为:80493/2048=39.3 ns<40 ns
从仿真的结果分析可以看出,设计完全符合要求。从中也可以看出,用数字的方法实现解调能够达到很高的精度,比模拟的方法精度要高近百倍,这也是本设计的一大亮点。
4 pxi数字传输模块硬件电路设计
随着 现代 总线技术的 发展 ,总线数据传输协议也逐渐的复杂,怎样将复杂的总线传输协议转化为简单的、易于控制的本地总线逻辑关系,是硬件电路设计首先要考虑的问题,也就是设计总线接口电路的问题。对于一种总线,接口设计的好坏将直接影响到模块功能的实现。好的接口设计,能够完全满足总线规范、达到最佳的总线数据传输、减少总线延迟、提高数据传输效率充分发挥总线性能。所以说总线接口设计是进行总线开发时第一项也是十分重要的关键技术。
总线接口开发过程主要有以下几个步骤:
首先,深入了解总线规范,对接口需要完成的信号进行分析,将信号分为接口必备信号和可选信号。这样,在完成接口设计时候就会有轻有重,首先完成接口必备信号,以完成基本的总线功能;然后再考虑完成可选信号以实现总线的扩展功能。
其次,在分析接口信号的基础上深入研究总线上数据传输过程和总线命令,只有对总线的传输过程有了全面清楚的理解,才能保证接口设计的正确性,这是完成接口设计的必要条件。
再次,在分析总线规范的基础上,结合当前流行的接口设计方法,提出可行的接口方案并对方案分析比较,选择最佳的接口方案。目前ic的集成度很高,对流行的总线都有专用接口芯片,在总线开发的初期推荐选用专用接口芯片加快开发数度,随着对总线研究的不断深入,可以自行开发全部接口电路。
最后,根据确定的接口方案,设计相应的外围电路完成本地译码和逻辑控制,此时多半选用可编程逻辑器件,这样能够方便的修改设计减少由于设计失误引起的损失。
4.1 pxi接口实现方案
pxi 总线规范复杂,在提高了传输速度的同时,也带来了实现上的麻烦。目前实现 pxi 通讯的方法大体有使用可编程逻辑芯片和使用专用的接口芯片这两种方案:
(1)直接用cpld/fpga进行接口设计
采用可编程逻辑器件可实现pxi接口的最大好处是比较灵活,而在逻辑器件中,fpga 是备受现代数字系统设计师欢迎的新一代系统设计 电子 器件。fpga(filed programmable gate array)现场可编程门列阵是八十年代出现的一个新概念。fpga 是一种可由用户根据所设计数字系统的要求,在现场自己配置、定义的高密度专用数字集成电路。设计模块可在fpga开发软件的支持下,建立和实现含有复杂逻辑的数字电路系统。
因为pxi总线对负载要求、传输数据的建立时间要求都比较苛刻,同时还需要器件内部实现配置的各类寄存器,以及完成逻辑校验、地址译码等工作的寄存器,还要加入fifo,用户寄存器和后端设备接口部分。自行设计pxi总线必然得将大量的人力和物力投入到纷繁的逻辑验证、时序分析工作上,开发周期长。另外有些公司,如 altera 也针对可编程逻辑器件提供pci控制器的软件包pci meagacore。该软件包中包含了pci 总线制电路部分所有功能,用户以此作为一个模块,设计自己的外部设备接口程序。但pci megacore软件包价格较昂贵,而且使用这一模块软件编程较复杂[23][24]。
(2)专用pci接口芯片加fpga/cpld的接口方案
接口芯片提供了可靠的pci逻辑,功能比较齐全。选用专用芯片可以降低设计成本,缩短电路的开发周期,同时还提供了专用的开发调试工具,是一种省时省力的方案。缺点是用户可能只用到部分pci接口功能,这样造成了一定的逻辑资源浪费,也缺乏灵活性,很可能增加板上的组件,导致产品成本的增加和可靠性的降低。综合以上两种实现方式的特点,为了减少开发时间,通常采用第二种方案选用专用的pci接口芯片来实现不同总线之间的通讯。
本设计选用了plx公司生产的pci9054作为pci控制器和altera公司的fpga 进行读写逻辑和pxi扩展功能设计。
4.2 pxi接口电路实现
用上述pci接口芯片加fpga的接口方案设计的pxi总线接口框图如图4-1所示。由图中可以看出,采用了pci接口芯片设计接口,可以大大简化设计难度,只需要将相应的地址/数据总线和控制总线接入pci9054即可,所有的这些信号经过 pci9054之后,将复杂的pci总线的数据传输逻辑,简化为简单的本地控制逻辑,使得所有的接口设计工作转化为本地对fpga 的编程。
图4-1 pxi总线接口框图
pxi接口中的pci9054是目标接口芯片,只作为pxi总线的从设备。对其功能的实现和控制都是通过控制其内部的寄存器实现的,这些内部寄存器可以通过pxi总线和eeprom访问。
pci9054是由美国plx公司生产的pci i/o加速器,采用了先进的plx数据流水线结构技术,是32位、33 mhz的pci总线主i/o加速器;符合pci本地总线规范2.2版,突发传输速率达到132 mb/s,本地总线支持复用/非复用的32位地址/数据;有m、c、j三种模式;针对不同的处理器及局总线特性可选,尽量减少中间逻辑;具有可选的串行eeprom接口,本地总线时钟可和pci时钟异步。pc9054内部有6种可编程的fifo,以实现零等待突发传输及本地总线和pci总线之间的异步操作;支持主模式,从模式,dma传输方式,因其强大的功能可应用于适配卡和嵌入式系统中。pci 9054是一种性价比较高的pci桥路芯片[25]。
(1)初始化和复位上电过程中,pci9054的内部寄存器由pxi总线rst#信号复位,pci9054相应复位信号后,在本地总线上输出lreset#信号,还要检查串行eeprom 是否存在,如果安装了eeprom,则pci9054用eeprom中的值来配置片内的寄存器,否则用缺省值。pxi总线上的主控设备还可以通过软件对pci9054复位。
(2)串行 eeprom
本设计中串行eeprom采用93lc66b,按顺序存储配置信息,如设备id、供应商id、子系统id、pxi总线和本地总线之间地址映射、片选地址、控制位、状态位等配置信息。
(3)片内寄存器访问pci9054提供了两类片内寄存器,即pxi总线配置寄存器和本地总线配置寄存器。两者都可以通过pxi总线和串行eeprom访问。
(4)直接数据传输pci9054支持pxi主设备直接访问本地总线上的设备,数据传输方式分为寄存器映射的突发传输和i/o映射的单次传输,并由pxi基地址寄存器设置其在pxi寄存器和i/o空间中的位置。另外,局部总线映射寄存器可以把pxi地址空间译码为本地地址空间。
4.2.1 串行eeprom配置实现
eeprom(93lc66b)的硬件结构如图4-2:
图4-2 eeprom硬件连接
与串行eeprom对应的端口为cs、sk、di、do,在电路中di和do是物理连接的,eeprom的eedi/eedo引脚的配置时要注意:当不安装eeprom时,该引脚一定要下拉,用1 k的下拉电阻即可,此时启动后9054会按默认的值进行配置;当安装空白的eeprom时,该引脚需要上拉;当安装烧录好的eeprom时,该引脚需要上拉。
本课题采用的是软件烧写程序,即用plxmon软件进项在线烧写。在烧写的时候要注意:必须安装plxmon驱动,一般是在plxmon 软件的安装目录下,如c:\plx\p cisdk\win32\driver\wdm\pci9054\driver\free\i386\9054.sys,eeprom内部的值必须全是ff的,否则不能正常启动,也是错误的。本设计eeprom配置内容如图4-3:
图4-3 eeprom配置内容
pxi总线对eeprom的访问时序如下图4-4。
图4-4 eeprom访问读写时序
eesk为eeprom读写的时钟信号,eecs为启动和终止信号。当pxi卡上电后,首先是复位信号产生一个上跳沿,将系统复位,然后eecs在下一个时钟上升沿置为高电平,9054通过eedi传送访问指令,访问指令传送完以后,eeprom通过eedo发送起始位表明eeprom开始输出数据。当最后一个数据被输出以后,eecs值低电平,标志着数据传送结束。
4.2.2 热插拔电路
热插拔(hot swap)是指在系统不断电的情况下, 可以拔出或插入热插拔工作模块, 而不影响系统的正常运行。热插拔技可以提供有计划地访问热插拔设备, 允许在不停机或很少需要操作人员参与的情况下, 实现故障恢复和系统重新配置。为了使系统能够支持热插拔, pxi 协议在硬件和软件方面都作了特殊的规定。
4.2.2.1 硬件方面
主要从连接器的角度进行讨论。pxi 的连接器分为长针、中针和短针。 它采用如图4-5 所示的分级针脚。 使得pxi模块在插入或拔出时的各引脚按一定的顺序与系统底板进行连接或断开。 从而在硬件方面使pxi 板卡支持热插拔。分级针脚分为长、中、短三级针脚。
图4-5 pxi连接器示意图
长针:电源、地线引脚。用于插槽放电和vo 引脚预充电。 如:3.3 v,5 v,士12 v,gnd 等;
中针:pci 总线信号引脚。当模块上电以后这些信号应该保持三态。为了减小对pci 信号的影响, 在插入过程中, 应预充电到1 v 左右。
短针:idsel、bd_sel# 引脚。用于模块插入/拔出的确认信号,当这个信号有效时(低电平), 表示整个模块已完全插入系统中。
4.2.2.2 软件方面
热插拔除了在硬件电路上要求采取上面所述的措施以防电气损坏或干扰其它模块正常工作外。 还要求在系统的驱动程序级、服务程序级以及在应用程序级上有足够的附加软件来支持。软件的功能,不外乎是能够反应外围卡所送来的信号。 进而分配或移除资源给外围卡以及加载或卸载外围卡的驱动程序。通常,当系统上电时。系统的bios 分配一个内存地址空间和一个i/o空间给每个pxi 模块。当一个pxi 模块被插入或在系统己经配置后被拔出。 它要求系统检测该事件并动态地重新配置内存和i/o 地址空间。hot swap 服务程序包含外围卡的资源分配及卸载、驱动程序的加载及卸载。以及hot swap 指示灯的使能等。这是最复杂的一部分。须考虑到所有可能的资源分配。包括i/o 端口、内存及中断分配。目前凌华科技等公司已经陆续开发了windows 系列及linux hot swap上 服务程序。
4.2.3.3 电源管理芯片lt1643的接口设计
ltc1643 是一块专门用于热插拔的电源管理芯片, 是针对pxi/compact pci总线的热插拔控制器,它具有电源延时开关能力,保证板卡在系统中安全的插入和拔出,同时它也可以保护系统电源。对于热插拔模块来说,除了pci9054 和ltc1643从pxi的接口取电以外,其它模块( 如ad、dsp 等)都只能从ltc1643 的输出端去电取电。它支持pxi 总线上的5 v 、3.3 v、12 v、-12 v 电源进行控制。 同时对5 v 和3. 3 v 输出电压提供过载和短路的双重保护。on# 引脚经2 k 电阻上拉后与pxi背板的bd_sel# 相连。pxi板卡的连接器分为长针,中针和短针。板卡插入时,长针刚刚接触,这个板卡进行预加电,当短针bd _ sel # 连接时,可以把它看作是ltc1643l的工作开关,bd _ sel # 刚刚接触背板时,被拉低,此时ltc1643l开始检测+ 5 v ,+ 3.3 v,+ 12 v,- 12 v 这四路电源是否过流,如果出现电流过大情况,ltc1643l将有效fault#信号,板卡将一直处于复位状态;如果各路的电压超过阀值( 12vout≥11.1 v, 5vout ≥4.62 v, 3.3vout ≥2.9 v and - 12vout ≤–10.5 v),ltc1643的pwrgd# 脚输出低有效。如果各路电压正常,那么ltc1643l输出powergood信号,此信号可以设计为与healthy#信号相连,板卡就通过healthy#信号通知背板中的热插拔控制器(hsc)板卡电源正常。电源正常,healthy# 输出低电平,反之输出高电平,随即板卡将退出复位状态。
图4-6 ltc1643热插拔电路
4.3 器件功能电路设计
本节在解决了pxi接口设计的基础上,根据本课题的技术指标,设计出了位宽16 bit和传输速率不低于16 m的器件功能电路。
4.3.1 功能电路总体设计
功能电路采用的方案是fpga控制pci9054局部总线和fifo的读写。具体数据流程和端口定义如下图4-7。
图4-7 pxi功能电路结构框图
4.3.2 fpga应用知识背景
fpga是 英文 field programmable gate array的缩写,即现场可编程门阵列,它是在pal, gal, epld等可编程器件的基础上进一步发展的产物,是一种可由用户根据所设计的数字系统的要求,在现场自己配制、定义的高密度专用数字集成电路。
fpga技术使设计者在fpga开发系统软件的支持下,根据系统要求,现场可以定义和修改其逻辑功能,使一个包含大量逻辑门的数字系统变的稳定、可靠、容易、快速。fpga技术能在调试模块过程中,发现设计有问题可随时通过编程修改其内部逻辑,而无须改变外部电路,这与传统的飞线、割线等方法相比能增加模块调试的灵活性,给调试工作带来方便,从而能缩短调试时间,相应的缩短了整个模块的开发周期[26~27]。
fpga采用了逻辑单元数组lca (logic cell array)这样一个新概念,内部包括6个部分,分别为可编程输入/输出单元、基本可编程逻辑单元、嵌入式块ram、丰富的布线资源、底层嵌入功能单元和内嵌专用硬核等,如图4-8所示[28]:
图4-8 fpga的结构原理图
fpga是由存放在片内ram中的程序来设置其工作状态的,因此,工作时需要对片内的ram进行编程。用户可以根据不同的配置模式,采用不同的编程方式。加电时,fpga芯片将eprom中数据读入片内编程ram中,配置完成后,fpga进入工作状态。掉电后,fpga恢复成白片,内部逻辑关系消失,因此,fpga能够反复使用。fpga的编程无须专用的fpga编程器,只须用通用的eprom, prom编程器即可。当需要修改fpga功能时,只需换一片eprom即可。这样,同一片fpga,不同的编程数据,可以产生不同的电路功能。因此,fpga的使用非常灵活。
fpga有多种配置模式:并行主模式为一片fpga加一片eprom 的方式;主从模式可以支持一片prom 编程多片fpga;串行模式可以采用串行prom编程fpga;外设模式可以将fpga作为微处理器的外设,由微处理器对其编程[29]。
一般来说,完整的fpga设计流程包括电路设计与输入、综合、功能仿真(前仿真)、实现、时序仿真(后仿真)、配置下载等六个步骤,如图4-9所示[30]:
图4-9 fpga开发流程
4.3.3 fifo的特点
fifo是一种先入先出的存储器阵列,它的控制逻辑执行所有必要的读写指针管理,产生状态标志信号和可选择的与用户逻辑电路接口连接的握手信号。单时钟fifo有一个控制数据读写操作的单时钟端口,出现在模块数据输入端口(din)的数据,在写使能输入信号(wren)低电平有效时,在时钟上升沿触发下写入到下一个可用的空存储器区域。存储器满状态输出信号(full)表示在模块的内部存储器中没有更多的空区域可供写入数据。fifo中的数据输出,是通过模块的数据输出端口(dout),按照在先前的时钟上升沿声明的读使能信号(rden)触发数据写入的顺序,依次输出。存储器空状态输出标志信号(empty)表示模块内部存储器读空,没有存储可用的数据。
fifo的状态信号不会被无效的申请破坏。当空状态标志信号有效时,申请读操作将不会对fifo的当前状态产生任何变化。类似地,当满状态标志信号有效时,申请写操作也将不会引起fifo当前状态标志的任何改变。如果这些无效信号产生,fifo将拒绝这些无效申请。
除了空 、满标志信号外,还可以设置计数变量(offset),用来提供一个更精确的fifo状态度量。这个变量的位宽是用户可编程的,以便于提供易于生成的附加状态标志信号。例如,一位的变量位宽可产生半满标志信号两位的变量位宽产生二进制编码象限标志信号等等。
单时钟fifo时钟clk是在fifo核的时钟上升沿触发,然而它也可以通过在时钟源和fifo时钟输入端口之间插入一个反相器来实现下降沿触发(相对于时钟源来说)。
同步初始化输入(reset)的激励将复位内部指针,分别初始化empty输出状态为1和初始化full输出状态为0。这种初始化可以有效地清空fifo,但同时也会丢弃一些仍然存放在模块中而没有被读出去的数据 [31]。
而图4-10和图4-11为该款fifo的同步standard mode读写时序图:
图4-10 标准读时序图
图4-11 标准写时序图
4.3.4 pci9054局部总线读写
pci9054 局部总线读写分为单模式(single)和突发(burst)模式两种。单模式基每次只读写一个字,突发模式每次读写多个字。基本的工作时序分为总线仲裁,总线读写两部分。
总线仲裁由仲裁请求信号lhold发出,当中总线响应该请求后,返回lholda作为确认信号反馈给pci9054。具体的时序如图4-12:
图4-12 总线仲裁时序图
通过设置寄存器dmamode的第7位可以使能或禁止外部等待输入控制信号ready#,以使pci9054工作于外部等待或内部等待状态。当ready#被禁止时,则在每次传输的地址和数据间插入等待状态,其数目有内部的等待状态计数器决定;若ready#使能,则ready#信号的持续时钟周期决定了附加的等待状态。本设计采用c模式ready#使能方式。根据pci9054的时序图(见pci9054 data book5.6节),每次数据传输时,首先发出地址选通信号ads#,当总线准备好则使ready#信号有效,开始数据传输;否则总线处于等待状态。数据一旦开始传输,局部总线控制器将判断传输结束标志信号blast#,若blast#为高电平,发出有效的ready#信号,数据持续传输,否则,在发出最后一个ready#信号后结束数据传输。具体的时序如图4-13:
图4-13 局部总线访问时序
4.3.5 局部总线时序控制的fpga实现
在本课题中,fpga主要用来控制fifo的读写时序;而fifo的读写则严格按照pci9054局部总线读写时序来实现。
各端口定义如表4-1:
表4-1 模块端口说明表
端口名
信号名
性质
位数
端口描述
gclk0
clock
input
1
上升沿触发时钟信号clk
lrsto_
reset
input
1
复位信号使终端复位到通电初始状态
lhold
hold request
input
1
对总线发出控制请求信号
lads_
address strobe
input
1
对局部总线发出的控制请信号求的应答,表示局部总线取得控制权
lwr_
write/read
input
1
高写入信号,低读信号
lblast_
burst last
input
1
由局部总线产生,说明当前传输的是最后一个数据
lla[31:2]
address bus
input
30
代表了物理地址
lbe [3:0]
byte enables
input
4
解码信号,位宽配置不同表示不同含义
lholda
hold acknowledge
output
1
由局部总线仲裁产生,以响应总线控制信号,lholda与lhold一起用于说明总线是否处于可用状态
lready_
ready input/output
output
1
说明是否存在数据和数据是否处于可读/写状态
lren_i_
input read enable
output
1
输入fifo的读使能信号
loe_i_
input enable
output
1
输入fifo的使能信号
lrs_i_
input reset
output
1
输入fifo的复位信号
wen_o_
output write enable
output
1
输出fifo的使能信号
loe_o_
output enable
output
1
输出fifo的输出使能信号
lrs_o_
output reset
output
1
输出fifo的复位信号
4.3.5.1 程序设计及说明
实现本程序的模块流程如图4-14:
图4-14 程序流程图
本程序采用有限状态机来实现,用跳变沿检测,而不是电平检测,是为了避免因有效电平持续过长而发生多次读取的现象[32]。具体程序见附录2。
由于涉及到时序状态的控制,所以编程过程中重要的一点是正确使用状态机。对自己使用的状态机要非常的清楚,每个状态的跳转都要把握准确。由于实际应用中将涉及到很多情况,所以编写程序要全面,确保每一种可能的情况都考虑到。
4.3.5.2 仿真结果及分析
本程序采用alter公司的quartus ⅱ软件来编写verilog程序和实现仿真分析。仿真结果如图4-15:
图4-15 仿真波形图
从仿真结果可以看出,在时钟上升沿时检测到lhold发生正跳变,将lholda置为1,当lhold由1跳变为0时,lholda跟随hold也立即跳变为0。 lholda跟随lhold的变化,即当存在要求总线访问控制信号时,系统立即反馈lholda,赋予局部总线总线访问控制权。
在拥有总线访问控制权时,在时钟上升沿检测到lads_为低,lready_立即由高电平跳变为低电平,当在时钟上升沿检测到lblast_为低时,lready_由低电平变为高电平。即在lads_为低电平与lblast_为低电平之间的时段,lready_为低电平,表示局部总线上数据处于可读/写状态,在其他时间段,lready_为高电平,表示局部总线上数据处于不可读/写状态。
此外,还可以从仿真结果看出,wen_o_跟随lready_的变化,这是由于输出fifo的使能信号与总线上是否存在数据及数据是否处于可读/写状态是相关的。lrsto_为高电平时,复位信号无效,fifo各端口正常工作。
由以上分析可知,仿真结果完全符合设计要求,从而验证了程序设计的正确性。
4.4 本章小结
本章详细的介绍了pxi数字i/o模块的硬件设计,包括接口设计和器件功能电路设计,实物图如图4-16。设计中采用了pci控制器加fpga的接口设计方案,使得设计简单化,由于pci控制器已经做得相当成熟,使得设计出板卡工作更加稳定,同时 大大缩短了开发周期。此外,采用fifo作为存储器,免去了地址信号,这样有助于简化局部总线时序控制程序,以及pxi软件的设计。
图4-16 pxi数字传输模块实物图
5 pxi软件设计
5.1 pxi 总线模块软件组成
虚拟仪器技术把 计算 机技术与仪器技术完美地结合起来,为 现代 仪器技术掀开了崭新的一页。借助通用的仪器硬件平台(pxi、vxi、gpib 等),调用不同的测试软件,就可以构成不同功能的仪器。所以,如何编制出好的软件是每一个开发人员必须认真解决的问题。要想编制出好的pxi总线模块软件,必须对pxi总线模块软件结构有一个清楚的认识。总体上,pxi 总线模块软件包括两个部分,其一是仪器驱动程序;其二是仪器软面板。
仪器驱动程序的外部接口模型如图 5-1 所示。
图5-1 仪器驱动器外部接口模型
其中,编程式开发者接口是将仪器驱动程序作为测试和测量程序的一部分应用时的接口机制,编程人员对仪器驱动程序中功能模块的调用是使用和其它软件模块一样的标准调用过程。当仪器驱动程序作为高层应用软件开发环境的一部分使用时,可利用图形化开发工具、图形化软面板等工具来增强编程开发人员接口,这些工具称为交互式开发者接口。硬件访问接口是仪器驱动程序调用底层访问的接口。子程序接口是为仪器驱动程序调用其它软件模块而提供的接口[33][34][35]。
软面板用于检验仪器的通信,并检验仪器的操作以及手动使用仪器。当对用户仪器进行编程式,可以通过观察软面板上的指示器和显示器来检验仪器是否被正确设置。pxi总线的仪器的软面板推荐符合vpp(vxi plug&play)软件规范。
由于windows系统的保护机制,对硬件的直接访问必须采用底层驱动的方式。使得这其中最关键的就是怎样实现硬件访问接口,对硬件的访问方式是整个虚拟仪器软件的灵魂。选择好的底层驱动方法可以达到事半功倍的效果。
5.2 驱动程序开发
由于在当今流行的windows操作系统中,cpu工作在保护模式下。因此运行在用户态的应用程序不能象在pc/xt时代那样直接访问硬件设备上的资源。因此要实现对数据采集卡上的硬件资源(如内存、中断等)的访问,必须编写运行在核心态的设备驱动程序。在isa卡时代,计算机分配给pci卡的内存空间是由卡上的插针决定的。而计算机分配给pci卡的内存空间是由计算机决定的,即其值是不以人的意志为转移的。在加上内存的保护机制,因此用户态程序直接访问pci卡上的内存单元是不可能的。必须编写设备驱动程序。
5.2.1 驱动程序开发工具和模式的选择
驱动程序开发工具的选择取决于你所要开发的驱动程序的模式。当今驱动程序有两种模式:vxd和wdm。其中vxd运行在windows 95和windows 98操作系统中。wdm 运行在windows 98和windows 2000操作系统中。虽然vxd也可以运行在 windows 98中,但在windows 2000中就显得无能为力了。因此wdm模式是 发展 的方向。开发驱动程序应尽可能开发wdm模式的以便和将来的操作系统相兼容。
wdm是在windows nt驱动程序体系的基础上发展而来的,修改或增加了即插即用、电源管理等功能,使之适应硬件和用户的要求。
开发wdm驱动程序的主要工具是微软为各操作系统提供的开发软件包device driver kits(ddk),该软件包为驱动程序开发者提供了用于驱动程序开发的资源文件、编译连接程序、开发技术文档等。还有第三方提供的开发工具:numega公司的driverstudio和jungo公司的windriver,这些工具是在ddk的基础上为方便开发用户而进行开发的工具。在使用中,虽然利用ddk开发驱动程序难度较大,但是代码非常简洁,结构清晰,效率也高。利用第三方开发工具使用简单,开发速度较快,但对于驱动程序的理解和深入开发不如ddk。因此选择ddk开发pci设备驱动程序,虽然开始会觉得非常复杂,但从执行效率和功能上会更有利。
5.2.2 pxi/pci设备驱动程序的特点
在开发驱动程序之前对pci总线和硬件设备进行了解是十分必要的,而且还要详细地掌握pci设备的特性以及pci设备驱动程序在设备程序栈的关系等,以便进行wdm驱动程序的设计。
pci总线是一种高性能、与cpu无关的32/64位地址数据复用的总线,它支持突发传输、即插即用、电源管理等功能,不但能满足现在的应用需要,而且能够适应未来的需求。pci总线支持硬件资源动态自动配置,以支持即插即用。在pci设备插入pci插槽或上电后,pci总线配置机构自动根据pci设备的要求实现配置。pci总线支持内存读写、i/o端口读写、中断机制和dma功能。由于这些硬件特点使pci设备的wdm驱动程序的设计变得很复杂。在开发wdm驱动程序之前,还有必须掌握pci设备的需要分配的资源等配置信息以及pci设备的功能和操作方法。
图5-2 通用pci总线的wdm驱动程序栈
在wdm中,采用了分层的驱动程序体系结构,总线驱动程序或类驱动程序在最底层直接与设备打交道,设备功能驱动程序在上层通过与低层驱动程序打交道,实现设备的功能,中间还可以有类过滤驱动程序或设备过滤驱动程序用于数据的过滤或转换。在pci总线的驱动程序层中,其层次图如图5-2:
在实际开发中,一般无需分很多层次,只需要开发一个设备驱动程序即可。设备驱动程序直接与pci总线驱动程序打交道,进行硬件操作,以实现pci设备的功能。
5.2.3 wdm驱动程序的设计
在pci设备的wdm驱动程序中,一般是编写功能驱动程序。pci总线驱动程序由操作系统实现,过滤驱动程序一般在特殊的情况下需要编写。因此本文只讨论pci设备功能驱动程序的设计。在pci设备功能驱动程序中,需要处理pci设备的内存、端口的读写、中断处理和dma数据传输,实现pci设备的功能,因此,pci设备功能驱动程序是很标准的wdm设备驱动程序。
pci设备驱动程序在框架上与其他类型的设备驱动程序基本相同,包括初始化、创建设备、卸载和删除设备、即插即用处理、分发例程处理、电源管理、wmi等部分,限于篇幅,在此只讨论pci设备的特别之处。
(1)pci设备资源的获得[36]
pci设备的硬件资源是由pci配置机构动态分配的,由pci设备实现pci配置寄存器,提出需要分配的硬件资源,由pci配置机构分配资源。驱动程序需要取得这些资源,才能操作硬件。因此,pci设备的硬件资源分配与管理是驱动程序中很重要的部分。硬件资源主要包括映射内存空间、i/o空间、中断。在wdm体系中,取得这些资源有四种方法:读写pci配置寄存器、调用硬件抽象层(hal)函数、向pci总线驱动程序发送读写配置irp和向pci总线驱动程序传递开启设备irp。第一种方法通过读写pci总线配置i/o寄存器,来取得pci设备的配置信息,其中包括资源的分配。这种方法需要将几乎所有的pci设备枚举一遍,考虑到这种方法是对公共寄存器的读写,不利于系统的安全性,最好不使用这种方法,但是在调试pci设备硬件时是个很好的方法。第二种方法通过调用函数halgetbusdata和halgetbusdatabyoffset来实现的,但是这种方法是为了能够与windows nt的驱动程序兼容,而保留下来的方法,不推荐使用,其功能被第三种方法取代。在wdm体系中,总线驱动程序必须实现总线上设备的管理功能。pci总线驱动程序实现了对pci设备资源的枚举,设备驱动程序通过向pci总线驱动程序传递设备配置irp_mj_pnp,经总线驱动程序的处理后,设备驱动程序得到pci设备的资源信息。第四种方法是推荐的方法,当系统的pnp管理器在取得设备的资源后会自动向驱动程序发出irp_mn_start_device的irp,在该irp栈中包含了设备的资源信息。好的驱动程序都应该使用这种方法,在此主要讨论该方法。
每个支持pnp功能的驱动程序,都应实现irp_mn_start_device处理。在该irp处理中应先交给低层驱动程序处理后,再根据irp栈内内容进行资源分配[37]。如下:
ntstatus pnpstartdevice(in pdevice_object fdo, in pirp pirp )
{
ntstatus status;
pio_stack_location stack;
pirp->iostatus.status = status_success;
//先由低层驱动程序处理,并等待
keinitializeevent(&a mp;event,notificationevent,false);
iocopycurrentirpstacklocationtonext(pirp);
iosetcompletionroutine(pirp,(pio_completion_routine)onrequestcomplete,
(pvoid)&event,true,true,true);
status=iocalldriver(((device_extension *)fdo->deviceextension)-> plowerdeviceobject ,pirp);
if (status == status_pending){
kewaitforsingleobject((pvoid)&event,executive,kernelmode,false,null);
}
if (!nt_success(status)){
return completerequest(pirp, status);
}
stack = iogetcurrentirpstacklocation(pirp);
resourceraw = stack->parameters.startdevice.allocatedresources ->list[0].partialresourcelist->partialdescriptors;
resource = stack->parameters.startdevice.allocatedresourcestranslated ->list[0].partialresourcelist->partialdescriptors;
for (i = 0; i < resourcelistraw->count; ++i, ++resource, ++resourceraw){
switch (resourceraw->type){
case cmresourcetypeinterrupt: //中断资源
irql = (kirql)resource->u.interrupt.level;//中断irql
vector = resource->u.interrupt.vector;//中断向量
affinity = resource->u.interrupt.affinity;//中断分发的处理器集
//判断中断触发的类型
if (resourceraw->flags == cm_resource_interrupt_latched)
mode = latched; //低电平触发
else
mode = levelsensitive; //下降沿出发
//是否共享,pci中断都是共享的
irqshare = resource->sharedisposition == cmresourceshareshared;
//连接中断
status=ioconnectinterrupt(&pdx->pinterruptobject, (pkservice_routine)oninterrupt,(pvoid)pdx,null,vector,irql,irql,
mode, irqshare,affinity,false);
case cmresourcetypeport: //端口资源
pdx->physicaliobase = resourceraw->u.port.start;//开始物理地址
pdx->iocount = resourceraw->u.port.length;//地址数量
pdx->iobase = (ulong *)mmmapiospace(pdx->physicaliobase,
pdx->iocount,mmnoncached);//映射端口
break;
case cmresourcetypememory: //内存资源
pdx->physicalmembase = resourceraw->u.memory.start;//开始地址
pdx->memcount = resourceraw->u.memory.length;//地址数量
pdx->membase = (ulong *)mmmapiospace(pdx->physicalmembase,
pdx-, , >memcount,mmnoncached);//映射内存
if (pdx->membase == null)
return status_insufficient_resources;
//其他资源一般没有,可默认处理
default:
break;
}
}
return status_success;
}
在以上的代码中,限于篇幅,没有增加错误处理代码,在实际中应用一定需要进行在调用系统函数之后,进行相应的处理,如果不符合要求,立即退出,否则在其他例程中会发生错误,使系统崩溃。同时,在退出之前,一定要释放已分配的资源。
(2)内存读写
windows工作在保护模式下,与实模式的区别在于cpu寻址方式不同,可以实现虚拟内存。在windows系统中对内存又分为分页和非分页内存。分页内存一般用于应用程序,系统提供分页和分段使用户应用程序使用的内存可以在程序空闲的时候由系统将其从物理内存调配到硬盘中,以节省物理内存资源,当程序重新运行的时候,再由系统将其调配到物理内存,这样,系统可以得到比物理内存非常大的内存量,允许更多得应用程序保持运行。而非分页内存为系统常驻内存,不可以从物理内存调配到硬盘上,因此内存无需分页。在wdm驱动程序中,对于硬件的内存映射一般需要用非分页内存,因为在一些运行在dispatch_level或更高得中断级例程中,禁止使用分页内存,比如在中断处理程序中就不可以使用分页内存。再者,使用非分页内存无需太多的转换,非常安全,效率也高。如果使用分页内存,系统就有可能将其调配到硬盘上,容易产出错误。但是,不能过多地使用非分页内存。
在pci设备的驱动程序中,获得的设备内存是一段映射物理内存,这是无法使用的,需要将其映射成系统可以访问的非分页内存。函数mmmapiospace完成该功能。该函数的原型为[38]:
pvoid mmmapiospace(
in physical_address physicaladdress,
in ulong numberofbytes,
in memory_caching_type cacheenable);
参数physicaladdress为物理地址;numberofbytes为地址的数量;cacheenable为内存是否可以隐藏,取值可为mmnoncached,mmcached,mmwritecombined,这里必须取为mmnoncached。其应用实例见以上代码中的“内存资源”处理部分。
当访问设备内存时,使用函数
uchar read_register_uchar(in puchar register);
ulong read_register_ulong(in pulong register);
ushort read_register_ushort(in pushort register);
void read_register_buffer_uchar(in puchar register,in puchar buffer,in ulong count);
void read_register_buffer_uchar(in pulong register,in pulong buffer,in ulong count);
void read_register_buffer_uchar(in pushort register,in pushort buffer,in ulong count);
void write_register_uchar(in puchar register,in uchar value);
void write_register_ulong(in pulong register,in ulong value);
void write_register_ushort(in pushort register,in ushort value);
void write_register_buffer_uchar(in puchar register,in puchar buffer,in ulong count);
void write_register_buffer_uchar(in pulong register,in pulong buffer,in ulong count);
void write_register_buffer_uchar(in pushort register,in pushort buffer,in ulong count);
以上函数对应的分别是对pci设备内存的读写函数,参数register为映射后的内存地址,在使用时,应进行相应的数据类型转换。其他参数为数据参数。xxx_register_xxx读写单个地址的内容;xxx_register_buffer_xxx读写一段内存的内容,这在pci设备支持突发读写(burst transmission)时应用。例如读写单个内存的地址:
write_register_uchar((puchar)pdx->mmbase,0x03c);
(3)i/o读写
在pc上,i/o空间是一个64 k字节的寻址空间。i/o端口的寻址方式与内存是不一样的。但是在wdm驱动程序中,对其处理与内存是一样的,把其看作寄存器,映射为设备内存。其映射方法和访问函数的用法与内存资源一样,只不过函数xxx_register_xxx改为xxx_port_xxx。
(4)中断的处理
在pci总线中,很多设备共享一个中断,这就需要在中断处理函数要格外小心,处理不当,就会导致系统崩溃。驱动程序首先要在irp_mn_start_device中获得中断资源,然后需要连接到中断处理函数中,使其当有中断请求时,进入中断服务例程。连接中断的函数为ioconnectinterrupt,具体用法见上段程序中的“中断资源”部分。十分需要注意的是在连接中断之前,一定要确定pci设备不会产生中断请求,最好在pci设备上电后,中断为屏蔽状态。在连接中断后,调用开启中断请求的函数需要同步处理,以防在函数的执行中,出现运行时间上的错误,而且在开启中断时,一定要在所有的硬件资源分配以后,否则如果有中断产生,系统就会立即调用中断处理例程,如果例程中使用了还没有分配的资源,就会出现意想不到的结果。同步处理使用函数:
boolean kesynchronizeexecution(in pkinterrupt interrupt,in pksynchronize_routine synchronizeroutine,in pvoid synchronizecontext);
参数interrupt为ioconnectinterrupt返回的变量,synchronizeroutine为函数名称,synchronizecontext为函数的输入参数。调用方式如下:
kesynchronizeexecution(pdx->pinterruptobject,(pksynchronize_routine)enablepciinterrupt,pdx);
在中断服务例程中,首先必须根据硬件信息来判断该中断是否是自己的设备发出的。这是因为pci总线共享中断,系统在接收到中断后,顺序调用各个注册该中断资源的驱动程序的中断处理例程,如果有返回true的例程,就代表该中断已处理,就不再调用其他例程,如果是返回false的例程,则说明该中断没有处理,则继续调用其他的例程。如果返回错误,就会扰乱系统,造成系统崩溃。其框图如图5-3。
图5-3 中断服务例程框图
在中断服务例程中,相应的处理最好简洁快速,因为中断例程运行的级别很高,当有中断请求时,不但会打断应用程序的执行,而且会打断在硬件中断级以下的所有运行程序。在wdm中,提供了dpc(deferred procedure call)例程,将在中断例程中耗时的但不需要立即处理的任务延时处理。比如,驱动程序接受应用程序的写pci设备的数据,当写完后,硬件产生中断标志执行完毕,这时需要结束该irp,就可以将结束irp这个耗时的任务交给dpc完成[39]。典型的用法示例如图5-4:
图5-4 中断处理过程示例
在该实例中,由应用程序调用函数writefile,将数据传递给驱动程序,驱动程序的dispatchwrite例程负责处理该irp,在该例程中,由于需要中断的配合(假定),无法立即执行完毕,必须将irp串行化,startio例程如果没有其他任务,就开始处理该irp,处理完毕后立即返回,但不能结束irp,当pci设备完成操作后,就会产生中断,在中断服务例程中把irp交给dpc,在dpc中处理完后结束该irp。