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

发布时间:2026/8/29 7:14:55
C# WinForm NModbus 串口通信+WinForm Modbus4 串口通信 一、项目基础说明1.1 项目依赖本项目基于 NModbus 库实现 Modbus-RTU 串口通信需通过 NuGet 安装两个核心包NModbusModbus 协议核心实现库提供主站/从站通信接口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/OFF1.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 表示线圈闭合ONfalse 表示线圈断开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 其他注意事项串口参数必须与下位机完全一致否则无法建立通信需提前确认下位机配置串口号需根据实际设备连接情况修改避免使用不存在的串口超时时间可根据设备响应速度调整若读取频繁超时可适当延长超时时间保持寄存器的读取地址与数量需匹配下位机寄存器配置避免地址越界导致读取失败