c#软件开发学习笔记--Modbus-RTU通讯

Modbus 通讯协议学习总结

一、Modbus 协议概述

1.1 什么是 Modbus

Modbus 是一种开放的串行通讯协议,主要用于工业领域的智能监控场景中。它定义了数据在传输过程中的格式标准,是全球工业设备中最常用的通讯协议之一。

1.2 Modbus 协议的三种格式

协议格式 说明
Modbus-RTU 基于二进制的协议,采用 CRC16 校验,适用于 RS-485、RS-232 等串行通讯
Modbus-ASCII 基于 ASCII 码的协议,采用 LRC 校验,可读性好但效率低
Modbus-TCP 基于 TCP/IP 网络的协议,无需 CRC 校验,适用于以太网通讯

本篇内容主要讲解 Modbus-RTU 协议格式。


二、Modbus 主从站架构

2.1 主站(Master)

  • 至少存在一个主站设备
  • 通常是一台运行软件的主机(如监控计算机)
  • 负责向从站发送指令

2.2 从站(Slave)

  • 执行系统参数测量和控制设备的开关状态
  • 接收到主站指令后,需要回复主站(响应指令)
  • 每个从站有唯一的地址(默认从站地址为 01

2.3 通讯流程

复制代码
主站发送指令 → 从站接收指令 → 从站响应主站 → 主站接收响应

三、Modbus-RTU 帧结构

3.1 请求帧(主站发送)

字段 字节数 说明 示例值
从机地址 1 字节 目标从站地址 01
功能码 1 字节 操作类型 03(读取寄存器)
起始地址 2 字节 高位在前,低位在后 00 00
数据长度 2 字节 高位在前,低位在后 00 04(读取4个寄存器)
校验码 (CRC16) 2 字节 低位在前,高位在后 09 44

完整请求帧示例:

复制代码
01 03 00 00 00 04 09 44

含义:从地址为 01 的设备,从寄存器地址 0000 开始,读取 4 个寄存器。

3.2 响应帧(从站回复)

字段 字节数 说明 示例值
从机地址 1 字节 响应的从站地址 01
功能码 1 字节 响应的功能码 03
有效数据长度 1 字节 返回数据字节数 08(4个寄存器 × 2字节)
数据 N 字节 寄存器数据 00 03 00 04 00 05 00 06
校验码 (CRC16) 2 字节 低位在前,高位在后 11 12

完整响应帧示例:

复制代码
01 03 08 00 03 00 04 00 05 00 06 11 12

含义:从站 01 响应,返回了 4 个寄存器的值:0003000400050006

3.3 常用功能码

功能码 说明
01 读取线圈状态(开关量)
02 读取离散输入状态
03 读取保持寄存器(模拟量)
04 读取输入寄存器
05 写单个线圈
06 写单个寄存器
0F 写多个线圈
10 写多个寄存器

四、CRC16 校验算法

4.1 CRC 校验概述

CRC(Cyclic Redundancy Check,循环冗余校验)是一种用于校验数据传输是否出错的算法。Modbus-RTU 采用 CRC16 校验。

4.2 CRC16-Modbus 参数

参数
初始值 0xFFFF
多项式 0xA001(反转多项式)
校验范围 从机地址到数据长度(共 6 字节)

4.3 CRC16 计算步骤

  1. 初始化 :将 16 位寄存器初始化为 0xFFFF
  2. 逐字节处理:将待校验数据的每个字节与 CRC 寄存器进行异或
  3. 逐位处理 :对每个字节的 8 个 bit 进行以下操作:
    • 取出 CRC 寄存器的最低位
    • CRC 寄存器右移 1 位
    • 如果被移出的位是 1,则 CRC 寄存器与 0xA001 异或
  4. 高低位互换:计算完成后,将 CRC 值的高低字节互换

4.4 CRC16 代码实现

csharp 复制代码
/// <summary>
/// 生成CRC16校验码
/// </summary>
/// <param name="data">待校验的字节数组</param>
/// <returns>CRC校验码(2字节,低位在前)</returns>
public static byte[] CRC16(byte[] data)
{
    int crc = 0xFFFF; // 初始化CRC寄存器

    // 只对前 Length-2 字节进行校验(排除最后2字节的校验码位置)
    for (int i = 0; i < data.Length - 2; i++)
    {
        crc = crc ^ data[i]; // 当前字节与CRC寄存器异或

        for (int j = 0; j < 8; j++) // 处理8个bit
        {
            int temp = crc & 1;   // 取出最低位
            crc = crc >> 1;       // 右移1位
            crc = crc & 0x7FFF;   // 确保最高位为0

            if (temp == 1)
            {
                crc = crc ^ 0xA001; // 移出位为1时与多项式异或
            }

            crc = crc & 0xFFFF;   // 确保只有16位有效
        }
    }

    // 高低位互换(Modbus协议要求:低字节在前,高字节在后)
    byte[] crc16 = new byte[2];
    crc16[1] = (byte)((crc >> 8) & 0xFF); // 高位
    crc16[0] = (byte)(crc & 0xFF);         // 低位
    return crc16;
}

4.5 CRC 校验示意图

复制代码
数据字节 → 异或CRC寄存器 → 逐位右移判断 → 与多项式异或 → 最终CRC值

五、串口通信配置

5.1 串口参数

参数 说明 常用值
波特率 每秒传输的符号数 9600, 19200, 115200
数据位 每个数据的位数 8
停止位 传输结束的停止位数 1
校验位 数据校验方式 None(无), Odd(奇), Even(偶)

5.2 串口初始化代码

csharp 复制代码
/// <summary>
/// 初始化串口参数
/// </summary>
private void InitSerialPort()
{
    serialPort1.PortName = comboBox5.SelectedItem.ToString(); // 串口名称
    serialPort1.BaudRate = Convert.ToInt32(comboBox1.SelectedItem.ToString()); // 波特率
    serialPort1.DataBits = Convert.ToInt32(comboBox4.SelectedItem.ToString()); // 数据位
    serialPort1.StopBits = (StopBits)Enum.Parse(typeof(StopBits), comboBox2.SelectedItem.ToString()); // 停止位
    serialPort1.Parity = (Parity)Enum.Parse(typeof(Parity), comboBox3.SelectedItem.ToString()); // 校验位
    
    if (!serialPort1.IsOpen)
        serialPort1.Open(); // 打开串口
}

六、Modbus 通讯完整流程

6.1 请求帧构建与发送

csharp 复制代码
/// <summary>
/// 发送Modbus读取请求帧
/// </summary>
public void SendData()
{
    // 1. 构建请求帧(8字节)
    byte[] buffer = new byte[8];
    
    buffer[0] = 0x01; // 从机地址
    buffer[1] = 0x03; // 功能码:读取保持寄存器
    buffer[2] = 0x00; // 起始地址高位
    buffer[3] = 0x00; // 起始地址低位
    buffer[4] = 0x00; // 数据长度高位
    buffer[5] = 0x04; // 数据长度低位(读取4个寄存器)
    
    // 2. 计算CRC16校验码
    byte[] crc = CRCHelper.CRC16(buffer);
    buffer[6] = crc[0]; // 校验码低位
    buffer[7] = crc[1]; // 校验码高位
    
    // 3. 发送数据
    serialPort1.Write(buffer, 0, buffer.Length);
}

6.2 响应帧接收与解析

csharp 复制代码
/// <summary>
/// 串口数据接收事件处理
/// </summary>
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    // 1. 读取接收缓冲区数据
    byte[] bs = new byte[serialPort1.BytesToRead];
    serialPort1.Read(bs, 0, bs.Length);
    
    // 2. 验证响应帧长度(13字节:1+1+1+8+2)
    if (bs.Length != 13) return;
    
    // 3. 解析数据(寄存器数据为高位在前,低位在后)
    int a = bs[3] * 256 + bs[4];   // 第1个寄存器值
    int b = bs[5] * 256 + bs[6];   // 第2个寄存器值
    int c = bs[7] * 256 + bs[8];   // 第3个寄存器值
    int d = bs[9] * 256 + bs[10];  // 第4个寄存器值
    
    // 4. 更新UI显示(使用Invoke跨线程更新)
    Invoke(new Action(() =>
    {
        textBox1.Text = a.ToString();
        textBox2.Text = b.ToString();
        textBox3.Text = c.ToString();
        textBox4.Text = d.ToString();
    }));
}

6.3 通讯流程图

复制代码
┌─────────────┐    构建请求帧     ┌─────────────┐
│  初始化串口   │ ──────────────→ │  计算CRC16   │
└─────────────┘                  └──────┬──────┘
                                          │
                                          ▼
┌─────────────┐    发送帧数据     ┌─────────────┐
│  更新UI显示   │ ←────────────── │  发送请求帧  │
└─────────────┘                  └──────┬──────┘
                                          │
                                          ▼
                                   ┌─────────────┐
                                   │  等待响应帧  │
                                   └──────┬──────┘
                                          │
                                          ▼
┌─────────────┐    解析数据       ┌─────────────┐
│  数据解析显示  │ ←────────────── │  接收响应帧  │
└─────────────┘                  └─────────────┘

七、关键知识点总结

7.1 Modbus-RTU 协议要点

要点 说明
字节序 数据字段高位在前(Big-Endian),校验码低位在前
寄存器长度 一个寄存器 = 2 字节(16 位)
响应帧组成 地址 + 功能码 + 数据长度 + 数据 + CRC
异常响应 功能码最高位置 1(如 0x83 表示功能码 0x03 异常)

7.2 数据解析要点

csharp 复制代码
// 寄存器值解析:高位字节 × 256 + 低位字节
int value = highByte * 256 + lowByte;

// 示例:数据字节为 [0x01, 0xF4]
// value = 1 * 256 + 244 = 500

7.3 线程安全

使用 Invoke 方法在串口数据接收事件中更新 UI,避免跨线程操作异常:

csharp 复制代码
Invoke(new Action(() =>
{
    // 更新UI控件
    textBox1.Text = value.ToString();
}));

八、常见问题与注意事项

8.1 通讯失败排查

  1. 检查串口参数:波特率、数据位、停止位、校验位是否与从站一致
  2. 检查从站地址:地址是否正确(默认 01)
  3. 检查接线:RS-485 需要注意 A/B 线连接
  4. 检查 CRC 校验:确保校验码计算正确

8.2 数据解析注意

  • 响应帧数据为 高位在前 ,解析时需 高位 × 256 + 低位
  • CRC 校验码为 低位在前,与数据字段相反
  • 不同寄存器类型(保持寄存器/输入寄存器)需使用对应功能码

8.3 异常响应码

异常码 含义
01 非法功能码
02 非法数据地址
03 非法数据值
04 从站设备故障

相关推荐
xqqxqxxq1 小时前
Java Socket 多人聊天室(私聊+群聊)技术笔记(V4版本)
java·网络·笔记
菜鸟‍2 小时前
【论文学习】MICCAI 2024 || SGSeg:通过自引导机制实现胸部X光片语言引导分割的无文本推理
人工智能·深度学习·学习
龙仔7252 小时前
人大金仓OS_Core数据库自动备份实施笔记(银河麒麟Linux)
linux·数据库·笔记·备份·人大金仓
lazy H2 小时前
Git clone 怎么用?克隆项目及常见问题完整教程
大数据·git·后端·学习·搜索引擎·github
菜鸟‍2 小时前
【论文学习】arxiv 2024 || RoSIS:基于鲁棒框架重新审视文本提示式手术器械分割
人工智能·学习·计算机视觉
颜x小2 小时前
[C#] C++与c#语法对比
开发语言·c++·c#
weixin_423652132 小时前
C#使用JObject
开发语言·c#
笨鸟先飞的橘猫3 小时前
只会脚本语言的游戏后端自我提升之路——c++学习(开篇)
学习·游戏
绿浪19844 小时前
async/await 深入学习
学习