You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#通过UDP基于FINS协议读取欧姆龙PLC DM区的实现方法

C# 基于FINS UDP协议读取欧姆龙PLC DM存储区实现方案

核心问题修正

你现有代码存在两个关键错误,必须先调整才能正常通信:

  • 本地UdpClient不能绑定9600端口,9600是PLC侧FINS UDP服务的默认监听端口,本地客户端应绑定系统分配的闲置端口,否则会出现端口冲突导致通信失败
  • 不能用Encoding.UTF8.GetString解析接收数据,FINS是二进制协议,所有帧都是字节流结构,文本解码会直接损坏数据

FINS UDP读DM区帧规则

读取DM区使用FINS标准的内存区读命令(命令码0x0101),帧结构固定:

  • DM区对应区域码为0x02,单通道长度2字节(16位无符号整数),地址采用3字节大端编码
  • 本地直连场景下网络号固定填0,节点号取IP地址最后一段,比如PLC IP为192.168.1.10则PLC节点号为0x0A,PC IP为192.168.1.20则本地节点号为0x14
  • 单次读取通道数建议不超过500,避免超过PLC单次通信长度限制

完整读请求帧字节定义:

字节位置内容说明固定值/规则
0-3FINS固定标识0x46 0x49 0x4E 0x53(ASCII字符FINS)
4-9帧头控制字段固定填0x00 0x00 0x00 0x0C 0x00 0x00
10-16寻址字段依次填0x00、PLC节点号、0x00、0x00、本地节点号、0x00、自增SID(请求序号,应答会原样返回用于匹配)
17-18命令码内存读固定为0x01 0x01
19区域码DM区固定为0x02
20-22起始地址3字节大端格式,按字寻址时最后一位填0,比如读DM100则填0x00 0x64 0x00
23-24读取长度2字节大端格式,比如读10个通道填0x00 0x0A

可直接复用的实现代码

代码需要引用System.Linq命名空间做字节序转换,WPF/WinForm均可直接使用:

using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Linq;

namespace OMRON_FINS
{
    public class FinsUdpClient
    {
        private readonly UdpClient _udpClient;
        private readonly IPEndPoint _plcEndPoint;
        private readonly byte _localNode;
        private readonly byte _plcNode;
        private byte _sid = 0;

        public FinsUdpClient(string plcIp, int plcPort = 9600)
        {
            _plcEndPoint = new IPEndPoint(IPAddress.Parse(plcIp), plcPort);
            // 取本机/PLC IP最后一段作为FINS节点号
            var localIp = GetLocalIPAddress();
            _localNode = byte.Parse(localIp.Split('.')[3]);
            _plcNode = byte.Parse(plcIp.Split('.')[3]);
            // 绑定本地任意闲置端口
            _udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
            _udpClient.Client.ReceiveTimeout = 2000;
        }

        public static string GetLocalIPAddress()
        {
            var host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (var ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    return ip.ToString();
                }
            }
            throw new Exception("本机无可用IPv4地址");
        }

        /// <summary>
        /// 读取DM区连续通道值
        /// </summary>
        /// <param name="startDm">起始DM地址,读DM100传100即可</param>
        /// <param name="length">读取通道数量</param>
        /// <returns>按地址顺序返回的16位无符号值数组</returns>
        public ushort[] ReadDmArea(int startDm, int length)
        {
            if (length < 1 || length > 500)
                throw new ArgumentException("单次读取长度范围为1-500");
            
            _sid++;
            List<byte> frame = new List<byte>();
            // 组装固定头
            frame.AddRange(new byte[] {0x46,0x49,0x4E,0x53,0x00,0x00,0x00,0x0C,0x00,0x00});
            // 组装寻址段
            frame.AddRange(new byte[] {0x00, _plcNode, 0x00, 0x00, _localNode, 0x00, _sid});
            // 组装读命令
            frame.AddRange(new byte[] {0x01, 0x01, 0x02});
            // 组装起始地址(C#默认小端,需转大端)
            frame.AddRange(BitConverter.GetBytes((ushort)startDm).Reverse());
            frame.Add(0x00);
            // 组装读取长度
            frame.AddRange(BitConverter.GetBytes((ushort)length).Reverse());

            // 发送请求并接收应答
            _udpClient.Send(frame.ToArray(), frame.Count, _plcEndPoint);
            byte[] recvBuffer = _udpClient.Receive(ref _plcEndPoint);
            
            // 校验应答合法性
            if (recvBuffer.Length < 23 + length * 2)
                throw new Exception("应答帧长度非法");
            if (recvBuffer[19] != _sid)
                throw new Exception("应答SID不匹配");
            ushort endCode = BitConverter.ToUInt16(recvBuffer.Skip(21).Take(2).Reverse().ToArray(),0);
            if (endCode != 0)
                throw new Exception($"FINS返回错误码:{endCode:X4}");

            // 解析通道数据
            List<ushort> result = new List<ushort>();
            for (int i = 23; i < 23 + length*2; i+=2)
            {
                result.Add(BitConverter.ToUInt16(recvBuffer.Skip(i).Take(2).Reverse().ToArray(),0));
            }
            return result.ToArray();
        }
    }
}

开发注意事项

  • WPF应用中不要在UI线程直接调用读取方法,需用Task.Run放到后台线程执行,读取完成后通过Dispatcher更新UI做可视化绑定,避免界面卡顿
  • 实时采集建议间隔不小于100ms,过高的请求频率会导致PLC通信负载过高
  • 如果需要解析32位整数、单精度浮点数,将连续2个/4个DM通道的字节按大端顺序拼接后转换即可,欧姆龙默认使用大端字节序
  • 跨网段通信时需要将代码中网络号字段替换为PLC路由配置中对应的实际值,本地直连场景填0即可

内容的提问来源于stack exchange,提问作者H.Daniel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 13:54:22