ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

VB.net实现欧姆龙CP1H PLC通讯:FINS UDP与Host Link协议详解

VB.net实现欧姆龙CP1H PLC通讯:FINS UDP与Host Link协议详解 简介面向工业自动化及上位机开发者的VB.NET与OMRON CP系列PLC通信示例工程重点演示基于FINS协议实现以太网通讯覆盖IO、DM、H位、W位等存储区域的读写控制并加入Socket编程、命令构造、响应解析与异常处理等关键环节。压缩包内含37个文件以VB.NET源码为主另有FCS校验计算器可执行程序、DLL依赖、XML配置及项目工程文件总大小仅145KB适合作为轻量级参考工程直接阅读。已有469人学习/下载。工程代码清晰展示了通过TCP连接PLC默认端口2404并构造FINS指令帧的过程同时给出处理网络超时、校验错误等实战注意事项。资源中还涉及地址映射与数据区域理解可帮助开发者在实际项目中快速定位并读写相应存储区是学习OMRON PLC上位机通信的不错起点。 前阵子帮客户做设备数据采集一台OMRON CP1H控制的多工位装配机要把每班产量、运行状态、关键温度传到上位机MES。上位机用VB.net写通讯方式一开始选了串口Host Link后来因为现场布线条件太差改成了以太网FINS UDP。整个过程踩了不少坑从协议字节顺序到节点号设置再到地址换算每一个都能让你卡上半天。这篇就把方案选型、协议拆解、代码实现和排查经验一起整理出来给常和欧姆龙CP系列PLC打交道、又需要自己写上位机的朋友做个参考。1. 通讯方案选型先搞清楚你的CP系列支持什么1.1 三种主流通讯路径对比OMRON CP系列家族里CP1H、CP1L、CP1E的硬件配置差异挺大。CP1H部分型号带以太网口CP1L多数带串口CP1E分N型和E型有的连串口都要靠扩展板。所以第一步不是写代码是拿着你的具体型号对着硬件手册看接口。主流的通讯路径无非三条串口Host LinkC-mode命令走RS-232或RS-485ASCII文本帧欧姆龙上位机链接的老牌协议。以太网FINS UDP走网口默认UDP端口9600二进制帧速度快适合周期性采集。以太网FINS TCP同样走网口基于TCP连接可靠但需要维护连接状态。三条路的差异主要体现在实时性、接线复杂度和开发难度上。串口方案最通用几乎每个CP系列都能接但波特率一般9600或115200一帧命令带返回适合低频读写。FINS UDP传一帧指令大概就几个毫秒适合批量读取或者多台设备轮询。FINS TCP和UDP的报文结构一样只是传输载体不同需要自己处理连接建立和断线重连。1.2 我为什么最终选了FINS UDP这个项目要求每500毫秒轮询一次读取10个字的数据还要检测报警信号。串口Host Link在这种频率下能做到但CPU占用率高而且现场设备柜里RS-485线路已经被变频器干扰得够呛。Ethernet口布线相对干净加上CP1H自带网口就直接绕过串口了。选UDP而不是TCP是因为数据采集场景对单次丢包不敏感下一轮马上又能补回来。UDP无连接代码结构简单不需要处理TCP的半关闭、粘包、重传这些东西。对西门子、三菱的PLC我一样优先UDP越简单的东西越不容易出错。当然如果要做远程下载程序、在线监控这种要求可靠连接的操作那还是得上TCP。注意一点不是所有CP系列都带网口。如果你的PLC是CP1E-N40不带以太网或者只有串口那就老老实实走Host Link。本文后面第四章会专门讲串口方案。2. 协议细节拆解FINS报文不是随便拼的2.1 十字节头部到底代表什么FINS是欧姆龙自己的应用层协议帧结构固定10字节头部 命令码 参数 数据。头部这10个字节是很多人的噩梦实际上逐个搞清楚就不难了字节名称含义0ICF信息控制字段网关命令填0x801RSV保留字段固定0x002GCT网关计数固定0x023DNA目标网络号同一网段填0x004DA1目标节点号即PLC的FINS节点号5DA2目标单元号CPU单元填0x006SA1源节点号上位机自己设为0x01到0xFE之间任意值7SA2源单元号上位机填0x008SID服务标识随机0-255用于匹配请求和响应最容易出错的就是DA1和SA1。DA1必须和PLC里CX-Programmer设置的FINS节点号一致SA1是你自己上位机的节点号两边不能重复。很多人的问题代码通一排查别的都没错就卡在这个节点号上PLC返回0101错误码意思是“本地节点错误”。2.2 内存区代码和地址编码FINS的读命令要指定内存区代码和起始地址。常用区域代码下面是固定的区域代码十六进制CIO区0xB0WR区工作继电器0xB1HR区保持继电器0xB2DM区数据内存0x82地址编码要注意字节顺序FINS报文里所有多字节数都是高位在前也就是BigEndian。比如DM100这个字地址十进制100转十六进制是0x0064放到帧里就是先发0x00再发0x64。很多人代码里直接把int转Byte数组忘了调字节序结果读回来的数据永远对不上。另外FINS地址是“字地址”不是位地址。想读DM100的第十位不能直接把100塞进去得用位操作或单独命令处理。我一般直接按字读回到上位机再解析Bit。2.3 命令码与响应结构读数据命令码是0101写数据命令码是0102。以读为例命令格式是命令码2字节 内存区代码1字节 起始地址2字节 数据个数2字节。响应的前四个字节是命令码和错误码后面才是实际数据。响应错误码里最常见的几个错误码含义常见原因0x0000正常无0x0101本地节点错误DA1节点号设错PLC和上位机节点重复0x0105帧格式错误命令长度不对命令码写错0x0111参数错误内存区代码或地址越界0x0201读数据越界起始地址加长度超出末尾拿到响应第一件事就是看错误码。我当时第一次联机PLC返回0105排查了半天才发现是多塞了一个字节的参数。3. VB.net实操写一个可复用的FINS UDP通讯类3.1 在VS里准备工程VB.net做这个没什么门槛直接在Visual Studio里建一个控制台或WinForms工程.NET Framework 4.6以上就够了不需要引用第三方库。需要用的是System.Net.Sockets里的UdpClient。你没有看错就这一个类搞定收发。它内部封装了Socket直接处理UDP的发送和接收比手写Socket省心很多。代码里需要设置三个核心参数PLC的IP地址、PLC的FINS UDP端口默认9600、PLC的FINS节点号。3.2 核心类结构与非阻塞收发我习惯写一个专门的类ClsOmronFins把组帧、发送、接收、解析封装好业务层只调用ReadWords和WriteWords就行。下面是核心代码Imports System.Net Imports System.Net.Sockets Imports System.Threading Public Class ClsOmronFins Private plcIP As String Private plcPort As Integer Private plcNode As Byte Private srcNode As Byte Private udp As UdpClient Public Sub New(ip As String, port As Integer, plcNodeId As Byte) Me.plcIP ip Me.plcPort port Me.plcNode plcNodeId Me.srcNode H3F 上位机节点可自定义 End Sub Public Function Connect() As Boolean Try udp New UdpClient() udp.Connect(plcIP, plcPort) udp.Client.ReceiveTimeout 1000 Return True Catch ex As Exception Return False End Try End Function Public Sub Close() If udp IsNot Nothing Then udp.Close() udp Nothing End If End Sub End Class注意ReceiveTimeout属性单位是毫秒。同步模式下如果PLC没响应会抛SocketException这正是我们想要的效果——超时后及时提醒而不是卡死界面。3.3 核心代码打包读命令、发送并解析响应下面这段是真正干活的。FINS帧的组包、发送和接收都放在这里。有一点必须强调所有的多字节数据都是BigEndian也就是高位在前。Public Function ReadWords(areaCode As Byte, startAddr As Integer, count As Integer) As Integer() 1. 构造FINS命令 0101 读 Dim cmd As New List(Of Byte)() cmd.Add(H0) 命令码 01 01 cmd.Add(H1) cmd.Add(areaCode) 内存区代码 cmd.Add(CByte((startAddr 8) And HFF)) cmd.Add(CByte(startAddr And HFF)) 起始地址 cmd.Add(CByte((count 8) And HFF)) cmd.Add(CByte(count And HFF)) 读取个数 Dim frame As Byte() BuildHeader() Dim fullCmd As Byte() New Byte(frame.Length cmd.Count - 1) {} Buffer.BlockCopy(frame, 0, fullCmd, 0, frame.Length) Buffer.BlockCopy(cmd.ToArray(), 0, fullCmd, frame.Length, cmd.Count) 2. 发送 Dim resp As Byte() Try udp.Send(fullCmd, fullCmd.Length) Dim remote As IPEndPoint Nothing resp udp.Receive(remote) Catch ex As Exception Throw New Exception(通讯超时或失败 ex.Message) End Try 3. 解析响应 If resp.Length 14 Then Throw New Exception(响应帧长度异常) End If Dim errCode As Integer (resp(12) 8) Or resp(13) If errCode 0 Then Throw New Exception(String.Format(PLC错误码: 0x{0:X4}, errCode)) End If 4. 提取数据返回Integer数组 Dim result(count - 1) As Integer For i As Integer 0 To count - 1 Dim base As Integer 14 i * 2 result(i) (resp(base) 8) Or resp(base 1) Next Return result End Function Private Function BuildHeader() As Byte() Dim h(9) As Byte h(0) H80 h(1) H0 h(2) H2 h(3) H0 h(4) plcNode h(5) H0 h(6) srcNode h(7) H0 h(8) CByte(Environment.TickCount And HFF) SID用系统时间低位做随机 Return h End Function这里响应帧的解析位置要仔细UDP返回的FINS响应里前10字节是头部第10、11字节是命令码第12、13字节是错误码14字节开始才是数据。由于读的是字每个值占2字节大端序。3.4 完整读写示例读DM100、写CIO100用上面的类读DM区前10个字再给CIO区100写个1代码长这样Dim omron As New ClsOmronFins(192.168.1.10, 9600, 1) If Not omron.Connect() Then Console.WriteLine(连接失败) Return End If Try 读DM100开始的10个字 Dim dmData As Integer() omron.ReadWords(H82, 100, 10) For i As Integer 0 To dmData.Length - 1 Console.WriteLine(DM (100 i) dmData(i)) Next 写CIO100为1 omron.WriteWords(HB0, 100, New Integer() {1}) Console.WriteLine(写入完成) Catch ex As Exception Console.WriteLine(异常 ex.Message) Finally omron.Close() End TryWriteWords的实现逻辑和ReadWords几乎一样只是命令码换成0102把要写的字换算成高八位低八位追加到帧尾。这里不贴全了照着ReadWords改就行。有个小技巧如果你要测试通讯又没有PLC在线可以在本地起一个假的UDP服务端按FINS响应格式回包就能验证组帧和解析逻辑。我当时就是用一个UDP回环调试工具这么干的代码一个字节一个字节对半小时就定位到问题是地址没有高位在前。3.5 顺带说一句Math.Sin在数据处理里的用法有网友问VB.net的Math.Sin和PLC通讯有什么关系。我在这个项目的模拟量标定里确实用到了。CP1H读出来的温度是数字量一般先做一个线性映射换算成温度值比如0-4095对应0-100度。如果要做曲线显示、平滑滤波或者生成正弦波测试上位机界面就用到了Math.Sin。举个例子我要在界面上画一个抖动非常规律的温度波动用来测试历史曲线控件代码就是For i As Integer 0 To 100 Dim temp As Double 50 10 * Math.Sin(i * Math.PI / 50) chart.Series(0).Points.AddXY(i, temp) Next别小看这个。上位机开发里读上来的数据合不合理经常需要先造一组数学上确定的数据来验证显示和逻辑Math.Sin就是最方便的信号发生器。4. 没有网口怎么办Host Link串口通讯完整方案4.1 Host Link报文格式与FCS校验算法CP系列不带网口的型号最常见通讯方法就是串口Host Link。帧格式比FINS还简单走ASCII文本设备号 命令字母 参数 FCS * CR设备号默认00可以改。命令里RD是读DM或CIOWR是写。读一个字的命令格式是00RD 地址(4位) 长度(4位) FCS * CR这里有个大坑Host Link命令里的地址是“字节地址”不是字地址必须把PLC的字地址乘2再转十六进制。比如想读DM100实际填的地址是200十六进制就是00C8。我第一次写串口通讯就卡在这里PLC一直返回FCS错误或者地址错误后来翻手册才发现这个设计。FCS校验是这么算的从‘’后面的第一个字符开始一直到长度字段的最后一个字符把所有字符的ASCII码按位异或最后转成两位十六进制大写字符。比如命令段是“00RD00C80001”把这串字符每个字符的ASCII码挨个异或得到一个0x00到0xFF的数转成大写十六进制就是FCS。VB.net里算FCS的代码Public Function CalcFcs(cmdStr As String) As String Dim fcs As Byte 0 For Each ch As Char In cmdStr fcs fcs Xor CByte(AscW(ch)) Next Return fcs.ToString(X2) End Function4.2 用SerialPort实现读写VB.net的SerialPort控件用起来很顺手参数设置这几点要注意CP系列串口默认设置一般是9600波特率、7位数据位、偶校验、2位停止位。但具体以PLC的CX-Programmer里外围端口设置为准有的项目改成115200、8、N、1的也有。初始化串口Dim sp As New IO.Ports.SerialPort(COM3, 9600, IO.Ports.Parity.Even, 7, IO.Ports.StopBits.Two) sp.ReadTimeout 1000 sp.Open()发送读DM100命令Dim cmdBody As String RD00C80001 注意地址00C8 DM100乘以2 Dim fcs As String CalcFcs(00 cmdBody) Dim frame As String 00 cmdBody fcs * vbCr sp.Write(frame) Thread.Sleep(50) Dim resp As String sp.ReadExisting()响应的格式类似“00RD00C8 0001 数据 FCS * CR”其中数据区是4位十六进制。解析的时候按空格split一下取倒数第三段就是数据。Host Link就这个特点调试直观串口调试助手里能看到明文命令特别适合初学者理解协议。代价就是效率低适合数据量不大、采集周期500毫秒以上的场景。如果上位机连接的是多台PLC可以把设备号改成01、02、03一主多从轮流轮询。5. 常见问题与排查技巧实录5.1 问题速查表我把这两个月里踩过的坑、同行问过我的问题汇总成了一张表基本覆盖了VB.net连欧姆龙CP系列的绝大部分故障现象可能原因排查思路FINS响应错误码0101DA1/SA1节点号重复或错误打开CX-Programmer确认PLC节点号确保上位机源节点不同FINS响应错误码0105帧长度不对命令码写错用串口调试助手或Wireshark抓包对帧结构逐字节核对FINS响应错误码0201起始地址加长度越界检查DM区最大地址读超过末尾就会报错串口收不到响应波特率、数据位、校验位、停止位不匹配在PLC设置里查看外围端口实际配置保证两边一致Host Link返回FCS错误FCS算法计算范围不对确认计算范围从后的第一个字符到长度字段尾端Host Link读回的数据不对地址没有乘2字地址先乘2再转十六进制比如DM100要填00C8UDP收不到PLC响应防火墙拦了UDP 9600在防火墙入站规则里放行UDP端口9600或暂时关闭防火墙测试通讯时好时坏PLC和上位机不在同一网段或IP冲突用ping命令检测网络确认PLC的IP和子网掩码配置正确5.2 几个必须提的独家细节第一个细节CP1H在RUN模式下写DM区是可以直接写的不需要切到PROGRAM模式。但如果你写的是保持继电器HR区或者想要修改PLC程序区就必须切到PROGRAM模式否则PLC直接拒绝写命令。第二个细节FINS UDP的SID字段最好每次不同。它本质是事务ID用来匹配请求和响应。如果每次都填同一个数在响应延迟比较大的时候前后两轮的命令可能搞混。我在代码里用Environment.TickCount的低8位每毫秒都在变实际用下来没出过问题。第三个细节串口Host Link的RS-485应用场景里两端一定要共地。我在一个项目里遇到过“时好时坏”的情况最后发现是现场两个设备的接地电位差太大导致485芯片频繁进入保护状态。加上一根共地线后通讯立刻稳定下来。第四个细节做上位机界面时所有PLC通讯操作别放在UI线程里面同步执行。特别是UDP ReceiveTimeout期间界面会整个卡住。建议用BackgroundWorker或Task.Run包一层回调的时候用Invoke更新UI。这不是通讯问题但几乎每个刚开始写工业上位机的朋友都会遇到。最后再分享一点这套通讯方案完整落地后我的体会是VB.net虽然不算时髦但在工业上位机领域依然有它的价值——开发速度快、调试直观、调用系统API方便尤其适合中小型设备的数据采集和监控项目。真正让人头疼的从来不是语言而是协议细节字节序、节点号、地址乘二、校验范围任何一个搞错都能让你怀疑人生。我自己的调试习惯是先不用代码拿一个通用的UDP调试工具或串口调试助手手工把一帧完整的命令发出去确认PLC有正确响应再开始写程序。这样做至少能帮你砍掉一半的联调时间。不要一上来就在VB.net里堆代码协议层没跑通写再多界面都是白搭。希望这篇对正在和OMRON CP系列PLC“搏斗”的你有帮助。如果你用的是三菱、西门子或者其他品牌PLC思路是一样的先把帧结构缩到最小再验证收发最后再写业务逻辑。祝调试顺利。本文还有配套的精品资源点击获取
返回列表