文章目录

ST忘记加分号 ;:编译报错中最频繁的低级错误

发布于 2026-03-19 08:16:47 · 浏览 7 次 · 评论 0 条

在结构化文本(ST)编程中,(分号)不是可选项,而是语法终结符。它标志着一条完整语句的结束。没有它,编译器无法确定指令边界,从而立即报错。这是电气自动化工程师——尤其是刚接触IEC 61131-3标准PLC编程的新手——最常遇到、最易忽视、也最耽误调试时间的错误。


一、为什么ST语言必须用分号?

ST(Structured Text)是IEC 61131-3定义的五种PLC编程语言之一,语法类似Pascal和C。它采用显式语句终止机制:每条可执行语句(赋值、调用、条件、循环等)必须以分号结尾。

这与梯形图(LD)或功能块图(FBD)不同——后两者靠图形连接隐含执行顺序;而ST是纯文本,依赖标点划分逻辑单元。编译器逐行扫描源码,遇到换行不等于语句结束,只看到分号才确认:“这一条指令到此为止”。

例如:

MotorSpeed := 1500

这段代码语法非法。编译器读完 1500 后继续向后查找,直到行末或文件尾仍未发现 ;,于是报错:

Error 214: Missing semicolon at end of statement

而加上分号后:

MotorSpeed := 1500;

编译器立刻识别为一条完整赋值语句,解析通过。

注意:分号仅终止语句,不终止声明。变量声明、函数声明、程序组织单元(POU)定义等使用 VAR ... END_VARFUNCTION_BLOCK ... END_FUNCTION_BLOCK 等成对关键字包裹,内部不需分号(但其内部语句仍需)。


二、哪些地方必须写分号?(逐类实操清单)

以下所有场景均以 ; 结尾,缺一不可。请对照你的代码逐项核对:

  1. 简单赋值语句
    输入 := 右侧是常量、变量、表达式时,整行必须加分号。
    ✅ 正确:

    Temperature := 25.3;
    PumpState := TRUE;
    CycleTime := T#5S;
  2. 复合表达式赋值
    即使含运算符、函数调用,只要是一条赋值,就需分号。
    ✅ 正确:

    OutputValue := (InputA * Gain) + Offset;
    MaxTemp := MAX(Temp1, Temp2, Temp3);
    TimerStart := NOT TimerDone AND StartButton;
  3. IF 条件语句的每个分支结尾
    IF...THEN 块内每条独立语句都要分号;ELSIFELSE 后的语句同理。
    ✅ 正确:

    IF MotorOverload THEN
        AlarmCode := 101;
        SoundAlarm := TRUE;
    ELSIF MotorReady THEN
        MotorStart := TRUE;
        ResetFault := FALSE;
    ELSE
        MotorStart := FALSE;
    END_IF;

    ⚠️ 注意:END_IF 后也要加分号(它是语句级关键字,不是块标记)。

  4. FOR / WHILE 循环体内的每条语句
    循环内部仍是普通语句序列,逐条终结。
    ✅ 正确:

    FOR i := 0 TO 9 DO
        DataBuffer[i] := SensorRead(i);
        DelayCounter[i] := DelayCounter[i] + 1;
    END_FOR;
  5. CASE 语句各分支中的语句
    每个 CASE ... OF 分支下若含多条语句,每条都需分号;单条也需。
    ✅ 正确:

    CASE Mode OF
        0: StateLED := OFF;
        1: BEGIN
               StateLED := GREEN;
               Buzzer := FALSE;
           END;
        2: StateLED := RED;
    END_CASE;

    ⚠️ BEGIN...END 是复合语句容器,其内部语句仍须分号;END_CASE 后同样需要分号。

  6. 函数/功能块调用语句
    调用本身是一条语句,必须分号。
    ✅ 正确:

    CalcPID(SETPOINT:=SP, PROCESSVAR:=PV, OUTPUT=>OutVal);
    SendCANMessage(ID:=100, DATA:=CanData);
  7. RETURN 语句
    RETURN; 是独立语句,不可省略分号。
    ✅ 正确:

    FUNCTION CheckLimit : BOOL
    VAR
        Input : REAL;
    END_VAR
    IF Input > 100.0 THEN
        CheckLimit := TRUE;
        RETURN;
    ELSE
        CheckLimit := FALSE;
    END_IF;

三、哪些地方绝对不加分号?(高频误加区)

错误加分号比漏加分号更隐蔽,可能引发“语法正确但逻辑异常”的问题。

  1. VAR 声明区内部
    变量声明用 : 分隔类型,用换行分隔不同变量,不加分号
    ❌ 错误:

    VAR
        Speed : INT;
        Temp : REAL;
        Running : BOOL;
    END_VAR;

    ✅ 正确:

    VAR
        Speed : INT
        Temp : REAL
        Running : BOOL
    END_VAR
  2. TYPE 自定义类型定义中
    类型字段声明同上,无分号。
    ✅ 正确:

    TYPE MOTOR_CONFIG :
    STRUCT
        MaxSpeed : INT
        MinVoltage : REAL
        EnablePin : BYTE
    END_STRUCT
    END_TYPE
  3. IF / FOR / CASE 等关键字之后
    IFFORCASE 后跟的是条件表达式或值列表,不是语句,不加分号
    ❌ 错误:

    IF Fault = TRUE; THEN  // 多余分号!编译直接失败
    FOR i := 0 TO n; DO    // 错误:分号不能出现在 DO 前
  4. 数组索引、结构体成员访问等表达式内部
    这些是语法成分,非语句。
    ❌ 错误:

    Buffer[0;] := 1;      // [0;] 是非法语法
    Motor.Status; := TRUE; // ; 不能插在 . 和标识符之间

四、典型报错信息与定位技巧

不同PLC品牌编译器提示略有差异,但核心关键词高度一致。遇到以下任一提示,请第一反应检查分号:

报错原文(示例) 含义 定位建议
Expected ';' before token 'xxx' 编译器在遇到 xxx(如 THEN, DO, END_IF)前,预期先看到分号 查看 xxx 前一行末尾是否漏掉 ;
Unexpected end of file 文件末尾未收尾,通常因最后一行语句缺分号 检查 .ST 文件最后一行是否以 ; 结束
Syntax error: missing semicolon 明确指出缺失分号 光标通常停在出错行末或下一行开头
Invalid statement termination 终止符非法(如用了中文分号、空格后加分号) 检查是否误按了中文输入法下的 (Unicode U+FF1B),或 ;(分号后带空格再换行)

💡 快速定位法

  • 在编辑器中启用“显示所有字符”(通常为 ¶ 图标),查看行尾是否有 (换行符)而无 ;
  • 将光标置于疑似出错行末,按 HomeEnd 观察是否跳转到 ; 后——若直接停在行尾,说明无分号;
  • 使用编辑器“括号高亮”功能:将光标放在 END_IFEND_FOR 等处,看对应 IFFOR 是否被高亮——若不匹配,大概率是中间某条语句缺分号导致块解析错位。

五、预防策略:四步固化习惯

  1. 敲完等号 := 后,立即按分号 ;**
    养成肌肉记忆::=; 是一对固定组合键入。不要思考,形成条件反射。

  2. 粘贴代码后,第一件事:扫视所有行尾
    新增或复制代码段后,用鼠标拖选全部代码 → 按 Ctrl+F 输入 ;$`(正则模式开),搜索行尾分号。若结果数 < 你预估的语句数,立即补全。 3. **启用编辑器自动补全** 大多数专业PLC IDE(如TIA Portal、Codesys、Unity Pro)支持: - 输入 `:=` 后自动追加 `;`; - 输入 `IF` 后自动生成 `IF ... THEN\n\nEND_IF;` 框架; - 输入 `FOR` 自动生成 `FOR ... DO\n\nEND_FOR;`。 **务必开启这些选项,并信任它们**。 4. **提交前执行“分号审计”宏(推荐)** 若使用支持脚本的编辑器(如VS Code + Codesys插件),可配置一键命令: - 高亮所有以字母/数字结尾、后跟换行的行; - 或反向高亮所有以 `;` 结尾的行,对比总行数。 示例正则(VS Code搜索): `^[^;]*[a-zA-Z0-9]\s*$ —— 匹配“行尾是字母数字且无分号”的行(需开启正则模式)。


六、真实故障案例复盘

现象:某包装线PLC程序编译失败,报错 Error 214 at line 87。该行内容为:

IF ProductCount >= BatchSize THEN

排查过程

  • 行号87是 IF 行,但报错指向它——说明编译器在此卡住;
  • 实际问题在上一行:第86行为 BatchSize := 100漏了分号
  • 编译器将第86行与第87行合并解析为 BatchSize := 100 IF ProductCount ...,导致语法崩溃;
  • 修复:在第86行末添加 ;,编译通过。

✅ 教训:报错行号往往是“问题暴露位置”,而非“问题发生位置”。分号缺失会导致编译器后续解析全面错乱,因此永远从报错行的上一行开始检查


七、高级陷阱:分号与作用域嵌套

当嵌套多层结构时,分号位置稍有偏差,就会改变逻辑归属。

❌ 错误写法(意图:仅在 i=5 时置位 Flag):

FOR i := 1 TO 10 DO
    IF i = 5 THEN
        Flag := TRUE;
    END_IF
END_FOR;

⚠️ 问题:END_IF 后无分号 → 编译器认为 END_IFFOR 循环体的最后一条语句,但 END_FOR 前缺少分号,导致语法错误。

✅ 正确写法:

FOR i := 1 TO 10 DO
    IF i = 5 THEN
        Flag := TRUE;
    END_IF;
END_FOR;

再看一个更隐蔽的案例:

❌ 错误:

IF SensorOK THEN
    MotorRun := TRUE;
    Timer(IN := MotorRun, PT := T#2S);
END_IF;

⚠️ 表面看无问题,但 Timer(...) 是功能块调用,必须以分号结尾。此处虽写了分号,却写在了括号内:Timer(...); → ✅ 正确;但若误写为 Timer(...)(无分号),或 Timer(...); 被意外删掉,则整条调用失效。

✅ 最终合规版本:

IF SensorOK THEN
    MotorRun := TRUE;
    Timer(IN := MotorRun, PT := T#2S);
END_IF;

分号不是装饰,是ST语言的呼吸节点。它不增加功能,但缺失即致死。每一次编译失败,都是编译器在提醒你:语句的边界,必须亲手划定。

评论 (0)

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

扫一扫,手机查看

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