
1. 工单拼接场景下的字符串处理需求拆解1.1 为什么工单系统离不开字符串拼接做过产线工单系统的人都有一个共识PLC 侧的数据从来不是给人看的。设备状态、批次号、时间戳、工位编号这些数据在寄存器里各占各的地址各用各的数据类型彼此之间毫无关联。但操作员需要看到的是一行能读懂的工单信息比如“WO-20240513-A线-03工位-合格”。这中间的转换工作就是字符串拼接要解决的核心问题。在三菱 STStructured Text编程环境里字符串处理不像高级语言那么自由。没有动态内存分配没有丰富的标准库字符串长度还要提前声明。很多从 C# 或 Python 转过来做 PLC 的工程师第一次写工单拼接逻辑时都会踩坑——要么拼接结果被截断要么数值转字符串后带了一堆空格要么 REPLACE 函数用错了位置参数导致整个字符串乱掉。这篇文章围绕 CONCAT 和 REPLACE 这两个最常用的字符串函数展开把工单拼接场景下的选型逻辑、参数细节、实操步骤和排查经验一次讲透。不管你是刚接触三菱 ST 的新手还是已经写过一些工单逻辑但总觉得不够优雅的老手都能从中找到可以直接复用的方案。1.2 CONCAT 与 REPLACE 的核心差异先给结论CONCAT 负责“从无到有”REPLACE 负责“从有到优”。CONCAT 的本质是把两个或多个字符串按顺序首尾相接生成一个新的字符串。它不关心原字符串里有什么内容只负责把它们拼在一起。在工单场景里CONCAT 适合做初始拼接——把工单号、产线编号、工位号、状态码这些独立字段拼成一条完整记录。REPLACE 的本质是在一个已有字符串中把指定的子串替换成新的内容。它关心的是“找到什么”和“换成什么”。在工单场景里REPLACE 适合做模板填充或格式修正——比如先定义一个工单模板“WO-XXXX-XX-XX”然后用实际数据逐段替换占位符。两者的核心差异可以用一个生活类比来理解CONCAT 像把几块积木首尾粘起来搭一座桥REPLACE 像在已经搭好的桥上把某一段换成不同颜色的积木。前者关注组合后者关注修改。对比维度CONCATREPLACE核心功能多个字符串顺序拼接替换指定子串输入数量2 个及以上字符串源字符串、被替换串、替换串结果长度各输入长度之和通常与源字符串等长或按替换内容变化典型场景初始工单信息组装模板占位符填充、格式修正常见坑点结果超长被截断位置参数理解错误1.3 工单拼接的典型数据流一条完整的工单信息在 PLC 内部的流转路径通常是这样的底层设备通过输入模块把状态字写入数据寄存器这些寄存器里存的是 INT 或 DINT 类型的数值。工单号可能来自上位机下发的字符串数组产线编号和工位号可能是固定的常量字符串时间戳需要从时钟寄存器读取后转换。这些数据源的类型不统一长度不统一格式也不统一。CONCAT 和 REPLACE 要做的就是把这些“散装”数据整合成一条格式规范的工单字符串再写入 HMI 或上传给 MES 系统。理解这个数据流很重要因为它决定了你在哪个环节用 CONCAT、在哪个环节用 REPLACE。我的经验是先用 CONCAT 把各字段拼成原始串再用 REPLACE 做格式微调。这个顺序反过来会非常别扭后面会详细说明原因。2. CONCAT 函数深度解析与实操要点2.1 CONCAT 的参数结构与返回值机制三菱 ST 中 CONCAT 的标准调用形式是CONCAT(IN1, IN2)返回一个字符串。在 GX Works3 环境下这个函数的参数类型通常是 STRING 或 STRING 数组元素。需要注意的是三菱的 CONCAT 一次只接受两个输入参数如果你要拼接四个字段就需要嵌套调用或者连续调用。嵌套调用的写法是CONCAT(CONCAT(IN1, IN2), CONCAT(IN3, IN4))这种写法在逻辑上没问题但可读性很差调试时很难一眼看出拼接顺序。连续调用的写法是分多步执行每一步把结果存入一个临时字符串变量下一步再拿这个临时变量和下一个字段拼接。我个人的习惯是连续调用虽然多写几行代码但后期维护和排查方便得多。返回值机制方面CONCAT 返回的是一个新的字符串不会修改任何输入参数。这一点和 REPLACE 不同REPLACE 在某些实现中会直接修改源字符串。理解这个差异对内存规划很重要——每次 CONCAT 都会产生一个新的字符串占用如果在一个扫描周期内做大量拼接操作需要留意字符串变量的声明长度是否足够。2.2 字符串长度声明与截断风险这是 CONCAT 使用中最容易踩的坑没有之一。三菱 ST 中声明字符串变量时必须指定最大长度比如strWorkOrder : STRING[50]。这个 50 是字符数上限不是字节数。如果你拼接后的结果超过 50 个字符多出来的部分会被直接截断而且不会报错。我见过一个真实的案例某工位拼接后的工单信息理论长度是 42 个字符工程师声明了 STRING[40]测试时用短工单号没发现问题上线后遇到长工单号结果状态码被截掉了操作员看到的工单永远显示“合”而不是“合格”。这种问题排查起来非常费时间因为逻辑本身没错错在声明长度。规避方法很简单在声明字符串变量时把所有可能拼接的字段最大长度加起来再留 20% 的余量。比如工单号最长 20 字符、产线编号 5 字符、工位号 5 字符、状态码 5 字符、分隔符 6 字符合计 41 字符那声明 STRING[50] 就比较稳妥。注意三菱 ST 中字符串长度声明不足导致的截断是静默发生的不会触发任何错误标志。建议在拼接完成后加一个长度校验逻辑如果结果长度等于声明上限就置一个警告位。2.3 数值转字符串的预处理CONCAT 只能拼接字符串不能直接拼接数值。工单号里的序号、时间戳里的时分秒、工位编号这些在 PLC 里往往是 INT 或 DINT 类型必须先转成字符串才能参与拼接。三菱 ST 提供了 INT_TO_STRING 和 DINT_TO_STRING 等转换函数。但这里有一个细节转换后的字符串默认不带前导零。比如工位号 3 转换后是“3”而不是“03”。如果你的工单格式要求固定两位就需要手动补零。补零的常用做法是用 REPLACE 或者条件判断。比如先拼接一个“00”然后用 REPLACE 把“00”中对应位置替换成实际数字。这个技巧在后面 REPLACE 章节会详细展开。另一种做法是用 RIGHT 函数截取“00”加数字后的右侧两位但 RIGHT 函数在三菱 ST 中的可用性取决于具体型号不是所有环境都支持。我的建议是在工单拼接逻辑设计阶段就把所有需要固定位数的字段列出来统一用 REPLACE 做补零处理这样格式最可控。2.4 CONCAT 嵌套调用的可读性优化前面提到嵌套调用可读性差这里展开说一下优化方案。假设你要拼接四个字段工单号、产线号、工位号、状态码。嵌套写法是这样的strResult : CONCAT(CONCAT(CONCAT(strWO, strLine), strStation), strStatus);这行代码能跑但如果你第二天回来看很难快速确认拼接顺序是不是自己想要的。而且如果中间某个字段需要加分隔符嵌套层级会更深代码会变得非常难读。连续调用的写法strTemp1 : CONCAT(strWO, -); strTemp2 : CONCAT(strTemp1, strLine); strTemp3 : CONCAT(strTemp2, -); strTemp4 : CONCAT(strTemp3, strStation); strResult : CONCAT(strTemp4, strStatus);这种写法代码行数多但每一步的结果都清晰可见调试时可以在任意一步设置监视点查看中间结果。如果拼接逻辑出问题能快速定位是哪个字段导致的。折中方案是用一个临时字符串变量反复拼接strResult : strWO; strResult : CONCAT(strResult, -); strResult : CONCAT(strResult, strLine); strResult : CONCAT(strResult, -); strResult : CONCAT(strResult, strStation); strResult : CONCAT(strResult, strStatus);这种写法兼顾了可读性和代码量是我在工单拼接场景中最常用的方式。唯一需要注意的是strResult 的声明长度要足够容纳最终结果。3. REPLACE 函数深度解析与模板填充技巧3.1 REPLACE 的参数语义与常见误解三菱 ST 中 REPLACE 的标准调用形式是REPLACE(IN, L, P, S)其中 IN 是源字符串L 是被替换的子串长度P 是替换的起始位置S 是替换后的新内容。返回替换完成后的字符串。这里最容易误解的是 L 和 P 的配合关系。L 是从 P 位置开始要替换掉的字符数不是替换后的字符数。举个例子源字符串是“WO-XXXX-XX”你想把从第 4 位开始的 4 个 X 替换成“2024”那么 L4P4S“2024”。替换后结果是“WO-2024-XX”。很多新手会误以为 L 是替换内容的长度写成 L4、P4、S“2024” 碰巧对了但如果替换内容长度不同就会出错。比如把“WO-XXXX-XX”中的“XXXX”替换成“202405”如果还写 L4结果会变成“WO-202405-XX”吗不会。因为 L4 只替换掉 4 个字符源字符串第 4 到第 7 位是“XXXX”替换成“202405”后结果是“WO-202405-XX”看起来对了但实际上第 8 位的“-”被挤到了后面如果源字符串是“WO-XXXXXX-XX”L4 就只能替换掉前 4 个 X剩下两个 X 还在。所以正确的做法是L 必须等于被替换子串的实际长度。如果占位符长度不固定REPLACE 就不太适合应该考虑用 CONCAT 重新拼接。3.2 模板占位符的设计原则用 REPLACE 做模板填充时占位符的设计直接决定了替换逻辑的复杂度。我总结了几条原则第一占位符长度要固定。比如用“XXXX”表示四位年份用“XX”表示两位月份不要用“X”表示一位、“XXX”表示三位这种变长设计。固定长度让 L 参数可以直接写常量不需要动态计算。第二占位符字符要唯一。不要用“-”或“_”这种在正常数据中也会出现的字符做占位符否则替换时可能误伤。用“X”或“#”这类在工单数据中不会自然出现的字符比较安全。第三占位符位置要可预测。模板设计好后每个占位符的起始位置应该是固定的。如果模板中有可变长度的字段要么把它放在最后要么用固定长度填充。一个典型的工单模板可以设计成“WO-XXXX-XX-XX-XX”分别对应工单年份、产线号、工位号、状态码。每个占位符长度固定位置固定替换逻辑就非常清晰。3.3 多字段替换的执行顺序当一个模板中有多个占位符需要替换时执行顺序会影响结果。因为每次 REPLACE 都会改变字符串的长度如果替换内容长度和被替换长度不同后续占位符的位置可能会偏移。最安全的做法是从右往左替换。因为替换右侧内容不会影响左侧占位符的位置。比如模板“WO-XXXX-XX-XX-XX”先替换最右边的状态码占位符再替换工位号再替换产线号最后替换年份。这样每次替换都不会影响还没替换的占位符位置。如果从左往右替换每次替换后都要重新计算后续占位符的位置逻辑会复杂很多。我试过从左往右替换代码里全是位置计算后期改模板时非常痛苦。从那以后我的工单模板替换逻辑一律从右往左。提示如果替换内容长度和被替换占位符长度完全一致那么替换顺序无所谓。但在实际工单场景中很难保证所有字段长度都刚好匹配所以从右往左是更稳妥的默认策略。3.4 REPLACE 与 CONCAT 的配合使用单独用 REPLACE 做模板填充有一个前提模板本身已经是一个完整的字符串。但模板从哪来通常是在 PLC 初始化时用 CONCAT 拼出来的或者直接用一个常量字符串赋值。更常见的做法是混合使用先用 CONCAT 把不需要格式化的字段拼在一起再用 REPLACE 处理需要补零或格式修正的字段。比如工单号和时间戳用 CONCAT 直接拼工位号和状态码需要固定两位就用 REPLACE 在拼接结果上做修正。这种混合方式的优势在于CONCAT 处理简单拼接效率高REPLACE 处理格式修正针对性强。两者各司其职逻辑清晰排查也方便。4. 工单拼接完整实操流程4.1 数据准备与变量声明假设我们要拼接一条工单信息包含以下字段工单号字符串最长 20 字符、产线号整数1-9、工位号整数1-99、状态码整数0-99、时间戳字符串格式 HH:MM:SS。变量声明如下strWorkOrder : STRING[20]; (* 工单号 *) iLineNum : INT; (* 产线号 *) iStationNum : INT; (* 工位号 *) iStatus : INT; (* 状态码 *) strTimeStamp : STRING[8]; (* 时间戳 *) strLineStr : STRING[2]; (* 产线号字符串 *) strStationStr : STRING[2]; (* 工位号字符串 *) strStatusStr : STRING[2]; (* 状态码字符串 *) strResult : STRING[60]; (* 最终工单信息 *) strTemplate : STRING[60]; (* 工单模板 *)这里 strResult 声明为 60 字符是因为工单号 20 产线号 2 工位号 2 状态码 2 时间戳 8 分隔符 6 前缀 4 44 字符留了足够余量。4.2 数值转字符串与补零处理产线号、工位号、状态码都是整数需要转成字符串并补零到固定两位。以工位号为例(* 先转成字符串 *) strStationStr : INT_TO_STRING(iStationNum); (* 如果长度不足两位前面补零 *) IF LEN(strStationStr) 2 THEN strStationStr : CONCAT(0, strStationStr); END_IF;这段逻辑很直白转字符串后检查长度不足两位就在前面拼一个“0”。产线号和状态码同理。这种补零方式比用 REPLACE 更直观适合字段数量不多的情况。如果字段很多可以写一个通用的补零函数块传入整数和目标长度返回补零后的字符串。这样主逻辑会更简洁。4.3 模板定义与 REPLACE 填充定义工单模板strTemplate : WO-XXXX-XX-XX-XX-XX:XX:XX;这个模板中“XXXX”是工单号占位符实际工单号可能更长这里只是示意“XX”分别对应产线号、工位号、状态码、时、分、秒。从右往左替换(* 替换秒 *) strTemplate : REPLACE(strTemplate, 2, 22, RIGHT(strTimeStamp, 2)); (* 替换分 *) strTemplate : REPLACE(strTemplate, 2, 19, MID(strTimeStamp, 4, 2)); (* 替换时 *) strTemplate : REPLACE(strTemplate, 2, 16, LEFT(strTimeStamp, 2)); (* 替换状态码 *) strTemplate : REPLACE(strTemplate, 2, 13, strStatusStr); (* 替换工位号 *) strTemplate : REPLACE(strTemplate, 2, 10, strStationStr); (* 替换产线号 *) strTemplate : REPLACE(strTemplate, 2, 7, strLineStr); (* 替换工单号 *) strTemplate : REPLACE(strTemplate, 4, 4, strWorkOrder);这里的位置参数需要根据模板实际结构调整。上面示例中模板长度和占位符位置是假设的实际使用时需要先数清楚每个占位符的起始位置。4.4 拼接结果校验与输出拼接完成后建议加一段校验逻辑(* 检查结果长度是否异常 *) IF LEN(strResult) 60 THEN bWarning : TRUE; (* 结果可能被截断 *) END_IF; (* 检查是否还有未替换的占位符 *) IF FIND(strResult, X) 0 THEN bWarning : TRUE; (* 存在未替换的占位符 *) END_IF;FIND 函数用于查找子串位置如果结果中还能找到“X”说明有占位符没被替换掉。这个校验在模板调整后特别有用能快速发现遗漏。校验通过后把 strResult 写入 HMI 显示寄存器或上传给上位机。写入时注意目标寄存器的字符串长度限制避免在输出环节被二次截断。5. 常见问题与排查技巧实录5.1 拼接结果乱码或显示异常乱码问题通常有三个原因字符串长度声明不足导致截断、数值转字符串时编码不对、HMI 显示控件的字符集设置不匹配。排查顺序先在 PLC 侧监视 strResult 的实际内容确认 PLC 内部字符串是否正确。如果 PLC 侧正常但 HMI 显示乱码问题在 HMI 控件设置。如果 PLC 侧就是乱码检查 INT_TO_STRING 的转换结果是否正常以及 CONCAT 的输入参数是否都是有效的字符串变量。我遇到过一次乱码是因为字符串变量声明为 STRING[10]但实际拼接结果有 12 个字符多出的部分被截断后最后一个字符的编码不完整导致 HMI 显示异常。把声明长度改大后问题消失。5.2 REPLACE 替换位置偏移位置偏移是 REPLACE 使用中最常见的问题。根本原因通常是替换内容长度和被替换占位符长度不一致导致后续占位符位置发生变化。解决方法有两个一是确保替换内容长度和占位符长度一致比如占位符是“XX”两位替换内容也必须是两位不足补零。二是从右往左替换这样即使长度变化也不会影响还没替换的左侧占位符。如果模板中占位符长度无法统一建议放弃 REPLACE 方案改用 CONCAT 逐段拼接。虽然代码多一些但位置逻辑简单不容易出错。5.3 CONCAT 嵌套层级过深导致编译错误三菱 ST 对嵌套调用的层级有限制具体层数取决于编译器和 PLC 型号。嵌套过深时编译会报错提示表达式过于复杂。解决方法就是前面说的连续调用把嵌套拆成多步。每步的结果存入临时变量下一步再拼接。这样既避免了嵌套限制又提高了可读性。5.4 工单号含特殊字符导致拼接异常工单号中如果包含“-”或“_”等分隔符而模板中也用这些字符做分隔REPLACE 替换时可能会误匹配。比如工单号是“WO-2024”模板中也有“WO-XXXX”替换工单号时可能把模板中的“WO-”也替换掉。规避方法模板占位符不要用工单数据中可能出现的字符。用“#”或“”这类在工单号中不会出现的字符做占位符替换时就不会误匹配。5.5 常见问题速查表问题现象可能原因排查方法解决措施结果被截断字符串声明长度不足监视结果长度是否等于声明上限增大声明长度留 20% 余量显示乱码编码不匹配或截断PLC 侧和 HMI 侧分别监视检查字符集设置确保长度足够替换位置偏移替换内容长度不一致逐步监视每次 REPLACE 结果从右往左替换或统一长度编译报错嵌套层级过深查看编译错误信息拆成连续调用占位符未替换替换逻辑遗漏用 FIND 检查结果中是否还有占位符补充替换逻辑数值转字符串带空格转换函数默认行为监视转换结果用 TRIM 或手动截取6. 性能优化与代码维护建议6.1 减少不必要的字符串操作字符串操作在 PLC 中是比较耗资源的尤其是在高速扫描周期中。每增加一次 CONCAT 或 REPLACE都会增加扫描周期的负担。优化原则能一次拼接完成的不要分多次。比如四个字段拼接如果嵌套调用可读性可以接受就尽量用嵌套而不是连续调用。另外工单信息如果不需要每个扫描周期都更新可以加一个触发条件只在数据变化时执行拼接逻辑。6.2 字符串变量的复用策略在工单拼接逻辑中临时字符串变量可以复用。比如 strTemp1 在第一步拼接后第二步可以覆盖使用。这样能减少变量声明数量降低内存占用。但复用变量会降低代码可读性因为同一个变量在不同步骤代表不同含义。我的建议是在逻辑简单、步骤少的场景中复用在逻辑复杂、步骤多的场景中为每个中间结果声明独立变量方便调试。6.3 模板与逻辑分离把工单模板定义成一个独立的字符串常量或初始化变量不要硬编码在拼接逻辑中。这样修改模板格式时只需要改一个地方不需要在大量代码中搜索替换。如果模板有多种格式比如不同产线用不同模板可以用数组或条件选择来管理。模板和拼接逻辑分离后代码的维护成本会大幅降低。6.4 版本兼容性注意事项不同型号的三菱 PLC 对字符串函数的支持程度不同。有些型号支持 FIND、MID、LEFT、RIGHT 等函数有些型号只支持基础的 CONCAT 和 REPLACE。在编写工单拼接逻辑前先确认目标 PLC 型号支持的函数列表。如果目标型号不支持某些函数可以用基础函数组合实现。比如 LEFT 可以用 MID 替代RIGHT 可以用 MID 加 LEN 计算位置来替代。虽然代码会复杂一些但兼容性更好。我在实际项目中遇到过因为 PLC 型号不支持 FIND 函数导致占位符校验逻辑无法实现的情况。后来改用逐字符比较的方式替代虽然效率低一些但功能不受影响。所以前期确认函数支持列表这一步能省掉很多后期返工的时间。