【物联网】Modbus 协议简介

Modbus 协议简介

QingHub设计器在设计物联网数据采集时不可避免的需要针对Modbus协议的设备做相关数据采集,这里就我们的实际项目经验分享Modbus协议

你可以通过QingHub作业直接体验试用,也可以根据手册开发相应的代码块。 qinghub项目已经全面开源。

源码文件地址: https://gitee.com/qingplus/qingcloud-platform

QingHub设计器体验

简介

Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的Modbus协议:ModbusTCP。Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。 标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

功能码

重点介绍常用如下几个功能码:

1: 读线圈寄存器

2: 读离散输入寄存器

3: 读保持寄存器

4: 读输入寄存器

5: 写单个线圈寄存器

6: 写单个保持寄存器

15: 写多个线圈寄存器

16: 写多个保持寄存器

几种继承器介绍

线圈寄存器

实际上就可以类比为开关量,每个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。

离散输入寄存器

如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。

保持寄存器

这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如设置时间年月日,不但可以写也可以读出来现在的时间。写分为单个写和多个写。

输入寄存器

只剩下这最后一个了,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。

通信协议 (重点看这里就可以了)

Modbus设备可分为主站(poll)和从站(slave)。主站只有一个,从站有多个,主站向各从站发送请求帧,从站给予响应。在使用TCP通信时,主站为client端,主动建立连接;从站为server端,等待连接(当然只要你愿意并足够熟悉,也可以反向操作)。

Mobus 的报文大致分为两类: MBAP+PDU。

MBAP= Modbus Application Protocol Header(Modbus应用协议) 头部

PDU = Protocol Data Unit (数据单元)

MBAP报文

  • MBAP为报文头,长度为7字节,组成如下:
事务处理标识 协议标识 长度 单元标识符
2字节 2字节 2字节 1字节

含义:

事务处理标识:可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。

协议标识符:00 00表示ModbusTCP协议。

长度:表示接下来的数据长度,单位为字节。

单元标识符:可以理解为设备地址。

PDU报文结构

PDU结构

PDU由功能码+数据组成。功能码为1字节,数据长度不定,由具体功能决定。

主站请求:功能码+数据

从站正常响应:请求功能码+响应数据

从站异常响应:异常功能码+异常码,其中异常功能码即将请求功能码的最高有效位置1,异常码指示差错类型

指令实例

查询(功能码0x03)

基本流程就是:

发送:地址 + 我要查 +(寄存器起始地址+个数)+校验

回复:地址 +(回)我要查 +(数据的字节数+数据) +校验

主机发送: 01 03 00 00 00 01 84 0A

含义:

01-地址

03-功能码,代表查询功能,其他功能后面再说

00 00-代表查询的起始寄存器地址.说明从0x0000开始查询.

00 01-代表查询了一个寄存器.结合前面的00 00,意思就是查询从0开始的1个寄存器值;

84 0A-CRC
从机回复: 01 03 02 12 34 B5 33

含义:

01-地址

03-功能码

02-代表后面数据的字节数,因为上面说到,一个寄存器有2个字节,所以后面的字节数肯定是2*查询的寄存器个数;

12 34-寄存器的值是12 34,结合发送的数据看出,01这个寄存器的值为12 34

B5 33-CRC校验码

修改单个寄存器(功能码0x06)

主机送: 01 06 00 00 00 01 48 0A

01-从机地址

06-功能码:修改单个寄存器功能,修改有些不同,有修改一个寄存器和修改多个寄存器;

00 00-修改的起始寄存器地址.说明从0x0000开始.

00 01-修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;

48 0A-CRC
从机回复: 01 06 00 00 00 01 48 0A

01-从机地址

06-功能码:修改单个寄存器功能;

00 00-修改的起始寄存器地址.说明是0x0000.

00 01-修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;

48 0A-CRC

修改多个寄存器(功能码0x10)

主机发送: 01 10 00 00 00 02 04 11 22 33 44 42 5A

01-从机地址

10-功能码,代表修改多个寄存器功能;

00 00-代表修改的起始寄存器地址.说明从0x0000开始.

00 02代表修改的寄存器数量

04 -表示修改的总字节数,由于只修改了1个寄存器,所以数据要有两个字节;

11 22 33 44-表示修改的值,结合上面,就是从第0000寄存器开始修改2寄存器值为11 22 33 44,就是把0000寄存器改为11 22,0001为33 44,

42 5A -循环冗余校验,是modbus的校验公式,从首个字节开始到22前面为止;
从机回复: 01 10 00 00 00 02 41 C8

01-从机地址

10-功能码

00 00-代表修改的起始寄存器地址.说明是0x0000.

00 02-代表修改的寄存器数量,只需要回复这么多久足够了,从机告诉主机修改了哪几个寄存器就足够了;

41 C8-循环冗余校验;

java 开发

maven 依赖
xml 复制代码
<!-- Modbus -->
<dependency>
    <groupId>com.infiniteautomation</groupId>
    <artifactId>modbus4j</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>com.digitalpetri.modbus</groupId>
    <artifactId>modbus-master-tcp</artifactId>
    <version>1.2.0</version>
</dependency>
API 实例

第一:建立连接

java 复制代码
/**
 * 获取 Modbus Master
 * @return ModbusMaster
 * @throws ModbusInitException ModbusInitException
 */
public ModbusMaster getMaster(ModbusConfig modbusConfig) throws ModbusInitException {
    log.debug("Modbus Tcp Connection Info {},{}", modbusConfig.getHostName(),modbusConfig.getPort());
    ModbusMaster modbusMaster = masterMap.get(modbusConfig.getEquipmentId());
    if (null == modbusMaster) {
        IpParameters params = new IpParameters();
        params.setHost(modbusConfig.getHostName());
        params.setPort(modbusConfig.getPort());
        **params.setEncapsulated(true);**
        modbusMaster = modbusFactory.createTcpMaster(params, true);
        modbusMaster.init();
        masterMap.put(modbusConfig.getEquipmentId(), modbusMaster);
    }
    return modbusMaster;
}

注意

这里有一个需要特别关注的地方,params.setEncapsulated(true)

如果encapsulated=true时:API 在构建消息是会自动加上CRC 校验码。

java 复制代码
public byte[] getMessageData() {
    ByteQueue msgQueue = new ByteQueue();
    modbusMessage.write(msgQueue);
    ModbusUtils.pushShort(msgQueue, ModbusUtils.calculateCRC(modbusMessage));
    return msgQueue.popAll();
}

如果encapsulated=false时:消息会加上事务处理标识,协议标识6个字节:

java 复制代码
public byte[] getMessageData() {
    ByteQueue msgQueue = new ByteQueue();
    modbusMessage.write(msgQueue);
    ByteQueue xaQueue = new ByteQueue();
    ModbusUtils.pushShort(xaQueue, transactionId);
    ModbusUtils.pushShort(xaQueue, ModbusUtils.IP_PROTOCOL_ID);
    ModbusUtils.pushShort(xaQueue, msgQueue.size());
    xaQueue.push(msgQueue);
    return xaQueue.popAll();
}

第二: API实例

java 复制代码
/**
 * 读线圈寄存器
 * @param modbusMaster
 * @param slaveId
 * @param initOffset
 * @param count
 * @return
 * @throws ModbusTransportException
 */
private static String batchCoilStatus01(ModbusMaster modbusMaster,Integer slaveId,Integer initOffset,Integer count) throws ModbusTransportException {
    ReadCoilsRequest request= new ReadCoilsRequest(slaveId,initOffset,count);
    ReadCoilsResponse response = (ReadCoilsResponse) modbusMaster.send(request);
    byte[] data  = response.getData();
    String bytes = ByteUtils.toHexAscii(data);
    return bytes;
}

/**
 * 读离散输入寄存器
 * @param modbusMaster
 * @param slaveId
 * @param initOffset
 * @param count
 * @return
 * @throws ModbusTransportException
 */
private static String batchInputStatus02(ModbusMaster modbusMaster,Integer slaveId,Integer initOffset,Integer count) throws ModbusTransportException {
    ReadDiscreteInputsRequest request= new ReadDiscreteInputsRequest(slaveId,initOffset,count);
    ReadDiscreteInputsResponse response = (ReadDiscreteInputsResponse) modbusMaster.send(request);
    byte[] data  = response.getData();
    String bytes = ByteUtils.toHexAscii(data);
    return bytes;
}
/**
 *批量读取保持继承器
 * @param modbusMaster
 * @param slaveId
 * @param initOffset
 * @param count
 * @return
 * @throws ModbusTransportException
 */
private static String batchRead03(ModbusMaster modbusMaster,Integer slaveId,Integer initOffset,Integer count) throws  ModbusTransportException {
    ReadHoldingRegistersRequest request= new ReadHoldingRegistersRequest(slaveId,initOffset,count);
    ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) modbusMaster.send(request);
    byte[] data  = response.getData();
    String bytes = ByteUtils.toHexAscii(data);
    return bytes;
}


/**
 * 批量读取输入继承器
 * @param modbusMaster
 * @param slaveId
 * @param initOffset
 * @param count
 * @return
 * @throws ModbusTransportException
 */
private static String batchRead04(ModbusMaster modbusMaster ,Integer slaveId,Integer initOffset, Integer count) throws  ModbusTransportException {
    ReadInputRegistersRequest request= new ReadInputRegistersRequest(slaveId,initOffset,count);
    ReadInputRegistersResponse response = (ReadInputRegistersResponse) modbusMaster.send(request);
    byte[] data  = response.getData();
    String bytes = ByteUtils.toHexAscii(data);
    return bytes;
}
相关推荐
小堃学编程42 分钟前
计算机网络(十) —— IP协议详解,理解运营商和全球网络
网络·tcp/ip·计算机网络
ok!ko1 小时前
设计模式之原型模式(通俗易懂--代码辅助理解【Java版】)
java·设计模式·原型模式
2402_857589362 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰2 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
哎呦没3 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
编程、小哥哥3 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring
小鹿( ﹡ˆoˆ﹡ )4 小时前
探索IP协议的神秘面纱:Python中的网络通信
python·tcp/ip·php
IT学长编程4 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
杨哥带你写代码5 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端