Modbus 基础

本文详细介绍了Modbus通信的基础知识,包括RTU和ASCII模式的区别、串行链路上的通用报文格式、存储区结构以及大小端的概念。此外,还提供了软件调试方法,如使用ModbusSlave和ModbusPoll进行主从站模拟,并通过VSPD创建虚拟串口。最后,文章列举了读取输出线圈、输入线圈、保持寄存器和输入寄存器的实例,以及强制线圈和预置寄存器的操作流程,深入解析了Modbus通信过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Modbus 基础

(一)、串行链路上的两种不同模式

特性RTU模式ASCII模式
编码二进制ASCII
每个字符位数起始位:1 Bit起始位:1 Bit
数据位:8 Bit数据位:7 Bit
奇偶校验:1位奇偶校验:1位
停止位:1 或 2停止位:1 或 2
报文校验CRC(循环冗余校验)LRC(纵向冗余校验)

(二)、串行链路上的通用报文格式

报文前后各需要大于3.5个字符的报文间隔时间,否则会被判断为一帧报文
(3.5个字节的时间主要由波特率决定的,在9600的波特率下大概是4ms)

地址功能码数据CRC校验
1 Byte1 ByteN Byte2 Byte

(三)、存储区

存储区标识名称类型读 / 写存储单元地址
0XXXX线圈读 / 写00001 ~ 0XXXX
1XXXX输入线圈只读10001 ~ 1XXXX
3XXXX输入寄存器只读30001 ~ 3XXXX
4XXXX保持 / 输出寄存器读 / 写40001 ~ 4XXXX

【注】:存储单元数量与实际设备相关

(四)、Modbus大小端

众所周知,计算机底层都是二进制代码,但在实际应用中,我们却经常和浮点数、整数或者字符串打交道,在进行赋值运算或者算术运算时,必须要保证参与运算的数据类型保持一致,如果不一致,就必须进行数据转换。

数据类型c#简称数据长度(位)数据范围
Bit10-1
字节Byte80-255
有符号16位整数Short16-32768-32767
无符号16位整数UShort160-65535
有符号32位整数Int32-2E31-2E31
无符号32位整数UInt320-2E32
单精度浮点数Float32-3.4E38-3.4E38
有符号64位整数Long64-2E63-2E63
无符号64位整数ULong640-2E64
双精度浮点数Double64-1.79E308-1.79E308
字符串String64

字节顺序简单来说,就是指超过一个字节的数据类型在内存中的存储顺序,如果只有1个字节就不存在顺序的说法了,一般来说,字节顺序会分两类,一种叫做大端字节顺序,一种叫做小端字节顺序。
大端字节顺序是指高位字节存储在低位地址,低位字节存储在高位地址
小端字节顺序则反之,高位字节存储在高位地址,低位字节存储在低位地址
如果一个Int类型数组,占用4个字节,4个字节顺序为ABCD,那么采用big-endian大端字节顺序,那么在内存中即为ABCD,如果采用small-endian小端字节顺序,那么在内存中存储即为DCBA,但是在实际应用中,还有可能出现BADC或者CDAB的情况,因此我们在大小端的基础上做了一下扩展,定义了4种不同字节顺序,采用枚举类型表示

public enum DataFormat
{
    ABCD = 0, // 大端形式
    BADC = 1, // 大端单字反转
    CDAB = 2, // 小端单字反转
    DCBA = 3, // 小端形式
}

二、调试

(一)、软件介绍

名称描述
Modbus Slave一款用于仿真 Modbus Rtu 从站或 Modbus TCP 服务器的软件
Modbus Poll一款用于仿真 Modbus Rtu 主站或 Modbus TCP 客户端的软件
VSPD一款用于虚拟电脑串口软件(Configure Virtual Serial Port Driver)
  1. 利用VSPD虚拟出两个串口(把本文为COM3 和 COM7)
  2. Function:选择的存储区需要相同

(二)、Modbus Slave 软件 —— Rtu从站 / TCP 服务器

Connection >> Connection Setup,设置如下
在这里插入图片描述
Setup >> Slave Definition,设置如下
在这里插入图片描述
Slave ID:从站的ID
Function:选择的存储区

(三)、Modbus Poll 软件 —— Rtu 主站 \ TCP 客户端

Connection >> Connection Setup,设置如下
在这里插入图片描述
Setup >> Read/Write Definition,设置如下
在这里插入图片描述
Slave ID:需要注意从站的ID
Function:选择的存储区

三、数据读取

(一)、读取输出线圈 (功能码:01H)

主站询问报文格式:

从站地址功能码起始地址(高位)起始地址(低位)线圈数量(高位)线圈数量(低位)CRC
0x110x010x000x130x000x1BXXXX

含义:
读取11H(17)号从站输出线圈
起始地址 = 0013H(19),对应地址是00020;
线圈数量 = 001BH(27),结束地址是 = 00020 + 27 - 1 = 00046
即从11H(17)号从站读取00020 - 00046 共27个线圈状态。


从站应答报文格式:

从站地址功能码字节计数线圈状态20-27线圈状态28-35线圈状态36-43线圈状态44-46CRC
0x110x010x040xCD0x6B0xB20x05XXXX

含义:来自11H(17)号从站输出线圈 00020-00046,共27个线圈状态,分别为 CD 6B B2 05
CD = 1100 1101 对应00020 - 00027
6B = 0110 1011 对应00028 - 00035
B2 = 1011 0010 对应00036 - 00043
05 = 0000 0101 对应00044 - 00046
【注】:状态从右往左进行对应低位到高位的线圈

/*
* 将byte数组转化为获取二进制
*/
var result = modbusRtu.ReadKeepRegister(1, 0, Convert.ToUInt16(this.textBox1.Text));
if (result.IsSuccess)
{
    string binaryString = string.Empty;
    foreach (var item in result.Content)
    {
        // 反转数组
        char[] charArray = Convert.ToString(item, 2).PadLeft(8, '0').ToCharArray();
        Array.Reverse(charArray);
        binaryString += new string(charArray);
    }
    binaryString = binaryString.Substring(0, Convert.ToUInt16(this.textBox1.Text));
    Console.WriteLine(binaryString);
}
else
{
    Console.WriteLine(result.Message);
}

(二)、读取输入线圈 (功能码:02H)

主站询问报文格式:

从站地址功能码起始地址(高位)起始地址(低位)线圈数量(高位)线圈数量(低位)CRC
0x110x020x000xC40x000x1DXXXX

含义:
读取11H(17)号从站输入线圈
起始地址 = 00C4H(196),对应地址是10197;
线圈数量 = 001DH(29),结束地址是 = 10197 + 29 - 1 = 10225
即从11H(17)号从站读取10197 - 10225 共29个线圈状态。


从站应答报文格式:

从站地址功能码字节计数线圈状态10197-10204线圈状态10205-10212线圈状态10213-10220线圈状态10221-10228CRC
0x110x020x040xCD0x6B0xB20x05XXXX

含义:来自11H(17)号从站输入线圈 10197-10228,共32个线圈状态,分别为 CD 6B B2 05
CD = 1100 1101 对应10197 - 10204
6B = 0110 1011 对应10205 - 10212
B2 = 1011 0010 对应10213 - 10220
05 = 0000 0101 对应10221 - 10228
【注】:状态从右往左进行对应低位到高位的线圈,当然也不是绝对的,和上文所讲到的大小端相关

/*
* 将byte数组转化为获取二进制
*/
var result = modbusRtu.ReadKeepRegister(1, 0, Convert.ToUInt16(this.textBox1.Text));
if (result.IsSuccess)
{
    string binaryString = string.Empty;
    foreach (var item in result.Content)
    {
        // 反转数组
        char[] charArray = Convert.ToString(item, 2).PadLeft(8, '0').ToCharArray();
        Array.Reverse(charArray);
        binaryString += new string(charArray);
    }
    binaryString = binaryString.Substring(0, Convert.ToUInt16(this.textBox1.Text));
    Console.WriteLine(binaryString);
}
else
{
    Console.WriteLine(result.Message);
}

(三)、读取保持寄存器 (功能码:03H)

主站询问报文格式:

从站地址功能码起始寄存器(高位)起始寄存器(低位)寄存器数量(高位)寄存器数量(低位)CRC
0x110x030x000x6B0x000x02XXXX

含义:
读取11H(17)号从站保持寄存器
起始地址 = 006BH(107),对应地址是40107;
寄存器数量 = 0002H(2),结束地址是 = 40108 + 2 - 1 = 40109
即从11H(17)号从站读取40108 - 40109 共2个寄存器的值。


从站应答报文格式:

从站地址功能码字节计数40108高位40108低位40109高位40109低位CRC
0x110x030x040x020x2B0x010x06XXXX

含义:
来自11H(17)号从站保持寄存器的值 40108-40109,共2个寄存器的值,分别为 CD 6B B2 05
保持寄存器 40108 的值为 CD6BH
保持寄存器 40109 的值为 B205H

(四)、读取输入寄存器 (功能码:04H)

主站询问报文格式:

从站地址功能码起始寄存器(高位)起始寄存器(低位)寄存器数量(高位)寄存器数量(低位)CRC
0x110x040x000x6B0x000x02XXXX

含义:
读取11H(17)号从站输入寄存器
起始地址 = 006BH(107),对应地址是40107;
寄存器数量 = 0002H(2),结束地址是 = 40108 + 2 - 1 = 40109
即从11H(17)号从站读取40108 - 40109 共2个寄存器的值。


从站应答报文格式:

从站地址功能码字节计数40108高位40108低位40109高位40109低位CRC
0x110x040x040x020x2B0x010x06XXXX

含义:
来自11H(17)号从站输入寄存器的值 40108-40109,共2个寄存器的值,分别为 CD 6B B2 05
输入寄存器 40108 的值为 CD6BH
输入寄存器 40109 的值为 B205H

(五)、强制单个线圈 (功能码:05H)

主站询问报文格式:

从站地址功能码线圈地址(高位)线圈地址(低位)断通标志断通标志CRC
0x110x050x000xAC0xFF0x02XXXX

含义:
强制 11H (17)号从站某个线圈的值
线圈地址=00ACH=172,对应地址 00173
断通标志为FF00H表示置位,断通标志为0000H表示复位
即置位 11H (17)号从站输出线圈00173


从站应答报文格式:

从站地址功能码线圈地址(高位)线圈地址(低位)断通标志断通标志CRC
0x110x050x000xAC0xFF0x02XXXX

含义:
强制 11H (17)号从站输出线圈00173为ON后原文返回

(六)、强制多个线圈 (功能码:0FH)

主站询问报文格式:

从站地址功能码起始(高位)起始(低位)线圈数(高位)线圈数(低位)字节计数字节1字节2CRC
0x110x0F0x000x130x000x0A0x020xCD0x00XXXX

含义:
预置 11H (17)号从站多个线圈的值,
线圈起始地址=0013H=19,对应地址 00020;
线圈数=0x000A=10,结束地址为00020+10-1=00029,
写入值为0xCD00,
即预置 11H (17)号从站线圈:
00020-00027=0xCD=1100 1101
00028-00029=0x00=0000 0000
【注】:状态从右往左进行对应低位到高位的线圈


从站应答报文格式:

从站地址功能码起始(高位)起始(低位)线圈数(高位)线圈数(低位)CRC
0x110x0F0x000x130x000x0AXXXX

含义:
预置 11H (17)号从站线圈:
00020-00027=0xCD=1100 1101
00028-00029=0x00=0000 0000

(七)、预置单个寄存器 (功能码:06H)

主站询问报文格式:

从站地址功能码寄存器地址(高位)寄存器地址(低位)写入值(高位)写入值(低位)CRC
0x110x060x000x870x030x9EXXXX

含义:
预置 11H (17)号从站某个寄存器的值
寄存器地址=0087H=135,对应地址 40136
写入值为0x039E,
即预置 11H (17)号从站保存寄存器40136值为0x039E。


从站应答报文格式:

从站地址功能码寄存器地址(高位)寄存器地址(低位)写入值(高位)写入值(低位)CRC
0x110x060x000x870x030x9EXXXX

含义:
预置 11H (17)号从站保存寄存器40136值为0x039E后原文返回

(八)、预置多个寄存器 (功能码:10H)

主站询问报文格式:

从站地址功能码起始(高位)起始(低位)数量(高位)数量(低位)字节计数字节1字节2字节3字节4CRC
0x110x100x000x870x000x020x040x010x050x0A0x10XXXX

含义:
预置 11H (17)号从站多个寄存器的值,
寄存器起始地址=0087H=135,对应地址 40136;
寄存器数=0x0002=2,结束地址为40136+2-1=40137,
写入值为0x0105,0x0A10,
即预置 11H (17)号从站寄存器:
40136=0x0105
40137=0x0A10


从站应答报文格式:

从站地址功能码起始(高位)起始(低位)数量(高位)数量(低位)CRC
0x110x100x000x870x000x02XXXX

含义:
预置 11H (17)号从站寄存器:40136=0x0105 40137=0x0A10

写在最后

本博文只是我在学习c#的过程中所做的笔记,方便以后查阅实现过程。资料均来自网上,如果有侵权请联系我删除,谢谢。

Freemodbus是一个用于Python的Modbus协议库,它并不直接提供修改起始地址的功能,因为起始地址通常是硬件配置的一部分,例如PLC(可编程逻辑控制器)或远程I/O设备的设置。不过,如果你正在使用Modbus TCP/IP通信,并且需要调整发送或接收的数据范围,你可以在连接建立之前或初始化连接的时候进行一些设置。 首先,你需要了解你要控制的设备的Modbus功能码,特别是读取/写入寄存器的函数(如`MODBUS_TCP_READ_HOLDING_REGISTERS`或`MODBUS_TCP_WRITE_SINGLE_REGISTER`)。然后,你可以通过传递额外的参数,比如偏移量(startAddress),来指定数据的开始位置。这通常在向设备发送请求之前完成,例如: ```python from pymodbus.client.sync import ModbusTcpClient # 设备IP和端口 ip = '192.168.0.1' port = 502 # 起始地址偏移(假设你的设备支持) offset_address = 1000 # 从1000开始读取或写入 client = ModbusTcpClient(ip, port) client.set_unit_id(0) # 如果有多个单元ID,可能需要设定这个 # 发送请求时包含偏移地址,例如读取寄存器 registers = client.read_holding_registers(startAddress=offset_address, quantity=10) # 或者写入时 client.write_single_register(offset_address, value) ``` 请注意,不是所有Modbus设备都支持这样的自定义,而且具体如何设置取决于设备制造商提供的API文档。如果需要更改硬件级别的起始地址,可能需要查阅设备手册并进行相应的物理接线或固件配置。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值