Skip to content

Commit

Permalink
新增一篇博客
Browse files Browse the repository at this point in the history
  • Loading branch information
yangzhenghan committed Jan 2, 2024
1 parent 083db52 commit b1cd106
Showing 1 changed file with 336 additions and 0 deletions.
336 changes: 336 additions & 0 deletions content/posts/build-epics-module-modbus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,336 @@
---
title: "EPICS的MODBUS模块的编译和使用"
date: 2024-01-02T14:38:38+08:00
draft: false
description: 交叉编译EPICS的MODBUS模块
tags: ["linux", "EPICS", "龙芯"]
keywords: ["linux", "EPICS", "龙芯"]
categories: ["EPICS"]
---

## 前言

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

|链接类型|描述|
|:---:|:---:|
|MODBUS TCP|TCP/IP 使用502端口|
|MODBUS RTU|RTU通常通过串行通信链路运行,即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位 word|只读|这种类型的数据可以由 I/O 系统提供。|
|保持寄存器|16位 word|读写|此类数据可由应用程序更改。|

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 - (launchpad.net)](https://git.launchpad.net/epics-base) / [epics-base/epics-base](https://github.com/epics-base/epics-base) / [EPICS Base (anl.gov)](https://epics.anl.gov/base/index.php)

- [epics-modules/asyn: EPICS module for driver and device support](https://github.com/epics-modules/asyn)

- [epics-modules/modbus: EPICS support for communication with Programmable Logic Controllers (PLCs) and other devices via the Modbus protocol over TCP, serial RTU, and serial ASCII links ](https://github.com/epics-modules/modbus)

- [epics-modules/sscan: APS BCDA synApps module: sscan](https://github.com/epics-modules/sscan)

- [epics-modules/calc: APS BCDA synApps module: calc](https://github.com/epics-modules/calc)

- [epics-modules/ipac: IPAC Carrier and Communication Module Drivers](https://github.com/epics-modules/ipac)

- [sequencer](https://www-csr.bessy.de/control/SoftDist/sequencer/repo/branch-2-2.git/) / [Download and Installation — EPICS Sequencer Version 2.2 (bessy.de)](https://www-csr.bessy.de/control/SoftDist/sequencer/Installation.html)

需要先安装好EPICS base.

### 编译 SSCAN(可选)

``` shell
cd sscan
touch configure/RELEASE.local
vi configure/RELEASE.local

# 修改成和EPICS Base一样的架构
# EPICS_HOST_ARCH=linux-loongarch64
# 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++
```

### 编译 CALC(可选)

``` shell
cd calc
touch configure/RELEASE.local
vi configure/RELEASE.local

# 修改成和EPICS Base一样的架构
# EPICS_HOST_ARCH=linux-loongarch64
# 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++
```

### 编译 asyn(必需)

``` shell
cd asyn
touch configure/RELEASE.local
vi configure/RELEASE.local

# 修改成和EPICS Base一样的架构
# EPICS_HOST_ARCH=linux-loongarch64
# 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++
```

### 编译 modbus

``` shell
cd modbus
touch configure/RELEASE.local
vi configure/RELEASE.local

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

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

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

## 使用 MODBUS 程序

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

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

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

``` shell
# 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)
```

这里需要配置好`base``asyn``modbus`模块的路径。

``` shell
# 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")

# 超时设置
#modbusInterposeConfig(const char *portName,
# modbusLinkType linkType,
# int timeoutMsec,
# int writeDelayMsec)
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
```

``` shell
# 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}
}
```

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

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

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

``` shell
#!../bin/<EPICS_HOST_ARCH>/modbusApp
```

然后直接执行`.cmd`脚本。

``` shell
chmod +x AMSAMOTION.cmd
./AMSAMOTION.cmd
```

**参考**

- [Overview of Modbus](https://epics-modbus.readthedocs.io/en/latest/overview.html)
- [Creating a modbus port driver](https://epics-modbus.readthedocs.io/en/latest/creating_driver.html)

0 comments on commit b1cd106

Please sign in to comment.