跳到主要内容

SCL 编程语言

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

截图: SCL 编辑器全景

数据类型

基本数据类型

类型大小范围示例
BOOL1 位TRUE / FALSEbReady := TRUE;
BYTE8 位0 ~ 255byStatus := 16#FF;
SINT8 位-128 ~ 127siTemp := -10;
INT16 位-32768 ~ 32767iCount := 100;
DINT32 位-2^31 ~ 2^31-1diPosition := 123456;
LINT64 位-2^63 ~ 2^63-1liTimestamp := 0;
UINT16 位0 ~ 65535uiSpeed := 3000;
UDINT32 位0 ~ 2^32-1udiEncoder := 0;
REAL32 位IEEE 754 单精度rTemp := 25.5;
LREAL64 位IEEE 754 双精度lrAngle := 3.14159265;
STRING可变最长 255 字符sName := 'Darra';
TIME32 位毫秒精度tDelay := T#500ms;
DATE32 位日期dToday := D#2026-04-07;
TOD32 位时间todNow := TOD#14:30:00;
DT64 位日期时间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()括号
2NOT逻辑非
3**幂运算
4*, /, MOD乘、除、取模
5+, -加、减
6<, >, <=, >=比较
7=, <>等于、不等于
8AND, &逻辑与
9XOR逻辑异或
10OR逻辑或

控制流

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
整数除法结果为 0iResult := 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 更清晰

截图: SCL 代码补全与参数提示

相关文档