C# WinForm NModbus 串口通信+WinForm Modbus4 串口通信

一、项目基础说明

1.1 项目依赖

本项目基于 NModbus 库实现 Modbus-RTU 串口通信,需通过 NuGet 安装两个核心包:

  • NModbus:Modbus 协议核心实现库,提供主站/从站通信接口

  • NModbus.Serial:适配串口通信的扩展库,依赖 System.IO.Ports 实现串口交互

1.2 项目核心功能

  • 串口初始化、打开/关闭控制

  • Modbus-RTU 主站功能:同步/异步读写保持寄存器、线圈、输入寄存器

  • 单寄存器/多寄存器写入、线圈状态读取等核心 Modbus 操作

1.3 Modbus 核心理论(前置知识点)

1.3.1 主从架构

本项目中,WinForm 程序为Modbus 主站(Master) ,主动发起通信请求;下位机(PLC、传感器等)为从站(Slave),被动响应主站指令,遵循"一问一答"通信规则。

1.3.2 核心寄存器类型

  • 保持寄存器(Holding Registers):可读写,存储设备状态、参数等数据,是最常用的寄存器类型

  • 输入寄存器(Input Registers):只读,存储设备实时采集数据(如温度、压力)

  • 线圈(Coils):可读写的布尔类型寄存器,用于控制设备开关状态(ON/OFF)

1.3.3 关键参数说明

  • 从站地址(Slave ID):区分不同从站设备,本项目默认使用 1

  • 寄存器起始地址:指定读写操作的起始位置

  • 读写数量:指定一次操作的寄存器/线圈个数


二、核心代码逐行解析

2.1 命名空间引用

复制代码
using NModbus;
using NModbus.Serial;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

解析:引入 NModbus 相关核心命名空间,以及 WinForm、串口操作所需的系统命名空间,为后续功能实现提供基础依赖。

2.2 窗体类与全局变量定义

复制代码
namespace _1Nmodbus
{
    public partial class Form1 : Form
    {
        // 串口对象:负责底层串口通信,NModbus 依赖该对象实现数据传输
        public SerialPort port; 
        // Modbus 主站接口对象:NModbus 提供的核心通信接口,需绑定 SerialPort
        public IModbusMaster _masterModbus; 

        // 串口默认参数(可根据实际设备调整)
        public string portName = "COM1";       // 串口号
        public int botelv = 9600;              // 波特率
        public int dataBit = 8;                // 数据位
        public StopBits stopbit = StopBits.One;// 停止位
        public Parity jiou = Parity.None;      // 奇偶校验
        public int timeout = 10000;            // 读写超时时间(毫秒)
    }
}

解析:定义全局变量,封装串口参数与 Modbus 主站对象,便于后续方法调用与参数修改,统一管理通信核心资源。

2.3 构造函数与初始化方法

复制代码
public Form1()
{
    InitializeComponent();
    InitPort(); // 初始化串口与 Modbus 主站
}

// 初始化串口与 Modbus 主站
public void InitPort()
{
    // 1. 初始化串口对象,配置基础参数
    port = new SerialPort(portName)
    { 
        BaudRate = botelv,
        StopBits = stopbit,
        Parity = jiou,
        DataBits = dataBit
    };

    // 2. 创建 Modbus 工厂对象,用于生成主站实例
    ModbusFactory fac = new ModbusFactory();
    // 3. 创建 Modbus-RTU 主站,绑定串口对象
    _masterModbus = fac.CreateRtuMaster(port);

    // 4. 配置读写超时时间,避免通信阻塞
    _masterModbus.Transport.ReadTimeout = timeout; 
    _masterModbus.Transport.WriteTimeout = timeout;
}

解析:构造函数中调用 InitPort 方法,完成串口参数配置与 Modbus 主站实例化,核心逻辑是"串口初始化→工厂创建主站→绑定串口→配置超时",为后续通信操作做好准备。

2.4 串口打开/关闭功能

复制代码
// 打开串口按钮
private void button1_Click(object sender, EventArgs e)
{
    try
    {
        if (!port.IsOpen)
        {
            port.Open();// 打开串口
            MessageBox.Show("打开串口成功");
        }
    }
    catch (Exception)
    {
        MessageBox.Show("打开串口失败");
    }
}

// 关闭串口按钮
private void button2_Click(object sender, EventArgs e)
{
    if (port.IsOpen)
    {
        port.Close();// 关闭串口,释放资源
    }
}

解析:通过 SerialPort 的 IsOpen 属性判断串口状态,避免重复打开;打开失败时捕获异常并提示,关闭时先判断状态再执行操作,保证资源合理释放。

2.5 保持寄存器读写操作

2.5.1 同步读取保持寄存器

复制代码
private void button3_Click(object sender, EventArgs e)
{
    try
    {
        if (!port.IsOpen)
        {
            MessageBox.Show("串口未打开");
            return;
        }
        // 参数说明:从站地址、起始寄存器地址、读取个数
        ushort[] bs = _masterModbus.ReadHoldingRegisters(1, 0, 4); 
        label1.Text = bs[2].ToString(); // 显示第3个寄存器(索引从0开始)的值
    }
    catch (Exception)
    {
        MessageBox.Show("读取数据失败");
    }
}

解析:ReadHoldingRegisters 是同步读取方法,直接返回 ushort 数组(寄存器值为无符号16位整数);读取前需校验串口状态,避免无效操作,异常捕获保证程序稳定性。

2.5.2 异步读取保持寄存器

复制代码
private async void button4_Click(object sender, EventArgs e)
{
    // 异步读取,避免阻塞 UI 线程
    ushort[] bs = await _masterModbus.ReadHoldingRegistersAsync(1, 0, 4); 
    foreach (ushort s in bs)
    {
        Console.WriteLine(s); // 打印读取结果
    }
}

解析:ReadHoldingRegistersAsync 是异步读取方法,配合 async/await 关键字,避免读取过程中 UI 卡顿,适合大量数据读取场景。

2.5.3 写入保持寄存器

复制代码
private void button5_Click(object sender, EventArgs e)
{
    // 写入多个寄存器:起始地址30,写入数据为100、200、300、400
    ushort[] data = new ushort[] { 100, 200, 300, 400 }; 
    _masterModbus.WriteMultipleRegisters(1, 30, data);
}

// 异步写入单个寄存器
private void button6_Click(object sender, EventArgs e)
{
    // 异步写入:从站地址1,寄存器地址11,写入值100
    _masterModbus.WriteSingleRegisterAsync(1, 11, 100);
}

解析:WriteMultipleRegisters 用于批量写入多个寄存器,WriteSingleRegisterAsync 用于异步写入单个寄存器,参数需匹配从站地址、寄存器地址与写入值,数据类型为 ushort。

2.6 其他寄存器操作

2.6.1 读取线圈状态

复制代码
private void button7_Click(object sender, EventArgs e)
{
    // 读取线圈:从站地址1,起始线圈地址0,读取4个线圈状态
    bool[] status = _masterModbus.ReadCoils(1, 0, 4);
    foreach (var item in status)
    {
        Console.WriteLine(item); // 打印线圈状态(true/false)
    }
}

解析:ReadCoils 方法读取线圈状态,返回 bool 数组,true 表示线圈闭合(ON),false 表示线圈断开(OFF),适用于设备开关控制场景。

2.6.2 读取输入寄存器

复制代码
private void button8_Click(object sender, EventArgs e)
{
    // 读取输入寄存器:从站地址1,起始地址2,读取2个寄存器
    ushort[] bs = _masterModbus.ReadInputRegisters(1, 2, 2);
    foreach (var item in bs)
    {
        Console.WriteLine(item);
    }
}

解析:ReadInputRegisters 方法读取只读的输入寄存器,返回 ushort 数组,适用于读取设备实时采集的传感器数据。


三、核心易错点与注意事项

3.1 串口通信易错点

  • 串口参数(波特率、数据位、停止位、奇偶校验)必须与下位机完全一致,否则无法通信

  • 操作串口前必须校验 IsOpen 属性,避免重复打开或关闭导致异常

  • 超时时间需合理设置,避免通信阻塞,建议根据设备响应速度调整(本项目设为10000毫秒)

3.2 NModbus 操作易错点

  • 寄存器地址、读写数量需匹配下位机设备配置,避免地址越界或数量错误

  • 保持寄存器、输入寄存器、线圈的读写权限不同,不可混用读写方法(如输入寄存器不可写入)

  • 异步方法需配合 async/await 使用,避免线程阻塞,UI 操作需通过 Invoke 跨线程执行(本项目未涉及复杂 UI 操作,可自行补充)

  • 数据类型需统一,寄存器值为 ushort(无符号16位整数),不可直接传入 int 等其他类型

3.3 资源释放注意事项

程序关闭时,需手动关闭串口(port.Close()),释放 Modbus 主站资源,避免资源泄漏;可在 FormClosing 事件中添加资源释放逻辑。

WinForm Modbus4 串口通信

一、项目基础说明

1.1 项目依赖

本项目基于 Modbus4 库实现 Modbus-RTU 串口通信,需通过 NuGet 安装核心包:Modbus.Device,该包封装了 Modbus 协议核心逻辑,依赖 System.IO.Ports 实现串口底层通信。

1.2 项目核心功能

  • 快速初始化串口,配置 Modbus-RTU 主站

  • 通过两个按钮分别绑定不同串口号(COM1、COM6),实现保持寄存器同步读取

  • 读取完成后自动关闭串口,释放资源,打印读取结果

1.3 Modbus 核心理论(前置知识点)

1.3.1 主从架构

本项目中,WinForm 程序为Modbus 主站(Master) ,主动发起读取请求;下位机(PLC、传感器等)为从站(Slave),被动响应主站指令,遵循"一问一答"通信规则。

1.3.2 核心参数说明

  • 串口号(PortName):指定通信所用串口,本项目分别使用 COM1、COM6

  • 波特率(BaudRate):数据传输速率,本项目固定为9600,需与下位机一致

  • 数据位、停止位、奇偶校验:串口通信核心参数,本项目配置为8位数据位、1位停止位、无校验,需与下位机完全匹配

  • 从站地址(Slave ID):区分不同从站设备,本项目默认使用1

  • 寄存器起始地址、读取数量:指定读取保持寄存器的起始位置与个数,本项目从地址0开始读取4个寄存器

1.3.3 保持寄存器(Holding Registers)

可读写的16位寄存器,用于存储设备参数、状态等数据,是 Modbus 通信中最常用的寄存器类型,本项目核心操作对象即为保持寄存器。


二、核心代码逐行解析

2.1 命名空间引用

复制代码
using Modbus.Device;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

解析:引入 Modbus.Device 核心命名空间,以及 WinForm、串口操作所需的系统命名空间,为后续串口初始化、Modbus 主站创建、寄存器读取提供基础依赖。

2.2 窗体类与构造函数

复制代码
namespace _2NModbus4
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

解析:定义项目命名空间与窗体类,构造函数中仅执行窗体初始化操作,无额外业务逻辑,核心功能通过按钮点击事件实现。

2.3 按钮1点击事件(COM1 串口读取)

复制代码
private void button1_Click(object sender, EventArgs e)
{
    // 1. 初始化串口对象,配置基础参数(COM1、9600波特率、无校验、8数据位、1停止位)
    SerialPort port = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
    // 2. 配置串口读写超时时间(1000毫秒),避免通信阻塞
    port.ReadTimeout = 1000;
    port.WriteTimeout = 1000;
    // 3. 创建 Modbus-RTU 主站实例,绑定初始化后的串口
    IModbusMaster master = ModbusSerialMaster.CreateRtu(port);
    // 4. 校验串口状态,未打开则打开串口
    if (!port.IsOpen)
    {
        port.Open();
    }
    // 5. 同步读取保持寄存器:从站地址1,起始地址0,读取4个寄存器
    ushort[] bs = master.ReadHoldingRegisters(1, 0, 4);
    // 6. 读取完成后关闭串口,释放资源
    port.Close();
    // 7. 遍历读取结果,打印至控制台
    foreach (var item in bs)
    {
        Console.WriteLine(item);
    }
}

解析:该方法实现 COM1 串口的 Modbus-RTU 主站读取功能,流程为"串口初始化→创建主站→打开串口→读取寄存器→关闭串口→打印结果",每一步均遵循 Modbus 通信规范,保证操作有序、资源合理释放。

2.4 按钮2点击事件(COM6 串口读取)

复制代码
private void button2_Click(object sender, EventArgs e)
{
    // 1. 初始化串口对象,绑定 COM6 串口,其余参数与按钮1一致
    SerialPort port = new SerialPort("COM6", 9600, Parity.None, 8, StopBits.One);
    // 2. 配置读写超时时间
    port.ReadTimeout = 1000;
    port.WriteTimeout = 1000;
    // 3. 创建 Modbus-RTU 主站实例
    IModbusMaster master = ModbusSerialMaster.CreateRtu(port);
    // 4. 打开串口
    if (!port.IsOpen)
    {
        port.Open();
    }
    // 5. 读取保持寄存器(参数与按钮1一致)
    ushort[] bs = master.ReadHoldingRegisters(1, 0, 4);
    // 6. 关闭串口
    port.Close();
    // 7. 打印读取结果
    foreach (var item in bs)
    {
        Console.WriteLine(item);
    }
}

解析:该方法与按钮1逻辑完全一致,仅将串口号改为 COM6,实现多串口切换读取功能,代码复用性强,核心逻辑统一,便于后续维护与扩展。


三、核心易错点与优化建议

3.1 现有代码易错点

  • 未添加异常捕获:若串口不存在、下位机未连接或通信失败,程序会直接崩溃,需补充 try-catch 语句捕获异常

  • 资源释放不彻底:若读取过程中出现异常,串口可能无法正常关闭,导致资源泄漏,需结合 finally 语句保证串口关闭

  • 参数硬编码:串口号、波特率、从站地址等参数直接写在代码中,修改不便,建议封装为变量或通过界面配置

3.2 优化建议(可直接复用)

复制代码
// 优化后按钮1点击事件(添加异常捕获与资源释放)
private void button1_Click(object sender, EventArgs e)
{
    SerialPort port = null;
    try
    {
        port = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
        port.ReadTimeout = 1000;
        port.WriteTimeout = 1000;
        IModbusMaster master = ModbusSerialMaster.CreateRtu(port);
        
        if (!port.IsOpen)
        {
            port.Open();
        }
        
        ushort[] bs = master.ReadHoldingRegisters(1, 0, 4);
        foreach (var item in bs)
        {
            Console.WriteLine(item);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"读取失败:{ex.Message}", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    finally
    {
        // 无论是否异常,都关闭串口,释放资源
        if (port != null && port.IsOpen)
        {
            port.Close();
        }
    }
}

解析:优化后的代码添加了 try-catch-finally 结构,既捕获通信异常并提示用户,又保证串口资源在任何情况下都能正常释放,提升程序稳定性。

3.3 其他注意事项

  • 串口参数必须与下位机完全一致,否则无法建立通信,需提前确认下位机配置

  • 串口号需根据实际设备连接情况修改,避免使用不存在的串口

  • 超时时间可根据设备响应速度调整,若读取频繁超时,可适当延长超时时间

  • 保持寄存器的读取地址与数量需匹配下位机寄存器配置,避免地址越界导致读取失败

相关推荐
Mojitocean18 分钟前
Win开发环境配置(持续更新)
开发语言·python
sramdram33 分钟前
基于MCU单片机的电推剪控制解决方案
单片机·嵌入式硬件·mcu·mcu单片机
CoderIsArt39 分钟前
C#环境中部署MQTT客户端
开发语言·c#
单片机仿真设计44 分钟前
【proteus仿真】基于 STM32 超声波传感器自动泊车系统 设计(仿真图+程序)
stm32·单片机·嵌入式硬件·proteus·毕设
HugoStudio_SWAN1 小时前
【按键反馈】C++ 实现键盘烟花秀
开发语言·c++·学习·程序人生
三佛科技-187366133971 小时前
HN8220A是什么芯片?大功率非隔离电源芯片HN8220A深度解析
单片机·嵌入式硬件
AI 编程助手GPT1 小时前
Bun 1.4 正式发布:从 Zig 改写为 Rust,内置浏览器、图片处理和并行测试
开发语言·人工智能·后端·ai·chatgpt
paopaokaka_luck1 小时前
基于springboot3+vue3的飞行器模型实验室预约管理系统(协同过滤算法、Echarts图形化分析)
开发语言·推荐算法
lingchen19061 小时前
用MATLAB r2010b和r2016b求解微分方程组
开发语言·算法·matlab