1.协议特点
1.标准开放、公开发表、无版税要求、无许可证费(没有费用)
2.支持多种电气接口(RS232、RS422、RS485、RJ45);各种介质传输(双绞线、网线)
3. 格式简单、紧凑、通俗易懂,容易上手
2.Modbus总线通信环境
3.Modbus通讯方式与分类
串口:ModbusAscii,ModbusRTU
以太网:ModbusTCP、ModbusUDP
4.Modbus协议下的数据存储
位bit (bool)、字节byte(8个位)、字Word(2个字节,16位)、双字(2个字,4个字节,32位)
4.1 由byte确定Bit
//确定第n位是否为1
bool state=(value&(1<<n))!=0
//按位与
1<<n
//关系型判断
&&
4.2大小端处理及转换
大端:数据的高位放在低地址空间,数据的低位放在高地址空间
小端:低位对应低地址,高位对应高地址
例如:整数 0x12345678
(十六进制) 在内存中的存储 :
举例(整数 0x12345678 (十六进制) 在内存中的存储 (地址递增方向 ->)) | PLC 厂商的常见默认字节序 | |
大端 |
地址0: 地址1: 地址2: 地址3: | 西门子 (Siemens) |
小端 |
地址0: 地址1: 地址2: 地址3: |
三菱 (Mitsubishi) ** 运行环境(如 x86, x64, ARM 架构的 Windows) 使用 BinaryPrimitives 是最佳实践,能避免环境依赖 |
双字:uint ,int ,float
float类型 IEEE754标准 浮点型数据二进制表示
double 64位 long 128位 decimal 高精度 数据存储的精度丢失问题
取高位:
//第一种 右移 8 位 仅限 16 位整数
byte[i]=(byte)(add/256);
//第二种 用掩码 0xFF00 保留高字节,清除低字节
//16/32/64 位整数均适用(需调整掩码)
byte b=(byte)((src&0xFF00)>>8)
取低位
//第一种
byte[i]=(byte)(add%256);
//第二种
byte b=(byte)(src&0x00FF)
4.3 内存分区与功能 
对应功能码
功能码 | 名称 |
01 | 读线圈状态 |
05 | 写单个线圈 |
15 | 写多个线圈 |
02 | 读取输入状态(输入线圈) |
04 | 读取输入寄存器 |
03 | 读取保持寄存器 |
06 | 写单个保持寄存器 |
16 | 写多个保持寄存器 |
4.4 格式
名称 | 类型 | 结构 | 类 |
Modbus RTU | 串口 | 站号+功能码+地址+长度+校验码 | ModbusRtu |
Modbus TCP | 网口 | 消息号+0x0000+字节长度+站号+功能码+地址+长度 | ModbusTcpNet |
Modbus Ascii | 0x3A+ADDR+PDU+LCR+0x0D+0x0A | ModbusAscii |
4.4.1、 Modbus RTU
字节序优化
short 2个字节
serialPort.Read(data, 0, data.Length);
// 解析 2字节的数据
for (int i = 3; i < data.Length - 2; i += 2)
{
// Modbus Slave大端处理
// 100
// 0000 0000 0110 0100
byte[] vb = new byte[2] { data[i + 1], data[i] };[Modbus Slave大端处理-》换顺序成小端]
ushort v = BitConverter.ToUInt16(vb);// 解析出无符号短整型数据
Console.WriteLine(v);
}
float 4个字节 ABCD
取决于从设备方接收的顺序:CDAB |BADC
for (int i = 3; i < data.Length - 2; i += 4)
{
// 封装库的需要注意字节序
byte[] vb = new byte[4]
{
data[i + 2],//D
data[i+3 ],//C
data[i ],//B
data[i+1]//A
};
float v = BitConverter.ToSingle(vb);
Console.WriteLine(v);
}
注:设备方当遇到简单小数,如4.5可以存储成int类型45,取出后再*0.1,可节约字节空间
寄存器和线圈的区别
1.读写保持寄存器时
每一个代表一个字节
2.读取线圈时
每一位代表一个bit(0或者1),8位一个字节
读取线圈状态并解析 每个字节对应8位
//2.读取每个字节对应8个位状态
int count = 0;
for (int i = 0; i < res.Count; i++)
{
for (int j = 0; j < 8; j++)
{
// 遍历每个字节中的每个位
byte temp = (byte)(1<<j);
//判断是TRUE还是false
bool state = (res[i] & temp) != 0;
count++;
if (count == len)
break;
}
}
写入多线圈报文处理
//写入内容 2字节 1011101011
List<bool> values1 = new List<bool>()
{
true, false, true, true, true, false, true, false, true, true
};
//写入数量
readBytes.Add((byte)(values1.Count / 256));
readBytes.Add((byte)(values1.Count % 256));
//写入字节数
List<byte> vbs = new List<byte>();
int index = 0;
//把true,false转换成字节表示 1011101011-》具体数值
for (int i = 0; i < values1.Count; i++)
{
if (i%8==0)
{
vbs.Add(0x00);// 初始值
}
index = vbs.Count - 1;
if (values1[i])
{
byte temp = (byte)(1 << (i % 8));
// | 是为了保留上一次计算结果
vbs[index] |= temp; 1
}
}
//写入字节数
readBytes.Add((byte)vbs.Count);
readBytes.AddRange(vbs);