@@ -1,4 +1,5 @@ | |||||
using ModbusDemo.VIew; | |||||
using ModbusDemo.Device; | |||||
using ModbusDemo.VIew; | |||||
using ModbusDemo.VIewModel; | using ModbusDemo.VIewModel; | ||||
using Prism.DryIoc; | using Prism.DryIoc; | ||||
using Prism.Ioc; | using Prism.Ioc; | ||||
@@ -39,6 +40,10 @@ namespace ModbusDemo | |||||
containerRegistry.RegisterForNavigation<SettingsUC, SettingsUCViewModel>(); | containerRegistry.RegisterForNavigation<SettingsUC, SettingsUCViewModel>(); | ||||
//将窗口注册为全局唯一的单例 | //将窗口注册为全局唯一的单例 | ||||
containerRegistry.RegisterSingleton<SerialPort>(); | containerRegistry.RegisterSingleton<SerialPort>(); | ||||
//将读线圈注册 | |||||
containerRegistry.Register<ModbusRTU>(); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// 程序打开默认是设置界面,来设置串口的各个东西 | /// 程序打开默认是设置界面,来设置串口的各个东西 | ||||
@@ -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); | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
/// <summary> | |||||
/// 用来发送读写线圈的指令 | |||||
/// </summary> | |||||
/// <param name="slaveAddress"></param> | |||||
/// <param name="startAddress"></param> | |||||
/// <param name="numberOfPoints"></param> | |||||
/// <returns></returns> | |||||
public bool[] ReadCoil(byte slaveAddress, ushort startAddress, ushort numberOfPoints) | |||||
{ | |||||
List<byte> sendByteList = new List<byte>(); | |||||
//设置从站地址 | |||||
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<byte> responseList = new List<byte>(response); | |||||
//移除前两位 | |||||
responseList.RemoveRange(0, 3); | |||||
//移除后两位校验码 | |||||
responseList.RemoveRange(responseList.Count - 2, 2); | |||||
responseList.Reverse(); | |||||
//数组反转之后,转为二进制字符 | |||||
List<string> 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<char>().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; | |||||
} | |||||
} | |||||
} |
@@ -77,5 +77,30 @@ namespace ModbusDemo.Uitls | |||||
} | } | ||||
return res; | return res; | ||||
} | } | ||||
/// <summary> | |||||
/// 用来计算CRC是否合理 | |||||
/// </summary> | |||||
/// <param name="fullFrame"></param> | |||||
/// <returns></returns> | |||||
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); | |||||
} | |||||
} | } | ||||
} | } |
@@ -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; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -1,12 +1,77 @@ | |||||
<UserControl x:Class="ModbusDemo.VIew.CoilUC" | |||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | |||||
xmlns:local="clr-namespace:ModbusDemo.VIew" | |||||
mc:Ignorable="d" | |||||
d:DesignHeight="450" d:DesignWidth="800"> | |||||
<UserControl | |||||
x:Class="ModbusDemo.VIew.CoilUC" | |||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | |||||
xmlns:local="clr-namespace:ModbusDemo.VIew" | |||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" | |||||
xmlns:viewmodel="clr-namespace:ModbusDemo.VIewModel" | |||||
d:DataContext="{d:DesignInstance Type=viewmodel:CoilUCViewModel, | |||||
IsDesignTimeCreatable=True}" | |||||
d:Background="White" | |||||
d:DesignHeight="450" | |||||
d:DesignWidth="800" | |||||
mc:Ignorable="d"> | |||||
<Grid> | <Grid> | ||||
<TextBlock Text="Coil" FontSize="60"></TextBlock> | |||||
<Grid.RowDefinitions> | |||||
<RowDefinition Height="60"> | |||||
</RowDefinition> | |||||
<RowDefinition> | |||||
</RowDefinition> | |||||
<RowDefinition> | |||||
</RowDefinition> | |||||
<RowDefinition> | |||||
</RowDefinition> | |||||
</Grid.RowDefinitions> | |||||
<TextBlock | |||||
HorizontalAlignment="Center" | |||||
VerticalAlignment="Center" | |||||
Text="{Binding SerialPortInfo}" | |||||
FontWeight="Black" | |||||
FontSize="16"> | |||||
</TextBlock> | |||||
<Grid Grid.Row="1"> | |||||
<Grid.ColumnDefinitions> | |||||
<ColumnDefinition> | |||||
</ColumnDefinition> | |||||
<ColumnDefinition> | |||||
</ColumnDefinition> | |||||
<ColumnDefinition> | |||||
</ColumnDefinition> | |||||
<ColumnDefinition> | |||||
</ColumnDefinition> | |||||
<ColumnDefinition> | |||||
</ColumnDefinition> | |||||
</Grid.ColumnDefinitions> | |||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center"> | |||||
<TextBlock Text="从站地址" Margin="20"></TextBlock> | |||||
<TextBox Text="{Binding SlaveAddress}" Margin="0,-10,0,0"></TextBox> | |||||
</StackPanel> | |||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Grid.Column="1"> | |||||
<TextBlock Text="起始地址" Margin="20"></TextBlock> | |||||
<TextBox Text="{Binding StartAddress}" Margin="0,-10,0,0"></TextBox> | |||||
</StackPanel> | |||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Grid.Column="2"> | |||||
<TextBlock Text="读取位数" Margin="20"></TextBlock> | |||||
<TextBox Text="{Binding NumberOfPoints}" Margin="0,-10,0,0"></TextBox> | |||||
</StackPanel> | |||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Grid.Column="3"> | |||||
<TextBlock Text="读取结果" Margin="20"></TextBlock> | |||||
<TextBox Text="{Binding ReadResult}" Margin="0,-10,0,0"></TextBox> | |||||
</StackPanel> | |||||
<Button | |||||
Grid.Column="4" | |||||
Width="80" | |||||
Height="30" | |||||
Command="{Binding ReadCoilCmm}" | |||||
materialDesign:ButtonAssist.CornerRadius="15" | |||||
Content="读取" | |||||
Style="{StaticResource MaterialDesignRaisedDarkButton}"> | |||||
</Button> | |||||
</Grid> | |||||
</Grid> | </Grid> | ||||
</UserControl> | </UserControl> |
@@ -43,10 +43,7 @@ | |||||
</ColumnDefinition> | </ColumnDefinition> | ||||
<ColumnDefinition> | <ColumnDefinition> | ||||
</ColumnDefinition> | </ColumnDefinition> | ||||
<ColumnDefinition> | |||||
</ColumnDefinition> | |||||
<ColumnDefinition> | |||||
</ColumnDefinition> | |||||
</Grid.ColumnDefinitions> | </Grid.ColumnDefinitions> | ||||
<!-- 这是设置串口的选择 --> | <!-- 这是设置串口的选择 --> | ||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> | <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> | ||||
@@ -127,7 +124,9 @@ | |||||
</ComboBoxItem> | </ComboBoxItem> | ||||
</ComboBox> | </ComboBox> | ||||
</StackPanel> | </StackPanel> | ||||
</Grid> | </Grid> | ||||
<!-- 两个按钮的定义 --> | |||||
<Grid Grid.Row="2"> | <Grid Grid.Row="2"> | ||||
<Grid.ColumnDefinitions> | <Grid.ColumnDefinitions> | ||||
<ColumnDefinition> | <ColumnDefinition> | ||||
@@ -1,17 +1,137 @@ | |||||
using Prism.Mvvm; | |||||
using ModbusDemo.Device; | |||||
using Prism.Commands; | |||||
using Prism.Mvvm; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.IO.Ports; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using System.Windows; | |||||
namespace ModbusDemo.VIewModel | namespace ModbusDemo.VIewModel | ||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// 这是CoilUC的VM用来支持数据绑定 | /// 这是CoilUC的VM用来支持数据绑定 | ||||
/// </summary> | /// </summary> | ||||
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("参数配置错误"); | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -45,10 +45,17 @@ namespace ModbusDemo.VIewModel | |||||
NavigationCmm = new DelegateCommand<MenusInfo>(Navigation); | NavigationCmm = new DelegateCommand<MenusInfo>(Navigation); | ||||
} | } | ||||
/// <summary> | |||||
/// 视图导航 | |||||
/// </summary> | |||||
/// <param name="info"></param> | |||||
private void Navigation(MenusInfo info) | private void Navigation(MenusInfo info) | ||||
{ | { | ||||
_regionManager.Regions["ModbusRegion"].RequestNavigate(info.ViewName); | |||||
if(info != null) | |||||
{ | |||||
_regionManager.Regions["ModbusRegion"].RequestNavigate(info.ViewName); | |||||
} | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// 初始化左下拉框 | /// 初始化左下拉框 | ||||
@@ -7,6 +7,7 @@ using System.IO.Ports; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using System.Windows; | |||||
namespace ModbusDemo.VIewModel | namespace ModbusDemo.VIewModel | ||||
{ | { | ||||
@@ -115,42 +116,25 @@ namespace ModbusDemo.VIewModel | |||||
//todo,做数据的判空处理 | //todo,做数据的判空处理 | ||||
if (!_serialPort.IsOpen) | 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<byte> bytes = new List<byte>(); | |||||
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); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// 处理下拉框的选择信息 | /// 处理下拉框的选择信息 | ||||