ARTICLE DETAIL

资讯详情

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

ABB机器人RAPID运算符实战:算术与逻辑运算的产线避坑指南

ABB机器人RAPID运算符实战:算术与逻辑运算的产线避坑指南 1. ABB机器人运算符工业现场最常被低估的“逻辑肌肉”在ABB机器人现场调试的第7年我拆过32台IRC5控制器写过187个RAPID程序也帮产线同事救过无数次“机器人突然不动”的火。但每次新人问“RAPID里怎么加减乘除”或者“IF判断为啥总走错分支”我都会停下来先问一句“你真搞懂运算符是怎么咬合逻辑、驱动动作的吗”——不是语法对不对而是运算符如何把工程师的意图一帧一帧翻译成伺服电机的扭矩指令。这恰恰是ABB机器人编程里最基础、最隐蔽、也最容易翻车的环节。今天这篇不讲教科书定义只聊我在汽车焊装线、锂电装配车间、食品包装产线踩过的坑、测过的边界、验证过的写法。核心关键词就五个ABB、机器人、运算符、算术运算符、逻辑运算符。它适合三类人刚拿到RobotStudio许可证的新手能写MoveL但卡在条件判断里的中级工程师还有负责产线稳定性的班组长——因为90%的“机器人误动作”和“节拍波动”根源都在运算符的隐式类型转换、优先级陷阱或布尔值误判上。下面所有内容都来自真实产线日志、示波器抓取的IO信号波形、以及RobotStudio仿真里反复复现的时序偏差。2. 运算符不是语法糖是RAPID程序的“神经突触”2.1 为什么ABB机器人必须用RAPID运算符绕不开的底层约束很多人以为运算符只是“ - * /”这些符号但在ABB机器人语境下它本质是RAPID语言与底层实时操作系统RTOS之间的协议接口。IRC5控制器运行的是VxWorks实时系统所有运动指令MoveL、MoveJ最终都要被分解为微秒级的轨迹插补点而每个插补点的位置、速度、加速度参数全靠RAPID程序里的运算符实时计算生成。举个最典型的例子某电池模组装配线要求机械臂末端在Z轴方向以0.5mm/s匀速下降同时X轴按正弦曲线微调补偿传送带偏移。这个需求如果写成VAR num z_speed : 0.5; VAR num x_offset : SIN(robtarget.trans.x * 3.1416 / 180) * 0.2;表面看没问题但实际运行时SIN函数的输入单位是度degree而robtarget.trans.x返回的是毫米mm。这里就触发了第一个陷阱运算符不进行单位自动转换。RAPID不会像MATLAB那样报错“单位不匹配”而是直接把毫米值当角度传给SIN结果算出一个完全偏离预期的浮点数导致X轴抖动。我亲眼见过因此导致的电芯刮伤停线27分钟。所以运算符在这里不是“计算工具”而是把物理量纲强行映射到数学空间的硬性桥梁——桥墩歪了整条路就塌。再深一层ABB机器人的运算符执行在任务级Task Level而非进程级。这意味着同一个RAPID任务里所有运算符共享同一套实时调度周期默认12ms。如果你在主任务里写了一串嵌套的逻辑运算IF (sensor_val 10 AND sensor_val 20) OR (flag_ready TRUE AND NOT flag_error) THEN MoveL p1, v1000, z10, tool1; ENDIF表面看是布尔逻辑但AND/OR运算符的执行耗时取决于左侧表达式的求值结果。RAPID采用短路求值Short-circuit Evaluation当sensor_val 10为FALSE时整个AND右边的sensor_val 20根本不会计算同理OR左边为TRUE时右边的flag_ready TRUE AND NOT flag_error直接跳过。这本是优化但在高节拍产线上反而成了隐患——因为短路导致的CPU周期波动可能让下一个运动指令的插补点生成延迟几个微秒累积起来就是轨迹抖动。我在某整车厂焊装线实测过同样一段MoveL指令在逻辑判断复杂度高的任务里TCP重复定位精度从±0.02mm恶化到±0.05mm。所以运算符在这里是实时性压力的传导器它把代码结构的松散度直接转化成机械臂的物理抖动。2.2 算术运算符毫米、毫秒、安培全靠它“对齐刻度”RAPID里的算术运算符、-、*、/、MOD、^看似简单但它们的“刻度对齐”能力决定了整个程序的物理可信度。我们先看最常被忽略的取余运算符MOD。在ABB机器人中MOD不是简单的“a除以b的余数”而是有符号整数的模运算且遵循“被除数符号决定结果符号”的规则。比如VAR num a : -7; VAR num b : 3; VAR num c : a MOD b; ! 结果是 -1不是 2这个特性在做循环分拣时极其关键。某食品包装线要求机械臂每抓取5件产品就触发一次托盘满载信号。如果写成counter : counter 1; IF counter MOD 5 0 THEN SetDO do_full_pallet, 1; ENDIF当counter从-1递增到0时-1 MOD 5等于-1永远不等于0满载信号永远不会触发。正确写法必须强制转正IF ABS(counter) MOD 5 0 THEN ! 或者用 IF (counter 0) AND (counter MOD 5 0)这就是算术运算符的“刻度”陷阱它不处理业务逻辑的正负约定只忠实地执行数学定义。再看幂运算符^。RAPID里2^3等于8没问题但2^0.5会直接报错“Exponent must be integer”因为RAPID的^只支持整数指数。想算平方根必须用SQRT()函数。很多新手试图用x^0.5替代结果程序在仿真里跑得好好的一上真实控制器就崩溃——因为IRC5的RAPID解释器对浮点指数零容忍。这背后是硬件资源限制IRC5的浮点运算单元FPU不支持非整数幂运算编译器在加载时就拒绝这种语法。最隐蔽的是除法运算符/。在RAPID中10/3的结果是3.3333333333333335但如果你把它赋值给一个num变量再用于位置计算VAR num pos_x : 10/3; MoveL Offs(pHome, pos_x, 0, 0), v100, z10, tool1;问题来了pos_x的精度是15位小数但IRC5的运动控制器内部位置寄存器只有32位浮点精度约7位有效数字。多出来的8位小数在插补时被截断导致实际TCP位置比预期偏移0.0000001mm——单次无关紧要但连续1000次后累积误差可能达到0.1mm超出视觉引导系统的容差。我的解决方案从来不是“提高精度”而是主动降维用整数运算代替浮点运算。比如把单位从毫米换成微米VAR num pos_x_um : 10000/3; ! 10000微米 ÷ 3 3333.333...微米 VAR num pos_x_mm : pos_x_um / 1000; ! 最后一步才转回毫米减少中间截断这样整数除法的截断发生在更高精度层级最终误差可控。算术运算符在这里不是计算器而是物理世界与数字世界的刻度校准仪——你不用它它就按自己的刻度乱标你用对了它才帮你把蓝图精准投射到现实。2.3 逻辑运算符布尔值背后的“电压开关”RAPID的逻辑运算符AND、OR、NOT常被当成“真假判断”但它的真实身份是数字IO信号的软开关。在ABB机器人里TRUE和FALSE不是抽象概念而是对应着0V和24V的电压状态。当你写IF di_sensor TRUE THENRAPID实际在读取DI端口的电压并将其映射为布尔值。但这个映射过程充满物理噪声。我用示波器抓过IRC5的DI输入信号一个标称“稳定高电平”的传感器信号在10ms内会有3~5次微秒级的电压跌落毛刺幅度在18~22V之间。RAPID的默认去抖动时间是10ms意味着只要毛刺持续时间10ms它就会被滤掉di_sensor保持TRUE。但如果你在逻辑判断里嵌套了多个ANDIF di_sensor1 TRUE AND di_sensor2 TRUE AND di_sensor3 TRUE THENRAPID会依次读取三个DI端口。由于硬件采样存在微秒级时序差某个端口可能恰好在毛刺窗口被读取返回FALSE导致整个AND链断裂。这不是代码bug是电磁兼容EMC设计的物理极限。解决方案不是改代码而是用WaitDI指令替代即时判断WaitDI di_sensor1, 1, 0.1; ! 等待di_sensor11且持续0.1秒抗毛刺 WaitDI di_sensor2, 1, 0.1; WaitDI di_sensor3, 1, 0.1;WaitDI的0.1秒等待本质是让硬件滤波电路充分工作把毛刺彻底平滑掉。逻辑运算符在这里暴露了软件逻辑与硬件物理的鸿沟——你以为在写条件其实是在调试电路。另一个致命陷阱是NOT运算符的“空悬”风险。RAPID里NOT di_sensor在di_sensor未定义或IO未连接时会返回UNKNOWN而UNKNOWN在IF判断中被视为FALSE。某次产线调试一个备用DI点没接线程序里写了IF NOT di_spare THEN ! 执行安全停机 ENDIF结果di_spare是UNKNOWNNOT UNKNOWN还是UNKNOWNIF块被跳过安全停机失效。正确做法是显式初始化VAR bool di_spare_init : FALSE; ... IF NOT di_spare_init OR di_spare FALSE THEN ! 安全停机 ENDIF逻辑运算符不是思维游戏它是把电气信号、硬件状态、软件意图拧在一起的扭矩扳手——拧轻了松动拧重了崩裂。3. 核心细节解析从RobotStudio仿真到产线实机的12个生死关3.1 运算符优先级括号不是可选项是生存必需品RAPID的运算符优先级表见官方手册RAPID Instructions, Functions and Data Types和C语言几乎一致但有一个关键差异关系运算符、、的优先级高于逻辑运算符AND、OR。这意味着IF a b AND c d THEN等价于IF (a b) AND (c d) THEN没问题。但一旦加入算术运算IF a b c * d AND e f THEN这里和*优先级高于所以先算ab和c*d再比较大小最后AND。看起来合理但危险在于人类阅读习惯的欺骗性。我见过最惨的案例某激光切割线要求“当温度80℃且功率5kW时启动冷却泵”。程序员写了IF temp 80 AND power 5 THEN StartCooling(); ENDIF测试时一切正常。上线后某天环境温度骤升temp传感器读数跳变到120℃但temp 80返回TRUEpower 5也TRUE冷却泵启动。问题出在哪power变量单位是瓦特W不是千瓦kWpower 5实际是“功率5瓦”而激光器额定功率是5000W永远满足。真正该写的是power 5000。但为什么仿真没发现因为RobotStudio默认的power仿真值设为30003000 5为FALSE冷却泵不启动逻辑看似正确。运算符优先级本身没错错的是它放大了单位混淆的后果。解决方案只有一条所有涉及物理量的比较必须显式标注单位! 温度单位℃功率单位W IF temp 80 AND power 5000 THEN ! 强制写5000杜绝“5kW”的歧义 StartCooling(); ENDIF括号在这里不是为了语法清晰而是把物理量纲钉死在代码里防止大脑在阅读时自动补全错误单位。3.2 类型转换隐式转换是产线夜班的噩梦源头RAPID是强类型语言但允许有限的隐式转换而这正是事故高发区。最典型的是num与string的混合运算。比如VAR string msg : Count: ; VAR num count : 5; VAR string full_msg : msg count; ! 合法RAPID自动把count转成5看起来很智能但问题在精度丢失。count是num类型精度15位转成string时默认只保留6位小数。如果count : 3.141592653589793msg count结果是Count: 3.141593后面9位全丢。更糟的是num与bool的转换VAR num x : 0; IF x THEN ! RAPID规定0转为FALSE非0转为TRUE ! 这里不会执行 ENDIF这没问题。但如果你从PLC读取一个INT型状态字通过Profibus传到ABB机器人存入num变量VAR num plc_status; ... GetSystemData plc_status, PLC_STATUS; ! 假设PLC_STATUS是INT值为16#0001plc_status现在是num类型值为1。IF plc_status THEN为TRUE。但如果PLC状态字是16#8000即-32768plc_status值为-32768IF plc_status THEN依然为TRUE因为非零。而你的业务逻辑本意可能是“只有bit0置位才TRUE”结果所有负数状态都被当成了TRUE。隐式转换把二进制位操作扭曲成了十进制数值判断。血泪教训所有来自外部系统的数据必须用BitAnd()、BitOr()等位运算函数处理而不是依赖IF的隐式转换! 正确只检查bit0 IF BitAnd(plc_status, 1) 1 THEN ! bit0为1 ENDIF类型转换不是便利功能它是把不同系统数据格式强行塞进同一套语法规则的暴力适配器——适配不好系统就崩。3.3 位运算符被严重低估的“硬件级控制开关”RAPID的位运算符BitAnd、BitOr、BitXor、BitNeg、Shl、Shr是直接操控硬件寄存器的钥匙但90%的程序员只用BitAnd查状态。其实Shl左移和Shr右移才是高效控制的核心。某汽车厂车身搬运项目需要根据车型代码1~8快速设置夹具气阀组合。传统写法是8个IFIF model 1 THEN SetDO do_valve1,1; SetDO do_valve2,0; ... ENDIF IF model 2 THEN ...不仅冗长而且每次修改都要动8处。用位运算一行搞定! 预定义阀门掩码valve_mask[1] 16#0001, valve_mask[2] 16#0002, ..., valve_mask[8] 16#0080 VAR num valve_pattern : valve_mask[model]; SetDOGroup do_valves, valve_pattern; ! 一次性设置8路DO这里valve_mask数组用十六进制定义model作为索引valve_pattern就是对应阀门的二进制模式。SetDOGroup指令直接把num值写入DO组寄存器毫秒级完成。位运算符在这里是把业务逻辑车型和硬件物理气阀通断直接映射的翻译官——省掉中间层效率翻倍故障点归零。3.4 赋值运算符 不是“给值”是“建立引用”RAPID的赋值运算符:常被误解为“把右边的值拷贝给左边”。实际上对于复杂数据类型如robtarget、pose:执行的是浅拷贝Shallow Copy。看这个经典陷阱VAR robtarget pStart : [[0,0,0],[1,0,0,0],[0,0,0,0],[0,0,0,0]]; VAR robtarget pCurrent : pStart; ! 浅拷贝pCurrent和pStart指向同一内存地址 MoveL pCurrent, v100, z10, tool1; pCurrent.trans.x : pCurrent.trans.x 100; ! 修改pCurrent MoveL pCurrent, v100, z10, tool1; ! 第二次移动x坐标比第一次多100mm看起来合理但问题在于pStart.trans.x也被改成了100因为pCurrent和pStart共享同一块内存。第二次MoveL的起点不再是原始pStart而是被污染的值。这导致轨迹偏移工件报废。解决方案只有两个一是用Copy函数深拷贝VAR robtarget pCurrent : Copy(pStart); ! 深拷贝独立内存二是用Offs()函数生成新目标点VAR robtarget pCurrent : Offs(pStart, 100, 0, 0); ! 基于pStart偏移不修改原值赋值运算符在这里暴露了RAPID内存管理的底层真相它不是值传递而是引用传递——你给的不是值是内存地址的指针。理解这点才能避开90%的“目标点莫名漂移”问题。4. 实操过程从零搭建一个抗干扰的“视觉引导分拣”RAPID模块4.1 需求拆解为什么这个案例能覆盖90%的运算符痛点我们以一个真实产线需求为例某锂电池电芯视觉分拣站要求机械臂根据相机识别的电芯尺寸长、宽、厚单位mm自动选择夹具姿态旋转角度、Z向高度并判断是否合格尺寸公差±0.1mm。这个需求直击运算符四大痛点算术运算符尺寸计算需加减乘除、取余分拣托盘编号、幂运算面积计算逻辑运算符合格判断需多重条件AND/OR且涉及传感器信号抗干扰比较运算符公差判断需精确到0.1mm单位一致性是生命线位运算符夹具姿态选择需根据尺寸组合查表位掩码最高效。整个模块在RobotStudio 2023.3中开发目标控制器为IRC5 with FlexPendant RW 6.12.01。4.2 模块架构三层隔离设计把运算符风险锁死我采用“输入-计算-输出”三层架构每层用不同运算符策略层级功能运算符策略关键防护Input Layer读取相机数据、传感器状态用WaitDI抗毛刺NumToStr显式转字符串所有输入加ValidCheck()函数验证范围Calculation Layer尺寸计算、公差判断、姿态查表全部用num类型单位统一为微米MOD前先ABS()关键计算加LogMessage记录中间值Output Layer设置夹具、触发气阀、发送OK信号SetDOGroup用位掩码MoveAbsJ用Offs()生成目标点输出前用TestDO确认硬件响应这样设计把运算符的风险隔离在计算层输入输出层只做“安全搬运”。4.3 核心代码实现每一行都带着产线教训Step 1安全输入Input Layer! --- 输入验证函数 --- PROC ValidCheck(num val, num min_val, num max_val, string desc) IF val min_val OR val max_val THEN LogMessage INPUT ERROR: desc out of range [ NumToStr(min_val) , NumToStr(max_val) ], got NumToStr(val); Stop; ! 立即停机不带任何犹豫 ENDIF ENDPROC ! --- 主输入流程 --- VAR num cam_length_um, cam_width_um, cam_thick_um; VAR bool sensor_ok; ! 用WaitDI确保传感器稳定 WaitDI di_camera_ready, 1, 0.05; ! 等待相机就绪信号持续50ms WaitDI di_sensor_ok, 1, 0.05; ! 等待传感器OK信号 ! 读取相机数据假设通过Socket接收已转为num cam_length_um : GetCamData(LENGTH); ! 单位微米 cam_width_um : GetCamData(WIDTH); cam_thick_um : GetCamData(THICK); ! 输入验证电芯长度10000~12000微米10~12mm ValidCheck(cam_length_um, 10000, 12000, CAM_LENGTH); ValidCheck(cam_width_um, 5000, 6000, CAM_WIDTH); ValidCheck(cam_thick_um, 2000, 3000, CAM_THICK);这里ValidCheck是核心防护。我见过太多因相机通信偶发错误导致cam_length_um读成-1或1e6直接让机械臂撞机。Stop指令比TPWrite报警更果断——安全无小事。Step 2鲁棒计算Calculation Layer! --- 公差判断单位微米--- VAR bool is_pass : TRUE; VAR num tol_um : 100; ! ±0.1mm ±100微米 ! 严格比较避免浮点误差 IF ABS(cam_length_um - 11000) tol_um THEN ! 标准长11mm11000um is_pass : FALSE; LogMessage FAIL: LENGTH NumToStr(cam_length_um/1000) mm, tolerance 11.0±0.1mm; ENDIF ! --- 夹具姿态查表位运算核心--- ! 预定义姿态掩码bit0旋转0°, bit1旋转90°, bit2Z高, bit3Z低 ! 组合长11.5mm → 旋转90°宽5.5mm → Z高厚2.5mm → Z低 VAR num pose_mask : 0; IF cam_length_um 11500 THEN ! 长11.5mm pose_mask : BitOr(pose_mask, 16#0002); ! bit1置位旋转90° ENDIF IF cam_width_um 5500 THEN ! 宽5.5mm pose_mask : BitOr(pose_mask, 16#0004); ! bit2置位Z高 ENDIF IF cam_thick_um 2500 THEN ! 厚2.5mm pose_mask : BitOr(pose_mask, 16#0008); ! bit3置位Z低 ENDIF ! --- 计算托盘编号取余防负--- VAR num tray_id : ABS(cam_length_um) MOD 4 1; ! 1~4号托盘ABS防负值注意ABS(cam_length_um) MOD 4——这是产线血泪换来的写法。曾经有次相机故障cam_length_um返回-5000-5000 MOD 4得-0托盘编号变成0SetDOGroup写入无效地址控制器报错停机。Step 3安全输出Output Layer! --- 输出前硬件确认 --- IF NOT TestDO(do_gripper_open) THEN LogMessage ERROR: Gripper not responding; Stop; ENDIF ! --- 设置夹具姿态位掩码--- SetDOGroup do_pose_ctrl, pose_mask; ! 一次性设置所有姿态DO ! --- 生成目标点Offs避免污染原点--- VAR robtarget pPick : [[0,0,0],[1,0,0,0],[0,0,0,0],[0,0,0,0]]; VAR robtarget pPickOffset : Offs(pPick, 0, 0, cam_thick_um/1000); ! Z向偏移单位转回mm ! --- 执行分拣 --- MoveL pPickOffset, v200, z10, tool1; WaitTime 0.5; SetDO do_gripper_close, 1; WaitTime 0.3; ! --- 根据合格与否分拣 --- IF is_pass THEN ! 合格放标准托盘 VAR robtarget pDrop : Offs(pTrayBase, (tray_id-1)*200, 0, 0); ! tray_id1~4间距200mm MoveL pDrop, v200, z10, tool1; SetDO do_gripper_open, 1; ELSE ! 不合格放NG托盘 MoveL pNGTray, v200, z10, tool1; SetDO do_gripper_open, 1; ENDIFOffs()函数是灵魂。它基于原点生成新点绝不修改pPick保证每次循环起点绝对一致。TestDO在输出前确认硬件在线比事后报错强一万倍。4.4 RobotStudio仿真验证三步压力测试法光写代码不够必须仿真验证。我用RobotStudio做三步压力测试边界值测试手动设置cam_length_um 10000, 12000, 9999, 12001观察is_pass是否正确翻转LogMessage是否输出预期信息毛刺注入测试在di_camera_ready信号上叠加10ms脉冲噪声验证WaitDI是否过滤成功程序是否跳过错误帧负载压力测试在主循环里加FOR i FROM 1 TO 1000 DO ... ENDFOR模拟高节拍用RobotStudio的“Cycle Time Analyzer”查看每个运算符耗时确保BitOr、ABS、MOD总和5ms。仿真通过后才敢烧录到真实控制器。记住RobotStudio不是玩具是产线的数字孪生体——它暴露出的问题99%会在真实世界重现。5. 常见问题与排查技巧实录产线老司机的17条硬核笔记5.1 运算符相关故障速查表现象可能原因排查步骤解决方案机器人轨迹抖动节拍不稳定逻辑运算符短路导致CPU周期波动MOD负数结果异常1. 用RobotStudio“Task Monitor”看主任务周期波动2. 检查所有MOD运算加ABS()用WaitDI替代即时IF所有MOD前加ABS()IF判断总是走错分支隐式类型转换失败如num转bool传感器毛刺未滤除1.TPWrite打印所有判断变量值2. 示波器测DI信号波形改用WaitDI用BitAnd()替代IF var THENMoveL目标点偏移每次都不一样robtarget浅拷贝被意外修改单位混淆mm vs um1. 检查所有:赋值是否用了Copy()2.TPWrite打印pTarget.trans.x一律用Offs()生成新点所有尺寸计算用微米单位程序在仿真跑得好上机就崩溃^用了浮点指数String拼接导致内存溢出1. 检查所有^确保指数为整数2.TPWrite打印字符串长度用SQRT()替代x^0.5字符串拼接前用StrLen()检查长度DO信号不动作但TP显示已置位SetDOGroup掩码位数超限硬件DO组配置错误1.TPWrite打印掩码值确认bit数≤82. 查Controller Configuration中DO组分配掩码用16#FF8位DO组配置匹配掩码宽度5.2 我踩过的5个最深的坑附真实日志坑1NOT的UNKNOWN陷阱现象安全门关闭信号di_safety_door未接线程序里IF NOT di_safety_door THEN Stop;没触发。日志TPWrite di_safety_door NumToStr(di_safety_door);输出di_safety_door0其实是UNKNOWN被转成0教训RAPID里未定义变量默认为0NOT 0是1TRUE但NOT UNKNOWN是UNKNOWN而UNKNOWN在IF中当FALSE。永远不要依赖未初始化变量的逻辑值。坑2MOD的负号灾难现象托盘计数从1跳到0再跳到-1。日志TPWrite counter NumToStr(counter) , MOD4 NumToStr(counter MOD 4);当counter-1输出MOD4-1教训MOD结果符号随被除数ABS()是保命符。所有计数类MOD前面必加ABS()。坑3字符串拼接的精度丢失现象TPWrite Pos: NumToStr(pTarget.trans.x);显示Pos:123.456但实际是123.456789导致视觉引导失准。日志用LogMessage记录pTarget.trans.x原始值对比NumToStr结果教训NumToStr默认6位小数。需要高精度时用FormatStr(%6.3f, pTarget.trans.x)。坑4WaitDI的超时陷阱现象程序卡在WaitDI di_sensor, 1永远不往下走。日志TPWrite Waiting for DI...一直输出无后续教训WaitDI默认无限等待。必须加超时参数WaitDI di_sensor, 1, 2.0;等2秒超时报错。坑5BitAnd的位宽误解现象BitAnd(plc_status, 16#0001)总是0但PLC明明发了bit0。日志TPWrite PLC_STATUS NumToStr(plc_status, 16);输出PLC_STATUS10000十六进制教训plc_status是num类型16#0001是整数但BitAnd对num执行的是32位运算。PLC状态字是16位高位补010000的bit0是0。用BitAnd(WORD_TO_NUM(plc_status), 16#0001)先转num再位运算。5.3 给新手的3条铁律所有物理量必须带单位注释VAR num length_mm : 100;而不是VAR num length : 100;。单位是代码的DNA丢了就变异。**所有IF判断先TPWrite再
返回列表