文章目录

ST字符串比较:为什么直接用=比较STRING可能出错,需用COMPARE

发布于 2026-03-20 11:18:13 · 浏览 3 次 · 评论 0 条

在 S7-1200 / S7-1500 PLC 的 TIA Portal(博途)编程中,STRING 类型常用于人机交互、设备通信、日志记录等场景。当你需要判断两个字符串是否相等时,直接使用 = 运算符比较两个 STRING 变量,表面上看似可行,实则隐藏严重逻辑漏洞——它可能在绝大多数情况下“碰巧”返回正确结果,但在关键工况下彻底失效,导致设备误动作、连锁保护失效或诊断信息丢失。

根本原因在于:STRING 不是“纯文本”,而是一个带长度前缀的结构体。其内存布局包含三部分:

  • 第 0 字节:最大长度(MAX_LEN),占 1 字节;
  • 第 1 字节:当前实际长度(LEN),占 1 字节;
  • 第 2 字节起:最多 MAX_LEN 个字节的字符数据(DATA[ ]),每个字符为 CHAR(ASCII,1 字节)。

例如,声明 MyStr : STRING[10];,则该变量固定占用 1 + 1 + 10 = 12 字节。若你赋值 'ABC',内存实际存储为:

偏移 值(十六进制) 含义
0 0A MAX_LEN = 10
1 03 LEN = 3
2 41 'A'
3 42 'B'
4 43 'C'
5–11 00(共7个) 未使用的填充字节(空字符 \0

注意:PLC 不自动在末尾添加 C 风格终止符 \0,但未赋值区域保持为 0x00


为什么 = 比较会出错?

=字节级全内存比较运算符。它逐字节比对两个 STRING 变量所占全部 12 字节(以 STRING[10] 为例)是否完全一致。

这意味着:

  • Str1 := 'ABC';LEN=3,后 7 字节为 00
  • Str2 := 'ABC'; 但此前被其他逻辑写入过(例如先赋 'XYZ' 再改 'ABC'),其 LEN 变为 3,但第 5–11 字节可能仍残留旧值(如 00 00 00 00 00 00 00 或非零垃圾值)
  • 更典型的是通信接收场景:StrRx 来自 MODBUS TCP 或 S7 通信,其内容由外部设备填充。某些设备只写入 LEN 和前 N 个字符,剩余字节不初始化、不置零。此时 StrRx 的后缀可能为随机值(如 FF FF 00 1A 8B...);
  • 此时 Str1 = Str2 返回 FALSE,尽管两者语义上完全相同(都是 'ABC')。

换句话说:= 比较的是“内存镜像是否一模一样”,而非“用户意图表达的文本内容是否相同”。


正确解法:必须使用 COMPARE 指令

COMPARE(函数块 EQ_STRING 或指令 COMPARE)是专为字符串设计的语义级比较工具。它严格按以下三步执行:

  1. 检查 LEN 值是否相等
  2. LEN 相同,则仅比较前 LENDATA 字节
  3. 忽略 MAX_LEN 字段及超出 LEN 的所有字节

因此,无论 StrAStrB 的尾部填充是 00FF 还是随机噪声,只要 LEN 相同且前 LEN 个字符一致,COMPARE 就返回 TRUE


实操步骤:在 TIA Portal 中正确比较 STRING

1. 确认数据类型与声明方式

确保变量明确指定长度,避免隐式 STRING(默认 STRING[254],浪费内存且易引发越界):

// ✅ 推荐:显式声明合理长度
ProductName : STRING[32];
ErrorCode   : STRING[16];

// ❌ 避免:无长度限制(实际仍为254,但可读性差)
FaultMsg : STRING;

2. 在 LAD/FBD 中调用 COMPARE 指令

  • 在梯形图中,从指令库拖入 COMPARE 指令(位于 “Extended Instructions > String Operations > COMPARE”);
  • 连接 IN1IN2 为待比较的 STRING 变量;
  • LEN 输入端留空(自动取 IN1.LEN)或指定比较长度(如只比前5位);
  • 输出 OUTBOOLTRUE 表示相等。

3. 在 SCL 中调用 EQ_STRING 函数

SCL 提供标准函数 EQ_STRING(IN1 := ..., IN2 := ...),返回 BOOL

IF EQ_STRING(IN1 := ProductName, IN2 := 'Motor_123') THEN
    // 执行匹配动作
    StartMotor := TRUE;
END_IF;

⚠️ 注意:EQ_STRING 会自动忽略 MAX_LEN 和尾部填充,仅基于 LEN 和有效字符比较。

4. 处理大小写敏感问题

COMPAREEQ_STRING 默认区分大小写。若需忽略大小写,必须预处理:

// 方法1:转大写后比较(使用 UPPER 函数)
IF EQ_STRING(IN1 := UPPER(ProductName), IN2 := 'MOTOR_123') THEN ...

// 方法2:转小写(LOWER)
IF EQ_STRING(IN1 := LOWER(RecCmd), IN2 := 'reset') THEN ...

5. 验证比较结果的可靠性

编写测试用例,覆盖边界场景:

测试项 Str1 值 Str2 值 = 结果 COMPARE 结果 说明
纯相等 'OK' 'OK' TRUE TRUE 基础通过
LEN 相同但尾部脏 'OK'(尾部含 FF 'OK'(尾部 00 FALSE TRUE = 失败,COMPARE 正确
LEN 不同 'A'(LEN=1) 'A '(LEN=2,含空格) FALSE FALSE 语义不同,两者均正确
通信残留 'ERR'(LEN=3,字节5=0xAA) 'ERR'(LEN=3,字节5=0x00) FALSE TRUE 典型通信问题

常见陷阱与规避方案

❌ 陷阱1:用 MOVE 复制 STRING 后直接 =

MOVE(IN := 'ABC', OUT := StrBuf); // MOVE 只复制 LEN 和 DATA,不保证尾部清零
IF StrBuf = 'ABC' THEN ... // 可能失败!因 StrBuf 尾部残留旧值

✅ 解决:使用 FILL 清零整个变量,或改用 COMPARE

FILL(OUT := StrBuf, LEN := SIZEOF(StrBuf), DATA := 0); // 清零全部12字节
MOVE(IN := 'ABC', OUT := StrBuf);
IF EQ_STRING(IN1 := StrBuf, IN2 := 'ABC') THEN ... // 安全

❌ 陷阱2:从 CHAR 数组构造 STRING 时未设置 LEN

ARRAY[0..9] OF CHAR := ['H','E','L','L','O']; // 未设 LEN
MyStr := ARRAY_TO_STRING(ARR := CharArr, LEN := 5); // ✅ 必须显式传 LEN
// 若漏掉 LEN 参数,MyStr.LEN = 0 → 比较永远为 FALSE

❌ 陷阱3:跨设备通信时假设对方会清零

MODBUS ASCII 帧、串口协议、OPC UA 字符串字段常不保证填充区为 0x00。务必以 COMPARE 为唯一可信比较手段。


性能与资源影响说明

COMPARE 指令执行时间为 O(n),其中 n 是较短字符串的 LEN。对于 STRING[32],最坏情况仅比较 32 字节,耗时远低于 1μs(S7-1500 CPU)。相比因逻辑错误导致的停机损失,此开销可忽略。

内存方面:COMPARE 为无状态指令,不占用额外 DB 或全局存储。


替代方案对比(不推荐,仅作技术澄清)

方法 是否安全 原理 缺陷
= 运算符 ❌ 不安全 全内存字节比较 依赖尾部填充一致性,不可控
LEFT() 提取再比 ⚠️ 伪安全 LEFT(Str, 3) = 'ABC' Str.LEN < 3LEFT 返回空字符串,比较失效
LEN() + MID() 循环比 ✅ 安全但冗余 手动逐字符比对 代码臃肿,易出错,性能差
COMPARE / EQ_STRING ✅ 唯一推荐 标准化语义比较 无缺陷,TIA 官方认证方案

最终检查清单(部署前必做)

  1. 所有 STRING 比较操作均已替换为 COMPARE 指令或 EQ_STRING 函数
  2. 无任何 STRING 变量直接参与 =<>>= 等关系运算
  3. 通信接收的 STRING 变量,在首次比较前已确认 LEN 有效性(如 LEN > 0 AND LEN <= MAX_LEN
  4. 大小写敏感需求已通过 UPPER/LOWER 显式处理
  5. 测试用例覆盖 LEN 不同、尾部填充随机、空字符串、全满字符串五种场景

直接使用 = 比较 STRING,等于把逻辑安全押注在内存初始化的偶然性上。而 COMPARE 是唯一将“字符串相等”这一人类语义,精确映射到 PLC 字节世界的工业级契约。

评论 (0)

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

扫一扫,手机查看

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