基于FINS协议实现的C#欧姆龙PLC通信,包含串口/TCP双模式支持、FCS校验、内存区读写等核心功能
一、核心代码结构
// 通信接口定义
public interface IFinsClient : IDisposable
{
bool Connect();
bool ReadMemory(string area, ushort address, ushort length, out byte[] data);
bool WriteMemory(string area, ushort address, byte[] data);
void Disconnect();
}
// 串口实现类
public class FinsSerialClient : IFinsClient
{
private SerialPort _port;
private const int FCS_OFFSET = 2; // FCS校验码起始位置
public FinsSerialClient(string portName, int baudRate = 9600)
{
_port = new SerialPort(portName, baudRate, Parity.Even, 7, StopBits.Two);
_port.Handshake = Handshake.None;
_port.DataReceived += OnDataReceived;
}
public bool Connect()
{
try
{
_port.Open();
SendCommand("STX@00FA0000000037ETX"); // 初始化握手命令
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"连接失败: {ex.Message}");
return false;
}
}
public bool ReadMemory(string area, ushort address, ushort length, out byte[] data)
{
string cmd = BuildReadCommand(area, address, length);
SendCommand(cmd);
var response = ReadResponse();
return ParseReadResponse(response, out data);
}
private string BuildReadCommand(string area, ushort addr, ushort len)
{
var ms = new MemoryStream();
ms.WriteByte(0x01); // 读命令
ms.WriteByte((byte)GetAreaCode(area));
WriteAddress(ms, addr);
ms.WriteByte(0x00); // 高字节长度
ms.WriteByte((byte)len);
return Convert.ToBase64String(ms.ToArray());
}
private byte[] GetAreaCode(string area)
{
return area switch
{
"CIO" => 0x30, // CIO区代码
"WR" => 0x33, // WR区代码
"HR" => 0x34, // HR区代码
"DM" => 0x35, // DM区代码
_ => throw new ArgumentException("无效的内存区域")
};
}
private void WriteAddress(MemoryStream ms, ushort addr)
{
ms.WriteByte((byte)(addr >> 8)); // 高字节
ms.WriteByte((byte)addr); // 低字节
ms.WriteByte((byte)(addr >> 8)); // 扩展高字节
ms.WriteByte((byte)addr); // 扩展低字节
}
// 其他方法实现...
}
// TCP实现类
public class FinsTcpClient : IFinsClient
{
private TcpClient _client;
private NetworkStream _stream;
private readonly byte[] _header = {
0x02, 0x46, 0x49, 0x4E, 0x53 }; // FINS头
public bool Connect(string ip, int port = 9600)
{
_client = new TcpClient();
_client.Connect(ip, port);
_stream = _client.GetStream();
return SendCommand("STX@00FA0000000037ETX");
}
}
二、关键功能实现
1. FCS校验算法
private byte CalculateFCS(byte[] data)
{
byte checksum = 0;
foreach (var b in data.Skip(FCS_OFFSET)) // 跳过STX和长度字段
{
checksum ^= b;
}
return checksum;
}
// 带校验的帧构建
private byte[] BuildFrame(byte[] payload)
{
using var ms = new MemoryStream();
ms.WriteByte(0x02); // STX
ms.WriteByte((byte)payload.Length); // 长度
ms.Write(payload, 0, payload.Length);
ms.WriteByte(CalculateFCS(payload));
ms.WriteByte(0x03); // ETX
return ms.ToArray();
}
2. 异步通信处理
public async Task<byte[]> ReadAsync(int timeoutMs = 5000)
{
var buffer = new byte[4096];
var cts = new CancellationTokenSource(timeoutMs);
try
{
int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
return buffer.Take(bytesRead).ToArray();
}
catch (OperationCanceledException)
{
throw new TimeoutException("读取超时");
}
}
3. 响应解析器
public bool ParseReadResponse(byte[] response, out byte[] data)
{
if (response.Length < 10)
{
data = null;
return false;
}
// 校验FCS
var receivedFcs = response;
var calcFcs = CalculateFCS(response.Skip(2).Take(response.Length - 3).ToArray());
if (receivedFcs != calcFcs)
{
Debug.WriteLine("FCS校验失败");
data = null;
return false;
}
// 提取数据段
data = response.Skip(6).Take(response.Length - 8).ToArray();
return true;
}
三、完整使用示例
using (var plc = new FinsSerialClient("COM3"))
{
if (plc.Connect())
{
// 读取D100开始的2个字
if (plc.ReadMemory("DM", 100, 2, out byte[] data))
{
ushort value = (ushort)(data[0] << 8 | data[1]);
Console.WriteLine($"D100值: {value}");
}
// 写入CIO 200地址
plc.WriteMemory("CIO", 200, new byte[] {
0x01, 0x00 });
}
}
四、工业级优化方案
1. 连接池管理
public class FinsConnectionPool
{
private readonly ConcurrentBag<IFinsClient> _clients = new();
private readonly object _lock = new();
public IFinsClient GetClient()
{
if (_clients.TryTake(out var client))
return client;
return CreateNewClient();
}
private IFinsClient CreateNewClient()
{
var client = new FinsSerialClient("COM3");
client.Connect();
return client;
}
public void ReturnClient(IFinsClient client)
{
_clients.Add(client);
}
}
2. 批量操作优化
public class BatchCommand
{
private readonly List<byte[]> _commands = new();
public void AddRead(string area, ushort addr, ushort len)
{
_commands.Add(BuildReadCommand(area, addr, len));
}
public byte[] GetBatchData()
{
using var ms = new MemoryStream();
foreach (var cmd in _commands)
{
ms.Write(cmd, 0, cmd.Length);
}
return ms.ToArray();
}
}
3. 异常处理策略
public class FinsExceptionHandler
{
private readonly Dictionary<byte, Action<byte[]>> _handlers = new();
public void RegisterHandler(byte errorCode, Action<byte[]> handler)
{
_handlers[errorCode] = handler;
}
public void HandleError(byte[] errorFrame)
{
var code = errorFrame[2]; // 错误码位置
if (_handlers.TryGetValue(code, out var action))
{
action(errorFrame);
}
else
{
Debug.WriteLine($"未知错误码: {code:X2}");
}
}
}
五、调试与测试工具
1. 串口监控器
public class SerialMonitor
{
private SerialPort _port;
public void StartCapture(string portName)
{
_port = new SerialPort(portName);
_port.DataReceived += (s, e) =>
{
var data = new byte[_port.BytesToRead];
_port.Read(data, 0, data.Length);
File.WriteAllBytes($"capture_{DateTime.Now:yyyyMMddHHmmss}.bin", data);
};
_port.Open();
}
}
2. 帧分析工具
public static class FrameAnalyzer
{
public static string AnalyzeFrame(byte[] frame)
{
if (frame.Length < 5) return "无效帧";
var type = frame[0] switch
{
0x01 => "读命令",
0x02 => "写命令",
_ => "未知类型"
};
var address = (frame[2] << 8) | frame[3];
return $"类型: {type}, 地址: {address:X4}, 长度: {frame[4]}";
}
}
六、部署建议
硬件配置
工业PC:研华AIMB-501(支持宽温运行)
通信模块:欧姆龙CP1E PLC + FINS兼容适配器
安全配置
// 启用加密通信 _port.EncryptMode = SerialPortEncryptMode.AES256; _port.SetEncryptionKey("YourSecureKey123");性能调优
设置
ReadBufferSize = 65536启用
DoubleBuffered = true
参考代码 C#写的欧姆龙PLC通信库与源码 www.youwenfan.com/contentalh/112013.html
七、扩展功能实现
1. 数据监控看板
public class RealTimeMonitor
{
private Chart _chart;
public void UpdateData(byte[] data)
{
var value = BitConverter.ToInt16(data, 0);
_chart.Series[0].Points.AddY(value);
if (_chart.Series[0].Points.Count > 1000)
{
_chart.Series[0].Points.RemoveAt(0);
}
}
}
2. 自动重连机制
public class AutoReconnect
{
private readonly IFinsClient _client;
private readonly int _retryInterval = 5000;
public AutoReconnect(IFinsClient client)
{
_client = client;
Task.Run(CheckConnection);
}
private async Task CheckConnection()
{
while (true)
{
if (!_client.IsConnected)
{
Debug.WriteLine("尝试重连...");
await Task.Delay(_retryInterval);
_client.Connect();
}
await Task.Delay(1000);
}
}
}