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

基于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/contentcnr/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);
        }
    }
}

 

专注于matlab/simulink,电子电路,编程