EPICS synApps modbus模块

在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功能码

访问 功能描述 功能码
位访问 读线圈 1
位访问 读离散输入 2
位访问 写单线圈 5
位访问 写多线圈 15
16位字访问 读输入寄存器 4
16位字访问 读保持寄存器 3
16位字访问 写单寄存器 6
16位字访问 写多寄存器 16
16位字访问 读/写多寄存器 23

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(portName, tcpPortName, slaveAddress, modbusFunction, modbusStartAddress, modbusLength, dataType, pollMsec, plcType)

drvModbusAsynConfigure命令

参数 数据类型 描述
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设备不同于其它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

# 用于寄存器输入的bi记录
record(bi,"$(P)$(R)") {
    field(DTYP,"asynUInt32Digital")
    field(INP,"@asynMask($(PORT) $(OFFSET) 0x1)")
    field(SCAN,"$(SCAN)")
    field(ZNAM,"$(ZNAM)")
    field(ONAM,"$(ONAM)")
    field(ZSV,"$(ZSV)")
    field(OSV,"$(OSV)")
}

 mbbiDirect.template

# 用于寄存器输入的mbbiDirect记录模板
record(mbbiDirect,"$(P)$(R)") {
    field(DTYP,"asynUInt32Digital")
    field(INP,"@asynMask($(PORT) $(OFFSET) $(MASK))")
    field(SCAN,"$(SCAN)")
}

intarray_in.template

record(waveform,"$(P)$(R)") {
    field(DTYP,"asynInt32ArrayIn")
    field(INP,"@asyn($(PORT) $(OFFSET=0))MODBUS_DATA")
    field(SCAN,"$(SCAN)")
    field(FTVL,"LONG")
    field(NELM,"$(NELM)")
}

bo_bit.template

# 用于寄存器输出的bo记录
record(bo,"$(P)$(R)") {
    field(DTYP,"asynUInt32Digital")
    field(OUT,"@asynMask($(PORT) $(OFFSET) 0x1)")
    field(ZNAM,"$(ZNAM)")
    field(ONAM,"$(ONAM)")
}

在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}
}

猜你喜欢

转载自blog.csdn.net/yuyuyuliang00/article/details/127111320