using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wsfly
{
/// <summary>
/// 委托-回调
/// </summary>
/// <param name="receiveData">十进制列表</param>
/// <param name="receiveHexData">十六进制字符串</param>
public delegate void Modbus_Callback(bool success, string msg, List<int> receiveData, string receiveHexData, string resultData);
/// <summary>
/// 信捷PLC Modbus 指令
/// </summary>
public class ModbusXJHandler
{
/*
* Modbus-RTU
功能码说明:
功能码 功能
01 读取线圈状态
02 读取输入状态
03 读取寄存器
04 读取输入寄存器
05 写单个线圈
06 写单个寄存器
0F 写多个线圈(0F=15)
10 写多个寄存器(10H=16)
协议格式:
模块地址 功能码 数据 CRC校验
1Byte=8Bit
03H格式:
发送:模块地址[1Byte] 功能码[1Byte] 寄存器起始地址[2Byte] 读取寄存器数目[2Byte] CRC校验(低字节在前)[2Byte]
返回:模块地址[1Byte] 功能码[1Byte] 读取字节数[2Byte] 读取寄存器内容[2Byte] CRC校验[2Byte]
06H格式:
发送:模块地址[1Byte] 功能码[1Byte] 寄存器起始地址[2Byte] 写入寄存器内容[2Byte] CRC校验(低字节在前)[2Byte]
返回:模块地址[1Byte] 功能码[1Byte] 寄存器起始地址[2Byte] 写入寄存器内容[2Byte] CRC校验[2Byte]
报文格式:
发送: 01 03 00 01 00 01 D5 CA
接收: 01 03 02 00 01 79 84
发送: 01 06 01 00 00 01 49 F6
接收: 01 06 01 00 00 01 49 F6
*/
/// <summary>
/// 串口 SerialPort
/// </summary>
private SerialPort _serialPort = null;
/// <summary>
/// 回调函数
/// </summary>
private Modbus_Callback _callback = null;
/// <summary>
/// 收到的数据 DataReceive
/// </summary>
private string _receiveHexData = null;
/// <summary>
/// 最后发送的数据
/// </summary>
private string _lastSendData = "";
/// <summary>
/// 自动打开与关闭串口
/// </summary>
public bool AutoOpenAndCloseSerialPort = false;
#region 构造函数
/*
RS-232C接口定义(DB9)
引脚 定义 符号
1 载波检测 DCD(Data Carrier Detect)
2 接收数据 RXD(Received Data)
3 发送数据 TXD(Transmit Data)
4 数据终端搜索准备好 DTR(Data Terminal Ready)
5 信号地 SG(Signal Ground)
6 数据准备好 DSR(Data Set Ready)
7 请求发送 RTS(Request To Send)
8 清除发送 CTS(Clear To Send)
9 振铃提示 RI(Ring Indicator)
*/
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serialPort"></param>
public ModbusXJHandler(SerialPort serialPort)
{
_serialPort = serialPort;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="port">端口号</param>
/// <param name="baudRate">波特率</param>
/// <param name="dataBits">数据位</param>
/// <param name="parity">奇偶检验位</param>
/// <param name="stopBits">停止位</param>
/// <param name="handshake">握手协议</param>
public ModbusXJHandler(int port, int baudRate = 9600, int dataBits = 8, Parity parity = Parity.Even, StopBits stopBits = StopBits.One, Handshake handshake = Handshake.None, bool dtrEnable = true, bool rtsEnable = true)
{
//实例化串口
_serialPort = new SerialPort();
_serialPort.PortName = "COM" + port; //端口号
_serialPort.BaudRate = baudRate; //波特率
_serialPort.DataBits = dataBits; //数据位
_serialPort.Parity = parity; //奇偶检验位
_serialPort.StopBits = stopBits; //停止位
_serialPort.Handshake = handshake; //握手协议
_serialPort.DtrEnable = dtrEnable;
_serialPort.RtsEnable = rtsEnable;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="port">端口号</param>
/// <param name="baudRate">波特率</param>
/// <param name="dataBits">数据位</param>
/// <param name="parity">奇偶检验位</param>
/// <param name="stopBits">停止位</param>
/// <param name="handshake">握手协议</param>
public ModbusXJHandler(int port, int baudRate, int dataBits, int parity, int stopBits, int handshake, bool dtrEnable = true, bool rtsEnable = true)
{
//实例化串口
_serialPort = new SerialPort();
_serialPort.PortName = "COM" + port; //端口号
_serialPort.BaudRate = baudRate; //波特率
_serialPort.DataBits = dataBits; //数据位
_serialPort.Parity = (Parity)parity; //奇偶检验位
_serialPort.StopBits = (StopBits)stopBits; //停止位
_serialPort.Handshake = (Handshake)handshake; //握手协议
_serialPort.DtrEnable = dtrEnable;
_serialPort.RtsEnable = rtsEnable;
}
#endregion
#region 获取串口
/// <summary>
/// 获取所有串口
/// </summary>
/// <returns></returns>
public static List<string> GetCom()
{
List<string> comList = new List<string>();
foreach (string portName in SerialPort.GetPortNames())
{
comList.Add(portName);
}
return comList;
}
/// <summary>
/// 获取所有串口 通过注册表
/// </summary>
/// <returns></returns>
public static List<string> GetComByReg()
{
Microsoft.Win32.RegistryKey keyCom = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("Hardware\\DeviceMap\\SerialComm");
if (keyCom != null) return keyCom.GetValueNames().ToList();
return null;
}
#endregion
#region 打开、关闭串口
/// <summary>
/// 打开串口
/// </summary>
public void Open()
{
//是否有串口
if (_serialPort == null) return;
//打开连接
if (!_serialPort.IsOpen)
{
//打开串口
_serialPort.Open();
//串口收到数据处理
_serialPort.DataReceived += _serialPort_DataReceived;
}
}
/// <summary>
/// 关闭串口
/// </summary>
public void Close()
{
//是否有串口
if (_serialPort == null) return;
//关闭连接
if (_serialPort.IsOpen)
{
//串口收到数据处理
_serialPort.DataReceived -= _serialPort_DataReceived;
_serialPort.Close();
//_serialPort.Dispose();
}
}
#endregion
#region 读写线圈 COL
/// <summary>
/// 读取线圈 状态
/// </summary>
/// <param name="device"></param>
/// <param name="addr"></param>
/// <param name="count"></param>
/// <param name="callback"></param>
public void COL_ReadData(short device, short addr, short count, Modbus_Callback callback)
{
//参考:https://blog.csdn.net/xukai871105/article/details/16330471
//读到的状态要 将16进制转为2进制
//比如:读1-10 开状态的有:1.2.5.8.9.10
//发送指令:01 01 0001 000A
//接收指令:01 01 02 93 03 95 0D
//解析:02示收到2个字节 即:93 03
// 每8位为一个字节 即 1-8 对应 93
// 9-10 对应 03
// 十六进制 转为 二进制 不足8位在前补0
// 93 03
// 10010011 00000011
// 高位在前,低位在后,将顺序颠倒即可对应如下
// 返回状态二进制:11001001 11000000
// 线圈对应的位置:1.2.3.4.5.6.7.8 9.10
//二进制:1表示开 0表示关
string cmd = device.ToString("X2") + " 01 " + addr.ToString("X4") + " " + count.ToString("X4");
SendData(cmd, callback);
}
/// <summary>
/// 写入线圈 状态
/// </summary>
/// <param name="device"></param>
/// <param name="addr"></param>
/// <param name="value"></param>
/// <param name="callback"></param>
public void COL_WriteData(short device, short addr, short value, Modbus_Callback callback)
{
string cmd = device.ToString("X2") + " 05 " + addr.ToString("X4") + " " + value.ToString("X4");
SendData(cmd, callback);
}
/// <summary>
/// 写入多个线圈 状态
/// </summary>
/// <param name="device"></param>
/// <param name="addr"></param>
/// <param name="value"></param>
/// <param name="callback"></param>
public void COL_WriteMoreData(short device, short addr, short addrLength, short[] values, Modbus_Callback callback)
{
int ys = addrLength % 8;
int byteCount = ys == 0 ? addrLength / 8 : (addrLength / 8) + 1;
string cmd = device.ToString("X2") + " 0F " + addr.ToString("X4") + " " + addrLength.ToString("X4") + " " + byteCount.ToString("X2");
List<int> sendValues = new List<int>();
//拼合 二进制 转为 十进制
string byteStr = "";
for (var i = 0; i < values.Length; i++)
{
byteStr += values[i];
if (i > 0 && i % 7 == 0)
{
byteStr = string.Concat(byteStr.Reverse());
sendValues.Add(Convert.ToInt32(byteStr, 2));
byteStr = "";
}
}
if (!string.IsNullOrWhiteSpace(byteStr))
{
byteStr = string.Concat(byteStr.Reverse());
sendValues.Add(Convert.ToInt32(byteStr, 2));
byteStr = "";
}
//转为十六进制
foreach (int value in sendValues)
{
cmd += value.ToString("X2") + " ";
}
SendData(cmd, callback);
}
#endregion
#region 读输入指令 INR
/// <summary>
/// 读输入线圈指令
/// </summary>
/// <param name="device"></param>
/// <param name="addr"></param>
/// <param name="count"></param>
/// <param name="callback"></param>
public void INR_ReadData(short device, short addr, short count, Modbus_Callback callback)
{
string cmd = device.ToString("X2") + " 02 " + addr.ToString("X4") + " " + count.ToString("X4");
SendData(cmd, callback);
}
/// <summary>
/// 读输入寄存器指令
/// </summary>
/// <param name="device"></param>
/// <param name="addr"></param>
/// <param name="count"></param>
/// <param name="callback"></param>
public void INR_ReadMoreData(short device, short addr, short count, Modbus_Callback callback)
{
string cmd = device.ToString("X2") + " 04 " + addr.ToString("X4") + " " + count.ToString("X4");
SendData(cmd, callback);
}
#endregion
#region 读写寄存器 REG
/// <summary>
/// 读取数据
/// </summary>
/// <param name="device"></param>
/// <param name="addr"></param>
/// <param name="count"></param>
/// <param name="callback"></param>
public void REG_ReadData(short device, short addr, short count, Modbus_Callback callback)
{
string cmd = device.ToString("X2") + " 03 " + addr.ToString("X4") + " " + count.ToString("X4");
SendData(cmd, callback);
}
/// <summary>
/// 写入数据
/// </summary>
/// <param name="device"></param>
/// <param name="addr"></param>
/// <param name="value"></param>
/// <param name="callback"></param>
public void REG_WriteData(short device, short addr, short value, Modbus_Callback callback)
{
string cmd = device.ToString("X2") + " 06 " + addr.ToString("X4") + " " + value.ToString("X4");
SendData(cmd, callback);
}
/// <summary>
/// 写入多个数据
/// </summary>
/// <param name="device"></param>
/// <param name="addr"></param>
/// <param name="values"></param>
/// <param name="callback"></param>
public void REG_WriteMoreData(short device, short addr, short addrLength, short[] values, Modbus_Callback callback)
{
//报文格式示例:
//从机地址 功能码 寄存器起始地址高字节 寄存器起始地址低字节 寄存器数量高字节 寄存器数量低字节 字节数 数据1高字节 数据1低字节 数据2高字节 数据2低字节 CRC校验高字节 CRC校验低字节
//11 10 00 01 00 02 04 00 0A 01 02 C6 F0
string cmd = device.ToString("X2") + " 10 " + addr.ToString("X4") + " " + addrLength.ToString("X4") + " " + (addrLength * 2).ToString("X2");
//对应写入 不够写入0
for (int i = 0; i < addrLength; i++)
{
int value = (values.Length >= (i - 1)) ? values[i] : 0;
cmd += value.ToString("x4");
}
SendData(cmd, callback);
}
#endregion
#region 发送数据
/// <summary>
/// 发送数据
/// </summary>
/// <param name="sendData">要发送的十六进制数据 如:01 06 01 00 00 01</param>
/// <param name="callback">结果回调</param>
public void SendData(string sendData, Modbus_Callback callback)
{
//检查参数
if (_serialPort == null)
{
//回调
callback?.Invoke(false, "串口未定义", null, null, null);
return;
}
if (string.IsNullOrWhiteSpace(sendData))
{
//回调
callback?.Invoke(false, "发送的数据不可为空", null, null, null);
return;
}
if (!AutoOpenAndCloseSerialPort && !_serialPort.IsOpen)
{
//回调
callback?.Invoke(false, "请先打开串口!", null, null, null);
return;
}
try
{
//回调函数
_callback = callback;
//最后发送的数据
_lastSendData = sendData;
//清空回传数据
_receiveHexData = string.Empty;
//发送十六进制
SendHexadecimalData(sendData);
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 发送16进制数据
/// </summary>
/// <param name="sendData">发送的十六进制的数据 如:01 06 01 00 00 01</param>
private void SendHexadecimalData(string sendData)
{
//发送的十六进制的数据
sendData = sendData.Replace(" ", "");
//定义发送的字节大小除以2是因为两个字位一个字节,加2是因为RTU发送后面还要加两个字节的校验码
byte[] sendBytes = new byte[sendData.Length / 2 + 2];
int k = 0;
//截取待发送的数据每次截取两位转换成10进制放到一个字节中
for (int i = 0; i < sendData.Length - 1; i = i + 2)
{
//第I个字截取两个
string str = sendData.Substring(i, 2);
//将截取到的两个字符转换成10进制方便异或
sendBytes[k] = Convert.ToByte(str, 16);
k++;
}
//调用CRC检验函数
string crc = CRCCode(sendData);
//获取CRC高位
string crcH = crc.Substring(2, 2);
//获取CRC低位
string crcL = crc.Substring(0, 2);
//将CRC转换成一个字节的10进制
sendBytes[sendData.Length / 2] = Convert.ToByte(crcH, 16);
//将CRC转换成一个字节的10进制
sendBytes[sendData.Length / 2 + 1] = Convert.ToByte(crcL, 16);
//自动打开串口
if (AutoOpenAndCloseSerialPort)
{
while (true)
{
try
{
//打开串口
Open();
break;
}
catch (Exception ex)
{
//打开串口异常,暂停0.1秒,重新打开
System.Threading.Thread.Sleep(100);
}
}
}
//发送数据
_serialPort.Write(sendBytes, 0, sendBytes.Length);
}
#endregion
#region 接收数据
/// <summary>
/// 接收数据处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//暂停0.1秒
System.Threading.Thread.Sleep(100);
//发回的字符数
int bytesLength = _serialPort.BytesToRead;
if (bytesLength <= 0) return;
//发回的数据
byte[] bytes = new byte[bytesLength];
//读取数据
int readDataLength = _serialPort.Read(bytes, 0, bytesLength);
if (readDataLength <= 2 || bytes == null) return;
//对数据进行转换
string resultData = Encoding.ASCII.GetString(bytes, 0, bytes.Length);
//要返回的十进制列表
List<int> results = new List<int>();
//转换为10进制、16进制
foreach (byte b in bytes)
{
results.Add(Convert.ToInt32(b));
_receiveHexData += b.ToString("X2") + " ";
}
_receiveHexData = _receiveHexData.Trim();
//验证CRC是否正确
string rhd = _receiveHexData.Replace(" ", "");
string rhd_Check = bytes[bytes.Length - 2].ToString("X2") + bytes.Last().ToString("X2");
string rhd_Data = rhd.Replace(rhd_Check, "");
//验证校验码
bool isRightCRC = false;
string rhdCRC = CRCCode(rhd_Data);
if (rhdCRC == rhd_Check)
{
//校验码是正确的
isRightCRC = true;
}
//回调通知
_callback?.Invoke(isRightCRC, "OK", results, _receiveHexData, resultData);
//自动关闭串口
if (AutoOpenAndCloseSerialPort)
{
//关闭串口
Close();
}
}
#endregion
#region CRC16-MODBUS
/// <summary>
/// CRC检验函数
/// </summary>
/// <returns></returns>
private static string CRCCode(string checkData)
{
Int32 crc = 65535;
string crcesult = "";
for (int i = 0; i < checkData.Length - 1; i = i + 2)
{
string str = "";
Int32 datadec = 0;
str = checkData.Substring(i, 2);//第I个字截取两个
datadec = Convert.ToInt32(str, 16); //讲截取到的两个字符转换成10进制方便异或
crc = crc ^ datadec;//异或之后又放到CRC里面
for (int j = 0; j < 8; j++)
{
string binstr;
binstr = DECToBIN(crc);
if (binstr.Substring(15, 1) == "0")//截取二进制的最低位判断是不是0
{
crc = crc >> 1; //如果是0直接右边移1位
}
else
{
crc = crc >> 1; //右边移1位
crc = crc ^ 40961; //与多项式异或
}
}
}
//保持4位字符如果不够就用0填上并且以大写形式
crcesult = Convert.ToString(crc, 16).PadLeft(4, '0').ToUpper();
//返回CRC检验值
return crcesult;
}
/// <summary>
/// 十进制转2进制
/// </summary>
/// <param name="dec"></param>
/// <returns></returns>
private static string DECToBIN(int dec)
{
string result = "";
while (dec > 0)
{
result = (dec % 2).ToString() + result;
dec = dec / 2;
}
//返回16位的二进制
return result.PadLeft(16, '0');
}
#endregion
}
}
c#辅助类:PLC信捷
最新推荐文章于 2024-07-08 16:57:50 发布