数据类型
SCL 是强类型语言,每个变量和常量在使用前必须声明其数据类型。数据类型决定了变量占用内存的大小、取值范围以及可执行的运算。
Darra SCL 全面支持 IEC 61131-3 Third Edition 标准定义的数据类型体系,包括基本类型、复合类型和用户自定义类型。
基本数据类型
基本数据类型是 SCL 语言内置的最小数据单元,按用途分为位类型、整数类型、浮点类型和时间/日期类型。
位与逻辑类型
| 类型 | 位宽 | 范围 | IEC 标准 | 默认值 |
|---|---|---|---|---|
BOOL | 1 bit | TRUE / FALSE | IEC 61131-3 | FALSE |
BYTE | 8 bit | 16#00 ~ 16#FF | IEC 61131-3 | 16#00 |
WORD | 16 bit | 16#0000 ~ 16#FFFF | IEC 61131-3 | 16#0000 |
DWORD | 32 bit | 16#00000000 ~ 16#FFFFFFFF | IEC 61131-3 | 16#00000000 |
LWORD | 64 bit | 16#0000000000000000 ~ 16#FFFFFFFFFFFFFFFF | IEC 61131-3 Ed3 | 16#0000000000000000 |
BOOL 是 SCL 中最基础的类型,用于逻辑运算和位控制。BYTE / WORD / DWORD / LWORD 常用来存放位串数据或与硬件 IO 模块直接交互。
VAR
bStart : BOOL := FALSE;
bStop : BOOL;
xInputByte : BYTE;
wStatus : WORD := 16#8000;
dwConfig : DWORD := 16#00FF00FF;
lwCounter : LWORD;
END_VAR
有符号整数
| 类型 | 位宽 | 范围 | IEC 标准 | 默认值 |
|---|---|---|---|---|
SINT | 8 bit | -128 ~ 127 | IEC 61131-3 Ed3 | 0 |
INT | 16 bit | -32,768 ~ 32,767 | IEC 61131-3 | 0 |
DINT | 32 bit | -2,147,483,648 ~ 2,147,483,647 | IEC 61131-3 | 0 |
LINT | 64 bit | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | IEC 61131-3 Ed3 | 0 |
无符号整数
| 类型 | 位宽 | 范围 | IEC 标准 | 默认值 |
|---|---|---|---|---|
USINT | 8 bit | 0 ~ 255 | IEC 61131-3 Ed3 | 0 |
UINT | 16 bit | 0 ~ 65,535 | IEC 61131-3 | 0 |
UDINT | 32 bit | 0 ~ 4,294,967,295 | IEC 61131-3 | 0 |
ULINT | 64 bit | 0 ~ 18,446,744,073,709,551,615 | IEC 61131-3 Ed3 | 0 |
VAR
iLoopCount : INT := 1000;
diPosition : DINT := -50000;
liBigCounter: LINT;
uiSpeed : UINT := 500;
udiTotal : UDINT := 0;
usiIndex : USINT := 1;
END_VAR
浮点类型
| 类型 | 位宽 | 有效位数 | 范围 | IEC 标准 | 默认值 |
|---|---|---|---|---|---|
REAL | 32 bit | 6 ~ 9 位 | ±1.18E-38 ~ ±3.40E+38 | IEC 61131-3 | 0.0 |
LREAL | 64 bit | 15 ~ 18 位 | ±2.23E-308 ~ ±1.80E+308 | IEC 61131-3 | 0.0 |
浮点运算在 SCL 中需要额外注意精度问题。REAL 适用于大多数过程控制场景(PID、滤波、比例运算),LREAL 用于需要高精度的位置计算、天文角度运算或长时间积分。
VAR
rTemperature : REAL := 25.5;
rSetpoint : REAL := 100.0;
lrPosition : LREAL := 123456.789012345;
rPi : REAL := 3.14159;
END_VAR
时间与日期类型
| 类型 | 位宽 | 范围 | 说明 | 默认值 |
|---|---|---|---|---|
TIME | 32 bit | T#-24d20h31m23s647ms ~ T#24d20h31m23s647ms | 时间长度,有符号 | T#0s |
DATE | 32 bit | D#1990-01-01 ~ D#2168-12-31 | 日历日期(日精度) | D#0001-01-01 |
TOD | 32 bit | TOD#00:00:00.000 ~ TOD#23:59:59.999 | 一天内时刻(毫秒精度) | TOD#00:00:00.000 |
DT | 64 bit | 日期 + 时刻组合 | 完整时间戳 | DT#0001-01-01-00:00:00.000 |
TIME 在定时器功能块(TON / TOF / TP)中作为周期和延时参数使用。DATE 和 TOD 用于排产、日志和定时任务。
VAR
tCycleTime : TIME := T#100ms;
tTimeout : TIME := T#5s30ms;
dToday : DATE := D#2026-07-26;
tNow : TOD := TOD#14:30:00.500;
dtTimestamp : DT;
END_VAR
// TIME 字面量格式
// T#100ms — 100 毫秒
// T#2s500ms — 2 秒 500 毫秒
// T#1m30s — 1 分 30 秒
// T#1h15m20s300ms — 1 小时 15 分 20 秒 300 毫秒
// T#-5s — 负值时间(用于倒计时)
字符与字符串类型
| 类型 | 存储方式 | 最大长度 | 默认值 | 说明 |
|---|---|---|---|---|
CHAR | 单字节 | 1 | '' | ASCII 单字符 |
STRING[N] | 字节数组 | N(缺省 80) | '' | ASCII 字符串,长度可变 |
WCHAR | 双字节 | 1 | '' | Unicode 宽字符(UTF-16) |
WSTRING[N] | 双字节数组 | N(缺省 80) | '' | Unicode 字符串(UTF-16) |
STRING 是工业自动化中最常用的文本类型,用于配方名、报警文本、产品批号和通讯报文。WSTRING 适用于国际化场景(中文、日文、西里尔字母等)。
VAR
cLetter : CHAR := 'A';
sProductName : STRING[32] := 'Conveyor-01';
sMessage : STRING := '默认长度 80'; // 不指定长度则默认 80
wsChinese : WSTRING[16] := "达拉控制";
wcSymbol : WCHAR := "Ω";
END_VAR
STRING 内部存储格式
STRING[N] 实际占用 N + 1 个字节:第一个字节存放当前有效长度(0 ~ N),后续 N 个字节存放字符数据。WSTRING[N] 类似,占用 (N + 1) * 2 个字节。
// STRING[10] "Hello" 的内存布局
// [0] = 5 (当前长度)
// [1] = 'H' [2] = 'e'
// [3] = 'l' [4] = 'l'
// [5] = 'o' [6] ~ [10] = 未定义
数组类型
声明语法
数组使用 ARRAY[ 下限..上限 ] OF 类型 声明,下标必须是整数常量表达式:
VAR
// 一维数组
aiValues : ARRAY[0..9] OF INT;
arData : ARRAY[1..100] OF REAL;
// 二维数组
aMatrix : ARRAY[0..3, 0..5] OF BOOL;
aGrid : ARRAY[1..10, 1..10] OF DINT;
// 三维数组
aCube : ARRAY[0..1, 0..2, 0..3] OF BYTE;
// 数组元素可以是 STRUCT
aMotors : ARRAY[1..6] OF AxisData;
END_VAR
下标范围
IEC 61131-3 允许下标从任意整数开始,不强制从 0 或 1。但建议遵循以下约定:
| 行业 | 常用下标起点 | 原因 |
|---|---|---|
| 过程控制 | 1 | 符合"第一个元件"的工程习惯 |
| 运动控制 | 0 或 1 | 取决于轴编号规则 |
| 通用 | 0 | 与 C 语言数组下标一致 |
数组初始化
VAR
// 全初始化
aiAllZero : ARRAY[0..4] OF INT := [0, 0, 0, 0, 0];
// 部分初始化(未指定元素保持默认值)
aiPartial : ARRAY[0..4] OF INT := [100, 200];
// 指定下标初始化(IEC 61131-3 Ed3)
aiExplicit : ARRAY[0..4] OF INT := [0 := 10, 2 := 30, 4 := 50];
// 二维数组初始化
aMatrixInit : ARRAY[0..1, 0..2] OF INT := [
[1, 2, 3],
[4, 5, 6]
];
END_VAR
数组访问
// 一维
aiValues[0] := 42;
arData[iIndex + 1] := rSensorValue;
// 多维
aMatrix[2, 3] := TRUE;
aGrid[row, col] := diEncoderPos;
// 下标范围检查(运行时可配置)
// Darra PLC 默认开启数组越界检查
// 越界时触发运行时错误,停止当前块执行
结构体类型
声明
STRUCT ... END_STRUCT 用于定义复合数据结构,将多个不同类型的变量组合为一个整体:
TYPE MotorData :
STRUCT
bRunning : BOOL;
rCurrent : REAL;
rSpeed : REAL;
rTemperature : REAL;
diPosition : DINT;
sName : STRING[32];
tRunTime : TIME;
END_STRUCT;
END_TYPE
嵌套结构
结构体可以嵌套,形成层次化的数据模型:
TYPE EncoderData :
STRUCT
diCounts : DINT;
rResolution : REAL; // 脉冲/圈
bFault : BOOL;
END_STRUCT;
END_TYPE
TYPE DriveData :
STRUCT
stMotor : MotorData;
stEncoder : EncoderData;
rTorque : REAL;
bBrakeEngaged : BOOL;
tCycleTime : TIME;
END_STRUCT;
END_TYPE
变量声明与访问
VAR
myMotor : MotorData;
driveA : DriveData;
aDrives : ARRAY[1..4] OF DriveData;
END_VAR
// 结构体成员访问
myMotor.bRunning := TRUE;
myMotor.rSpeed := 1500.0;
myMotor.sName := '主轴电机';
myMotor.diPosition := diEncoderFeedback;
// 嵌套访问
driveA.stMotor.rTemperature := 65.2;
driveA.stEncoder.bFault := FALSE;
// 数组中的结构体
aDrives[1].stMotor.rCurrent := 12.5;
aDrives[2].bBrakeEngaged := TRUE;
结构体初始化
VAR
// 按成员顺序初始化
stInitMotor : MotorData := [
bRunning := FALSE,
rCurrent := 0.0,
rSpeed := 0.0,
rTemperature := 25.0,
diPosition := 0,
sName := '未命名',
tRunTime := T#0s
];
// 部分初始化(未指定字段保持默认)
stPartial : MotorData := [
rTemperature := 25.0,
sName := 'Default'
];
END_VAR
枚举类型
声明
枚举将一组有意义的命名常量组织为独立的数据类型,提高代码可读性:
TYPE AxisState :
(
AXIS_IDLE := 0,
AXIS_HOMING := 1,
AXIS_RUNNING := 2,
AXIS_ERROR := 3,
AXIS_STOPPED := 4
);
END_TYPE
TYPE MachineMode :
(
MODE_MANUAL := 1,
MODE_SEMI_AUTO := 2,
MODE_AUTO := 3
);
END_TYPE
变量声明与赋值
VAR
eAxisState : AxisState;
eMode : MachineMode;
END_VAR
// 赋值
eAxisState := AxisState.AXIS_IDLE;
eMode := MachineMode.MODE_AUTO;
// CASE 语句配合枚举
CASE eAxisState OF
AxisState.AXIS_IDLE:
bReady := TRUE;
AxisState.AXIS_HOMING:
bHomingActive := TRUE;
AxisState.AXIS_RUNNING:
bMotionEnabled := TRUE;
AxisState.AXIS_ERROR:
bFault := TRUE;
ResetAxis();
AxisState.AXIS_STOPPED:
bStopped := TRUE;
END_CASE;
类型安全
Darra SCL 的枚举是类型安全的:
- 不同枚举类型之间不能隐式比较或赋值
- 枚举值可通过显式转换读取底层整数值
- 编译器检查 CASE 语句是否覆盖全部枚举分支
// 编译错误:不同类型不能比较
// IF eAxisState = eMode THEN // ✗ 禁止
// 正确做法:显式转换
IF USINT(eAxisState) = USINT(eMode) THEN
// 需要显式类型转换
END_IF;
用户自定义类型(UDT)
TYPE ... END_TYPE 块用于定义全局可用的自定义类型,包括结构体、枚举和类型别名:
// 类型别名
TYPE MyReal : REAL; END_TYPE
TYPE CounterRange : DINT; END_TYPE
// 结构体 UDT
TYPE ConveyorData :
STRUCT
bRunning : BOOL;
rSpeed : REAL;
diDistance : DINT;
sProductID : STRING[16];
tProcessTime : TIME;
END_STRUCT;
END_TYPE
// 枚举 UDT
TYPE ProductType :
(
PRODUCT_A := 10,
PRODUCT_B := 20,
PRODUCT_C := 30
);
END_TYPE
// 数组 UDT
TYPE AxisArray : ARRAY[1..8] OF REAL; END_TYPE
UDT 一旦定义,可以在项目的所有 POU(PROGRAM、FUNCTION、FUNCTION_BLOCK)中复用:
FUNCTION_BLOCK FB_ConveyorControl
VAR_INPUT
stConfig : ConveyorData;
eProduct : ProductType;
END_VAR
VAR_OUTPUT
rSpeedCmd : REAL;
END_VAR
VAR
aTemperatures : AxisArray;
END_VAR
// 使用 UDT
rSpeedCmd := stConfig.rSpeed;
aTemperatures[1] := 45.2;
END_FUNCTION_BLOCK
类型转换
SCL 是强类型语言,不同数据类型之间的运算必须经过显式或隐式转换。
隐式转换
以下情况编译器会自动插入转换代码,无需显式写转换函数:
| 源类型 | 目标类型 | 条件 | 风险 |
|---|---|---|---|
SINT → INT / DINT / LINT | 更宽有符号 | 安全 | 无损 |
USINT → UINT / UDINT / ULINT | 更宽无符号 | 安全 | 无损 |
INT → DINT / LINT | 更宽有符号 | 安全 | 无损 |
UINT → UDINT / ULINT | 更宽无符号 | 安全 | 无损 |
USINT / UINT → INT / DINT / LINT | 同宽或更宽有符号 | 安全 | 无损 |
INT → REAL | 浮点 | 安全 | 可能精度损失(INT → REAL 无损,DINT → REAL 大值有损) |
DINT → LREAL | 64 位浮点 | 安全 | 无损 |
REAL → LREAL | 更宽浮点 | 安全 | 无损 |
BYTE → WORD / DWORD | 更宽位串 | 安全 | 高位填 0 |
WORD → DWORD | 更宽位串 | 安全 | 高位填 0 |
VAR
siSmall : SINT := 100;
iMedium : INT;
diLarge : DINT;
rFloat : REAL;
lrDouble : LREAL;
END_VAR
// 以下为隐式转换(自动执行)
iMedium := siSmall; // SINT → INT, 安全
diLarge := iMedium; // INT → DINT, 安全
rFloat := diLarge; // DINT → REAL, 大值可能精度损失
lrDouble := rFloat; // REAL → LREAL, 安全
显式转换
所有不在隐式转换表中的情况必须使用显式转换函数。转换函数命名格式为 <目标类型>_TO_<源类型>:
| 转换函数 | 源类型 | 目标类型 | 说明 |
|---|---|---|---|
INT_TO_REAL | INT / DINT / SINT | REAL | 有符号整数 → 浮点 |
DINT_TO_REAL | DINT | REAL | 32 位有符号整数 → 浮点 |
REAL_TO_INT | REAL | INT | 浮点 → 整数(截断舍入) |
REAL_TO_DINT | REAL | DINT | 浮点 → 32 位整数 |
REAL_TO_LREAL | REAL | LREAL | 单精度 → 双精度 |
LREAL_TO_REAL | LREAL | REAL | 双精度 → 单精度(可能精度损失) |
DINT_TO_TIME | DINT | TIME | 毫秒数 → 时间 |
TIME_TO_DINT | TIME | DINT | 时间 → 毫秒数 |
DINT_TO_TOD | DINT | TOD | 午夜后的毫秒数 → 时刻 |
TOD_TO_DINT | TOD | DINT | 时刻 → 午夜后的毫秒数 |
DATE_TO_DINT | DATE | DINT | 日期 → 天数偏移 |
DINT_TO_DATE | DINT | DATE | 天数偏移 → 日期 |
WORD_TO_BYTE | WORD | BYTE | 位串截断(低 8 位) |
DWORD_TO_WORD | DWORD | WORD | 位串截断(低 16 位) |
INT_TO_BOOL | INT | BOOL | 非零为 TRUE |
BOOL_TO_INT | BOOL | INT | TRUE → 1, FALSE → 0 |
VAR
rValue : REAL := 123.456;
iInteger : INT;
diBigInt : DINT;
tDuration : TIME;
dwBits : DWORD := 16#AABBCCDD;
wLowWord : WORD;
END_VAR
// 显式转换
iInteger := REAL_TO_INT(rValue); // 123(截断)
diBigInt := REAL_TO_DINT(rValue); // 123(截断)
tDuration := DINT_TO_TIME(5000); // T#5s
wLowWord := DWORD_TO_WORD(dwBits); // 16#CCDD
浮点取整方式
REAL_TO_INT / REAL_TO_DINT / LREAL_TO_DINT 等浮点→整数的转换使用**截断(truncation toward zero)**方式,即直接丢弃小数部分:
| 输入 | 转换结果 |
|---|---|
REAL_TO_INT(3.99) | 3 |
REAL_TO_INT(-3.99) | -3 |
REAL_TO_INT(0.001) | 0 |
如需四舍五入,使用标准函数 ROUND:
iRounded := REAL_TO_INT(ROUND(3.99)); // 4
iRounded := REAL_TO_INT(ROUND(-3.99)); // -4
iRounded := REAL_TO_INT(ROUND(0.001)); // 0
REF_TO 和 POINTER TO 类型
REF_TO(引用)
REF_TO 是 IEC 61131-3 Third Edition 引入的安全引用类型,提供指针能力但具有编译期类型检查和空引用保护:
TYPE
// 引用基本类型
RefInt : REF_TO INT;
// 引用结构体
RefMotor : REF_TO MotorData;
END_TYPE
FUNCTION_BLOCK FB_ReferenceExample
VAR_INPUT
refValue : REF_TO INT;
END_VAR
VAR_OUTPUT
iResult : INT;
END_VAR
VAR
refMotor : REF_TO MotorData;
myMotor : MotorData;
END_VAR
// 引用赋值
refValue^ := 42; // 通过引用写入值
iResult := refValue^; // 通过引用读取值
// 引用结构体
refMotor := REF(myMotor); // 获取引用
refMotor^.rSpeed := 1500.0; // 通过引用修改成员
END_FUNCTION_BLOCK
POINTER TO(指针)
POINTER TO 是传统指针类型,与 REF_TO 相比缺少空值保护,但兼容 IEC 61131-3 早期版本:
TYPE
pInt : POINTER TO INT;
pMotor : POINTER TO MotorData;
END_TYPE
FUNCTION_BLOCK FB_PointerExample
VAR_INPUT
pData : POINTER TO INT;
iLength : INT;
END_VAR
VAR_OUTPUT
iSum : INT;
END_VAR
VAR
pCurrent : POINTER TO INT;
iIdx : INT;
END_VAR
pCurrent := pData;
iSum := 0;
FOR iIdx := 0 TO iLength - 1 DO
iSum := iSum + pCurrent^; // 解引用
pCurrent := pCurrent + SIZEOF(INT); // 地址偏移
END_FOR;
END_FUNCTION_BLOCK
REF_TO vs POINTER TO
| 特性 | REF_TO | POINTER TO |
|---|---|---|
| 空引用检测 | 运行时检测,解引用空引用触发错误 | 无自动检测 |
| 类型安全 | 编译期类型检查 | 编译期类型检查 |
| 算术运算 | 不允许指针算术 | 支持地址偏移 |
| 与 ADR 配合 | 使用 REF() 获取引用 | 使用 ADR() 获取地址 |
| 兼容标准 | IEC 61131-3 Ed3+ | IEC 61131-3 Ed1+ |
| 推荐场景 | 新项目、安全关键代码 | 内存操作、兼容旧代码 |
ADR 和 REF 运算符
VAR
iValue : INT := 100;
pPointer : POINTER TO INT;
refInt : REF_TO INT;
diAddr : DINT;
END_VAR
// ADR — 获取变量地址(返回 DINT 或指针)
pPointer := ADR(iValue);
pPointer^ := 200;
// REF — 获取引用
refInt := REF(iValue);
refInt^ := 300;
// SIZEOF — 获取变量或类型占用字节数
diAddr := SIZEOF(iValue); // 2
diAddr := SIZEOF(MotorData); // 结构体总字节数
空引用安全模式
Darra SCL 默认启用 REF_TO 空引用运行时检测:
FUNCTION_BLOCK FB_SafeRefAccess
VAR_INPUT
refData : REF_TO MotorData;
END_VAR
VAR_OUTPUT
bValid : BOOL;
rSpeed : REAL;
END_VAR
// 解引用前检查(Darra 扩展)
IF refData = NULL THEN
bValid := FALSE;
rSpeed := 0.0;
ELSE
bValid := TRUE;
rSpeed := refData^.rSpeed;
END_IF;
END_FUNCTION_BLOCK
数据类型选用指南
按应用场景
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 位逻辑(启停、连锁) | BOOL | 最小存储,逻辑运算效率最高 |
| 计数器(产品计数) | UINT / UDINT | 无符号,无需负数处理 |
| 编码器位置 | DINT / LINT | 有符号,支持正反方向 |
| 模拟量采集 | INT / REAL | 匹配 AD 转换位宽,或标定后浮点 |
| PID 计算 | REAL | 浮点精度足够,占用小 |
| 精密定位 | LREAL | 高精度坐标计算 |
| 定时器参数 | TIME | 直接用于 TON/TOF 功能块 |
| 报警文本 | STRING[128] | 长度可控,内存固定 |
| 国际化文本 | WSTRING[64] | 支持中文等非 ASCII 字符 |
按内存占用
// ❌ 低效:使用 DINT 存储开关量
VAR
diSwitch1 : DINT; // 32 bit 只用了 1 bit
diSwitch2 : DINT;
diSwitch3 : DINT; // 浪费 96 bit
END_VAR
// ✅ 高效:使用 BOOL 或位打包
VAR
bSwitch1 : BOOL;
bSwitch2 : BOOL;
bSwitch3 : BOOL; // 3 bit
END_VAR
// ✅ 更紧凑:使用 BYTE 位域(与 IO 模块交互)
VAR
xInputByte : BYTE; // 8 个数字量输入
END_VAR
// 访问: xInputByte.0 ~ xInputByte.7
默认值规则
SCL 变量在声明时若未显式初始化,则使用该类型的默认值:
- 整数类型(
INT/DINT/SINT等):0 - 浮点类型(
REAL/LREAL):0.0 - 位串类型(
BYTE/WORD/DWORD):16#00/16#0000/16#00000000 - 布尔类型(
BOOL):FALSE - 时间类型(
TIME/DATE/TOD/DT):T#0s/D#0001-01-01/TOD#00:00:00.000 - 字符串(
STRING/WSTRING):空字符串''