GKMLT通讯工具箱(WPF MVVM) - 01-网口/串口通讯与 ModBus RTU/TCP

目录

  1. 概述
  2. [串口通讯 vs 网口通讯](#串口通讯 vs 网口通讯)
  3. [Modbus 协议基础](#Modbus 协议基础)
  4. [Modbus RTU vs Modbus TCP](#Modbus RTU vs Modbus TCP)
  5. 报文格式详解
  6. 实现架构
  7. 使用示例

概述

本项目实现了完整的串口通讯、网口通讯以及 Modbus RTU 和 Modbus TCP 协议调试工具。


串口通讯 vs 网口通讯

串口通讯(Serial Communication)

定义:

串口通讯是一种通过串行通信接口进行数据传输的方式,数据按位(bit)顺序,一位接一位地传输。

特点:

  • 物理层: 使用物理电缆(如 RS-232、RS-485、RS-422)
  • 传输方式: 串行传输,一位一位地发送
  • 传输距离:
    • RS-232:最长 15 米(约 50 英尺)
    • RS-485:最长 1200 米(约 4000 英尺)
  • 传输速率: 常用波特率:4800、9600、19200、38400、57600、115200 bps
  • 连接方式: 点对点(RS-232)或总线型(RS-485,最多支持 32 个设备)
  • 抗干扰能力: RS-485 具有较强的抗干扰能力,适合工业环境

应用场景:

  • 工业现场设备通讯
  • 传感器数据采集
  • PLC 与上位机通讯
  • 短距离设备控制

关键参数:

复制代码
- 端口名称:COM1、COM2、COM3...
- 波特率:数据传输速度(如 9600 bps)
- 数据位:通常为 7 或 8 位
- 校验位:None、Odd、Even、Mark、Space
- 停止位:1、1.5、2 位

网口通讯(TCP/IP Communication)

定义:

网口通讯是基于 TCP/IP 协议栈的网络通讯方式,通过以太网进行数据传输。

特点:

  • 物理层: 使用以太网线(双绞线、光纤)
  • 传输方式: 并行传输,数据包形式
  • 传输距离:
    • 双绞线:100 米(单段)
    • 光纤:数十公里
  • 传输速率: 10/100/1000 Mbps,远高于串口
  • 连接方式: 基于客户端-服务器模型
  • 网络协议: TCP/IP 协议栈

应用场景:

  • 远程设备监控
  • 跨网络设备集成
  • 高速数据采集
  • 工业以太网环境

关键参数:

复制代码
- IP 地址:如 192.168.1.100
- 端口号:如 502(Modbus TCP 默认端口)
- 通讯模式:客户端(Client)/服务器(Server)
- 连接类型:长连接/短连接

串口 vs 网口对比表

特性 串口通讯 网口通讯
物理介质 串行电缆 以太网线/光纤
传输距离 较短(15-1200米) 较长(可达数十公里)
传输速率 低(4800-115200 bps) 高(10-1000 Mbps)
抗干扰能力 RS-485 较强 需要屏蔽和接地
布线复杂度 简单(总线型) 需要交换机/路由器
设备数量 有限(RS-485: 32个) 几乎无限
成本 中等
维护难度 简单 较复杂
适用环境 工业现场、短距离 远程监控、跨网络

Modbus 协议基础

Modbus 协议概述

Modbus 是一种工业通讯协议,由 Modicon 公司(现 Schneider Electric)于 1979 年发布,是工业领域中最流行的通讯协议之一。

特点:

  • 开放、免费、无版权限制
  • 简单、易于实现
  • 支持多种物理层(RS-232、RS-485、TCP/IP)
  • 主从架构(Master-Slave)
  • 支持多种数据类型

Modbus 核心概念

1. 主从模式(Master-Slave)

复制代码
Master(主站)    Slave(从站)
   ↓ 发送请求
   ← 返回响应
   
- 只能由主站发起通讯
- 从站被动响应
- 同一时间只能有一个事务在进行

2. 功能码(Function Code)

功能码 名称 说明 数据类型
0x01 读取线圈 读取输出线圈的 ON/OFF 状态 Bool[]
0x02 读取离散输入 读取输入线圈的 ON/OFF 状态 Bool[]
0x03 读取保持寄存器 读取一个或多个保持寄存器的值 Byte[]
0x04 读取输入寄存器 读取一个或多个输入寄存器的值 Byte[]
0x05 写入单个线圈 写入单个线圈为 ON 或 OFF -
0x06 写入单个寄存器 写入单个保持寄存器的值 -
0x0F 写入多个线圈 写入多个线圈为 ON 或 OFF -
0x10 写入多个寄存器 写入多个保持寄存器的值 -

3. 数据地址模型

复制代码
线圈(Coil):    地址 0xxxx  - 位读写,表示开关量
离散输入(Input):地址 1xxxx  - 位只读,表示开关量输入
输入寄存器:     地址 3xxxx  - 字只读,16位数据
保持寄存器:     地址 4xxxx  - 字读写,16位数据

4. 字节序(Endianness)

  • 大端序(Big-Endian): 高位字节在前,低位字节在后
  • 小端序(Little-Endian): 低位字节在前,高位字节在后
  • Modbus 标准: 使用大端序

Modbus RTU vs Modbus TCP

核心区别

特性 Modbus RTU Modbus TCP
传输层 串口(RS-232/485) TCP/IP
编码方式 二进制 二进制
帧结构 从站地址 + 功能码 + 数据 + CRC MBAP头 + 功能码 + 数据
错误校验 CRC16(2字节) TCP校验和
地址标识 从站地址(1字节) 单元标识符(1字节)
默认端口 串口参数配置 502
速度 较慢 较快
传输距离 短距离 长距离(跨网络)

Modbus RTU 详细说明

报文结构:

复制代码
┌──────────┬──────┬──────┬────────┬─────┐
│ 从站地址 │ 功能码 │ 数据  │ CRC16 │CRC  │
│ 1 字节   │1 字节 │ N 字节│ 低位 │高位 │
└──────────┴──────┴──────┴────────┴─────┘

示例:读取保持寄存器(功能码 0x03)
请求报文:01 03 00 00 00 0A [CRC] [CRC]
- 01:从站地址
- 03:功能码(读保持寄存器)
- 00 00:起始地址(0)
- 00 0A:寄存器数量(10个)
- [CRC] [CRC]:CRC16校验码

响应报文:01 03 14 [20字节数据] [CRC] [CRC]
- 01:从站地址
- 03:功能码
- 14:返回字节数(20字节)
- [20字节数据]:实际数据
- [CRC] [CRC]:CRC16校验码

CRC16 校验算法:

csharp 复制代码
// CRC16-MODBUS 算法
// 初始值:0xFFFF
// 多项式:0xA001
// 反转输入和输出

private byte[] CRC16(byte[] data, int length)
{
    int i = 0;
    byte[] res = new byte[2] { 0xFF, 0xFF };
    ushort iIndex;
    
    while (length-- > 0)
    {
        iIndex = (ushort)(res[0] ^ data[i++]);
        res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
        res[1] = aucCRCLo[iIndex];
    }
    return res;
}

Modbus TCP 详细说明

报文结构:

复制代码
┌───────────────────MBAP 头───────────────────┬──────┬────────┬─────┐
│ 事务ID │ 协议ID │ 长度  │ 单元ID │ 功能码 │ 数据  │
│ 2 字节 │ 2 字节 │ 2 字节│ 1 字节 │1 字节 │ N 字节│
└─────────┴────────┴───────┴────────┴──────┴────────┘

示例:读取保持寄存器(功能码 0x03)
请求报文:00 01 00 00 00 06 01 03 00 00 00 0A
- 00 01:事务标识符(Transaction ID)
- 00 00:协议标识符(Protocol ID,Modbus = 0)
- 00 06:长度(后续字节数:6字节)
- 01:单元标识符(Unit ID,对应从站地址)
- 03:功能码(读保持寄存器)
- 00 00:起始地址(0)
- 00 0A:寄存器数量(10个)

响应报文:00 01 00 00 00 15 01 03 14 [20字节数据]
- 00 01:事务标识符(与请求相同)
- 00 00:协议标识符
- 00 15:长度(21字节)
- 01:单元标识符
- 03:功能码
- 14:返回字节数(20字节)
- [20字节数据]:实际数据

MBAP 头(Modbus Application Protocol Header):

字段 长度 说明
Transaction ID 2 字节 事务标识符,用于匹配请求和响应。客户端每次递增,服务器原样返回
Protocol ID 2 字节 协议标识符,Modbus TCP 固定为 0
Length 2 字节 后续字节数(单元ID + 功能码 + 数据)
Unit ID 1 字节 从站地址,对应 RTU 中的从站地址

事务ID处理:

csharp 复制代码
private ushort transactionId = 0;

public ushort TransactionId
{
    get
    {
        lock (lockobj)
        {
            return transactionId == ushort.MaxValue ? (ushort)1 : ++transactionId;
        }
    }
}

报文格式详解

读取操作报文

1. 读取线圈(功能码 0x01)

RTU 报文:

复制代码
请求:[SlaveID][0x01][StartAddrHi][StartAddrLo][QtyHi][QtyLo][CRCL][CRCH]
响应:[SlaveID][0x01][ByteCount][CoilStatus...][CRCL][CRCH]

示例:读取从站1的线圈0-7(8个线圈)
请求:01 01 00 00 00 08 [CRC_L] [CRC_H]
响应:01 01 01 [ CoilData ] [CRC_L] [CRC_H]
      └─1字节─┘

TCP 报文:

复制代码
请求:[TransID][ProtoID][Len][UnitID][0x01][StartAddrHi][StartAddrLo][QtyHi][QtyLo]
响应:[TransID][ProtoID][Len][UnitID][0x01][ByteCount][CoilStatus...]

示例:读取从站1的线圈0-7(8个线圈)
请求:00 01 00 00 00 06 01 01 00 00 00 08
响应:00 01 00 00 00 04 01 01 01 [ CoilData ]
      └─1字节─┘
2. 读取保持寄存器(功能码 0x03)

RTU 报文:

复制代码
请求:[SlaveID][0x03][StartAddrHi][StartAddrLo][QtyHi][QtyLo][CRCL][CRCH]
响应:[SlaveID][0x03][ByteCount][DataHi][DataLo]...[CRCL][CRCH]

示例:读取从站1的保持寄存器地址0-9(10个寄存器)
请求:01 03 00 00 00 0A [CRC_L] [CRC_H]
响应:01 03 14 [20字节寄存器数据] [CRC_L] [CRC_H]
      └─20字节─┘

TCP 报文:

复制代码
请求:[TransID][ProtoID][Len][UnitID][0x03][StartAddrHi][StartAddrLo][QtyHi][QtyLo]
响应:[TransID][ProtoID][Len][UnitID][0x03][ByteCount][DataHi][DataLo]...

示例:读取从站1的保持寄存器地址0-9(10个寄存器)
请求:00 02 00 00 00 06 01 03 00 00 00 0A
响应:00 02 00 00 00 15 01 03 14 [20字节寄存器数据]
      └─21字节─┘

写入操作报文

1. 写入单个寄存器(功能码 0x06)

RTU 报文:

复制代码
请求:[SlaveID][0x06][RegAddrHi][RegAddrLo][RegValHi][RegValLo][CRCL][CRCH]
响应:[SlaveID][0x06][RegAddrHi][RegAddrLo][RegValHi][RegValLo][CRCL][CRCH]

示例:写入从站1的寄存器地址0,值为0x0001
请求:01 06 00 00 00 01 [CRC_L] [CRC_H]
响应:01 06 00 00 00 01 [CRC_L] [CRC_H]
      └─回显请求报文(Echo)─┘

TCP 报文:

复制代码
请求:[TransID][ProtoID][Len][UnitID][0x06][RegAddrHi][RegAddrLo][RegValHi][RegValLo]
响应:[TransID][ProtoID][Len][UnitID][0x06][RegAddrHi][RegAddrLo][RegValHi][RegValLo]

示例:写入从站1的寄存器地址0,值为0x0001
请求:00 03 00 00 00 06 01 06 00 00 00 01
响应:00 03 00 00 00 06 01 06 00 00 00 01
      └─回显请求报文(Echo)─┘
2. 写入多个寄存器(功能码 0x10)

RTU 报文:

复制代码
请求:[SlaveID][0x10][StartAddrHi][StartAddrLo][RegQtyHi][RegQtyLo][ByteCount]
      [Data1_Hi][Data1_Lo]...[DataN_Hi][DataN_Lo][CRCL][CRCH]
响应:[SlaveID][0x10][StartAddrHi][StartAddrLo][RegQtyHi][RegQtyLo][CRCL][CRCH]

示例:写入从站1的寄存器地址0-1(2个寄存器,4字节数据)
请求:01 10 00 00 00 02 04 [Data] [CRC_L] [CRC_H]
      └─4字节数据─┘
响应:01 10 00 00 00 02 [CRC_L] [CRC_H]
      └─地址和数量确认─┘

TCP 报文:

复制代码
请求:[TransID][ProtoID][Len][UnitID][0x10][StartAddrHi][StartAddrLo][RegQtyHi][RegQtyLo][ByteCount]
      [Data1_Hi][Data1_Lo]...[DataN_Hi][DataN_Lo]
响应:[TransID][ProtoID][Len][UnitID][0x10][StartAddrHi][StartAddrLo][RegQtyHi][RegQtyLo]

示例:写入从站1的寄存器地址0-1(2个寄存器,4字节数据)
请求:00 04 00 00 00 0B 01 10 00 00 00 02 04 [Data]
      └─11字节─┘              └─4字节数据─┘
响应:00 04 00 00 00 06 01 10 00 00 00 02
      └─6字节(MBAP + 功能码 + 地址 + 数量)─┘

实现架构

整体架构

复制代码
┌─────────────────────────────────────────────────────────┐
│                    Presentation Layer                  │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │ ModbusRTUView│  │ ModbusTCPView│  │ SerialPort/TcpClient│ │
│  │   .xaml       │  │   .xaml       │  │   View        │ │
│  └──────────────┘  └──────────────┘  └──────────────┘ │
└─────────────────────────────────────────────────────────┘
                         ↓ Binding
┌─────────────────────────────────────────────────────────┐
│                   ViewModel Layer                        │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │ModbusRTUView  │  │ModbusTCPView  │  │ SerialPort/TcpClient    │ │
│  │   Model       │  │   Model       │  │   ViewModel   │ │
│  └──────────────┘  └──────────────┘  └──────────────┘ │
└─────────────────────────────────────────────────────────┘
                         ↓ 调用
┌─────────────────────────────────────────────────────────┐
│                    Business Logic Layer                   │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │  ModbusRTU   │  │  ModbusTCP   │  │   SerialBase │ │
│  │   (IModbusRW)│  │   (IModbusRW)│  │              │ │
│  │              │  │              │  │   TCPBase    │ │
│  └──────────────┘  └──────────────┘  └──────────────┘ │
└─────────────────────────────────────────────────────────┘
                         ↓ 通讯
┌─────────────────────────────────────────────────────────┐
│                  Hardware/Transport Layer                   │
│  ┌──────────────┐  ┌──────────────┐                     │
│  │  SerialPort  │  │   TcpClient  │                     │
│  │   (System.IO │  │  (System.Net │                     │
│  │  .Ports)      │  │   .Sockets)  │                     │
│  └──────────────┘  └──────────────┘                     │
└─────────────────────────────────────────────────────────┘

核心类设计

1. ModbusRTU 类
csharp 复制代码
public class ModbusRTU : SerialBase, IModbusRW
{
    /// <summary>
    /// 最后发送的完整报文
    /// </summary>
    public byte[] LastSendFrame { get; private set; }

    /// <summary>
    /// 最后接收的完整报文
    /// </summary>
    public byte[] LastReceiveFrame { get; private set; }

    // 读取操作
    public OperateResult<bool[]> ReadCoils(ushort start, ushort length, byte slaveId = 1)
    public OperateResult<bool[]> ReadInputs(ushort start, ushort length, byte slaveId = 1)
    public OperateResult<byte[]> ReadHoldingRegisters(ushort start, ushort length, byte slaveId = 1)
    public OperateResult<byte[]> ReadInputRegisters(ushort start, ushort length, byte slaveId = 1)

    // 写入操作
    public OperateResult WriteSingleCoil(ushort start, bool value, byte slaveId = 1)
    public OperateResult WriteSingleRegister(ushort start, byte[] value, byte slaveId = 1)
    public OperateResult WriteMultipleCoils(ushort start, bool[] values, byte slaveId = 1)
    public OperateResult WriteMultipleRegisters(ushort start, byte[] values, byte slaveId = 1)
}

关键实现要点:

  • 继承自 SerialBase,复用串口通讯逻辑
  • 实现 IModbusRW 接口,提供统一的 Modbus 操作接口
  • 使用 LastSendFrameLastReceiveFrame 保存完整报文
  • CRC16 校验算法确保数据完整性
2. ModbusTCP 类
csharp 复制代码
public class ModbusTCP : TCPBase, IModbusRW
{
    /// <summary>
    /// 默认的单元标识符
    /// </summary>
    public byte SlaveId { get; set; } = 0x01;

    /// <summary>
    /// 最后发送的完整报文
    /// </summary>
    public byte[] LastSendFrame { get; private set; }

    /// <summary>
    /// 最后接收的完整报文
    /// </summary>
    public byte[] LastReceiveFrame { get; private set; }

    // 事务ID管理(线程安全)
    private ushort transactionId = 0;
    public ushort TransactionId { get { ... } }

    // 读取和写入方法(与 RTU 相同接口)
}

关键实现要点:

  • 继承自 TCPBase,复用 TCP 通讯逻辑
  • MBAP 头构建(Transaction ID + Protocol ID + Length + Unit ID)
  • 事务ID自动递增,支持并发请求
  • 响应报文验证(长度、单元标识符)
3. SerialBase 和 TCPBase

SerialBase(串口基类):

csharp 复制代码
public class SerialBase
{
    private SerialPort serialPort = null;
    public int ReadTimeOut { get; set; } = 500;
    public int WriteTimeOut { get; set; } = 500;
    public int SleepTime { get; set; } = 1;
    public int ReceiveTimeOut { get; set; } = 100;

    public OperateResult Open(string portName, int baudRate, 
                               Parity parity, int dataBits, StopBits stopBits)
    public void Close()
    public OperateResult<byte[]> SendAndReceive(byte[] request)
}

TCPBase(网口基类):

csharp 复制代码
public class TCPBase
{
    private TcpClient tcpClient = null;
    public int MaxWaitTime { get; set; } = 100;

    public OperateResult Connect(string ipAddress, int port)
    public void DisConnect()
    public OperateResult<byte[]> SendAndReceive(byte[] request)
}

使用示例

示例 1:Modbus RTU 读取保持寄存器

csharp 复制代码
// 1. 创建 ModbusRTU 实例
ModbusRTU modbus = new ModbusRTU();

// 2. 打开串口
var openResult = modbus.Open("COM1", 9600, Parity.None, 8, StopBits.One);
if (!openResult.IsSuccess)
{
    Console.WriteLine($"串口打开失败:{openResult.Message}");
    return;
}

// 3. 读取保持寄存器(起始地址0,读取10个寄存器,从站地址1)
var result = modbus.ReadHoldingRegisters(0, 10, 1);

if (result.IsSuccess)
{
    // 4. 查看发送的完整报文
    Console.WriteLine($"发送报文:{BitConverter.ToString(modbus.LastSendFrame)}");
    
    // 5. 查看接收的完整报文
    Console.WriteLine($"接收报文:{BitConverter.ToString(modbus.LastReceiveFrame)}");
    
    // 6. 处理返回的数据(20字节)
    byte[] data = result.Content;
    
    // 7. 转换为具体数值(假设是 Int32,大端序)
    int value1 = BitConverter.ToInt16(data, 0);
    int value2 = BitConverter.ToInt16(data, 2);
    // ...
}
else
{
    Console.WriteLine($"读取失败:{result.Message}");
}

// 8. 关闭串口
modbus.Close();

示例 2:Modbus TCP 写入寄存器

csharp 复制代码
// 1. 创建 ModbusTCP 实例
ModbusTCP modbus = new ModbusTCP();

// 2. 连接到服务器
var connectResult = modbus.Connect("192.168.1.100", 502);
if (!connectResult.IsSuccess)
{
    Console.WriteLine($"连接失败:{connectResult.Message}");
    return;
}

// 3. 写入单个寄存器(地址100,值为0x1234)
byte[] value = new byte[] { 0x12, 0x34 };
var result = modbus.WriteSingleRegister(100, value, 1);

if (result.IsSuccess)
{
    // 4. 查看发送的完整报文
    Console.WriteLine($"发送报文:{BitConverter.ToString(modbus.LastSendFrame)}");
    
    // 5. 查看接收的完整报文
    Console.WriteLine($"接收报文:{BitConverter.ToString(modbus.LastReceiveFrame)}");
    
    Console.WriteLine("写入成功!");
}
else
{
    Console.WriteLine($"写入失败:{result.Message}");
}

// 6. 断开连接
modbus.DisConnect();

示例 3:读取寄存器位操作

csharp 复制代码
// Modbus RTU - 写入寄存器的某一位
string address = "100.5"; // 寄存器100,位5
bool bitValue = true;
var result = modbus.WriteRegisterBit(address, bitValue, isLittleEndian: false, slaveId: 1);

if (result.IsSuccess)
{
    Console.WriteLine("位操作成功!");
}

附录

A. 串口参数配置

参数 可选值 说明
端口名称 COM1-COM256 串口号
波特率 4800, 9600, 19200, 38400, 57600, 115200 数据传输速率
数据位 7, 8 数据字节长度
校验位 None, Odd, Even, Mark, Space 错误检测方式
停止位 1, 1.5, 2 数据包结束标志

B. Modbus 异常码

异常码 名称 说明
0x01 非法功能 收到的功能码不支持
0x02 非法数据地址 请求的地址超出允许范围
0x03 非法数据值 请求的值超出允许范围
0x04 从站设备故障 从站无法响应请求

C. 性能优化建议

  1. 批量读取

    • 尽可能一次读取多个寄存器,而不是逐个读取
    • 示例:使用 ReadHoldingRegisters(0, 10) 一次读10个,而不是循环10次读取1个
  2. 超时设置

    • RTU:根据设备响应速度调整 ReceiveTimeOut(默认100ms)
    • TCP:根据网络延迟调整 MaxWaitTime(默认100ms)
  3. 连接管理

    • TCP:对于频繁操作,保持长连接
    • RTU:操作完成后及时关闭串口
  4. 错误处理

    • 所有操作都应检查 IsSuccess 属性
    • 查看 Message 属性获取详细错误信息

总结

本文档详细介绍了串口通讯、网口通讯、Modbus RTU 和 Modbus TCP 协议的技术特点、报文格式和实现方法。


相关推荐
志栋智能3 小时前
运维超自动化:构建弹性IT架构的关键支撑
运维·服务器·网络·人工智能·架构·自动化
网安情报局10 小时前
除了 CDN,DDoS 攻击还有哪些更有效的防护方式?
网络
Promise微笑10 小时前
2026年国产替代油介损测试仪:油介损全场景解决方案与技术演进
大数据·网络·人工智能
AnalogElectronic13 小时前
linux 测试网络和端口是否连通的命令详解
linux·网络·php
Rust研习社14 小时前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust
灰子学技术14 小时前
Envoy HTTP 流量层面的 Metric 指标分析
网络·网络协议·http
上海云盾-小余14 小时前
海外恶意 UDP 攻击溯源:分层封禁策略与业务兼容平衡方案
网络·网络协议·udp
智慧光迅AINOPOL14 小时前
校园全光网建设指南:从架构到调优,打造稳定高体验校园网络
网络·全光网解决方案·全光网·酒店全光解决方案·泛住宿全光网解决方案
被摘下的星星14 小时前
Internet 的域名系统:从“名字”到“地址”的翻译官
网络