一、Modbus 基础体系知识点(必背)
1.1 六大核心功能码含义
Modbus 所有通信全部依靠功能码区分操作类型:
-
01:读取线圈寄存器(开关量、状态量,只有 0/1,可读可写)
-
02:读取离散输入状态(只读开关量)
-
03:读取保持寄存器(数值型、可读可写、最常用)
-
04:读取输入寄存器(传感器采集值、只读)
-
06:写入单个保持寄存器(定点写入1个数值)
-
16:写入多个保持寄存器(批量写入)
1.2 线圈寄存器存储特点
线圈只有 0 / 1 状态,如果一个状态占用一个字节极度浪费空间。
Modbus 规则:8 个线圈状态 = 1 个字节,用单字节的8个Bit分别存储8个开关状态,极致节省带宽。
二、Modbus-RTU 与 Modbus-ASCII 核心区别(考试高频)
2.1 Modbus-RTU(二进制模式)
-
传输格式:纯字节二进制
-
校验方式:CRC16
-
帧结构:从机地址 + 功能码 + 起始地址 + 数据长度 + CRC校验(固定8字节)
-
特点:传输快、数据紧凑、工业最常用
2.2 Modbus-ASCII(字符串模式)
-
传输格式:ASCII 字符串(肉眼可读)
-
校验方式:LRC 纵向冗余校验
-
帧格式:: + 有效数据 + LRC校验 + \\r\\n
-
特点:兼容性强、调试直观、传输慢、字符冗余大
2.3 帧格式对比(核心记忆点)
RTU帧:01 03 00 00 00 04 CRC低 CRC高
ASCII帧::010300000004XXXX\\r\\n
XXXX 为 LRC 校验码
三、Modbus-ASCII 通信完整流程
程序启动,自动打开串口
组装 6 字节有效指令数据(地址、功能码、起始地址、长度)
对6字节数据计算 LRC 校验码
按照 ASCII 协议拼接:冒号开头 + 十六进制字符串 + LRC + 回车换行
串口发送字符串指令
接收下位机返回 ASCII 字符串
截取数据区、解析4个寄存器数值、打印输出
支持按钮触发 06功能码单寄存器写入
四、核心算法详解:LRC纵向冗余校验(逐原理拆解)
4.1 LRC 计算公式(固定三步)
LRC 只对有效指令字节计算,不含帧头冒号、不含\\r\\n
将所有参与校验的字节累加求和
对总和 按8位取反 + 1(补码运算)
与 0xFF 做与运算,强制截断为8位,防止溢出
4.2 LRC 完整源码 + 逐行注释
/// <summary>
/// Modbus-ASCII 专属 LRC 校验算法
/// </summary>
/// <param name="data">需要校验的有效字节数组(6字节指令)</param>
/// <returns>两位十六进制LRC校验字符串</returns>
public static string CalcLRC(byte[] data)
{
// 1.所有字节累加求和
uint sum = (uint)data.Sum(x => x);
// 2.总和8位取反+1 得到补码
// 3.&0xff 截断高位,只保留低8位,保证LRC为单字节
uint res = (~sum + 1) & 0xff;
// 转2位十六进制大写字符串
return res.ToString("X2");
}
4.3 LRC 与 CRC16 本质区别
-
CRC16:16位校验、多项式移位异或、精度高、RTU使用
-
LRC:8位校验、累加取反补码、算法简单、ASCII使用
五、ASCII请求帧组装函数(核心模块)
5.1 功能说明
传入6字节原始指令数组,自动转ASCII字符串、追加LRC、追加协议头尾,生成可直接发送的标准Modbus-ASCII帧。
5.2 源码+逐行解析
/// <summary>
/// 生成标准 Modbus-ASCII 完整请求帧
/// 格式规则 : + 数据串 + LRC + \\r\\n
/// </summary>
/// <param name="data">6字节有效指令数据</param>
/// <returns>ASCII协议完整帧字符串</returns>
public static string GetRequestFrame(byte[] data)
{
// 先计算当前指令的LRC校验码
string lrc = CalcLRC(data);
// 将所有字节转为2位十六进制字符串,拼接为长串
string requestData = "";
foreach (byte b in data)
{
requestData += b.ToString("X2");
}
// 协议固定格式:
// 开头冒号 + 指令数据 + LRC校验 + 结尾回车换行
string value = ":" + requestData + lrc + "\\r\\n";
return value;
}
5.4 最终生成帧示例
输入:01 03 00 00 00 04
输出::010300000004E0\\r\\n
六、ASCII 响应帧数据解析函数
6.1 解析规则
Modbus-ASCII 响应帧数据区:每一个寄存器数值占 4 个字符(2字节十六进制)
固定起始解析下标:7
6.2 完整源码+逐行注释
/// <summary>
/// 从ASCII响应字符串中批量解析保持寄存器数值
/// </summary>
/// <param name="s">下位机返回完整ASCII字符串</param>
/// <param name="valueCount">需要解析的寄存器个数</param>
/// <param name="startIndex">数据区起始下标,默认7</param>
/// <returns>ushort寄存器数值数组</returns>
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个字符 = 1个寄存器2字节数据
string value = s.Substring(startIndex, 4);
// 下次读取位置后移4位
startIndex += 4;
// 十六进制字符串转数值
bs[i] = Convert.ToUInt16(value);
}
return bs;
}
七、串口初始化 + 自动读取逻辑
// 全局串口定义:COM1、9600波特率、无校验、8数据位、1停止位
public SerialPort port = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
public Form1()
{
InitializeComponent();
// 打开串口
port.Open();
// 绑定数据接收事件
port.DataReceived += Port_DataReceived;
// 组装读取指令:从站1、03功能码、0地址、读取4个寄存器
string s2 = GetRequestFrame(new byte[] { 01, 03, 00, 00, 00, 04 });
Console.WriteLine(s2);
// 发送ASCII读取指令
port.Write(s2);
}
八、串口数据接收事件(响应帧解析)
private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// 获取缓冲区数据长度
int readLength = port.BytesToRead;
byte[] bs = new byte[readLength];
// 读取串口数据
port.Read(bs, 0, readLength);
// 字节转ASCII字符串,去除首尾\\r\\n
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]);
}
}
九、06功能码:单个寄存器写入功能(按钮事件)
9.1 写入指令规则
06功能码帧结构:从站地址 + 06 + 写入地址高8位 + 写入地址低8位 + 写入值高8位 + 写入值低8位
9.2 写入源码逐行解析
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), // 地址高8位
(byte)(addres & 0xff), // 地址低8位
(byte)(value >> 8), // 数值高8位
(byte)(value & 0xff) // 数值低8位
};
// 生成ASCII写入帧并发送
string s = GetRequestFrame(bs);
port.Write(s);
}
十、RTU与ASCII 完整帧对照总结
10.1 RTU读取帧(8字节)
01 03 00 00 00 04 CRC低 CRC高
10.2 ASCII读取帧(字符串)
:010300000004LRC\\r\\n
10.3 RTU响应帧
01 03 08 0001 0002 0003 0004 CRC
10.4 ASCII响应帧
:0103080001000200030004XX\\r\\n
十一、全套核心考点总结
-
ASCII 独有特征:冒号开头、回车换行结尾、LRC校验、字符串传输
-
RTU 独有特征:二进制字节、CRC16校验、无多余字符
-
LRC三步公式:求和 → 取反+1 → &0xff截断
-
高低字节拆分:右移8位取高字节,与0xff取低字节
-
ASCII解析规则:单寄存器4个字符,固定偏移7开始
-
03读、06写是工业设备最常用两个功能码
十二、项目整体运行流程
程序启动 → 初始化串口并打开
自动组装03读取指令 → 计算LRC → 生成ASCII标准帧
串口下发读取指令
下位机返回ASCII字符串响应帧
程序接收、清洗字符串、解析4路寄存器数据
点击按钮可触发06单个寄存器写入操作
网硕互联帮助中心







评论前必须登录!
注册