一、Modbus 全部功能码 必考对照表(完整版)
考试必背:区分线圈、离散输入、保持寄存器、输入寄存器读写权限
-
01 功能码 :读取线圈状态(可读可写),存储 开关量 0/1
-
02 功能码:读取离散输入状态(只读),传感器开关信号
-
03 功能码:读取保持寄存器(可读可写),存储数值、温度、压力
-
04 功能码:读取输入寄存器(只读),下位机采集数据上传
-
06 功能码:写入单个保持寄存器
-
16 功能码:写入多个保持寄存器
重点区别 : 线圈是 位存储(0/1状态) ,8个状态占1字节 寄存器是 字存储(数值),1个寄存器=2字节
二、Modbus-RTU 与 Modbus-ASCII 核心区别(简答题满分)
2.1 Modbus-RTU(二进制模式)
-
传输格式:纯字节流二进制
-
校验方式:CRC16
-
帧首尾:无特殊标记,靠时间间隔判定帧结束
-
效率高、工业最常用
2.2 Modbus-ASCII(字符串模式)
-
传输格式:ASCII字符串
-
校验方式:LRC纵向冗余校验
-
帧头:冒号 :
-
帧尾:\r\n 回车换行
-
可读性强、效率低
2.3 帧格式对比
RTU帧:从机地址 + 功能码 + 起始地址 + 长度 + CRC(纯8字节二进制)
ASCII帧:: + 地址功能码数据字符串 + LRC校验 + \r\n
示例::010300000004E0\r\n
三、LRC 校验算法原理 + 逐行代码(必考手写)
3.1 LRC 计算三步流程(背诵)
-
对所有有效参与校验字节累加求和
-
总和 按8位取反 + 1(补码运算)
-
和 0xFF 与运算,强制截断保留8位,得到LRC校验码
3.2 LRC 核心代码(可直接默写)
cs
/// <summary>
/// LRC纵向冗余校验(Modbus-ASCII专用)
/// </summary>
public static string CalcLRC(byte[] data)
{
//1.所有字节累加求和
uint sum = (uint)data.Sum(x => x);
//2.8位取反+1 3.截断8位
uint res = (~sum + 1) & 0xff;
//返回两位十六进制大写
return res.ToString("X2");
}
四、ASCII 请求帧组装原理与代码
4.1 组帧规则
-
所有字节转两位16进制字符串
-
拼接LRC校验码
-
头部加 : 尾部加 \r\n
4.2 完整组帧代码
cs
public static string GetRequestFrame(byte[] data)
{
//计算LRC校验
string lrc = CalcLRC(data);
//字节数组转十六进制字符串
string requestData = "";
foreach (byte b in data)
{
requestData += b.ToString("X2");
}
//拼接ASCII标准帧格式
return ":" + requestData + lrc + "\r\n";
}
五、03读取寄存器 业务代码(ASCII)
5.1 读取请求帧组装
cs
// 固定格式:从机地址01 + 功能码03 + 起始地址0000 + 读取长度0004
string s2 = GetRequestFrame(new byte[] { 01, 03, 00, 00, 00, 04 });
port.Write(s2);
5.2 响应帧数据解析算法
ASCII返回为字符串,每4个字符代表1个寄存器数值
cs
/// <summary>
/// ASCII字符串批量解析为寄存器数值
/// </summary>
public static ushort[] StringToUshort(string s, int valueCount, int startIndex = 7)
{
//长度校验
if (valueCount * 4 + startIndex > s.Length)
{
throw new ArgumentException("响应字符串个数不够");
}
ushort[] bs = new ushort[valueCount];
for (int i = 0; i < valueCount; i++)
{
//截取4位16进制字符串
string value = s.Substring(startIndex, 4);
startIndex += 4;
//转为数值
bs[i] = Convert.ToUInt16(value);
}
return bs;
}
六、06写入单个寄存器 完整代码(考试重点)
6.1 写入帧结构
从机地址(1) + 功能码06(1) + 写入地址(2) + 写入数据(2) 共6字节,再算LRC
6.2 写入核心代码
cs
private void button1_Click(object sender, EventArgs e)
{
byte add = 0x01; //从机地址
byte func = 0x06; //写入功能码
ushort addres = 0x0010; //写入寄存器地址
ushort value = 0x1234; //写入数值
//拆分高低字节
byte[] bs =
{
add,
func,
(byte)(addres >> 8), //地址高位
(byte)(addres & 0xff), //地址低位
(byte)(value >> 8), //数据高位
(byte)(value & 0xff) //数据低位
};
//生成ASCII帧并发送
string s = GetRequestFrame(bs);
port.Write(s);
}
七、串口接收事件代码(ASCII专属)
cs
private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int readLength = port.BytesToRead;
byte[] bs = new byte[readLength];
port.Read(bs, 0, readLength);
//ASCII转字符串,去除首尾换行回车
string s = Encoding.ASCII.GetString(bs).Trim('\r', '\n');
Console.WriteLine(s);
//判断是否为03读取响应帧
if (s.Contains("03"))
{
//解析4个寄存器数据
ushort[] us = StringToUshort(s, 4);
Console.WriteLine($"{us[0]}:{us[1]}:{us[2]}:{us[3]}");
}
}
八、RTU与ASCII 终极考点总结(必背)
| 对比项 | Modbus-RTU | Modbus-ASCII |
|---|---|---|
| 传输格式 | 二进制字节流 | 字符串 |
| 校验方式 | CRC16(0xFFFF,0xA001) | LRC纵向校验 |
| 帧头帧尾 | 无标识,靠间隔 | 帧头: : 帧尾:\r\n |
| 效率 | 高,工业主流 | 低,可读性强 |
九、LRC与CRC核心区别
-
CRC16:16位校验、多项式异或、RTU使用
-
LRC:8位累加取反、算法简单、ASCII使用
十、完整初始化代码
cs
private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int readLength = port.BytesToRead;
byte[] bs = new byte[readLength];
port.Read(bs, 0, readLength);
//ASCII转字符串,去除首尾换行回车
string s = Encoding.ASCII.GetString(bs).Trim('\r', '\n');
Console.WriteLine(s);
//判断是否为03读取响应帧
if (s.Contains("03"))
{
//解析4个寄存器数据
ushort[] us = StringToUshort(s, 4);
Console.WriteLine($"{us[0]}:{us[1]}:{us[2]}:{us[3]}");
}
}