基于C#和NModbus4库实现的Modbus RTU串口通信

基于C#和NModbus4库实现的Modbus RTU串口通信,包含完整的界面设计和功能实现:


一、项目依赖配置

  1. NuGet包安装

    powershell 复制代码
    Install-Package NModbus4
    Install-Package System.IO.Ports
  2. 窗体控件布局

    xml 复制代码
    <!-- 基础控件配置 -->
    <ComboBox x:Name="cmbPort" Margin="5" Width="120"/>
    <Button x:Name="btnConnect" Content="连接" Margin="5"/>
    <Button x:Name="btnRead" Content="读取寄存器" Margin="5"/>
    <TextBox x:Name="txtLog" Height="200" Margin="5" TextWrapping="Wrap"/>
    <DataGrid x:Name="dgData" AutoGenerateColumns="False" Margin="5">
        <DataGrid.Columns>
            <DataGridTextColumn Header="地址" Binding="{Binding Address}"/>
            <DataGridTextColumn Header="值" Binding="{Binding Value}"/>
        </DataGrid.Columns>
    </DataGrid>

二、核心代码实现

csharp 复制代码
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Windows;
using Modbus.Device;

namespace ModbusRTUDemo
{
    public partial class MainWindow : Window
    {
        #region 成员变量
        private IModbusSerialMaster _master;
        private SerialPort _serialPort;
        private const ushort START_ADDR = 40001; // 起始寄存器地址
        private const ushort READ_COUNT = 10;    // 读取数量
        #endregion

        public MainWindow()
        {
            InitializeComponent();
            InitializeSerialPorts();
            btnConnect.Click += BtnConnect_Click;
            btnRead.Click += BtnRead_Click;
        }

        #region 串口初始化
        private void InitializeSerialPorts()
        {
            var ports = SerialPort.GetPortNames();
            cmbPort.ItemsSource = ports;
            cmbPort.SelectedIndex = 0;
        }
        #endregion

        #region 连接控制
        private void BtnConnect_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (_master != null && _master.IsOpen)
                {
                    Disconnect();
                    btnConnect.Content = "连接";
                    txtLog.AppendText("已断开连接\n");
                    return;
                }

                _serialPort = new SerialPort(cmbPort.Text, 9600, Parity.None, 8, StopBits.One)
                {
                    ReadTimeout = 3000,
                    WriteTimeout = 3000
                };

                _master = ModbusSerialMaster.CreateRtu(_serialPort);
                _master.Transport.Retries = 3; // 重试次数
                _master.Transport.WriteTimeout = 2000;
                _master.Transport.ReadTimeout = 2000;

                _master.Open();
                btnConnect.Content = "断开";
                txtLog.AppendText($"已连接到 {_serialPort.PortName}\n");
            }
            catch (Exception ex)
            {
                txtLog.AppendText($"连接失败: {ex.Message}\n");
            }
        }
        #endregion

        #region 数据读取
        private async void BtnRead_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var result = await Task.Run(() => 
                    _master.ReadHoldingRegisters(1, START_ADDR, READ_COUNT));

                dgData.ItemsSource = result.Select((value, index) => new 
                {
                    Address = START_ADDR + index,
                    Value = value.ToString("0.00")
                }).ToList();
                
                txtLog.AppendText($"读取成功: {result.Length} 个寄存器\n");
            }
            catch (Exception ex)
            {
                txtLog.AppendText($"读取失败: {ex.Message}\n");
            }
        }
        #endregion

        #region 连接管理
        private void Disconnect()
        {
            _master?.Close();
            _master = null;
            _serialPort?.Close();
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
            Disconnect();
        }
        #endregion
    }
}

参考代码 C# 写的串口通信程序源码 youwenfan.com/contentcsb/111840.html

三、关键功能说明

  1. 串口配置
    • 支持自动检测可用串口(通过SerialPort.GetPortNames()
    • 默认参数:9600波特率、无校验、8数据位、1停止位
  2. Modbus操作
    • 读取保持寄存器ReadHoldingRegisters方法实现
    • 写单个寄存器 :扩展方法WriteSingleRegister
    • 批量写线圈WriteMultipleCoils方法
  3. 异常处理
    • 自动重试机制(默认3次重试)
    • 超时设置(读写各2秒)

四、扩展功能实现

  1. 定时数据采集

    csharp 复制代码
    private System.Timers.Timer _pollTimer = new System.Timers.Timer(5000);
    
    private void StartPolling()
    {
        _pollTimer.Elapsed += (s,e) => 
        {
            var data = _master.ReadHoldingRegisters(1, START_ADDR, READ_COUNT);
            Dispatcher.Invoke(() => UpdateDataGrid(data));
        };
        _pollTimer.Start();
    }
  2. CRC校验实现

    csharp 复制代码
    private byte[] CalculateCRC(byte[] data)
    {
        ushort crc = 0xFFFF;
        for (int i = 0; i < data.Length; i++)
        {
            crc ^= (ushort)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, (byte)(crc >> 8) };
    }

五、调试技巧

  1. 串口监控

    使用虚拟串口工具(如VSPD)进行通信调试

  2. 数据验证

    csharp 复制代码
    // 校验从站响应
    if (response.SlaveId != slaveId) 
        throw new InvalidDataException("从站ID不匹配");
  3. 协议分析

    通过Wireshark抓包分析Modbus RTU帧结构


完整项目源码可通过NuGet部署NModbus4库后导入Visual Studio运行。实际应用中需根据设备手册调整功能码和寄存器地址。