C#上位机与KUKA机器人联动:EthernetKRL通讯实战
2026/8/31 17:37:24 网站建设 项目流程

简介:面向需要实现工业机器人远程监控与控制的C#开发者和机器人集成工程师,这套基于TCP通讯的库卡(KUKA)机器人上位机控制方案提供了一份可运行的完整工程。程序适配KUKA系统软件8.3版本,PC端基于.NET Framework 4.0开发,支持实时读取机器人各关节位置、导出CSV数据,并可通过上位机下发指令完成单步运动和指定坐标点运动。资源共68个文件,压缩包约36.47MB,主要包含C#上位机源码、KUKA端程序文件(src/sps/dat)、XML参数配置,以及KUKA官方Ethernet KRL中文文档和TCP通讯参数配置说明,便于对照学习与二次开发。目前已有737人学习下载,适合具备一定C#基础、正在调试库卡机器人以太网通讯的工程技术人员参考,可有效缩短通讯联调周期。 前段时间刚在产线上做完一套C#上位机与库卡(KUKA)机器人联动的项目,需求很典型:操作员在触摸屏上实时看到机器人当前末端坐标,输入目标点位后机器人自动走位。听起来就是一台TCP通讯的事,但真正把位置刷起来、把运动指令安全可靠地发下去,中间涉及KRL编程、EKI协议配置、C#端Socket处理和大量联调排错,还是有不少设计取舍的。这篇文章就把能直接复用的方案完整梳理一遍,从机器人端配置到上位机代码,该给的代码给全,该踩的坑也一并说清楚,给正在做机器人上位机开发的朋友当个参考。

1. 方案选型:KUKA对外通讯方式为什么是EthernetKRL

需求拆解下来其实只有两点:一是上位机要周期性拿机器人的实时位置,刷新率几十毫秒级别就够;二是上位机能下发目标坐标并控制机器人运动。基于这两点,KUKA对外通讯的常用路线基本就排除掉一大半了。

先看最传统的数字量I/O。通过CC20.1等通讯板卡配置数字量输入输出,确实能传输信号,但一个点位只能表示一位,要传X/Y/Z坐标就得按浮点数拆位去实现,工程量大且效率低下,适合做启停和到位信号这种简单场景,不适合做连续坐标传输。

再看总线方案,比如Profinet或者EtherNet/IP。这种方案在工业现场很常见,但配置过程比较重,需要硬件支持、配置GSD组态文件,还要在机器人控制器里做好地址映射。它更适合PLC作为主站去组态,纯C#上位机去接Profinet的话,要么引入额外的通讯库,要么就得有总线主站硬件,没必要。

OPC UA是近年来流行的一种选择。KUKA KSS 8.5以上版本可以通过选项包支持OPC UA服务器,上位机用现成的UA客户端库就能读写变量。它的优势在于信息模型标准化,适合MES、SCADA这类需要集成大量数据点的场景。但在实时性上,OPC UA的典型轮询周期在100ms级别,而且对于高频的位置数据刷新来说,协议开销偏大,做实时位置显示和运动控制都有点使不上劲。

最后落在两个方案上:EthernetKRL(EKI)和RobotSensorInterface(RSI)。RSI支持毫秒级实时反馈,官方文档推荐用于视觉伺服、力控这类需要闭环控制的场景,但配置极其繁琐,需要在WorkVisual里挂RSI对象,写大量的XML配置文件,开发周期长,对普通坐标监控项目来说属于杀鸡用牛刀。

EthernetKRL的定位恰好卡在中间。它是KUKA官方提供的基于TCP/IP的XML数据交换方案,通过KRL程序里的EKI函数库读取和发送数据。配置简单,开发效率高,通讯周期可以做到10到50毫秒,对HMI显示坐标和常规点位运动控制完全够用。下表把几种方案放在一起对比,结论很直观:

通讯方案典型刷新率开发难度适合场景
数字量I/O几十ms级(PLC周期)简单但传坐标很累启停、到位信号
Profinet/总线1-10ms复杂,需硬件组态PLC为主站的控制系统
OPC UA100ms级中等MES/SCADA数据集成
EthernetKRL10-50ms较低,纯Socket上位机坐标监控与点位控制
RSI1-12ms高,需WorkVisual配置视觉伺服、力控闭环

所以我的结论很明确:没有特殊实时性要求的话,C#上位机和KUKA机器人的TCP通讯,直接选EthernetKRL就是成本最低、见效最快的路线。

2. 机器人端部署:EKI配置文件和KRL程序骨架

EKI用起来像做菜先备料,机器人端需要把两件事准备好:一个是两个XML配置文件,一个是KRL收发程序。

2.1 两个配置文件分别管什么

EKI的配置存放在KRC控制器的C:\KRC\ROBOTER\Config\User\Common\EthernetKRL\目录下(KSS版本不同路径略有差异),核心文件是EthernetKRLConfiguration.xmlEthernetKRLCell.xml

EthernetKRLConfiguration.xml定义通讯的基本属性,我习惯把它理解成"通讯开关":

<?xml version="1.0" encoding="UTF-8"?> <ETHERNETKRL> <CONFIGURATION> <EXTERNAL> <TYPE>Server</TYPE> <IPADDRESS></IPADDRESS> <PORT>54600</PORT> </EXTERNAL> </CONFIGURATION> <CELLFILE>EthernetKRLCell.xml</CELLFILE> </ETHERNETKRL>

这里TYPEServer表示机器人作为TCP服务器端,由上位机主动连接,这是最容易理解的模式。PORT默认是54600,没特殊需求不用改。IPADDRESS在Server模式下可以留空,机器人打开4660端口监听,上位机直接连机器人控制器的IP即可。

EthernetKRLCell.xml才是真正定义"实时位置返回"和"运动控制"数据流的文件,可以理解成"数据字典":

<?xml version="1.0" encoding="UTF-8"?> <ETHERNETKRL> <SEND> <XML> <ELEMENT TAG="KRC"> <ELEMENT TAG="POS" TYPE="FRAME" SETVAR="CUR_POS"/> <ELEMENT TAG="RUN" TYPE="BOOL" SETVAR="IS_RUNNING"/> </ELEMENT> </XML> </SEND> <RECEIVE> <XML> <ELEMENT TAG="HMI"> <ELEMENT TAG="CMD" TYPE="INT" SETVAR="CMD"/> <ELEMENT TAG="X" TYPE="REAL" SETVAR="TAR_X"/> <ELEMENT TAG="Y" TYPE="REAL" SETVAR="TAR_Y"/> <ELEMENT TAG="Z" TYPE="REAL" SETVAR="TAR_Z"/> </ELEMENT> </XML> </RECEIVE> </ETHERNETKRL>

SEND部分定义机器人往外发什么,SETVAR指定了数据来源是KRL程序里的CUR_POSIS_RUNNING这两个变量。RECEIVE部分定义机器人收什么,上位机发过来的XML会被解析到TAR_XTAR_YTAR_ZCMD变量里。

配置改完以后,记得重启控制器或者重新激活EKI服务,修改才能生效。这个坑我在项目里踩过,第一次改完EthernetKRLCell.xml后没重启,上位机连是能连上,但收发的数据字段还是旧的,折腾了半天才反应过来。

2.2 KRL主程序:接收指令、执行运动、回传位置

机器人端的主逻辑我用了一个KRL后台循环程序,大致逻辑是这样:

DEF EKI_LOOP() DECL EKI_STRUC EKI_SRV DECL FRAME CUR_POS DECL FRAME TAR_POS DECL REAL TAR_X, TAR_Y, TAR_Z DECL INT CMD TAR_X = 0.0 TAR_Y = 0.0 TAR_Z = 0.0 CMD = 0 ; 初始化并打开EKI服务,参数“Server”对应配置文件中的连接名称 EKI_SRV = EKI_Init("Server") IF EKI_SRV.OK THEN IF EKI_Open(EKI_SRV) THEN LOOP IF EKI_Check(EKI_SRV) THEN ; 读取上位机下发的运动指令 CMD = EKI_GetInt(EKI_SRV, "CMD") TAR_X = EKI_GetReal(EKI_SRV, "X") TAR_Y = EKI_GetReal(EKI_SRV, "Y") TAR_Z = EKI_GetReal(EKI_SRV, "Z") ; 上位机把CMD置1表示请求运动 IF CMD == 1 THEN ; 基于当前实际位姿,只改X/Y/Z坐标,保持A/B/C姿态不变 TAR_POS = $POS_ACT TAR_POS.X = TAR_X TAR_POS.Y = TAR_Y TAR_POS.Z = TAR_Z PTP TAR_POS ; 运动完成后把CMD回置为0,握手协议告知上位机 CMD = 0 EKI_SetInt(EKI_SRV, "CMD", CMD) ENDIF ; 实时读取当前位置并回传 CUR_POS = $POS_ACT EKI_SetFrame(EKI_SRV, "POS", CUR_POS) ENDIF WAIT SEC 0.02 ENDLOOP ENDIF ENDIF EKI_Close(EKI_SRV) END

这个循环里的核心设计点是"先读后写"的流程:每一轮先取出上位机发来的指令,判断是否需要运动,然后把当前实际位置回传。WAIT SEC 0.02把循环周期控制在20毫秒,即50Hz的刷新率,对于坐标监控和常规点位控制来说非常充裕。

有一点必须提醒:我这里的EKI程序放在机器人运行的主程序里。实际项目里更推荐把它放到SPS后台程序中,因为SPS不占用机器人主程序的控制资源,也不影响运动指令的执行。SPS是KUKA的后台循环程序,适合做通讯和IO处理。

另外,EKI的FRAME类型数据在上位机收到的格式是XML属性,不是子元素。比如EKI_SetFrameCUR_POS发给上位机后,报文是这样:

<KRC> <POS X="123.45" Y="234.56" Z="78.90" A="10.5" B="-3.2" C="45.6"/> <RUN>TRUE</RUN> </KRC>

X、Y、Z、A、B、C是POS元素的属性,对应的坐标值全在属性里。这个细节如果不注意,写解析代码时容易一头雾水。

3. C#上位机实现:TCP连接、XML收发与数据解析

机器人端准备好了,C#这边的工作就清晰了。整个上位机核心模块可以拆成四块:连接管理、指令发送、数据接收解析、UI刷新。

3.1 TCP连接管理

我封装了一个KukaEkiClient类,用TcpClient实现基础通讯。有几个关键点要说一下:

public class KukaEkiClient : IDisposable { private TcpClient _tcp; private NetworkStream _stream; private readonly object _sendLock = new object(); private CancellationTokenSource _cts; public event Action<string> OnXmlReceived; public event Action OnConnectionLost; public async Task<bool> ConnectAsync(string ip, int port, int timeoutMs = 3000) { _tcp = new TcpClient(); _tcp.NoDelay = true; // 禁用Nagle算法,降低延迟 var connectTask = _tcp.ConnectAsync(IPAddress.Parse(ip), port); var completed = await Task.WhenAny(connectTask, Task.Delay(timeoutMs)); if (completed != connectTask) throw new TimeoutException($"连接超时: {ip}:{port}"); await connectTask; _stream = _tcp.GetStream(); _cts = new CancellationTokenSource(); _ = Task.Run(() => ReceiveLoop(_cts.Token)); return true; } }

第一是NoDelay = true。TCP默认开启Nagle算法,会把小雨滴攒成大包再发,虽然提高了网络利用率,但增加了延迟。我们和机器人通讯的数据本身就不大,追求的是及时性,必须关掉。

第二是连接超时用Task.WhenAny配合Task.Delay实现,避免机器人没开机时上位机界面卡死。

第三是接收循环放到后台线程,用CancellationToken来管理线程退出。

3.2 粘包拆包处理

这是TCP通讯里永远绕不开的话题。EKI发送数据是按报文发送的,但TCP协议是流式传输,数据到达对端时可能粘连、可能拆开。如果上位机简单粗暴地Read一次然后当完整XML解析,大概率会遇到解析异常。

我用了一个字符串缓存区,每次有数据先追加到缓存,然后尝试从缓存中提取完整报文:

private StringBuilder _receiveBuffer = new StringBuilder(); private void ReceiveLoop(CancellationToken token) { try { byte[] buffer = new byte[4096]; while (!token.IsCancellationRequested) { int bytesRead = _stream.Read(buffer, 0, buffer.Length); if (bytesRead > 0) { string data = Encoding.UTF8.GetString(buffer, 0, bytesRead); _receiveBuffer.Append(data); TryParseCompleteMessages(); } else { break; } } } catch { OnConnectionLost?.Invoke(); } } private void TryParseCompleteMessages() { string content = _receiveBuffer.ToString(); // 以KRC为根元素的报文为一条完整数据 while (true) { int start = content.IndexOf("<KRC>"); if (start < 0) { _receiveBuffer.Clear(); return; } if (start > 0) content = content.Substring(start); int end = content.IndexOf("</KRC>"); if (end < 0) { // 数据不完整,保留到下一轮 _receiveBuffer.Clear(); _receiveBuffer.Append(content); return; } string xml = content.Substring(0, end + "</KRC>".Length); content = content.Substring(end + "</KRC>".Length); OnXmlReceived?.Invoke(xml); } }

这里明确以<KRC></KRC>作为一条完整报文,遇到不完整就留在缓存里等下一个TCP段。实际项目中这个逻辑覆盖率很高,唯一的前提是和机器人端约定好了SEND报文的根元素。

3.3 解析位置数据并更新界面

当收到完整的XML报文后,解析就顺理成章了:

private void OnXmlReceived(string xml) { XDocument doc = XDocument.Parse(xml); XElement root = doc.Root; XElement posElem = root.Element("POS"); double x = (double)posElem.Attribute("X"); double y = (double)posElem.Attribute("Y"); double z = (double)posElem.Attribute("Z"); double a = (double)posElem.Attribute("A"); double b = (double)posElem.Attribute("B"); double c = (double)posElem.Attribute("C"); bool isRunning = (bool)root.Element("RUN"); // 非UI线程,需要封送 BeginInvoke(new Action(() => { txtX.Text = x.ToString("F2"); txtY.Text = y.ToString("F2"); txtZ.Text = z.ToString("F2"); // ... })); }

有一个容易忽略的坑:XML属性值可能带科学计数法,比如1.5E-05这种,(double)转换能处理好。但如果机器人端坐标数值很大,KRL的REAL精度是单精度浮点,C#这边用double接收后,

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询