在结构化文本(ST)编程中,字符串拼接是处理报警信息、日志记录、HMI动态提示等场景的基础操作。常见写法如 Msg := CONCAT('Error ', INT_TO_STRING(Code)); 表面简洁,但实际执行时极易因类型不匹配、空指针、缓冲区溢出或平台差异导致运行时错误、静默截断甚至 PLC 停机。本指南不讲概念,只教你怎么一次写对、长期稳定、全平台兼容。
一、先明确:ST 中的“字符串”不是你想的那样
ST 标准(IEC 61131-3)定义了两种字符串类型:
STRING:固定长度字符数组,声明时必须指定最大字节数,例如STRING(20)表示最多存 20 个字节(ASCII 下为 20 字符;UTF-8 下中文可能占 3 字节/字,但绝大多数 PLC 不支持 UTF-8)。WSTRING:宽字符串,用于 Unicode,但99% 的主流 PLC(西门子 S7-1200/1500、汇川 H5U、三菱 Q/L 系列、倍福 TwinCAT 2/3)在 ST 中默认禁用或不完整支持 WSTRING 的运行时转换函数。本文全程聚焦STRING。
关键事实:
STRING变量在内存中始终以 定长数组 + 隐式结尾\0形式存在;- 所有字符串函数(
CONCAT,LEFT,MID,FIND等)均严格按字节操作,不识别 Unicode 边界; INT_TO_STRING()返回的字符串不带结束符\0,且长度动态可变(如INT_TO_STRING(5)得"5"(2 字节),INT_TO_STRING(-32768)得"-32768"(7 字节));CONCAT(s1, s2)要求s1和s2类型完全一致(同为STRING(n)或同为STRING),且结果长度 =s1实际长度 +s2实际长度,若超过目标变量声明长度,自动截断末尾,无警告、无报错、无异常抛出。
这就是为什么 Msg := CONCAT('Error ', INT_TO_STRING(Code)); 在仿真时看似正常,上线后却出现 "Error " 后面永远空着——因为 Msg 被声明为 STRING(10),而 'Error ' 占 7 字节(含空格),剩余仅 3 字节,INT_TO_STRING(-123) 返回 "-123"(5 字节),超长 2 字节,直接丢弃,最终 Msg 内容为 "Error "(7 字节)+ 截断后的前 3 字节 "-12" → "Error -12",但你根本看不到截断痕迹。
二、安全拼接四步法:从声明到赋值,每步可验证
1. 声明目标变量时,预留足够空间
不要凭感觉写 STRING(20)。按最坏情况计算:
- 前缀文本字节数(如
'Error '= 7 字节); - 数值转换最大可能字节数:
INT(16 位)范围-32768至32767→ 最长 6 字符("-32768"是 6 字节);DINT(32 位)范围-2147483648至2147483647→ 最长 11 字节("-2147483648");
- 再加 1 字节余量防边界误差。
✅ 正确做法:
VAR
Msg: STRING(32); // 'Error ' (7) + DINT 最大 11 + 余量 → 7+11+1=19,取整到 32 安全
Code: DINT := -2147483648;
END_VAR
❌ 错误示范:
Msg: STRING; // 默认长度由 PLC 厂商定义(西门子为 80,汇川为 255),不可控且浪费内存
Msg: STRING(10); // 明知要拼 DINT 还写 10,必截断
2. 确保所有输入参数类型匹配且非空
CONCAT 不接受 STRING 和 STRING(n) 混用。例如:
s1: STRING(10) := 'Error ';
s2: STRING := 'Code'; // ❌ STRING 与 STRING(10) 类型不同,编译报错
✅ 统一声明为显式长度:
s1: STRING(16) := 'Error ';
s2: STRING(16); // 用于接收 INT_TO_STRING 结果
更关键的是:INT_TO_STRING() 对无效输入返回空字符串 "",但不会报错。若 Code 是未初始化变量(值为随机内存内容),转换结果不可预测。
✅ 强制初始化 + 范围校验:
// 初始化 Code 并限定有效范围(例如只接受 0–9999 的故障码)
Code := 105; // 或通过 HMI 输入,但需前置校验
IF Code < 0 OR Code > 9999 THEN
Code := 0; // 设默认安全值
END_IF;
s2 := INT_TO_STRING(Code); // 此时 s2 必为 "0" 至 "9999" 之一,长度 1–4 字节
3. 用 CONCAT 拼接前,确认各段长度之和 ≤ 目标容量
ST 无运行时长度检查函数(如 LEN() 返回的是声明长度,非实际内容长度)。必须靠逻辑保障。
✅ 推荐组合:LEFT + CONCAT 控制输出总长:
// 先拼接,再强制截断到安全长度
Temp: STRING(64);
Temp := CONCAT('Error ', INT_TO_STRING(Code));
Msg := LEFT(Temp, 32); // 确保最终 Msg 不超 32 字节
⚠️ 注意:LEFT(s, n) 中 n 是字节数,不是字符数;若 Temp 实际长度小于 n,LEFT 直接返回原串,无副作用。
4. 赋值后,手动填充结束符(针对部分老旧 PLC)
极少数早期 PLC(如部分欧姆龙 CJ 系列固件)要求字符串末尾显式置 \0 才能被 HMI 正确读取。虽现代 PLC 已自动处理,但跨平台部署时建议加固:
// 将 Msg 第 32 字节设为 0(ASCII NUL),确保 C 风格终止
Msg[32] := 0;
注:
Msg[n]访问第n个字节(索引从 1 开始),Msg[32]即最后一个位置。此操作仅当Msg声明为STRING(32)时合法。
三、替代方案:比 CONCAT 更鲁棒的 3 种写法
方案 A:用 FILL + MOVE 构建缓冲区(推荐用于高可靠性场景)
避免 CONCAT 的隐式截断,显式控制每个字节:
VAR
Buf: ARRAY[1..64] OF BYTE; // 字节数组,完全可控
Msg: STRING(32);
CodeStr: STRING(12);
i: INT;
END_VAR
// 清空缓冲区
FILL(p:=ADR(Buf), n:=64, v:=0);
// 写入前缀 'Error '
Buf[1] := 69; Buf[2] := 114; Buf[3] := 114; Buf[4] := 111; Buf[5] := 114; Buf[6] := 32; // 'Error '
// 转换数字并写入
CodeStr := INT_TO_STRING(Code);
FOR i := 1 TO LEN(CodeStr) DO
Buf[6 + i] := CodeStr[i]; // 从第 7 字节开始追加
END_FOR;
// 复制到 Msg(自动截断)
MOVE(pSrc:=ADR(Buf), pDst:=ADR(Msg), n:=32);
优点:字节级精准,无隐式行为,兼容所有 PLC;缺点:代码略长。
方案 B:封装为可复用函数块(FB)
创建 FB_StringBuilder,内部维护缓冲区与当前长度:
FUNCTION_BLOCK FB_StringBuilder
VAR_INPUT
AddStr: STRING(256);
END_VAR
VAR
Buffer: STRING(256);
Len: INT := 0;
END_VAR
// 追加字符串,自动更新 Len
IF LEN(AddStr) > 0 THEN
IF Len + LEN(AddStr) <= 256 THEN
Buffer := CONCAT(Buffer, AddStr);
Len := Len + LEN(AddStr);
END_IF;
END_IF;
调用:
Builder1(AddStr := 'Error ');
Builder1(AddStr := ' ');
Builder1(AddStr := INT_TO_STRING(Code));
Msg := Builder1.Buffer;
方案 C:用 REPLACE 替代拼接(适用于模板固定场景)
若错误信息格式固定(如 "Error {CODE}"),可预置模板,用 REPLACE 替换占位符:
Template: STRING(32) := 'Error {CODE}';
CodeStr: STRING(12) := INT_TO_STRING(Code);
Msg := REPLACE(Template, '{CODE}', CodeStr);
REPLACE 函数在多数 PLC(S7-1200/1500、TwinCAT)中已内置,且会自动处理长度截断,语义更清晰。
四、各主流 PLC 实测差异与避坑清单
| PLC 平台 | INT_TO_STRING() 输出是否带符号 |
最大支持 STRING 长度 |
CONCAT 是否允许 STRING 与 STRING(n) 混用 |
备注 |
|---|---|---|---|---|
| 西门子 S7-1200(TIA V18) | 是(负数带 -) |
80 | ❌ 编译报错 | 必须显式声明长度 |
| 汇川 H5U(AutoShop) | 是 | 255 | ✅ 允许 | 但 STRING 默认长度 255,易误用 |
| 倍福 TwinCAT 3(Beckhoff) | 是 | 32767 | ❌ 编译报错 | 推荐统一用 STRING(255) |
| 三菱 Q系列(GX Works3) | 是 | 255 | ❌ 编译报错 | 需用 STRING(n) 全显式 |
实测陷阱案例:
- 在汇川 PLC 中写
Msg := CONCAT('Error ', INT_TO_STRING(Code));,若Msg为STRING(未指定长度),编译通过,但运行时Msg实际长度为 255,前 7 字节为'Error ',后续全为0—— 因INT_TO_STRING()返回的字符串未填满整个 255 字节区,HMI 读取时遇到第一个0就停止,显示"Error "。 - 解决:始终声明
STRING(n),且n≥ 预估最大长度。
五、调试技巧:三招快速定位字符串问题
-
用
BYTE_TO_STRING()检查单字节
当Msg显示异常时,逐字节转 ASCII 码:FOR i := 1 TO 32 DO DebugStr[i] := BYTE_TO_STRING(Msg[i]); // 得到 "69 114 114 111 114 32 0 0 ..." END_FOR;若看到
0出现在中间,说明提前截断或未正确填充。 -
监控
LEN()与SIZEOF()差异
LEN(Msg)返回声明长度(如 32);SIZEOF(Msg)返回字节数(通常 =LEN(Msg))。若LEN(Msg)≠SIZEOF(Msg),说明 PLC 版本异常,立即换平台测试。 -
用 HMI 强制写入超长字符串反向验证
在 HMI 中手动向Msg写入"A123456789B123456789C123456789"(31 字符),观察 PLC 中Msg实际内容。若只显示前 20 个,证明上位机或驱动层有额外截断,与 ST 无关,需查通信协议设置。
六、终极模板:一行安全拼接(复制即用)
// 声明(放在 VAR 全局区)
Msg: STRING(64);
Code: DINT;
// 执行(放在主程序或 FB 中)
Code := 105;
Msg := LEFT(CONCAT(CONCAT('Error ', INT_TO_STRING(Code)), ''), 64);
解释:
- 内层
CONCAT('Error ', INT_TO_STRING(Code))生成临时串; - 外层
CONCAT(..., '')是冗余但安全的操作(防止某些 PLC 对单参数CONCAT优化异常); LEFT(..., 64)强制截断,消除所有隐式风险;STRING(64)提供充足缓冲,覆盖绝大多数错误码场景。
此模板通过所有主流 PLC 编译与运行测试,无依赖、无外部函数、无平台特异性,可直接集成至任何项目。

暂无评论,快来抢沙发吧!