Modbus RTU

一是什么

工业串口 问答 协议 走RS485/RS232,主站问,从站答

RTU(Remote Terminal Unit)远程终端单元

二RTU帧格式

从站地址 功能码 数据区 CRC16 校验
1 字节 1 字节 N 字节 2 字节(低字节在前)

三串口基础

3.1引用

cs 复制代码
using System.IO.Ports;

3.2串口参数(必须和从站完全一致!)

cs 复制代码
SerialPort sp = new SerialPort("COM1")
{
    BaudRate=9600,
    DataBits =8, 
    Parity = StopBits.One,
    ReadTimeout = 1000,
    WriteTiemout = 1000
};
sp.Open();
sp.Close();

3.3CRC校验(Modbus)

发之前算,收之后验。 CRC 不包含自己的两个字节

cs 复制代码
// Modbus CRC16
public static ushort Crc16(byte[] data, int len)
{
    ushort crc = 0xFFFF;
    for (int i = 0; i < len; i++)
    {
        crc ^= data[i];
        for (int j = 0; j < 8; j++)
        {
            if ((crc & 0x0001) != 0)
                crc = (ushort)((crc >> 1) ^ 0xA001);
            else
                crc >>= 1;
        }
    }
    return crc;
}

使用

cs 复制代码
byte[] frame = new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x02 };
ushort crc = Crc16(frame, frame.Length);
// 拼到后面:低字节在前
byte[] sendBuf = frame.Concat(new byte[] { (byte)crc, (byte)(crc >> 8) }).ToArray();

四发送RTU请求

目标:从站地址 1,读寄存器 0,数量 2

cs 复制代码
// 1. 构造帧(不含 CRC)
byte[] req = new byte[]
{
    0x01,       // 从站地址
    0x03,       // 功能码:读保持寄存器
    0x00, 0x00, // 起始地址 0
    0x00, 0x02  // 寄存器数量 2
};

// 2. 加 CRC
ushort crc = Crc16(req, req.Length);
byte[] send = new byte[req.Length + 2];
Buffer.BlockCopy(req, 0, send, 0, req.Length);
send[req.Length] = (byte)crc;
send[req.Length + 1] = (byte)(crc >> 8);

// 3. 串口发送
sp.Write(send, 0, send.Length);

五C# 接收 RTU 应答

应答帧格式(03 应答)

从站地址 03 字节数 N 数据1 数据2 ... CRC低 CRC高

接收 + 校验 + 解析

cs 复制代码
// 收 1 帧(先按最大长度收,再处理)
byte[] recvBuf = new byte[256];
int len = sp.Read(recvBuf, 0, recvBuf.Length);
byte[] frame = recvBuf.Take(len).ToArray();

// 1. 校验 CRC
if (frame.Length < 2) return;
ushort recvCrc = (ushort)(frame[frame.Length - 2] | (frame[frame.Length - 1] << 8));
ushort calcCrc = Crc16(frame, frame.Length - 2);
if (recvCrc != calcCrc)
{
    Console.WriteLine("CRC 错误");
    return;
}

// 2. 检查地址、功能码
byte slaveId = frame[0];
byte func = frame[1];
if (slaveId != 1 || func != 0x03)
{
    Console.WriteLine("应答不匹配");
    return;
}

// 3. 解析数据
byte numBytes = frame[2];
for (int i = 0; i < numBytes; i += 2)
{
    ushort val = (ushort)(frame[3 + i] << 8 | frame[3 + i + 1]);
    Console.WriteLine("寄存器值:" + val);
}

完整示例

cs 复制代码
using System;
using System.IO.Ports;
using System.Linq;

class ModbusRtuDemo
{
    static SerialPort sp;

    static void Main()
    {
        sp = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One)
        {
            ReadTimeout = 1000,
            WriteTimeout = 1000
        };
        sp.Open();
        Console.WriteLine("串口打开");

        // 发:从站1,读寄存器0,2个
        byte[] req = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x02 };
        ushort crc = Crc16(req, req.Length);
        byte[] send = req.Concat(new byte[] { (byte)crc, (byte)(crc >> 8) }).ToArray();
        sp.Write(send, 0, send.Length);
        Console.WriteLine("已发送:" + BitConverter.ToString(send));

        // 收
        byte[] buf = new byte[256];
        int len = sp.Read(buf, 0, buf.Length);
        byte[] frame = buf.Take(len).ToArray();
        Console.WriteLine("收到:" + BitConverter.ToString(frame));

        // CRC 校验
        ushort rCrc = (ushort)(frame[frame.Length - 2] | (frame[frame.Length - 1] << 8));
        ushort cCrc = Crc16(frame, frame.Length - 2);
        Console.WriteLine(rCrc == cCrc ? "CRC 正确" : "CRC 错误");

        sp.Close();
    }

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

六第三方库

NModbus4/新Modbus

NModbus4 示例

cs 复制代码
using Modbus.Device;
using System.IO.Ports;

var sp = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
sp.Open();
var master = ModbusFactory.CreateRtuMaster(sp);
ushort[] regs = master.ReadHoldingRegisters(1, 0, 2); // 从站1,地址0,2个
相关推荐
段一凡-华北理工大学9 分钟前
AI Agent 从入门到封神:24 讲打造你的超级智能体~系列文章23:从Demo到上线:Agent应用的架构设计、性能优化与成本控制实战
运维·网络·人工智能·性能优化·高炉炼铁·工业智能体
L16247618 分钟前
Zabbix 7.0 LTS 完整部署与运维手册(AlmaLinux 9 + MySQL 8.0 + Nginx)
运维·mysql·zabbix
happymade19 分钟前
MSRM3 区域层级嵌套功能深度解析与实操教程
运维·网络拓扑·网络管理·网络运维·msrm3
陈同学xxx1 小时前
Hermes Agent 接入飞书,在手机上随时聊天
linux·python·飞书
网络小白不怕黑1 小时前
14.VSFTP服务相关配置
linux·运维·服务器
是摆烂第一名呀1 小时前
RK3576+GMSL摄像头底层适配与调试
linux·arm开发·驱动开发·嵌入式硬件·数码相机
爱喝水的鱼丶1 小时前
SAP-ABAP:ALV字段布局定制全攻略——快速适配业务端个性化展示需求
运维·性能优化·sap·abap·经验交流·alv报表
qetfw1 小时前
CentOS 7 配置 firewalld 防火墙
linux·运维·centos
留不住的何止是时间1 小时前
关于docker构建镜像build指令的注意事项
运维·docker·容器
Dovis(誓平步青云)2 小时前
《Linux CPU频率为何忽高忽低:cpufreq、idle状态与性能抖动教程》
linux·运维·服务器·spring boot·后端·生成对抗网络