跳到主要内容

SCL 语法完全教程

SCL(Structured Control Language,结构化控制语言)是 Darra PLC 的文本编程语言,基于 IEC 61131-3 标准中的结构化文本(ST)语法。语法类似 Pascal / Ada,适合复杂算法、数据处理、状态机等高级控制逻辑。


目录

  1. SCL 基础
  2. 数据类型
  3. 变量声明
  4. 运算符
  5. 控制流语句
  6. 函数 (FUNCTION)
  7. 功能块 (FUNCTION_BLOCK)
  8. 程序 (PROGRAM)
  9. 标准函数库
  10. 标准系统函数
  11. 数组与字符串操作
  12. 指针与引用
  13. 面向对象编程
  14. 预处理器与编译指令

1. SCL 基础

1.1 什么是 SCL?

SCL 是 Darra PLC 的文本编程语言,对应于 IEC 61131-3 标准的 ST(结构化文本)。与梯形图(LAD)的图形化不同,SCL 用代码写控制逻辑,类似 C 语言但是用 Pascal 风格的关键字。

SCL 的定位:

  • 复杂算法 — PID 变体、滤波、运动规划
  • 数据处理 — 数组排序、统计、协议解析
  • 数学运算 — 浮点矩阵、三角变换、坐标映射
  • 状态机 — 多状态工艺步、安全连锁
  • 配方管理 — 配方参数计算与切换

1.2 Hello World

PROGRAM Main
VAR
sMessage : STRING[50];
END_VAR
sMessage := 'Hello, Darra PLC!';
// 在调试窗口输出
// 实际 PLC 中可写到一个 DB 变量供 HMI 读取
END_PROGRAM

试一试:在 Darra IDE 中新建一个 SCL 程序,输入上面的代码,编译后在线监视变量 sMessage 的值。

1.3 程序组织单元(POU)

SCL 有三种程序组织单元:

POU 类型关键字有记忆?用途
程序PROGRAM否(每次扫描结束输出清零)顶层入口,任务分配
函数FUNCTION否(纯组合逻辑)数学运算、转换、无状态算法
功能块FUNCTION_BLOCK是(内部状态保持)电机控制、PID、计数器

1.4 注释

// 单行注释 — 两个斜杠

/*
多行注释
块注释
*/

(*
另一种多行注释
IEC 61131-3 标准注释风格
*)

1.5 大小写敏感

SCL 不区分大小写,以下写法都合法:

motor_run := TRUE;    // 小写
MOTOR_RUN := TRUE; // 大写
Motor_Run := TRUE; // 混合

推荐使用约定

  • 变量名:camelCase(首字母小写驼峰)
  • 常量:UPPER_SNAKE(全大写 + 下划线)
  • 类型名:PascalCase(首字母大写)
  • FB 实例名:fbLowerCamel(小写驼峰 + fb 前缀)

1.6 语句结束符

每条语句以 分号 ; 结束:

xOut := xIn1 AND xIn2;
iCount := iCount + 1;

1.7 文件结构

一个典型的 SCL 文件:

// 文件: MotorControl.scl
// 功能: 电机启停控制功能块

FUNCTION_BLOCK MotorControl
VAR_INPUT
bStart : BOOL; // 启动按钮
bStop : BOOL; // 停止按钮
END_VAR
VAR_OUTPUT
xRun : BOOL; // 运行状态
END_VAR
VAR
fbTimer : TON; // 内部定时器实例
END_VAR

// 自锁逻辑
IF bStart AND NOT bStop THEN
xRun := TRUE;
END_IF;

IF bStop THEN
xRun := FALSE;
END_IF;
END_FUNCTION_BLOCK

2. 数据类型

SCL 是强类型语言,所有变量必须先声明类型再使用。

2.1 基本数据类型

位与逻辑类型

类型位宽范围默认值说明
BOOL1 bitTRUE / FALSEFALSE布尔量、逻辑运算
BYTE8 bit16#00 ~ 16#FF16#00位串、硬件 IO
WORD16 bit16#0000 ~ 16#FFFF16#0000位串、状态字
DWORD32 bit16#00000000 ~ 16#FFFFFFFF16#00000000双字位串
LWORD64 bit16#0000000000000000 ~ 16#FFFFFFFFFFFFFFFF16#0000000000000000长字位串

示例:

VAR
bStart : BOOL := FALSE;
bStop : BOOL;
xInputByte : BYTE;
wStatus : WORD := 16#8000;
dwConfig : DWORD := 16#00FF00FF;
lwCounter : LWORD;
END_VAR

有符号整数

类型位宽范围默认值
SINT8 bit-128 ~ 1270
INT16 bit-32,768 ~ 32,7670
DINT32 bit-2,147,483,648 ~ 2,147,483,6470
LINT64 bit-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,8070

示例:

VAR
iLoopCount : INT := 1000;
diPosition : DINT := -50000;
liBigCounter: LINT;
END_VAR

无符号整数

类型位宽范围默认值
USINT8 bit0 ~ 2550
UINT16 bit0 ~ 65,5350
UDINT32 bit0 ~ 4,294,967,2950
ULINT64 bit0 ~ 18,446,744,073,709,551,6150

示例:

VAR
uiSpeed : UINT := 500;
udiTotal : UDINT := 0;
usiIndex : USINT := 1;
END_VAR

浮点类型

类型位宽有效位数范围默认值
REAL32 bit6 ~ 9 位±1.18E-38 ~ ±3.40E+380.0
LREAL64 bit15 ~ 18 位±2.23E-308 ~ ±1.80E+3080.0

示例:

VAR
rTemp : REAL := 25.5;
rPi : REAL := 3.14159;
lrAngle: LREAL := 3.14159265358979;
END_VAR

精度陷阱REAL 只有 6-9 位有效数字,累加 100 万次后误差可达几百。需要高精度累积时用 LREAL

时间与日期类型

类型位宽说明示例
TIME32 bit毫秒精度时间T#500msT#2.5s
DATE32 bit日期D#2026-04-07
TOD32 bit时刻(Time of Day)TOD#14:30:00
DT64 bit日期和时间DT#2026-04-07-14:30:00

TIME 字面量语法:

tDelay1 := T#100ms;       // 100 毫秒
tDelay2 := T#2.5s; // 2.5 秒
tDelay3 := T#1m30s; // 1 分 30 秒
tDelay4 := T#1h15m30s; // 1 小时 15 分 30 秒
tDelay5 := T#24h; // 24 小时

日期示例:

VAR
tDelay : TIME := T#500ms;
dToday : DATE := D#2026-04-07;
todNow : TOD := TOD#14:30:00;
dtStamp : DT := DT#2026-04-07-14:30:00;
dtEvent : DT := DT#2026-12-31-23:59:59;
END_VAR

字符串类型

类型说明最大长度
STRING单字节 ASCII 字符串255 字符
STRING[N]指定长度的单字节字符串N 字符
WSTRING双字节 Unicode 字符串255 字符
WSTRING[N]指定长度的双字节字符串N 字符

示例:

VAR
sName : STRING[32] := 'Darra PLC';
sMessage : STRING[255];
wsUnicode: WSTRING[50] := "中文Unicode";
END_VAR

2.2 字面量语法

数字字面量:

iDecimal := 12345;          // 十进制
iBinary := 2#10101010; // 二进制(前缀 2#)
iOctal := 8#77; // 八进制(前缀 8#)
iHex := 16#FF; // 十六进制(前缀 16#)

浮点字面量:

r1 := 3.14159;              // 小数
r2 := 1.5e6; // 科学计数法(1.5 × 10^6)
r3 := 1.5E-3; // 科学计数法(1.5 × 10^-3)

布尔字面量:

b1 := TRUE;
b2 := FALSE;

2.3 类型转换

SCL 是强类型语言,不同类型之间不能自动转换,必须使用显式转换函数

隐式转换(允许的有限情况):

// 小范围 → 大范围(隐式允许)
iSmall : INT := 100;
diBig : DINT := iSmall; // 合法:INT 自动提升到 DINT

// 整数 → 浮点(隐式允许)
iVal : INT := 10;
rVal : REAL := iVal; // 合法:INT 自动转为 REAL

显式转换(必须使用转换函数):

// 大范围 → 小范围(必须显式)
diBig : DINT := 100000;
iSmall : INT := INT_TO_INT(diBig); // 合法,但可能溢出

// 浮点 → 整数(必须显式)
rVal : REAL := 25.8;
iVal : INT := REAL_TO_INT(rVal); // 结果为 25(截断,非四舍五入)

// 位串 → 整数(必须显式)
wWord : WORD := 16#00FF;
iVal : INT := WORD_TO_INT(wWord);

// 字符串 → 数值
sText : STRING[10] := '123';
iVal : INT := STRING_TO_INT(sText);

常见错误:忘记显式转换会编译报错。例如 DINT := INT + DINT 需要先做 INT_TO_DINT(INT)

2.4 数组

一维数组:

VAR
aiValues : ARRAY[0..9] OF INT; // 10 个元素
arTemp : ARRAY[1..100] OF REAL; // 100 个元素,下标从 1 开始
abFlags : ARRAY[0..31] OF BOOL; // 32 个布尔
END_VAR

多维数组:

VAR
arMatrix : ARRAY[0..2, 0..2] OF REAL; // 3×3 矩阵
aiGrid : ARRAY[0..3, 0..4, 0..5] OF INT; // 4×5×6 三维
END_VAR

数组初始化:

VAR
// 全部元素赋同一值
aiAllOnes : ARRAY[0..4] OF INT := [5(1)]; // [1, 1, 1, 1, 1]

// 逐个赋值
aiValues : ARRAY[0..4] OF INT := [10, 20, 30, 40, 50];

// 部分赋值,其余为默认值
aiPartial : ARRAY[0..4] OF INT := [10, 20]; // [10, 20, 0, 0, 0]

// 二维数组初始化
arMatrix : ARRAY[0..1, 0..2] OF INT := [1, 2, 3, 4, 5, 6];
END_VAR

数组访问:

// 读写元素
aiValues[0] := 100;
iX := aiValues[iIndex];

// 多维访问
arMatrix[1, 2] := 3.14;
rVal := arMatrix[row, col];

常见错误:数组下标越界!ARRAY[0..9] 只有 10 个元素,访问 [10] 会运行时错误。Darra PLC 支持运行时边界检查(可配置关闭以提升性能)。

2.5 结构体

定义结构体类型:

TYPE MotorData :
STRUCT
bEnable : BOOL;
rSpeed : REAL;
diPosition : DINT;
sName : STRING[32];
END_STRUCT;
END_TYPE

嵌套结构体:

TYPE AxisConfig :
STRUCT
bEnabled : BOOL;
rMaxSpeed : REAL;
rAcceleration: REAL;
rDeceleration: REAL;
END_STRUCT;
END_TYPE

TYPE MachineAxis :
STRUCT
stConfig : AxisConfig; // 嵌套结构体
diActualPos : DINT;
diTargetPos : DINT;
bHome : BOOL;
END_STRUCT;
END_TYPE

结构体变量声明与访问:

VAR
stMotor1 : MotorData;
stAxisX : MachineAxis;
END_VAR

// 访问成员
stMotor1.bEnable := TRUE;
stMotor1.rSpeed := 1500.0;
stMotor1.sName := '主轴电机';

rSpeed := stAxisX.stConfig.rMaxSpeed; // 嵌套访问

2.6 枚举类型

TYPE MachineState :
(
IDLE := 0, // 空闲
RUNNING := 1, // 运行
PAUSED := 2, // 暂停
ERROR := 3, // 故障
EMERGENCY := 4 // 急停
);
END_TYPE

使用枚举:

VAR
eState : MachineState := MachineState.IDLE;
END_VAR

CASE eState OF
MachineState.IDLE:
// 空闲逻辑
MachineState.RUNNING:
// 运行逻辑
MachineState.ERROR:
// 故障处理
END_CASE;

提示:枚举值在代码中用 类型名.值 访问,比直接写数字 0、1、2 更可读。

2.7 引用类型 (REF_TO)

VAR
refValue : REF_TO INT; // 指向 INT 的引用
refMotor : REF_TO MotorData; // 指向结构体的引用
END_VAR

(详见第 12 章「指针与引用」)

2.8 ANY 类型

ANY 是 IEC 61131-3 第三版引入的泛型占位类型,用于标准库函数:

// 标准库函数 MOVE 可接受 ANY 类型
MOVE(src := anyValue, dest := anyDest);

注意ANY 不能用于用户自定义函数(目前 Darra IDE 不支持用户泛型)。


3. 变量声明

3.1 变量声明区

SCL 用 VAR...END_VAR 块声明变量,不同的声明区有不同的作用域和访问权限:

声明区关键字作用域说明
输入VAR_INPUT外部传入只读,调用方传入
输出VAR_OUTPUT外部读取只写,函数执行后返回给调用方
输入输出VAR_IN_OUT双向传递引用传递,可读写
内部变量VAR内部使用仅本 POU 内可见
临时变量VAR_TEMP内部临时每次调用重新分配,不保持
静态变量VAR_STATICFB 内部保持上一次调用的值(仅 FB)
常量VAR CONSTANT全局/内部编译时常量,不可修改
全局变量VAR_GLOBAL全局可见所有 POU 共享

3.2 变量声明语法

FUNCTION_BLOCK Example
VAR_INPUT
bEnable : BOOL; // 输入:只读,从外部传入
iSetpoint : INT := 100; // 带默认值(调用方可不传)
END_VAR
VAR_OUTPUT
xRunning : BOOL; // 输出:只写,外部读取
rActual : REAL;
END_VAR
VAR_IN_OUT
stData : SomeStruct; // 输入输出:引用传递,可读写
END_VAR
VAR
iCounter : INT := 0; // 内部变量,保持值
fbTimer : TON; // 内部功能块实例
END_VAR
VAR_TEMP
iTemp : INT; // 临时变量,每次调用重新分配
END_VAR
VAR CONSTANT
c_MAX_SPEED : INT := 1500; // 常量
END_VAR

3.3 RETAIN 与 PERSISTENT

RETAIN(保持性变量):PLC 断电后值保留(靠电池或超级电容保持 RAM)。

VAR RETAIN
diProductionCount : DINT := 0; // 断电后保留
rLastTemperature : REAL;
END_VAR

PERSISTENT(持久化变量):PLC 断电后值保留在非易失存储器。

VAR PERSISTENT
sSerialNumber : STRING[20]; // 持久化,不掉电丢失
diTotalHours : DINT;
END_VAR

NON_RETAIN(显式非保持):即使控制器默认开启 RETAIN,也强制不保持。

VAR RETAIN
diCounter : DINT; // 保持
END_VAR
VAR NON_RETAIN
diTempValue : DINT; // 不保持
END_VAR

3.4 AT 声明(地址分配)

将变量映射到绝对地址(用于硬件 IO 或共享内存):

VAR
xInput AT %I0.0 : BOOL; // 数字量输入 0.0
xOutput AT %Q0.0 : BOOL; // 数字量输出 0.0
wConfig AT %MW100 : WORD; // 保持寄存器 MW100
diCounter AT %MD200 : DINT; // 双字 MD200
aiBuffer AT %MB100 : ARRAY[0..9] OF BYTE; // 字节数组
END_VAR

地址格式:

前缀含义示例
%I输入 (Input)%I1.0%IW64
%Q输出 (Output)%Q0.0%QW32
%M内存 (Memory)%M0.0%MW100
%DB数据块 (Data Block)%DB1.DBW0

3.5 默认值

所有变量声明时可以不赋值,此时使用类型的默认值

类型默认值
BOOLFALSE
整数类型0
浮点类型0.0
字符串''(空字符串)
时间/日期T#0s
数组全部元素为默认值
结构体全部成员为默认值

显式指定默认值:

VAR
iCounter : INT := 100;
rGain : REAL := 1.0;
bEnable : BOOL := TRUE;
sName : STRING[20] := 'Default';
END_VAR

4. 运算符

4.1 算术运算符

运算符说明示例
+加法rSum := a + b;
-减法rDiff := a - b;
*乘法rProd := a * b;
/除法rQuot := a / b;
MOD取模(余数)iRem := 10 MOD 3; // 结果为 1
**幂运算rPow := 2.0 ** 3.0; // 结果为 8.0

示例:

iSum := 10 + 20;           // 30
iDiff := 50 - 23; // 27
iProd := 6 * 7; // 42
rQuot := 100.0 / 3.0; // 33.3333
iRem := 17 MOD 5; // 2(余数)

// 注意:整数除法会截断
iResult := 10 / 3; // 结果为 3,不是 3.333
rResult := 10.0 / 3.0; // 结果为 3.3333

常见错误:整数除法会截断!10 / 3 结果是 3,要得到浮点结果需先转换成 REAL

4.2 比较运算符

运算符说明示例
=等于IF a = b THEN
<>不等于IF a <> b THEN
<小于IF a < b THEN
>大于IF a > b THEN
<=小于等于IF a <= b THEN
>=大于等于IF a >= b THEN

示例:

IF iCount = 100 THEN
xDone := TRUE;
END_IF;

IF rTemp > 85.0 THEN
xAlarm := TRUE;
END_IF;

4.3 逻辑运算符

运算符别名说明
AND&逻辑与(两个都为 TRUE 才为 TRUE)
OR逻辑或(一个为 TRUE 即为 TRUE)
XOR逻辑异或(不同时 TRUE)
NOT逻辑非(取反)

示例:

// 与
xOut := xA AND xB; // A 和 B 都为 TRUE 时输出 TRUE
xOut := xA & xB; // 同上,& 是 AND 的别名

// 或
xOut := xA OR xB; // A 或 B 任一为 TRUE 输出 TRUE

// 异或
xOut := xA XOR xB; // A 和 B 不同时输出 TRUE

// 非
xOut := NOT xA; // 输出 A 的反相

// 组合
xOut := (xA AND xB) OR (NOT xC AND xD);

4.4 位运算符

运算符说明示例
AND按位与wResult := wA AND wB;
OR按位或wResult := wA OR wB;
XOR按位异或wResult := wA XOR wB;
NOT按位取反wResult := NOT wA;
SHL左移wResult := wValue SHL 3;
SHR右移wResult := wValue SHR 2;
ROL循环左移wResult := wValue ROL 4;
ROR循环右移wResult := wValue ROR 4;

示例:

wA := 16#00FF;
wB := 16#0F0F;

wAnd := wA AND wB; // 16#000F
wOr := wA OR wB; // 16#0FFF
wXor := wA XOR wB; // 16#0FF0
wNot := NOT wA; // 16#FF00

wShifted := wA SHL 4; // 16#0FF0(左移 4 位)
wRotated := wA ROL 4; // 16#0FF0(循环左移 4 位)

4.5 赋值运算符

运算符说明示例
:=普通赋值xOut := xIn;
S=置位(Set)xOut S= xIn;(xIn 触发时 xOut 置 TRUE 并保持)
R=复位(Reset)xOut R= xIn;(xIn 触发时 xOut 置 FALSE 并保持)

S= 和 R= 的典型用法:

// S= 置位:当 bSet 为 TRUE 时,xFlag 置为 TRUE 并保持
xFlag S= bSet;

// R= 复位:当 bReset 为 TRUE 时,xFlag 置为 FALSE 并保持
xFlag R= bReset;

// 等价于:
IF bSet THEN
xFlag := TRUE;
END_IF;
IF bReset THEN
xFlag := FALSE;
END_IF;

4.6 运算符优先级

从高到低:

优先级运算符说明
1(最高)()括号
2NOT逻辑非
3**幂运算
4*/MOD乘、除、取模
5+-加、减
6<><=>=比较
7=<>等于、不等于
8AND&逻辑与
9XOR逻辑异或
10(最低)OR逻辑或

示例:

// 不带括号:
rResult := 2 + 3 * 4; // 14(乘法优先)
xOut := xA OR xB AND xC; // xA OR (xB AND xC)(AND 优先于 OR)

// 推荐加括号,清晰:
rResult := 2 + (3 * 4);
xOut := xA OR (xB AND xC);

5. 控制流语句

5.1 IF 语句

语法:

IF 条件1 THEN
// 条件1 为 TRUE 时执行
ELSIF 条件2 THEN
// 条件1 为 FALSE 且条件2 为 TRUE 时执行
ELSE
// 所有条件都不满足时执行
END_IF;

示例:

IF rTemp > 100.0 THEN
xAlarm := TRUE;
xAutoMode := FALSE;
ELSIF rTemp > 80.0 THEN
xWarning := TRUE;
ELSE
xNormal := TRUE;
END_IF;

// 嵌套 IF
IF bEnable THEN
IF iCounter >= 100 THEN
xDone := TRUE;
END_IF;
END_IF;

5.2 CASE 语句

语法:

CASE 表达式 OF
1:
// 当表达式 = 值1 时执行
2,3:
// 当表达式 = 值2 或 值3 时执行
4..10:
// 当表达式在 4 到 10 之间时执行
ELSE
// 其他情况
END_CASE;

示例:

CASE iMode OF
0:
xIdle := TRUE;
1:
xRun := TRUE;
rSpeed := 1000.0;
2, 3:
xPause := TRUE;
rSpeed := 0.0;
4..10:
xError := TRUE;
iErrorCode := iMode;
ELSE
xError := TRUE;
iErrorCode := 99;
END_CASE;

提示:CASE 比长串 IF-ELSIF 更清晰,尤其适合状态机

5.3 FOR 循环

语法:

FOR 循环变量 := 起始值 TO 终止值 BY 步长 DO
// 循环体
END_FOR;

示例:

// 从 0 到 9 循环
FOR i := 0 TO 9 DO
aiArray[i] := i * 10;
END_FOR;

// 从 1 到 100,步长 2(奇数值)
FOR i := 1 TO 100 BY 2 DO
aiOdd[i] := i;
END_FOR;

// 递减
FOR i := 10 DOWNTO 0 DO
aiReverse[i] := i;
END_FOR;

// 数组求和
iSum := 0;
FOR i := 0 TO 99 DO
iSum := iSum + aiValues[i];
END_FOR;

5.4 WHILE 循环

语法:

WHILE 条件 DO
// 循环体(条件为 TRUE 时反复执行)
END_WHILE;

示例:

i := 0;
WHILE aiValues[i] <> 0 AND i < 100 DO
i := i + 1;
END_WHILE;
// 找到第一个值为 0 的元素

// 注意:确保条件最终会变为 FALSE,否则死循环!

5.5 REPEAT 循环

语法:

REPEAT
// 循环体(至少执行一次)
UNTIL 条件
END_REPEAT;

示例:

i := 0;
REPEAT
i := i + 1;
aiValues[i] := i * 2;
UNTIL i >= 10
END_REPEAT;
// 至少执行一次,条件为 TRUE 时退出

WHILE vs REPEATWHILE 先判断后执行(可能一次都不执行),REPEAT 先执行后判断(至少执行一次)。

5.6 EXIT、CONTINUE、RETURN

EXIT — 立即退出循环:

FOR i := 0 TO 99 DO
IF aiValues[i] = 0 THEN
EXIT; // 找到目标值,立即退出循环
END_IF;
iCount := iCount + 1;
END_FOR;

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

FOR i := 0 TO 99 DO
IF aiValues[i] < 0 THEN
CONTINUE; // 跳过负数,不处理
END_IF;
iSum := iSum + aiValues[i];
END_FOR;

RETURN — 立即退出当前 POU:

FUNCTION SafeDivide : REAL
VAR_INPUT
a : REAL;
b : REAL;
END_VAR
IF b = 0.0 THEN
SafeDivide := 0.0;
RETURN; // 除数为 0,直接返回
END_IF;
SafeDivide := a / b;
END_FUNCTION

5.7 循环嵌套与作用域

FOR i := 0 TO 9 DO
FOR j := 0 TO 9 DO
arMatrix[i, j] := INT_TO_REAL(i * 10 + j);
END_FOR;
END_FOR;

注意:循环变量不能嵌套声明同名变量。不要在循环内修改循环变量的值(除非很确定你在做什么)。


6. 函数 (FUNCTION)

6.1 函数声明

语法:

FUNCTION 函数名 : 返回数据类型
VAR_INPUT
// 输入参数(只读)
END_VAR
VAR_OUTPUT
// 输出参数(只写)
END_VAR
VAR_IN_OUT
// 输入输出参数(引用传递,可读写)
END_VAR
VAR
// 临时变量(每次调用重新分配)
END_VAR
// 函数体
函数名 := 返回值; // 必须给函数名赋值!
END_FUNCTION

6.2 最简单的函数

FUNCTION Add : INT
VAR_INPUT
a : INT;
b : INT;
END_VAR
Add := a + b;
END_FUNCTION

调用:

iResult := Add(10, 20);       // 位置参数
iResult := Add(a := 10, b := 20); // 命名参数(推荐)

6.3 多参数函数

FUNCTION CalculateMotorTorque : REAL
VAR_INPUT
rCurrent : REAL; // 电流 (A)
rFlux : REAL; // 磁通 (Wb)
rResistance : REAL; // 电阻 (Ohm)
END_VAR
VAR
rTemp : REAL;
END_VAR
rTemp := rCurrent * rFlux;
CalculateMotorTorque := rTemp - (rCurrent * rCurrent * rResistance);
END_FUNCTION

调用:

rTorque := CalculateMotorTorque(
rCurrent := 5.0,
rFlux := 0.8,
rResistance := 0.5
);

6.4 多输出函数

SCL 函数只有一个返回值,但可以通过 VAR_OUTPUT 返回多个值:

FUNCTION MinMax : VOID          // 返回类型为 VOID(无返回值)
VAR_INPUT
value1 : REAL;
value2 : REAL;
END_VAR
VAR_OUTPUT
minVal : REAL;
maxVal : REAL;
END_VAR
IF value1 < value2 THEN
minVal := value1;
maxVal := value2;
ELSE
minVal := value2;
maxVal := value1;
END_IF;
END_FUNCTION

调用:

MinMax(
value1 := 10.5,
value2 := 20.3,
minVal => rMin, // => 箭头表示输出参数映射
maxVal => rMax
);

6.5 VAR_IN_OUT 参数

VAR_IN_OUT 参数按引用传递,函数内部修改会影响外部变量:

FUNCTION Swap : VOID
VAR_IN_OUT
a : INT;
b : INT;
END_VAR
VAR
temp : INT;
END_VAR
temp := a;
a := b;
b := temp;
END_FUNCTION

调用:

Swap(a := iX, b := iY);   // iX 和 iY 的值会被交换

6.6 函数调用语法

位置参数(按顺序):

rResult := MyFunc(1.0, 2.0, 3.0);

命名参数(推荐):

rResult := MyFunc(a := 1.0, b := 2.0, c := 3.0);

链式调用:

rResult := Func1(Func2(a := 10, b := 20), c := 30);

6.7 递归

SCL 支持递归(函数调用自身),但需要限制递归深度

FUNCTION Factorial : DINT
VAR_INPUT
n : DINT;
END_VAR
IF n <= 1 THEN
Factorial := 1;
ELSE
Factorial := n * Factorial(n - 1); // 递归调用
END_IF;
END_FUNCTION

警告:PLC 运行时的堆栈深度有限(通常 256 层),递归太深会导致堆栈溢出。工业控制中不推荐深度递归,建议用循环替代。


7. 功能块 (FUNCTION_BLOCK)

7.1 功能块声明

功能块(FB)与函数(FC)最大的区别:FB 有内部状态,保持上一次调用的值

语法:

FUNCTION_BLOCK 功能块名
VAR_INPUT
// 输入参数
END_VAR
VAR_OUTPUT
// 输出参数
END_VAR
VAR_IN_OUT
// 输入输出参数
END_VAR
VAR
// 内部变量(保持状态)
END_VAR
// 功能块体
END_FUNCTION_BLOCK

7.2 最简单的 FB — 计数器

FUNCTION_BLOCK Counter
VAR_INPUT
bCount : BOOL; // 计数触发
bReset : BOOL; // 复位
END_VAR
VAR_OUTPUT
iValue : INT; // 当前计数值
END_VAR
IF bReset THEN
iValue := 0;
ELSIF bCount THEN
iValue := iValue + 1; // 状态保持,每次调用累加
END_IF;
END_FUNCTION_BLOCK

7.3 FB 实例化

FB 必须先声明实例才能使用:

PROGRAM Main
VAR
fbCounter1 : Counter; // 声明 FB 实例
fbCounter2 : Counter;
xTrig : BOOL;
iVal1 : INT;
iVal2 : INT;
END_VAR

// 调用(调用方法已在 FB 中定义,这里直接写输入输出)
fbCounter1(bCount := xTrig);
iVal1 := fbCounter1.iValue; // 读取输出

fbCounter2(bCount := xTrig, bReset := TRUE);
iVal2 := fbCounter2.iValue;
END_PROGRAM

7.4 带定时器的 FB

FUNCTION_BLOCK PulseGenerator
VAR_INPUT
bEnable : BOOL;
tOnTime : TIME := T#500ms; // 导通时间
tOffTime : TIME := T#500ms; // 断开时间
END_VAR
VAR_OUTPUT
xPulse : BOOL; // 脉冲输出
END_VAR
VAR
fbOnTimer : TON; // 导通定时器
fbOffTimer : TON; // 断开定时器
bOnPhase : BOOL; // 当前相位
END_VAR

IF NOT bEnable THEN
xPulse := FALSE;
bOnPhase := TRUE;
fbOnTimer(IN := FALSE);
fbOffTimer(IN := FALSE);
RETURN;
END_IF;

IF bOnPhase THEN
fbOnTimer(IN := TRUE, PT := tOnTime);
xPulse := TRUE;
IF fbOnTimer.Q THEN
bOnPhase := FALSE;
fbOnTimer(IN := FALSE);
END_IF;
ELSE
fbOffTimer(IN := TRUE, PT := tOffTime);
xPulse := FALSE;
IF fbOffTimer.Q THEN
bOnPhase := TRUE;
fbOffTimer(IN := FALSE);
END_IF;
END_IF;
END_FUNCTION_BLOCK

7.5 FB 的 INIT 和 EXIT 方法

FB 在第一次调用时执行 __INIT,最后一次调用后执行 __EXIT

FUNCTION_BLOCK MyDevice
VAR
iInitCount : INT;
END_VAR

// 主逻辑
iInitCount := iInitCount + 1;
END_FUNCTION_BLOCK

// 附加 INIT 方法(在第一次调用前自动执行)
METHOD __INIT
iInitCount := 0;
// 初始化硬件、分配资源等
END_METHOD

// 附加 EXIT 方法(在 FB 销毁时自动执行)
METHOD __EXIT
// 释放资源、关闭硬件等
END_METHOD

7.6 THIS 引用

在 FB 方法内部,THIS 指向当前 FB 实例:

FUNCTION_BLOCK Motor
VAR
rSpeed : REAL;
END_VAR

// 方法中访问自身
METHOD SetSpeed
VAR_INPUT
rNewSpeed : REAL;
END_VAR
THIS.rSpeed := rNewSpeed; // THIS 指向当前 FB 实例
END_METHOD

7.7 FB 继承 (EXTENDS)

FUNCTION_BLOCK BaseMotor
VAR
rSpeed : REAL;
bRun : BOOL;
END_VAR
// 基础电机逻辑
END_FUNCTION_BLOCK

// 继承 BaseMotor
FUNCTION_BLOCK AdvancedMotor EXTENDS BaseMotor
VAR
rAcceleration : REAL;
rDeceleration : REAL;
END_VAR
// 继承来自 BaseMotor 的 rSpeed 和 bRun
// 可以增加新的功能
IF bRun THEN
rSpeed := rSpeed + rAcceleration * 0.1;
END_IF;
END_FUNCTION_BLOCK

7.8 接口实现 (IMPLEMENTS)

INTERFACE IMotionControl
METHOD MoveTo : BOOL
VAR_INPUT
diTargetPos : DINT;
rSpeed : REAL;
END_END_METHOD
END_INTERFACE

// 实现接口
FUNCTION_BLOCK ServoMotor IMPLEMENTS IMotionControl
VAR
diPosition : DINT;
END_VAR

// 必须实现 IMotionControl 的所有方法
METHOD MoveTo : BOOL
VAR_INPUT
diTargetPos : DINT;
rSpeed : REAL;
END_VAR
diPosition := diTargetPos;
MoveTo := TRUE;
END_METHOD
END_FUNCTION_BLOCK

8. 程序 (PROGRAM)

8.1 PROGRAM 声明

PROGRAM 是 PLC 的顶层入口,每个程序可以分配给一个任务(OB)按周期执行。

PROGRAM Main
VAR
iCounter : INT;
xOutput AT %Q0.0 : BOOL;
END_VAR
iCounter := iCounter + 1;

IF iCounter >= 1000 THEN
xOutput := TRUE;
END_IF;
END_PROGRAM

8.2 任务分配

在 IDE 中配置 PROGRAM 的执行周期:

// 快速任务(1ms 周期)
PROGRAM FastTask
// 高速 IO 处理、EtherCAT 通讯
END_PROGRAM

// 主任务(10ms 周期)
PROGRAM MainTask
// 控制逻辑、运动规划
END_PROGRAM

// 慢速任务(100ms 周期)
PROGRAM SlowTask
// 数据记录、HMI 通讯
END_PROGRAM

8.3 多程序协作

PROGRAM Main
VAR
diCycleCount : DINT;
END_VAR
diCycleCount := diCycleCount + 1;
// 调用子程序
ProcessInputs();
ExecuteControl();
UpdateOutputs();
END_PROGRAM

FUNCTION ProcessInputs : VOID
// 读取输入
END_FUNCTION

FUNCTION ExecuteControl : VOID
// 控制逻辑
END_FUNCTION

FUNCTION UpdateOutputs : VOID
// 更新输出
END_FUNCTION

9. 标准函数库

9.1 数学函数

函数说明示例
ABS(x)绝对值ABS(-10) → 10
SQRT(x)平方根SQRT(16.0) → 4.0
LN(x)自然对数LN(2.71828) → 1.0
LOG(x)常用对数(以 10 为底)LOG(100.0) → 2.0
EXP(x)指数函数EXP(1.0) → 2.71828
SIN(x)正弦(弧度)SIN(0.0) → 0.0
COS(x)余弦(弧度)COS(0.0) → 1.0
TAN(x)正切(弧度)TAN(0.7854) → 1.0
ASIN(x)反正弦ASIN(1.0) → 1.5708
ACOS(x)反余弦ACOS(0.0) → 1.5708
ATAN(x)反正切ATAN(1.0) → 0.7854
EXPT(x, y)幂函数EXPT(2.0, 3.0) → 8.0

示例:

// 三角函数(注意:弧度制)
rRad := 45.0 * 3.14159 / 180.0; // 角度转弧度
rSin := SIN(rRad); // sin(45°) ≈ 0.7071
rCos := COS(rRad); // cos(45°) ≈ 0.7071

// 反三角函数
rAngle := ASIN(0.7071); // 0.7854 rad ≈ 45°
rDeg := rAngle * 180.0 / 3.14159; // 弧度转角度

// 平方根
rHypot := SQRT(a*a + b*b); // 勾股定理

9.2 统计函数

函数说明示例
MIN(x, y)取最小值MIN(10, 20) → 10
MAX(x, y)取最大值MAX(10, 20) → 20
SUM(arr)数组求和
MEAN(arr)求平均值
STDDEV(arr)求标准差

示例:

rLow  := MIN(25.0, 18.5);       // 18.5
rHigh := MAX(25.0, 18.5); // 25.0

// 限幅
rSpeed := MAX(0.0, MIN(rSpeed, 1500.0)); // 限制在 0-1500 之间

9.3 选择函数

函数说明示例
SEL(g, a, b)二选一:g=0 选 a,g=1 选 bSEL(xIn, v0, v1)
MUX(k, v0, v1, v2, ...)多路选择MUX(iSel, a, b, c, d)
LIMIT(lo, x, hi)限幅:lo ≤ x ≤ hiLIMIT(0, rVal, 100)

示例:

// SEL:二选一
rOut := SEL(xMode, rNormalSpeed, rFastSpeed);
// 等价于:IF xMode THEN rOut := rFastSpeed; ELSE rOut := rNormalSpeed; END_IF;

// MUX:多路选择
rOut := MUX(iChannel, rCh0, rCh1, rCh2, rCh3);
// 等价于:CASE iChannel OF 0: rOut := rCh0; 1: rOut := rCh1; ... END_CASE;

// LIMIT:限幅
rOut := LIMIT(0.0, rInput, 100.0);
// 等价于:rOut := MAX(0.0, MIN(rInput, 100.0));

9.4 类型转换函数

函数说明
BOOL_TO_INT(x)BOOL 转 INT
INT_TO_REAL(x)INT 转 REAL
REAL_TO_INT(x)REAL 转 INT(截断)
REAL_TO_DINT(x)REAL 转 DINT
DINT_TO_REAL(x)DINT 转 REAL
WORD_TO_INT(x)WORD 转 INT
INT_TO_WORD(x)INT 转 WORD
STRING_TO_INT(s)字符串转 INT
INT_TO_STRING(x)INT 转字符串
TIME_TO_DINT(t)TIME 转 DINT(毫秒)
DINT_TO_TIME(d)DINT 转 TIME

示例:

iVal := REAL_TO_INT(25.8);            // 25(截断,非四舍五入)
iVal := REAL_TO_INT(25.8 + 0.5); // 26(四舍五入技巧)
rVal := INT_TO_REAL(100); // 100.0
sText := INT_TO_STRING(12345); // '12345'
iMillis := TIME_TO_DINT(T#2.5s); // 2500

9.5 字符串函数

函数说明示例
LEN(s)字符串长度LEN('Hello') → 5
LEFT(s, n)取左边 n 个字符LEFT('Hello', 2) → 'He'
RIGHT(s, n)取右边 n 个字符RIGHT('Hello', 3) → 'llo'
MID(s, p, n)从位置 p 取 n 个字符MID('Hello', 2, 2) → 'll'
CONCAT(s1, s2)拼接字符串CONCAT('A', 'B') → 'AB'
INSERT(s1, s2, p)在位置 p 插入 s2INSERT('AB', '12', 1) → 'A12B'
DELETE(s, p, n)删除从 p 开始的 n 个字符DELETE('Hello', 2, 2) → 'Hlo'
REPLACE(s1, s2, p, n)替换从 p 开始的 n 个字符为 s2REPLACE('Hello', 'XYZ', 2, 2) → 'HXYZo'
FIND(s1, s2)查找 s2 在 s1 中的位置FIND('Hello', 'll') → 3

示例:

sFull := CONCAT('Darra', ' PLC');           // 'Darra PLC'
sLeft := LEFT('Darra PLC', 5); // 'Darra'
iPos := FIND('192.168.1.100', '.'); // 4(第一个点位置)
sIP := DELETE('192.168.1.100', 4, 1); // '192168.1.100'

9.6 日期时间函数

函数说明
ADD_TIME(t1, t2)时间相加
SUB_TIME(t1, t2)时间相减
TIME_TO_DINT(t)TIME 转 DINT(毫秒)
DINT_TO_TIME(d)DINT 转 TIME

示例:

tNew := ADD_TIME(T#10s, T#5s);          // T#15s
tDiff := SUB_TIME(T#30s, T#10s); // T#20s
iMs := TIME_TO_DINT(T#1.5s); // 1500
tTime := DINT_TO_TIME(2000); // T#2s

9.7 位操作函数

函数说明
SET_BIT(x, n)将第 n 位置 1
GET_BIT(x, n)读取第 n 位的值
TOGGLE_BIT(x, n)翻转第 n 位

示例:

wData := 16#0000;
wData := SET_BIT(wData, 3); // 16#0008(第 3 位置 1)
bBit := GET_BIT(wData, 3); // TRUE
wData := TOGGLE_BIT(wData, 3); // 16#0000(第 3 位翻转回 0)

10. 标准系统函数

10.1 系统时间函数

函数说明返回类型
SYS_TIME()获取系统时间(毫秒)UDINT
SYS_DATE()获取当前日期DT
SYS_TICK()获取系统滴答计数(1μs 精度)ULINT

示例:

// 运行时间
diStartTime := SYS_TIME();
// ... 执行某些操作 ...
diElapsed := SYS_TIME() - diStartTime; // 耗时(毫秒)

// 高精度计时
liStartTick := SYS_TICK();
// ... 执行操作 ...
liElapsedTick := SYS_TICK() - liStartTick; // 耗时(微秒)

10.2 系统控制函数

函数说明使用注意
SYS_RESET()软件复位 PLC会重启运行时,谨慎使用
SYS_DIAG(id)获取诊断信息返回诊断字符串

示例:

// 紧急情况复位
IF bEmergencyReset THEN
SYS_RESET();
END_IF;

// 获取诊断信息
sInfo := SYS_DIAG(1); // 系统版本
sInfo := SYS_DIAG(2); // 运行时间
sInfo := SYS_DIAG(3); // 内存使用

11. 数组与字符串操作

11.1 数组初始化

VAR
// 全部元素相同值
aiOnes : ARRAY[0..4] OF INT := [5(1)];

// 逐个列举
aiValues : ARRAY[0..4] OF INT := [10, 20, 30, 40, 50];

// 混合初始值
aiMixed : ARRAY[0..4] OF INT := [10, 3(20), 30];
// 结果:[10, 20, 20, 20, 30]

// 二维数组
arMatrix : ARRAY[0..1, 0..2] OF INT := [1, 2, 3, 4, 5, 6];
// 相当于:
// [0,0]=1, [0,1]=2, [0,2]=3
// [1,0]=4, [1,1]=5, [1,2]=6
END_VAR

11.2 数组切片

Darra SCL 支持数组切片操作:

// 切片:从原数组取一段
aiSlice := aiSource[2..5]; // 取索引 2 到 5 的四个元素

// 切片赋值
aiDest[0..3] := aiSource[10..13];

11.3 数组比较

// 数组不能直接用 = 比较,必须逐元素比较
FUNCTION ArrayEqual : BOOL
VAR_INPUT
arr1 : ARRAY[0..9] OF INT;
arr2 : ARRAY[0..9] OF INT;
END_VAR
VAR
i : INT;
END_VAR
ArrayEqual := TRUE;
FOR i := 0 TO 9 DO
IF arr1[i] <> arr2[i] THEN
ArrayEqual := FALSE;
RETURN;
END_IF;
END_FOR;
END_FUNCTION

11.4 字符串操作技巧

字符串拼接:

sFull := CONCAT('Temp: ', CONCAT(INT_TO_STRING(iTemp), '°C'));
// 或使用 + 运算符(如果支持)
sFull := 'Temp: ' + INT_TO_STRING(iTemp) + '°C';

格式化字符串:

sMsg := CONCAT('轴 ', INT_TO_STRING(iAxis), 
' 速度 ', INT_TO_STRING(iSpeed),
' RPM');

字符串遍历:

FOR i := 1 TO LEN(sText) DO
cChar := MID(sText, i, 1); // 取第 i 个字符
// 处理字符
END_FOR;

12. 指针与引用

12.1 REF_TO(引用)

语法:

VAR
refInt : REF_TO INT; // 指向 INT 的引用
refMotor : REF_TO MotorData; // 指向结构体的引用
END_VAR

赋值:

VAR
iValue : INT;
refValue : REF_TO INT;
END_VAR
refValue := REF(iValue); // REF 运算符取引用
// 现在 refValue 指向 iValue
END_VAR

解引用:

// 使用 ^ 运算符解引用
iValue := refValue^; // 读取 refValue 指向的值
refValue^ := 100; // 写入 refValue 指向的值

NULL 检查:

IF refValue <> NULL THEN
iValue := refValue^;
ELSE
// 引用为空,做错误处理
END_IF;

12.2 引用作为参数

FUNCTION ProcessData : VOID
VAR_IN_OUT
refData : REF_TO INT; // 引用类型的 IN_OUT 参数
END_VAR
IF refData <> NULL THEN
refData^ := refData^ * 2;
END_IF;
END_FUNCTION

12.3 REF vs POINTER

Darra SCL 支持 REF_TO(引用,推荐)和 POINTER(指针,兼容旧代码):

特性REF_TOPOINTER
语法REF_TOPOINTER TO
取地址REF(var)ADR(var)
解引用ref^ptr^
NULL 检查ref <> NULLptr <> NULL
安全性推荐兼容旧代码

POINTER 示例:

VAR
pValue : POINTER TO INT;
iValue : INT;
END_VAR
pValue := ADR(iValue); // ADR 取地址
pValue^ := 100; // 解引用赋值
END_VAR

13. 面向对象编程

13.1 方法 (METHOD)

语法:

FUNCTION_BLOCK MyClass
VAR
iValue : INT;
END_VAR

// 主逻辑
iValue := iValue + 1;
END_FUNCTION_BLOCK

// 附加方法
METHOD GetValue : INT
GetValue := THIS.iValue;
END_METHOD

METHOD SetValue : VOID
VAR_INPUT
iNewVal : INT;
END_VAR
THIS.iValue := iNewVal;
END_METHOD

调用:

fbMyClass.SetValue(100);
iVal := fbMyClass.GetValue();

13.2 属性 (PROPERTY)

语法:

FUNCTION_BLOCK Motor
VAR
rSpeed : REAL;
rMaxSpeed : REAL := 1500.0;
END_VAR

// 属性定义
PROPERTY Speed : REAL
GET
Speed := THIS.rSpeed;
END_GET
SET
// 带限幅的设置
IF rNewValue <= THIS.rMaxSpeed THEN
THIS.rSpeed := rNewValue;
ELSE
THIS.rSpeed := THIS.rMaxSpeed;
END_IF;
END_SET
END_PROPERTY

PROPERTY IsRunning : BOOL
GET
IsRunning := THIS.rSpeed > 0.0;
END_GET
// 只读属性:没有 SET 块
END_PROPERTY

调用:

fbMotor.Speed := 1000.0;          // SET 调用
rCurrentSpeed := fbMotor.Speed; // GET 调用
bRun := fbMotor.IsRunning; // 只读属性

13.3 接口 (INTERFACE)

定义接口:

INTERFACE ISwitchable
METHOD TurnOn : VOID
METHOD TurnOff : VOID
METHOD IsOn : BOOL
END_INTERFACE

实现接口:

FUNCTION_BLOCK Lamp IMPLEMENTS ISwitchable
VAR
bOn : BOOL;
END_VAR

METHOD TurnOn : VOID
THIS.bOn := TRUE;
END_METHOD

METHOD TurnOff : VOID
THIS.bOn := FALSE;
END_METHOD

METHOD IsOn : BOOL
IsOn := THIS.bOn;
END_METHOD
END_FUNCTION_BLOCK

多态使用:

FUNCTION_BLOCK Motor IMPLEMENTS ISwitchable
VAR
bRunning : BOOL;
END_VAR

METHOD TurnOn : VOID
THIS.bRunning := TRUE;
END_METHOD

METHOD TurnOff : VOID
THIS.bRunning := FALSE;
END_METHOD

METHOD IsOn : BOOL
IsOn := THIS.bRunning;
END_METHOD
END_FUNCTION_BLOCK

13.4 继承 (EXTENDS)

FUNCTION_BLOCK Animal
VAR
sName : STRING[20];
END_VAR
// 基类逻辑
END_FUNCTION_BLOCK

FUNCTION_BLOCK Dog EXTENDS Animal
VAR
iLegs : INT := 4;
END_VAR
// 继承自 Animal 的 sName
// 子类扩展
END_FUNCTION_BLOCK

14. 预处理器与编译指令

14.1 条件编译

{IF DEFINED(DEBUG)}
// 调试模式下的代码
SYS_DIAG('Debug: counter = ', iCounter);
{ELSE}
// 发布模式下的代码
{END_IF}

14.2 区域折叠

{REGION '初始化'}
// 初始化代码
iCounter := 0;
bReady := FALSE;
{END_REGION}

{REGION '主循环'}
// 主控制逻辑
iCounter := iCounter + 1;
{END_REGION}

14.3 编译提示与错误

{WARNING '此函数尚未完整实现,请检查'}
// 编译时输出警告

{ERROR '内存不足,请减少数组大小'}
// 编译时输出错误,阻止编译

14.4 页面分隔

// 在打印时插入分页
{PAGE}

// 设置打印标题
{TITLE '电机控制模块'}

附录:常见错误速查表

错误原因改正
忘记 END_IFIF 块不完整每一层 IF 都有对应的 END_IF
忘记 END_FORFOR 循环没闭合在循环体后加 END_FOR
数组越界访问 ARRAY[0..9][10]检查边界条件
类型不匹配INT := REAL 没有显式转换使用 REAL_TO_INT()
整数除法截断10 / 3 得到 3转成 REAL 再除
忘记给函数名赋值函数没有返回值函数体内写 函数名 := 值;
死循环WHILE TRUE DO 无退出条件确保有退出条件或 EXIT
递归太深堆栈溢出用循环替代递归
分号漏了语句结束没写 ;每条语句末尾加 ;

本教程由 Darra 文档团队维护,持续更新。 最后更新:2026-07-26