跳到主要内容

SCL 控制流语句

SCL 基于 Pascal 语法体系,提供完整的结构化控制流语句。合理使用控制流是编写可维护 PLC 程序的基础。本章详细说明每种语句的语法、语义和典型用法。


1. IF...THEN...ELSIF...ELSE — 条件语句

语法

IF 条件1 THEN
// 条件1 为 TRUE 时执行
ELSIF 条件2 THEN
// 条件1 为 FALSE 且条件2 为 TRUE 时执行
ELSE
// 以上条件均为 FALSE 时执行
END_IF;
  • ELSIFELSE 分支可选
  • 条件表达式结果必须为 BOOL 类型
  • END_IF 后必须加分号

示例

IF iTemperature > 100 THEN
bOverTemp := TRUE;
uiWarningCount := uiWarningCount + 1;
ELSIF iTemperature > 80 THEN
bOverTemp := FALSE;
bPreWarning := TRUE;
ELSE
bOverTemp := FALSE;
bPreWarning := FALSE;
END_IF;

单行 IF

IF bEnable THEN xOutput := TRUE; END_IF;

嵌套 IF

IF bModeAuto THEN
IF bSensorIn THEN
xRun := TRUE;
ELSE
xRun := FALSE;
END_IF;
ELSE
xRun := xManualCmd;
END_IF;

注意:嵌套深度建议不超过 3 层,超过时考虑使用 CASE 或重构为子函数。


2. CASE...OF — 多分支选择

语法

CASE 表达式 OF
1:
// 值1 分支
2,3:
// 值2 或值3 分支
4..10:
// 值4 到值10 范围分支
ELSE
// 未匹配任何值时执行
END_CASE;
  • 表达式必须为整数类型(INTDINTSINTUINT 等)
  • 值列表支持:单个常量、逗号分隔列表、范围(..
  • ELSE 分支可选

示例

CASE iState OF
0:
// 空闲状态
xMotor := FALSE;
iStep := 0;
1:
// 启动状态
xMotor := TRUE;
tTimer(IN := NOT tTimer.Q, PT := T#2s);
IF tTimer.Q THEN
iState := 2;
END_IF;
2, 3:
// 运行中
xMotor := TRUE;
iSpeed := 1000;
10..20:
// 错误代码范围
xMotor := FALSE;
iErrorCode := iState;
ELSE
// 未定义状态
xMotor := FALSE;
iErrorCode := 99;
END_CASE;

注意CASE 分支执行后自动跳出,无需 BREAK。每个分支执行完即跳至 END_CASE


3. FOR...TO...BY...DO — 计数循环

语法

FOR 计数变量 := 初始值 TO 终止值 BY 步长 DO
// 循环体
END_FOR;
  • 计数变量自动声明为 INT,无需在变量表声明
  • BY 步长 可选,默认为 1
  • 步长可为负数(递减循环)

示例

// 从1到10,步长1
FOR i := 1 TO 10 DO
aiBuffer[i] := 0;
END_FOR;

// 从10到1,步长-1(递减)
FOR i := 10 TO 1 BY -1 DO
aiBuffer[i] := i * 10;
END_FOR;

// 步长2:偶数索引
FOR i := 0 TO 100 BY 2 DO
aiEven[i] := aiSource[i];
END_FOR;

数组求和

iSum := 0;
FOR i := 1 TO 100 DO
iSum := iSum + aiData[i];
END_FOR;

风险提示

  • 不要在循环体内修改计数变量
  • 避免死循环:确保终止条件可达
  • 扫描周期内循环次数建议不超过 10000,防止看门狗超时

4. WHILE...DO — 条件循环

语法

WHILE 条件 DO
// 循环体
END_WHILE;
  • 条件为 TRUE 时执行循环体
  • 进入循环检查条件
  • 适合循环次数未知的场景

示例

// 查找数组中第一个零值位置
iIdx := 1;
WHILE (iIdx <= 100) AND (aiData[iIdx] <> 0) DO
iIdx := iIdx + 1;
END_WHILE;
IF iIdx <= 100 THEN
iFirstZero := iIdx;
ELSE
iFirstZero := 0; // 未找到
END_IF;

链表遍历(伪代码示意)

iCurrent := iHeadPtr;
WHILE iCurrent <> 0 DO
// 处理当前节点
aiResult[iCurrent] := aiValue[iCurrent];
// 移到下一节点
iCurrent := aiNextPtr[iCurrent];
END_WHILE;

风险WHILE 条件永不满足时直接跳过循环体;条件始终为 TRUE 时形成死循环。务必在循环体内有改变条件的语句。


5. REPEAT...UNTIL — 直到循环

语法

REPEAT
// 循环体
UNTIL 条件
END_REPEAT;
  • 先执行循环体,再检查条件
  • 条件为 TRUE 时退出循环
  • 至少执行一次循环体

示例

// 至少执行一次,直到数据有效或超时
iRetry := 0;
REPEAT
bReadResult := ReadSensorData(adiRaw[iRetry + 1]);
iRetry := iRetry + 1;
UNTIL (bReadResult = TRUE) OR (iRetry >= 5)
END_REPEAT;

IF bReadResult THEN
iValidCount := iRetry;
ELSE
iValidCount := 0;
END_IF;

WHILE vs REPEAT 对比

特性WHILEREPEAT
检查时机进入前执行后
最少执行次数0 次1 次
退出条件条件为 FALSE 退出条件为 TRUE 退出
典型场景循环次数未知,可能跳过至少执行一次的操作

6. EXIT、CONTINUE、RETURN — 跳转语句

EXIT — 立即退出循环

跳出当前最内层循环(FORWHILEREPEAT)。

FOR i := 1 TO 100 DO
IF aiData[i] = 0 THEN
iFirstZero := i;
EXIT; // 找到第一个零值后立即退出
END_IF;
END_FOR;

CONTINUE — 跳过本次循环剩余部分

跳过当前迭代,进入下一次循环。

FOR i := 1 TO 100 DO
// 跳过偶数索引
IF i MOD 2 = 0 THEN
CONTINUE;
END_IF;
// 仅处理奇数索引
aiOddBuffer[i] := aiData[i];
END_FOR;

RETURN — 立即退出当前 POU

FUNCTION ScaleValue : REAL
VAR_INPUT
iRaw : INT;
rMin : REAL;
rMax : REAL;
END_VAR
VAR
rRange : REAL;
END_VAR

rRange := rMax - rMin;
IF rRange <= 0.0 THEN
ScaleValue := 0.0;
RETURN; // 输入无效,直接返回
END_IF;

ScaleValue := (INT_TO_REAL(iRaw) / 32767.0) * rRange + rMin;
END_FUNCTION

使用建议

语句使用场景建议
EXIT在循环中提前找到目标可接受,但有嵌套循环时注意层级
CONTINUE跳过特定条件,处理其余可读性有时不如 IF...ELSE 重构
RETURN提前退出函数/函数块建议限制在参数校验和错误处理

7. 嵌套与作用域规则

嵌套限制

  • 控制流语句可嵌套,深度建议不超过 4 层
  • 超过 4 层时重构为子函数(FC/FB)或使用状态机

变量作用域

// 外层变量(POU 级)
VAR
iCounter : INT;
aiData : ARRAY[1..100] OF INT;
END_VAR

// FOR 循环计数变量自动作用域在循环内
FOR i := 1 TO 100 DO
// i 仅在循环内可见
aiData[i] := i * 2;
END_FOR;
// 此处 i 不可访问

// 嵌套循环注意不能同名计数变量
FOR iOuter := 1 TO 10 DO
FOR iInner := 1 TO 20 DO
aiMatrix[iOuter, iInner] := iOuter * iInner;
END_FOR;
END_FOR;

标签与 GOTO 禁止

Darra PLC 的 SCL 实现不提供 GOTO 语句。结构化编程应使用 IFCASE、循环和函数调用来替代 GOTO

推荐嵌套模式

// 推荐:浅层 + 提前退出
IF NOT bPowerOK THEN
xMotor := FALSE;
RETURN;
END_IF;

IF bEmergencyStop THEN
xMotor := FALSE;
RETURN;
END_IF;

// 主逻辑
xMotor := bStartCmd AND NOT bFault;

反例(深层嵌套,不推荐):

// 不推荐
IF bPowerOK THEN
IF NOT bEmergencyStop THEN
IF bStartCmd THEN
IF NOT bFault THEN
xMotor := TRUE;
ELSE
xMotor := FALSE;
END_IF;
ELSE
xMotor := FALSE;
END_IF;
ELSE
xMotor := FALSE;
END_IF;
ELSE
xMotor := FALSE;
END_IF;

8. 综合示例:状态机 + 定时

PROGRAM MainCycle
VAR
iState : INT := 0;
tHold : TON; // 定时器
iStep : INT := 0;
bPause : BOOL;
END_VAR

CASE iState OF
0: // 等待启动
xRun := FALSE;
IF bStart THEN
iState := 10;
iStep := 0;
END_IF;

10: // 执行步进序列
iStep := iStep + 1;
xRun := TRUE;
tHold(IN := TRUE, PT := T#500ms);
IF tHold.Q THEN
iState := 20;
END_IF;

20: // 检查条件
IF bSensorOK THEN
iState := 30;
ELSE
iState := 99; // 错误
END_IF;

30: // 完成
xRun := FALSE;
iState := 0;

99: // 错误处理
xRun := FALSE;
xAlarm := TRUE;
IF bReset THEN
xAlarm := FALSE;
iState := 0;
END_IF;
END_CASE;

总结

语句用途执行次数关键点
IF...THEN条件分支0-1 次支持 ELSIF 链
CASE...OF多路选择0-1 次整数类型,自动跳出
FOR...TO...DO固定次数循环按计数计数变量自动管理
WHILE...DO可变条件循环0-N 次进入前检查
REPEAT...UNTIL至少一次循环1-N 次执行后检查
EXIT跳出循环仅跳出最内层
CONTINUE跳过本次进入下次迭代
RETURN退出 POU函数/函数块内使用

下一章SCL 数组与字符串