C# Modbus-ASCII 超详细完整笔记(协议原理+LRC算法+读写源码全拆解)

一、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 通信完整流程

  1. 程序启动,自动打开串口

  2. 组装 6 字节有效指令数据(地址、功能码、起始地址、长度)

  3. 对6字节数据计算 LRC 校验码

  4. 按照 ASCII 协议拼接:冒号开头 + 十六进制字符串 + LRC + 回车换行

  5. 串口发送字符串指令

  6. 接收下位机返回 ASCII 字符串

  7. 截取数据区、解析4个寄存器数值、打印输出

  8. 支持按钮触发 06功能码单寄存器写入


四、核心算法详解:LRC纵向冗余校验(逐原理拆解)

4.1 LRC 计算公式(固定三步)

LRC 只对有效指令字节计算,不含帧头冒号、不含\r\n

  1. 将所有参与校验的字节累加求和

  2. 对总和 按8位取反 + 1(补码运算)

  3. 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写是工业设备最常用两个功能码


十二、项目整体运行流程

  1. 程序启动 → 初始化串口并打开

  2. 自动组装03读取指令 → 计算LRC → 生成ASCII标准帧

  3. 串口下发读取指令

  4. 下位机返回ASCII字符串响应帧

  5. 程序接收、清洗字符串、解析4路寄存器数据

  6. 点击按钮可触发06单个寄存器写入操作

相关推荐
DeviceHub4 小时前
硬件工程师选型笔记:ALPS SKPMAPE010 与 Tonevee TS-3025 贴片轻触开关 PIN TO PIN 评估指南
笔记
疯狂打码的少年5 小时前
【数据结构】二叉树的性质(五大性质+计算)
数据结构·笔记·算法
Capricorn19886 小时前
知芽研究问题看板无法推进?从孵化区到笔记的机制排查指南
大数据·论文阅读·人工智能·笔记·学习方法·论文笔记
闪闪发亮的小星星6 小时前
相机脱靶量VS精跟踪精度
笔记
皖山文武6 小时前
逻辑代数基础学习笔记--概述
笔记·学习·机器学习
demodeom10 小时前
Win11 + RTX 5080 本地 Coding 模型测试笔记(已弃坑)
笔记
青山是哪个青山10 小时前
LangChain 学习笔记(五):Tools 工具调用
笔记·学习·langchain
peijiping10 小时前
Agent 工具执行:并行(parallel_tool_calls)和后台(run_in_background)到底有什么区别? (学习笔记)
笔记·学习·ai agent·claude code
Oll Correct10 小时前
Adobe illustrator 案例四:吃豆人 (Pac-Man)图标绘制
笔记·ui·adobe·illustrator
LuminousCPP11 小时前
单链表专题(四)-刷题复盘篇-LeetCode 138 随机链表复制|原地拷贝法突破复杂指针操作
数据结构·笔记·算法·leetcode·链表