ST怎么写字符串拼接:Msg := CONCAT('Error ', INT_TO_STRING(Code));

发布于 2026-03-15 00:59:48 · 浏览 2 次 · 评论 0 条

在结构化文本(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) 要求 s1s2 类型完全一致(同为 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 位)范围 -3276832767 → 最长 6 字符("-32768" 是 6 字节);
    • DINT(32 位)范围 -21474836482147483647 → 最长 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 不接受 STRINGSTRING(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 实际长度小于 nLEFT 直接返回原串,无副作用。

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 是否允许 STRINGSTRING(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));,若 MsgSTRING(未指定长度),编译通过,但运行时 Msg 实际长度为 255,前 7 字节为 'Error ',后续全为 0 —— 因 INT_TO_STRING() 返回的字符串未填满整个 255 字节区,HMI 读取时遇到第一个 0 就停止,显示 "Error "
  • 解决:始终声明 STRING(n),且 n ≥ 预估最大长度。

五、调试技巧:三招快速定位字符串问题

  1. 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 出现在中间,说明提前截断或未正确填充。

  2. 监控 LEN()SIZEOF() 差异
    LEN(Msg) 返回声明长度(如 32);SIZEOF(Msg) 返回字节数(通常 = LEN(Msg))。若 LEN(Msg)SIZEOF(Msg),说明 PLC 版本异常,立即换平台测试。

  3. 用 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 编译与运行测试,无依赖、无外部函数、无平台特异性,可直接集成至任何项目。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文