C#上位机:Modbus RTU通讯实例

前言

前文与完整代码:
C#上位机:串口通讯
本文是对前文的补充,主要是针对上位机的串口通讯在Modbus RTU协议方面的运用。在前文中有详细代码描述了如何搭建一个串口通讯上位机模板,在本文中,给出一些在前文中代码基础上的Modbus RTU的实际操作范例。

关于Modbus RTU

作为工业中最常用的通讯协议,常作为上位机与各下位机交流的媒介。详细介绍在这不赘述,网上资料都有。本文的核心主要是通过操作实例,分析使用机制与报文结构。一般来说,上位机作为主站,各下位机作为从站,对应Modbus 协议的一个主/从(Master/Slave)以及客户 端/服务器(Client/Server)架构。机制是上位机通过串口通讯发送Modbus RTU报文,从站回复报文(执行操作)。
本文中使用的是Modbus Slave作为从站模拟硬件。下载去百度下都有。

报文分析

打开Modbus Slave,我们可以看到Connection,点进去设置模拟串口的参数:
在这里插入图片描述
然后点击Set up下的Slave Definition,在此处我们将切换模拟地址:
在这里插入图片描述
点击ok后设置完毕,可以开始模拟测试。
首先来看一组报文
01 06 00 01 00 01 19 CA
其中: 01 (从机地址) 06 (功能号) 00 01 (数据地址) 00 01 (作用数据) 19 CA(CRC校验码)
此时的Slave ID,便是报文中的从机地址,对应01。
而后的四个Function,可以理解为一长串的连续寄存器地址:

线圈寄存器地址位
01 Coil Status 线圈(可读写)00001-09999
02 Input Status 离散输入(只读)10001-19999
03 Holding Register 保持寄存器(可读写)40001-49999
04 Input Registers 输入寄存器(只读)30001-39999

其中,当我们打开01的地址会发现,线圈是一位二进制状态
在这里插入图片描述
而寄存器则是整数变量。
在这里插入图片描述

因为可读写性不同,功能号其实同时决定了是在哪个寄存器进行哪一种操作,也就是说,报文中的地址只是寄存器内部的地址(1-9999),但是在哪个寄存器(线圈),由功能号做了区分。例如上述报文:01 06 00 01 00 01 19 CA,其实是作用于03 保持寄存器的第01位地址并写入01(如下)
在这里插入图片描述
自此,通过串口发送Modbus RTU报文的操作就完成了。附录全功能码:
01 (0x01) 读线圈
02 (0x02) 读离散量输入
03 (0x03) 读保持寄存器
04(0x04) 读输入寄存器
05 (0x05) 写单个线圈
06 (0x06) 写单个寄存器
15 (0x0F) 写多个线圈
16 (0x10) 写多个寄存器

我们再以01 05 00 01 FF 00 XXXX(此处省略校验码)为例
Slave ID为01,写线圈(也就是Coil Status 0X)地址01,FF 00 ,即更改状态为1。
在这里插入图片描述
如果是读取状态,那么就看报文反馈,往从机发送的报文都是有报文回复的,以功能码01 02 00 01 00 01 XXXX(此处同样省略CRC)为例,我们先去输入寄存器的01位修改值为X,发送后接收报文,16进制下读取报文,再切换为十进制,即可得到查询结果X。
在这里插入图片描述
注意事项:查明具体的地址与寄存器再进行操作实验。

生成CRC校验算法

如若需要查询报文对应的CRC校验码,可在此网页http://www.metools.info/code/c15.html选取CRC-16Modbus查询。
同时补充一个自动生成CRC校验码的算法:

private static byte[] GetModbusCrc16(byte[] bytes)
        {
            byte crcRegister_H = 0xFF, crcRegister_L = 0xFF;
            byte polynomialCode_H = 0xA0, polynomialCode_L = 0x01;
            for (int i = 0; i < bytes.Length; i++)
            {
                crcRegister_L = (byte)(crcRegister_L ^ bytes[i]);
                for (int j = 0; j < 8; j++)
                {
                    byte tempCRC_H = crcRegister_H;
                    byte tempCRC_L = crcRegister_L;
                    crcRegister_H = (byte)(crcRegister_H >> 1);
                    crcRegister_L = (byte)(crcRegister_L >> 1);
                    if ((tempCRC_H & 0x01) == 0x01)
                    {
                        crcRegister_L = (byte)(crcRegister_L | 0x80);
                    }
                    if ((tempCRC_L & 0x01) == 0x01)
                    {
                        crcRegister_H = (byte)(crcRegister_H ^ polynomialCode_H);
                        crcRegister_L = (byte)(crcRegister_L ^ polynomialCode_L);
                    }
                }
            }
            return new byte[] { crcRegister_L, crcRegister_H };
        }
  • 32
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值