I/O模块(精选9篇)
I/O模块 篇1
0 引言
随着工业自动化系统向分散化、网络化和开放式结构的方向发展,自动化系统将部分测控功能下放到远程I/O模块完成,同时将符合同一标准的各种远程I/O连接在总线上,实现整个测控系统的分散控制。目前Modbus已经成为工业测控网络应用中的一种事实上的标准协议,并已列为我国的指导性国家标准(GB/Z 19582.1-2004和GB/Z 19582.2-2004)。由于具有简单、可靠和透明等特点,Modbus测控设备在工业现场的占有率和需求均较大。
现有文献报道的Modbus远程I/O模块均是以单片机或ARM为核心实现的[13],硬件电路的复杂程度和成本相对较高,软件编程也较困难。本文介绍的Modbus远程I/O模块以CPLD作为核心芯片,Modbus协议采用VHDL语言编程在CPLD内部实现,外围输入/输出电路的控制也由CPLD完成,使模块的硬件结构得以简化,电路的集成度得以提高,具有运算速度快和可靠性高等特点。
1 远程I/O模块结构
CPLD选用了价格低廉的MAX7000S系列EPM7064SLC44。基于CPLD的远程I/O模块设计包括多种类型,主要有:1) 32路数字量输入模块,用于检测开关、按钮、运行和故障等信号的状态;2)16路数字量输出模块,通过继电器驱动现场的被控设备;3) 8路模拟量输入模块,用于检测现场输入的4mA~20mA标准电流信号;4) 4路模拟量输出模块,可以通过拨码开关选择电压或电流输出。
远程I/O模块的硬件电路主要包括以下几个部分:CPLD芯片、电源电路、数字量输入电路、数字量输出电路、A/D转换电路、D/A转换电路、I/O模块站号设置和RS-485接口电路等,硬件整体逻辑结构如图1所示。CPLD芯片的作用包括:1)控制外围电路完成输入数据采集或输出控制量;2)通过嵌入的Modbus协议模块和UART接口及外部的RS-485接口芯片,实现远程I/O模块与Modbus主站之间的数据通信。
1.1 数字量数入/输出电路
数字量输入电路可以承受10V~24V直流电压输入,由于外部干扰信号作用,在输入电路设计部分需要加入光耦来隔离外部干扰信号,对于数字量输出部分则采用了继电器输出,由于CPLD管脚的驱动能力有限,同时也为了保护CPLD芯片免受外部电流冲击,需要采用三极管驱动继电器线圈。在数字量输入和输出电路部分同时使用发光二极管指示其所在通道的通断状态,便于操作人员观察。
1.2 模拟量输入/输出电路
A/D转换电路采用AD7858芯片完成0V~10V标准直流电压输入信号采集,该芯片可以支持8路模拟电压同时输入,采样速率高达100K。通过SPI总线与CPLD连接,可以节省CPLD外围管脚使用数量同时提高数据采样速度。通道切换由SPI指令完成,如果有模拟通道未使用,可以将其与地短接,否则会有感应电压产生,影响测量。
SPI (Serial Peripheral Interface)是一种高速的、全双工、同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局节省空间,提供方便。正是由于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。在远程I/O模块中需要将SPI连接和控制功能集成到CPLD芯片内部。
D/A转换电路采用了高精度、低功耗、全数字、电流环转换芯片AD420。具体输出量程可以通过管脚Range Selectl和Range Select2,配置为0V~5V、4mA~20mA、0mA~20mA或0mA~24mA。如果需要输出电压代替输出,可以增加一个额外的电压放大器得到0V~5V、0V~10V、±5V或±10V的电压。AD420具有灵活的串行数字接口,最大通信速率可达3.3Mbps,AD420与外部控制器可以通过SPI或Micro-wire接口通信。在我们设计的远程I/O模块中通过3线的SPI方式实现了模拟量的输出功能。
1.3 站号设置电路与RS-485接口电路
站号设置电路采用了8位拨码开关设置模块的站号。在同一网络中不能有相同的I/O模块站号,站号设置范围为1~255之间。由于I/O模块需要和Modbus主站之间进行远距离通信,同时为了提高模块工作的稳定性,在通信接口电路中选用了抗雷击的半双工RS-485接口芯片SN75LBC184,通过CPLD对半双工管脚的控制,可以使芯片在接收和发送之间切换。为了便于接收Modbus主站查询I/O模块的数据,芯片默认处于接收状态。
2 Modbus协议的实现
Modbus协议是串行现场总线通信协议,定义了独立于物理介质的消息帧结构,通过Modbus协议可以实现控制器之间、控制器与远程I/O之间串行通信。随着Modbus协议的广泛应用,该通信协议已经发展成为一种在工业控制领域真正开放的、标准的网络通信协议。
Modbus协议采用主从技术,是一种主从应答方式的通信协议。在同一个通信网络中只有一个主设备可以初始化传输(查询),其他设备(从设备)根据主设备的查询数据做出相应的反应。主设备可以单独与从设备通信,也可以以广播方式与从设备通信。
Modbus协议规定了两种传输方式:ASCII模式和RTU (Remote Terminal Unit)模式。ASCII模式规定在消息中的每个字节(8 bits)作为两个ASCII码字符发送,数据校验方式采用LRC逻辑冗余校验。这种方式的优点是字符发送的时间间隔可达到1s而不发生错误,缺点是通信速率较RTU模式慢。而RTU模式则规定在消息中的每个字节包含有两个16进制字符,数据校验采用CRC循环冗余校验,这样可以极大限度地利用数据位空间,提高通信效率。在相同的通信速率下,RTU模式比ASCII模式可以传输更多的数据。
2.1 Modbus远程I/O模块整体功能及模块划分
CPLD芯片内部采用并行数据通信方式,为了实现其与外部电路的串行通信,需要设计一个UART接口实现串、并行数据之间的转换,其中UART接口包括发送模块、接收模块、接口控制模块和波特率模块。接口模块控制接收模块和发送模块完成外部Modbus主站与Modbus从站之间的数据交换。考虑到工业现场需要处理的数据较多,可以利用CPLD自带的LPM功能为接收模块和发送模块分别定制一个FIFO缓冲区,缓冲区大小可以设置为32个字节。整个远程I/O模块的功能结构如图2所示。通过UART接口将Modbus主站查询数据转为并行数据传到Modbus模块,Modbus调用CRC子函数完成校验,判别功能代码并执行相应的操作。根据执行功能代码的情况,远程I/O应答外部Modbus主站,需要通过UART将并行数据转换为串行数据输出。
2.2 远程I/O模块的Modbus从站协议
远程I/O模块在通信中以数据帧为单位,每个帧包括1 1位的串行比特流,具体格式为:1个启始位、8个数据位、奇/偶检验位、停止位(无检验位时,需要两个停止位)。
消息帧中的地址码标志查询/选通的设备,功能码将告之从设备执行何种命令,其中Modbus常用的功能码如表1所示。
在远程I/O中支持02、04、05、06、15、16代码,Modbus主站设备可以通过以上功能码完成对远程I/O模块的测控,远程I/O模块根据执行情况返回控制器一个应答帧。Modbus从站程序查询应答流程如图3所示。
远程I/O模块的功能主要包括数据采集、数据处理、状态指示、查询应答、异常处理。数据采集是CPLD在机器时钟的作用下完成对A/D与数字量输入状态的采集。由于工业现场环境恶劣、干扰源多、如环境温度、强电子磁场等,这些干扰会使A/D转换结果偏离实际值。数据处理可以在完成A/D转换后对取得的数字量进行滤波求取平均值,提取有用信号,抑制或消除干扰和噪声。状态指示灯可以方便用户判断远程I/O模块的供电情况、A/D电路工作情况、各路输入/输出的通断状态。异常处理主要针对从站执行主站的查询情况而设置的,由于外界干扰或从站不支持某些功能,需要对主站控制器返回异议应答,并利用错误代码指示相应的错误。
根据图3的流程利用VHDL语言编写Modbus从站协议,对接收到的数据首先进行地址判断,以决定是否继续接收数据,然后对数据帧进行CRC校验,如果正确则对功能码进行判别并执行相应的功能,否则返回异议应答,其中CRC校验采用的是8位并行计算方法。
2.3 CRC校验
循环冗余校验码CRC (Cyclical Redundancy Check)在数据通信和计算机通信中有着广泛的应用,它具有编码和解码方法简单,检错纠错能力强等特点,可以显著提高系统的查错能力[4]。CRC校验的基本原理是在一个K位的二进制数据序列之后附加一个R位的二进制校验码序列,构成一个长度为N=K→R位的二进制序列,这种编码又叫(N,K)码,它可以提高整个编码系统的码矩和检错能力。
在CRC计算中的关键因素有两个:一是CRC生成多项式的选择;二是依据生成多项式计算CRC校验结果。目前常用的CRC生成多项式如表2所示,由于Modbus协议采用了CRC-16计算方式,因此生成多项式选择x16+x15→x2+1。
CRC校验一般可分为软件和硬件实现两种方法。Tenkasi、V.Ramabadran和Sunil S.Gaitonde总结了5种用于软件的实现方法,其中最常用的是按字节查表法和半字节查表法[5,6]。软件查表法不但需要占用大量的存储空间,而且由于软件顺序执行的速度要比硬件并行速度慢,因此软件查表计算CRC不适合在实时性要求较高和数据量大的场合使用。
CRC的并行计算有查表法以及基于查表法导出的一些方法,但是这些方法都需要存储长度较大的CRC余数,并且随着并行位数的增加,余数表的长度按指数增加,因此查表法的硬件实现的可行性大大降低。Giuseppe Campobello、Giuseppe Patane和Marco Russo根据线性时不变系统LSFR的特性推导出了用于CRC计算的转换矩阵,但变换矩阵推导方法过于繁琐[6,7]。
目前,大部分CRC都是在芯片内利用硬件直接完成串行校验的,其中经典的硬件电路采用了LSFR (Linear Shift Feedback Register,线性反馈移位寄存器)来完成,如图4所示。这种串行CRC计算电路仅使用了移位寄存器和异或门,占用CPLD的资源很少。这种方法虽然简单,但由于CRC计算是一个机器时钟计算一个比特位,因此对于处理数据量较多的Modbus数据不能满足实时性要求。
在远程I/O模块设计中采用的是一种按字节并行计算CRC的方法,它直接推导出CRC校验码、输入数据和生成多项式的逻辑关系,然后直接运算得出CRC校验码。这种按字节并行计算的思路是模拟图4中LSFR线性反馈移位寄存器串行计算电路的计算过程,从而推导出每次并行处理一个字节后的CRC校验码与当前输入字节和CRC寄存器前一状态的关系。直接推导计算CRC校验码的方法具有直接、简洁、运算速度快、占用资源少的优点。经过8次模拟串行数据计算得到CRC-16按字节并行计算的结果,如图5所示。
图5中“CRC”表示按字节并行运算的校验结果,“因子”表示每位CRC寄存器中所包含的项数,“异或”表示因子之间的关系,Xi=Cixor Di,其中Ci为CRC寄存器的第i位,Di为输入字节的第i位。在CRC计算过程中,使用VHDL语言首先将CRC寄存器初始化为0XFFFF,再根据图5中CRC并行计算公式编写程序,在CPLD内仅需要占用少量的异或门和寄存器就可以在一个机器周期内完成CRC校验。将编写好的CRC-16程序作为子函数并打包存放在工作库中,这样可以十分方便地调用CRC函数完成对发送数据和接收数据的CRC校验。
3 串行通信接口功能的实现
Modbus协议可以运行在RS-232接口或RS-485接口上,通过RS-485接口可以将远程I/O模块与主站连接。由于主站通常距离现场I/O模块较远,因此选用了通信距离远、抗干扰能力强、通信速率快、价格便宜的RS-485接口。另外由于RS-485总线传输串行数据而CPLD内部采用了并行方式处理数据,因此需要一个并、串转换电路。
UART (Universal Asynchronous Receiver Transmitter,通用异步收发器)广泛应用于串行数据传输协议。UART用于短距离通信,主要包括数据缓冲器、奇偶校验、串/并转换。UART的特点是一个字符接一个字符地传输,并且传送每个字符总是以“0”为起始位,以“1”为停止位。每个UART字符帧都是由1个起始位,5~8个数据位,1个校验位,1~2个停止位组成,如图6所示。
一般采用专用集成芯片实现UART功能,如8250、8251等,这类芯片有时还需要很多辅助电路例如FIFO。在远程I/O模块设计时为了节省空间,将UART的功能集成到CPLD内部,将半双工RS-485芯片SN75LBC184的接收、发送、控制管脚分别与集成UART功能的CPLD管脚连接,通过CPLD控制数据的收发,实现利用485接口通信的目的。
根据UART的功能,将其划分为4个模块:波特率发生器、接收模块、发送模块、控制模块。在每个模块里采用状态机编程,因此可以提高程序的可靠性,使程序易读、结构合理、容易修改和排错。
3.1 波特率发生器
波特率发生器实际就是一个分频器,可以根据给定的系统时钟频率(外部晶振时钟)和要求的波特率计算出波特率的分频因子。波特率发生器的分频系数一般在CPLD内是固定的,但对于不同的通信设备,这个系数往往需要修改,在VHDL语言中可以采用Generic语句使问题得以解决。通过总线将不同的数值写到波特率发生器保持寄存器,分频器就可以产生不同的分频时钟,这个分频时钟不是波特率时钟,而是波特率时钟的16倍,目的是为了在接收数据时可以进行精确的采样。CPLD采用外部有源24M晶振时钟,在9600bps的通信速率下发生器的分频的系数为:24M/(16×9600×2)=78,将分频系数写入波特率发生器并对波特率发生器的波形仿真,结果如图7所示。clk外部晶振频率;rst为复位信号,低电平有效;clk16为分频时钟输出,是波特率的16倍。
3.2 发送模块设计
根据UART协议的描述,将字符的发送分为6个状态:空闲、发送起始位、等待、移位发送数据、校验、停止。数据发送由接口控制模块控制,接口模块给出wrn信号后,发送模块根据此信号锁存并行数据,通过发送保持寄存器和发送移位寄存器进行并串行数据转换。其中计数器no控制状态的转移,计数值为0时发送起始位,计数器为1~8时发送8位数据位,计数器为9时发送校验位,计数器为10时发送停止位,计数器随后清零并向控制接口模块发送并串转换完毕的信号。采用16倍于波特率的时钟发送数据,整个发送逻辑流程如图8所示。
图8中rst为复位信号,低电平信号有效;xmit_cmd_p为并串转换开始脉冲信号,高电平信号有效;no为发送数据个数计数脉冲;cnt为等待计数器。对发送模块进行时序仿真,结果如图9所示。clk16为波特率时钟的16倍;rst为复位信号,低电平有效;txdbuf为发送寄存器;txd_done为并串转换完毕信号;xmit_cmd_p为串并转换开始脉冲信号;txd为发送数据线。
3.3接收模块设计
根据UART的协议描述,接收模块的作用与发送模块正好相反,接收模块编程与发送模块类似,将接收分为6个状态:空闲、发送起始位、等待、移位发送数据、校验、停止,按照不同的状态分别完成接收起始位、8位数据位、产生校验位、停止位。但接收模块接收时钟与发送部分时钟是异步的,因此接收逻辑首先通过检测输入数据的下降沿来检查起始位,然后产生接收同步时钟,利用接收时钟每隔16个周期采样串行输入数据的中间部分,提高接收数据的准确程度消除干扰,在缓冲其中作移位操作,同时产生校验位,在第9位处比较校验位是否正确,在第10位处比较停止位是否为高电平,在校验位错误或停止位错误的情况下产生错误指示信号,若接收完毕需要输出一个指示信号。
UART接收模块时序仿真结果如图10所示。clk16波特率的16倍;rst为复位信号,低电平有效;rdfull为串并转换完毕信号;rxd为串行输入数据线;qout为串并转换接收寄存器。
3.4接口控制模块设计
接口控制模块控制发送、接收、波特率发生模块并与外部并行总线相连接,从外部接收控制信号来控制串并数据之间的转换,同时设置并串转换完成标志和串并接收完成标志。利用QuartusⅡ软件自带的工具将各个功能模块生成工程原理图,在原理图中完成各个模块的接口连线与管脚锁定任务。
4结束语
基于CPLD的远程I/O模块可灵活组合,并通过标准的RS-485总线与主控设备通信,完成数据采集和控制功能。主控设备与远程I/O模块之间采用被广泛应用并透明公开的Modbus协议实现数据通信,远程I/O模块接收到控制器命令后可以独立完成数字量和模拟量的输入输出功能。因此,大大提高了控制系统的可靠性、灵活性和开放性,还可有效地减小主控设备的负荷。基于CPLD的Modbus远程I/O模块内部Modbus协议采用硬件电路实现,执行效率更高。该模块具有体积小、操作简单、使用方便、价格低廉、可靠性高等优点。
参考文献
[1]李彬,肖德云,张正芳.基于单片机和CPLD的智能I/O模块设计[J].计算机工程与应用,2006,42(36):66-69.
[2]黄育和,程韬波.基于Modbus RTU协议的数字智能模块的设计[J].机电工程技术,2007,36(5):38-40.
[3]潘长清,蒋大明,欧阳劲松.基于S3C44BOX处理器Modbus通信协议的实现[J].仪器仪表标准化与计量,2007,23(2):26-28.
[4]王新梅,肖国镇.纠错码-原理与方法[M].西安:西安电子科技大学出版社,2001.
[5]李永忠.通用并行CRC计算原理及其硬件实现方法[J].西北民族学院学报:自然科学版,2002,23(1):33-37.
[6]张树刚,张遂南黄士坦.CRC校验并行计算的FPGA实现[J].计算机技术与发展,2007,17(2):56-58.
[7]Campobello G,Patane G,Russo M.Parallel CRC Realization. [J].IEEE TRANSACTIONS ON COMPUTERS,2003,52 (10):1312-1319.
I/O模块 篇2
Python提供了必要的函数和方法进行默认情况下的文件基本操作。你可以用file对象做大部分的文件操作。
open函数
你必须先用Python内置的open()函数打开一个文件,创建一个file对象,相关的辅助方法才可以调用它进行读写。
语法:
file bject = open(file_name [, access_mode][, buffering])
各个参数的细节如下:
file_name:file_name变量是一个包含了你要访问的文件名称的字符串值。
access_mode:access_mode决定了打开文件的模式:只读,写入,追加等。所有可取值见如下的完全列表。这个参数是非强制的,默认文件访问模式为只读(r)。
buffering:如果buffering的值被设为0,就不会有寄存。如果buffering的值取1,访问文件时会寄存行。如果将buffering的值设为大于1的整数,表明了这就是的寄存区的缓冲大小。如果取负值,寄存区的缓冲大小则为系统默认。
不同模式打开文件的完全列表:
I/O模块 篇3
R-83XX系列模块是一款以太网数据采集及控制模块, 它能够在一个模块中实现所有的I/O、数据采集和网络功能。
该模块能够帮助您组建一套经济的分布式监控解决方案, 满足各种工业应用的需求。
R-83XX系列模块使用的是标准的以太网, 能够接收来自传感器的I/O值, 并将这些I/O值输出给局域网、Intranet和Internet上的网络节点。由于采用了以太网技术, R-83XX模块能够组建一套低成本的分布式数据采集及控制系统, 将其应用于楼宇自动化、环境监控、设备管理和制造业。
R-83XX模块使用了一片10/100 Mb/s以太网芯片, 支持TCP/IP中的通用工业Modbus/TCP协议。R-83XX I/O模块能够同时将一个I/O数据流发送到8个以太网节点。
R-83XX系列以太网数据采集和控制模块可以当作一个以太网I/O数据处理中心工作。这款新产品不仅是一台标准的I/O设备, 而且还是一套带有本地控制功能、支持Modbus/TCP标准的智能化系统, 可方便与组态软件及HMI连接。用户可以很方便地在它的基础上开发各种基于以太网的应用。
元音字母i, o, u的读音 篇4
(Aladdin, Chibi 为元音字母a的读音多变啧啧称奇。)
Little Monkey: 依我看i, o, u的读音也少不到哪儿去。还是让Miss Expert 给我们讲讲吧。
Aladdin, Chibi: What a good idea!
Miss Expert:诚如Little Monkey所言,元音字母i, o, u的读音变化也不少。在开音节中,它们大都发各自的字母音,如hi, no, bike, nose, use等。不过,也有不少例外,如come, give, move等。在闭音节单词中,i, o, u的读音变化很大,以o, u的读音变化为最。
Little Monkey: 那您快说说。
Miss Expert: 先看一下元音字母i在闭音节单词中的读音,主要有三种:
1. i在重读闭音节词中多读/I/,如:big, this, list
2. i 在以-nd,-gh(t),-mb结尾的词中多读/aI/,如: kind, high, light, climb
3. 特例:i在 beautiful, holiday, possible 等词中读//。
再看看元音字母o在闭音节单词中的读音,主要有五种读音:
1. o在重读闭音节词中多读//,如: lot, sock
特例:o 在post, hold, most, only, old, gold等词中读/u/。
2. o 在today, computer, nobody, police等词中读//。
3. o在women中读/I/。
4. o在son, mother, brother, nothing等词中读//。
5. o在woman等词中读/u/。
我特别提醒你们o在一些单词中的读音变化,如o在some,come中发//,而在handsome, welcome等词中却读//。
Aladdin:o在body中读//,但在 nobody, somebody中却读//。
Miss Expert: Aladdin, you are very careful(细心).最后,我们看一下元音字母u在闭音节单词中的发音,有六种读音之多。
1. u在full, put, push, full, sugar等重读闭音节词中读/u/。
2. u在bus, hurry, lucky, thus,sun等重读闭音节词中读//。
3. u在suggest, support, difficult等词中读//。
4. u 在busy, minute 等词中读 /I/。
5. u在communist等闭音节词中读/ju:/。
6. u在 rule, ruler等词中读/u:/。
Chibi:看来o, u在单词中读音变化之大仅次于元音字母a。
Miss Expert: 是的。元音字母在单词中读音多变,我们已充分领略了这一特点。要想真正掌握元音字母的读音,还需要大家多动脑、勤归纳、总结。
关于I/O模拟的讨论 篇5
多年以来,Windows都为这种机制而引以为豪,其实在Windows下,有这样的一个原则,硬件例如CPU最好是一直工作的,这样才能得到更加充分的利用,而在Windows下,程序都是以进程形式获得在CPU这类的硬件中执行的机会,而作为轻量级的进程,执行资源的实际获得者线程的角度上看,就是要让线程不要停下来,需要让各个线程就它们正在执行的操作进行相互的通信,Microsoft在这个领域进行了数十年的研究和测试,终于开发了这种机制,叫做I/O完成端口(I/O completion port),通过这种机制,就可以让线程在读取设备或写入设备的时候不必挂起等待设备的响应,从而很大程度上提高了吞吐量,实现了计算机性能的巨大提升,如图1所示。
首先分析一下Windows的消息机制,只有了解了Windows的消息机制才能明白,什么是消息,Windows下利用上述的I/O机制,实现了线程与I/O之间的交互不必需要线程的挂起,但是消息产生到接收又是怎么解决的呢?Windows感知到有I/O的操作以后,会将该消息进行封装成为struct tagMSG(详细内容请查阅MSDN),这个结构体中将包含消息的类型,参数,时间,位置,和传递的对象。Windows将该消息投入到Windows消息队列,然后等待应用程序从队列中提取相对应的消息,在这里要明确几个问题,便是模拟I/O的层次问题和模拟的关键是什么?正如图1表达的,模拟可以在驱动级,系统级,或者用户级3个级别上进行,关键就在于模拟的何种消息、何时、何位置、发送给那些应用程序,这就成为了I/O模拟的关键所在。
2 用户级I/O模拟
正如图1所示,从消息队列到指定应用程序的这一段路径上的I/O模拟,便是用户级模拟,用户级模拟的特点是:
(1)通过Windows消息机制,并由系统发出,给指定的应用程序。
(2)指定的应用程序通过自己的窗口句柄等标识,从消息队列中获取消息。
这一部分的模拟需要用到Windows API,首先了解到在Windows下的应用程序,都会拥有一个自己的窗口句柄,这如同一个ID一样,指向了某一个窗口程序,就通过获取到指定应用程序的句柄,然后发送特定消息,这如何来实现呢,分享一段模拟代码:
这里就不再赘述一些细节了,但是需要注意的是,在FindWindowsEx中的第二参数,解决一些存在父子窗口的情况,通过对子窗口的句柄,有点类似线程ID了,就可以向指定线程发送消息,按键的消息需要延迟200毫秒,这是因为正常人的点击操作便是200毫秒,这样首先可以避免写入消息队列过快,造成溢出,也可以满足一些需要一定按键时长程序的特殊要求。这里的PostMessage函数有时可以用SendMessage函数替代,本身参数上没有区别,但是核心在于如果使用的是PostMessage那么发出消息后,模拟程序是不会理会该消息的处理的,但是使用SendMessage的话,应用程序就会一直等待该消息被正确处理的反馈,这样会一定程度上影响模拟程序的正常运行,因此建议使用PostMessage函数。其次Windows的消息叫做Windows_Message,这类消息共有1024(0x0400),所以可以设置高于1024的自定义消息。这样就可以模拟很多Windows下无法模拟的I/O消息,但是弊端就是要为自定义消息编写处理函数。
3 系统级I/O模拟
这一部分分析操作系统到消息队列这一段路径上的I/O模拟,这一段的模拟所具有的特点是:
(1)系统并不指定发送的对象,接受消息的进程为当前的活动进程。
(2)接收消息的程序可以发生改变,换句话说是可以存在进程间切换的。
首先分析一下正常情况下键盘消息的产生过程,这好比要完成对某一好友的问候,那么应当先选中一个好友,然后对他发送你好的消息,这一过程就是平时使用鼠标点击选中当前活动进程,或是用键盘切换选择活动进程的过程。这一部分的模拟通过Windows API也是可以实现的,下面我们分享一段模拟代码:
键盘消息的模拟,需要讨论的是虚键码MapVirtualKey函数,这个函数把逻辑上的‘U’键转换成计算机可以识别的二进制虚键码,可以不使用这个函数,但是效果就是模拟出的键盘消息不会被一些DirectX类游戏所接受,鼠标事件的模拟就要显得相对复杂了,因为不仅要产生事件消息,还要产生位置消息,这里的案例是对640*480窗口向全屏的鼠标坐标转换,有点类似窗口压缩的意思,需要解释的是对于一些DirectX类的射击游戏的鼠标模拟,这套方案并不是首选方案,其原因会在下面的驱动级模拟中详细给大家分析。
4 驱动级I/O模拟
这一部分要分析从驱动到操作系统和一种特殊的消息传递方式,在这之前,先要了解这样一个事实,在应用程序中有一类游戏应用程序叫做DirectX类游戏,比如冰封王座,CS,街霸,铁拳等,这类游戏的一个共同特征是:
(1)需要很高的即时操作要求,而且操作频繁。
(2)不使用Windows的消息机制,而是与驱动直接交互。
首先为了很好的人机交互感,这类游戏都会将操作精确到毫秒级,而且会产生大量的操作,这就要求对I/O缓存的一定使用要求,其次为了实现这类游戏的即时要求,这类游戏都会选择绕过Windows消息机制,因为通过Windows消息机制至少都有数百毫秒的延时,这难以满足游戏的需要,为此目的,这类游戏会选择直接与驱动进行交互,来接受I/O的操作。在这个层面的模拟就会显得比较复杂,而且Windows的API就不太好用了。
因此在这个层面的I/O模拟有两种设计方案,一种是需要重写某些I/O设备的驱动,这是比较麻烦的,还有一种方法是将消息写入某个特定的驱动然后产生相应的消息,下面着重分享一下第二种方法,写入驱动程序:
使用写入驱动的接口是WinI/O提供的,通过WinI/O就可以和驱动进行一定程度上的交互,分析一下这几段关键性代码:首先KBCWait4IBE()函数是用来检测驱动的缓存是否为空的,因为开始讨论到,如果模拟的消息过平凡地投递到驱动,那么消息就会出现一定程度上的溢出,这样的话,很多的操作就会出现丢失,这样是很不好的,所以每当要将内容写入到驱动的时候,都要检测一下是否驱动的缓存已经满了,这并不会造成严重的延迟,但是对一些组合操作来讲,丢失一个操作就有可能丢失了一批操作,所以在驱动级模拟I/O的操作关键就在于避免缓存溢出。此外,MY_KEY_DOWN&MY_KEY_UP是相类似的,将差异分析一下,首先,要产生的按键的ASCII码转换为虚键码,然后等待缓存,当可以写入的时候首先要发送一个命令消息0xD2,即为写入缓存,然后得到写入的权限后写入一个数据0xE2,告诉驱动要写入的是一个键盘消息,然后等待缓存,写入要写入消息的虚键码,于是消息被写入,但是这个时候键盘消息是按下消息,如何将其按起呢?使用0x80也就是标识当前某一按键被取消的标识位,当这一位被标识后,按键消息就变成了按键取消消息,从而从键盘上驱动模拟发出这一消息。
5 结语
详细介绍了在用户级、系统级和驱动级实现对I/O设备的模拟操作,分享了Windows API编程、WinIO驱动编程的案例。也详细地分析了Windows消息机制的优缺之处和如何利用和脱离Windows消息机制。
摘要:详细分析Windows的I/O机制,提出了分别在用户级、系统级、驱动级的I/O模拟操作,通过实际案例和源代码分享在I/O模拟上的得失,着重介绍鼠标与键盘的模拟。
关键词:I/O机制,I/O模拟
参考文献
对象I/O技术的模拟实现 篇6
我们知道C++对象是“存活”在RAM中的,由于RAM的易失性[1],程序需要将对象存入磁盘中,将来需要时再把对象读入内存加以恢复,这样一来就好像对象一直“活”着一样,因此对象的持久化是C++中的一个非常重要的操作。
许多程序员可能有这样的误区,利用下面的代码:saveClassName();className=readClassName();p=new className;不就轻松实现对象的保存和恢复了吗?
需要指出的是,在C++中,是通过new A而非new“A”(或className=“A”,new className)实例化A的对象。换句话说,试图利用下面的代码className=readClassName();p=new className;来达到通过类的字符串名称动态创建对象的做法是根本行不通的,因为p=new className根本无法通过编译![2]
由此我们得出对象的持久化需要的两个条件:
(1)获取对象所属类的名称的能力;
(2)能根据类的字符串名字动态创建对象的能力。
这两种能力的获得目前有两种解决方案:
一是由C++编译器(compiler)提供———例如Borland C++4.5:
二是由程序员自己加上去。
本文是通过第二种方法模拟实现对象的持久化机制,从而深入探讨了对象I/O技术的实现机制。
本文结构如下,首先描述了对象持久化的实现,其次对实现进行了验证,最后是论文进行总结。
1 对象持久化的实现
为了说明问题而又不失一般性,我们假定有三个类,分别是Object,A和B,其中A和B都派生于Object,类的定义如下:
为了让对象具备持久化的两个条件,需要依次为对象添加如下信息。
1.1 为每个类增加对象创建函数CreatObject,如表2所示。
1.2 增加类的识别信息(类名称或ID等)以及继承信息,由于这部分信息比较多,可以将其整合到一个结构体Struct ClassInfo中。ClassInfo中保存有两个链表:
类的继承链表和程序中所有类的类型信息链表。类的识别能力就由这两个链表来完成。换句话说,我们希望在main函数执行之前内存中就存在如图1所示的两个链表。其中类的继承链表是由Struct ClassInfo的带参构造函数完成的(在VC++中,结构体也可以有构造函数),而类的类型信息链表则是由ClassInfoInit类完成的。Struct ClassInfo和ClassInfoInit的定义如表3所示。注意,链表的创建是在它们的构造函数中完成的。
最后一步,将类型信息作为类的静态的成员变量添加进来,并为每个类实例化静态的初始化类,目的是在main函数执行之前得到图1所示的两个链表。
在这要强调一下static关键字的作用:
(1)如果类的成员变量被Static关键字所修饰,则这该属性不是为类中的每个对象分别拥有,而是共用,其引用形式不是对象成员变量,而是类成员变量,即不需要市里实例化对象就可以使用。成员函数也与此相仿[3]。
(2)如果对象(包括作为类成员的对象),如initObject、ObjectInfo等,被说明为Static,则这些对象是在main函数执行之前就已经存在了,换句话说,这些对象的构造函数是在main函数调用之前就已经被调用了。因此在main函数执行之前,内存中就已经存在着图1所示的链表就可以理解了。
我们添加的RTTI信息能否有效的支持对象的持久化可以由试验来验证。
2 试验
该试验主要验证了对象持久化必须具备的两个能力,即动态创建对象能力和获取对象所属类的能力。试验的环境如下:操作系统Windows XP sp2,IDE环境是VC++6.0 SP6。如需要全部源代码可与作者联系(hehh6@henu.edu.cn)。
通过对象增加的RTTI信息(增加的静态成员变量),获取对象所属类的能力自然具备。
动态创建对象能力实际上就是根据类的字符串名称来实例化对象的能力,在图1所示链表的支持下,该功能可以非常轻松的实现。
思路如下:通过ClassInfo::pFirstClass查找类名匹配的ClassInfo,利用ClassInfo中的pCreateObject指针实例化对象,为方便起见,可为程序增加查找函数
3 结束语
本文在手动添加的RTTI信息的支持下实现了对象的持久化,从中读者可以深入了解对象持久化的的实现机制。
在商业的编译器VC++中其实现持久化是通过一个神秘的宏,当将该宏展开后,VC++实现持久化的的思路和本文大同小异,只是更精巧[4]。
参考文献
[1]Stanley B.Lippman.Inside The C++Object Model(深度探索C++对象模型)[M].侯捷,译.武汉:华中科技大学出版社,2001.
[2]葛磊.The Object's Permanence Technology and Its Realization in MFC[J].开封大学学报,2006,20(1).
[3]钱能.C++程序设计教程[M].第二版.北京:清华大学出版社,2005:418-419.
远程I/O系统在罐区的应用 篇7
关键词:远程I/O,通信,DCS
0 引言
金陵石化芳烃部中间罐区仪表采用远程RTU接入DCS方式, 现场检测、报警仪表的信号接入远程I/O单元, 由远程I/O单元转换成通信信号, 连接至相应的DCS系统, 控制仪表及可燃气有毒气检测仪表采用电缆直连至相应的DCS系统。
现场每套远程I/O系统输出两路Modbus信号, 分别进入两个不同装置的DCS系统, 一路进PX装置, 另一路进3重整装置。
1 远程I/O系统
模块化的远程I/O系统通过总线接口将现场二进制和模拟量的传感器和执行器连接至控制系统, 传输安全区域或者爆炸危险区域的现场过程数据。远程I/O系统可连接设备:流量变送器、阀门定位器、压力传感器或者温度变送器、热电偶 (TCs) 、电阻式温度检测器 (RTDs) 、机械触点以及声光报警器等。远程I/O系统兼容Profibus-DP、Modbus-RTU、Modbus TCP/IP和Foundation Fieldbus等网络协议。远程I/O适用于爆炸危险区或者严酷工业环境, 例如海洋平台和户外。典型的应用行业包括石油天然气 (内陆及海洋) 、制药、化工、污水处理、食品饮料等。
远程I/O系统具备高适应性和操作简单的特征。系统安装在现场设备侧, 仅需一根总线电缆即可连接远程I/O系统到DCS, 能够减少接线, 还能够节省时间、节约成本和减少工作量。
P+F公司的LB系列远程I/O具备危险区2区认证, 可安装在Zone 2、Class I、Div.2或者非危险区域。该模块化的信号调节系统将现场信号关联至非危险区域的控制系统, 连接方式有安全区域现场连接 (NonEx) 和本质安全区域现场连接 (Ex i) 。
金陵石化公司芳烃部新建中间罐区正是采用LB系列的远程I/O系统, 如图1所示。
该系统的底板结构如图2所示。X4为现场总线接口, 用来与DCS通信, 现场实际用于与PX通信;X5为冗余现场总线接口, 用来与DCS通信, 现场实际用于与#3重整通信;X6为服务总线, 用于连接PC机进行相应的组态;X7为扩展机架接口;CHANNEL1/2为通信模块;CHANNEL3-24为I/O卡件;POWER1-3插槽为插相应的供电模块。
2 远程I/O系统的组态过程
(1) 打开PACTWARE, 添加设备, 如图3所示。
(2) 选择添加相应的LB系统, 选择好相应的COM口后选择SCAN SERVICE BUS, 自动扫描网关设备, 如图4所示。
(3) 按照设计图纸在每个通道插入相应的插件, 然后进行具体的点组态, 如图5所示。
(4) 在实际的组态中, 为了通信方便, 处理量程统一设置成0~4095。为了能够与TPS系统通信, 必须对相应的Modbus地址进行分配, 如图6所示。点击Auto marshalling按钮, 系统会自动根据卡件的通道号分配相应的地址。组态结束后, 对相应的系统进行下载。
3 与DCS通信
LB远程I/O系统与Honeywell TPS系统采用的是标准的Modbus-RTU通信协议。
控制器和主机为主从关系。仅一设备 (主设备) 能初始化传输 (查询) , 其它设备 (从设备) 根据主设备查询提供的数据做出相应反应。这种传输方式也使得信号在Modbus网络中的通信井然有序。
根据系统的特点, 采用RS-485接口, 接口采用平衡驱动器和差分接收器的组合, 使用屏蔽双绞线传输 (一般只需二根连线) 数据, 抗共模干扰能力增强, 抗噪声干扰性好。
将现场远程I/O系统的通信线连接DCS中的SI卡FTA板, 在DCS中组态相应的ARRAY点, 设置地址、波特率、通信协议, 分别如图7、图8、图9所示。
建立相应的通信之后, 要对模拟量点的量程进行转换, 以达到实际使用要求。
4 结语
远程I/O系统不但节省成本, 减少工作量, 而且可以在线对参数进行修改, 在线监测功能强大, 利于故障的判断, 减少了维护量。
参考文献
[1]丁宝苍.过程控制系统与装置[M].重庆:重庆大学出版社, 2013
[2]乐嘉谦.仪表工手册[M].北京:化学工业出版社, 2003
[3]厉玉鸣.化工仪表及自动化[M].北京:化学工业出版社, 2011
[4]张春华, 肖体兵, 李迪.工程测试技术基础[M].北京:机械工业出版社, 2011
[5]王树青, 乐嘉谦.自动化与仪表工程师手册[M].北京:化学工业出版社, 2010
[6]武平丽, 高国光.过程控制工程实施[M].北京:电子工业出版社, 2011
CC2530普通I/O口的扩展 篇8
1 CC2530的I/O口扩展方法的总体思路及各组成工作原理
1.1 CC2530的I/O口扩展方法的总体思路
本扩展方法采用CC2530的I/O口模拟IIC总线的SCL和SDA, 通过IIC总线控制PCA9554芯片, 使串行通信变成并行通信。从理论上来讲CC2530芯片P1口的8个I/O口就可以连接4组IIC通信, 每组因扩展芯片地址A2A1A0的不同可同时支持8个芯片, 每个芯片又可控制8个端口。因此, CC2530的P1口从理论上就可支持128个 (4×8×8) 端口, 如图1所示。并且, 如果芯片固定的头地址不一样的话, 例如PCA9554和PCA9554A, 那么端口数量又可增加一倍, 这样P1口就扩展成了256个端。依此类推CC2530的I/O口通过扩展数量呈几十倍的增加, 可大大丰富I/O口的应用。
1.2 IIC总线工作原理
IIC总线只有两条线, 一条是数据线SDA, 另一条是时钟线SCL。数据线SDA在时钟线SCL的控制下可串行发送和接收数据, 传送速率最高达100kbps。各被控制器件均并联在这两条线上, 每个接到IIC总线上的器件都有唯一的地址, 通过不同地址决定通信对象, 如图1所示。这样, 各控制电路虽然挂在同一条总线上, 却彼此独立, 互不相关。IIC总线的优点是占用的空间非常小, 可避免线路互连而使电路板走线错综复杂的现象, 减少了电路板上的布线空间, 降低了互联成本。
IIC总线上只有四种信号:开始信号、停止信号、重新开始信号和应答信号。IIC总线上能实现主/从双向通讯, 器件发送数据到总线上, 则定义为发送器, 器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。IIC总线时序如图2所示。
1.3 PCA9554工作原理
PCA9554是带中断的GPIO扩展芯片, 它提供了IIC总线方式应用中的8位串行转并行输入/输出口的扩展。PCA9554共有4条命令, 在总线主控制器 (本文中为CC2530) 向PCA9554发送写数据过程中, 命令字节是紧跟地址字节, 之后第一个字节作为一个指针指向要进行写或读操作的寄存器。CC2530通过总线写I/O口的相应配置位来激活端口的输入或输出。通过给PCA9554的A2A1A03个管脚不同的电平来实现不同IIC地址, A2A1A03个管脚可组成8个地址码, 由此总线上最多允许挂8个PCA9554。而PCA9554与PCA9554A的IIC固定地址不同 (PCA9554的固定地址为01000A2A1A0, PCA9554的固定地址为01110A2A1A0) , 这样就允许16个器件 (PCA9554和PCA9554A各8个) 连接到同一个IIC总线上, 每个器件扩展出8个I/O端口, 这样CC2530的两个I/O端口就可扩展到8x16个I/O口。
2 扩展I/O口的应用
本应用通过CC2530的I/O口 (P1.0和P1.1) 模拟IIC总线的SCL和SDA, 然后通过IIC总线形式控制GPIO扩展芯片PCA9554, 最后通过扩展的IO来控制LED、蜂鸣器、按键信号、继电器等, 如图3、4所示。
3 结语
本文通过IIC总线形式控制GPIO扩展芯片PCA9554扩展CC2530的I/O口。在文主要提出了具体的硬件设计, 并运用于实践中。CC2530的I/O口进行扩展, 使I/O口的数量大大增多, 能丰富开发设计者的运用。
参考文献
[1]施邦平.采用I2C器件扩展单片机的多级通信[J].自动化与仪器仪表, 自动化与仪器仪表, 2006 (6) :63-64.
I/O模块 篇9
关键词:I/O性能,流,缓冲区,分配和回收
Java应用程序都会经常使用java.io包, 例如磁盘文件读写和通过网络传输数据。然而初学者由于对java.io包的理解的局限性导致编写的程序I/O性能很差。所幸的是, 只要对java.io包有了很好的理解, 就可以杜绝这方面的问题, 从而保证程序拥有较好的I/O性能。
本文首先对java.io包做一个简要的介绍, 然后指出导致I/O性能低下的根本原因是没有采用缓冲机制, 然后循序渐进地采取了三种策略, 将I/O性能逐步提高到168倍, 这三种策略分别是采用缓冲流, 定制自己的缓冲区, 定制静态的固定长度的缓冲区。
1 I/O操作的基本概念
在Java I/O中最基本的概念是流 (Stream) , 流是一个连续的字节序列, 包括输入流和输出流, 输入流用来读取这个序列, 而输出流则用来写这个序列, 在默认情况下Java的流操作是基于字节的, 即一次只读或只写一个字节。
java.io包提供了Input Stream和Output Stream作为对I/O操作的抽象, 这两个接口决定了类层次结构的基本格局。Input Stream和Output Stream的具体实现类提供了对不同数据源的访问, 如磁盘文件和网络连接。
java.io包还提供了过滤流 (Filter Stream) , 过滤流并不指向具体的数据源, 而是在其他流之上进行了包装, 这些过滤流其实是java.io包的核心。从性能的角度来看, 最重要的过滤流是缓冲流 (Buffered Stream) , 图1显示了一个简化的类层次结构图。
2 缓冲流
导致I/O性能低下的主要原因是没有对I/O操作进行缓冲。众所周知, 硬盘擅长于大块数据的读写, 但是在小量数据的读写上性能不好, 所以, 为了最大化I/O性能, 我们应该选择批量的数据操作, 而缓冲流正是为这个目的设计的。缓冲流, 包括Buffered Input Stream和Buffered Output Stream, 它为I/O流增加了内存缓冲区, 使得Java程序一次可以向底层设备写入或者读取大量数据, 从而提高了程序的性能。
为了更好地理解缓冲流的效果, 请阅读程序清单一, 这个例子采用了原始的文件流实现文件的拷贝。copy方法打开了一个File Input Stream和一个File Output Stream, 并将数据从一个流直接拷贝到另一个流。由于read和write方法是基于字节的, 所以实际的磁盘读写也是按字节发生的。实践证明使用这段代码拷贝一个8M的文本文件需要花费36750ms。
程序清单一中的copy方法只需稍作修改就能有效地改善性能, 如程序清单二所示, 它在原始的文件流之上采用了缓冲流Buffered Input Stream和Buffered Output Stream, 缓冲流将每一个小的读写请求积攒起来, 然后一次性地批处理, 通常是将几千个读写请求合并成一个大的请求。改进后的copy方法拷贝一个8M的文本文件需要花费1187ms, 性能大约提高了31倍。
3 建立自己的缓冲
缓冲流虽然在它的内部增加了内存缓冲区, 使得在缓冲区和底层设备之间写入或读取大量数据成为可能[2,3], 但是在这之上Java程序仍旧使用while循环从该流 (实际上是缓冲数组) 按字节读写数据。由于缓冲数组的大小是一定的, JVM需要针对数组做越界检查, 该操作会导致额外的系统开销。另一方面, 为了支持多线程环境, 将数据从Buffered Input Stream的缓冲数组拷贝到Buffered Output Stream的缓冲数组, 其中的方法调用很多都是synchronized, 这也会导致额外的系统开销。所以尽管程序清单二所示代码可以有效地改善性能, 但是改善的程度仍然不尽人意。
还是利用硬盘擅长读写大块数据这一特性, 我们可以自己建立缓冲, 以避免上述额外的系统开销。这需要使用Input Stream和Output Stream提供的两个重载方法, 它们允许按字节读写, 也可以按字节数组读写, 如下所示:
程序清单三创建了自己的缓冲区, 即字节数组byte[]buffer, 其大小是整个文件的字节长度, 然后将整个文件一次性读入内存, 再一次性写到另一个文件。这个代码非常快, 拷贝一个8M的文本文件所花费时间降低到750ms。
注意, 使用这个策略需要权衡两个因素。首先是缓冲区的大小, 它所创建的缓冲区的大小等于被拷贝的文件大小, 当文件很大时, 该缓冲区也会很大。第二, 它为每次文件拷贝操作都要创建一个新的缓冲区, 当有大量文件需要拷贝时, JVM不得不分配和回收这些大缓冲区内存, 这对程序性能是极大的伤害。
如何在保持速度甚至速度更快的前提下避免这两个缺陷呢?方法是这样的:创建一个静态的固定长度的字节数组, 如1024*1024字节即1M, 每次只读写1M的数据。虽然对于大于1M的文件会需要多次读写才能完成整个文件的拷贝, 但是这样避免了内存的反复分配和回收。缓冲区大小是可以调整的, 针对某个具体的应用场景, 我们可以在速度和内存之间取得一个最佳的平衡。
程序清单四的copy方法使用了这样的1M字节的数组, 这个版本表现更为出色, 它拷贝一个8M的文本文件所花费时间只有218ms, 性能提高了168倍。
值得注意的是代码中的同步块, 在单线程环境下没有同步块是可以的, 但是在多线程环境下需要同步块防止多个线程同时对缓冲区实施写操作。尽管同步会带来系统开销, 但是由于while循环的次数很小 (8M文件只要循环8次) , 所以由此带来的性能损失是可以忽略不计的。实验证明, 有同步块的版本和去掉同步块的版本, 性能是一样的。
下表显示了采取四种不同的策略拷贝一个8M文件所花费的时间。它充分说明, 有效地改进缓冲机制可以大大提高程序的I/O性能。
4 结束语
一般情况下, 我们总可以为具体的应用程序找到改善I/O性能的方法, 这需要具体分析该应用程序的目的和操作特性。例如, 考虑FTP和Http服务器, 这些服务器的主要工作就是将文件从磁盘拷贝到网络的Socket, 一个网站的主页通常比其他网页访问得更多。为了提高性能, 我们可以建立快速缓冲贮存区, 将那些经常被访问的文件做缓存, 这样这些文件就不必每次从磁盘读写, 而是直接从内存拷贝到网络。
参考文献
[1]What is Java I/O?[EB/OL], http://www.roseindia.net/java/example/java/io/Java_io.shtml
[2]Buffered Input Stream, JavaTM Platform Standard Ed.7[EB/OL], http://docs.oracle.com/javase/7/docs/api/java/io/Buffered Input Stream.html