# 5. IEC61131-3语法
# 5.1 基本概念介绍
实时编程功能参考IEC61131-3软件信息模型实现,该模型描述软件元素之间的相互关系,这些元素包括:配置、资源、应用、任务、程序单元(POU)、全局变量。
配置:配置用于描述可编程控制系统(一台PLC)的特性,包括硬件装置(CPU模块以及与其关联的IO扩展及通讯模块)、资源与任务的绑定关系。
资源:可以加载执行应用的物理单元,例如,一台PLC内的每个CPU可以视作一个资源。
应用:包括三种类型:实时应用、非实时应用、通讯协议栈应用。其中实时应用可采用梯形图(LD)或结构化文本(ST)编写,非实时应用可以使用Python编写,通讯协议栈应用可以基于配置页面设置参数。本项目中每个应用对应一个独立进程。
任务:在实时应用中,程序单元(POU)需要挂载到任务中才会执行。每个应用中可以包含多个任务,根据任务的优先级及触发方式来决定执行顺序。任务的触发方式包括定时循环、事件触发两种方式。
程序单元(POU):包括程序(PROG)、程序单元包括函数(FUN)、函数块(FB)三种类型,其中程序(PROG)构成应用的主程序,可定义输入及输出参数; 函数(FUN)可定义输入、输出参数,以及一个函数返回值,没有用于保存状态的静态变量,对于同样的输入参数,始终返回同样的结果;函数块(FB)可定义输入及输出参数,可以用静态变量保存状态,每次执行的结果取决于内部(VAR)与外部(VAR_EXTERNAL)变量。
全局变量与过程镜像区: 全局变量的“全局”是指在一个实时应用中处处可见。同一应用中不同的POU可以通过全局变量进行数据交互。此外,运行时系统会留出一块共享内存区域存储控制过程中的输入输出变量,作为应用与IO寄存器之间的交互中介,这称为过程镜像区(存储控制过程的变量),其中包括输入变量区(I区), 输出变量区(Q区)、中间变量区(M区)。当用户创建一个新的全局变量之后,可以手动将其与过程镜像区地址进行绑定,绑定后的全局变量可以被NR应用、NRT应用以及通讯协议栈应用访问。本项目会为用户添加的通讯协议对象(测点)自动创建全局变量并绑定过程镜像地址。
# 5.2 公共元素
# 5.2.1 标识符
标识符由字母、数字、下划线构成,首个字符应为字母或下划线(不能为数字), 尾字符应为字母或数字(不能为下划线)。标识符不区分大小写,abcd, ABCD或aBCd会被编译器视为相同的标识符。在标识符中不允许连续使用下划线,例如,__LIM_SW5与LIM___SW5会被视作非法标识符。
IEC61131-3(2003)标准要求标识符应分辨至少前6个字符的区别,即ABCDE1与ABCDE2应被判断为两个不同的标识符。
以下为合法的标识符
标识符组成元素 | 举例 |
---|---|
大写字母、小写字母、数字、在标识符中间的下划线、作为首字符的下划线 | IW215、IW215Z、QX75、IDENT LIM_SW_5、LimSw5、abcd、ab_Cd_MAIN、_12V7 |
# 5.2.2 关键字
关键字不应包含空格,也不区分大小写,例如,FOR 与 for被编译器视为相同关键字。
关键字具体定义请参考IEC61131-3(2003)附录C。
关键字 | 描述 |
---|---|
VAR … END_VAR | 变量声明 VAR CONSTANT LOCALVAR0 : INT := 1; END_VAR |
VAR_INPUT | 输入变量 |
VAR_OUTPUT | 输出变量 |
VAR_IN_OUT | 输入输出变量 |
VAR_EXTERNAL | 外部变量 |
VAR_TEMP | 临时变量 |
VAR_ACCESS | 访问变量(暂不支持) |
VAR_CONFIG | 配置变量(暂不支持) |
VAR_GLOBAL | 全局变量 |
RETAIN | 限定符:保持 |
NON_RETAIN | 限定符:非保持 |
CONSTANT | 限定符:常量 |
R_EDGE | 限定符:上升沿 |
F_EDGE | 限定符:下降沿 |
READ_ONLY | 限定符:只读 |
READ_WRITE | 限定符:可读可写 |
TYPE … END_TYPE | 类型声明 TYPE COLOR : (RED, YELLOW, BLUE); END_TYPE |
STRUCT … END_STRUCT | 结构体类型 TYPE DEVICE : STRUCT IP : STRING; PORT : INT; END_STRUCT; END_TYPE |
ARRAY … OF … | 数组类型 TYPE MEASURE : ARRAY [1..10] OF INT; END_ARRAY |
AT | 指定IEC地址 |
NOT | 取反 |
MOD | 模运算 |
AND | 与 |
OR | 或 |
XOR | 异或 |
LABEL | 标号 |
JMP | 跳转 |
PROGRAM … END_PROGRAM | 程序声明 |
CONFIGURATION … END_CONFIGURATION | 配置声明 CONFIGURATION config RESOURCE resource1 ON PLC TASK MAINTASK(INTERVAL := T#100ms, PRIORITY := 1); PROGRAM MY_PRG1 WITH MAINTASK : MYPRG; END_RESOURCE END_CONFIGURATION |
RESOURCE … ON … END_RESOURCE | 资源声明 |
TASK | 任务声明 |
PROGRAM … WITH … | 任务调用程序 |
IF … THEN … ELSIF … ELSE … END_IF | IF语句 IF d < e THEN f := 1; ELSIF d = 4 THEN f := 2; ELSE f := 3; END_IF; |
CASE … OF … ELSE … END_CASE | CASE语句 CASE f OF 1: g := 11; 2: g := 12; ELSE g ;= 13; END_CASE; |
FOR … TO … BY … DO … END_FOR | FOR语句 FOR h := 1 TO 10 BY 2 DO f[h/2] := h; END_FOR; |
WHILE … DO … END_WHILE | WHILE语句 WHILE m > 1 DO N := n / 2; END_WHILE; |
REPEAT … UTIL … END_REPEAT | REPEAT语句 REPEAT i := i * j; UNTIL i < 10000 END_REPEAT; |
EXIT | 跳出语句 EXIT; |
# 5.2.3 常数
在PLC中通过“类型名#值”或“值”的形式声明
类型 | 描述 | 示例 |
---|---|---|
BOOL | 布尔值 | BOOL#FALSEBOOL#0TRUE |
BYTE | 8位,0 ~ 16#FF | 1116#0B2#0000_1011 |
WORD | 16位,0 ~ 16#FFFF | 0 |
DWORD | 32位,0 ~ 16#FFFF FFFF | 16#ABCDEF |
LWORD | 64位,0 ~ 16#FFFF FFFF FFFF FFFF | 16#ABCDEFABCDEF |
SINT | 短整型,-128 ~ 127 | 125 |
INT | 整型,-32768 ~ 32767 | -32456 |
DINT | 双精度整型,-231~ 231-1 | 324534 |
LINT | 长整型,-263 ~ 263-1 | 545672345 |
USINT | 无符号短整型,0 ~ 255 | 1 |
UINT | 无符号整型,0 ~ 65535 | 10 |
UDINT | 无符号双精度整型,0 ~ 231-1 | 500 |
ULINT | 无符号长整型,0 ~ 263-1 | 60000 |
REAL | 32位浮点类型 | 1.0 |
LREAL | 64位长浮点类型 | 2.0 |
DATE | 日期 | DATE#2023-06-29, D#2023-06-29 |
LDATE | 长日期 | LDATE#2023-06-29, LD#1970-1-1 |
TIME_OF_DAYTOD | 时间 | TOD#11:30:00 |
LTIME_OF_DAYTOD | 长时间 | LTOD#11:30:00 |
DATE_AND_TIMEDT | 日期时间 | DT#2023-06-09-11:30:00 |
LDATE_AND_TIMEDT | 长日期时间 | lDT#2023-06-09-11:30:000001 |
TIME | 时长 | TIME#1000msT#1d2h7m19s45.7ms |
LTIME | 长时长 | LTIME#0NS, LTIME#213503D23H34M33S709MS551US615NS |
STRING | 字符串 | 'text' |
WSTRING | 宽字符串 | "text" |
# 5.2.4 注释
注释可以用 (*
开始,以*)
结束。注释内部不允许嵌套注释,例如,(* (* NESTED *) *) 视为语法错误。
允许以 //
开头的行内注释,或者以 /*
开始,*/
结束的块注释
# 5.2.5 预处理指令
预处理指令应以”{”开始,以”}”结束。例如:{VERSION 3.1} {AUTHOR JHC} {x := 256, y := 384}
目前IDE中已支持下列指令,用于编译过程中调试,打印日志信息:
- INFO 示例:{INFO: "this is an info message."}
- WAENING 示例:{WARNING: "this is a warn message."}
- ERROR 示例:{ERROR:"this is an error message."}
# 5.3 变量
# 5.3.1 变量的数据类型
# 5.3.1.1 基本数据类型
基本数据类型、每种数据类型的关键字、每个数据元素的位数以及每种基本数据类型的值范围应如下表所示。
关键字 | 数据类型 | 位(N) | 取值范围 | 初始值 |
---|---|---|---|---|
BOOL | 布尔 | 1 | 0,1 | 0(FALSE) |
SINT | 短整型 | 8 | [-(2^N-1), (2^N-1)-1] | 0 |
INT | 整型 | 16 | [-(2^N-1), (2^N-1)-1] | 0 |
DINT | 双整型 | 32 | [-(2^N-1), (2^N-1)-1] | 0 |
LINT | 长整型 | 64 | [-(2^N-1), (2^N-1)-1] | 0 |
USINT | 无符号短整型 | 8 | [0, (2^N)-1] | 0 |
UINT | 无符号整型 | 16 | [0, (2^N)-1] | 0 |
UDINT | 无符号双整型 | 32 | [0, (2^N)-1] | 0 |
ULINT | 无符号长整型 | 64 | [0, (2^N)-1] | 0 |
REAL | 实数 | 32 | -- | 0.0 |
LREAL | 长实数 | 64 | -- | 0.0 |
TIME | 持续时间 | 32 | -- | T#0ms |
LTIME | 长持续时间 | 64 | -- | LT#0ns |
DATE | 日期 | 32 | -- | D#1970-01-01 |
LDATE | 长日期 | 64 | -- | LD#1970-01-01 |
TIME_OF_DAT或 TOD | 一天中的时间 | 32 | -- | TOD#00:00:00 |
LTIME_OF_DAT或 LTOD | 一天中的长时间 | 64 | -- | LTOD#00:00:00.999999 |
DATE_AND_TIME或DT | 日期和时间 | 32 | -- | DT#1970-01-01-00:00:00 |
LDATE_AND_TIME或LDT | 长日期和时间 | 64 | -- | LDT#1970-01-01-00:00:00 |
STRING | 单字节字符串 | 8*N | -- | '' (空串) |
BYTE | 8位长度位串 | 8 | -- | 0 |
WORD | 16位长度位串 | 16 | -- | 0 |
DWORD | 32位长度位串 | 32 | -- | 0 |
LWORD | 64位长度位串 | 64 | -- | 0 |
WSTRING | 双字节字符串 | 16*N | -- | "" (空串) |
# 5.3.1.2 自定义数据类型(DUT)
通过在应用上右击->选择DUT,即可添加用户自定义数据类型。目前支持6中用户自定义数据类型,分别为:
- 直接衍生数据类型(Alias)
- 枚举数据类型(Enumerate)
- 范围数据类型(Scope)
- 数组数据类型(Array)
- 结构体数据类型(Struct)
- 联合体数据类型(Union)
# 5.3.1.2.1 直接衍生数据类型
声明直接衍生数据类型时,除了基础数据类型外,还可以在直接衍生出用户自定义数据类型。
若非特别指定,直接衍生数据类型与其衍生的数据类型有相同的初始值。例如,下图中衍生数据类型dut_alias_INT与INT使用方式完全相同。若在初始值中给dut_alias_INT类型赋初值,那么dut_alias_INT类型将使用设置的初值作为默认初始值。
# 5.3.1.2.2 枚举
枚举数据类型声明指定该类型的任何数据元素的值只能为关联标识符列表中给定的值之一,枚举列表定义了一组有序的枚举值,从列表的第一个标识符开始,以最后一个标识符结束。不同的枚举数据类型可以使用相同的标识符作为枚举值。
为了在特定上下文中实现唯一标识的使用,可以用由其关联的数据类型名称和'#'符号组成的前缀来限定枚举字面值。这种前缀不得在枚举列表中使用。如果在枚举文字中没有提供足够的信息来明确地确定其值,则会产生语法错误。
枚举数据类型的默认初始值为枚举列表中的第一个标识符,或由用户指定其他的枚举值。
例如,下图中定义枚举数据类型dut_my_enum1,有三个枚举项,分别为aa,bb和cc,其默认初始值为aa。
# 5.3.1.2.3 范围
范围声明指定该类型的任何数据元素的值只能在指定的上限和下限之间。如果范围类型的值超出指定的值范围,在使用的时候会将其严格限制在用户指定的范围内。
对于范围数据类型,若没有特别指定初始值,默认初始值为范围的最小值(下限值)。
# 5.3.1.2.4 数组
数组声明指定为该类型的每个元素分配足够的数据存储空间,以存储可以通过指定的索引范围访问的所有数据。
若没有特别指定,数组元素的初始值,为其对应数据类型默认初始值。
下例中,定义了一个二维数组dut_array,其下表分别是1..2和2..6。二维数组包含两行五列十个元素,下表分别为[1,2], [1,3], [1,4], [1,5], [1,6], [2,2], [2,3], [2,4], [2,5], [2,6]。
数组的初始化,可以以下面的方式对数组进行初始化:
- 定义数组的时候对数组元素赋予初值,例如: [1,2,3,4,5,6,7,8,9,10],那么将会将十个元素分别赋值为1到10;
- 只给部分数组元素赋值,例如[1, 2];在这个初始化中,只有前两个元素被赋值,没有赋值的数组元素,使用其基本数据类型的默认值进行初始化,在上例中,后面八个元素被初始化为0;
- 对于重复的初值,可以批量定义,只需要在括号前加上数量,例如上例图中4(8)的元素定义,表示4个元素的值为8
- 数组元素定义初值时,其中中括号[]是必须的,用来表示是数组结构。
若只需要定义一维数组,则添加一个维度即可。
# 5.3.1.2.5 结构体
结构体声明指定该类型的数据元素应该包含指定类型的子元素,这些子元素可以通过指定的名称访问。
若没有特别指定,结构体元素的初始值,为其对应元素数据类型初值。
例如,下例中定义一个结构体数据类型dut_struct,包含两个元素,分别是整型age和字符串类型name;这两个元素都没有设置初始值,那么就按照其变量类型的初值作为结构体元素的初始值。
# 5.3.1.2.6 联合体
联合体是一种数据结构,通常包含不同的数据类型。其定义形式与结构体类似
在联合中,所有元素具有相同的偏移量,因此具有相同的内存量。在下面的联合声明示例中,对联合体变量元素 a
的赋值。也会影响其元素b
# 5.3.1.3 指针数据类型
# 5.3.1.3.1 指针定义
指针在运行时存储对象(如变量或功能块块实例)的内存地址。其语法如下:
<pointer name>: POINTER TO <data type | data unit type | function block>;
- 使用
POINTER TO
关键字定义指针类型- 指针可以指向基础数据类型、用户自定义数据类型、或者功能块类型
# 5.3.1.3.2 使用样例
FUNCTION_BLOCK FB_Point
VAR
piNumber: POINTER TO INT;
iNumber1: INT := 5;
iNumber2: INT;
END_VAR
piNumber := ADR(iNumber1); // iNumber1的地址赋值给piNumber
iNumber2 := piNumber^; // 通过指针解引用,将值5赋值给iNumber2变量
2
3
4
5
6
7
8
9
指针变量必须指向内存中某一数据后才能使用;如果目标机有数据对齐要求,必须保证指针所指类型与访问的数据类型是一致的。
指针支持取值和取地址操作:
- 可以通过附加内容运算符到指针标识符直接取值,例如上述
piNumber^
。- 可以通过
VAL
取值指令获取指针所指地址的变量值,赋值给其他变量,类似于使用^
符号对指针解引用- 可以通过
ADR
取地址指令获取一个变量的地址值,赋值给指针,例如上述piNumber := ADR(iNumber1);
# 5.3.2 变量的表示
变量可以简单分为两类,单元素变量和多元素变量。
单元素变量被定义为:
- 表示基本类型之一的单个数据元素的变量;
- 表示自定义数据类型中枚举或范围类型的变量;
- 一个自定义数据类型中的衍生数据类型,其“父类型”可追溯到基本类型、枚举类型或范围类型。
使用本文中定义的标识符作为变量符号表示。变量还可以关联地址,在地址栏由百分号“%”、下表中的位置前缀和大小前缀以及一个或多个以句点(.)分隔的无符号整数的连接而成的特殊符号来提供。
直接表示变量的位置和大小前缀特性
多元素变量的数据类型为数组或者结构体。
数组是具有相同数据类型的数据元素的集合,由一个或多个下标索引,数组下标用中括号("[]")括起来并以逗号分隔。
在ST语言中使用数组变量的一个例子如下:
OUTARY[%MB6,SYM] := INARY[0] + INARY[7] - INARY[%MB6] * %IW62;
结构体变量是一种变量,它被声明为先前定义的结构体类型,即由命名元素集合组成的数据类型。
结构体变量的元素应由两个或多个标识符或以单个句点(.)分隔的数组访问来表示。第一个标识符表示结构化元素的名称,随后的标识符表示访问数据结构中特定数据元素的组件名称序列。
例如,如果变量person被声明为dut_struct类型,以下代码将导致18被分配给变量person的元素age;而name被分配为'Whoami':
person.age := 18;
# 5.3.3 变量的声明和初始化
程序组织单元类型的每个声明(程序、函数或功能块的每个声明)应在其开头至少包含一个变量声明部分,该变量声明部分指定POU中使用的变量的类型(如果需要,还包括物理或逻辑位置)。
变量声明特性
类型 | 关键字 | 外部读写 | 内部读写 | 描述 |
---|---|---|---|---|
本地 | VAR | R/W | 仅在POU内部可见的变量 | |
输入 | VAR_INPUT | R/W | R | 输入型形参变量。 被调用的POU使用该类型变量时先对实参进行拷贝(call-by-value), 计算过程不会改变实参的数值。 |
输出 | VAR_OUTPUT | R | R/W | 输出型形参变量,用于返回计算结果(return-by-value),调用完成后变量数值不可被改变。 |
输入输出变量 | VAR_IN_OUT | R/W | R/W | 被调用的POU直接使用实参变量的指针(call-by-reference),计算过程会直接改变实参的数值,因此VAR_IN_OUT类型变量可以用来传递计算结果(对于数组或结构体变量,这种调用方式效率更高)。 |
临时变量 | VAR_TEMP | R/W | 仅在POU内部可见的变量,且程序组织单元每次执行都会重新初始化。 | |
外部变量 | EXTERNAL | R/W | 用于声明在GVL中定义的全局变量;只有声明之后才可以在POU中使用 |
每个变量包含以下属性
- 类别:根据变量的作用域进行分类
- 名称: 用户自定义,应遵守命名规则。
- 地址:关联的过程镜像地址(可以不关联)。
- 数据类型:包括基本类型和自定义类型(DUT)。
- 初值:用户根据变量类型设置初始值,IDE提供默认值。
注释:用户输入的描述文本,对变量在程序中作用、使用方式进行说明
变量初始化
若一个配置元素(配置或者资源)被启动时,与配置元素及其程序相关联的每个变量都可以采用以下初始值之一:
- 配置元素上次被“停止”时变量的值(保留值),暂未支持保留特性;
- 用户指定的初始值;
- 根据变量关联的数据类型的默认值
VAR_EXTERNAL是引用全局变量,不能给VAR_EXTERNAL声明的变量赋初始值。
在数组初始化过程中,括号可以用作数组初始化列表中的重复因子,例如,2(1,2,3)相当于初始化序列1,2,3,1,2,3。
如果初始化列表中给出的初始值的数量超过了数组条目的数量,则多余的(最右边的)初始值将被忽略。
如果初始值的数量少于数组条目的数量,则剩余的数组元素将使用对应数据类型的默认初始值填充。
当变量声明为功能块实例时,功能块的输入和任何可访问变量的初始值,都可以在功能块类型标识符后面用赋值操作符及其后面的圆括号列表中声明,如下表所示。未列出初始值的元素应具有功能块声明中为这些元素声明的默认初始值。
变量初始值赋值特性
特性描述/样例 | |
---|---|
直接表示变量的初始化 | |
VAR AT %QX5.1 : BOOL :=1; AT %MW6 : INT := 8 ; END_VAR | 布尔类型,初始化为1 初始化内存字,值为整型8 |
符号变量带地址变量初始化 | |
VAR VALVE_POS AT %QW28 : INT := 100; END_VAR | 将输出字28赋给整型变量VALVE_POS,初始值为100 |
符号变量初始化 | |
VAR MYBIT : BOOL := 1 ; OKAY : STRING[10] := 'OK'; END_VAR | 为布尔变量MYBIT分配一个内存位,初始值为1。 分配内存以包含最大长度为10个字符的字符串。初始化后,字符串的长度为2,并包含字符'OK'的两个字节序列(分别为十进制79和75),其顺序适合作为字符串打印。 |
数组初始化 | |
VAR BITS : ARRAY[0..7] OF BOOL := [1,1,0,0,0,1,0,0] ; TBT : ARRAY [1..2,1..3] OF INT := [1,2,3(4),6] ; END_VAR | 分配8个内存位来存放初始值: BITS[0]:= 1, BITS[1] := 1,..., BITS[6]:= 0, BITS[7] := 0 一个具有初始值的2 × 3整数数组TBT: TBT[1,1]:=1, TBT[1,2]:=2, TBT[1,3]:=4, TBT[2,1]:=4, TBT[2,2]:=4, TBT[2,3]:=6. |
结构体变量初始化 | |
VAR person: dut_struct := ( age := 18, name := ‘Whoami’); END_VAR | 派生数据类型变量的初始化 这个示例演示了为变量person的age字段name字段被分别初始化为18和‘Whoami’。 |
常数变量初始化 | |
VAR CONSTANT PI : REAL := 3.141592 ; END_VAR | |
功能块实例初始化 | |
VAR TempLoop : PID := (PropBand := 2.5, Integral := T#5s); END_VAR | 为功能块实例的输入和输出分配初始值 |
# 5.4 程序组织单元(POU)
在IEC 61131中定义的程序组织单元包括函数、功能块和程序。
程序组织单位不得递归;也就是说,一个程序组织单元的调用不得引起其自身的调用。
# 5.4.1 函数
函数在执行时只产生一个数据元素(被认为是函数返回值)和任意多个附加输出元素(VAR_OUTPUT和VAR_IN_OUT)。函数返回值可以是数组或结构体。在文本语言中,函数的调用可以用作表达式中的操作数。
函数不应包含内部状态信息,也就是说,调用具有相同参数的函数(VAR_INPUT变量和VAR_IN_OUT变量)应始终产生相同的值(VAR_OUTPUT变量,VAR_IN_OUT变量和函数返回值)。基于这个原则,函数中不可以使用全局GVL中定义的变量。
# 5.4.1.1 函数表示和声明
通过在应用上右击->POU,创建POU,将POU类型选择为函数,选择所使用的编程语言以及函数的返回值,定义一个函数。例如,下图定义一个名为myAdd的函数,该函数返回INT数据类型的值,并用ST语言编写函数体。
函数名即为函数的返回值,所以在函数定义中,需要将函数至少赋值一次。以最后一次赋值结果作为函数的返回值。例如,下图为函数的实现,该函数有两个输入参数IN1和IN2,函数返回两个输入参数的和。
函数定义中不需要额外定义EN和ENO参数,将由系统自动生成相应的逻辑。
函数及其调用方式可以有图形或文本两种表示方式。其中函数的调用应遵循以下规则:
- 对VAR_OUTPUT变量的赋值要么为空,要么为变量。
- 对VAR_IN_OUT参数的赋值必须是变量。
- 对VAR_INPUT参数的赋值可以为空、常量、变量或函数调用。在后一种情况下,函数结果被用作实际参数。
下图阐述函数的图形和其等价的文本的函数调用使用方法,以标准函数(ADD)为例;
样例 | 解释说明 |
---|---|
![]() | 使用ADD函数的图形表示 |
D := ADD(A,B,C); | 使用ADD函数的文本语言(非正式调用) |
D := ADD(IN1:=A, IN2:=B, IN3:=C); | 使用ADD函数的文本语言(正式调用) |
![]() | 执行控制的ADD函数图形使用 |
D := ADD(EN:=bVar, IN1:=A, IN2:=B, IN3:=C); | 执行控制的ADD函数的文本语言(正式调用) |
函数的文本调用应由函数名后跟参数列表组成。在ST语言中,参数用逗号分隔,该列表的左、右用圆括号"()"分隔。且需要满足如下要求:
- 带执行控制的函数文本语言调用中不支持非正式调用,只能正式调用。
- 正式调用中,使用“:=”操作符将值赋值给输入变量和输入输出变量
- 正式调用中,使用“=>”运算符将输出变量的值赋值给变量
- 正式调用中,列表中参数的顺序不重要,且传递的参数数量也不重要,任何未在列表中赋值的变量,如果有,则使用函数规范中赋值的默认值,或者使用关联数据类型的默认值。
- 非正式调用参数列表应包含与函数定义中给出的参数数量、顺序和数据类型完全相同的参数,不包含执行控制参数EN和ENO。
# 5.4.1.2 函数执行控制逻辑
函数定义中,系统根据函数声明提供一个额外的布尔EN (Enable)输入或ENO (Enable Out)输出,类似于如下定义:
VAR_INPUT EN: BOOL := 1; END_VAR
VAR_OUTPUT ENO: BOOL; END_VAR
在使用这两个变量时,函数定义操作的执行将按照以下规则进行控制:
- 当函数被调用时,若EN的值是FALSE(0),在函数体中定义的操作将不会执行,且ENO被设置成FALSE;
- 若EN的值是TRUE(1),ENO将被系统设置为TRUE,且函数体中的逻辑操作被执行。
# 5.4.1.3 标准库函数
标准函数中对于指定的函数允许可扩展的输入,例如,可扩展加法应将其所有输入的总和作为其输出。在可扩展参数的函数的正式调用中有效的实际输入数由参数名称序列中位置最高的正式输入名称决定。例如:
- X := ADD(Y1,Y2,Y3); 等价于 X := ADD(IN1 := Y1, IN2 := Y2, IN3 := Y3);
- I := MUX_INT(K:=3, IN0 := 1, IN2 := 2, IN4 := 3);等价于I := 0; 因为IN3没有输入,默认为0,任何数与0相乘结果都为0。
标准可扩展函数的函数参数遵循如下规则:
- 如果在标准函数中没有给出输入变量的名称,则默认名称为IN1, IN2,…,图形中应按从上到下的顺序使用。
- 当标准函数只有一个未命名的输入时,应使用默认名称IN。
- 当标准函数只有一个未命名的输出时,应使用默认名称OUT。
# 5.4.1.3.1 类型转换函数
类型转换函数的形式应为*TO,其中“*”为输入变量IN的数据类型,“”为输出变量OUT的数据类型,如INT_TO_REAL。
从类型REAL或LREAL到类型SINT, INT, DINT或LINT的转换应根据IEC 60559的约定进行舍入,根据该约定,如果两个最接近的整数相等,则结果应为最接近的偶数,例如:
- REAL_TO_INT(1.6) 值为 2
- REAL_TO_INT(-1.6) 值为 -2
- REAL_TO_INT(1.5) 值为2
- REAL_TO_INT(-1.5) 值为-2
- REAL_TO_INT(1.4) 值为1
- REAL_TO_INT(-1.4) 值为-1
- REAL_TO_INT(2.5) 值为2,
- REAL_TO_INT(-2.5) 值为–2
函数TRUNC用于将REAL或LREAL表示的浮点数的整数部分转换为一个整数,例如:
- TRUNC(1.6) 值为1
- TRUNC(-1.6) 值为-1
- TRUNC(1.4) 值为1
- TRUNC(-1.4) 值为-1
转换函数*_BCD_TO_**和**_TO_BCD_*
在对应的位串变量包含以BCD格式编码的数据时,执行BYTE、WORD、DWORD、LWORD类型变量与USINT、UINT、UDINT、ULINT类型变量(分别用“”和“**”表示)之间的转换。例如,USINT_TO_BCD_BYTE(25)的值将是2#0010_0101,而WORD_BCD_TO_UINT(2#0011_0110_1001)的值将是369。
# 5.4.1.3.2 数值类函数
单个数值变量函数的函数名称、输入输出变量类型和函数描述,如下表所示。这些函数应该在定义的泛型类型上重载。这些函数,输入和输出的类型相同。
两个或两个以上变量的算术函数的函数名称和符号以及描述,如下表所示。这些函数应在所有数值类型上重载,如果其中一个函数的求值结果超出了为函数输出的数据类型指定的值范围,或者试图除以零,则会产生语法错误。
# 5.4.1.3.3 位串函数
单个位串变量的移位函数的函数名称和描述如表所定义。这些函数应该重载所有的位串类型。
函数名 | 描述 |
---|---|
SHL | OUT等于IN左移N位,最右侧填充0 |
SHR | OUT等于IN右移N位,最左侧填充0 |
ROR | OUT等于IN循环右移N位 |
ROL | OUT等于IN循环右移N位 |
位布尔函数的函数名称、符号以及描述的标准图形表示、如下表所定义。这些函数应该是可扩展的(除了NOT),并且可以在所有位串类型上重载。
函数名 | 符号表示 | 描述 |
---|---|---|
AND | & | OUT := IN1 & IN2 ... & INn |
OR | OUT := IN1 OR IN2 ... OR INn | |
XOR | OUT := IN1 XOR IN2 ... XOR INn | |
NOT | OUT := NOT IN1 |
# 5.4.1.3.4 选择和比较函数
选择和比较函数应在所有数据类型上重载。选择函数的标准函数名称和描述如下表所示。
图形表示 | 说明/样例 |
---|---|
SEL | 二元选择:^ ^如果 G = 0,那么OUT := IN0如果 G = 1,那么OUT := IN1例如: A := SEL(G:=0,IN0:=X,IN1:=5) ; |
MAX | 可扩展最大值函数:OUT := MAX (IN1,IN2, ...,INn)例如: A := MAX(B,C,D) ; |
MIN | 可扩展最小值函数:OUT := MIN (IN1,IN2, ...,INn)例如: A := MIN(B,C,D) ; |
LIMIT | 限制范围函数:OUT := MIN(MAX(IN,MN),MX)例如: A := LIMIT(IN:=B,MN:=0,MX:=5); |
MUX | 可扩展的多路复用器^^根据输入K从N个输入中选择一个例如:A := MUX(0, B, C, D);相当于A := B ; |
比较函数的标准函数名称和符号以及描述如下表所定义。所有比较函数(NE除外)都应具有可扩展性。
对位串数据进行从最高有效位到最低有效位的逐位比较,较短的位串与较长的位串相比,认为在左侧填满了零;也就是说,位串变量的比较结果应该与无符号整数变量的比较结果相同。
函数名 | 符号表示 | 描述 |
---|---|---|
GT | > | 递减序列:OUT := (IN1>IN2) & (IN2>IN3) & ... & (INn-1 > INn) |
GE | >= | 单调序列,降序:OUT := (IN1>=IN2)&(IN2>=IN3)& ... & (INn-1 >= INn) |
EQ | = | 相等序列:OUT := (IN1=IN2) & (IN2=IN3) & ... & (INn-1 = INn) |
LE | <= | 递增序列:OUT := (IN1<=IN2)&(IN2<=IN3)& ... & (INn-1 <= INn) |
LT | < | 单调序列,升序:OUT := (IN1<IN2) & (IN2<IN3) & ... & (INn-1 < INn) |
NE | <> | 不相等(非扩展性):OUT := (IN1 <> IN2) |
# 5.4.1.3.5 字符串函数
上节中定义的所有函数都适用于字符串。为了比较两个长度不等的字符串,应认为较短的字符串在右侧通过值为0的字符扩展到了较长字符串的长度。根据字符集中字符码的数值,从左到右进行比较。例如,字符串'Z'必须大于字符串'AZ', 'AZ'必须大于'ABC'。
字符串函数的标准函数名和描述如表所示。出于操作的目的,字符串中的字符位置应被视为编号为1、2、…,L,从最左边的字符位置开始,其中L是字符串的长度。
函数名 | 说明/样例 |
---|---|
LEN | 字符串长度函数, 例如:A := LEN('ASTRING');相当于A := 7; |
LEFT | 获取IN最左边L个字符组成的子串,例如:A := LEFT(IN:='ASTR',L:=3);相当于A := 'AST' ; |
RIGHT | 获取IN最右边L个字符组成的子串,例如:A := RIGHT(IN:='ASTR',L:=3);相当于A := 'STR' ; |
MID | 获取IN从P位置开始,长度为L的字串,例如:A := MID(IN:='ASTR',L:=2,P:=2); 相当于A := 'ST' ; |
CONCAT | 可扩展的连接,例如:A := CONCAT('AB','CD','E') ;相当于A := 'ABCDE' ; |
INSERT | 在IN1的位置P出插入IN2,得到拼接串,例如:A:=INSERT(IN1:='ABC',IN2:='XY',P=2);相当于A := 'ABXYC' ; |
DELETE | 删除IN中的L个字符,从第P个字符位置开始:A := DELETE(IN:='ABXYC',L:=2, P:=3) ;相当于A := 'ABC' ; |
REPLACE | 将IN1中的L个字符替换为IN2,从第P个字符位置开始:REPLACE(IN1:='ABCDE',IN2:='X',L:=2, P:=3) ;相当于:A := 'ABXE' ; |
FIND | 查找IN1中IN2第一次出现的开头字符的位置。如果没有发现IN2,则OUT:= 0。A := FIND(IN1:='ABCBC',IN2:='BC');相当于A := 2 ; |
# 5.4.1.3.6 时间数据类型相关函数
下表中给出时间数据类型相关的函数。
如果其中一个函数的计算结果超出了输出数据类型的取值范围,将造成语法错误。
# 5.4.1.3.7 枚举数据类型相关函数
下表列出的选择和比较函数可以应用于枚举数据类型的输入。
名字 | 符号 |
---|---|
SEL | |
MUX | |
EQ | = |
NE | <> |
# 5.4.2 功能块
功能块是一个程序组织单元,它在执行时产生一个或多个值。可以创建一个功能块的多个命名实例。
每个实例都应该有一个关联的标识符(实例名),一个包含其输出和内部变量的数据结构。该数据结构的所有输出变量和必要的内部变量的值将从功能块的一次执行持续到下一次执行;因此,调用具有相同参数(输入变量)的功能块不一定总是产生相同的输出值。
只有输入和输出变量可以在功能块实例的外部访问,也就是说,功能块的内部变量应该对功能块的用户隐藏。
任何已经声明的功能块都可以在声明另一个功能块或程序类型中使用。功能块实例的作用域对于它被实例化的程序组织单元来说是局部的,除非它在GVL中被声明为全局的。
如果在VAR_INPUT声明中将功能块声明为输入变量,或在VAR_IN_OUT声明中将功能块声明为输入-输出变量,那么功能块实例的实例名可以用作函数和功能块的输入参数。
# 5.4.2.1 功能块表示和声明
通过在应用上右击->POU,创建POU,将POU类型选择为功能块,选择所使用的编程语言,定义一个功能。例如,下图定义一个名为myFB的功能块,并用ST语言编写功能块体。
除非在功能块内部,否则不允许给功能块的输出变量赋值。给功能块的输入变量赋值只允许作为功能块调用的一部分。功能块的未赋值的输入应保留其初始值或上次调用的值(如果有的话)。下表总结了功能块输入和输出变量的允许用法,示例以ST语言显示。
使用方式 | 功能块内部 | 功能块外部 |
---|---|---|
Input 读 | IF IN1 THEN ... | 不允许 |
Input 赋值 | 不允许 | FB_INST(IN1:=A,IN2:=B); |
Output 读 | OUT := OUT AND NOT IN2; | C := FB_INST.OUT; |
Output赋值 | OUT := 1; | 不允许 |
In-out 读 | IF INOUT THEN ... | IF FB1.INOUT THEN... |
In-out 赋值 | INOUT := OUT OR IN1; | FB_INST(INOUT:=D); |
可以通过VAR_EXTERNAL声明定义于GVL的全局变量,且全局变量的变量值可以在功能块内部进行修改。
功能块定义中不需要额外定义EN和ENO参数,将由系统自动生成相应的逻辑。
# 5.4.2.2 功能块执行控制逻辑
在功能块定义中,系统根据提供一个额外的布尔EN (Enable)输入或ENO (Enable Out)输出,类似于如下定义:
VAR_INPUT EN: BOOL := 1; END_VAR
VAR_OUTPUT ENO: BOOL; END_VAR
在使用这两个变量时,功能块的操作的执行将按照以下规则进行控制:
- 当功能块实例被调用时,若EN的值是FALSE(0),在功能块逻辑体中定义的操作将不会执行,且ENO被系统设置成FALSE;
- 若EN的值是TRUE(1),ENO将被系统设置为TRUE,功能块逻辑体中的逻辑操作将被执行;
根据上述规则,可以使用EN输入的FALSE值来“冻结”相关功能块的操作;也就是说,不管任何其他输入值的变化如何,输出值都不会改变。当EN输入值变为TRUE时,功能块可以恢复正常操作。每次对EN输入为FALSE的功能块调用后,ENO输出的值为FALSE。当EN为TRUE时,ENO的TRUE值反映了功能块的正常执行。
# 5.4.2.3 标准库功能更快
标准功能块可以被重载,并且可以具有可扩展的输入和输出。
# 5.4.2.3.1 双稳态元素
双稳态功能块包括置位优先功能块(SR) 和复位优先功能块(RS)。
# 5.4.2.3.2 边缘检测
标准上升沿和下降沿检测功能块的行为对应以下规则:
R_TRIG功能块的Q输出在CLK输入从0到1转换之后,从功能块的一次执行到下一次执行都应该保持BOOL#1的值,并在下一次执行时返回0。
F_TRIG功能块的Q输出在CLK输入从1到0转换后,从功能块的一次执行到下一次执行时,Q值应保持在BOOL#1值,并在下一次执行时返回0。
# 5.4.2.3.3 计数器
# 5.4.2.3.4 定时器
标准定时器功能块的图形形式如下表所示。
特性描述 | 图形表示 |
---|---|
*** 表示为:TP(脉冲定时器) TON (通电延时定时器) TOF (断电延时定时器) |
这些功能块的操作应按照下表所示的时序图进行定义。
TP 脉冲定时器 |
---|
![]() |
通电延时 (TON)定时器 |
![]() |
断电延时 (TOF)定时器 |
![]() |
# 5.4.3 程序
程序的声明和用法与功能块的声明和用法相同。
# 5.5 配置元素
# 5.5.1 配置、资源与访问路径
配置可以理解为一套设备的一整套控制系统,一个配置由一个或多个资源组成。
资源用于支持任务的执行,资源可以包含多个任务、程序组织单元、全局变量等。
全局变量用于程序组织单元之间交换数据,访问路径提供了当前配置对外通信的接口。
# 5.5.2 任务
任务是程序的调度者,用来执行一个或多个程序。任务支持多种类型:循环、事件、惯性滑行。循环性任务以固定时间间隔来执行;事件型任务在事件变量的上升沿开始执行。任务具有不同的优先级,优先级高的任务可以中断优先级低的任务而抢先执行。
# 5.6 文本语言
# 5.6.1 结构文本化
结构化文本(ST)是一种高级文本语言,可以用来描述函数、功能块和程序的行为。它由表达式和各种语句组成。
# 5.6.1.1 表达式
表达式由操作符和操作数组成。下表总结了ST语言的操作符。表达式的求值按照表中所示的操作符优先级定义的顺序将操作符应用于操作数。表达式中优先级最高的运算符应首先计算,其次是优先级次之的运算符,以此类推,直到求值完成。优先级相等的操作符应按表达式中从左到右的顺序使用。例如,如果A、B、C和D是INT类型,值分别为1、2、3和4,则A+B-C*ABS(D)将被计算成-9, 而(A+B-C)*ABS(D)将被计算成0。
当操作符有两个操作数时,应首先计算最左边的操作数。例如,在表达式SIN(A)*COS(B)中,SIN(A)的值将先被计算,然后是COS(B),最后才是对乘法结果的求值。
执行操作时有下列情况被视为语法错误:
- 除数为0
- 操作数不是该操作的正确数据类型。
- 数值运算的结果超出了其数据类型的值范围。
布尔表达式只能在确定结果值所需的范围内求值。例如,在表达式 (A>B) & (C<D)中,如果前面的条件(A>B)为FALSE,那么就能确定整个表达式 (A>B) & (C<D) 的值为FALSE,表达式(C<D)被短路,不会被计算。
函数应作为表达式的元素调用,表达式由函数名后跟带圆括号的参数列表组成。
# 5.6.1.2 运算符
# 5.6.1.3 语句
下表列出ST语言支持的语句格式,每条语句都需要以分号结束。
语句 | 样例 |
---|---|
赋值语句 | A := B; CV := CV+1; C := SIN(X); |
+=/-= 语句 | A += B; 相当于A := A + B; |
功能块调用与功能块输出使用 | CMD_TMR(IN:=%IX5, PT:=T#300ms) ; A := CMD_TMR.Q ; |
RETURN语句 | RETURN ; |
IF语句 | D := BB - 4AC ; IF D < 0.0 THEN NROOTS := 0 ; ELSIF D = 0.0 THEN NROOTS := 1 ; X1 := - B/(2.0A) ; ELSE NROOTS := 2 ; X1 := (- B + SQRT(D))/(2.0A) ; X2 := (- B - SQRT(D))/(2.0A) ; END_IF ; |
CASE语句 | TW := BCD_TO_INT(THUMBWHEEL); TW_ERROR := 0; CASE TW OF 1,5: DISPLAY := OVEN_TEMP; 2: DISPLAY := MOTOR_SPEED; 3: DISPLAY := GROSS - TARE; 4,6..10: DISPLAY := STATUS(TW - 4); ELSE DISPLAY := 0 ; TW_ERROR := 1; END_CASE; QW100 := INT_TO_BCD(DISPLAY); |
FOR语句 | J := 101 ; FOR I := 1 TO 100 BY 2 DO IF WORDS[I] = 'KEY' THEN J := I ; EXIT ; END_IF ; END_FOR ; |
WHILE语句 | J := 1;WHILE J <= 100 & WORDS[J] <> 'KEY' DO J := J+2 ; END_WHILE ; |
REPEAT语句 | J := -1 ; REPEAT J := J+2 ; UNTIL J = 101 OR WORDS[J] = 'KEY' END_REPEAT ; |
EXIT语句 | EXIT ; |
空语句 | ; |
赋值语句
赋值语句用表达式求值的结果替换单个或多个元素变量的当前值。赋值语句应在左侧包含一个变量,然后是赋值操作符“:=”,最后是要求值的表达式。例如,语句A := B ; 如果两者都是INT类型,将用变量B的当前值替换变量A的单个数据值。但是,如果A和B都是结构体类型,那么结构化变量A的所有元素的值将被变量B对应元素的当前值所替换。
赋值语句还可以用来赋值函数返回值,方法是将函数名放在ST语句赋值操作符的左侧。函数的返回值应为最后一次赋值的结果。函数定义中必须至少包含一个这样的赋值,用来确定返回值,否则函数的返回结果将是函数返回值类型的默认值。
函数和功能块控制语句
函数和功能块控制语句由调用函数或功能块语句组成。在函数或功能块结束时将控制权返回给调用实体。
函数求值应作为表达式求值的一部分使用;功能块调用语句由功能块实例名后跟带圆括号的参数列表组成。
RETURN语句可以从函数、功能块或程序中提前退出。
选择语句
选择语句包括IF语句和CASE语句。选择语句根据指定的条件选择执行它的一个(或一组)语句块。上表给出了选择语句的例子。
IF语句指定只有当关联的布尔表达式求值为1 (TRUE)时才执行一组语句。如果条件为假,则不执行任何语句,或者执行ELSE关键字后面的语句块(或者执行ELSIF关键字后面的语句,若其关联的布尔条件为真)。
CASE语句由一个表达式组成以及一个语句组列表组成,该表达式的求值为ANY_INT类型或枚举数据类型的变量,每个语句组由一个或多个整数或枚举值或整数值范围(视情况而定)标记。它指定执行第一组语句,其中一个范围包含变量的计算值。如果变量的值没有出现在任何CASE的范围内,则执行ELSE关键字后面的语句序列(如果它出现在CASE语句中)。否则,不执行任何语句序列。
循环语句
循环语句指要重复执行一组相关语句。如果循环次数可以提前确定,推荐使用FOR语句;否则,可以使用WHILE或REPEAT循环语句。
EXIT语句应用于在循环终止条件满足之前结束循环。
当EXIT语句位于嵌套循环构造中时,EXIT应该终止EXIT所在的最内层循环,也就是说,EXIT应该传递到EXIT语句之后的第一个循环结束符(END_FOR、END_WHILE或END_REPEAT)之后的下一个语句。例如,在执行下图所示的语句之后,如果布尔变量FLAG的值为0,则变量SUM的值应为15,如果FLAG=1,则为6。
SUM := 0 ; FOR I := 1 TO 3 DO FOR J := 1 TO 2 DO IF FLAG THEN EXIT ; END_IF SUM := SUM + J ; END_FOR ; SUM := SUM + I ; END_FOR ; |
---|
FOR语句表示应该重复执行一个语句序列,直到END_FOR关键字,同时将一系列值分配给FOR循环控制变量。控制变量、初始值和最终值必须是相同整数类型的表达式(例如,SINT、INT或DINT),并且不能被任何重复语句改变。FOR语句将控制变量从初始值向上或向下递增到最终值,递增量由表达式的值决定;该值默认为1。终止条件的判断在每次循环开始前进行,因此,如果初始值超过最终值,则不执行语句序列。
WHILE语句将重复执行END_WHILE关键字之前的语句序列,直到关联的布尔表达式为FALSE。如果表达式最初值为FALSE,则根本不执行这组语句。
REPEAT语句将重复执行END_REPEAT关键字之前的语句序列,直到关联的布尔表达式为FALSE。如果表达式最初值为FALSE,则这组语句也将被执行一次。
# 5.7 图形化编程
# 5.7.1 梯形图(LD)/功能块图(FBD)
# 5.7.1.1 连接线
连接线用于连接图形元素的输入输出端口,控制PLC程序的执行逻辑。在IEC61131-3 xml规范中,连接线connection的坐标点数组由[后方输入端口坐标,…,前方输出端口坐标]组成。
在IDE中,当连接元素的输入输出类型匹配时,连接线颜色为“灰色”。当类型不匹配时,连接线显示为“红色”。
当从右侧工具箱拖拽元素到画布内元素的端口上时,端口显示为“紫色”高亮,释放拖拽元素。连接输入端口时,默认创建与拖拽元素第一个输出端口的连线。连接输出端口时,默认创建与拖拽元素第一个输入端口的连接。其中,在拖拽变量元素进行自动连线时,变量会自动转为输入或输出变量。
# 5.7.1.2 标号
标号名称由一个包含字母、数字的标识符或是一个无符号十进制整数组成。通过标号对网络进行标记,在网络中使用对应网络的标号进行执行逻辑的跳转。默认在网络的左上角坐标添加标号元素,跳转执行时从网络的起始位置开始执行。
在IDE中拖拽网格时,标号的坐标信息不可修改。通过点击标号名,进行标号名称的修改。
# 5.7.1.3 节(网络)
网络内包含网络标号、注释、图形元素。通过对执行逻辑进行分组,形成一段段网络。每段网络都应具有唯一的标号。相比较于FBD功能块图,在LD梯形图中还应具有触点、线圈及左右电源线。
在IDE中添加LD/FBD程序时,会默认添加一个网格。通过右侧工具箱拖拽新网格,或在选中网格上通过右键菜单,在当前网格的上方或下方添加新网格。
# 5.7.1.4 触点
触点用来从变量读入布尔值,包括四种类型:
- 常开触点:右端 = 左端 AND 触点变量
- 常闭触点:右端 = 左端 AND NOT 触点变量
- 上升沿触点: if左端 AND “触点变量在上一个周期为FALSE” AND “触点变量在当前周期为TRUE”, 右端 = TRUE;else: 右端 = FALSE
- 下降沿触点: if左端 AND “触点变量在上一个周期为TRUE” AND “触点变量在当前周期为FALSE”, 右端 = TRUE ; else: 右端 = FALSE
# 5.7.1.5 线圈
线圈用来向可写变量输出布尔值,包括6种类型
- 普通线圈: 写入左端的布尔值
- 取反线圈:写入左端的取反值
- 置位线圈:当左端为TRUE时写入TRUE;当左端为FALSE时保持上一个周期的值
- 复位线圈:当左端为TRUE时写入FALSE;当左端为FALSE时保持上一个周期的值
- 上升沿线圈: if左端 AND “触点变量在上一个周期为FALSE” AND “触点变量在当前周期为TRUE”: 写入TRUE; else: 保持不变。
- 下降沿线圈:if左端 AND “触点变量在上一个周期为TRUE” AND “触点变量在当前周期为FALSE”: 写入TRUE; else: 保持不变
# 5.7.1.6 左右电源线
梯形图与电器控制系统中的电路图很相似,在分析梯形图的逻辑关系时,为了借用继电器电路图的分析方法,可以想象左右电源线之间有一个左正右负的直流电源电压,电源线之间有能流从左向右流动,右母线可以不画出。
# 5.7.1.7 执行控制(control flow)
在基于LD图形使用函数时,可以使用EN与ENO控制执行。只有在EN为TRUE时,函数才会被执行;如果函数完成执行,则ENO默认输出为TRUE,如果执行过程出现异常,则ENO输出为FALSE。
IEC61131-3禁止递归调用,在一个POU中不允许调用同类型的POU,这将使得PLC系统无法计算运行时所需要的内存空间。
# 5.7.1.8 函数与功能块的使用
函数具有返回值类型,在声明时为函数名赋值表示输出该返回值,在使用时不需要进行实例化,可以直接使用。部分数学运算如ADD、MAX等,可以调整输入参数的数量。
功能块在使用时,需要声明该类型的局部变量作为实例名。在编写用户自定义功能块时,添加输入输出变量作为功能块的输入输出。
通过修改优先级,可以调整元素的执行顺序。
在IDE中,工具箱中添加需要使用的功能块或函数,通过双击元素进行属性的修改。
通过修改输入属性调整ADD、MAX等数学运算输入参数的数量;通过修改执行控制,开启或关闭函数与功能块的EN、ENO使能口;通过修改优先级,调整元素的执行顺序。
# 5.7.1.9 跳转执行
为跳转符号绑定需要跳转网络的标号,在逻辑执行时,跳转到网络的起始位置开始执行。
# 5.8 C/CPP
# 5.8.1 用C/CPP语言编写的POU规范
用户在创建POU的时候,在编程语言选项中选择C/C++(CPP)选项,如下图所示,即可用C/CPP语言编写POU的主 体代码。这是对IEC61131-3标准支持的五种编程语言的扩展。
打开刚才创建的POU,如下图所示,与使用其他IEC编程语言创建POU不同的是, 用C/CPP编程语言创建的POU有 三个编辑区域。如下图所示,分别为:
- 变量声明区,这部分功能与IEC标准其他编程语言的变量定义完全相同;
- C/CPP头文件编辑区,用于包含C/CPP创建的POU代码主体所使用到的头文件,主要是当前程序主体逻辑中所使用的标准库头文件以及用户自定义其他C/CPP库,例如
#include <string>
;- 主体逻辑区,用来书写当前POU的主体逻辑代码。在主体逻辑代码中使用当前应用中的其他POU时不需要在头文件编辑区包含头文件。
# 5.8.2 C/CPP语言POU总体规范
用C/CPP编程语言创建POU,作为对IEC61131-3标准的扩展。 POU创建者需要遵守如下规范:
- POU中额外需要使用的C/CPP标准库头文件只能在头文件编辑区添加,不可以写在主体逻辑区;
- 主体逻辑区域只能编写当前POU的主体逻辑,不可以在主体逻辑区中再包含头文件,也不可以在主体逻辑区定义函数或者类,但是可以定义和使用临时变量;
- 对主体逻辑区C/CPP代码语句要求如下:
- 除了包含在'{ }'中的代码块(
if
,for
,while
等),每条单独的语句必须以分号作为结尾;if
,for
,while
等包含块语句必须包含在'{}'内,即使只有一条语句,'{}'也不可以省略,并且左大括号'{'需要与if
,for
,while
写在同一行,不可以另起一行,且左大括号'{'位于块语句行尾,对右大括号位置没有限制;例如:
/* 错误示例1,if后面的语句需要在大括号{}之内 */
if (dp.size() > 10)
localVar1 = localVar0 + 1;
/* 错误示例2,左大括号{需要与if在同一行 */
if (dp.size() > 10)
{
localVar1 = localVar0 + 1;
}
/* 正确示例,for, while, do 等语句同理 */
if (dp.size() > 10) {
localVar1 = localVar0 + 1;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 允许添加符合C/CPP格式的注释("//…"行注释或者"/*…*/"块注释),但是注释只能添加在单独行中,不允许在语句后面添加注释,也不允许"/*…*/"块注释嵌套,例如:
/* comment 1 */ // <-- 允许块注释
std::vector<int> dp; // comment 2 // <-- 禁止在语句后面添加行注释
dp.push_back(12);
// comment 3 // <-- 允许行注释
dp.push_back(12); /* comment 4 */ // <-- 禁止在语句后面添加块注释
/* Multi line comment // <-- 允许多行块注释
* Multi line comment
*/
2
3
4
5
6
7
8
- 与IEC61131-3标准对标识符、关键字等不区分大小写不同,用C/CPP语言编写的POU,是区分大小写的。例如,假设在变量定义区域定义一个名为var1的
INT
型初值为3的IEC变量, 文本代码为:var1 : INT := 3;
列表展示形式如下:
那么在CPP逻辑代码中使用此变量的时候,必须用var1作为变量名。这与使用ST/FBD/LD是不同的,这点要注意。再次声明:用C/CPP语言编写POU逻辑对变量、关键字等是区分大小写的。如
INT
是IEC数据整型,而int
是C/CPP数据整型
- 另外在C/CPP主体逻辑中,对其他IEC元素使用应该注意:
- 主体逻辑区中调用类型为函数的POU时,不论这个函数是用哪种语言创建,函数名都必须用大写形式。
- 使用DUT中用户自定义数据类型时,数据类型名,数据元素名(结构体元素或枚举值)都要使用大写形式;
- 使用IEC基础类型名也需要都使用其大写形式,如
INT
,BOOL
等;
- 其他使用规范,请参考相关章节。
# 5.8.3 在C/CPP主体逻辑区定义与使用临时变量
C/CPP临时变量是指在主体逻辑区定义的变量,变量类型可以为C/CPP数据类型的变量(如int
,bool
,或者std::string
),也可以为IEC基础变量类型(如INT
, BOOL
),例如在逻辑主体区有如下定义:
int a = 0; // 定义C/C++整型变量a,初值为0
std::string myStr; // 定义C/C++字符串类型myStr
INT b = 1; // 定义IEC基础整型变量b,并且赋初值为1
2
3
那么在使用变量a和b时,满足标准C/C++规范即可,如:
int c = a + b; // 新建变量c,将a与b的和值赋给c
a++; // 变量a自增
++b; // 变量b自增
2
3
# 5.8.4 在C/CPP主体逻辑区使用变量定义区中定义的变量
寂山软件提供了对变量进行写入、变量强制、变量监控等功能。这些变量都需要在变量定义区定义。对于ST/LD/FBD编程语言,他们不允许在主体逻辑区中定义变量,所以对于ST/LD/FBD编程语言来说,所有变量都能够监控,写入和强制。但是C/CPP语言编写的POU允许在主体逻辑区中定义临时变量,但是在主体逻辑区中定义的变量不能使用监控、写入和强制功能,另外,主体逻辑区中定义的变量只能是临时变量,在每次执行都会重新创建和初始化,执行完后变量内存被释放。建议编程者创建C/CPP语言的POU时与ST/LD/FBD语言保持一致,尽量不要在主体逻辑区定义IEC临时变量。
在变量定义区中定义的变量,根据其类型的不同,可分为如下几类:
- IEC基础类型的变量
- IEC枚举类型的变量
- IEC数组类型的变量
- IEC结构体类型的变量
- IEC功能块类型的变量
# 5.8.4.1 IEC基础类型的变量
IEC基础类型的变量指的是在变量声明区中声明的变量,且变量类型都是IEC基础数据类型(BOOL
, SINT
等),除了四种时间相关的变量(TIME
, TIME_OF_DAY
, DATE
和DATE_AND_TIME
)和字符串(STRING
)变量外,其他的基础类型的变量的操作与临时变量相同。唯一需要注意的是,C/CPP语言创建的POU是区分大小写的,例如在变量定义区中有a : INT;
这样的定义,在主体逻辑区只能使用a
(注意大小写)这样的变量作为变量名。例如下图所示: 我们创建了一个名为cppFunc
的函数,在函数中有a和b两个INT
类型的变量,函数返回两个变量差的绝对值。
关于C/CPP语言的函数定义,请参考函数定义相关章节。这里是展示如何使用IEC基础类型的变量。
# 5.8.4.2 时间相关的数据类型
对于IEC时间相关的数据类型有以下四种:
TIME
: 表示一段时间TIME_OF_DAY
:表示一天中的时间DATE
:表示日期DATE_AND_TIME
:表示日期和时间
对于时间相关类型的赋值与初始化有所不同,在变量定义区初始化时间类型的变量与IEC标准保持一致,如下图所示,定义了TIME
类型的变量varTime,并且初值为T#3d2m20s;但是在主体逻辑区给变量赋值需要使用TIME_CPP
库函数,例如varTime = TIME_CPP(1, 0, 20, 2, 0, 3);
。
关于时间类型的库函数说明如下:
TIME_CPP(int sign, double mseconds, double seconds, double minutes, double hours, double days)
: 参数sign非负值代表正时间,sign为负数代表正时间,其他参数如参数名所示,分别表示毫秒,秒,分钟,小时,天TOD_CPP(double seconds, int minutes, int hours)
: 参数分别表示秒,分,时,其中最低为秒可以为浮点数DATE_CPP(int day, int month, int year)
:参数分别表示天,月,年,其中所有值都为整数DT_CPP(double seconds, int minutes, int hours, int day, int month, int year)
:参数分别表示秒,分,时,天,月,年;其中最低为秒可以为浮点数
# 5.8.4.3 字符串类型的变量
对于字符串类型的赋值与初始化不同,在变量定义区初始化字符串类型的变量与IEC标准保持一致,所有STRING
字符串初值用单引号'
括起来。但是在C/CPP主体逻辑区中使用字符串字面量的时候,用的是双引号"
,这里需要注意一下。寂山对字符串类型的变量提供两种方法:c_str()
和size()
,分别是返回C/CPP类型的字符串和返回字符串的长度。例如,下例中,首先用std::string
类型的字符串cppStr获取str的值,然后将字符串cppStr拼接"hello, jishan!",最终将cppStr赋值给str,这里最终str的值应该为"this is a test.hello, jishan!"
# 5.8.4.4 IEC枚举类型的变量
对于枚举类型的变量,在变量定义区初始化枚举类型的变量需要符合ICE标准。在主体逻辑区使用枚举变量的值需要使用如下结构:
枚举类型::枚举值
例如,假如有如下枚举类型myEnum
定义,该枚举有三个值aa, bb和cc。
那么在变量定义区与主体逻辑区需要如下使用枚举类型的变量值。在变量定义区赋初值时,可以写myEnum#aa
或者直接用AA
(再次提醒,IEC标准是不区分大小写的),但是在主体逻辑区使用枚举类型的时候,必须使用MYENUM::AA
的形式。类型与值都需要大写。
# 5.8.4.5 IEC数组类型的变量
对于数组类型的变量,在变量定义区初始化数组类型的变量需要符合IEC标准,在主体逻辑区中使用数组类型的变量时需要使用如下结构:
数组变量.table[一维下标 - 维度下限值][二维下标 - 维度下限值]...
例如:
- 假设在DUT中有名为
myArrType
的INT
数组定义如下:
上例定义了一个二维数组
myArrType
数据类型,该数组类型包含3*4个INT
类型的数据,IEC中的下标分别为[1, 2], [1, 3], [1, 4], [1, 5], [2, 2], [2, 3], [2, 4], [2, 5], [3, 2], [3, 3], [3, 4], [3, 5]
。
- 假设在用C/CPP语言创建的POU中有这样的数组变量定义:
定义了类型为
myArrType
的数组变量arrVar
,即变量arrVar
包含12个INT
类型的数据。这里没有将数组变量初始化,在变量定义区初始化数组变量的时候遵循IEC规则即可。 3. 在C/CPP主体逻辑区中使用数组变量的时候,需要以这样的方式访问:
在DUT中定义的数组下标为
[1..3, 2..5]
的二维数组,但是在C/CPP中使用的时候,为了节约内存,将数组限制为[3][4]
的二维数组。C/CPP中下标为[0][0]
的就对应IEC中下标为[1, 2]
的元素,以此类推。所以在C/CPP中访问数组元素的时候切记下标要减去其维度对应的下限值。
- 总结:
- 访问数组元素变量的时候需要在变量后面加上
.table
(注意table是小写字母),用来表示是数组。- 访问数组元素变量的时候,元素变量对应的下标要减去定义时数组对应的下限值。
# 5.8.4.6 IEC结构体类型的变量
对于结构体类型的变量,在变量定义区初始化结构体类型的变量需要符合IEC标准,在主体逻辑区中使用结构体类型的变量时需要使用如下结构:
- 假设在DUT中有名为
myStructType
的结构体定义如下:
上例结构体中有两个元素,一个
INT
型的age,还有一个数组类型myArrType
的元素arr。 2. 假设在用C/CPP语言创建的POU中有这样的结构体变量定义:
定义了类型为
myStructType
的结构体变量structVar
,即变量structVar
包含2个元素,一个为INT
类型的age,一个是数组myArrType
类型的arr。这里没有将结构体变量初始化,在变量定义区初始化结构体变量的时候遵循IEC规则即可。
- 在C/CPP主体逻辑区中使用结构体变量的时候,需要以这样的方式访问:
- 结构体类型是以IEC标准在DUT中定义的,IEC中不区分大小写,但是在C/CPP主体逻辑区中访问结构体变量元素时,要用其大写形式(上例中是AGE元素和ARR元素)。这个例子还展示了访问结构体元素为数组的情况。
总结: 访问结构体变量的时候,结构体元素名都要大写。(是结构体元素要用大写形式,不是结构体变量)
# 5.8.4.7 IEC功能块类型的变量
对于FB类型的变量,在变量定义区初始化FB类型的变量需要符合IEC标准,在主体逻辑区中使用FB类型的变量时需要使用如下结构:
- 假设有名为
cppFB
的FB定义如下:
这个FB也是用C/CPP语言创建的,关于C/CPP语言创建FB,请参考后面章节。这个FB逻辑很简单,求两个输入
in1
和in2
的和,将结果赋值给输出变量out
。
- 假设在用C/CPP语言创建的POU中有这样的结构体变量定义:
- 在C/CPP主体逻辑区中使用FB变量的时候,需要以这样的方式访问:
- FB中输入输出变量要以方法的方式访问,并且在其FB内部定义的变量,通过实例访问的时候要用大写的形式。这里展示了先分别将FB的两个输入变量赋值,然后调用FB的执行逻辑,最后将FB的输出变量取出。
- 总结:
- 访问FB输入输出变量的时候要以方法的方式访问(在变量名后加括号)。
- 以实例访问输入输出变量时,其变量要大写形式(不管在FB定义中是否大写)
- 这里还展示了以
Body
的方式调用FB执行逻辑,具体FB调用方式,参考后面对应章节
# 5.8.5 创建与使用C/CPP函数类型POU
1. 创建C/CPP语言编写的函数
通过在寂山IDE的应用上右击->POU,即可弹出创建POU的对话框。如下图所示:
在POU名称中填写需要创建的函数名(cppFunc),将POU类型选择为函数,编程语言选择C/CPP,返回值类型选择实际需要的返回值类型,这里以INT
。点击确认即创建了函数cppFunc,返回值为INT
在变量定义区定义函数需要的接口变量,在主体逻辑区中书写函数的逻辑即可。需要注意的是,与IEC标准的其他编程语言一样,在C/CPP语言编写的函数中,函数名也即函数的返回值,需要将函数名赋值,并且函数名属于IEC元素,所以需要大写。
例如下图中,函数有两个输入参数,参数名为a和b,这个函数返回两个输入参数差的绝对值。
- 总结:
- 函数名即为函数的返回值,函数名需要大写,需要给函数名赋值;
- C/CPP编写的函数中,会自动添加EN/ENO执行控制逻辑,用户在创建函数的时候不需要考虑
- 函数中的变量操作,参考上面
在C/CPP主体逻辑区使用变量定义区中定义的变量
章节
2. 在C/CPP语言编写的POU中调用函数
以C/CPP语言在主体逻辑中调用函数有两种方式:
- 包含执行控制EN/ENO参数的形式
- 不包含执行控制EN/ENO参数形式
例如,若在其他POU中调用上面创建的函数时,需要使用下面两种形式:
LocalVar1 = CPPFUNC(true, nullptr, 1, 5); // 包含执行控制EN/ENO参数的形式
LocalVar2 = CPPFUNC(1, 5); // 没有执行控制EN/ENO参数形式
2
前提是在变量定义区,提前定义了INT
类型的变量LocalVar1
和LocalVar2
;
- 这里包含执行控制逻辑的函数调用中,添加了两个参数
BOOL EN
和BOOL* ENO
,用来控制执行逻辑,只有EN为true/TRUE时才执行函数逻辑,并且将是否执行正确的标志放到ENO中,这里我对ENO不感兴趣,就传递nullptr
指针即可。若对ENO感兴趣,需要传递BOOL
类型变量的地址给被调用函数。
- 在没有包含执行控制逻辑的函数调用时,与直接书写C/CPP函数调用一致,直接传递函数的参数即可。
这里给的样例中,函数创建只有输入变量。当函数创建的时候有输出变量时,以C/CPP语言调用函数的时候需要将函数的输出变量(参数)也以传递地址的方式传递给被调用函数。
# 5.8.6 创建与使用C/CPP功能块类型的POU
1. 创建C/CPP语言编写的功能块 以C/CPP语言创建功能块的时,同上创建函数的步骤,在弹出的对话框中,将POU类型选择为功能块,并且在POU名称中输入功能块名即可创建功能块。下例创建了功能块cppFB,如下:
上例中,功能块有两个输入参数in1和in2,一个输出参数out,功能块的功能是将两个输入的和赋值给输出。
2. 在C/CPP语言编写的POU中使用功能块
C/CPP语言编写的POU中使用功能块时,首先需要定义功能块类型的变量。在调用功能块逻辑的时候,需要先将功能块的输入变量赋值,然后调用功能块的Body()
方法,最后使用功能块的输出变量。如IEC功能块类型的变量
章节所示那样。
只有功能块的输入变量、输出变量和输入输出变量能够在功能块外部访问。
若功能块中包含输入输出变量,在使用的时候,需要先将输入输出变量赋值,然后再将调用功能块逻辑,最后将输入输出变量再赋给当前变量来实现输入输出逻辑。例如,假设功能块cppFB
中还有一个INT
类型的输入输出变量inouVar
,那么在使用的时候就需要以下面的方式调用。
fbVar.IN1() = 12;
fbVar.IN2() = 12;
// 调用前给输入输出变量赋值
fbVar.INOUTVAR() = localVar1;
fbVar.Body();
// 调用后将输入输出变量赋给变量
localVar1 = fbVar.INOUTVAR();
INT result = fbVar.OUT();
2
3
4
5
6
7
8
注意功能块的变量的操作权限如下:
使用方式 | 功能块内部 | 功能块外部 |
---|---|---|
INPUT变量 读 | 允许 | 不允许 |
INPUT变量 写 | 不允许 | 允许 |
OUTPUT变量 读 | 允许 | 允许 |
OUTPUT变量 写 | 允许 | 不允许 |
INOUT变量 读 | 允许 | 允许 |
INOUT变量 写 | 允许 | 允许 |
- 功能块的输入变量只能在功能块外部进行赋值,不能在功能块内部改写输入变量的值
- 功能块的输出变量在功能块外部只能进行读,不可以在功能块外部改写功能块输出变量的值
- 这些功能在IEC标准编程语言编译的时候都会做编译检查,但是在用C/CPP语言编写的POU中,没有做相关的编译检查,也就是说,原则上用户可以用C/CPP语言在功能块外部改写功能块输出的值。或者在C/CPP创建的功能块内部改写输入变量的值。这样做是没有意义的,会破坏功能块的实际功能,用户在用C/CPP语言创建或者使用功能块的时候需要注意。逻辑正确性需要使用者自己保证。
# 5.8.7 创建与使用C/CPP程序类型的POU
以C/CPP语言创建程序的时,同上创建功能块的步骤,唯一不同的就是将POU类型改为程序。
程序的使用只能在任务中将程序实例化,我们暂时不允许程序将相互调用。
# 5.9 顺序功能图语言(SFC)
IEC 61131-3 标准中,顺序功能图(Sequential Function Chart,SFC)是作为编程语言的公用元素定义的。它是采用文字叙述和图形符号相结合的方法描述顺序控制系统的过程、功能和特性的一种编程方法。它既可作为文本类编程语言,也可作为图形类编程语言,但通常将它归为图形类编程语言。因此,通常讲IEC 61131-3 有三种图形类编程语言。
SFC编辑器如下图所示
SFC元素提供了一种将可编程控制器程序组织单元(POU)划分为一组由定向线相互连接的步和转换的方法。与每个步相关联的是一组动作,与每个转换相关联的是一个转换条件。
由于SFC元素需要存储状态信息,因此可以使用这些元素构建的程序组织单元只有功能块和程序。
# 5.9.1 SFC组件
SFC的元件包括步、转换、动作块、跳转、发散分支、收敛分支。
SFC的各个元件形状以及特征如下表所示。
名称 | 图形元素 |
---|---|
步 | 初始步 普通步 |
转换 | |
动作快 | |
跳转 | |
发散分支 | 选择发散 同步发散 |
收敛分支 | 选择收敛 同步收敛 |
# 5.9.2 步
步表示整个工业过程中的某个主要功能。它可以是特定的时间、特定的阶段或者是几个设备执行的动作。程序组织单元的输入、输出行为遵循由步的相关执行动作确定。步只有两种状态:激活和未激活。任何时刻,POU的状态由激活的步的集合及其内部变量和输出变量的值定义。
初始步
每个SFC语言的POU均以初始步开始。POU运行总是从初始步依次往下演变运行。 初始步是SFC语言POU必不可少的元件,不可删除。
初始步如下图所示。
普通步
SFC语言的POU支持多个普通步的组态,普通步可以增加删除,每个普通步可以增加入口动作,出口动作,关联步动作以及关联工程管理树的动作。
普通步如下图所示。
激活步
当步激活时,在线可以看到该步的边框颜色变为红色,当前步关联的动作被调用执行。
如下图所示。
激活步起始时间为步名.T(TIME
):当步刚被激活时,步获得的当前起始时间。
激活步运行时间为步名.T(TIME
): 当步刚被激活时,步获得当前起始时间(步名.T),并且步的运行时间开始累积,当运行到该步解除激活状态时,步的运行时间复位为T#0s。如上图所示,step1步处于活动状态,运行了T#2s,则Step1.T:=T#2s。
步下一状态为步名.X(BOOL
):步活动标志,当值为TRUE时,表明该步下一调度周期为活动状态,当值为FALSE
,表明该步下一调度周期为非活动状态。
步当前状态为步名.X(BOOL
):步活动标志,当值为TRUE时,表明该步当前调度周期为活动状态,当值为FALSE
,表明该步当前调度周期为非活动状态,如上图所示Step1.X:=TRUE。
步名称、步运行时间、步状态只在当前 POU 有效,不能在其它 POU 中引用。
步名约束规则:
- 步名必须满足变量名的定义规则
- 步名不能与该SFC POU变量区的变量名相同
- 步名不能与动作重名
# 5.9.3 转换
在步和步之间的有所谓的转换。转换条件的值必须是BOOL
值(TRUE
或FALSE
)。因此,它可以是一个BOOL
变量、BOOL
地址或者BOOL
常量。只有当步的转换条件为TRUE
时,步的转换才进行。
各个转移条件如下图所示。
转移约束规则:
- 转换已经关联了引用 POU,则该 SFC 转换引用 POU 定义的变量区不能定义转移名称的变量
- 转换已经关联了POU,则该SFC POU不能在工程管理树上添加同转移名称的动作
- 该SFC POU在工程管理树上添加同转换名称的动作,则该转换不能关联POU,SFC POU也不能定义同转换名称的变量
- 多个转换同名(不区分大小写)的情况下,如果关联POU,则关联的是同一个POU
# 5.9.4 动作块
动作块包括限定符和动作名,限定符包括时间限制符和非时间限定符,限定符决定了这个动作名关联动作的执行方式,动作名关联的动作为该SFC POU树上添加的动作。
动作块如下图所示。
SFC 限定符如下表所示:
限定符 | 英文含义 | 说明 |
---|---|---|
- | Non-stored (null qualifier) | 只要步激活动作就激活,步失效就失效 |
N | Non-stored | 只要步激活动作就激活,步失效就失效 |
R | Overriding Reset | 只要步激活就复位,步失效状态保持,直至下次激活 |
S | Set (Stored) | 只要步激活就置位,步失效状态保持,直至下次激活 |
L | time Limited | 当步激活时动作启动,动作会继续直到步不活跃或设定时间已到 |
D | time Delayed | 激活后延时器启动。延时后如果步仍激活,动作开始直到步失效 |
P | Pulse | 当步首次激活,只执行一个周期后关闭 |
SD | Stored and time Delayed | 延时后动作开始,它会继续保持直到重启 |
DS | Delayed and Stored | 具体设定的事件延时之后如果步仍激活,动作开始并继续保持,直到重启 |
SL | Stored and time Limited | 步激活后动作开始,并继续一定时间或重启 |
P1 | Pulse (rising edge) | 进入步时执行,且只执行一遍 |
P0 | Pulse (falling edge) | 退出步时执行,且只执行一遍 |
注意:限定符L、D、SD、DS和SL应具有TIME类型的相关持续时间。
动作名为工程管理树该SFC POU底下挂的动作,如下图所示。
通过如下步骤可以实现SFC POU下挂动作
- SFC 语言的 POU 右键
- 在弹出的右键菜单中,选择【添加动作】
- 弹出添加动作对话框
- 命名动作名称,点击【确定】
动作支持ST、FBD、LD、SFC 以及 CPP 语言。
# 5.9.5 跳转
跳转实际上是连接到有跳转符号的步或者连接到有标签的并行分支的操作,可以通过该元件使SFC实现循环,SFC语言编辑的 POU至少包含一个跳转。 跳转是一种程序执行时,可以无条件转到跳转关联的标签处执行,可以从某一步直接转移到当前分支内部或外部的其他步或者带标签的并行分支上。跳转用来代替一个步,并且由它所在位置的转移来执行,转移条件满足时,跳转才执行。
跳转元素如下图所示。
# 5.9.6 发散分支
SFC 发散分支同步发散分支和选择发散分支。
选择发散分支
执行步n的动作输出A时,选择转换条件b或者c中最先成立的条件的步(n+1或者n+2),执行改步的动作 转换条件同时成立时,左侧的转换条件优先,并且对步n进行失效处理 转换后,依次执行所选列的各个步,直至进行合并位置
同步发散分支
执行步n的动作输出A时,如果转换条件b成立,则同时激活步n+1和步n+3,失效步n 转换条件c成立时,激活步n+2, 转换条件d成立时激活步n+4
# 5.9.7 收敛分支
SFC 收敛分支并行收敛分支和选择收敛分支。
选择收敛分支
如果分支中执行列的转换条件b或者c成立,则对执行步n或者n+1进行失效处理,激活步n+2
同步收敛分支
执行步n的动作输出A,步n+1的动作输出B时,如果转换条件b和转换条件c同时成立,则激活等待步,失效步n和步n+1 等待步是用于使并行处理的步同步的步,通过将并行处理的所有的步转换至等待步,对转换条件d进行检查,若转换条件d成立,则激活步n+2 等待步被作为虚拟步,即使没有动作输出也没有关系
# 5.9.8 步的演变规则
SFC 编程语言中,步的进展和动作的执行、转换实现有关。步的演变(进展)规则如下:
- 初始状态是由包含网络的程序或功能块的初始化在激活状态的初始步确定的。
- 当该状态的所有前级步都是活动步时,该转换是使能转换。只有当是能转换的转换条件为
TRUE
时,才发生转换,从而实现步的演变- 实现转换使连接到该转换的前级步成为非活动步,并使所有连接该转换的后续步成为活动步
- 同时使多个步成为活动部的序列称为同步序列。他们的每个求值是独立的,为此,常用水平线表示同步序列的分支和合并。
- 在SFC元件连接中应始终保持步/转换和转换/步, 即:
- 两个步永远不能直接相连;它们总是由转换分开。
- 两个转换永远不能直接连接在一起;他们之间总是隔着步。
- 步的演变从初始步开始。因此,初始步在程序执行开始应是活动步。
- 与步连接的动作控制功能块只有在步的活动步时才被执行,动作的执行根据动作控制块的输出Q确定,当Q为TRUE时,这些动作被执行;当Q为FALSE时,这些动作不被执行。
- 在动作的执行过程中,对后续转换进行求值,当转换条件满足时,发生转换的实现,并实现步的演变,从而使步根据SFC的程序不断演变。
- 选择序列的分支,有多个转换条件,他们是有优先级的。当不设置优先级时,转换条件的判别是从左到右进行的。当设置优先级时,数字表示优先级,数据越小优先级越高。
- 步激活需要一定时间,同样,转换的实现也需要一定时间。不同PLC产品所需的时间不同。因此,当活动步连接的动作对延时限定功能时,可能出现在活动步要启动动作时,发生了实现转换的过程,从而出现不活动的步中还有正在执行的动作这种现象,在设计时应考虑。
← 4. 设备管理与组态 6. 编写IoT应用 →