前言

MODBUS是一种应用层消息传递协议,通常用于 I/O 系统通信和可编程逻辑控制器(PLC)通信。

链接类型描述
MODBUS TCPTCP/IP 使用502端口
MODBUS RTURTU通常通过串行通信链路运行,即RS-232、 RS-422 或 RS-485。RTU 使用额外的 CRC 进行数据包检查。协议直接将每个字节作为 8 个数据位传输,因此使用“二进制” 而不是 ASCII 编码。使用串行链路开始和结束时,消息帧是按时间而不是按特定字符检测的。
MODBUS ASCII串行协议,通常在串行通信链路上运行,即 RS-232、RS-422 或 RS-485。串行 ASCII 使用额外的 LRC 数据包检查。该协议将每个字节编码为 2 个 ASCII 字符。消息帧的开始和结束由特定字符检测 (“:” 开始消息,CR/LF 结束消息)。该协议效率低于 Modbus RTU,但在某些环境中可能更可靠。

Modbus 提供对以下 4 种类型的数据的访问:

主表对象类型访问说明
离散输入1bit只读这种类型的数据可以由 I/O 系统提供。
线圈1bit读写此类数据可由应用程序更改。
输入寄存器16位字(2字节)只读这种类型的数据可以由 I/O 系统提供。
保持寄存器16位字(2字节)读写此类数据可由应用程序更改。

Modbus 通信由从 Modbus 客户端发送到 Modbus 服务器的请求消息组成。服务器使用响应消息进行回复。Modbus 请求消息包含:

  • 描述数据传输类型的 Modbus 功能码(1字节)。
  • Modbus 地址(2字节),用于描述从服务器中读取或写入数据的地址。
  • 对于写入操作,则需要传输写入的数据。

Modbus模块 支持以下 9 个 Modbus 功能码:

访问功能说明功能码
1bit读取线圈1
1bit读取离散输入2
1bit写入单线圈5
1bit写入多个线圈15
16位字访问(2字节)读取输入寄存器4
16位字访问(2字节)读取保持寄存器3
16位字访问(2字节)写入单个寄存器6
16位字访问(2字节)写入多个寄存器16
16位字访问(2字节)读/写多个寄存器23

Modbus读取操作仅限于传输125个16位字或2000 bit。Modbus写入操作仅限于传输123个16位字或1968 bit。

编译MODBUS模块

使用到的模块下载地址

以下步骤需要先安装好EPICS Base.

编译 SSCAN(可选)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
cd sscan
touch configure/RELEASE.local
vi configure/RELEASE.local

# 修改成和EPICS Base一样的架构
# EPICS_HOST_ARCH=linux-loong64
# EPICS Base路径(示例)
EPICS_BASE=/home/ubuntu/loongson/base-7.0.8
# 放置EPICS模块的路径(示例)
SUPPORT=/home/ubuntu/loongson/modules

# 直接编译
# make
# 交叉编译(示例)
# make LD=loongarch64-linux-gnu-ld CC=loongarch64-linux-gnu-gcc CCC=loongarch64-linux-gnu-g++
make

编译 CALC(可选)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
cd calc
touch configure/RELEASE.local
vi configure/RELEASE.local

# 修改成和EPICS Base一样的架构
# EPICS_HOST_ARCH=linux-loong64
# EPICS Base路径(示例)
EPICS_BASE=/home/ubuntu/loongson/base-7.0.8
# 放置EPICS模块的路径(示例)
SUPPORT=/home/ubuntu/loongson/modules
# SSCAN模块路径
SSCAN=$(SUPPORT)/sscan

# 直接编译
# make
# 交叉编译(示例)
# make LD=loongarch64-linux-gnu-ld CC=loongarch64-linux-gnu-gcc CCC=loongarch64-linux-gnu-g++
make

编译 asyn(必需)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
cd asyn
touch configure/RELEASE.local
vi configure/RELEASE.local

# 修改成和EPICS Base一样的架构
# EPICS_HOST_ARCH=linux-loong64
# EPICS Base路径(示例)
EPICS_BASE=/home/ubuntu/loongson/base-7.0.8
# 放置EPICS模块的路径(示例)
SUPPORT=/home/ubuntu/loongson/modules
# SSCAN模块路径
SSCAN=$(SUPPORT)/sscan
# CALC模块路径
CALC=$(SUPPORT)/calc

# 直接编译
# make
# 交叉编译(示例)
# make LD=loongarch64-linux-gnu-ld CC=loongarch64-linux-gnu-gcc CCC=loongarch64-linux-gnu-g++
make

编译 modbus

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
cd modbus
touch configure/RELEASE.local
vi configure/RELEASE.local

# 修改成和EPICS Base一样的架构
# EPICS_HOST_ARCH=linux-loong64
# EPICS Base路径(示例)
EPICS_BASE=/home/ubuntu/loongson/base-7.0.8
# 放置EPICS模块的路径(示例)
SUPPORT=/home/ubuntu/loongson/modules
# ASYN模块路径
ASYN=$(SUPPORT)/asyn

# 直接编译
# make
# 交叉编译(示例)
# make LD=loongarch64-linux-gnu-ld CC=loongarch64-linux-gnu-gcc CCC=loongarch64-linux-gnu-g++
make

编译完成后,可以看到bin\<EPICS_HOST_ARCH>路径下生成了可执行程序modbusApp,它就是与Modbus设备通信的主程序了。

使用 MODBUS 程序

在Modbus模块的iocBoot\iocTest目录下,可以看到很多示例程序。这里总结一下,我们使用时主要需要编写两部分内容。

  • 用于配置设备连接和通信的.cmd文件
  • 用于使用模板解析数据的.substitutions文件

这里给出示例并做简要说明。

envPaths文件:用于配置程序运行时的环境变量路径。
这里需要配置好baseasynmodbus模块的路径。

1
2
3
4
5
6
7
8
9
# envPaths

epicsEnvSet("IOC","app")
epicsEnvSet("TOP","..")
epicsEnvSet("SUPPORT","/root/modules")
epicsEnvSet("ASYN","/root/modules/asyn")
epicsEnvSet("MODBUS","/root/modules/modbus")
epicsEnvSet("EPICS_BASE","/root/base")
# epicsEnvSet("EPICS_CAS_SERVER_PORT", 9001)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# AMSAMOTION.cmd

< envPaths

dbLoadDatabase("$(MODBUS)/dbd/modbusApp.dbd")
modbusApp_registerRecordDeviceDriver(pdbbase)

# MODBUS TCP 配置
# Use the following commands for TCP/IP
#drvAsynIPPortConfigure(const char *portName,
#                       const char *hostInfo,
#                       unsigned int priority,
#                       int noAutoConnect,
#                       int noProcessEos);
drvAsynIPPortConfigure("AMSAMOTION","192.168.xxx.xxx:502",0,0,1)
#asynSetOption("AMSAMOTION",0, "disconnectOnReadTimeout", "Y")

# MODBUS RTU配置
#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")

# Modbus ASCII 还需配置其他项
# asynOctetSetOutputEos("Koyo1",0,"\r\n")
# asynOctetSetInputEos("Koyo1",0,"\r\n")

# 超时设置
#modbusInterposeConfig(const char *portName,
#                      modbusLinkType linkType,
#                      int timeoutMsec,
#                      int writeDelayMsec)
# Modbus Link Type: 0 = TCP/IP,1 = RTU,2 = ASCII
modbusInterposeConfig("AMSAMOTION",0,5000,0)

# 读取/写入配置
#drvModbusAsynConfigure(portName,
#                       tcpPortName,
#                       slaveAddress,
#                       modbusFunction,
#                       modbusStartAddress,
#                       modbusLength,
#                       dataType,
#                       pollMsec,
#                       plcType);
drvModbusAsynConfigure("AMSA:AI", "AMSAMOTION",  1, 4, 0, 6, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:AO1","AMSAMOTION",  1, 6, 0, 1, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:AO2","AMSAMOTION",  1, 6, 1, 1, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:AOSta","AMSAMOTION",1, 3, 0, 2, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:DI", "AMSAMOTION",  1, 2, 0, 8, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:DO1","AMSAMOTION",  1, 5, 0, 1, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:DO2","AMSAMOTION",  1, 5, 1, 1, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:DO3","AMSAMOTION",  1, 5, 2, 1, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:DO4","AMSAMOTION",  1, 5, 3, 1, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:DO5","AMSAMOTION",  1, 5, 4, 1, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:DO6","AMSAMOTION",  1, 5, 5, 1, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:DO7","AMSAMOTION",  1, 5, 6, 1, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:DO8","AMSAMOTION",  1, 5, 7, 1, 0, 100, "AMSA")
drvModbusAsynConfigure("AMSA:DOSta","AMSAMOTION",1, 1, 0, 8, 0, 100, "AMSA")

# Enable ASYN_TRACEIO_HEX on modbus server
asynSetTraceIOMask("AMSAMOTION",0,4)
# Dump up to 512 bytes in asynTrace
asynSetTraceIOTruncateSize("AMSAMOTION",0,512)

dbLoadTemplate("AMSAMOTION.substitutions")

iocInit
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# AMSAMOTION.substitutions

# asyn record for the underlying asyn octet port
file "$(ASYN)/db/asynRecord.db" { pattern
{P,             R,            PORT,         ADDR,   IMAX,    OMAX}
{AMSAMOTION:    OctetAsyn,    AMSAMOTION,      0,      80,      80}
}

file "$(TOP)/db/ai.template" { pattern
{P,              R,        PORT,    OFFSET,     BITS,  EGUL,     EGUF,   PREC,        SCAN}
{AMSAMOTION:,    AI1,   AMSA:AI,         0,   0xFFFF,     0,    65535,      0,  "I/O Intr"}
{AMSAMOTION:,    AI2,   AMSA:AI,         1,   0xFFFF,     0,    65535,      0,  "I/O Intr"}
{AMSAMOTION:,    AI3,   AMSA:AI,         2,   0xFFFF,     0,    65535,      0,  "I/O Intr"}
{AMSAMOTION:,    AI4,   AMSA:AI,         3,   0xFFFF,     0,    65535,      0,  "I/O Intr"}
{AMSAMOTION:,    AI5,   AMSA:AI,         4,   0xFFFF,     0,    65535,      0,  "I/O Intr"}
{AMSAMOTION:,    AI6,   AMSA:AI,         5,   0xFFFF,     0,    65535,      0,  "I/O Intr"}
}

file "$(TOP)/db/ao.template" { pattern
{P,              R,         PORT,   OFFSET,     BITS,  EGUL,    EGUF,   PREC}
{AMSAMOTION:     AO1,   AMSA:AO1,        0,   0xFFFF,     0,    65535,     0}
{AMSAMOTION:     AO2,   AMSA:AO2,        0,   0xFFFF,     0,    65535,     0}
}

file "$(TOP)/db/ai.template" { pattern
{P,              R,           PORT,    OFFSET,     BITS,  EGUL,    EGUF,   PREC,        SCAN}
{AMSAMOTION:,    AO1:STATE,   AMSA:AOSta,   0,   0xFFFF,     0,    65535,      0,  "1 second"}
{AMSAMOTION:,    AO2:STATE,   AMSA:AOSta,   1,   0xFFFF,     0,    65535,      0,  "1 second"}
}

file "$(TOP)/db/bi_bit.template" { pattern
{P,              R,         PORT,  OFFSET,  ZNAM,   ONAM,       ZSV,    OSV,         SCAN}
{AMSAMOTION:     DI1,    AMSA:DI,       0,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DI2,    AMSA:DI,       1,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DI3,    AMSA:DI,       2,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DI4,    AMSA:DI,       3,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DI5,    AMSA:DI,       4,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DI6,    AMSA:DI,       5,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DI7,    AMSA:DI,       6,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DI8,    AMSA:DI,       7,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
}

file "$(TOP)/db/bi_bit.template" { pattern
{P,              R,               PORT,     OFFSET,  ZNAM,   ONAM,       ZSV,    OSV,         SCAN}
{AMSAMOTION:     DO1:STATE,    AMSA:DOSta,       0,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DO2:STATE,    AMSA:DOSta,       1,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DO3:STATE,    AMSA:DOSta,       2,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DO4:STATE,    AMSA:DOSta,       3,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DO5:STATE,    AMSA:DOSta,       4,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DO6:STATE,    AMSA:DOSta,       5,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DO7:STATE,    AMSA:DOSta,       6,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
{AMSAMOTION:     DO8:STATE,    AMSA:DOSta,       7,   OFF,     ON,  NO_ALARM,  MAJOR,   "I/O Intr"}
}


file "$(TOP)/db/bo_bit.template" { pattern
{P,                R,         PORT,  OFFSET,  ZNAM, ONAM}
{AMSAMOTION:     DO1,     AMSA:DO1,       0,   OFF,   ON}
{AMSAMOTION:     DO2,     AMSA:DO2,       0,   OFF,   ON}
{AMSAMOTION:     DO3,     AMSA:DO3,       0,   OFF,   ON}
{AMSAMOTION:     DO4,     AMSA:DO4,       0,   OFF,   ON}
{AMSAMOTION:     DO5,     AMSA:DO5,       0,   OFF,   ON}
{AMSAMOTION:     DO6,     AMSA:DO6,       0,   OFF,   ON}
{AMSAMOTION:     DO7,     AMSA:DO7,       0,   OFF,   ON}
{AMSAMOTION:     DO8,     AMSA:DO8,       0,   OFF,   ON}
}

最后运行程序,在终端执行:

1
/path/to/modbus/bin/<EPICS_HOST_ARCH>/modbusApp AMSAMOTION.cmd

或者在.cmd文件第一行添加下面一行:

1
#!../bin/<EPICS_HOST_ARCH>/modbusApp

然后直接执行.cmd脚本。

1
2
chmod +x AMSAMOTION.cmd
./AMSAMOTION.cmd

参考