1. ModBus从入门到精通

1. Modbus协议基础知识

1.1 为什么要学习Modbus

  • 协议简单,对初学者友好
  • 工控领域应用广泛,学会后容易上手其他协议
  • 1979年诞生,由Modicon(莫迪康,后被施耐德收购)公司制定
  • 诞生原因:解决PLC控制器之间通信的问题
  • Mod + Bus = Modbus

1.2 Modbus协议特点

|----------|---------------------------------------------------------|
| 特点 | 说明 |
| 免费 | 开放协议,免费使用才能获取最大使用量 |
| 简单 | 帧格式简单紧凑,用户易理解,厂商易集成 |
| 接口灵活 | 应用层协议,不绑定物理层------可在串口(232/485/422)、以太网、光纤、蓝牙、无线等多种介质传输 |

关键认知:站在协议制定者的角度思考------协议的目的是数据传输,存储区是数据的载体,功能码是行为的代号。

1.3 Modbus调试软件

|-----------------|-----------------------------------|
| 软件 | 用途 |
| ModbusPoll | Modbus主站/客户端 |
| ModbusSlave | Modbus从站/服务器 |
| VSPD | Virtual Serial Port Driver,虚拟一对串口 |

1.4 Modbus存储区说明

存储区类型分为布尔(线圈) 和**数据(寄存器)**两大类:

|--------|-------|----|----|-----------------|
| 区号 | 名称 | 类型 | 读写 | 说明 |
| 0区 | 输出线圈 | 布尔 | 读写 | Output Coil |
| 1区 | 输入线圈 | 布尔 | 只读 | Input Coil |
| 3区 | 输入寄存器 | 数据 | 只读 | Input Register |
| 4区 | 输出寄存器 | 数据 | 读写 | Output Register |

  • Modbus规定每个存储区最大范围 65536
  • 对比各PLC地址表示:西门子MW100/DB1.DBD0,三菱D0/X0/Y0,欧姆龙D0/W0
地址模型
  • 绝对地址 = 区号 + 相对地址
  • Modbus绝对地址 = 区号 + (相对地址+1)
    • 例:输出寄存器第一个绝对地址 = 40001
  • 长地址模型 vs 短地址模型:数据量小用短地址,否则用长地址

人为交流/说明文档用绝对地址 ;协议报文用相对地址(功能码已隐含区号信息)

1.5 Modbus协议功能码

功能码 = 行为的代号:

|----------|---------|-----|
| 功能码 | 名称 | 说明 |
| 0x01 | 读取输出线圈 | 读0区 |
| 0x02 | 读取输入线圈 | 读1区 |
| 0x03 | 读取输出寄存器 | 读4区 |
| 0x04 | 读取输入寄存器 | 读3区 |
| 0x05 | 写入单个线圈 | 写0区 |
| 0x06 | 写入单个寄存器 | 写4区 |
| 0x0F | 写入多个线圈 | 写0区 |
| 0x10 | 写入多个寄存器 | 写4区 |

此外还有异常功能码和自定义功能码

1.6 Modbus协议分类

|-----------------|----------------|---------------|
| 分类 | 通信介质 | 说明 |
| ModbusRTU | 串口 232/485/422 | 二进制帧,紧凑高效 |
| ModbusASCII | 串口 232/485/422 | ASCII编码帧,可读性好 |
| ModbusTCP | 以太网 TCP/IP | 基于TCP,无CRC校验 |
| ModbusUDP | 以太网 UDP/IP | 基于UDP |

扩展组合:

  • ModbusRTU Over TCP / ModbusRTU Over UDP
  • ModbusASCII Over TCP / ModbusASCII Over UDP

2. ModbusRTU通信报文分析

2.1 通用报文帧格式

复制代码
| 从站地址(1B) | 功能码(1B) | 数据部分(NB) | CRC16(2B) |
  • 从站地址:和哪个设备通信
  • 功能码:要做什么
  • CRC16校验:保证数据准确,本质是一种算法(与串口奇/偶/无校验无关)
各操作的报文结构

|----------|----------------------------------------------|--------------------------------|
| 操作 | 发送报文 | 接收报文 |
| 读取 | 从站地址 + 功能码 + 开始地址 + 读取数量 + CRC | 从站地址 + 功能码 + 字节计数 + 数据 + CRC |
| 写入单个 | 从站地址 + 功能码 + 具体地址 + 写入数据 + CRC | 从站地址 + 功能码 + 具体地址 + 写入数据 + CRC |
| 写入多个 | 从站地址 + 功能码 + 开始地址 + 写入数量 + 字节计数 + 写入数据 + CRC | 从站地址 + 功能码 + 开始地址 + 写入数量 + CRC |

2.2 01H 读取输出线圈

读取1号站点从10开始的20个线圈

  • 发送:01 01 00 0A 00 14 1C 07
  • 接收:01 01 03 03 00 00 CC 4E

报文解析:

  • 01 从站地址 | 01 功能码 | 00 0A 起始地址10 | 00 14 数量20 | 1C 07 CRC
  • 返回:03 字节计数(3字节=24bit≥20) | 03 00 00 线圈数据

2.3 02H 读取输入线圈

读取5号站点从20开始的10个线圈

  • 发送:05 02 00 14 00 0A B9 8D
  • 接收:05 02 02 03 00 48 88

2.4 03H 读取输出寄存器

读取2号站点从10开始的4个寄存器

  • 发送:02 03 00 0A 00 04 64 38
  • 接收:02 03 08 00 01 00 02 00 03 00 04 02 50

报文解析:

  • 08 字节计数(4个寄存器×2字节=8字节)
  • 00 01 00 02 00 03 00 04 4个寄存器值

2.5 04H 读取输入寄存器

读取2号站点从10开始的4个寄存器

  • 发送:02 04 00 0A 00 04 D1 F8
  • 接收:02 04 08 00 01 00 02 00 03 00 04 B3 8A

2.6 05H 预置单线圈

将2号站点地址05置位

  • 发送:02 05 00 05 FF 00 9C 08
  • 接收:02 05 00 05 FF 00 9C 08(原样回显)

FF 00 = 置位(ON),00 00 = 复位(OFF)

2.7 06H 预置单寄存器

将5号站点地址10写入123(0x7B)

  • 发送:05 06 00 0A 00 7B E8 6F
  • 接收:05 06 00 0A 00 7B E8 6F(原样回显)

2.8 0FH 预置多线圈

将1号站点从1开始的5个线圈写入 True True False True False

  • 发送:01 0F 00 01 00 05 01 16 D3 58
  • 接收:01 0F 00 01 00 05 C4 08

5个线圈 = 1 1 0 1 0 → 位排列 00010110 = 0x16

2.9 10H 预置多寄存器

将1号站点从10开始的5个寄存器写入 01 02 03 04 05

  • 发送:01 10 00 0A 00 05 0A 00 01 00 02 00 03 00 04 00 05 E0 60
  • 接收:01 10 00 0A 00 05 E0 60

0A = 字节计数(5个寄存器×2字节=10字节)


3. ModbusRTU通信库开发

|------|------------------------------|
| 章节 | 内容 |
| 3.1 | 串口连接与断开 |
| 3.2 | 读取输入输出线圈 |
| 3.3 | 读取输入输出寄存器 |
| 3.4 | 预置单线圈与寄存器 |
| 3.5 | 预置多线圈与寄存器 |
| 3.6 | 测试平台UI界面设计 |
| 3.7 | 参数初始化及串口连接 |
| 3.8 | 输入输出线圈读取测试 |
| 3.9 | 输入输出寄存器读取测试 |
| 3.10 | 预置单线圈多线圈测试 |
| 3.11 | 预置单寄存器多寄存器测试 |
| 3.12 | Modbus通信库加锁处理(重要:防止并发冲突) |

4. 多路温湿度采集项目案例

|-----|-------------------------|
| 章节 | 内容 |
| 4.1 | 温湿度硬件接线及设置 |
| 4.2 | UI界面设计及搭建 |
| 4.3 | 实现单温湿度模块采集 |
| 4.4 | 实现多温湿度模块采集 |
| 4.5 | 使用自定义控件优化 |
| 4.6 | 基于OOP实现多路采集(面向对象封装) |

需求:后续需要自行开发

5. ModbusTCP通信报文分析

5.1 通信格式说明

MBAP报文头(7字节)
复制代码
| 事务标识(2B) | 协议标识(2B) | 长度(2B) | 单元标识(1B) | 功能码(1B) | 数据(NB) |

|---------|----|----------------------|
| 字段 | 长度 | 说明 |
| 事务处理标识符 | 2B | 报文ID,不参与运算,用于匹配请求/响应 |
| 协议标识符 | 2B | 固定 00 00 |
| 长度 | 2B | 长度字段之后所有字节数(N+2) |
| 单元标识符 | 1B | 相当于RTU中的从站地址 |

ModbusTCP vs RTU 对比

|---------|---------|--------------------------------|
| 对比项 | RTU | TCP |
| 校验 | CRC16校验 | 无应用层校验(TCP传输层已有校验) |
| 站地址 | 从站地址 | 单元标识符(弱化,IP已区分设备;可用可不用,默认1或忽略) |
| 报文头 | 无 | MBAP头7字节 |

5.2 读取输入输出线圈

读取1号站点从10开始的20个线圈

读取输入线圈(02H)

  • 发送:00 51 00 00 00 06 01 02 00 0A 00 14
  • 接收:00 51 00 00 00 06 01 02 03 03 00 00

读取输出线圈(01H)

  • 发送:00 51 00 00 00 06 01 01 00 0A 00 14
  • 接收:00 51 00 00 00 06 01 01 03 03 00 00

5.3 读取输入输出寄存器

读取1号站点从4开始的2个寄存器

读取输入寄存器(04H)

  • 发送:01 08 00 00 00 06 01 04 00 04 00 02
  • 接收:01 08 00 00 00 07 01 04 04 00 7B 01 59

读取输出寄存器(03H)

  • 发送:01 08 00 00 00 06 01 03 00 04 00 02
  • 接收:01 08 00 00 00 07 01 03 04 00 7B 01 59

5.4 预置单线圈与寄存器

预置单线圈(05H):将1号站点08线圈置位

  • 发送:00 00 00 00 00 06 01 05 00 08 FF 00
  • 接收:00 00 00 00 00 06 01 05 00 08 FF 00

预置单寄存器(06H):将1号站点08地址写入123(0x7B)

  • 发送:00 00 00 00 00 06 01 06 00 08 00 7B
  • 接收:00 00 00 00 00 06 01 06 00 08 00 7B

5.5 预置多线圈与寄存器

预置多线圈(0FH):将1号站点从0开始的6个线圈写入 1 1 0 0 1 1

  • 发送:07 90 00 00 00 08 01 0F 00 00 00 06 01 33
  • 接收:07 90 00 00 00 06 01 0F 00 00 00 06

预置多寄存器(10H):将1号站点从0开始的3个寄存器写入 12 34 56

  • 发送:08 B4 00 00 00 0D 01 10 00 00 00 03 06 00 0C 00 22 00 38
  • 接收:08 B4 00 00 00 06 01 10 00 00 00 03

6. ModbusTCP通信库编写

|-----|----------------|
| 章节 | 内容 |
| 6.1 | 以太网连接与断开 |
| 6.2 | ByteArray通用工具类 |
| 6.3 | 读取输入输出线圈 |
| 6.4 | 读取输入输出寄存器 |
| 6.5 | 预置单线圈与寄存器 |
| 6.6 | 预置多线圈与寄存器 |
| 6.7 | 测试平台UI界面及连接 |
| 6.8 | 线圈寄存器读取测试 |
| 6.9 | 预置线圈寄存器测试 |


7. ModbusTCP与西门子PLC通信

|-----|-------------------------------|
| 章节 | 内容 |
| 7.1 | 西门子PLC仿真环境搭建(PLCSIM-Advanced) |
| 7.2 | 编写ModbusServer程序 |
| 7.3 | 西门子PLC通信界面设计 |
| 7.4 | 常用数据类型及解析思路 |
| 7.5 | 多线程实现PLC数据解析 |
| 7.6 | 多线程实现解析并更新 |
| 7.7 | ModbusTCP变量写入 |

7.1 PLC仿真环境搭建要点

  1. 搭建PLCSIM-Advanced,设置IP地址(与PLCSIM一致)
  2. 根据情况设置允许PutGet
  3. 创建PLC程序,勾选"块编译时支持仿真"
  4. DB需要取消优化访问(Optimized Block Access)

7.4 Tag变量解析格式

Tag由三部分组成,用分号分割:

复制代码
VarType;Start;OffsetOrLength

例:INT;0;2 表示从地址0开始的INT类型,长度2


速查表

RTU报文快速对照

|-----|--------|--------------------------|-------------------|
| 功能码 | 操作 | 发送报文结构 | 接收报文结构 |
| 01 | 读输出线圈 | 站址+01+起始地址+数量+CRC | 站址+01+字节数+数据+CRC |
| 02 | 读输入线圈 | 站址+02+起始地址+数量+CRC | 站址+02+字节数+数据+CRC |
| 03 | 读输出寄存器 | 站址+03+起始地址+数量+CRC | 站址+03+字节数+数据+CRC |
| 04 | 读输入寄存器 | 站址+04+起始地址+数量+CRC | 站址+04+字节数+数据+CRC |
| 05 | 写单线圈 | 站址+05+地址+FF00/0000+CRC | 原样回显 |
| 06 | 写单寄存器 | 站址+06+地址+值+CRC | 原样回显 |
| 0F | 写多线圈 | 站址+0F+起始地址+数量+字节数+数据+CRC | 站址+0F+起始地址+数量+CRC |
| 10 | 写多寄存器 | 站址+10+起始地址+数量+字节数+数据+CRC | 站址+10+起始地址+数量+CRC |

TCP vs RTU 快速对比

|------|-----------|--------------|
| 对比项 | RTU | TCP |
| 报文头 | 无 | MBAP头(7B) |
| 校验 | CRC16(2B) | 无 |
| 站地址 | 从站地址(1B) | 单元标识(1B,可忽略) |
| 典型场景 | 串口通信 | 以太网通信 |


8. NModbus4 --- C# Modbus通信库

8.1 为什么选NModbus4

|-----------|--------------------------------------------------------------------------------------------------------------------------------------------|
| 优势 | 说明 |
| 开源免费 | MIT协议,GitHub: https://github.com/frede-bundy/nmodbus4 |
| 协议全覆盖 | 支持 RTU / TCP / ASCII / UDP |
| 跨平台 | .NET Framework 4.5+、.NET Core / .NET 5+,Windows/Linux/macOS |
| 异步支持 | 完整async/await接口,不阻塞UI线程 |
| 社区活跃 | 目前最稳定的社区维护分支,修复了老版本内存泄漏和并发问题 |

核心价值:手写CRC/帧拼接/解析极易出错(字节序、位操作、异常帧处理),NModbus4封装了所有底层细节,只留干净的API。

8.2 安装

复制代码
dotnet add package NModbus4
# 或 NuGet包管理器搜索 NModbus4

8.3 核心命名空间

复制代码
using Modbus.Device;   // 核心设备类(Master/Slave)
using Modbus.Data;     // 数据存储(寄存器、线圈等)
using System.IO.Ports; // RTU/ASCII模式需要(串口操作)
using System.Net.Sockets; // TCP模式需要

8.4 核心概念速查

|----------|--------------------------|---------------|--------|----|
| 存储区 | NModbus4 API名称 | 地址范围 | 类型 | 读写 |
| 0区 输出线圈 | Coils | 0x0000-0xFFFF | bool | 读写 |
| 1区 输入线圈 | Discrete Inputs / Inputs | 0x0000-0xFFFF | bool | 只读 |
| 3区 输入寄存器 | Input Registers | 0x0000-0xFFFF | ushort | 只读 |
| 4区 输出寄存器 | Holding Registers | 0x0000-0xFFFF | ushort | 读写 |

⚠️ 地址注意 :NModbus4默认使用0起始地址,与多数工业设备一致。例如40001对应代码中地址0。开发时需避免地址偏移错误。

8.5 Modbus TCP模式

适用场景:远程PLC、物联网网关、跨网络设备,需知道设备IP和Modbus端口(默认502)。

复制代码
// 1. 创建TCP连接
using (TcpClient tcpClient = new TcpClient())
{
    tcpClient.Connect("192.168.1.100", 502); // 设备IP + Modbus端口

    // 2. 创建Master
    IModbusMaster master = ModbusIpMaster.CreateIp(tcpClient);
    master.Transport.ReadTimeout = 1000;  // 读取超时1秒
    master.Transport.WriteTimeout = 1000; // 写入超时1秒

    byte slaveId = 1; // 从站地址,多数设备默认1

    // 3. 读取输入寄存器(如温度,地址0,读1个)
    ushort[] inputRegs = master.ReadInputRegisters(slaveId, 0, 1);
    float temperature = inputRegs[0] / 10.0f; // 多数设备16位整型存小数
    Console.WriteLine($"当前温度:{temperature}℃");

    // 4. 写入保持寄存器(如变频器频率,地址1,写50Hz)
    master.WriteSingleRegister(slaveId, 1, 50);

    // 5. 读取线圈(如设备运行状态,地址0,读1个)
    bool[] coils = master.ReadCoils(slaveId, 0, 1);
    Console.WriteLine($"设备状态:{(coils[0] ? "运行" : "停止")}");

    // 6. 写入线圈(控制启动,地址0,置true)
    master.WriteSingleCoil(slaveId, 0, true);
}

8.6 Modbus RTU模式

适用场景:车间PLC、传感器、变频器等短距离设备,RS232/RS485串口连接。

复制代码
// 1. 配置串口参数(必须与设备一致,否则通信失败)
using (SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One))
{
    serialPort.Open();

    // 2. 创建RTU Master
    IModbusMaster master = ModbusSerialMaster.CreateRtu(serialPort);
    master.Transport.ReadTimeout = 1000;
    master.Transport.WriteTimeout = 1000;

    byte slaveId = 1;

    // 3. 读写操作(API与TCP几乎一致)
    ushort[] holdingRegs = master.ReadHoldingRegisters(slaveId, 0, 10);
    master.WriteSingleRegister(slaveId, 0, 1234);

    // ⚠️ RTU操作间加延迟,让总线稳定,避免通信冲突
    Thread.Sleep(10);
}

关键点 :RTU是主从轮询模式,总线上同一时间只能有一个主站发指令,操作之间加 Task.Delay(10) 避免冲突。

8.7 异步API(推荐)

复制代码
// TCP异步连接与读写
var tcpClient = new TcpClient();
await tcpClient.ConnectAsync("192.168.1.100", 502);
var master = ModbusIpMaster.CreateIp(tcpClient);

// 异步读取
ushort[] regs = await master.ReadHoldingRegistersAsync(slaveId: 1, startAddress: 0, numberOfRegisters: 10);

// 异步写入
await master.WriteSingleRegisterAsync(slaveId: 1, registerAddress: 0, value: 1234);

8.8 常用方法速查表

复制代码
// ===== 读 =====
ReadCoilsAsync(slaveId, start, count)                → bool[]
ReadInputsAsync(slaveId, start, count)               → bool[]
ReadHoldingRegistersAsync(slaveId, start, count)     → ushort[]
ReadInputRegistersAsync(slaveId, start, count)       → ushort[]

// ===== 写单个 =====
WriteSingleCoilAsync(slaveId, address, value)        → void
WriteSingleRegisterAsync(slaveId, address, value)    → void

// ===== 写多个 =====
WriteMultipleCoilsAsync(slaveId, start, values)      → void
WriteMultipleRegistersAsync(slaveId, start, values)  → void

// ===== 高级(很少用)=====
ReadWriteMultipleRegistersAsync(...)                  // 读写组合
MaskWriteRegisterAsync(...)                           // 位掩码写

同步版本去掉 Async 后缀即可,如 ReadHoldingRegisters()ushort[]

8.9 工业级封装(RTU + TCP 统一客户端)

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

public class IndustrialModbusClient : IDisposable
{
    private ModbusMaster _master;
    private SerialPort _serialPort;
    private TcpClient _tcpClient;
    private readonly string _target;
    private readonly bool _isTcp;

    public IndustrialModbusClient(string target, bool isTcp = false,
        int baudRate = 9600, Parity parity = Parity.None)
    {
        _target = target;
        _isTcp = isTcp;

        if (!_isTcp)
        {
            _serialPort = new SerialPort(target, baudRate, parity, 8, StopBits.One)
            {
                ReadTimeout = 1000,
                WriteTimeout = 1000,
                DtrEnable = true,
                RtsEnable = true
            };
        }
    }

    public async Task<bool> ConnectAsync()
    {
        try
        {
            if (_isTcp)
            {
                _tcpClient = new TcpClient();
                await _tcpClient.ConnectAsync(_target, 502);
                _master = ModbusIpMaster.CreateIp(_tcpClient);
            }
            else
            {
                if (!_serialPort.IsOpen)
                {
                    _serialPort.Open();
                    await Task.Delay(100); // 等待串口稳定
                }
                _master = ModbusSerialMaster.CreateRtu(_serialPort);
            }
            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"连接失败: {ex.Message}");
            return false;
        }
    }

    // 统一读写接口
    public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort start, ushort count)
    {
        await EnsureConnectedAsync();
        return await _master.ReadHoldingRegistersAsync(slaveId, start, count);
    }

    public async Task WriteSingleRegisterAsync(byte slaveId, ushort address, ushort value)
    {
        await EnsureConnectedAsync();
        await _master.WriteSingleRegisterAsync(slaveId, address, value);
    }

    private async Task EnsureConnectedAsync()
    {
        if ((_isTcp && (_tcpClient == null || !_tcpClient.Connected)) ||
            (!_isTcp && (_serialPort == null || !_serialPort.IsOpen)))
        {
            await ConnectAsync();
        }
    }

    public void Dispose()
    {
        _master?.Dispose();
        _serialPort?.Close();
        _serialPort?.Dispose();
        _tcpClient?.Close();
        _tcpClient?.Dispose();
    }
}

使用示例

复制代码
// RTU模式
var client = new IndustrialModbusClient("COM3", isTcp: false, baudRate: 19200);
await client.ConnectAsync();
ushort[] values = await client.ReadHoldingRegistersAsync(1, 0, 5);

// TCP模式
var client = new IndustrialModbusClient("192.168.1.100", isTcp: true);
await client.ConnectAsync();
ushort[] values = await client.ReadHoldingRegistersAsync(1, 0, 5);

8.10 定时轮询(数据采集核心模式)

复制代码
using System.Timers;

// 每秒轮询一次
var pollTimer = new Timer(1000);
pollTimer.AutoReset = true;
pollTimer.Elapsed += async (sender, e) => await PollRegistersAsync();
pollTimer.Start();

async Task PollRegistersAsync()
{
    try
    {
        ushort[] data = await master.ReadHoldingRegistersAsync(1, 0, 4);
        // 更新UI(WinForms需用Invoke)
        Console.WriteLine($"寄存器值: {string.Join(", ", data)}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"轮询异常: {ex.Message}");
    }
}

轮询防坑

  • System.Timers.Timer(线程池线程),不要用WinForms Timer(UI线程会卡)
  • 加异常兜底,工业现场网络和硬件不稳定是常态
  • 防止重入:上次还没读完新的请求又来 → 用 lock_isPolling 标志

8.11 多从站组网(RS485典型场景)

复制代码
public class MultiSlaveModbusManager
{
    private readonly IndustrialModbusClient _client;
    private readonly byte[] _slaveIds = { 1, 2, 3, 4 };

    public MultiSlaveModbusManager(IndustrialModbusClient client) => _client = client;

    public async Task<Dictionary<byte, ushort[]>> ReadMultiSlavesAsync(ushort start, ushort count)
    {
        var results = new Dictionary<byte, ushort[]>();
        foreach (byte slave in _slaveIds)
        {
            try
            {
                var data = await _client.ReadHoldingRegistersAsync(slave, start, count);
                results[slave] = data;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"从站{slave}读取失败: {ex.Message}");
                // 跳过失败的从站,继续读下一个
            }
        }
        return results;
    }
}

8.12 Modbus TCP从站(Slave/Server)

复制代码
// 创建从站,地址1,监听502端口
var slave = ModbusTcpSlave.CreateTcp(1, IPAddress.Any, 502);

// 初始化数据存储
slave.DataStore = DataStoreFactory.CreateDefaultDataStore();

// 注册写入事件(主站写寄存器时触发)
slave.DataStore.DataStoreWrittenTo += (sender, e) =>
{
    ushort val = e.Data.Registers[0];
    Console.WriteLine($"主站写入地址{e.StartAddress},值={val}");
};

// 后台线程监听(Listen是阻塞方法,必须放Task.Run)
Task.Run(() => slave.Listen());

// 手动设置寄存器值
slave.DataStore.HoldingRegisters[1] = 100; // 地址0=值100

8.13 常见踩坑与解决方案

|------------|---------------------|--------------------------------------------|
| 问题 | 原因 | 解决 |
| RTU通信乱码 | 串口参数(波特率/校验位)与设备不一致 | 严格对照设备手册配置 |
| TCP连接慢/超时 | 目标IP不存在时等几十秒 | 先Ping或用CancellationToken设超时 |
| 数据大小端错误 | Modbus大端 vs Intel小端 | NModbus已处理;自己转float/int时用BitConverter注意字节序 |
| 跨线程UI更新 | 后台线程直接操作控件抛异常 | WinForms用Invoke,WPF用Dispatcher.Invoke |
| Linux串口打不开 | 权限不足 | sudo usermod -a -G dialout $USER,重新登录 |
| 多任务并发冲突 | 同时发多个Modbus请求 | 加锁(lockSemaphoreSlim),RTU总线同一时间只能一个请求 |
| 端口耗尽 | 频繁创建TcpClient未释放 | 用using确保释放,或复用连接 |

9.思维导图

modbus入门到精通.xmind

相关推荐
爱讲故事的1 小时前
计算机网络第六讲复习博客:链路层与局域网
网络·计算机网络·智能路由器
luweis1 小时前
企智孪生 ETA (3.5 执行层技术落地)【浙江联保网络 卢伟舜】
网络·人工智能·程序人生·职场和发展·学习方法
jiguang1271 小时前
Windows11安装eNSP华为网络仿真工具平台
网络·华为
code monkey.3 小时前
【Linux之旅】Linux 应用层自定义协议与序列化:从粘包问题到网络计算器
linux·网络·c++
2401_892423363 小时前
OSPF笔记
网络·智能路由器
草莓熊Lotso3 小时前
【Linux网络】深入理解 HTTP 协议(二):从协议格式到手写工业级 HTTP 服务器
linux·运维·服务器·网络·c++·http
The Straggling Crow9 小时前
Network
网络
yyuuuzz10 小时前
独立站的技术基础与常见运维问题
大数据·运维·服务器·网络·数据库·aws
Oll Correct12 小时前
实验二十九:TCP的运输连接管理
网络·笔记