资讯详情 三菱PLC ST语言字符串处理:CONCAT与REPLACE在工单拼接中的实战应用
📅 2026/10/7 6:14:02
1. 为什么要在PLC里折腾字符串工控现场干久了你会发现一个挺扎心的事实PLC最擅长的是逻辑控制、数值运算、运动控制但对字符串的处理能力一直比较拉胯。尤其是从三菱FX3U一路用到FX5U、Q系列的老电气工程师大部分时间都在跟BOOL、WORD、DINT打交道字符串顶多用来显示个报警文本。但设备一旦涉及MES对接、扫码枪上传、工单下发、配方管理你就躲不开字符串处理。“这台设备的工单号B2407-018停机原因是AL10.1当前计件128件”——类似这种信息要么你花大价钱买上位机或者HMI慢慢拼要么干脆用PLC自己把字符串拼接出来直接写进触摸屏或者传给MES。我做三菱ST语言的项目也有几年了从早期的简单寄存器加减到后来的复杂数据结构、字符串处理踩过的坑不少。这篇文章就把我实际用得最频繁的两个字符串函数——CONCAT和REPLACE结合工单拼接这个真实场景做一个详细的对比拆解。顺便把ST里头其它常用的字符串辅助函数也捋一遍算是给后来人铺条路。先说个结论CONCAT负责“把碎片拼成整体”REPLACE负责“把错误的部分纠正过来”两者在工单拼接里是黄金搭档。但用不好一个乱码就够你排查一整天。下面逐个展开。2. ST语言字符串基础三菱PLC的字符串变量没那么难用2.1 字符串变量的定义与存储三菱ST语言里字符串变量常用的有两种固定长度字符串和可变长度字符串对应到三菱PLC的数据类型就是String定长和String可变。GX Works3软件的ST编辑器里声明方式如下VAR sWorkNo : STRING(16); // 工单号最多16个字符 sStepName : STRING(32); // 工序名称最多32个字符 sResult : STRING(64); // 拼接最终结果 END_VAR说实话这里最大的坑在于字符串长度。你在声明的时候给了多大空间这个变量就只能容纳多少个字符。如果拼接后的总长度超过了目标变量的声明长度超出部分会直接丢弃不会报错也不会自动扩展。这种“静默截断”机制坑了多少人我见过不少新手调试半天发现字符串尾部莫名其妙“消失”了最后才想起是长度不够。从底层存储看三菱PLC的字符串就是连续字节区域加结束符类似C语言的\0逻辑但编译器自动帮你管理终结符。所以当你声明STRING(16)实际可用的有效字符一般到15或者16个不同系列有差异为了稳当我习惯在声明时就多预留20%的长度。2.2 ST字符串函数的执行逻辑与注意事项三菱ST语言里的字符串函数执行逻辑和高级语言差不多但是有几个差异点必须注意函数调用不像C语言那样返回值赋给变量那么简单而是直接修改目标变量。举个例子C语言里你写 a concat(b, c)三菱ST里通常是这样sResult : CONCAT(sWorkNo, sStepName);这条语句等价于C语言的 strcat 逻辑把sWorkNo和sStepName依次存入sResult。看起来区别不大但ST语言里所有字符串函数的执行都是在扫描周期里按顺序完成的如果你在同一个扫描周期内多次调用同一个目标变量后一次调用会覆盖前一次的结果这个和高级语言的局部变量作用域完全是两码事。另外函数对空字符串的处理也需要留意。三菱ST的CONCAT一旦遇到某个字符串参数为空行为是跳过该参数继续拼接而不是报错。这一点用熟了反而是优势——动态拼接工单时有些字段可能没填写你可以放心传空字符串不会中断逻辑。3. CONCAT的完整拆解工单拼接这台“流水线”3.1 CONCAT的基础语法与三菱官方习惯CONCAT是三菱ST语言里最常用的字符串拼接函数官方手册上的标准调用格式如下sResult : CONCAT(sString1, sString2);这是两个参数的写法功能和高级语言里的“字符串连接”完全一致就是把sString1和sString2依次放进sResult返回合并后的字符串。但工单拼接往往不止两个部分我拿最典型的场景举例——把设备号、工单号、报警代码和当前产量合并成一个完整信息串sWorkOrder : CONCAT( CONCAT(sDeviceID, ), // 设备ID CONCAT(工单号:, sOutputOrder), // 工单号 CONCAT(|报警:, sAlarmCode), // 报警代码 CONCAT(|产量:, sCount) // 当前产量 );这一长串嵌套写法逻辑上没问题但可读性很差。你三个月后回来看这段代码大概率要愣一下才能厘清嵌套关系。我个人的习惯是用中间变量分步拼接虽然代码行数多一点但排错和后期维护的效率高很多sTemp1 : CONCAT(sDeviceID, 工单号:); sTemp2 : CONCAT(sTemp1, sOutputOrder); sTemp3 : CONCAT(sTemp2, |报警:); sTemp4 : CONCAT(sTemp3, sAlarmCode); sResult : CONCAT(sTemp4, sCount);工单拼接现场表面上是拼字符串实质是拼数据源类型。这里有个关键点CONCAT只能接受字符串参数你没法直接拿INT、DINT这类数值变量去做拼接。比如上面的sCount是INT型直接写sResult : CONCAT(产量:, sCount); // 编译报错三菱ST编译器直接给你标红。解决办法是先做类型转换用INT_TO_STRING()或者DINT_TO_STRING()把数值变成字符串再拼sCountStr : INT_TO_STRING(sCount); sResult : CONCAT(产量:, sCountStr);这个类型转换问题我特意放在前面讲因为它是新手最常见的一个编译错误而且提示信息有时候不说人话。3.2 CONCAT在工单拼接中的经典实战案例下面给一个可以“抄作业”的完整实例。场景设备完成一次加工后需要生成一条工单状态信息内容包含设备编号、产品型号、加工数量、报警等级并传给HMI显示或者MES上报。VAR sDeviceID : STRING(8); // 设备号T-18 sModel : STRING(16); // 产品型号PX-200 iQty : INT; // 加工数量 iAlarmLevel : INT; // 报警等级 sQtyStr : STRING(8); // 数量转字符串 sAlarmStr : STRING(4); // 等级转字符串 sMessage : STRING(64); // 最终工单信息 bTrigger : BOOL; // 触发拼接 END_VAR触发控制程序段IF bTrigger THEN sQtyStr : INT_TO_STRING(iQty); sAlarmStr : INT_TO_STRING(iAlarmLevel); sMessage : CONCAT(设备, sDeviceID); sMessage : CONCAT(sMessage, 产品型号:); sMessage : CONCAT(sMessage, sModel); sMessage : CONCAT(sMessage, 加工数量:); sMessage : CONCAT(sMessage, sQtyStr); sMessage : CONCAT(sMessage, 报警等级:); sMessage : CONCAT(sMessage, sAlarmStr); sMessage : CONCAT(sMessage, #); // 结尾分隔符 END_IF;执行后如果iQty128iAlarmLevel3sMessage的值就是设备T-18 产品型号:PX-200 加工数量:128 报警等级:3#尾部的“#”是留给上位机做报文解析用的结束符。很多MES报文都要求固定结尾字节方便解析程序判断一帧数据已经收完。这个习惯我建议你保留。3.3 CONCAT的隐藏缺点参数数量上限三菱ST的CONCAT函数不同系列、不同版本的手册对这个函数的参数个数定义不太一样。FX5U里很多版本支持最多9个参数Q系列可能多一些。但如果你的GX Works3软件版本比较老或者固件版本不支持多参数那你写CONCAT(s1, s2, s3, s4, s5)会直接提示函数参数个数错误。所以我的建议是如果确定固件支持多参数可以用sResult : CONCAT(s1, s2, s3, s4, s5);简化嵌套。如果不确定老老实实用嵌套或者中间变量最多损失一点代码行数但稳定性是优先的。实测下来FX5U在较新的GX Works3版本1.075J以上里CONCAT可以传多个参数拼接顺序按照参数排列从左到右。旧项目迁移过来的建议统一验证别想当然。4. REPLACE的深度解析纠正与重组的利器4.1 REPLACE的基本语法与主要用途REPLACE在三菱ST中的作用是把字符串中某个子串替换成另一个字符串。官方调用格式sResult : REPLACE(sSource, sOld, sNew);三个参数分别是源字符串、要被替换的旧内容、替换后的新内容。我在工单拼接里用REPLACE较多的是这两种场景场景A设备停机后产生的工单内容里包含了报警代码但操作员在HMI上修正了报警原因需要把原工单文本里的报警类别或者代码换成人工确认后的信息。场景B工单号因为条码扫描头误读某个字符被识别错了比如把字母“O”录成了数字“0”用REPLACE把这个错误字符批量纠正过来。下面给一个实际代码片段把字符串里的“AL10.1”替换成“AL11.0”VAR sSourceStr : STRING(64); // 原始数据 sNewStr : STRING(64); // 替换后数据 END_VAR sSourceStr : 设备02 报警AL10.1 停机; sNewStr : REPLACE(sSourceStr, AL10.1, AL11.0);执行后sNewStr的值变为“设备02 报警AL11.0 停机”。但要提醒你一个容易踩的点REPLACE在有些三菱系列固件里只替换第一个匹配项不会替换后面所有匹配项。手册里写的是“将对源字符串中检索到的字符串进行替换”但实测中FX5U的REPLACE行为是针对首个匹配还是全部匹配不同版本有不同的表现。稳妥的办法是确认你的PLC固件手册必要时用循环扫描替代。4.2 REPLACE与数据清洗工单文本的“橡皮擦”工单系统中最常见的是工单号或者批次号被MES系统推送下来时带了非法空格或特殊字符。比如sSourceStr : B2407-018 末尾多了一个空格你看不到但一旦把它拼到报警文本里显示就会多出一个错位空格上位机解析也会出问题。这种场景用REPLACE把空格替换成空字符串sNewStr : REPLACE(sSourceStr, , );注意第三个参数传空字符串表示“删除所有空格”。这个操作我几乎在每个项目里都用堪称“工单清洗三件套”第一名。还有一招比较偏门但是很实用用REPLACE做字符串特征的临时替换配合条件判断实现类似高级语言里startsWith/endsWith的效果。比如要判断工单号是否以“B”开头IF REPLACE(sWorkNo, B, ) sWorkNo THEN // 说明开头是B END_IF;这段逻辑的本质是如果替换后字符串变了说明原字符串里有B至于是不是开头还得配合位置判断。不过在三菱ST里确实没有现成的startswith函数这种变通方法能顶上。4.3 REPLACE的参数边界与长度陷阱REPLACE最容易翻车的是长度边界问题。比如sOld的长度比sNew长替换后总字符串变短了这通常没问题。但反过来如果sNew的长度比sOld长替换后总字符串变长你必须确保目标变量的声明长度够大否则尾部会被截断。我举个实际例子原字符串“设备01报警AL10.1”长度是14个字符包含空格和小数点。现在想把“AL10.1”替换成“ALARM-1101”变长了不少最终结果长度超过目标变量声明直接给你截掉后面几个字导致工单信息不完整。处理方法很简单计算替换后的预期长度在声明变量时把余量留足。你可以把中间字符串变量声明为最大可能长度再赋值给最终工单变量宁可浪费一点内存不要因为截断丢了信息。VAR sTempFull : STRING(128); // 中间长变量 sFinal : STRING(64); // 最终输出变量 END_VAR sTempFull : REPLACE(sSourceStr, AL10.1, ALARM-1101); IF LEN(sTempFull) 64 THEN sFinal : sTempFull; ELSE // 记录一个出错标志而不是静默截断 bStrOverflow : TRUE; END_IF;这段代码的意义在于主动检查长度而不是被动接受截断。我把这个习惯称为“字符串安全的最后一道防线”。5. 工单拼接完整实战从数据采集到信息生成的ST代码5.1 工单拼接的数据结构与整体流程设计工单拼接绝不是一句CONCAT就完事它背后隐藏着一个数据结构设计的问题。我以一条完整工单为例设备编号、工单号、产品型号、工位号、报警代码、操作员编号、加工时间、计件数量。这些数据来源各不相同设备编号、工位号PLC内部固定参数掉电不丢工单号、产品型号MES下发的报文可能存在缓存区报警代码报警功能块输出操作员编号HMI操作记录或者刷卡器扫码加工时间、计件数量PLC运行数据把这些不同类型的数据字符串、整型、实数、时间类型统一拼成一个字符串中间要过“类型转换关”和“分隔符设计关”。归一化流程可以这样设计定义各字段的源变量统一转成字符串定义拼接中间变量按照固定顺序逐步CONCAT所有字段之间用统一分隔符如“|”或“,”总拼接结果做长度校验把结果传给HMI显示或者通过以太网发送到MES。我用的一张典型数据结构表如下字段名称数据类型长度示例值备注设备编号STRING8T-18固定参数工单号STRING16B2407-018MES下发产品型号STRING16PX-200MES下发工位号STRING4A03固定参数报警代码STRING8AL10.1报警模块操作员IDSTRING8OP01HMI输入加工时间STRING202024-07-18 10:30时间转换计件数量STRING6128整数转换5.2 带校验与截断防护的完整ST函数代码下面这段代码是我在三菱FX5U项目里实际使用的工单拼接逻辑做了适当简化去掉了项目相关的保密字段保留了核心拼接与防截断框架。FUNCTION BLOCK FB_WorkOrderBuild VAR // 输入 bBuild : BOOL; // 拼接触发 sDeviceID : STRING(8); // 设备ID sWorkNo : STRING(16); // 工单号 sModel : STRING(16); // 产品型号 sStation : STRING(4); // 工位号 sAlarmCode : STRING(8); // 报警代码 sOperator : STRING(8); // 操作员ID sDateTime : STRING(20); // 时间字符串 iQty : INT; // 计件数量 // 输出 sResultMsg : STRING(128); // 最终工单信息 bOverflow : BOOL; // 长度溢出标志 END_VAR VAR // 中间变量 sQtyStr : STRING(6); sTemp : STRING(128); sTemp2 : STRING(128); sTemp3 : STRING(128); sTemp4 : STRING(128); sTemp5 : STRING(128); END_VAR拼接逻辑段// 类型转换 sQtyStr : INT_TO_STRING(iQty); // 分步拼接便于排错 sTemp : CONCAT(设备:, sDeviceID); sTemp2 : CONCAT(sTemp, 工单号:); sTemp2 : CONCAT(sTemp2, sWorkNo); sTemp2 : CONCAT(sTemp2, 型号:); sTemp2 : CONCAT(sTemp2, sModel); sTemp2 : CONCAT(sTemp2, 工位:); sTemp2 : CONCAT(sTemp2, sStation); sTemp3 : CONCAT(sTemp2, 报警:); sTemp3 : CONCAT(sTemp3, sAlarmCode); sTemp3 : CONCAT(sTemp3, 操作员:); sTemp3 : CONCAT(sTemp3, sOperator); sTemp4 : CONCAT(sTemp3, 时间:); sTemp4 : CONCAT(sTemp4, sDateTime); sTemp4 : CONCAT(sTemp4, 数量:); sTemp5 : CONCAT(sTemp4, sQtyStr); sTemp5 : CONCAT(sTemp5, ;); // 结束分隔符长度校验段// 长度校验防止静默截断 IF LEN(sTemp5) 128 THEN sResultMsg : sTemp5; bOverflow : FALSE; ELSE bOverflow : TRUE; // 截断处理取前128字符 sResultMsg : LEFT(sTemp5, 128); END_IF;执行效果当各输入值如下时sDeviceID T-18sWorkNo B2407-018sModel PX-200sStation A03sAlarmCode AL10.1sOperator OP01sDateTime 2024-07-18 10:30iQty 128最终sResultMsg为设备:T-18 工单号:B2407-018 型号:PX-200 工位:A03 报警:AL10.1 操作员:OP01 时间:2024-07-18 10:30 数量:128;这条消息可以直接送到HMI的报警信息栏或者通过以太网模块用ASCII码发送给MES系统。5.3 为什么分步拼接比写一行嵌套更值得我见过很多人在ST语言里追求“一行代码搞定看起来很高级”比如sResult : CONCAT(CONCAT(CONCAT(sA, sB), sC), sD);这种写法在ST语言里最大的问题是一旦结果和预期不符你根本不知道是哪一步出了问题。变量监视窗口里只能看到最终结果看不到中间过程。用分步拼接每个中间变量都能在GX Works3的监视窗口里单独查看。哪个字段拼坏了一目了然。而且ST语言的调试本来就不如C语言方便能多拆一步就多拆一步别怕麻烦。另外分步拼接配合一个额外的小技巧在整条消息的首尾都加上标志性字符。开头可以加“”或“$”结尾加“;”或“#”。这样上位机解析时能用“找第一个标志字符”和“找最后一个标志字符”的方式快速截取有效内容。6. 常见问题排查实录字符串拼接的坑我替你踩过了6.1 数值拼接后出现乱码或菱形符号这是字符串实操里最容易遇到的问题。你把iQty128直接用CONCAT拼进去编译都不一定报错有的旧版本GX Works2会允许隐式转换但结果完全不对但运行起来HMI上显示的不是“128”而是一个方块或乱码字符。根因是PLC内部对数值和字符串的存储格式不一样INT是二进制补码字符串是一串ASCII码。你直接把INT当字符串解析解析出来的是二进制数据对应的ASCII控制字符自然是乱码。处理办法任何数值参与字符串拼接前一律显式转换。sQtyStr : INT_TO_STRING(iQty); // 正确 sBad : CONCAT(数量:, iQty); // 错误/未定义行为这里有个进阶提示如果数值是REAL实数类型比如重量2.5kg用REAL_TO_STRING转换后不同版本的三菱ST可能输出“2.500000E00”这种科学计数法格式并不适合直接显示。建议用REAL_TO_STRING之后再用REPLACE做格式修正把多余的零和小数点尾巴去掉或者干脆用MUL到INT再显示省心得多。6.2 字符串长度不足导致工单信息被“砍头去尾”这个问题的隐蔽性极强。你声明了一个STRING(64)拼接时没算总长结果拼接后的字符串长度是70。PLC不会报错而是把第65个字符开始的内容全部丢弃。更坑的是截断是从尾部开始的也就是说你工单最后的时间和产量信息会直接消失但前半部分看起来完全正常。这种bug在调试时极难定位。我建议的做法是声明字符串变量时按最大可能值预留宁长勿短拼接后尽量加一条长度判断逻辑如果项目没有严格的内存限制直接用STRING(255)做中间变量拼完再压缩到短变量。6.3 REPLACE替换了所有匹配项跟你预期不符再回到REPLACE的一个特性差异。假设你的工单文本里有多个“报警”字样sStr : 设备20 报警AL01 等待处理,报警AL02 已确认; sNew : REPLACE(sStr, 报警, ALARM);如果当前固件的REPLACE做的是全部替换结果就是“设备20 ALARMAL01 等待处理,ALARMAL02 已确认”你会发现连“ALARMAL01”里的ALARM都被替换逻辑处理过看起来就别扭了。而如果只是首个匹配替换结果则是“设备20 ALARMAL01 等待处理,报警AL02 已确认”。这个行为的差异带来的麻烦在于你在调试环境验证过的逻辑移植到另一台固件版本的PLC上结果可能不一样。我的做法是在程序注释里明确写明需要的REPLACE行为并且尽量把要替换的字符串做得“独一无二”比如替换‘报警AL01’而不是‘报警’缩小匹配范围。6.4 中文字符串拼接后的另类长度问题三菱ST在中文系统下的字符串处理有个隐藏属性一个字算多少长度不同厂商的PLC处理不一样。三菱PLC里String长度按照字节数计算中文UTF-8编码一个字占3个字节某些版本是内部转码成Unicode一个字算2个字节所以你在工单里“设备”这两个字可能会占用4到6个字符长度。这个细节直接导致一个问题你在电脑上用字符串长度计算器算好的工单长度传到PLC里长度溢出信息被截断。处理办法是中文工单场景下声明长度最好翻倍预留。比如你认为工单最长为30个字符实际声明STRING(64)比较稳。6.5 常见问题速查表问题现象可能原因排查与解决办法拼接结果尾部缺失目标字符串长度不够预留更大声明长度加LEN检查数值拼进去显示乱码INT/REAL未转字符串使用INT_TO_STRING等方法显式转换REPLACE结果与预期不符固件版本替换行为不同缩小匹配子串避免通配替换中文工单截断中文字节数大于字符数STRING长度按字节数留足余量编译报错CONCAT参数过多函数版本参数个数限制嵌套调用或分步拼接HMI显示多出空格源数据带了空白字符REPLACE(sStr, , ) 清洗多个报警拼接出现重复源报警代码没清空拼接前做MOVE空串处理7. 我总结的ST工单拼接处理心得最后分享几条用了很多项目、翻了很多车之后总结的经验第一条先定分隔符再谈拼接。工单信息的字段越多越要先确定分隔符方案。用“|”还是“,”还是空格取决于上位机解析的配置。经常出现PLC这边用空格分隔上位机那边用逗号分隔结果两边团队扯皮半天。项目启动第一周就把通讯协议文本定下来能省后面两个月的联调时间。第二条字符串操作的变量尽量全部独立命名不要为了省事复用一个中间变量。复用同一个临时字符串变量在顺序执行中确实没问题但是在调试时你会非常痛苦——你根本不知道上一步的结果是被哪个逻辑改掉的。独立命名成本很低排查收益很高。第三条保留一个全局的“原始字符串缓冲区”和“最终结果缓冲区”。我习惯在公共数据块里预定义几个大字符串区域专门用于拼接不跟FB内部变量混在一起。这样即使FB重新初始化缓冲区的原始数据还在方便复现问题。第四条报警工单拼接中REPLACE的使用率比CONCAT还高。因为设备运行过程中报警代码是实时变化的AL10.1变成AL10.2你需要即时更新工单上的报警内容。REPLACE比重新走一遍CONCAT更高效跳过了之前一堆无关字段的重组。实测在FX5U的扫描周期里这种局部更新能节省不少CPU执行时间尤其是工单字符串特别长的时候。以上是我在ST语言工单拼接上的一部分实操积累。字符串看似是个“辅助功能”但在工单追溯、MES交互、报警展示这些关键事项里拼不好一条字符串影响的是整条产线的数据完整性。希望这篇文章能帮你少踩几个坑。