原文链接:EPICS synApps modbus模块_epics synapps win-CSDN博客
在EPICS下用于Modbus协议的驱动支持
Modbus概要
MODBUS是一个应用层消息协议,位于OSI模块的第7层,它在在不同总线类型或网络上连接的设备之间提供了客户端/服务器通信。它一般用于用I/O系统通信,包括可编程逻辑控制器(PLCs)。
Modbus通信链路
Modbus支持以下3种通信链路层:
Modbus通信链路
链路类型 | 描述 |
TCP | 使用标准端口502的TCP/IP |
RTU | RTU通常运行在串行通信链路,即:RS-232, RS-433或RS-485。RTU使用一个附加的CRC用于包校验。协议直接以8个数据为传输每个字节,所以使用"二进制"而非ASCII编码。当使用串行链路时,消息帧的起始和结束时通过计时而非通过特定字符被探测到。RTU也可以在TCP上运行,虽然这比不使用RTU的标准Modbus TCP不常见。 |
Serial ASCII | 串行协议,其通常运行在串行通信链路,即:RS-232, RS-433或RS-485。串行ASCII使用一个附加的LRC用于包校验。这个协议以2个ASCII字符编码每个字节。消息帧的起始和结束是通过特殊字符被探测(":"开始一条消息和CR/LR结束一条消息)。此协议效率低于RTU,但在某些环境中更加可靠。ASCII也可以运行在TCP上,虽然这比标准Modbus TCP不常见。 |
这个modbus包支持所有以上modbus通信链路层。
Modbus数据类型
Modbus提供对以下4中数据类型的访问:
modbus数据类型
主要表格 | 对象类型 | 访问 | 注释 |
离散输入 | 单bit | 只读 | 可以由一个I/O系统提供这种数据类型。 |
线圈 | 单bit | 读写 | 这种数据类型可以由应用程序更改。 |
输入寄存器 | 16位字 | 只读 | 可以由一个I/O系统提供这种数据类型。 |
保持寄存器 | 16位字 | 读写 | 这种数据类型可以由应用程序更改。 |
Modbus通信
Modbus通信由一条从Modbus客户端发送给Modbus服务器的请求消息组成。服务器用一条应答消息回答。Modbus请求消息包括:
1)一个8位Modbus功能码,其描述要被执行的数据传输的类型。
2) 一个16位Modbus地址,其描述要从其读取或者写数据到的服务器中的位置。
3) 对于写到操作,要被传输的数据。(???)
Modbus功能码
modbus支持以下9种Modbus功能码:
Modbus功能码
Modbus地址
通过一个16位整数地址指定Modbus地址。在16位地址空间中输入和输出的位置不是由Modbus协议定义,它是特定于厂家的。
注意:通过用一个400001(或300001)的偏移指定16位Modbus地址。此偏移未被这个modbus驱动使用,它只使用16位地址,不使用这个偏移。
Modbus数据长度限制
Modbus读取操作被限制于传输125个16位字或者2000比特。Modbus写操作被限制于传输123个16位字或者1968比特。
驱动架构
小心:modbus可以提供对PLC的所有I/O和内存的访问。实际上,甚至完全不需要在PLC中运行一个梯形逻辑程序。PLC可以用做一个"dumb" I/O子系统,所有逻辑驻留在EPICS IOC。但如果一个梯形逻辑正被运行在PLC中,则必须仔细地设置使用modbus的EPICS访问。例如,EPICS IOC可能被允许读取任何PLC I/O点(X输入,Y输出等),但写可能被限制于一个小范围的控制寄存器(例如:C200-C400)。梯形逻辑会监控这些控制寄存器,仅在这么做安全时,才认为来自EPICS的请求应该被响应。
modbus模块的架构从上自下由以下4层组成:
1) EPICS asyn device support。这是由asyn提供的通用设备支持。modbus不需要或不提供特殊的设备支持。
2) 一个EPICS asyn 端口驱动程序,用能作为一个Modbus客户端。modbus端口驱动程序使用标准的asyn接口(asynUInt32Digital, asynInt32等)与EPICS设备支持(第1层)进行通信。此驱动程序通过标准的asynOctet接口发送和接收设备无关的Modbus帧到"interpose interface(第三层)"。这些帧是独立于底层通信协议。在R3-0前,这个驱动程序用C编写。在R3-0中,它被编写成一个从asynPortDriver继承来的C++类。这使得它用一种方式导出其方法,这对其它驱动程序使用简单,尤其doModbusIO()方法。
3) 一个asyn "interpose interface"层处理底层通信层(TCP, RTU, ASCII)所需的其它数据。这层通过标准的asynOctet接口与上层Modbus层(第二层)和底层asyn硬件端口驱动程序(第4层)进行通信。
4) 一个asyn端口驱动程序处理底层通信(TCP/IP或串行)。这是asyn提供的标准端口驱动程序之一,即:drvAsynIPPort或drvAsynSerialPort。它们不是modbus模块的组成部分。
因为modbus充分使用了已有的asyn功能,并且值需要实现以上第2层和第3层,modbus中的代码量非常小(少于2500行)。
每个modbus端口驱动程序被分配单个Modbus功能码。通常一个驱动程序被被分配单个连续范围的Modbus内存,最多2000个位或125个字。一般位单个PLC创建若干modbus端口驱动程序,每个驱动程序读或写一个不同集合的离散输入、线圈,输入寄存器或保持寄存器。例如,可以创建一个端口驱动程序来读取离散输入X0-X37,创建第二个端口驱动程序读取控制寄存器C0-C377,和第三个端口驱动程序写控制寄存器C300-C377。
创建一个驱动程序,其被允许在16位Modbus地址空间中寻址任何位置,是可能的。每个读或写操作仍然被限制于125/123个字的限制。在这种情况中,被每个记录使用的asyn地址是绝对Modbus地址。在创建这个驱动程序时,通过传递-1作为modbusStartAddress,启用绝对寻址模式。
modbus端口驱动程序对单个Modbus功能的限制不应用到doModbusIO()方法。此访问可以用于使用任何功能码的任意Modbus IO。如果如上描述启用了绝对寻址,则doModbusIO()函数也可以寻址任意Modbus内存位置。
端口驱动程序的行为对读功能码(1,2,3,4),写功能码(5,6,15,16)以及读/写功能码(23)不同。
Modbus读功能
对于读功能码(当未使用绝对寻址时),这个驱动程序产生一个poller线程。这个poller线程在单个Modbus事务中读取分配给这个端口的整个Modbus内存块。值被存储在此驱动程序一个缓存中。在创建这个端口驱动时,设置了查询之间的延时,并且在之后运行时可以被更改。EPICS通过使用标准asyn接口(asynUInt32Digital, asynFloat64, asynInt32等)读取这些值。被读取的值是来自这个poller线程的最后被存储值。这表示EPICS读取操作是异步的,即:它们可以阻塞。这是因为虽然它们不直接导致Modbus I/O,它们需要等待一个指明这个poller线程完成的mutex。
对于读功能,设置EPICS记录为"I/O Intr"扫描是可能的。如果这已经完成了,则当有新数据对应那个输入时,这个端口驱动程序将回调设备支持。这提高了效率,因为这样的记录只在需要时才运行,不需要周期地扫描它们。
先前地段落描述了对应读取操作的正常配置,在此使用了相对的Modbus寻址。如果使用绝对寻址,则这个驱动不创建一个poller线程,因为它不知道Modbus地址空间的什么部分应该被查询。在这种情况中,进行读取的记录不能设置SCAN=I/O Intr。它们要么被周期地扫描,或者通过直接使记录运行被扫描,诸如写1到.PROC字段。这个记录每次运行时,它将产生一个单独地Modbus读操作。注意:这效率比用相对Modbus寻址一次读取很多寄存器低得多。出于此原因,通常应该避免用读功能的绝对Modbus寻址。
Modbus写功能
对于写功能,此驱动不自己创建一个单独的线程。此驱动程序而是进行Modbus I/O立即响应在标准asyn接口上的写操作。这表示EPICS写操作也是异步的,即:因为需要Modbus I/O,所有它们阻塞。当创建modbus驱动程序时,它告诉asynManager它可以阻塞,并且asynManager创建一个执行写操作的单独线程。
使用读/修改/写操作,完成使用asynUInt32Digital接口(掩码参数不是0x0或0xFFFF)的字写操作。这使得多个Modbus客户端可以读和写在相同Modbus内存块单个字。如果多个Modbus客户端(或PLC自身)能够修改单个字中位时,它不确保正确的操作。这是因为Modbus服务器不能以Modbus客户端级别一个原子操作执行读/修改/写。
对于写操作,指定单个读操作应该在端口驱动程序被创建时完成是可能的。这是通常被使用的,使得EPICS在IOC被初始化时获取一个输出设备的当前值。
Modbus RTU指定在向设备写之间一个至少3.5个字符的延时。modbusInterposeConfig函数允许你指定在每次写前一个写延时(msec为单位)。
Modbus写/读函数
Modbus功能码23允许写在单次操作中写一个寄存器集合并且读取一个寄存器集合。在写操作后执行读操作,并且要被读取的寄存器范围可以不同于要被写的寄存器范围。功能码23不被广泛使用,并且写/读操作不合适只读和只写驱动程序的modbus驱动程序模型。在modbus中实现23功能码有以下限制:
- 一个使用modbus功能码23的驱动程序要么只读要么只写
- 通过指定功能码123给drvModbusAsynConfigure命令创建的只读驱动程序。此驱动程序将使用对应Modbus协议的Modbus功能码23。它将只读取寄存器(如功能码3和4),它将不写任何数据到设备。
- 通过指定功能码223给drvModbusAsynConfiure命令创建的只写驱动程序。此驱动程序将使用对应Modbus协议的Modbus功能码23。它将只写寄存器(如功能码16),它将不从设备读取任何数据。
平台无关
modbus应该运行在所有EPICS平台。已经在linux-x86, linux-x86_64, vxWorks, win32-x86,window-x6(带有Microsoft Visual Studio 2010 C++编译器的本地Windows),和cygwin-x86(带有gcc编译器和Cygwin库的Windows)上测试了它。
在modbus中可能与架构相关的唯一东西是在modbus.h中结构体包装。在gnu和Microsoft编译器上支持在那是使用的指令"#pragma pack(1)"。如果在某些感兴趣的编译器上不支持这个指令,则modbus.h将需要添加合适的架构相关代码。
创建一个modbus驱动程序
在modbus端口驱动程序被创建前,必须至少首先创建一个asyn TCP/IP或串口驱动程序来与硬件通信。所需命令取决于正在被使用的通信链路。
TCP/IP
对于TCP/IP,使用以下标准asyn命令:
drvAsynIPPortConfigure(portName, hostInfo, priority, noAutoConnect, noProcessEos)
以下示例在IP地址192.168.3.80的TCP端口502上创建了一个名为"Koyo1"的asyn IP端口驱动程序。使用默认优先级,并且noAutoConnect标志被设置为0,因而asynManager将进行正常的自动连接管理。设置noProcessEos为1,因为在TCP上的Modbus不需要字符串结尾处理。
drvAsynIPPortConfigure("Koyo1","192.168.3.80:502",0,0,1)
Serial RTU
对于串行RTU,使用以下标准asyn命令:
- drvAsynSerialPortConfigure(portName, ttyName, priority, noAutoConnect, noProcessEos)
- asynSetOption(portName, addr, key, value)
以下示例在/dev/ttyS1上创建了一个名为"Koyo1"的asyn本地串口驱动程序。使用默认优先级,并且noAutoConnect标志被设为0,使得asynManager将进行正常的自动连接管理。noProcessEos设置为0,因为在串行上的Modbus需要字符串结尾处理。串行端口参数被配置成38400波特,不检验,8数据位,1停止位。
- drvAsynSerialPortConfigure("Koyo1", "/dev/ttyS1", 0,0,0)
- asynSetOption("Koyo1",0,"baud","38400")
- asynSetOption("Koyo1",0,"parity","none")
- asynSetOption("Koyo1",0,"bits",8)
- asynSetOption("Koyo1",0,"stop",1)
Serial ASCII
对于串行RTU,使用与用于串行RTU描述相同的命令。在asynSetOption命令之后,使用以下标准asyn命令:
- asynOctetSetOutputEos(portName, addr, eos)
- asynOctetSetInputEos(portName, addr, eos)
以下示例在/dev/ttyS1上创建了一个名为"Koyo1"的asyn本地串口驱动程序。使用默认优先级并且设置noAutoConnect标记为0,使得asynManager将进行正常的自动连接管理。noProcessEos标志设为0,因为在串行上的Modbus需要字符串结尾处理。串口参数被设置成38400,不校验,8数据位,1停止位。输入和输出的end-of-string被设置成CR/LF。
- drvAsynSerialPortConfigure("Koyo1", "/dev/ttyS1", 0,0,0)
- asynSetOption("Koyo1",0,"baud","38400")
- asynSetOption("Koyo1",0,"parity","none")
- asynSetOption("Koyo1",0,"bits",8)
- asynSetOption("Koyo1",0,"stop",1)
- asynOctetSetOutputEos("Koyo1",0,"\r\n")
- asynOctetSetInputEos("Koyo1",0, "\r\n")
modbusInterposeConfig
在创建asynIPPort或者asynSerialPort驱动程序后,下一步是添加asyn “interpose interface”驱动程序。此驱动程序接收设备无关的Modbus帧并且位TCP,RTU或ASCII链路协议添加或移除通信链路专用信息。用以下命令创建这个interpose驱动程序:
modbusInterposeConfig(portName, linkType, timeoutMsec, writeDelayMsec)
modbusInterposeConfig命令
参数 | 数据类型 | 描述 |
portName | string | 先前创建的asynIPPort或asynSerialPort的名称 |
linkType | int |
Modbus链路层类型: 0=TCP/IP 1=RTU 2=ASCII |
timeoutMsec | int | 对底层asynOcete驱动程序写和读操作的超时时间(毫秒为单位)。这个只被用于替代在EpICS设备支持中指定的超时时间。如果指定0,则使用要给默认2000毫秒的超时。 |
writeDelayMsec | int | 每次从EPICS写到设备前延时(毫秒为单位)。这一般只是Serial RTU设备需要。Modicon Modbus Protocol Reference Guide说着对于Serial RUT必须至少3.5个字符时间,即在9600波特时3.5毫秒。默认为0。 |
对于以上串行ASCII示例,在asynOctetSetInputEos命令后,将使用以下命令。着使用1秒超时时间,以及一个0ms的写超时。
modbusInterposeConfig("Koyo1",2,1000,0)
drvModbusAsynConfigure
一旦创建了asyn IP或串行端口驱动程序,并且已经配置了modbusInterpose驱动程序,用以下命令创建一个modbus端口驱动程序:
drvModbusAsynConfigure命令drvModbusAsynConfigure(portName, t
cpPortName, slaveAddress, modbusFunction, modbusStartAddress, modbusLength, dataType, poll
Msec, plcType)
参数 | 数据类型 | 描述 |
portName | string | 要被创建的modbus端口的名称 |
tcpPortName | string | 先前创建的asyn IP或串口的名称 |
slaveAddress | int | Modbus slave的地址。这必须匹配对应RTU和ASCII的Modbus slave的配置。对于TCP,slave地址被用于"单元标识符",最后字段在MBAP头。"单元标识符"被大部分PLCs忽略,但某些PLC可能需要。 |
modbusFunction | int | modbus功能ma(1,2,3,4,5,6,15,16,123(对应23只读))或223(对于23只写) |
modbusStartAddress | int | 要被访问的odbus数据段的起始地址。对于相对寻址,这必须在十进制范围0-65536,或者八进制范围0-0177777。对于绝对寻址,这必须设置为-1。 |
modbusLength | int |
要被访问的Modbus数据段的长度。 对于Modbus功能码1,2,5和15,以位指定这个。 对于Modbus功能码3,4,6,16,23,以16位字指定这个。 对于功能码1和2,长度限制是2000,对于功能5和15长度限制是1968. 对于3和4功能码,长度限制是125,对于功能码6,16和23,长度限制是123。 对于绝对寻址,这必须被设置成可能被使用的最大单次Modbus操作所需的尺寸。如果所有Modbus读和写是用于16位寄存器,这会是1,但如果64位浮点(4个16位寄存器)正在被使用,它将是4,例如,如果一个NELM=100的Int32 waveform记录 正在被读取或写,它将是200。 |
modbusDatatype | int | 这为这个端口设置默认数据类型。如果一个记录的drvUser是空或者它是MODBUS_DATA,这是使用的数据类型。支持的Modbus数据类型和相应的drvUser字段在以下表格中被描述。 |
pollMsec | int | 用于读取功能的查询线程的查询延时(毫秒为单位)。对于写功能,一个非0值表示在这个端口驱动程序首次被创建时Modbus数据应该被读取一次。 |
plcType | string |
PLC的类型(例如:Koyo, Modicon等)。 这个参数当前被用于在asynReport中打印信息。如果plcType字符串包含子串"Wago",它也用于特殊地对待Wago设备。 |
Modbus寄存器数据类型
modbus功能码3,4,6和16是用于访问16位寄存器。Modbus说明没有定义在这些寄存器中数据如何被解析,例如,以有符号或者无符号数值,二进制编码十进制(BCD)数值等。实际中,厂家组合多个16位寄存器去编码32位整数,32位或64位浮点数。以下表格列出了modbus支持地数据类型。用以上描述地modbusDataType参数定义了这个端口的默认数据类型。通过在链接中用drvUser字段指定一个不同的数据类型,对应特定记录的数据类型可以重写这个默认值。驱动程序使用这个信息在EPICS设备支持和Modbus之间转换数值。数据以epicsUInt32, epicsInt32和epicsFloat64数值与EPICS设备支持来回传输。注意:在此表格中描述的数据类型转换只用于使用asynInt32或asynFloat64接口的记录,在使用asynInt32Digtial接口时没有使用数据类型转换。asynUInt32Digital接口总是把寄存器当成无符号16位整数。
支持的Modbus数据类型
Modbus DataType 值 |
drvUser字段 | 描述 |
0 | UNIT16 | 无符号16位二进制是整数 |
1 | INT16SM | 16位二进制整数,符号和幅度格式。在这种格式中,第15位是符号位,第0-14位是这个数值的幅度的绝对值。这是Koyo PLCs使用用于数值的其中一种格式,诸如ADC转换。 |
2 | BCD_UNSIGNED | 二进制编码BCD,无符号。这种数据类型是对应由4个4位半字节组成的一个16位数值,每个半字节编码一个从0到9的十进制数值。一个BCD |
3 | BCD_SIGNED | 4个数字编码的十进制(BCD),有符号。这种数据类型是用于由3个4位半字节和一个3位比特组成的一个16位数值。第15位是一个符号位。有符号BCD数值可以保存从-7999到+7999的数值。这是由Koyo PLCs用于数值的其中一种格式,诸如ADC转换。 |
4 | INT16 | 16位带符号的整数(2的补码)。当转换成epicsInt32时,这种数据类型扩展符号位。 |
5 | INT32_LE | 32位整数,小端(最低字在Modbus地址N,最高字在Modbus地址N+1)。 |
6 | INT32_BE | 32位整数。大端(最高字在Modbus地址N+1,最低字在Modbus地址N+1) |
7 | FLOAT32_LE | 32位浮点,小端(最低字在Modbus地址N,最高字在Modbus地址N+1) |
8 | FLOAT32_BE | 32位浮点,大端(最高字在Modbus地址N,最低字在Modbus地址N+1) |
9 | FLOAT64_LE | 64位浮点,小端(最低字在Modbus地址N,最高字在Modbus地址N+3) |
10 | FLOAT64_BE | 64位浮点,大端(最高字在Modbus地址N,最低字在Modbus地址N+3) |
11 | STRING_HIGH | 字符串数据。在每个寄存器的高位字节中存储一个字符。 |
12 | STRING_LOW | 字符串数据。在每个寄存器的低位字节中存储一个字符 |
11 | STRING_HIGH_LOW | 字符串数据。在每个寄存器中存储两个字符,第一个字符在高字节,第二个字符存储低字节。 |
11 | STRING_LOW_HIGH | 字符串数据。在每个寄存器中存储两个字符,第一个存在低字节,第二个存在高字节。 |
注意:如果想要在asynInt32接口上不经转换的传输BCD数值给EPICS,则应该使用数据类型0,因为在这种情况中,不进行转换。
以下时一个使用32位浮点值的示例ai记录:
- # 用于寄存器输入的ai记录模板
- record(ai, "$(P)$(R)"){
- field(DTYP, "asynFloat64")
- field(INP, "@asyn($(PORT) $(OFFSET)FLOAT32_LE)")
- field(HOPR, "$(HOPR)")
- field(LOPR, "$(LOPR)")
- field(PREC, "$(PREC)")
- field(SCAN, "$(SCAN)")
- }
Wago设备的注意
Wago设备的注意
通常在与写操作相同的地址进行这种初始读取操作。Wago设备不同于其它Modbus设备,因为要回读一个寄存器的地址与要写一个寄存器的地址不同。对于Wago设备,用于回读一个Modbus写功能的初始值的地址必须比用于写功能的地址大0x200。如果传给drvModbusAsynConfigure的plcType包含子串"Wago"(区分大小写),通过位回读地址添加0x200的偏移处理这个。注意:这不影响用于Wago读功能的地址。用户必须为读功能指定实际的Modbus地址。
用于TCP的drvAsynIPPort驱动程序的数目
每个drvAsynIPPort驱动程序创建一个单独的TCP/IP套接字连接PLC。使所有modbus端口驱动共享单个drvAsynIPPort驱动程序是可能的。在这种情况中,在单个套接字上以"串行"方式进行对这个PLC的所有I/O。一个modbus驱动程序的一个事务必须在另一个modbus驱动程序的事务开始前结束。创建多个drvAsynIPPort驱动程序(套接字)到单个PLC也是可能的,并且对每个modbus端口使用一个不同的drvAsynIPPort。在这种情况中,来自多个modbus驱动程序的I/O操作可以并行运行,而不是串行。这可以以在IOC和PLC上更多CPU负载以及多个网络流量为代价提高性能。
注意很多PLCs在几秒不活动后将超时是重要的。这对使用读功能码的modbus驱动程序不是一个问题,因为它们频繁进行查询。但使用写功能码的modbus驱动程序可能只进行偶尔的I/O,并且如果它们是通过一个drvAsynIPPort驱动程序进行通信的仅有驱动程序。因而,对于有写功能码的modbus驱动程序通常需要以至少一个有读功能码的驱动程序使用相同drvAsynIPPort驱动程序(套接字)来避免超时。
每个PLC使用多少drvAsynIPPort驱动程序的选择将根据外设性能以及资源 使用的考虑。一般从每个PLC一个drvAsynIPPort服务器开始(例如:由用于那个PLC的所有modbus驱动程序共享)并且看看此结果是否满足性能。
数值格式
用八进制而非十进制指定modbusStartAddress和modbusLength会是方便的,因为在大部分PLCs上这是方便的。在iocsh和vxWorks shell中,通过在数值前带一个0做这件事,例如:040400是一个八进制数。
EPICS设备支持
modbus实现了以下标准asyn接口:
- asynUInt32Digital
- asynInt32
- asynInt32Array
- asynFloat64
- asynOctet
- asynCommon
- asynDrvUser
因为它实现了这些标准接口,完全用asyn自身提供的通用EPICS设备支持进行EPICS设备支持。没有提供作为modbus组成部分的特殊设备支持。
必须使用asyn R4-8或以上,因为对asyn做了某种次要增强来支持modbus所需的特性。
以下表格记录了EPICS设备支持使用的asyn接口。
由这个去哦的那个程序使用的drvUser参数确定了从设备支持发送什么命令。默认是MODBUS_DATA,因而其是设备支持链接说明中可选的。如果没有使用drvUser或者如果治党MODBUS_DATA,则用于使用asynInt32和asynFloat64接口的记录的数据类型是在drvModbusAsynConfigure命令中指定的默认数据类型。记录可以通过指定数据类型专用的drvUser字段重写默认的Modbus数据类型,例如:BCD_SIGNED, INT16,FLOAT32_LE等。
offset参数用于为一个记录指定相对于那个驱动程序的起始Modbus地址的数据位置。用位为使用功能1,2,5和15控制离散输入或线圈的驱动程序指定这个offset。例如,如果Modbus功能是2,并且Modbus起始地址是04000,则offset=2指向地址04002。对于Koyo PLC,X输入是位于对应功能2的Modbus起始地址,所以offset=2是输入X2。
如果使用了绝对寻址,则offset参数是一个绝对16位Modbus地址,而不i是相对于其是-1的起始Modbus地址。
用字为使用Modbus功能3,4,6和16寻址输入寄存器或保持寄存器的驱动程序指定offset。如果Modbus功能被设置成6并且Modbus地址是040600,则offset指向地址040602。对于Koyo PLC,在Modbus起始地址用功能6以16位字访问C控制继电器,offset=2将写到第三个16位字,其是线圈C40-C57。
对于32位或64位数据类型(INT32_LE, INT32_BE, FLOAT32_LE, FLOAT32_BE),offset指定第一个16位寄存器的位置,而第二个寄存器是在offset+1等。
对于字符串数据类型(STING_HIGH, STRING_LOW, STRING_HIGH_LOW, STRING_LOW_HIGH),offset指定第一个16位寄存器的位置,而第二个寄存器是在offset+1等。
asynUInt32Digital
用以下选择asynUInt32Digital设备支持
- field(DTYP, "asynUInt32Digital")
- field(INP, "@asynMask(portName, offset, mask timeout)drvUser")
asynUInt32Digial设备支持
Modbus 功能 |
偏移类型 | 数据类型 | drvUser | 支持的记录 | 描述 |
1,2 | 位 | 单bit | MODUBS_DATA |
bi,mbbi, mbbiDirect longin |
value=(Modbus data & mask) (通常mask=1) |
3,4,23 | 16位字 | 16位字 | MODUBS_DATA |
bi,mbbi, mbbiDirect longin |
value=(modbus data & mask) (mask选择感兴趣的位) |
5 | 位 | 单bit | MODUBS_DATA |
bo, mbbo, mbboDirect longout |
modbus写(value&mask) (通常mask=1) |
6 | 16位字 | 16位字 | MODUBS_DATA |
bo, mbbo, mbboDirect longout |
如果mask==0或mask=0xFFFF,进行modbus写(value),否则进行读/修改/写:对在value中置1和在mask中置1的位置1,在value中置0和在mask中置1的位,置0 |
any | NA | NA | ENABLE_HISTOGRAM |
bi,mbbi, mbbiDirect, longin |
根据在驱动中禁用/启用I/O时间直方图,返回0/1 |
any | NA | NA | ENABLE_HISTOGRAM |
bo, mbbo, mbboDirect longout |
根据value=0/1,则在驱动中禁用/启用I/O时间直方图 |
asynInt32
用以下选择asynInt32设备支持:
- field(DTYP, "asynInt32")
- field(INP, "@asyn(portName, offset, timeout)drvUser")
或者
field(INP, "@asynMask(portName, offset, nbits, timeout)drvUser")
asynMask语法用于模拟I/O设备,为了指定在设备中的位数。对于Modbus需要这个,因为驱动程序只知道它返回了一个16位寄存器,单步知道在设备中实际的位数,并且因而不能用asynInt32->getBounds()返回有意义的数据。
nbits>0对应一个单极性设备。例如,nbits=12表示范围0-4095的单极性12位设备。nbits<-对应一个双极性设备。例如,nbits=-12表示范围-2048-2047的双极性12位设备。
注意:当写32位或64位值时,如果设备支持共功能码16,应该使用它。这种写将是"原子的"。如果使用功能码6,则将多条消息写这个数据,将有一段短时间,在此段时间内设备有不正确的数据。
asynInt32设备支持
Modbus功能 | 偏移类型 | 数据类型 | drvUser | 支持的记录 | 描述 |
1,2 | 位 | 单bit | MODBUS_DATA |
ai,bi,mbbi, longin |
value=(epicsUInt32)Modbus data |
3,4,23 | 16位字 | 16,32或64位字 | MODBUS_DATA(或数据类型专用值) |
ai,mbbi, longin |
value=(epicsUInt32)Modbus data |
5 | 位 | 单bit | MODBUS_DATA |
ao,bo,mbbo, longout |
modbus写值 |
6,16,23 | 16位字 | 16,32或64位字 | MODBUS_DATA(或数据类型专用值) |
ao,mbbo, longout |
modbus写值 |
any | NA | NA | MODBUS_READ | ao,bo,longout | 用这个drvUser值写到一个Modbus输入驱动程序将强制这个poller线程立即运行一次,无论POLL_VALUE的值。 |
any | NA | NA | READ_OK | ai,longin | 返回在这个asyn端口上成功读操作的数目。 |
any | NA | NA | WRITE_OK | ai,longin | 返回在这个asyn端口上成功写操作的数目。 |
any | NA | NA | IO_ERRORS | ai,longin | 返回在这个asyn端口上I/O错误的数目 |
any | NA | NA | LAST_IO_TIME | ai,longin | 返回用于上次I/O操作的毫秒数 |
any | NA | NA | MAX_IO_TIME | ai,longin | 返回用于I/O操作的最大毫秒数目 |
any | NA | NA | HISTOGRAM_BIN_TIME | ai,longin | 用毫秒设置在统计直方图中每个bin的时间 |
asynFloat64
用以下选择asynFloat64设备支持
- field(DTYP, "asynFloat64")
- field(INP, "@asyn(portName, offset, timeout)drvUser")
注意:当写32位或64位值时,如果设备支持支持功能码16,应该使用它。这种写将是"原子的"。如果使用功能码6,将用多条消息写这个数据,将有一段短时间,在这段时间内,设备将有不正确的数据。
Modbus功能 | 偏移类型 | 数据类型 | drvUser |
支持的 记录 |
描述 |
1,2 | 位 | 单bit | MODBUS_DATA | ai | value=(epicsFloat64)Modbus data |
3,4,23 | 16位字 | 16,32或64位字 | MODBUS_DATA(或数据类型专用值) | ai | value=(epicsFloat64)modbus data |
5 | 位 | 单bit | MODBUS_DATA | ao | modbus写(epicsUInt16)value |
6,16,23 | 16位 | 16位字 | MODBUS_DATA(或数据类型专用值) | ao | modbus写值 |
any | NA | NA | POLL_DELAY | ai,ao | 用于读poller线程的查询之间读或写延时时间(秒为单位)。如果小于等于0,poller线程不周期地运行,只在其被一个epicsEvent信号唤醒时它才运行,这发生在驱动程序有一个用MODBUS_READ字符串的asynInt32写时。 |
asynInt32Array
- field(DTYP, "asynInt32ArrayIn")
- field(INP, "@asyn(portName, offset, timeout)drvUser")
- 或
- field(DTYP, "asynInt32ArrayOut")
- field(INP, "@asyn(portName, offset, timeout)drvUser")
asynInt32Array设备支持用于读或写最多2000个线圈值或者对多125个16位寄存器的数组。当启用直方图时,它也用于读取I/O次数的直方数组。
asynInt32Array设备支持
modbus功能 | 偏移类型 | 数据类型 | drvUser | 支持的记录 | 描述 |
1,2 | 位 | 位的数组 | MODBUS_DATA | waveform(input) | value=(epicsInt32)Modbus data[] |
3,4,23 | 16位字 | 16,32,64位字的数组 | MODBUS_DATA(或数据类型专用值) | waveform(input) | value=(epicsInt32)Modbus data[] |
15 | bit | bits数组 | MODBUS_DATA | waveform(output) | modbus写(epicsUInt16)value[] |
16,23 | 16位字 | 16,32,64位字的数组 | MODBUS_DATA(或数据类型专用值) | waveform(output) | modbus写value[] |
any | 32位字 | NA | READ_HISTOGRAM | waveform(input) | 返回从上次启用直方图以来一个I/O次数的直方数组(毫秒为单位) |
any | 32位字 | NA | HISTOGRAM_TIME_AXIS | waveform(input) | 返回直方图数据的时间轴。每个元素是HISTOGRAM_BIN_TIME毫秒 |
asynOctet
用以下选择asynOctet设备支持
- field(DTYP, "asynOctetRead")
- field(INP, "@asyn(portName, offset, timeout)drvUser")
- 或
- field(DTYP, "asynOceteWrite")
- field(INP,"@asyn(portName, offset, timeout)drvUser")
asynOctet设备支持用于读或写最多250个字符的字符串。
注意:在waveform记录或stringout记录中串末尾的0终止字节不被写到Modbus设备。
注意:从Modbus设备读取的输入字符数目是以下两种中的较小者:
1) 在记录中字符数目减去终结的0字节(对于stringin为39,对于waveform是NELM-1)或
2) 在寄存器中包含的字符数目定义了传递给drvModbusAsynConfigure的modbusLength参数(modbusLength或modbusLength*2取决于drvUser字段指定每个寄存器1个或2个字符)。
如果从Modbus读取的任意字符是0字节,这个字符串将被截短,但不保证Modbus寄存器中在字符串中最后一个字符后跟一个0字节。
asynOctet设备支持
modbus功能 | 偏移类型 | 数据类型 | drvUser | 支持的记录 | 描述 |
3,4,23 | 16位字 | 字符串 |
STRING_HIGH,STRING_LOW STRING_HIGH_LOW STRING_LOW_HIGH |
waveform(输入) 或 stringin |
value=modbus data[] |
16,23 | 16位字 | 字符串 |
STRING_HIGH,STRING_LOW STRING_HIGH_LOW STRING_LOW_HIGH |
waveform(输出) 或 stringout |
modbus写value[] |
模板文件
modbus在modbusApp/Db目录中提供了示例模板文件。这些包括:
模板文件
modbus在modbusApp/Db目录中提供了一个示例文件。这些包含:
文件 | 描述 | 宏参数 |
bi_bit.template | asynUInt32Digital支持具有离散输入或线圈的bi记录。Mask=1 |
P,R,PORT, OFFSET, ZNAM, ONAM, ZSV, OSV, SCAN |
bi_word.template | asynUInt32Digital支持具有寄存器输入的bi记录。 |
P, R, PORT, OFFSET, MASK, ZNAM, ONAM, ZSV, OSV |
mbbiDirect. template |
asynUInt32Digital支持具有寄存器输入的mbbiDirect记录 |
P,R,PORT, MASK OFFSET, SCAN |
longin.template | asynUInt32Digital支持具有寄存器输入的longin记录。Mask=0xFFFF。 |
P,R,PORT,OFFSET, SCAN |
longinInt32. template |
asynInt32支持具有寄存器输入的longin记录 |
P,R,PORT,OFFSET, SCAN,DATA_TYPE |
intarray_in.template | asynInt32Array支持具有离散,线圈或寄存器输入的waveform记录 |
P,R,PORT,OFFSET, NELM,SCAN |
bo_bit.template | asynUInt32Digital支持具有线圈输出的bo记录。Mask=1 |
P,R,PORT,OFFSET, ZNAM,ONAM |
bo_word.template | asynUInt32Digital支持具有寄存器输出的bo记录。 |
P,R,PORT,OFFSET, MASK, ZNAM ONAM |
mbboDirect.template | asynUInt32Digital支持具有寄存器输出的mbboDirect记录 |
P,R,PORT,OFFSET, MASK |
longout.template | asynUInt32Digital支持具有寄存器输出的longout记录。Mask=0xFFFF。 | P,R,PORT,OFFSET |
longoutInt32.template | 对带有寄存器输出的longout记录的asynInt32支持 |
P,R,PORT,OFFSET, DATA_TYPE |
intarray_out.template | 对带有离散,线圈或寄存器输出的waveform记录的支持 |
P,R,PORT,OFFSET, NELM |
ai.template | 对带有LINEAR转换的ai记录的asynInt32支持 |
P,R,PORT,OFFSET,BITS EGUL,EGUF,PREC, SCAN |
ai_average.template | 对带有LINEAR转换的ai记录的asynInt32Average支持。每查询线程读取模拟输入,并且直到记录被运行对读取就平均,这个支持获取回调。 |
P,R,PORT,OFFSET, BITS,EGUL,EGUF PREC,SCAN |
ao.template | asynInt32支持带有LINEAR转换的ao记录 |
R,R,PORT,OFFSET BITS,EGUL,EGUF PREC |
aoFloat64.template | asynFloat64支持ao记录 |
P,R,PORT,OFFSET LOPR,HOPR,PREC DATA_TYPE |
stingin.template | 对string记录的asynOctet支持 |
P,R,PORT,OFFSET DATA_TYPE,SCAN |
stringout.template | 对stringout的asynOctet支持 |
P,R,PORT,OFFSET DATA_TYPE,NELM SCAN |
stringWaveformIn .template |
对waveform记录的asynOctet输入支持 |
P,R,PORT,OFFSET, DATA_TYPE,NELM, SCAN |
stringWavefromOut .template |
对waveform记录的asynOctet输出支持 |
P,R,PORT,OFFSET, DATA_TYPE,NELM INITIAL_READBACK |
asynRecord.template | 对asyn记录的支持。对控制跟踪打印,和用于调试有用 |
P,R,PORT,ADDR,TMOD, IFACE |
poll_delay.template | 对控制poller线程的延时时间的ao记录的支持 | P,R,PORT |
poll_trigger.template | 对触发运行poller线程的bo记录的支持 | P,R,PORT |
statistics.template | 对控制读取端口的I/O统计数据的bo,longin和waveform记录的支持 |
以下表格解释了在前一个表格中使用的宏参数。
宏参数
宏 | 描述 |
P | 这个记录的前缀。完整的记录名是$(P)$(R)。 |
R | 记录名。完整的记录名是$(P)$(R) |
PORT | 对modbus asyn端口的端口名 |
OFFSET | 相对于这个端口的起始地址的Modbus数据的偏移。 |
MASK | 用于位这个记录选取数据的位掩码。 |
ZNAM |
用于对应bi/bo记录的0值的字符串。 |
ONAM | 用于对应bi/bo记录的1值得字符串 |
OSV | 对应bi/bo记录的0严重性 |
ZSV | 对应bi/bo记录的1严重性 |
BITS | 用于模拟I/O设备的位数。>0=单极性,<0=双极性 |
DATA_TYPE | 指定Modbus数据类型的drvUser字段。如果这个字段位空或是MODBUS_DATA,则使用在drvModbusAsynConfigure命令指定的默认数据类型。在前面表格中列出了其它可用值(UINT16, INT16SM, BCD_SIGNED等) |
EGUL | 对应模拟设备低限的工程单位 |
EGUF | 对应模拟设备高限的工程单位 |
LOPR | 模拟设备的低显示限制 |
HOPR | 模拟设备的高显示限制 |
PREC | 用于ai/ao记录的精度数字位数 |
NELM | 在waveform记录中的元素数目 |
ADDR | 对应asyn记录的地址,与以上OFFSET相同。 |
TMOD | 对应asyn记录的传输模式。 |
IFACE | 用于asyn记录的asyn接口 |
SCAN | 记录的扫描速率(例如:"1 second", "I/O Intr"等) |
INITIAL_ READBACK |
控制duistringout或string waveform输出记录是否从设备读取一个初始回读。 |
示例应用程序
modbus构建一个名为modbusApp的示例应用程序。可以运行这个应用程序去控制任意数目的Modbus PLCs。
在iocBoot/iocTest目录中,有用于EPICS IOCs的若干启动脚本。它们被用于在Koyo PLCs上测试大部分modbus驱动程序特性,诸如来自Automation Direct的DL系列。
1) Koyo1.cmd创建modbus端口驱动程序去读取X输入,写入Y输出,以及读写C控制寄存器。以线圈和以寄存器(V内存)都可以访问这些输入和输出集合中每个集合。装载bi/bo,mbbiDirect/mbboDirect和waveform记录使用这些驱动程序去读和写。
2) Koyo2.cmd创建一个modbus驱动程序去读取X输入,写到Y输出,并且读取C控制寄存器。仅使用线圈访问。这个示例也读取一个4通道13位双极A/D转换器。使用有符号BCD和符号和幅度二进制格式测试了这个驱动。注意:必须装载一个进行合适的A/D值转换成V内存的梯形逻辑程序。
3) st.cmd是在非vxWorks IOCs上运行的简单示例启动脚本。它只是装载Koyo1.cmd和Koyo2.cmd。使用以下像以下的命令调用它:
../../bin/linux-x86/modbusApp st.cmd
你也可以用以下分别装载Koyo1.cmd和Koyo2.cmd:
../../bin/linux-x86/modbusApp Koyo1.cmd
st.cmd.vxWorks是一个运行在vxWorks IOCs上的简单示例启动脚本。它只是装载Koyo1.cmd和Koyo2.cmd。
以下是Koyo1.cmd起始,在/dev/ttyS1上用slave地址1为串行RTU配置它。它也展示了如何配置TCP和串行ASCII连接。(但Koyo PLCs不支持ASCII)。
# Koyo1.cmd dbLoadDatabase("../../dbd/modbus.dbd") modbus_registerRecordDeviceDriver(pdbbase) # 对TCP/IP使用以下命令 #drvAsynIPPortConfigure(const char *portName, 这个asyn端口的端口名,供后面使用 # const char *hostInfo, 这个端口的主机信息,IP地址:tcp端口号 # unsigned int priority, 优先级,0为默认优先级 # int noAutoConnect, 是否自动连接,0表示进行自动连接 # int noProcessEos); 是否进行字符末尾字符处理,1表示不处理 # drvAsynIPPortConfigure("Koyo1","164.54.160.158:502",0,0,1) #modbusInterposeConfig(const char *portName, 先前创建的asynIPPort或asynSerialPort的名称 # modbusLinkType linkType, 链路类型,0表示TCP/IP # int timeoutMsec, 对底层asynOcete驱动程序写和读操作的超时时间 # int writeDelayMsec) 每次从EPICS写到设备前延时(毫秒为单位)。一般仅串行RTU需要使用。 modbusInterposeConfig("Koyo1",0,5000,0) # 对serial RTU或ASCII使用以下命令 #drvAsynSerialPortConfigure(const char *portName, # const char *ttyName, # unsigned int priority, # int noAutoConnect, # int noProcessEos); #drvAsynSerialPortConfigure("Koyo1", "/dev/ttyS1", 0, 0, 0) #asynSetOption("Koyo1",0,"baud","38400") #asynSetOption("Koyo1",0,"parity","none") #asynSetOption("Koyo1",0,"bits","8") #asynSetOption("Koyo1",0,"stop","1") # 对串行RTU使用以下命令 # 注意:可能需要非0写延时(末尾参数) #modbusInterposeConfig("Koyo1",1,1000,0) # 对串行ASCII使用以下命令 #asynOctetSetOutputEos("Koyo1",0,"\r\n") #asynOctetSetInputEos("Koyo1",0,"\r\n") # 注意:可能需要非0写延时(最后一个参数) #modbusInterposeConfig("Koyo1",2,1000,0) # drvModbusAsynConfigure(portName, tcpPortName, slaveAddress, modbusFunction, # modbusStartAddress, modbusLength, dataType, pollMsec, plcType) # 注意:我们对起始地址和长度(起始0)使用八进制数值来保持与PLC命名法一致。 # 这是可选的,十进制数值(不以0开头)或十六进制数值也可以被使用。 # 在这些示例中,我们使用slave地址0(在"Koyo1"后面的数值) # DL205在Modbus偏移4000(八进制)对Xn输入位访问 # 读32位(X0-X37)。功能码=2,读离散输入 # 起始地址04000,长度040,数据类型UINT16,读取的查询时间100毫秒 drvModbusAsynConfigure("K1_Xn_Bit", "Koyo1", 0, 2, 04000, 040, 0, 100, "Koyo") # DL205在Modbus偏移40400(八进制)处对Xn输入进行字访问 # 读取8个字(128位)。功能码3,读寄存器输入。 # 这个modbus驱动程序名为"K1_Xn_Word", asyn端口驱动程序"Koyo1" # slave地址为0,功能码3,起始地址040400,长度010 drvModbusAsynConfigure("K1_Xn_Word", "Koyo1", 0, 3, 040400, 010, 0, 100, "Koyo") # DL205在Modbus偏移4000(八进制)对Yn输出进行位访问 # 读取32位(Y0-Y37)。功能码=1,读线圈。 drvModbusAsynConfigure("K1_Yn_In_Bit", "Koyo1", 0, 1, 04000, 040, 0, 100, "Koyo") # DL205在Modbus偏移4000(八进制)处对Yn输出进行位访问。 # 写32位(Y0-Y37)。功能码=5,写线圈。 drvModbusAsynConfigure("K1_Yn_Out_Bit", "Koyo1", 0, 5, 04000, 040, 0, 1, "Koyo") # DL205在Modbus偏移40500(八进制)处对Yn输出进行字访问。 # 读取8个字(128位)。功能码=3,读保持寄存器。 drvModbusAsynConfigure("K1_Yn_In_Word", "Koyo1", 0, 3, 040500, 010, 0, 100, "Koyo") # 写8个字(128位)。功能码=6,写寄存器。 drvModbusAsynConfigure("K1_Yn_Out_Word", "Koyo1", 0, 6, 040500, 010, 0, 100, "Koyo") # The DL205 has bit access to the Cn bits at Modbus offset 6000 (octal) # DL205在Modbus偏移6000(八进制)处对Cn位进行位访问。 # 访问256位(C0-C377)作为输入。读线圈。 功能码=1 drvModbusAsynConfigure("K1_Cn_In_Bit", "Koyo1", 0, 1, 06000, 0400, 0, 100, "Koyo") # 方位相同的256位作为输出。功能码=5,写线圈。 drvModbusAsynConfigure("K1_Cn_Out_Bit", "Koyo1", 0, 5, 06000, 0400, 0, 1, "Koyo") # 访问相同的256位(C0-C377)作为数组输出。功能码=15,写多个线圈。 drvModbusAsynConfigure("K1_Cn_Out_Bit_Array", "Koyo1", 0, 15, 06000, 0400, 0, 1, "Koyo") # DL205在Modbus偏移40600(八进制)处对Cn进行字访问。 # 我们使用前16个字(C0-C377)作为输入(256位)。功能码=3,读保持寄存器。 drvModbusAsynConfigure("K1_Cn_In_Word", "Koyo1", 0, 3, 040600, 020, 0, 100, "Koyo") # 我们访问相同的16个字(C0-C377)作为输出(256位)。功能码=6,写寄存器。 drvModbusAsynConfigure("K1_Cn_Out_Word", "Koyo1", 0, 6, 040600, 020, 0, 1, "Koyo") # 我们访问相同的16个字(C0-C377)作为数组输出(256位)。功能码=16,写多个寄存器。 drvModbusAsynConfigure("K1_Cn_Out_Word_Array", "Koyo1", 0, 16, 040600, 020, 0, 1, "Koyo") # 在八进制服务器上启用ASYN_TRACEIO_HEX asynSetTraceIOMask("Koyo1",0,4) # Enable ASYN_TRACE_ERROR and ASYN_TRACEIO_DRIVER on octet server #asynSetTraceMask("Koyo1",0,9) # Enable ASYN_TRACEIO_HEX on modbus server asynSetTraceIOMask("K1_Yn_In_Bit",0,4) # Enable all debugging on modbus server #asynSetTraceMask("K1_Yn_In_Bit",0,255) # Dump up to 512 bytes in asynTrace asynSetTraceIOTruncateSize("K1_Yn_In_Bit",0,512) dbLoadTemplate("Koyo1.substitutions") iocInit
注意:设计这个示例用于测试和演示目的,不是通常如何使用modbus的真实示例。例如,它装载了6个分别使用功能码1(读线圈),3(读保持寄存器),5(写单个线圈),6(写单个保持寄存器),15(写多个线圈和16(写多个保持寄存器)的驱动程序访问C控制继电器。这允许测试所有功能码和记录类型,包括waveforms。实际中,你通常最多字装载2个驱动程序用于C控制继电器,例如功能码1(读线圈),和功能码5(写单个线圈)。
bi_bit.template
mbbiDirect.template
intarray_in.template
bo_bit.template
在iocTest目录中有另一个示例应用程序,sim*.cmd和sim*.substitutions。这些示例用于测试不同的Modbus数据类型和其它特性。用Modbus Slave程序(modbus slave模拟器)测试了它们。
sim1.cmd
< envPaths
# simulator.cmd
dbLoadDatabase("../../dbd/modbus.dbd")
modbus_registerRecordDeviceDriver(pdbbase)
# 对TCP/IP使用以下命令
#drvAsynIPPortConfigure(const char *portName, asyn端口驱动名
# const char *hostInfo, 主机信息:IP地址:TCP端口号
# unsigned int priority, 优先级,0是默认优先级
# int noAutoConnect, 是否自动连接,0自动连接
# int noProcessEos); 是否处理字符串末尾,1不处理字符串
drvAsynIPPortConfigure("sim1","camaro:502",0,0,1)
#asynSetOption("sim1",0, "disconnectOnReadTimeout", "Y")
#modbusInterposeConfig(const char *portName, 前面创建的asyn端口驱动程序的名称
# modbusLinkType linkType, 链接类型,0是TCP/IP
# int timeoutMsec, 对底层asynOcete驱动程序写和读操作的超时时间
# int writeDelayMsec) 每次从EPICS写到Modbus前的延时。
modbusInterposeConfig("sim1",0,2000,0)
# 对串行RTU或ASCII使用以下命令
#drvAsynSerialPortConfigure(const char *portName,
# const char *ttyName,
# unsigned int priority,
# int noAutoConnect,
# int noProcessEos);
#drvAsynSerialPortConfigure("sim1", "/dev/ttyS1", 0, 0, 0)
#asynSetOption("sim1",0,"baud","38400")
#asynSetOption("sim1",0,"parity","none")
#asynSetOption("sim1",0,"bits","8")
#asynSetOption("sim1",0,"stop","1")
# 对串行RTU使用以下命令
# 注意:可能需要非0写延时(最后一个参数)
# 2000:对底层asynOcete驱动程序写和读操作的超时时间
#modbusInterposeConfig("sim1",1,2000,0)
# 对串行ASCII使用以下命令
#asynOctetSetOutputEos("sim1",0,"\r\n")
#asynOctetSetInputEos("sim1",0,"\r\n")
# 注意:可能需要非0写延时(最后一个参数)
# 2000:对底层asynOcete驱动程序写和读操作的超时时间
#modbusInterposeConfig("sim1",2,2000,0)
# 位访问Modbus地址0
# 访问128位
# 功能码=1,读取线圈
# 数据类型,默认MODBUS_DATA
# 100:毫秒,两次读取线圈之间的延时
# drvModbusAsynConfigure("portName", "tcpPortName", slaveAddress, modbusFunction, modbusStartAddress, modbusLength, dataType, pollMsec, "plcType")
drvModbusAsynConfigure("A0_In_Bits", "sim1", 0, 1, 0, 128, 0, 100, "Simulator")
# 位访问Modbus地址0
# 访问128比特作为输出。
# 功能码=5,写线圈。
# drvModbusAsynConfigure("portName", "tcpPortName", slaveAddress, modbusFunction, modbusStartAddress, modbusLength, dataType, pollMsec, "plcType")
drvModbusAsynConfigure("A0_Out_Bits", "sim1", 0, 5, 0, 128, 0, 100, "Simulator")
# Access 60 words as outputs. 访问60个字作为输出。
# 可以使用功能码6(单寄存器)或16(多寄存器),但16更好,因为当写大于16位的值时,它是原子的。
# 默认数据类型无符号整数。
# drvModbusAsynConfigure("portName", "tcpPortName", slaveAddress, modbusFunction, modbusStartAddress, modbusLength, dataType, pollMsec, "plcType")
drvModbusAsynConfigure("A0_Out_Word", "sim1", 0, 16, 100, 60, 0, 1, "Simulator")
# 0字访问Modbus地址100
# 访问60个字作为输入。
# 功能码=3
# 默认数据类型无符号。
# drvModbusAsynConfigure("portName", "tcpPortName", slaveAddress, modbusFunction, modbusStartAddress, modbusLength, dataType, pollMsec, "plcType")
drvModbusAsynConfigure("A0_In_Word", "sim1", 0, 3, 100, 60, 0, 100, "Simulator")
# Enable ASYN_TRACEIO_HEX on octet server
asynSetTraceIOMask("sim1",0,4)
# Enable ASYN_TRACE_ERROR and ASYN_TRACEIO_DRIVER on octet server
#asynSetTraceMask("sim1",0,9)
dbLoadTemplate("sim1.substitutions")
iocInit
sim1.substitutions
# 用于底层asyn octet端口的asyn记录
file "$(ASYN)/db/asynRecord.db" { pattern
{P, R, PORT, ADDR, IMAX, OMAX}
{SIM1: OctetAsyn, sim1, 0, 80, 80}
}
# 这些是用位访问完成的A0输入
# drvModbusAsynConfigure("A0_In_Bits", "sim1", 0, 1, 0, 128, 0, 100, "Simulator")
# A0_In_Bits是modbus驱动程序的名称
# sim1是asyn端口驱动程序
# slave地址为0, 功能码=1,读线圈,从地址偏移0开始读取128位,数据类型默认MODBUS_DATA
file "../../db/bi_bit.template" { pattern
{P, R, PORT, OFFSET, ZNAM, ONAM, ZSV, OSV, SCAN}
{SIM1:, BI0B, A0_In_Bits, 0, Low, High, NO_ALARM, MAJOR, "I/O Intr"}
{SIM1:, BI1B, A0_In_Bits, 1, Low, High, NO_ALARM, MAJOR, "I/O Intr"}
{SIM1:, BI2B, A0_In_Bits, 2, Low, High, NO_ALARM, MAJOR, "I/O Intr"}
{SIM1:, BI3B, A0_In_Bits, 3, Low, High, NO_ALARM, MAJOR, "I/O Intr"}
{SIM1:, BI4B, A0_In_Bits, 4, Low, High, NO_ALARM, MAJOR, "I/O Intr"}
{SIM1:, BI5B, A0_In_Bits, 5, Low, High, NO_ALARM, MAJOR, "I/O Intr"}
{SIM1:, BI6B, A0_In_Bits, 6, Low, High, NO_ALARM, MAJOR, "I/O Intr"}
{SIM1:, BI7B, A0_In_Bits, 7, Low, High, NO_ALARM, MAJOR, "I/O Intr"}
}
# A0_In_Bits端口读出了128位,取了末尾16位
file "../../db/mbbiDirect.template" { pattern
{P, R, PORT, OFFSET, MASK, SCAN}
{SIM1:, MBBID0, A0_In_Bits, 0, 0xFFFF, "I/O Intr"}
}
file "../../db/intarray_in.template" { pattern
{P, R, PORT, NELM, SCAN}
{SIM1:, BIArray, A0_In_Bits, 32, "I/O Intr"}
}
file "../../db/asynRecord.template" { pattern
{P, R, PORT, ADDR, TMOD, IFACE}
{SIM1:, BIAsyn, A0_In_Bits, 0, Read, asynUInt32Digital}
}
file "../../db/statistics.template" { pattern
{P, R, PORT, SCAN}
{SIM1:, BI, A0_In_Bits, "10 second"}
}
file "../../db/poll_delay.template" { pattern
{P, R, PORT}
{SIM1:, BIPollDelay, A0_In_Bits}
}
# These are the A0 outputs done with bit access.
file "../../db/bo_bit.template" { pattern
{P, R, PORT, OFFSET, ZNAM, ONAM}
{SIM1:, BO0B, A0_Out_Bits, 0, Low, High}
{SIM1:, BO1B, A0_Out_Bits, 1, Low, High}
{SIM1:, BO2B, A0_Out_Bits, 2, Low, High}
{SIM1:, BO3B, A0_Out_Bits, 3, Low, High}
{SIM1:, BO4B, A0_Out_Bits, 4, Low, High}
{SIM1:, BO5B, A0_Out_Bits, 5, Low, High}
{SIM1:, BO6B, A0_Out_Bits, 6, Low, High}
{SIM1:, BO7B, A0_Out_Bits, 7, Low, High}
}
file "../../db/mbboDirect.template" { pattern
{P, R, PORT, OFFSET, MASK}
{SIM1:, MBBOD0, A0_Out_Bits, 0, 0xFFFF}
}
file "../../db/asynRecord.template" { pattern
{P, R, PORT, ADDR, TMOD, IFACE}
{SIM1:, BOAsyn, A0_Out_Bits, 0, Read, asynUInt32Digital}
}
file "../../db/statistics.template" { pattern
{P, R, PORT, SCAN}
{SIM1:, BO, A0_Out_Bits, "10 second"}
}
file "../../db/poll_delay.template" { pattern
{P, R, PORT}
{SIM1:, BOPollDelay, A0_Out_Bits}
}
# These are the A0 inputs done with word access
file "../../db/longinInt32.template" { pattern
{P, R, PORT, OFFSET, DATA_TYPE, SCAN}
{SIM1:, LI:UINT16, A0_In_Word, 0, UINT16, "I/O Intr"}
{SIM1:, LI:BCD_UNSIGNED, A0_In_Word, 1, BCD_UNSIGNED, "I/O Intr"}
{SIM1:, LI:BCD_SIGNED, A0_In_Word, 2, BCD_SIGNED, "I/O Intr"}
{SIM1:, LI:INT16, A0_In_Word, 3, INT16, "I/O Intr"}
{SIM1:, LI:INT32_LE, A0_In_Word, 4, INT32_LE, "I/O Intr"}
{SIM1:, LI:INT32_BE, A0_In_Word, 6, INT32_BE, "I/O Intr"}
{SIM1:, LI:FLOAT32_LE, A0_In_Word, 8, FLOAT32_LE, "I/O Intr"}
{SIM1:, LI:FLOAT32_BE, A0_In_Word, 10, FLOAT32_BE, "I/O Intr"}
{SIM1:, LI:FLOAT64_LE, A0_In_Word, 12, FLOAT64_LE, "I/O Intr"}
{SIM1:, LI:FLOAT64_BE, A0_In_Word, 16, FLOAT64_BE, "I/O Intr"}
{SIM1:, LI:DEFAULT, A0_In_Word, 20, "", "I/O Intr"}
}
# These are the A0 outputs done with word access.
file "../../db/longoutInt32.template" { pattern
{P, R, PORT, OFFSET, DATA_TYPE}
{SIM1:, LO:UINT16, A0_Out_Word, 0, UINT16}
{SIM1:, LO:BCD_UNSIGNED, A0_Out_Word, 1, BCD_UNSIGNED}
{SIM1:, LO:BCD_SIGNED, A0_Out_Word, 2, BCD_SIGNED}
{SIM1:, LO:INT16, A0_Out_Word, 3, INT16}
{SIM1:, LO:INT32_LE, A0_Out_Word, 4, INT32_LE}
{SIM1:, LO:INT32_BE, A0_Out_Word, 6, INT32_BE}
{SIM1:, LO:FLOAT32_LE, A0_Out_Word, 8, FLOAT32_LE}
{SIM1:, LO:FLOAT32_BE, A0_Out_Word, 10, FLOAT32_BE}
{SIM1:, LO:FLOAT64_LE, A0_Out_Word, 12, FLOAT64_LE}
{SIM1:, LO:FLOAT64_BE, A0_Out_Word, 16, FLOAT64_BE}
{SIM1:, LO:DEFAULT, A0_Out_Word, 20, ""}
}
file "../../db/aiFloat64.template" { pattern
{P, R, PORT, OFFSET, DATA_TYPE, LOPR, HOPR, PREC, SCAN}
{SIM1:, AI:UINT16, A0_In_Word, 30, UINT16, -1e6, 1e6, 0, "I/O Intr"}
{SIM1:, AI:BCD_UNSIGNED, A0_In_Word, 31, BCD_UNSIGNED, -1e6, 1e6, 0, "I/O Intr"}
{SIM1:, AI:BCD_SIGNED, A0_In_Word, 32, BCD_SIGNED, -1e6, 1e6, 0, "I/O Intr"}
{SIM1:, AI:INT16, A0_In_Word, 33, INT16, -1e6, 1e6, 0, "I/O Intr"}
{SIM1:, AI:INT32_LE, A0_In_Word, 34, INT32_LE, -1e6, 1e6, 0, "I/O Intr"}
{SIM1:, AI:INT32_BE, A0_In_Word, 36, INT32_BE, -1e6, 1e6, 0, "I/O Intr"}
{SIM1:, AI:FLOAT32_LE, A0_In_Word, 38, FLOAT32_LE, -1e6, 1e6, 3, "I/O Intr"}
{SIM1:, AI:FLOAT32_BE, A0_In_Word, 40, FLOAT32_BE, -1e6, 1e6, 3, "I/O Intr"}
{SIM1:, AI:FLOAT64_LE, A0_In_Word, 42, FLOAT64_LE, -1e6, 1e6, 3, "I/O Intr"}
{SIM1:, AI:FLOAT64_BE, A0_In_Word, 46, FLOAT64_BE, -1e6, 1e6, 3, "I/O Intr"}
{SIM1:, AI:DEFAULT, A0_In_Word, 50, "" , -1e6, 1e6, 3, "I/O Intr"}
}
file "../../db/aoFloat64.template" { pattern
{P, R, PORT, OFFSET, DATA_TYPE, LOPR, HOPR, PREC}
{SIM1:, AO:UINT16, A0_Out_Word, 30, UINT16, -1e6, 1e6, 0}
{SIM1:, AO:BCD_UNSIGNED, A0_Out_Word, 31, BCD_UNSIGNED, -1e6, 1e6, 0}
{SIM1:, AO:BCD_SIGNED, A0_Out_Word, 32, BCD_SIGNED, -1e6, 1e6, 0}
{SIM1:, AO:INT16, A0_Out_Word, 33, INT16, -1e6, 1e6, 0}
{SIM1:, AO:INT32_LE, A0_Out_Word, 34, INT32_LE, -1e6, 1e6, 0}
{SIM1:, AO:INT32_BE, A0_Out_Word, 36, INT32_BE, -1e6, 1e6, 0}
{SIM1:, AO:FLOAT32_LE, A0_Out_Word, 38, FLOAT32_LE, -1e6, 1e6, 3}
{SIM1:, AO:FLOAT32_BE, A0_Out_Word, 40, FLOAT32_BE, -1e6, 1e6, 3}
{SIM1:, AO:FLOAT64_LE, A0_Out_Word, 42, FLOAT64_LE, -1e6, 1e6, 3}
{SIM1:, AO:FLOAT64_BE, A0_Out_Word, 46, FLOAT64_BE, -1e6, 1e6, 3}
{SIM1:, AO:DEFAULT, A0_Out_Word, 50, "", -1e6, 1e6, 3}
}
file "../../db/asynRecord.template" { pattern
{P, R, PORT, ADDR, TMOD, IFACE}
{SIM1:, A0:AsynIn, A0_In_Word, 0, Read, asynInt32}
}
file "../../db/asynRecord.template" { pattern
{P, R, PORT, ADDR, TMOD, IFACE}
{SIM1:, A0:AsynOut, A0_Out_Word, 0, Read, asynInt32}
}
file "../../db/statistics.template" { pattern
{P, R, PORT, SCAN}
{SIM1:, A0:, A0_In_Word, "10 second"}
}
file "../../db/poll_delay.template" { pattern
{P, R, PORT}
{SIM1:, A0:PollDelay, A0_In_Word}
}
标签:Modbus,驱动程序,16,EPICS,modbus,A0,Koyo1,synApps From: https://www.cnblogs.com/helloword-2022/p/18051294