☰
三菱ST字符串拼接与替换:工单报文生成的实战指南
2026/10/7 8:55:34 网站建设 项目流程

1. 先搞清楚三菱ST字符串的底层脾气

干过三菱PLC项目的朋友应该都有同感:在梯形图里处理字符串,就像拿筷子夹汤圆,怎么都不顺手。后来ST语言普及了,字符串函数多了起来,情况好转不少,但依然有一堆"看着能用、一跑就翻车"的细节。这篇文章我围绕三菱ST语言里的CONCAT和REPLACE展开,拿"工单拼接"这个最常见的场景做实操例子,把从指令机制到项目接线再到调试排错的完整链路都过一次,最后给出我自己常用的替代方案,如果你手上正好有FX5U或者Q系列的项目,应该能直接抄作业。

先得接受一个现实:三菱ST的字符串,本质上是一个固定长度的字符数组,不是C语言里那种按需扩容的char数组。你在声明变量时就得先定好尺寸,比如:

VAR sOrderNo : STRING(32); sBatchNo : STRING(16); sFullInfo : STRING(64); END_VAR

字符串变量一旦定义了长度,后面所有操作都必须在这个框框里玩。长度不够要么被截断,要么报错。而且三菱ST字符串自带终止符,类似C语言的\0,你从触摸屏、扫码枪、MES系统读进来的数据,后面往往跟着看不见的空格或结束符,直接拼接就是坑。

1.1 字符串长度、空格和ASCII编码的关系

三菱ST字符串从本质上看是ASCII字节串。字母、数字、标点符号一个字符占一个字节,中文一个字符占两个字节,LEN函数返回的到底是字符数还是字节数,不同系列、不同GX Works版本的处理不太一样。我的经验是按字节数来理解,因为底层是UINT数组和ASCII码的映射,字符编码再复杂,最后PLC内部走的都是字节处理。

也因为这样,工单拼接里有个高频问题:你从MES拿到的数据可能带前后空格。比如工单号实际是"WO20250801",但上位机给你传过来的是"WO20250801 ",尾部多了个空格。直接用CONCAT拼到路径里,生成的文件名看起来没问题,但上位机反读时解析就会出怪事——文件名末尾有个不可见字符,FTP上传、数据库查询都会对不上。

处理办法有两种。一是用三菱自带的函数做规整,类似TRIM功能,但很多低端系列没提供现成的,得自己写;二是拼接前显式检查LEN,再用RIGHT、LEFT、MID裁剪掉异常字符。没有通用函数不可怕,可怕的是你根本不知道字符串里有空格这回事。

1.2 为什么有人绕道用数组存字符

我在一些老工程师的项目里见过这样的做法:不用STRING类型,改用ARRAY[0..31] OF BYTE或ARRAY[0..31] OF CHAR存工单信息,然后自己拼ASCII码。表面看是自讨苦吃,实际理解了就明白:早期三菱FX3U的ST支持不完善,字符串函数不是缺这个就是缺那个,而且函数处理起来有长度限制,不如自己用数组灵活。数组方式的好处是拼接时可以控制精确字节位置,上位机通信也直观,每个字节都看得见摸得着。代价是代码量翻倍,可读性差,后来FX5U和Q系列ST函数补全了,新项目我基本不再推荐这种土办法。

但是有一个场景我至今还在用数组:需要拼接"工单号 + 日期时间 + 随机校验码"这种固定格式报文时,数组方式可以直接一个字节一个字节填进去,调试时用监视窗口看到的就是纯ASCII值,不会出现STRING变量那种"你看到的是空格还是终止符"的暧昧问题。除此之外,还是乖乖用CONCAT / REPLACE省心。

2. CONCAT和REPLACE,到底谁适合做工单拼接

工单拼接看起来就是"把几段字符串串起来",但真正在项目里,拼接不是只有串起来这一种动作。有时候你要在固定模板里替换一段参数,有时候你要把多个字段并成一个报文,这两个需求恰好对应CONCAT和REPLACE两个函数。

2.1 指令层面的机制对比

先看官方指令的典型用法。

CONCAT的作用是把多个字符串按顺序连接成一个新字符串,三菱ST里一般支持多个参数,我实际用下来建议最多只传三个,参数太多可读性太差,出问题也不好排查。示例:

sFullInfo := CONCAT(sOrderNo, sBatchNo); // 拼接后:WO20250801BATCH07

注意一个关键机制:CONCAT是把左操作数和右操作数的有效字符部分连接起来,但左右操作数自己的长度不会主动压缩。比如sOrderNo实际占32字节,只有前12字节有数据,后面的全是空格,那CONCAT(sOrderNo, sBatchNo)的结果是"WO20250801后面跟着一堆空格再接BATCH07"。为了避免这种情况,我给每条规则是:所有参加CONCAT的字符串变量在使用前必须做一次"有效长度截断",或者保证源字符串写入时没有多余空格。

REPLACE的作用是在指定字符串的指定位置,替换掉指定长度的字符。这个函数在处理"模板填充"场景时非常好用。比如你收到上位机的指令格式是ORDER:00000000|QTY:0000,要把中间的具体订单号和数量填进去,用REPLACE就是精确打击:

sMsg := 'ORDER:00000000|QTY:0000'; // 从第7个字符开始,替换8个字符为实际工单号 sMsg := REPLACE(sMsg, 7, 8, sOrderNo); // 从第18个字符开始,替换4个字符为实际数量 sMsg := REPLACE(sMsg, 18, 4, sQty);

这里REPLACE四个参数分别是源字符串、开始位置、替换长度、替换内容。有些工程师会把源字符串和替换内容搞反,或者在替换长度上填错,结果整个报文结构直接被干翻。

2.2 用C语言视角理解三菱ST函数

如果你写过C语言,理解这两个函数特别简单。CONCAT约等于C里的strcat,但三菱ST是函数式写法,返回一个新字符串;REPLACE则有点像sprintf加指针偏移的组合体,只是它更机械——不在内容里找匹配,而是直接按位置和长度干。

C语言里有strstr这样的函数,可以在字符串里搜索某个子串的位置,但三菱ST里没有现成的"全局替换"函数。这是很多从C转过来的人最不适应的一点。在工单拼接场景里,你可能希望"只要字符串里出现日期前缀,就自动替换成新日期",但ST的REPLACE需要你先用INSTR找到日期前缀的位置,再拿那个位置做替换参数。看起来多了一步,实际也不难,关键是脑子里要清楚:REPLACE不负责找位置,只负责按坐标操作,找位置的活儿归INSTR管。

所以我的判断是:CONCAT适合字段拼接,REPLACE适合模板填充。工单拼接如果只是把几个数据并到一起,用CONCAT;如果是从MES下发的模板报文里填参数,用REPLACE。这两个不是二选一的关系,更多时候是组合拳。

2.3 耗时和工程习惯上的差异

还有一个很现实的问题:字符串拼接太频繁会影响PLC扫描周期吗?以我的实测经验,单个CONCAT或REPLACE指令执行时间都在微秒到几十微秒级别,对于通常的扫描周期5ms~10ms来说,偶尔拼几次完全无感。但你要是设了一个每秒执行N次的功能块,里面反复做长字符串拼接,CPU占用会明显上去。曾经有一次我在FX5U里做每秒50次的报文生成,每个报文拼60个字符,地址连续传输,结果扫描周期从4ms飙到了7ms,查了半天才发现是频繁的字符串拷贝占用了时间。

从此我养成一个习惯:高频执行段里静态字符串用常量数组预置,动态内容只在数据变化时才重新拼接。这个思路不只是省CPU,还能让程序逻辑更清晰——"什么时候拼、拼给谁用"一眼就能看出来。

3. 工单拼接实战:从报文到文件名的三个真实场景

光说指令机制不够,我拿三个项目里踩过的真实场景展开,每个都附可复现的代码骨架。

3.1 场景一:MES下发工单号批次信息拼接

设备上有一个扫码枪读取托盘条码,MES会下发当前工单号、批次号、数量,设备需要把这些信息拼成一条报文上传给上位机,回车换行结尾。

// 注意+1、+2是回车换行的ASCII码 sTmp := CONCAT(sOrderNo, ','); sTmp := CONCAT(sTmp, sBatchNo); sTmp := CONCAT(sTmp, ','); sTmp := CONCAT(sTmp, INT_TO_STRING(iQty)); sSend := CONCAT(sTmp, '$R$L');

这段代码在功能上没错,但它犯了一个我在前面说的经典错误:sOrderNo和sBatchNo如果带尾部空格,拼出来的报文字段间就会出现莫名空格。正确做法是先规整数据:

// 规整函数:去除字符串尾部空格 // 方法:从右往左找第一个非空格字符,用LEFT截取

这个规整函数后面第5章我给出完整实现。实际抓包调试时你会发现,上位机根本不关心你逻辑多复杂,它就要求报文的每一个字节都和协议完全一致,多一个空格都是校验失败。工单拼接不是给人看的,是给机器看的,严格是按字节对齐来考核。

3.2 场景二:模板字符串用REPLACE填参数

第二个项目里,上位机通过以太网给FX5U下发一套模板报文,内容是:

ORDER,{ORD},DATE,{DAT},QTY,{QTY}

PLC要做的不是拼接整条报文,而是把大括号里的占位符替换成实际值。这种场景用REPLACE做模板填充就非常合适,比CONCAT拼接更灵活,因为模板本身是上位机可配置的。比如PLC收到模板后先检查长度,确认在STRING变量范围内,然后逐个用INSTR找占位符的位置:

iPos := INSTR(sTpl, '{ORD}'); IF iPos > 0 THEN sTpl := REPLACE(sTpl, iPos, 5, sOrderNo); END_IF;

注意{ORD}在字符串里占5个字符,替换成实际的工单号后,整个字符串长度可能变长也可能变短。REPLACE是"按位置替换指定长度",替换后如果新字符串比旧内容短,占位符多出来的部分会被塞成空格;如果长,可能会顶掉后面的内容。所以在模板填充前,确认每个字段长度上限,宁可配一个64位的模板字符串,也不要把大小卡得刚刚好。这个项目我后来改用第5章的手写模板替换函数来解决变长问题,总体稳定很多。

3.3 场景三:触摸屏路径/文件名拼接

触摸屏上要显示一个工单报告页面,文件路径是/usr/order/20250801/WO20250801_A.csv这种格式。这部分逻辑放在PLC里做,用CONCAT拼接。拼接逻辑不难,难点在于触摸屏和PLC之间的字符串变量映射。很多国产触摸屏与三菱通信时,STRING变量传输会带上长度前缀或终止符,导致PLC里拼得好好的字符串,到触摸屏上末尾多了个空格或有乱码。

我的应对方式是:PLC侧拼完文件名后,顺手把字符串用BYTE数组方式填充一遍,确保最后一个有效字节后面是终止符,再映射给触摸屏。如果你用的是三菱GOT,情况好一些,GOT和三菱自家PLC的字符串类型对接相对规范。如果是第三方屏,就得做好"屏幕显示和PLC监视不一致"的思想准备。

4. 拼接时的典型翻车现场与排查链路

如果说前面是理论课,这一节就是实战课。字符串相关bug隐蔽性高,报错信息又不直观,排查起来最花时间。我总结两个高频翻车现场和一套完整排查链路。

4.1 翻车现场一:拼接结果带空格导致上位机解析失败

现象就是上位机报"报文格式错误",看日志字符串显示正常,但用Wireshark抓包发现某几个字节是0x20(空格ASCII码)。

排查链路:

  1. 先确认是不是CONCAT引入的空格。打开GX Works软件监视窗口,把拼接前的源字符串拉出来逐个字节看。注意监视窗口显示空格和字符不直观,可以把字符串强制转换为字节数组,或者用十六进制显示模式。
  2. 确认源字符串本身是否干净。如果你是从触摸屏组态或MES通信存储区读出来的数据,极大概率带了填充空格。很多通信协议里字段是固定长度8字节,内容只占6字节,后两位自动补空格。
  3. 在拼接前后分别做LEN测试。比较LEN的结果和"预期有效字符数"是否一致。如果LEN结果比预期大,说明有空格或不可见字符混入。
  4. 修复方式:对参与拼接的每个字符串进入前统一执行一遍"尾部空格清理"。

这个坑我踩过不只一次,后来在团队里立了一条硬规矩:所有来自外部通信的字符串在存入全局变量前,必须过一遍规整函数。宁可损失一点扫描周期,也不能让脏数据流到业务逻辑里。

4.2 翻车现场二:字符串长度不够直接截断

第二种翻车是字符串变量声明太短,拼接时数据被截断。比如工单号实际是18位,你声明STRING(16),CONCAT拼接时,数据会超出16字节的一部分被悄悄丢掉,而且通常不报错。等你发现工单号对不上的时候,可能已经过去半天。

这个坑的隐蔽性在于:PLC正常情况下不会崩溃,程序照跑,只有数据校验时才暴露。更坑的是,REPLACE往指定位置填内容时,如果替换内容比声明的字符串空间长,也可能出现"字符串无变化"的假象,因为被截断后看起来像没替换成功。

排查方式很明确:凡是出现"字符串变化不符合预期"的现场,第一件事先看变量定义的大小,再去看函数参数。我一般会在程序里加一段上电自检逻辑,把每条字符串变量的最大长度、当前LEN、关键拼接结果上传到上位机日志,这样可以做到事后复盘。

4.3 排查链路:DUT、Watch窗口、串口调试的三层递进

提到排查,我建议按三层递进去做,而不是一上来就猜函数用法。

第一层是DUT(Device Unit Test)层,说白了就是写个独立测试功能块,只把输入和输出打印出来,验证字符串函数本身的拼接逻辑。很多问题在这一层就能暴露。比如你怀疑REPLACE位置参数不对,就单独写一段sT := REPLACE('HELLO WORLD', 7, 5, 'PLC');,在Watch窗口看结果,不用把整个设备拉起来就能确认。

第二层是Watch窗口实时监视。GX Works的监视窗口可以看STRING变量的当前值,但注意它显示的字符对你判断空格不友好。可以把监视格式切到ASCII或十六进制字节视图,能看到每个字节的码值。

第三层是用串口或以太网调试工具,把PLC实际发出的原始报文截下来和协议文档逐字节比对。很多通信问题最后都是在这一层破案的。比如上位机说"你发了两个逗号",你根本不用在逻辑里翻,直接把报文拉出来数一下就知道是哪个字段多拼了东西。

5. 没有常用函数时的手写替代方案

你可能会遇到一个尴尬的情况:用的PLC系列比较老,或者项目组要求在标准库基础上减少函数调用,这时候CONCAT和REPLACE不一定可随便用。这些年我攒了两个比较稳定的手写方案,放在这里供参考。

5.1 手写字符串拼接:用FOR循环和BYTE数组实现

思路就是定义一个字节数组,用FOR循环把源字符串里的有效字符依次搬进目标数组。三菱ST里BYTE数组配合ASCII相关函数可以完成这个操作。简单骨架:

// 功能:把src追加到dst尾部,dst是STRING变量 // 注意:src里的空格会被跳过(按有效字符处理)

实际写成ST代码时,我会先用LEN拿到两个字符串的字节数,再用MID按单字节取出字符,拼到目标字符串后面。性能方面,10个字节以内的拼接基本无感,超过50个字节循环取字符会稍微慢一些,但工单信息这种体量完全够用。优点是你对每个字节都有绝对控制权,能剔除空格、终止符、回车的干扰。

5.2 手写模板替换:解决REPLACE遇到变长内容的尴尬

REPLACE的问题是:它按固定长度替换,内容变长变短都会导致模板错位。工单模板填充的时候,工单号可能8位也可能18位,直接REPLACE很容易把后面的字符顶丢。所以我写了一个"按起始标记、结束标记截断替换"的函数。原理是:

  • 用INSTR找到起始标记位置;
  • 用INSTR找到结束标记位置;
  • 计算出要替换的旧内容长度;
  • 用LEFT取标记前内容、RIGHT取标记后内容,再CONCAT成新字符串。

这个方案相当于手写了一个支持变长的模板替换。同样是填充{ORD},它可以保持模板其它部分原封不动,比原生REPLACE更稳。代价是代码量上去了,一旦遇到多个占位符,就得写循环或者重复多段处理。我的建议是:占位符少于三个时手写没问题,多了还是建议用原生REPLACE配合长度约束,让上位机下发固定长度的工单号字符串。

6. 选型建议:CONCAT、REPLACE、手写方案各自的上场时机

很多朋友喜欢把问题简单化,总想问"哪个函数好用"。但字符串处理这件事,没有绝对的好用,只有匹配不匹配。我列一个自己在项目里用来做选型的对照表,也方便后面复制到内部文档里。

场景首选方案备选方案原因
几段字段拼成一条报文(工单+批次+数量)CONCAT手写数组拼接CONCAT写法简单,可读性好;手写方案能严格控字节
模板报文填充固定占位符REPLACE + INSTR手写模板替换REPLACE高效,配合INSTR定位即可
模板报文填充变长字段手写模板替换REPLACE + 长度规整变长内容容易破坏模板结构
通信数据清洗(去空格)自己写Trim无原生可直接调用三菱没标配Trim,至少我接触的系列里没有
高频扫描段内拼接尽量少用字符串函数静态常量预置防止扫描周期恶化

选型判断标准有三条:数据来源是否可控、字符串长度是否固定、寄存到通信收发是否与协议严格对齐。数据来源不可控,优先做清洗再拼接;长度不固定,优先模板替换和手写方案;对接上位机通信,优先字节级方案。

另外提一个容易被忽略的点:ST语言里字符串变量可以赋字符串常量,比如sVersion := 'VER1.0';,但常量赋值同样受限于变量长度。工单号这类业务数据,我建议在触摸屏或者MES侧就统一成固定位数,不足位补零或者补空格,到PLC里只是当一个"定长字段"处理,所有拼接逻辑都按定长来做,工程会简单很多。千万不要在PLC里既要处理定长又要处理变长,那是最容易埋雷的。

7. 一个不起眼但能救命的习惯:上电自检和字符串诊断

字符串函数的坑,很多时候是"运行时才暴露",不是编译时能拦住的。所以我在项目里给字符串处理单独建了一个诊断页,把关键拼接结果、源字符串长度、目标字符串长度全部传给触摸屏。设备调试阶段打开诊断页,跑一遍完整流程,所有字符串相关交接点一目了然。

上电自检我建议做三件事。第一,把每条外部通信输入的重要字符串变量打印LEN,和协议预期值做对比;第二,把工单拼接后的首字节、末字节打印出来,看有没有异常;第三,把拼接结果做一次往返校验——用PLC自己解析一遍拼出来的报文,看能不能还原回各字段,类似编码解码自检。

这三件事成本极低,但能避免大量"设备跑三天后突发通讯故障"的诡异问题。我在好几个项目里就是因为这个习惯,把原本可能要在现场熬两天的排查工作,缩短到了半小时内定位到是MES端多传了一个换行符导致的。

最后再分享一个小技巧:调试字符串相关逻辑时,别只用监视器看,试着把字符串输出到触摸屏报警信息里,或者直接在PLC里用串口调试指令发到PC端串口工具。眼睛看到实际字节流之后,很多"我认为是这样"的错误预判都会被纠正。经过几次洗礼,你自然就会总结出自己的一套字符串处理规矩。

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

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

立即咨询