using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PLC_Control
{ internal class FXPLC
{
private SerialPort _serialPort;
private bool _isConnected = false;
private const string D0_ADDRESS = "D0000"; // D0寄存器固定地址
///
/// 初始化通讯对象
///
/// 串口号(如"COM3")
/// 波特率(默认9600)
/// PLC站号(默认0)
public FXPLC(string portName, int baudRate = 9600, byte stationNumber = 0)
{ _serialPort = new SerialPort
{ PortName = portName,
BaudRate = baudRate,
DataBits = 8,
StopBits = StopBits.One,
Parity = Parity.None,
Encoding = Encoding.ASCII,
ReadTimeout = 1000,
WriteTimeout = 1000
};
StationNumber = stationNumber;
}
///
/// PLC站号
///
public byte StationNumber { get; set; }
///
/// 连接PLC
///
/// 是否连接成功
public bool Connect()
{ try
{ if (!_serialPort.IsOpen)
{ _serialPort.Open();
_isConnected = true;
return true;
}
return false;
}
catch
{ return false;
}
}
///
/// 断开连接
///
public void Disconnect()
{ if (_serialPort.IsOpen)
{ _serialPort.Close();
}
_isConnected = false;
}
///
/// 读取D0寄存器的值(16位)
///
/// 返回的16进制字符串(4位)
/// 返回的十进制数值
/// 是否读取成功
public bool ReadD0(out string hexValue, out int decValue)
{ hexValue = "";
decValue = 0;
if (!_isConnected)
return false;
try
{ // 构建RR指令帧(读取D0)
byte[] requestFrame = BuildReadFrame();
if (requestFrame == null)
return false;
// 发送并接收响应
byte[] response = SendAndReceive(requestFrame);
if (response == null)
return false;
// 解析响应
if (ParseReadResponse(response, out hexValue))
{ // 转换为十进制
decValue = int.Parse(hexValue, System.Globalization.NumberStyles.HexNumber);
return true;
}
return false;
}
catch
{ return false;
}
}
///
/// 写入值到D0寄存器
///
/// 16进制字符串(4位,如"1234")
/// 是否写入成功
public bool WriteD0(string hexValue)
{ if (!_isConnected)
return false;
// 验证输入格式
if (string.IsNullOrEmpty(hexValue) || hexValue.Length != 4 || !IsHexString(hexValue))
return false;
try
{ // 构建WR指令帧(写入D0)
byte[] requestFrame = BuildWriteFrame(hexValue.ToUpper());
if (requestFrame == null)
return false;
// 发送并接收响应
byte[] response = SendAndReceive(requestFrame);
if (response == null)
return false;
// 解析响应
return ParseWriteResponse(response);
}
catch
{ return false;
}
}
///
/// 构建读取D0的请求帧(RR指令)
///
private byte[] BuildReadFrame()
{ try
{ // 帧结构:ENQ(0x05) + 站号(ASCII) + "0" + "WR" + "0" + "D0000" + "01" + 校验和 + CR+LF
StringBuilder frame = new StringBuilder();
frame.Append((char)0x05); // ENQ
frame.Append((char)0x30);
frame.Append((char)(StationNumber + 0x30)); // 站号(ASCII)
frame.Append("FF"); // PC号 frame.Append("WR"); // 指令码(WR=读取) frame.Append("0"); // 报文等待 frame.Append(D0_ADDRESS); // D0地址
frame.Append("01"); // 读取数量(1个) frame.Append(CalculateChecksum(frame.ToString().Substring(1))); // 校验和
frame.Append("\r\n"); // 帧尾
return Encoding.ASCII.GetBytes(frame.ToString());
}
catch
{ return null;
}
}
///
/// 构建写入D0的请求帧(WW指令)
///
private byte[] BuildWriteFrame(string hexValue)
{ try
{ // 帧结构:ENQ(0x05) + 站号(ASCII) + "0" + "WW" + "0" + "D0000" + "01" + 数据 + 校验和 + CR+LF
StringBuilder frame = new StringBuilder();
frame.Append((char)0x05); // ENQ
frame.Append((char)0x30);
frame.Append((char)(StationNumber + 0x30)); // 站号(ASCII)
frame.Append("FF"); // PC号 frame.Append("WW"); // 指令码(WW=写入) frame.Append("0"); // 报文等待 frame.Append(D0_ADDRESS); // D0地址
frame.Append("01"); // 写入数量(1个) frame.Append(hexValue); // 写入的数据
frame.Append(CalculateChecksum(frame.ToString().Substring(1))); // 校验和
frame.Append("\r\n"); // 帧尾
return Encoding.ASCII.GetBytes(frame.ToString());
}
catch
{ return null;
}
}
///
/// 发送数据并接收响应
///
private byte[] SendAndReceive(byte[] data)
{ try
{ _serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
// 发送数据
_serialPort.Write(data, 0, data.Length);
// 读取完整响应(替换原有的 Read 逻辑)
byte[] response = ReadCompleteResponse();
if (response == null || response.Length == 0)
{ Console.WriteLine("未收到响应或响应超时"); return null;
}
return response;
}
catch
{ return null;
}
}
private byte[] ReadCompleteResponse()
{ List<byte> responseBuffer = new List<byte>();
DateTime startTime = DateTime.Now;
byte[] tempBuffer = new byte[128];
try
{ // 循环读取,直到收到完整帧或超时(3秒)
while ((DateTime.Now - startTime).TotalMilliseconds < 3000)
{ // 检查是否有数据可读取
if (_serialPort.BytesToRead > 0)
{ int bytesRead = _serialPort.Read(tempBuffer, 0, tempBuffer.Length);
responseBuffer.AddRange(tempBuffer.Take(bytesRead).ToArray());
// 检查是否包含帧尾 0D 0A(CR+LF),判断帧是否完整
if (responseBuffer.Count >= 2 &&
responseBuffer[responseBuffer.Count - 2] == 0x0D &&
responseBuffer[responseBuffer.Count - 1] == 0x0A)
{ break; // 已获取完整帧,退出循环
}
}
else
{ // 无数据时短暂休眠,避免CPU占用过高
System.Threading.Thread.Sleep(10);
}
}
return responseBuffer.ToArray();
}
catch (Exception ex)
{ Console.WriteLine($"读取响应失败:{ex.Message}"); return null;
}
}
///
/// 解析读取响应
///
private bool ParseReadResponse(byte[] response, out string hexValue)
{ hexValue = "";
// 有效响应至少包含:ACK(1) + 站号(1) + PC号(1) + 数据长度(2) + 数据(4) + 校验(2) + CR(1) + LF(1)
if (response.Length < 12)
return false;
// 检查响应头(ACK)
if (response[0] != 0x02)
return false;
// 提取数据(4位十六进制)
hexValue = Encoding.ASCII.GetString(response, 5, 4);
return true;
}
///
/// 解析写入响应
///
private bool ParseWriteResponse(byte[] response)
{ // 有效响应至少包含:ACK(1) + 站号(1) + PC号(1) + CR(1) + LF(1)
if (response.Length < 5)
return false;
// 检查响应头(ACK)
if (response[0] == 0x06)
return true;
return false;
}
///
/// 计算校验和
///
private string CalculateChecksum(string data)
{ int sum = 0;
foreach (char c in data)
{ sum += (int)c;
}
// 取低8位,转换为2位十六进制
return (sum & 0xFF).ToString("X2"); }
///
/// 验证是否为十六进制字符串
///
private bool IsHexString(string hex)
{ foreach (char c in hex)
{ if (!((c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'F') ||
(c >= 'a' && c <= 'f')))
{ return false;
}
}
return true;
}
///
/// 释放资源
///
public void Dispose()
{ Disconnect();
_serialPort?.Dispose();
}
}
}