基于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);
        }
    }
}
相关文章
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
1129 0
|
网络协议 Windows
59【工控通信】ModbusTCP通讯之ModbusPoll客户端工具配置
【工控通信】ModbusTCP通讯之ModbusPoll客户端工具配置
1592 0
|
11月前
|
缓存 网络协议 安全
基于C#实现欧姆龙PLC FINS/TCP通信
基于C#实现欧姆龙PLC FINS/TCP通信
|
6月前
|
人工智能 编解码 数据可视化
豆包怎么导出成图片
本文分析2025–2026年豆包“导出图片难”这一高频痛点,揭示其在流程图、聊天记录、学习笔记等场景中因缺乏原生导出功能导致的模糊、截断、失真等问题;通过与ChatGPT、Claude等竞品对比,指出豆包在分辨率、格式保留、易用性上的短板;并提供基于浏览器插件的轻量级结构化解决方案——支持一键导出高清PNG/JPG,兼容复杂内容(LaTeX、表格、流程图),本地处理、安全高效。(239字)
|
9月前
|
C#
C# 实现简单的定时任务工具
基于 `System.Timers.Timer` 实现定时任务,支持周期性与单次执行。通过封装 Timer 类,提供启动、停止功能,并包含异常处理,确保任务稳定运行,适用于后台定时操作场景。
|
网络协议 关系型数据库 MySQL
【Linux环境】Centos7.x 安装MySQL,使用Navicat等远程连接工具报2003-Can‘t connect to MySQL server on(10060 Unknown err)
【Linux环境】Centos7.x 安装MySQL,使用Navicat等远程连接工具报2003-Can‘t connect to MySQL server on(10060 Unknown err)
1341 0
|
10月前
|
缓存 监控 NoSQL
阿里云NAS高并发场景下的读取延迟优化:NFS缓存机制深度解析与实战
本文解析阿里云NAS在高并发场景下NFS缓存导致的数据一致性问题,如电商图片更新延迟。深入分析客户端属性缓存机制,结合最佳实践提出三步优化:挂载参数调优、升级NFSv4.1、应用层缓存协同,并提供监控诊断方法,助力实现高性能与强一致性平衡。(238字)
|
12月前
|
存储 弹性计算 监控
阿里云渠道商:云文件存储 NAS 的价格是如何计算的?
阿里云NAS提供灵活计费与分层存储,支持按量付费和预购资源包,结合生命周期管理可降低存储成本超60%。通过冷热数据分离、合理选型与容量优化,助力企业高效降本。新用户可享专业指导与优惠支持。
|
存储 NoSQL 关系型数据库
阿里DataX极简教程
【5月更文挑战第1天】DataX是一个高效的数据同步工具,用于在各种数据源之间迁移数据,如MySQL到另一个MySQL或MongoDB。它的工作流程包括read、write和setting步骤,通过Framework协调多线程处理。其核心架构包括Job、Task和TaskGroup,支持并发执行。DataX支持多种数据源,如RDBMS、阿里云数仓、NoSQL和无结构化数据存储。例如,从MySQL读取数据并同步到ClickHouse的实践操作包括下载DataX、配置任务文件和执行同步任务。
3876 1
阿里DataX极简教程
|
存储 JSON Go
在Gin框架中优雅地处理HTTP请求体中的JSON数据
在Gin框架中优雅地处理HTTP请求体中的JSON数据