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;
ELSIF和ELSE分支可选- 条件表达式结果必须为
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;
- 表达式必须为整数类型(
INT、DINT、SINT、UINT等) - 值列表支持:单个常量、逗号分隔列表、范围(
..) 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 对比
| 特性 | WHILE | REPEAT |
|---|---|---|
| 检查时机 | 进入前 | 执行后 |
| 最少执行次数 | 0 次 | 1 次 |
| 退出条件 | 条件为 FALSE 退出 | 条件为 TRUE 退出 |
| 典型场景 | 循环次数未知,可能跳过 | 至少执行一次的操作 |
6. EXIT、CONTINUE、RETURN — 跳转语句
EXIT — 立即退出循环
跳出当前最内层循环(FOR、WHILE、REPEAT)。
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 语句。结构化编程应使用 IF、CASE、循环和函数调用来替代 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 数组与字符串