From 1173bdd8fe3dda1a82804b0902fa05137a8740a1 Mon Sep 17 00:00:00 2001 From: zhangyongpan Date: Sat, 26 Jul 2025 14:17:12 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BA=BF=E5=9C=88=E7=9A=84=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E5=92=8C=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ModbusDemo/App.xaml.cs | 7 +- ModbusDemo/Device/IModbusRTU.cs | 13 ++ ModbusDemo/Device/ModbusRTU.cs | 108 +++++++++++++++++ ModbusDemo/Uitls/CRCUitl.cs | 25 ++++ ModbusDemo/Uitls/CheckData.cs | 34 ++++++ ModbusDemo/VIew/CoilUC.xaml | 83 +++++++++++-- ModbusDemo/VIew/SettingsUC.xaml | 7 +- ModbusDemo/VIewModel/CoilUCViewModel.cs | 124 +++++++++++++++++++- ModbusDemo/VIewModel/MainWindowViewModel.cs | 11 +- ModbusDemo/VIewModel/SettingsUCViewModel.cs | 52 +++----- 10 files changed, 412 insertions(+), 52 deletions(-) create mode 100644 ModbusDemo/Device/IModbusRTU.cs create mode 100644 ModbusDemo/Device/ModbusRTU.cs create mode 100644 ModbusDemo/Uitls/CheckData.cs diff --git a/ModbusDemo/App.xaml.cs b/ModbusDemo/App.xaml.cs index 0d3cea4..769b243 100644 --- a/ModbusDemo/App.xaml.cs +++ b/ModbusDemo/App.xaml.cs @@ -1,4 +1,5 @@ -using ModbusDemo.VIew; +using ModbusDemo.Device; +using ModbusDemo.VIew; using ModbusDemo.VIewModel; using Prism.DryIoc; using Prism.Ioc; @@ -39,6 +40,10 @@ namespace ModbusDemo containerRegistry.RegisterForNavigation(); //将窗口注册为全局唯一的单例 containerRegistry.RegisterSingleton(); + + + //将读线圈注册 + containerRegistry.Register(); } /// /// 程序打开默认是设置界面,来设置串口的各个东西 diff --git a/ModbusDemo/Device/IModbusRTU.cs b/ModbusDemo/Device/IModbusRTU.cs new file mode 100644 index 0000000..fd17ccc --- /dev/null +++ b/ModbusDemo/Device/IModbusRTU.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ModbusDemo.Device +{ + interface IModbusRTU + { + public bool[] ReadCoil(byte slaveAddress, ushort startAddress, ushort numberOfPoints); + } +} diff --git a/ModbusDemo/Device/ModbusRTU.cs b/ModbusDemo/Device/ModbusRTU.cs new file mode 100644 index 0000000..d71b998 --- /dev/null +++ b/ModbusDemo/Device/ModbusRTU.cs @@ -0,0 +1,108 @@ +using ModbusDemo.Uitls; +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace ModbusDemo.Device +{ + public class ModbusRTU : IModbusRTU + { + private SerialPort _serialPort; + + public ModbusRTU(SerialPort serialPort) + { + _serialPort = serialPort; + } + /// + /// 用来发送读写线圈的指令 + /// + /// + /// + /// + /// + public bool[] ReadCoil(byte slaveAddress, ushort startAddress, ushort numberOfPoints) + { + + List sendByteList = new List(); + //设置从站地址 + sendByteList.Add(slaveAddress); + //设置功能码 + sendByteList.Add(0x01); + //设置起始地址的高位和低位 + sendByteList.Add(BitConverter.GetBytes(startAddress)[1]); + sendByteList.Add(BitConverter.GetBytes(startAddress)[0]); + //设置读取几个线圈的高位和低位 + sendByteList.Add(BitConverter.GetBytes(numberOfPoints)[1]); + sendByteList.Add(BitConverter.GetBytes(numberOfPoints)[0]); + //获取CRC校验码 + byte[] getCRC = sendByteList.ToArray(); + byte[] resultCRC = CRCUitl.CalculateCRC(getCRC, getCRC.Length); + sendByteList.Add(resultCRC[0]); + sendByteList.Add(resultCRC[1]); + + byte[] sendByte = sendByteList.ToArray(); + if (_serialPort.IsOpen) + { + // 清空接收缓冲区(避免残留数据干扰) + _serialPort.DiscardInBuffer(); + + _serialPort.Write(sendByte, 0, sendByte.Length); + Thread.Sleep(300); + byte[] response = new byte[_serialPort.BytesToRead]; + _serialPort.Read(response, 0, _serialPort.BytesToRead); + return Parseresponse(response,numberOfPoints); + } + + + return null; + } + + + public bool[] Parseresponse(byte[] response, ushort numberOfPoints) + { + //判断发送回来的数据是否正确 + CheckData.CheckResponse(response); + + if (!CRCUitl.ValidateCRC(response)) + { + MessageBox.Show("0x14:CRC校验错误"); + return null; + } + List responseList = new List(response); + //移除前两位 + responseList.RemoveRange(0, 3); + //移除后两位校验码 + responseList.RemoveRange(responseList.Count - 2, 2); + responseList.Reverse(); + //数组反转之后,转为二进制字符 + List StringList = responseList.Select(m => Convert.ToString(m, 2)).ToList(); + + var result = ""; + + foreach (var item in StringList) + { + result += item.PadLeft(8, '0'); + } + //先将字符串转为数组,再反转,再转为字节数组 + char[] chars = result.ToArray().Reverse().ToArray(); + bool[] f = new bool[numberOfPoints]; + for (int i = 0; i < numberOfPoints; i++) + { + if (chars[i] == '1') + { + f[i] = true; + } + else + { + f[i] = false; + } + } + return f; + } + + } +} diff --git a/ModbusDemo/Uitls/CRCUitl.cs b/ModbusDemo/Uitls/CRCUitl.cs index c28d880..e4fb93d 100644 --- a/ModbusDemo/Uitls/CRCUitl.cs +++ b/ModbusDemo/Uitls/CRCUitl.cs @@ -77,5 +77,30 @@ namespace ModbusDemo.Uitls } return res; } + /// + /// 用来计算CRC是否合理 + /// + /// + /// + public static bool ValidateCRC(byte[] fullFrame) + { + // 报文必须至少包含2个字节的CRC校验码 + if (fullFrame.Length < 2) + return false; + + // 计算数据部分的长度(排除最后2个CRC字节) + int dataLength = fullFrame.Length - 2; + + // 提取报文中包含的CRC校验码(传输顺序:低字节在前,高字节在后) + byte receivedCrcLo = fullFrame[fullFrame.Length - 2]; + byte receivedCrcHi = fullFrame[fullFrame.Length - 1]; + + // 计算数据部分的CRC(返回顺序:高字节在前,低字节在后) + byte[] calculatedCrc = CalculateCRC(fullFrame, dataLength); + + // 比较计算出的CRC和接收到的CRC + return (calculatedCrc[calculatedCrc.Length - 1] == receivedCrcHi) && + (calculatedCrc[calculatedCrc.Length - 2] == receivedCrcLo); + } } } diff --git a/ModbusDemo/Uitls/CheckData.cs b/ModbusDemo/Uitls/CheckData.cs new file mode 100644 index 0000000..c64642b --- /dev/null +++ b/ModbusDemo/Uitls/CheckData.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace ModbusDemo.Uitls +{ + public static class CheckData + { + + public static void CheckResponse(byte[] response) + { + // 检查数组长度是否足够 + if (response.Length > 1) + { + byte secondByte = response[1]; // 获取第二个字节(索引为1) + + // 使用掩码0xF0获取高4位,并右移4位 + int highNibble = (secondByte & 0xF0) >> 4; + + // 判断高4位是否等于8 + if (highNibble == 8) + { + MessageBox.Show($"数据错误:错误码为:0x{response[2]:X2}"); + return; + } + + } + } + + } +} diff --git a/ModbusDemo/VIew/CoilUC.xaml b/ModbusDemo/VIew/CoilUC.xaml index 30ab690..a0dfbac 100644 --- a/ModbusDemo/VIew/CoilUC.xaml +++ b/ModbusDemo/VIew/CoilUC.xaml @@ -1,12 +1,77 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ModbusDemo/VIew/SettingsUC.xaml b/ModbusDemo/VIew/SettingsUC.xaml index 8c7e3b3..929a6b0 100644 --- a/ModbusDemo/VIew/SettingsUC.xaml +++ b/ModbusDemo/VIew/SettingsUC.xaml @@ -43,10 +43,7 @@ - - - - + @@ -127,7 +124,9 @@ + + diff --git a/ModbusDemo/VIewModel/CoilUCViewModel.cs b/ModbusDemo/VIewModel/CoilUCViewModel.cs index c5c3d9e..d03db36 100644 --- a/ModbusDemo/VIewModel/CoilUCViewModel.cs +++ b/ModbusDemo/VIewModel/CoilUCViewModel.cs @@ -1,17 +1,137 @@ -using Prism.Mvvm; +using ModbusDemo.Device; +using Prism.Commands; +using Prism.Mvvm; using System; using System.Collections.Generic; +using System.IO.Ports; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; namespace ModbusDemo.VIewModel { /// /// 这是CoilUC的VM用来支持数据绑定 /// - public class CoilUCViewModel:BindableBase + public class CoilUCViewModel : BindableBase { + //获取读线圈的类 + private IModbusRTU _modbusRTU; + //定义读线圈的命令 + public DelegateCommand ReadCoilCmm { get; set; } + //获取当前使用的串口 + private SerialPort _serialPort; + //显示当前链接串口的信息 + public string SerialPortInfo + { + get + { + return "当前链接的状态:" + "串口号:" + _serialPort.PortName + ",波特率:" + _serialPort.BaudRate + + ",数据位:" + _serialPort.DataBits + ",校验位:" + _serialPort.Parity + ",停止位:" + _serialPort.StopBits; + } + } + + + //读取的从站id + private string _slaveAddress; + + public string SlaveAddress + { + get { return _slaveAddress; } + set + { + _slaveAddress = value; + RaisePropertyChanged(); + } + } + + private string _startAddress; + + //读取的起始地址 + public string StartAddress + { + get { return _startAddress; } + set + { + _startAddress = value; + RaisePropertyChanged(); + } + } + + private string _numberOfPoints; + + //读取的位数 + public string NumberOfPoints + { + get { return _numberOfPoints; } + set + { + _numberOfPoints = value; + RaisePropertyChanged(); + } + } + + private string _readResult; + + //读取的结果 + public string ReadResult + { + get { return _readResult; } + set + { + _readResult = value; + RaisePropertyChanged(); + } + } + + + + public CoilUCViewModel() + { + + } + + public CoilUCViewModel(SerialPort serialPort, ModbusRTU modbusRTU) + { + _serialPort = serialPort; + ReadCoilCmm = new DelegateCommand(ReadCoil); + _modbusRTU = modbusRTU; + } + + private void ReadCoil() + { + + + try + { + bool[] result = _modbusRTU.ReadCoil(byte.Parse(SlaveAddress), + ushort.Parse(StartAddress), + ushort.Parse(NumberOfPoints)); + + if(result != null) + { + string temp = ""; + foreach(var item in result) + { + temp += item; + temp += " "; + } + ReadResult = temp; + } + + + } + catch (Exception) + { + + MessageBox.Show("参数配置错误"); + } + + + + + } } } diff --git a/ModbusDemo/VIewModel/MainWindowViewModel.cs b/ModbusDemo/VIewModel/MainWindowViewModel.cs index 15849c6..093fff5 100644 --- a/ModbusDemo/VIewModel/MainWindowViewModel.cs +++ b/ModbusDemo/VIewModel/MainWindowViewModel.cs @@ -45,10 +45,17 @@ namespace ModbusDemo.VIewModel NavigationCmm = new DelegateCommand(Navigation); } - + /// + /// 视图导航 + /// + /// private void Navigation(MenusInfo info) { - _regionManager.Regions["ModbusRegion"].RequestNavigate(info.ViewName); + if(info != null) + { + _regionManager.Regions["ModbusRegion"].RequestNavigate(info.ViewName); + } + } /// /// 初始化左下拉框 diff --git a/ModbusDemo/VIewModel/SettingsUCViewModel.cs b/ModbusDemo/VIewModel/SettingsUCViewModel.cs index 2f81bfd..79beda4 100644 --- a/ModbusDemo/VIewModel/SettingsUCViewModel.cs +++ b/ModbusDemo/VIewModel/SettingsUCViewModel.cs @@ -7,6 +7,7 @@ using System.IO.Ports; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; namespace ModbusDemo.VIewModel { @@ -115,42 +116,25 @@ namespace ModbusDemo.VIewModel //todo,做数据的判空处理 if (!_serialPort.IsOpen) { - _serialPort.PortName = GetComboBoxItemValue(this.PortName); - _serialPort.BaudRate = int.Parse(GetComboBoxItemValue(this.BaudRate)); - _serialPort.Parity = this.Parity; - _serialPort.StopBits = this.StopBits; - _serialPort.DataBits = int.Parse(GetComboBoxItemValue(this.DataBits)); - _serialPort.Open(); + + + try + { + _serialPort.PortName = GetComboBoxItemValue(this.PortName); + _serialPort.BaudRate = int.Parse(GetComboBoxItemValue(this.BaudRate)); + _serialPort.Parity = this.Parity; + _serialPort.StopBits = this.StopBits; + _serialPort.DataBits = int.Parse(GetComboBoxItemValue(this.DataBits)); + _serialPort.Open(); + MessageBox.Show("串口链接成功"); + } + catch (Exception) + { + + MessageBox.Show("串口已经链接,请先断开链接在尝试"); + } } - - - - - List bytes = new List(); - bytes.Add(0x01); - bytes.Add(0x05); - - - bytes.Add(BitConverter.GetBytes(301)[1]); - bytes.Add(BitConverter.GetBytes(301)[0]); - - - bytes.Add(0xFF); - bytes.Add(0x00); - - byte[] bytes1 = CRCUitl.CalculateCRC(bytes.ToArray(), bytes.Count); - - bytes.Add(bytes1[0]); - bytes.Add(bytes1[1]); - byte[] bytes2 = bytes.ToArray(); - System.Threading.Thread.Sleep(200); // 等待200毫秒 - - byte[] response = new byte[_serialPort.BytesToRead]; - //int bytesRead = serialPort.Read(response, 0, response.Length); - - //string hexResponse = BitConverter.ToString(response, 0, bytesRead); - _serialPort.Write(bytes2, 0, bytes2.Length); } /// /// 处理下拉框的选择信息