RTDE 125Hz同步链路:recipe配置、寄存器边界与大端解码

发布时间:2026/10/29 19:02:32
RTDE 125Hz同步链路:recipe配置、寄存器边界与大端解码 简介面向UR优遨机器人应用开发与自动化集成人员的RTDE实时数据交换手册聚焦外部程序如何通过标准TCP/IP与UR控制器同步数据而不破坏其实时特性。内容覆盖连接建立与同步循环两阶段流程逐项说明机器人、关节、工具与安全状态及模拟/数字I/O、通用寄存器等输入输出字段的类型与配置方法并完整梳理报文头、协议版本请求、控制器版本获取、文本消息、数据包及输入输出设置、启动与暂停等控制包格式同时给出二进制序列化与recipe配方机制、2048字节字段名上限等约束。资源含1个PDF文件约226KB便于随查随用。已有926人学习下载适合需要完成机器人状态绘图、I/O操作或与以太网/IP等现场总线驱动交互的工程人员作为协议查阅与联调排错参考。1. RTDE 到底解决什么问题30004 端口上的 125Hz 同步链路场景很具体你要在外围 PC 上实时拿到六个关节角画轨迹或者让 PLC 通过 Ethernet/IP 把寄存器值喂给控制器结果发现 Dashboard 那套 XML-RPC 慢得像拨号上网一条状态来回几十毫秒。RTDE 就是为这个场景准备的。它跑在标准 TCP/IP 上端口固定 30004默认以 125Hz 往客户端推数据同时不破坏控制器实时循环的任何时序。说白了它给你一条订阅-推送的二进制通道你把想要的字段列成清单发过去控制器按顺序打包推回来你要写的 I/O 和通用寄存器也按同样的清单反向送进去。这份手册把字段表、数据类型、八种控制包和组包规则列全了适合做上位机监控、视觉引导、力控外挂和现场总线桥接的人。它本身不难难在字节序和寄存器边界下面从两阶段模型开始拆。2. 两阶段模型recipe 配置与同步循环怎么搭起来2.1 setup 阶段到底在协商什么RTDE 不是“连上就吐数据”。连上 30004 以后客户端先要发一份菜单手册里叫 recipe。recipe 就是一组字段名输入一组、输出一组各配各的。控制器收到后按清单核对字段名是否存在、类型能不能对上然后回一个 recipe id。之后所有数据包都带这个 id客户端拿 id 对号入座。这里有个硬约束客户端发过去的输入/输出字段名列表最大 2048 字节。注意是“字段名列表”的字节数不是数据包大小。字段名是 ASCII 字符串每个名字前面带 2 字节长度。2048 字节大概能塞一两百个短字段名正常场景很难撞上限但如果你真按寄存器逐个订阅比如 64 个布尔寄存器各来一条就会开始紧张。我的习惯是优先订阅位宽大的聚合字段比如actual_digital_input_bits一个 UINT64 就覆盖了 18 路数字输入比逐个 bit 订阅省得多。setup 的返回分两种情况全部字段都认识就返回每个字段的类型码只要有一个字段名拼错、或者当前控制器版本不支持比如在 5.3 以前的控制器上订阅input_bit_register_64整个 setup 就返回失败不会部分成功。所以第一次接新控制器常见做法是先把最小字段集跑通再加字段别一上来就贴五十个字段名错了都不知道错在哪一行。output recipe 和 input recipe 分开配置各有自己的 id。只读不写就只配 output recipe要写 I/O再配 input recipe。两个 id 在 START 包里一起发给控制器。recipe id 由客户端自己定0 到 255 都行只要 START 时用同一个值。2.2 同步循环START/PAUSE 与数据顺序配置完发 START带上 output recipe id 和 input recipe id。从这一刻起控制器开始按你订阅的顺序以大约 125Hz 往这条 TCP 连接上推 DATA_PACKAGE。注意顺序包里的字段顺序严格等于 setup 时字段名列表的顺序不是字母序也不是字段表里的顺序。你在 setup 时把actual_q写在第一个解包时第一个字段就得按 6 个 double 去切。手册写得很清楚RTDE 的实时循环优先级高于 RTDE 接口本身。控制器算力紧张时会跳包而且跳掉的包不会补发你收到的永远是“最新”的那一帧。这对画轨迹、做监控没问题但如果你的逻辑依赖“每一帧都必须处理”就得自己用 timestamp 判断有没有断档而不是假设包间隔恒定。我见过有人用包计数当时间轴结果控制器一忙轨迹图上出现一段假匀速排查了半天才发现是丢包。input 方向则不同客户端发过去的包在收到的那个控制周期里就会被处理但不需要恒定速率。input 字段会保持最后一次收到的值所以你完全可以在按钮按下时只发一次standard_digital_output然后一直不动。这个“保持最后值”的特性很好用但也意味着你没法靠“不发包”来复位某个输出要复位必须显式发一个新值。还有一条容易忽略的规则同一时刻一个变量只能被一个 RTDE 客户端控制。你在两台 PC 上各开一个客户端去写同一个output_int_register后连的那个要么被拒要么行为不可预期。现场调试时如果发现写入时灵时不灵先查是不是有另一个客户端还挂着。2.3 用 Python 客户端把两阶段跑通官方示例是 Python 写的实际项目里我一般直接用封装好的 ur_rtde省掉自己拼字节的活。import rtde_receive import rtde_control ROBOT_IP 192.168.1.10 # 构造时内部完成连接 - 协议版本协商 - 配置 output recipe - START rtde_r rtde_receive.RTDEReceiveInterface(ROBOT_IP) # 按字段名取值字段顺序由库内部按 recipe 维护 q rtde_r.getActualQ() # 对应 actual_q6 个关节角单位 rad tcp rtde_r.getActualTCPPose() # 对应 actual_TCP_posex,y,z,rx,ry,rz ts rtde_r.getTimestamp() # 对应 timestamp控制器启动以来的秒数 print(timestamp%.4f q%s % (ts, [round(v, 4) for v in q])) # 控制接口用于写 speed slider、数字/模拟输出等 rtde_c rtde_control.RTDEControlInterface(ROBOT_IP) rtde_c.setSpeedSlider(0.3) # 0..1等价于 speed_slider_fraction rtde_r.disconnect() rtde_c.disconnect()逻辑说明RTDEReceiveInterface在构造时就完成了协议版本协商、output recipe 配置和 START所以拿到对象就能直接读字段。getActualQ对应 output 字段actual_qgetActualTCPPose对应actual_TCP_posegetTimestamp对应timestamp。disconnect会把连接和同步循环一起关掉不写这句进程退出时控制器那边可能还挂着一个半死的会话下次连会提示资源占用。参数说明ROBOT_IP是控制器地址默认端口 30004库内部写死但一般不用改。setSpeedSlider传的是比例值不是百分比数值0.3 表示 30%。如果要写数字输出用rtde_io.RTDEIOInterface不要拿 receive 接口硬塞。这段代码只覆盖了读。而手册里那堆协议包的价值在于当你要自己实现客户端、或者要在非 Python 环境里接 RTDE比如 C# 上位机、PLC 的 socket 功能块你得知道每一步在线上到底发了什么。所以下面还是把 raw socket 的握手走一遍。2.4 一个最小 raw socket 握手import socket import struct HOST 192.168.1.10 PORT 30004 # RTDE 用单字节 ASCII 码区分包类型这里只列握手要用的两个 RTDE_REQUEST_PROTOCOL_VERSION 86 # V RTDE_CONTROL_PACKAGE_SETUP_OUTPUTS 79 # O def send_packet(sock, ptype, payloadb): # 3 字节头2 字节长度 1 字节类型长度 类型字节 负载 size len(payload) 1 sock.sendall(struct.pack(!HB, size, ptype) payload) def recv_packet(sock): head sock.recv(3) if len(head) 3: raise ConnectionError(连接被关闭) size, ptype struct.unpack(!HB, head) body b need size - 1 while len(body) need: chunk sock.recv(need - len(body)) if not chunk: raise ConnectionError(读到一半断开) body chunk return ptype, body def encode_name(name): # STRING 编码2 字节长度 ASCII 内容 raw name.encode(ascii) return struct.pack(!H, len(raw)) raw with socket.create_connection((HOST, PORT), timeout2.0) as sock: # 第一步协商协议版本负载是 UINT16 send_packet(sock, RTDE_REQUEST_PROTOCOL_VERSION, struct.pack(!H, 2)) ptype, body recv_packet(sock) print(协商回包 type0x%02x accepted%d % (ptype, body[0])) # 第二步配置 output reciperecipe id 由客户端自己定通常从 0 开始 names [timestamp, actual_q, actual_TCP_pose] payload struct.pack(!B, 0) b.join(encode_name(n) for n in names) send_packet(sock, RTDE_CONTROL_PACKAGE_SETUP_OUTPUTS, payload) ptype, body recv_packet(sock) recipe_id body[0] type_codes body[1:].decode(ascii, errorsreplace) print(recipe_id%d types%s % (recipe_id, type_codes))逻辑说明send_packet把长度、类型和负载拼成完整包recv_packet先读固定 3 字节头再按长度字段把剩余字节读满。TCP 是流不能假设一次 recv 就能拿到整包这个循环是必须的。encode_name对应手册里 STRING 的编码2 字节长度加 ASCII 内容。返回的 body 第一个字节是控制器确认的 recipe id其余是每个字段的类型码顺序与请求一致。参数说明协议版本负载写 2表示请求 v2控制器如果只支持 v1回包里的 accepted 会是 0这时后续逻辑要降级。setup outputs 的 payload 第一个字节是客户端指定的 recipe id后面接字段名列表。字段名区分大小写actual_TCP_pose不是actual_tcp_pose。这段跑通你就能在不依赖任何第三方库的情况下确认字段名有没有写错、控制器支持哪个协议版本、返回类型对不对。很多“连不上”的问题用这二十行就能定位到底是网络、端口、字段名还是协议版本。3. 字段与类型选择inputs/outputs 清单怎么读、寄存器区间怎么分3.1 output 字段从 actual_q 到 safety_status_bitsoutput 是控制器往客户端推的字段。手册列了几十个日常高频的就那一组。我按用途整理了一张表订阅时对着挑。字段名类型用途与单位timestampDOUBLE控制器启动以来的秒数做时间对齐必带actual_qVECTOR6D实际关节角radactual_qdVECTOR6D实际关节速度rad/sactual_currentVECTOR6D实际关节电流Atarget_qVECTOR6D目标关节角算跟踪误差用actual_TCP_poseVECTOR6DTCP 位姿x,y,z 单位 mrx,ry,rz 是旋转向量actual_TCP_speedVECTOR6D线速度 m/s角速度 rad/sactual_TCP_forceVECTOR6DTCP 广义力已补偿负载ft_raw_wrenchVECTOR6D未补偿负载的原始力5.9.0 起才有actual_digital_input_bitsUINT640-7 标准8-15 可配置16-17 工具actual_digital_output_bitsUINT64同上输出侧robot_modeINT32机器人模式枚举safety_modeINT32安全模式safety_status_bitsUINT3211 个安全状态位3.10.0/5.4.0 起robot_status_bitsUINT32上电、程序运行、示教按钮等speed_scalingDOUBLE轨迹限速器的实际缩放joint_temperaturesVECTOR6D各关节温度摄氏度挑字段有两个原则。第一能做聚合就别拆散要数字输入状态就订actual_digital_input_bits一个 UINT64别订十八个 bit省 recipe 字节也省解包代码。第二注意版本门槛safety_status_bits在 3.10.0/5.4.0 才引入ft_raw_wrench要到 5.9.0老控制器上订这些字段 setup 会直接失败。还有一个容易混的点actual_TCP_force是补偿过负载的ft_raw_wrench是原始值做碰撞检测和做负载辨识选的不是同一个字段。3.2 input 字段数字/模拟输出与通用寄存器input 是客户端往控制器写的字段。它和 output 最大的区别是大量使用 mask 机制不是直接赋值。字段名类型说明standard_digital_output_maskUINT8位掩码置 1 的位才生效standard_digital_outputUINT8标准数字输出值configurable_digital_output_maskUINT8可配置数字输出的掩码configurable_digital_outputUINT8可配置数字输出值standard_analog_output_maskUINT8bit0-bit1 对应两路模拟输出standard_analog_output_typeUINT80电流 mA1电压 Vstandard_analog_output_0/1DOUBLE比例值 0..1speed_slider_maskUINT320不改进度1启用speed_slider_fractionDOUBLE速度比例input_bit_register_64..127BOOL高位布尔输入寄存器input_int_register_24..47INT32高位整型输入寄存器input_double_register_24..47DOUBLE高位浮点输入寄存器external_force_torqueVECTOR6D外部力/力矩输入配合 ft_rtde_input_enablemask 机制的逻辑是只有 mask 里为 1 的位output 里对应的值才会被写进控制器mask 为 0 的位保持原状。这跟 STM32 的 BSRR 寄存器一个思路好处是你改第三路输出时不用先读回整个字节。代价是新手容易只发 output 不发 mask然后纳闷为什么没反应。模拟输出的比例值是 0 到 1 的 double不是 0 到 10V 的电压值具体电压还是电流由standard_analog_output_type决定。3.3 数据类型与字节宽度对照手册里给了完整的数据类型表我把它和 Python 的 struct 格式对上了解包时直接查。类型位宽struct 格式大端说明BOOL8!B非 0 即 TrueUINT88!BUINT3232!IUINT6464!QINT3232!i二进制补码DOUBLE64!dIEEE 754VECTOR3D192!3d3 个 doubleVECTOR6D384!6d6 个 doubleVECTOR6INT32192!6i6 个 int32STRING变长!H bytes2 字节长度加 ASCII手册明确写了 network byte order数据永远按大端发送。你在 x86 上直接 memcpy 结构体double 会变成天文数字UINT32 会翻个个儿。struct 格式串前面那个!就是强制大端别省。3.4 寄存器 0-23 与 24-47 的归属边界这是最容易翻车的地方之一。手册里通用寄存器分两段input/output_int_register_0..23、input/output_double_register_0..23低段保留给 FieldBus/PLC 接口用。24..47高段留给外部 RTDE 客户端比如 URCAPS。布尔寄存器分得更细0..63保留给 FieldBus/PLC64..127才给 RTDE 客户端。这意味着什么如果现场已经用 Ethernet/IP 把 PLC 和控制器连起来PLC 在写int_register_0..23你的 RTDE 客户端也去写0..23两边就打架而且这种冲突不会报错只表现成“值偶尔不对”。血泪经验是接 RTDE 之前先问清楚现场总线占了哪段寄存器RTDE 只碰 24 以上的段。另外注意版本差异。高段 24..47 是 3.9.0/5.3.0 才引入的布尔高段 64..127 也是同一批。手上如果是 3.4/3.5 时代的控制器高段不存在字段名发过去 setup 会直接失败。这就是为什么 setup 之前一定要先确认控制器版本——手册每个字段后面都标了“Introduced in version”看字段表时别只看名字。4. 协议报文拆解header、控制包与 DATA_PACKAGE 的字节布局4.1 header 的 3 字节结构所有 RTDE 包共享同一个头2 字节长度加 1 字节类型共 3 字节。长度字段是“类型字节加负载”的字节数不包含长度字段自身。收到一个包先读 3 字节解出 size 和 type再读 size-1 字节负载一个完整包就齐了。为什么强调这个因为 TCP 不保消息边界。你可能一次 recv 拿到两个半包也可能拿到半个包。我见过有人直接sock.recv(65536)然后按固定偏移切本地回环测试没问题一到现场网络抖动就解出乱码。正确做法永远是“读头、按长度读满”。类型字节是个 ASCII 字符不是枚举整数。手册里那串RTDE_REQUEST_PROTOCOL_VERSION之类的名字落到线上就是一个字符。用十六进制 dump 看包会看到 0x56、0x4F 这种值。4.2 控制包全家桶与返回包名类型字符方向负载要点有无返回RTDE_REQUEST_PROTOCOL_VERSIONV客户端到控制器UINT16 版本号有1 字节 acceptRTDE_GET_URCONTROL_VERSIONv客户端到控制器无有版本字符串RTDE_TEXT_MESSAGEM双向v1/v2 格式不同无RTDE_DATA_PACKAGEU双向recipe id 加字段数据无RTDE_CONTROL_PACKAGE_SETUP_OUTPUTSO客户端到控制器recipe id 加字段名列表有类型码列表RTDE_CONTROL_PACKAGE_SETUP_INPUTSI客户端到控制器recipe id 加字段名列表有类型码列表RTDE_CONTROL_PACKAGE_STARTS客户端到控制器output id 加 input id有1 字节 successRTDE_CONTROL_PACKAGE_PAUSEP客户端到控制器无有1 字节 successsetup 阶段的包有返回同步阶段的 DATA_PACKAGE 没有。TEXT_MESSAGE 分两个协议版本v1 只有消息类型和内容v2 多了 warning level 等字段用来表达严重程度。实际调试时把 TEXT_MESSAGE 打出来很有用控制器拒绝 setup 时原因往往就藏在这条文本消息里而不是返回码里。4.3 DATA_PACKAGE 与大端解码DATA_PACKAGE 的结构header 加 1 字节 recipe id 加按 setup 顺序排列的字段数据。没有字段名没有分隔符纯靠顺序和类型宽度切。这就是为什么 setup 时的字段顺序不能乱也是为什么类型码必须存下来。字节序这一条要单独说。手册写的是 network byte order也就是大端。x86 是小端你如果直接用struct.unpack(d, ...)不加!解出来的 double 会是 1e308 级别或者负得离谱的数。这不是玄学是字节顺序反了。验证方法很简单订timestamp正常应该是平滑递增的浮点数如果解出来是天文数字先查字节序。4.4 手撸一个最小解码器import struct # setup 返回的类型码 - (struct 格式, 字节宽度) TYPE_MAP { B: (!B, 1), # BOOL U: (!B, 1), # UINT8 I: (!I, 4), # UINT32 L: (!Q, 8), # UINT64 i: (!i, 4), # INT32 d: (!d p a hrefhttps://download.csdn.net/download/weixin_43455581/76665681 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询