云计算百科
云计算领域专业知识百科平台

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

  • 程序启动,自动打开串口

  • 组装 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单个寄存器写入操作

  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » C# Modbus-ASCII 超详细完整笔记(协议原理+LRC算法+读写源码全拆解)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!