SCL 编程语言
SCL (Structured Control Language) 是 Darra PLC 的文本编程语言,语法类似 Pascal,适用于复杂算法、数据处理和数学运算。它是 Darra IDE 三种 PLC 逻辑编辑器之一(SCL、LAD、CFC)。

数据类型
基本数据类型
| 类型 | 大小 | 范围 | 示例 |
|---|---|---|---|
BOOL | 1 位 | TRUE / FALSE | bReady := TRUE; |
BYTE | 8 位 | 0 ~ 255 | byStatus := 16#FF; |
SINT | 8 位 | -128 ~ 127 | siTemp := -10; |
INT | 16 位 | -32768 ~ 32767 | iCount := 100; |
DINT | 32 位 | -2^31 ~ 2^31-1 | diPosition := 123456; |
LINT | 64 位 | -2^63 ~ 2^63-1 | liTimestamp := 0; |
UINT | 16 位 | 0 ~ 65535 | uiSpeed := 3000; |
UDINT | 32 位 | 0 ~ 2^32-1 | udiEncoder := 0; |
REAL | 32 位 | IEEE 754 单精度 | rTemp := 25.5; |
LREAL | 64 位 | IEEE 754 双精度 | lrAngle := 3.14159265; |
STRING | 可变 | 最长 255 字符 | sName := 'Darra'; |
TIME | 32 位 | 毫秒精度 | tDelay := T#500ms; |
DATE | 32 位 | 日期 | dToday := D#2026-04-07; |
TOD | 32 位 | 时间 | todNow := TOD#14:30:00; |
DT | 64 位 | 日期时间 | dtStamp := DT#2026-04-07-14:30:00; |
复合数据类型
// 数组
VAR
aiValues : ARRAY[0..9] OF INT;
arMatrix : ARRAY[0..2, 0..2] OF REAL;
END_VAR
// 结构体
TYPE MotorData :
STRUCT
bEnable : BOOL;
rSpeed : REAL;
diPosition : DINT;
sName : STRING[32];
END_STRUCT;
END_TYPE
// 枚举
TYPE MachineState :
(
IDLE := 0,
RUNNING := 1,
PAUSED := 2,
ERROR := 3
);
END_TYPE
运算符
运算符优先级 (从高到低)
| 优先级 | 运算符 | 说明 |
|---|---|---|
| 1 | () | 括号 |
| 2 | NOT | 逻辑非 |
| 3 | ** | 幂运算 |
| 4 | *, /, MOD | 乘、除、取模 |
| 5 | +, - | 加、减 |
| 6 | <, >, <=, >= | 比较 |
| 7 | =, <> | 等于、不等于 |
| 8 | AND, & | 逻辑与 |
| 9 | XOR | 逻辑异或 |
| 10 | OR | 逻辑或 |
控制流
IF-THEN-ELSE
IF rTemperature > 80.0 THEN
bOverheat := TRUE;
bFanOn := TRUE;
ELSIF rTemperature > 60.0 THEN
bOverheat := FALSE;
bFanOn := TRUE;
ELSE
bOverheat := FALSE;
bFanOn := FALSE;
END_IF;
CASE
CASE iState OF
0: // 待机
bMotorOn := FALSE;
1: // 运行
bMotorOn := TRUE;
rSpeed := rTargetSpeed;
2: // 减速
rSpeed := rSpeed - rDecelRate;
IF rSpeed <= 0.0 THEN
iState := 0;
END_IF;
ELSE
iState := 0; // 未知状态复位
END_CASE;
FOR
// 正向循环
FOR i := 0 TO 9 DO
aiValues[i] := i * 10;
END_FOR;
// 带步长
FOR i := 0 TO 100 BY 5 DO
rSum := rSum + DINT_TO_REAL(i);
END_FOR;
WHILE
iIndex := 0;
WHILE iIndex < 10 AND NOT bFound DO
IF aiBuffer[iIndex] = iTarget THEN
bFound := TRUE;
ELSE
iIndex := iIndex + 1;
END_IF;
END_WHILE;
REPEAT-UNTIL
iRetry := 0;
REPEAT
bResult := SendCommand(iCmd);
iRetry := iRetry + 1;
UNTIL bResult OR iRetry >= 3
END_REPEAT;
循环保护
编译器会检测死循环风险。循环体内必须有可达的退出条件,否则编译器将报告警告。运行时如果单次扫描中循环执行超过最大迭代次数,系统会强制中断并触发 OB80 时间错误。
函数
FUNCTION CalculateDistance : REAL
VAR_INPUT
rX1, rY1 : REAL;
rX2, rY2 : REAL;
END_VAR
VAR
rDx, rDy : REAL;
END_VAR
rDx := rX2 - rX1;
rDy := rY2 - rY1;
CalculateDistance := SQRT(rDx * rDx + rDy * rDy);
END_FUNCTION
功能块
FUNCTION_BLOCK FB_MotorControl
VAR_INPUT
bStart : BOOL;
bStop : BOOL;
rSetSpeed : REAL;
END_VAR
VAR_OUTPUT
bRunning : BOOL;
rActSpeed : REAL;
END_VAR
VAR
tonDelay : TON;
rRampSpeed : REAL;
END_VAR
// 启停逻辑
IF bStart AND NOT bStop THEN
bRunning := TRUE;
ELSIF bStop THEN
bRunning := FALSE;
END_IF;
// 速度斜坡
IF bRunning THEN
IF rRampSpeed < rSetSpeed THEN
rRampSpeed := rRampSpeed + 1.0;
END_IF;
ELSE
rRampSpeed := 0.0;
END_IF;
rActSpeed := rRampSpeed;
END_FUNCTION_BLOCK
功能块实例化与调用
VAR
fbMotor1 : FB_MotorControl;
fbMotor2 : FB_MotorControl;
END_VAR
// 调用功能块
fbMotor1(bStart := bBtn_Start, bStop := bBtn_Stop, rSetSpeed := 1500.0);
fbMotor2(bStart := bBtn_Start2, bStop := bBtn_Stop2, rSetSpeed := 3000.0);
// 读取输出
bMotor1Running := fbMotor1.bRunning;
rMotor1Speed := fbMotor1.rActSpeed;
程序 (PROGRAM)
PROGRAM Main
VAR
fbMotor : FB_MotorControl;
bStart : BOOL;
bStop : BOOL;
END_VAR
// 读取输入
bStart := %IX0.0;
bStop := %IX0.1;
// 执行控制
fbMotor(bStart := bStart, bStop := bStop, rSetSpeed := 1000.0);
// 写入输出
%QX0.0 := fbMotor.bRunning;
END_PROGRAM
编码建议
- 变量命名使用匈牙利前缀:
b(BOOL),i(INT),di(DINT),r(REAL),s(STRING),t(TIME),a(ARRAY),fb(功能块实例) - 每个功能块应有明确的单一职责
- 复杂逻辑添加中文注释说明意图
字符串处理
IEC 61131-3 提供丰富的字符串函数:
VAR
sName : STRING[32] := 'Darra PLC';
sPart : STRING[32];
sFull : STRING[64];
iLen : INT;
iPos : INT;
END_VAR
// 基本操作
iLen := LEN(sName); // 9
sPart := LEFT(sName, 5); // 'Darra'
sPart := RIGHT(sName, 3); // 'PLC'
sPart := MID(sName, 3, 7); // 'PLC' (从位置7取3字符)
sFull := CONCAT(sName, ' v1.0'); // 'Darra PLC v1.0'
iPos := FIND(sName, 'PLC'); // 7
sFull := INSERT(sName, '软件', 6); // 'Darra 软件PLC'
sPart := DELETE(sName, 5, 6); // 'Darra' (从位置6删5字符)
sFull := REPLACE(sName, 'C++', 3, 7); // 'Darra PC++'
// 格式化 (厂家扩展)
sFull := CONCAT('Temp=', REAL_TO_STRING(rTemp));
sFull := CONCAT(sFull, '°C');
// 查找失败判断
iPos := FIND(sInput, 'ERROR');
IF iPos > 0 THEN
LogWarn('Input contains ERROR');
END_IF;
字符串与 STRING 容量
STRING[N]表示最大 N 字符, 未指定时默认 80- 超出容量的赋值会被截断, 不抛异常
- 字符串赋值按值复制, 大字符串拷贝开销显著, 频繁拼接建议用中间缓冲
- 中文字符串建议用
WSTRING(UTF-16) 避免编码问题
数组操作深化
VAR
// 多维数组
arMap : ARRAY[0..9, 0..9] OF BOOL;
arCube : ARRAY[0..3, 0..3, 0..3] OF REAL;
// 数组切片 (厂家扩展)
arSource : ARRAY[0..99] OF INT;
arDest : ARRAY[0..9] OF INT;
// 数组常量初始化
arPreset : ARRAY[0..4, 0..2] OF INT := [
[10, 20, 30],
[40, 50, 60],
[70, 80, 90],
[100, 110, 120],
[130, 140, 150]
];
// 高级初始化 (重复语法)
arZeros : ARRAY[0..99] OF INT := [100(0)]; // 100 个 0
arOnes : ARRAY[0..9] OF REAL := [10(1.0)]; // 10 个 1.0
END_VAR
// 遍历
FOR i := 0 TO 9 DO
FOR j := 0 TO 9 DO
arMap[i, j] := (i = j); // 对角线置 TRUE
END_FOR;
END_FOR;
// 整体赋值 (同类型同尺寸)
arDest := arSource[10..19]; // 切片赋值 (厂家扩展)
// 查找
bFound := FALSE;
FOR i := 0 TO 99 DO
IF arSource[i] = iTarget THEN
iIndex := i;
bFound := TRUE;
EXIT; // 提前退出循环
END_IF;
END_FOR;
// 排序 (简化冒泡)
FOR i := 0 TO 98 DO
FOR j := 0 TO 98 - i DO
IF arSource[j] > arSource[j + 1] THEN
iTmp := arSource[j];
arSource[j] := arSource[j + 1];
arSource[j + 1] := iTmp;
END_IF;
END_FOR;
END_FOR;
结构体 (STRUCT) 深化
// 嵌套结构体
TYPE ST_Position :
STRUCT
rX : LREAL;
rY : LREAL;
rZ : LREAL;
END_STRUCT;
END_TYPE
TYPE ST_Pose :
STRUCT
stPos : ST_Position; // 嵌套
rRoll : REAL;
rPitch : REAL;
rYaw : REAL;
END_STRUCT;
END_TYPE
TYPE ST_Robot :
STRUCT
sName : STRING[32];
iAxisCount : INT := 6; // 默认值
stPose : ST_Pose;
arJointPos : ARRAY[0..5] OF REAL; // 数组成员
END_STRUCT;
END_TYPE
VAR
stRobot1 : ST_Robot := (sName := 'R1', iAxisCount := 6);
END_VAR
// 访问
stRobot1.stPose.stPos.rX := 100.5;
stRobot1.arJointPos[0] := 45.0;
// 整体赋值
stRobot2 := stRobot1; // 按值复制整个结构
指针 (REF_TO / POINTER TO)
IEC 61131-3 Third Edition 引入引用类型, Darra PLC 支持:
VAR
rValue : REAL := 3.14;
pRef : REF_TO REAL; // 引用类型
pPtr : POINTER TO INT; // 指针类型 (旧语法)
iArr : ARRAY[0..9] OF INT;
END_VAR
// 取地址
pRef := REF(rValue); // 引用赋值
pPtr := ADR(iArr[0]); // 取地址
// 解引用
pRef^ := 6.28; // 通过引用修改 rValue
iSum := iSum + pPtr^; // 读取指针指向的值
// 空引用判断
IF pRef <> 0 THEN
pRef^ := 0.0;
END_IF;
// 引用作为 VAR_IN_OUT 的替代
FUNCTION_BLOCK FB_ArraySum
VAR_INPUT
pData : REF_TO ARRAY[0..99] OF INT; // 传数组引用
iCount : INT;
END_VAR
VAR_OUTPUT
diSum : DINT;
END_VAR
diSum := 0;
FOR i := 0 TO iCount - 1 DO
diSum := diSum + pData^[i];
END_FOR;
END_FUNCTION_BLOCK
指针安全
- 空指针解引用 (
pRef^当pRef = 0) 触发 OB121 运行时异常 - 指针不可算术运算 (不支持
pPtr + 1), 只能赋值 / 解引用 / 比较 - 建议优先用
REF_TO而非POINTER TO(前者类型安全, 后者只检查大小) - 禁止用指针跨 POU 生命周期传递 (引用的对象可能已析构)
运算符扩展
位运算
VAR
wStatus : WORD;
bBit5 : BOOL;
END_VAR
wStatus := wStatus OR 16#0020; // 置 bit5
wStatus := wStatus AND NOT 16#0020; // 清 bit5
wStatus := wStatus XOR 16#FFFF; // 取反所有位
bBit5 := (wStatus AND 16#0020) <> 0; // 读 bit5
// 位访问 (Darra 扩展)
wStatus.5 := TRUE; // 等价于置 bit5
bBit5 := wStatus.5; // 等价于读 bit5
移位与循环
iShifted := SHL(iValue, 4); // 左移 4 位 (乘 16)
iShifted := SHR(iValue, 2); // 右移 2 位 (无符号除 4)
iRotated := ROL(iValue, 3); // 循环左移
iRotated := ROR(iValue, 3); // 循环右移
完整的 FB 示例: 状态机
FUNCTION_BLOCK FB_MachineController
VAR_INPUT
bStart : BOOL;
bStop : BOOL;
bReset : BOOL;
bHome : BOOL; // 回原完成信号
END_VAR
VAR_OUTPUT
eState : E_MachineState;
bRunning : BOOL;
sDiagMsg : STRING[64];
END_VAR
VAR
rtStart : R_TRIG;
tonTimeout : TON;
END_VAR
rtStart(CLK := bStart);
// 主状态机
CASE eState OF
E_MachineState.IDLE:
sDiagMsg := '待机';
bRunning := FALSE;
IF rtStart.Q THEN
eState := E_MachineState.HOMING;
END_IF;
E_MachineState.HOMING:
sDiagMsg := '回原中';
bRunning := TRUE;
tonTimeout(IN := TRUE, PT := T#30s);
IF bHome THEN
eState := E_MachineState.RUNNING;
tonTimeout(IN := FALSE);
ELSIF tonTimeout.Q THEN
eState := E_MachineState.ERROR;
sDiagMsg := '回原超时';
END_IF;
E_MachineState.RUNNING:
sDiagMsg := '运行中';
bRunning := TRUE;
IF bStop THEN
eState := E_MachineState.STOPPING;
END_IF;
E_MachineState.STOPPING:
sDiagMsg := '停机中';
bRunning := FALSE;
eState := E_MachineState.IDLE;
E_MachineState.ERROR:
sDiagMsg := '故障';
bRunning := FALSE;
IF bReset THEN
eState := E_MachineState.IDLE;
END_IF;
END_CASE;
END_FUNCTION_BLOCK
常见陷阱与排错
| 问题 | 原因 | 解决 |
|---|---|---|
FOR 循环意外退不出 | 循环变量被循环体修改 | 不要在 FOR 体内修改计数器 |
| 浮点数比较不相等 | IF rA = rB 精度问题 | 用 ABS(rA - rB) < 0.001 |
| 整数除法结果为 0 | iResult := 1 / 2 | 改为 REAL: 1.0 / 2.0 |
| 赋值类型不匹配 | iX := rY 报错 | 显式转换 iX := REAL_TO_INT(rY) |
| 字符串截断 | STRING[8] 存 20 字符 | 放大容量或缩短内容 |
| 数组越界 | arr[iIndex] 超范围 | 访问前 guard IF iIndex IN [0..99] THEN |
| FB 多实例串扰 | 忘了给每个实例命名 | 每个 FB 变量独立声明 |
| 递归调用报错 | FUNCTION 不支持递归 | 用 FUNCTION_BLOCK 或迭代 |
最佳实践
- 变量命名一致性: 项目内统一匈牙利前缀, 提高可读性
- 魔法数字常量化: 用
VAR CONSTANT MAX_SPEED : REAL := 6000.0;替代散落的6000.0 - 注释关键逻辑: 说明 "为什么", 不是 "做什么"
- 避免超长函数: 单个 POU 代码 > 200 行考虑拆分
- 显式类型转换: 禁止依赖隐式转换
- 字符串少用在高频周期: STRING 拷贝有性能开销, 高速任务用数值类型
- 用 CASE 代替深层 IF: 3 层以上 IF 嵌套改写为 CASE 更清晰

相关文档
- 梯形图 (LAD) — LAD 语言参考
- 拖拽式编程 (CFC) — 数据流连续功能图参考
- 变量与数据类型 — 作用域与类型详解
- 标准功能块 — 内置 FB 库
- 在线调试 — 调试工作流