当前位置: 首页 > news >正文

c#:使用Modbus RTU协议

Modbus是一种广泛应用于工业自动化领域的通信协议,支持多种传输方式,如RTU、TCP等。其中,Modbus RTU是一种基于串行通信的协议,具有高效、可靠的特点。本文将详细介绍Modbus RTU协议的基本原理,并重点解析功能码0x03(读取保持寄存器)、0x06(写单个寄存器)和0x10(写多个寄存器)的使用和作用。同时,我们将通过C#代码实现这些功能码的读写操作。

1. Modbus RTU协议简介

1.1 协议概述

Modbus RTU协议是一种主从式通信协议,采用二进制编码,数据帧紧凑,传输效率高。它通过串口(如RS-232、RS-485)进行数据传输,适用于工业现场设备之间的通信。

1.2 数据帧格式

Modbus RTU数据帧由以下部分组成:

从站地址:1字节,表示目标设备的地址。

功能码:1字节,表示操作类型(如读取寄存器、写入寄存器等)。

数据域:可变长度,包含操作所需的数据。

CRC校验:2字节,用于校验数据帧的完整性。

2. 功能码详解

2.1 功能码0x03:读取保持寄存器

作用:读取从站设备中的一个或多个保持寄存器的值。

请求帧格式:

从站地址(1字节)

功能码(0x03,1字节)

起始寄存器地址(2字节)

寄存器数量(2字节)

CRC校验(2字节)

响应帧格式:

从站地址(1字节)

功能码(0x03,1字节)

数据字节数(1字节)

寄存器数据(N字节)

CRC校验(2字节)

2.2 功能码0x06:写单个寄存器

作用:向从站设备中的一个保持寄存器写入数据。

请求帧格式:

从站地址(1字节)

功能码(0x06,1字节)

寄存器地址(2字节)

写入值(2字节)

CRC校验(2字节)

响应帧格式:

从站地址(1字节)

功能码(0x06,1字节)

寄存器地址(2字节)

写入值(2字节)

CRC校验(2字节)

2.3 功能码0x10:写多个寄存器

作用:向从站设备中的多个保持寄存器写入数据。

请求帧格式:

从站地址(1字节)

功能码(0x10,1字节)

起始寄存器地址(2字节)

寄存器数量(2字节)

数据字节数(1字节)

写入数据(N字节)

CRC校验(2字节)

响应帧格式:

从站地址(1字节)

功能码(0x10,1字节)

起始寄存器地址(2字节)

寄存器数量(2字节)

CRC校验(2字节)

3. C#实现Modbus RTU协议

以下是使用C#实现Modbus RTU协议的代码示例,包括功能码0x03、0x06和0x10的读写操作。

3.1 串口初始化

using System.IO.Ports;

public class ModbusRTU
{
    private SerialPort serialPort;

    public ModbusRTU(string portName, int baudRate)
    {
        serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
        serialPort.Open();
    }

    public void Close()
    {
        serialPort.Close();
    }
}

3.2 CRC校验

public static byte[] CalculateCRC(byte[] data)
{
    ushort crc = 0xFFFF;
    for (int i = 0; i < data.Length; i++)
    {
        crc ^= data[i];
        for (int j = 0; j < 8; j++)
        {
            if ((crc & 0x0001) != 0)
            {
                crc >>= 1;
                crc ^= 0xA001;
            }
            else
            {
                crc >>= 1;
            }
        }
    }
    return new byte[] { (byte)(crc & 0xFF), (byte)(crc >> 8) };
}

3.3 功能码0x03:读取保持寄存器

public ushort[] ReadHoldingRegisters(byte slaveAddress, ushort startAddress, ushort numberOfRegisters)
{
    byte[] request = new byte[8];
    request[0] = slaveAddress; // 从站地址
    request[1] = 0x03; // 功能码
    request[2] = (byte)(startAddress >> 8); // 起始地址高字节
    request[3] = (byte)(startAddress & 0xFF); // 起始地址低字节
    request[4] = (byte)(numberOfRegisters >> 8); // 寄存器数量高字节
    request[5] = (byte)(numberOfRegisters & 0xFF); // 寄存器数量低字节
    byte[] crc = CalculateCRC(request.Take(6).ToArray());
    request[6] = crc[0]; // CRC低字节
    request[7] = crc[1]; // CRC高字节

    serialPort.Write(request, 0, request.Length);

    byte[] response = new byte[5 + 2 * numberOfRegisters];
    serialPort.Read(response, 0, response.Length);

    ushort[] registers = new ushort[numberOfRegisters];
    for (int i = 0; i < numberOfRegisters; i++)
    {
        registers[i] = (ushort)(response[3 + 2 * i] << 8 | response[4 + 2 * i]);
    }
    return registers;
}

3.4 功能码0x06:写单个寄存器

public void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value)
{
    byte[] request = new byte[8];
    request[0] = slaveAddress; // 从站地址
    request[1] = 0x06; // 功能码
    request[2] = (byte)(registerAddress >> 8); // 寄存器地址高字节
    request[3] = (byte)(registerAddress & 0xFF); // 寄存器地址低字节
    request[4] = (byte)(value >> 8); // 写入值高字节
    request[5] = (byte)(value & 0xFF); // 写入值低字节
    byte[] crc = CalculateCRC(request.Take(6).ToArray());
    request[6] = crc[0]; // CRC低字节
    request[7] = crc[1]; // CRC高字节

    serialPort.Write(request, 0, request.Length);

    byte[] response = new byte[8];
    serialPort.Read(response, 0, response.Length);
}

3.5 功能码0x10:写多个寄存器

public void WriteMultipleRegisters(byte slaveAddress, ushort startAddress, ushort[] values)
{
    byte[] request = new byte[9 + 2 * values.Length];
    request[0] = slaveAddress; // 从站地址
    request[1] = 0x10; // 功能码
    request[2] = (byte)(startAddress >> 8); // 起始地址高字节
    request[3] = (byte)(startAddress & 0xFF); // 起始地址低字节
    request[4] = (byte)(values.Length >> 8); // 寄存器数量高字节
    request[5] = (byte)(values.Length & 0xFF); // 寄存器数量低字节
    request[6] = (byte)(2 * values.Length); // 数据字节数
    for (int i = 0; i < values.Length; i++)
    {
        request[7 + 2 * i] = (byte)(values[i] >> 8); // 数据高字节
        request[8 + 2 * i] = (byte)(values[i] & 0xFF); // 数据低字节
    }
    byte[] crc = CalculateCRC(request.Take(7 + 2 * values.Length).ToArray());
    request[7 + 2 * values.Length] = crc[0]; // CRC低字节
    request[8 + 2 * values.Length] = crc[1]; // CRC高字节

    serialPort.Write(request, 0, request.Length);

    byte[] response = new byte[8];
    serialPort.Read(response, 0, response.Length);
}

4. 总结

本文详细介绍了Modbus RTU协议的基本原理,并通过C#代码实现了功能码0x03、0x06和0x10的读写操作。Modbus RTU协议在工业自动化领域应用广泛,掌握其实现方法对于开发工业控制系统具有重要意义。

希望这篇文章对您有所帮助!如果有任何问题,欢迎在评论区留言讨论。

相关文章:

  • 高频SQL50题 第一天 | 1757. 可回收且低脂的产品、584. 寻找用户推荐人、595. 大的国家、1683. 无效的推文、1148. 文章浏览 I
  • 3.19刷题
  • 【VUE】day05-ref引用
  • 3.18-1
  • 基于Spring Boot的冷链物流系统的设计与实现的设计与实现(LW+源码+讲解)
  • docker需要sudo才能使用
  • Qt窗口控件之颜色对话框QColorDialog
  • jvm中每个类的Class对象是唯一的吗
  • 计算机网络分层结构
  • 【第14节】windows sdk编程:进程与线程介绍
  • Vue下载与安装步骤
  • Windows Docker 报错: has no HTTPS proxy,换源
  • 网络空间安全(37)获取webshell方法总结
  • 面试八股 —— Redis篇
  • Dagger2从入门到放弃
  • c++ - 笔记
  • C/C++结构体简单介绍
  • 深度学习:从零开始的DeepSeek-R1-Distill有监督微调训练实战(SFT)
  • Python 中下划线 “_” 的多面性:从变量到约定
  • java agent 学习
  • 传奇sf 新开网站/站长工具国色天香
  • 平台手机app开发/自然搜索优化
  • 重庆做网站嘉兴公司/互联网站
  • 广东省人防工程建设网站/浏览器老是出现站长工具
  • 营销网站建设hanyous/360搜索指数
  • wordpress教育培训/seo排名优化技术