2023-03-05
通讯
00
请注意,本文编写于 364 天前,最后修改于 353 天前,其中某些信息可能已经过时。

目录

实现
引用NModbus4
代码实现
测试准备
测试开始

上文中介绍了C#ModBus Tcp的学习及Master的实现,本篇介绍串口实现Modbus RTU。

Modbus协议目前存在用于串口、以太网以及其他支持互联网协议的网络的版本。

uTools_1678010627779.png

实现

引用NModbus4

Modbus RTU实现依然借助开源库NModbus4,在vs中.打开NuGet管理器.安装NModbus4

1630815-20190511105244313-446452823.png

代码实现

实现与之前的Modbus Tcp的实现类似 ,只是在实例化master时将TCPClient换为串行端口资源SerialPort,并在实例化是设置好端口所需参数(端口名,波特率,校验位,停止位,数据位)。

csharp
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Modbus.Device; using System.Net.Sockets; using System.Threading; using System.IO.Ports; namespace ModbusRtu { public partial class Form1 : Form { private static IModbusMaster master; private static SerialPort port; //写线圈或写寄存器数组 private bool[] coilsBuffer; private ushort[] registerBuffer; //功能码 private string functionCode; //参数(分别为站号,起始地址,长度) private byte slaveAddress; private ushort startAddress; private ushort numberOfPoints; //串口参数 private string portName; private int baudRate; private Parity parity; private int dataBits; private StopBits stopBits; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { cmb_portname.SelectedIndex = 0; cmb_baud.SelectedIndex = 5; cmb_parity.SelectedIndex = 2; cmb_databBits.SelectedIndex = 1; cmb_stopBits.SelectedIndex = 0; } private SerialPort InitSerialPortParameter() { if (cmb_portname.SelectedIndex < 0 || cmb_baud.SelectedIndex < 0 || cmb_parity.SelectedIndex < 0 || cmb_databBits.SelectedIndex < 0 || cmb_stopBits.SelectedIndex < 0) { MessageBox.Show("请选择串口参数"); return null; } else { portName = cmb_portname.SelectedItem.ToString(); baudRate = int.Parse(cmb_baud.SelectedItem.ToString()); switch (cmb_parity.SelectedItem.ToString()) { case "奇": parity = Parity.Odd; break; case "偶": parity = Parity.Even; break; case "无": parity = Parity.None; break; default: break; } dataBits = int.Parse(cmb_databBits.SelectedItem.ToString()); switch (cmb_stopBits.SelectedItem.ToString()) { case "1": stopBits = StopBits.One; break; case "2": stopBits = StopBits.Two; break; default: break; } port = new SerialPort(portName, baudRate, parity, dataBits, stopBits); return port; } } /// <summary> /// 读/写 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { try { //初始化串口参数 InitSerialPortParameter(); master = ModbusSerialMaster.CreateRtu(port); ExecuteFunction(); } catch (Exception) { MessageBox.Show("初始化异常"); } } private async void ExecuteFunction() { try { //每次操作是要开启串口 操作完成后需要关闭串口 //目的是为了slave更换连接是不报错 if (port.IsOpen == false) { port.Open(); } if (functionCode != null) { switch (functionCode) { case "01 Read Coils"://读取单个线圈 SetReadParameters(); coilsBuffer = master.ReadCoils(slaveAddress, startAddress, numberOfPoints); for (int i = 0; i < coilsBuffer.Length; i++) { SetMsg(coilsBuffer[i] + " "); } SetMsg("\r\n"); break; case "02 Read DisCrete Inputs"://读取输入线圈/离散量线圈 SetReadParameters(); coilsBuffer = master.ReadInputs(slaveAddress, startAddress, numberOfPoints); for (int i = 0; i < coilsBuffer.Length; i++) { SetMsg(coilsBuffer[i] + " "); } SetMsg("\r\n"); break; case "03 Read Holding Registers"://读取保持寄存器 SetReadParameters(); registerBuffer = master.ReadHoldingRegisters(slaveAddress, startAddress, numberOfPoints); for (int i = 0; i < registerBuffer.Length; i++) { SetMsg(registerBuffer[i] + " "); } SetMsg("\r\n"); break; case "04 Read Input Registers"://读取输入寄存器 SetReadParameters(); registerBuffer = master.ReadInputRegisters(slaveAddress, startAddress, numberOfPoints); for (int i = 0; i < registerBuffer.Length; i++) { SetMsg(registerBuffer[i] + " "); } SetMsg("\r\n"); break; case "05 Write Single Coil"://写单个线圈 SetWriteParametes(); await master.WriteSingleCoilAsync(slaveAddress, startAddress, coilsBuffer[0]); break; case "06 Write Single Registers"://写单个输入线圈/离散量线圈 SetWriteParametes(); await master.WriteSingleRegisterAsync(slaveAddress, startAddress, registerBuffer[0]); break; case "0F Write Multiple Coils"://写一组线圈 SetWriteParametes(); await master.WriteMultipleCoilsAsync(slaveAddress, startAddress, coilsBuffer); break; case "10 Write Multiple Registers"://写一组保持寄存器 SetWriteParametes(); await master.WriteMultipleRegistersAsync(slaveAddress, startAddress, registerBuffer); break; default: break; } } else { MessageBox.Show("请选择功能码!"); } port.Close(); } catch (Exception ex) { MessageBox.Show(ex.Message); } } private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { if (comboBox1.SelectedIndex >= 4) { groupBox2.Enabled = true; groupBox1.Enabled = false; } else { groupBox1.Enabled = true; groupBox2.Enabled = false; } comboBox1.Invoke(new Action(() => { functionCode = comboBox1.SelectedItem.ToString(); })); } /// <summary> /// 初始化读参数 /// </summary> private void SetReadParameters() { if (txt_startAddr1.Text == "" || txt_slave1.Text == "" || txt_length.Text == "") { MessageBox.Show("请填写读参数!"); } else { slaveAddress = byte.Parse(txt_slave1.Text); startAddress = ushort.Parse(txt_startAddr1.Text); numberOfPoints = ushort.Parse(txt_length.Text); } } /// <summary> /// 初始化写参数 /// </summary> private void SetWriteParametes() { if (txt_startAddr2.Text == "" || txt_slave2.Text == "" || txt_data.Text == "") { MessageBox.Show("请填写写参数!"); } else { slaveAddress = byte.Parse(txt_slave2.Text); startAddress = ushort.Parse(txt_startAddr2.Text); //判断是否写线圈 if (comboBox1.SelectedIndex == 4 || comboBox1.SelectedIndex == 6) { string[] strarr = txt_data.Text.Split(' '); coilsBuffer = new bool[strarr.Length]; //转化为bool数组 for (int i = 0; i < strarr.Length; i++) { // strarr[i] == "0" ? coilsBuffer[i] = true : coilsBuffer[i] = false; if (strarr[i] == "0") { coilsBuffer[i] = false; } else { coilsBuffer[i] = true; } } } else { //转化ushort数组 string[] strarr = txt_data.Text.Split(' '); registerBuffer = new ushort[strarr.Length]; for (int i = 0; i < strarr.Length; i++) { registerBuffer[i] = ushort.Parse(strarr[i]); } } } } /// <summary> /// 清除文本 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button2_Click(object sender, EventArgs e) { richTextBox1.Clear(); } /// <summary> /// SetMessage /// </summary> /// <param name="msg"></param> public void SetMsg(string msg) { richTextBox1.Invoke(new Action(() => { richTextBox1.AppendText(msg); })); } } }

测试准备

借助工具 Virtual Serial Port Dirver 虚拟串口工具。

链接:https://pan.baidu.com/s/1opGre3GS-HWFoA_dP9qYYg

提取码:2afu

  1. 借助工具我们添加两个虚拟串口 COM1和COM2 点击 Add Virtual Pair 添加:

    1630815-20190511194028409-724607865.png

  2. 设置Modbus Slave,选择连接方式为串口,选择对应端口,模式选择RTU,建立连接:

    1630815-20190511194645309-195780803.png

测试开始

  1. 运行上文实现的Modbus RTU Master;

  2. 设置串口参数(波特率,数据位,奇偶校验,停止位)要与Slave的串口参数一致:

    测试 功能码 0x01 读一组线圈

    1630815-20190511195346652-1022612729.png

到此为止,Modbus的学习到此结束。若有错误之处,欢迎大家指正。

本文作者:Peter.Pan

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!