ModbusTCP学习笔记+PLC相关功能块编程
2023-07-18
PLC编程,Modbus TCP,Codesys V2.3,OtoStudio V2.3,ST语言
1、Modbus协议介绍
-
Modbus是一种工业总线协议标准,包括ASCII、RTU、TCP三种报文类型。
-
Modbus协议物理层接口有RS232、RS485、RS422和以太网接口,采用master/slave方式通信。
-
Modbus -TCP,是基于在以太网TCP/IP上,将数据以Modbus帧格式进行传输。
具备数据准确性(帧头、帧尾),具有TCP传输快速性(物理层是RJ45网口、TCP传输层)。
图为Modbus数据帧内容:
- MBAP为报文头,长度为7字节,组成如下:
- 帧结构PDU
PDU由功能码+数据组成。功能码为1字节,数据长度不定,由具体功能决定。
Modbus的操作对象有四种:线圈、离散输入、保持寄存器、输入寄存器。
- 通信方式
Modbus分为主站和从站,主站给从站发送请求帧,从站响应。在使用TCP通信时,主站为client端,主动建立连接,从站为server端,等待连接。
Modbus TCP是较为特别的,举个例子,之前我们客户使用IDB3 控制器(PLC)与威纶通触摸屏通过Modbus TCP通信,IDB3就是作为Modbus从站,而Modbus从站作为TCP就是Server端。而触摸屏作为Modbus主站,Modbus主站在TCP就是Client端。
Modbus主站主动对Modbus从站发送消息请求,从站做回应。
这个在IDB3的Modbus TCP库里可以详细了解到,Modbus从站是需要或可以配置本机port(端口)、IP地址 的。而Modbus主站则需要设置远端IP,以及端口。
2、编程示例
从站:
- iDEABox 3控制器,在此文中简称IDB3,是PLC,是Softlink品牌的控制器,我使用的是固高科技的OtostudioV2.3进行编程,其内核也是CodysysV2.3版本。
ModbusTCP从站作为TCP主站。也即是其有TCPServer的属性。
从站使用的代码如下:
VAR_GLOBAL
SlaveArrayBool:ARRAY [1..60] OF BOOL; // HMI布尔数据
SlaveArrayReal:ARRAY [1..60] OF REAL; // HMI字数据
END_VAR
// 从站功能块调用
SlaveTcp(
pCoils:= ADR(SlaveArrayBool[1]), //指定线圈地址
pDiscreteInput:= , //离散量地址
pInputReg:= , //输入寄存器
pHoldingReg:= ADR(SlaveArrayReal), //保持寄存器
CMax:= 32, //线圈大小
DIMax:= ,
IRMax:= ,
HRMax:= SIZEOF(SlaveArrayReal)/SIZEOF(REAL)*2, //保持寄存器大小
Enable:= bEnable,
port:= 4455, //指定端口号
Enable_WatchDog:= ,
tWatchDogTime:= ,
ClientStatus=> ,
wErrorId=> ,
bError=> );
上述代码中,SlaveTcp是ModbusTcp 从站功能块实例。其需要的参数入代码中所示。主要可配置以下:
(1)指定 “Coils线圈、离散量、输入寄存器、保持寄存器” 4部分的地址及大小。
(2)指定端口
(3)看门狗配置(可以不用,不设置)
- Coils配置
从站指定线圈地址 pCoils 是地址开头,CMax 是线圈大小,指的是以bool为单位的长度。
如上例子:pCoils:= ADR(aybyHMIData[1]) 指线圈从aybyHMIData[1]开始,长度32Bit(位),也就是4个Byte(字节)。
这里的线圈变量类型是 《布尔(BOOL)数组》
- HoldingReg配置
指定保持寄存器地址开头 aywHMIData,也就是aywHMIData[1]的地址,长度位60*2=120,因为Real占4个字节,比word大2倍,所以要传real值时,要把地址乘以2。这个配置就占了保持寄存器120个地址。
这里的保持寄存器变量类型 是 《实数(REAL)数组》
主站:
- 主站也是在IDB3中建立,因为ModbusTCP是基于TCP的通信,所以可以在本机上实现主从站并存。
- 当有多个设备需要通过ModbusTCP协议通信,那么把主从站运行在不同设备上即可。
- ModbusTCP作TCP从站,其有TCPClient的属性。
以下是主站用到的代码:
VAR_GLOBAL
MasterArrayByte:ARRAY [1..60] OF BYTE; // HMI布尔数据
MasterArrayReal:ARRAY [1..60] OF REAL; // HMI字数据
END_VAR
// ModbusTcp主站
ModbusTcpMaster(
Client:= ADR(Client),
StationID:= 1, // 设置站号
IP:= remoteIP, // 远端服务器的IP
Port:= remotePort, // 远端服务器端口
Enable:= bMasterEnable,
Enable_WatchDog:= ,
WatchDogTime:= ,
wErrorId=> ,
bError=> );
IF bMasterEnable THEN
IF bReadEnable THEN
bReadEnable:=FALSE;
ELSE
bReadEnable:=TRUE;
END_IF
IF bWriteEnable THEN
bWriteEnable:=FALSE;
ELSE
bWriteEnable:=TRUE;
END_IF
END_IF
MasterTcpRead(
Execute:= bReadEnable,
Client:= ADR(client),
Watch_Time:= ,
ReSendTimes:= ,
DataModel:= 4, // 读取保存寄存器模式
DataAddress:= 0, // 地址 0 ~ 8
DataLength:= 4*2, // 共读出 8/2 = 4 个REAL
pData:= ADR(MasterArrayReal), // 存放地址
ResponseDone=> ,
Exception=> ,
ExceptionCode=> ,
ExceptionCnt=> ,
SendCnt=> ,
TimeOut=> ,
Error=> ,
ErrCode=> );
MasterTcpReadB(
Execute:= bReadEnable,
Client:= ADR(client),
Watch_Time:= ,
ReSendTimes:= ,
DataModel:= 0, // 读取线圈模式
DataAddress:= 0, // 地址 0 ~ 8
DataLength:= 8, // 共读出 8/8 = 1 个BYTE
pData:= ADR(MasterArrayByte[1]), // 存放地址
ResponseDone=> ,
Exception=> ,
ExceptionCode=> ,
ExceptionCnt=> ,
SendCnt=> ,
TimeOut=> ,
Error=> ,
ErrCode=> );
MasterTcpWrite(
Execute:= bWriteEnable,
Client:= ADR(client),
Watch_Time:= ,
ReSendTimes:= ,
DataModel:= 4, // 写保持寄存器
DataAddress:= 60, // 地址 60 ~ 120
DataLength:= 60, // 共写入 60/2 = 30 个REAL
pData:= ADR(MasterArrayReal[31]), // 要写入的变量地址
ResponseDone=> ,
Exception=> ,
ExceptionCode=> ,
ExceptionCnt=> ,
SendCnt=> ,
TimeOut=> ,
Error=> ,
ErrCode=> );
MasterTcpWriteB(
Execute:= bWriteEnable,
Client:= ADR(client),
Watch_Time:= ,
ReSendTimes:= ,
DataModel:= 0, // 写线圈
DataAddress:= 8, // 地址 8 ~ 24
DataLength:= 24, // 共写入 24/8 = 3 个BYTE
pData:= ADR(MasterArrayByte[2]), // 要写入的变量地址
ResponseDone=> ,
Exception=> ,
ExceptionCode=> ,
ExceptionCnt=> ,
SendCnt=> ,
TimeOut=> ,
Error=> ,
ErrCode=> );
上述代码中,ModbusTcpMaster是ModbusTCP主站功能块。而主站并不是只用这一个功能块,还需要使用 “读/写” 的功能块。MasterTcpRead、MasterTcpReadB是 ”ModbusTCP主站读“ 功能块,MasterTcpWrite、MasterTcpWriteB是 ”ModbusTCP主站写“ 功能块。
在读写功能块里面,每个功能块实例只能同时对一个区域进行操作,所以上面代码就调用了2个读写功能块进行对线圈的读写、保持寄存器的读写。
在读写功能块中,也需要像从站那样配置其数据地址及大小。
主站需要配置的内容:
(1)要读写的内容( Coils线圈、离散量、输入寄存器、保持寄存器)的地址和大小。
(2)配置从站(TCP服务器)的IP地址,和端口。
(3)看门狗(可以不用,不配置)
- Coils线圈读写
读写的都是同一个 字节BYTE数组 MasterArrayByte[] ,区别在于,读取的是第一个元素,MasterArrayByte[1],而要写入的是MasterArrayByte[2…4] 第2~4个元素。
这里用到的数组类型是 字节BYTE数组 ,和Slave站的不一致?为什么?
上面从站配置的线圈,配置地址从0开始,32长度,就是第031位,也就是第03 个BYTE。
而主站读功能块配置的线圈,配置地址从0开始,8长度,指的是第0~7个位,也就是第0个BYTE。但是其指定的需要用Byte数组来处理,这里试过用BOOL数组,就会有异常。
而同理,主站写功能块配置线圈,地址从8开始,24长度,指的是831个位,也就是第13个BYTE。也同样用byte数组来操作。
- HoldingReg保持寄存器读写
读写保持寄存器也都是用同一个 实数REAL数组 MasterArrayReal[],区别在于,读取的是MasterArrayReal[0…3]共4个real。写入的是MasterArrayReal[31…60]共30个real。
以上主从站线圈的对应图如下:
主从站保持寄存器对应图如下:
布置好以上内容后,启动功能块,即可实现ModbusTCP主从站的通信了。