基于FINS协议实现的C#欧姆龙PLC通信

简介: 基于FINS协议实现的C#欧姆龙PLC通信,包含串口/TCP双模式支持、FCS校验、内存区读写等核心功能

基于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]}";
    }
}

六、部署建议

  1. 硬件配置

    • 工业PC:研华AIMB-501(支持宽温运行)

    • 通信模块:欧姆龙CP1E PLC + FINS兼容适配器

  2. 安全配置

    // 启用加密通信
    _port.EncryptMode = SerialPortEncryptMode.AES256;
    _port.SetEncryptionKey("YourSecureKey123");
    
  3. 性能调优

    • 设置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);
        }
    }
}
相关文章
|
8天前
|
存储 人工智能 关系型数据库
OpenClaw怎么可能没痛点?用RDS插件来释放OpenClaw全部潜力
OpenClaw插件是深度介入Agent生命周期的扩展机制,提供24个钩子,支持自动注入知识、持久化记忆等被动式干预。相比Skill/Tool,插件可主动在关键节点(如对话开始/结束)执行逻辑,适用于RAG增强、云化记忆等高级场景。
498 41
OpenClaw怎么可能没痛点?用RDS插件来释放OpenClaw全部潜力
|
2月前
|
机器学习/深度学习 边缘计算 安全
C#实现OPC客户端
C#实现OPC客户端,结合OPC DA与OPC UA两种协议
|
5月前
|
监控 编译器 Windows
Qt5实现Windows平台串口通信
Qt5实现Windows平台串口通信
|
1月前
|
缓存 安全 测试技术
基于C#实现Modbus RTU通信
基于C#实现Modbus RTU通信
|
6月前
|
数据可视化 5G
Turbo码与卷积码误码率性能对比分析
Turbo码与卷积码误码率性能对比分析
|
6月前
|
算法 自动驾驶 机器人
MATLAB中实现LSD直线检测
MATLAB中实现LSD直线检测
|
8天前
|
Arthas 人工智能 Java
我们做了比你更懂 Java 的 AI-Agent -- Arthas Agent
Arthas Agent 是基于阿里开源Java诊断工具Arthas的AI智能助手,支持自然语言提问,自动匹配排障技能、生成安全可控命令、循证推进并输出结构化报告,大幅降低线上问题定位门槛。
442 43
我们做了比你更懂 Java 的 AI-Agent -- Arthas Agent
|
C++
基于 C++ 的 IEC60870-5-104 规约的主从站模拟数据通信
基于 C++ 的 IEC60870-5-104 规约的主从站模拟数据通信
404 0
|
6月前
|
移动开发 监控 网络协议
C#通过TCP/IP控制康奈视读码枪实现方案
C#通过TCP/IP控制康奈视读码枪实现方案
|
5月前
|
计算机视觉
MATLAB实现图像分割:Otsu阈值法
Otsu方法(大津法)是一种广泛使用的自动图像阈值分割技术,它通过最大化类间方差来确定最佳阈值。