在开发可编程序控制器(PLC)的早期阶段没有统一的标准,这种编程语言不统一的情况,给用户带来极大的不方便,近几年由于自动化系统的发展,IEC的第七个工作组制定了IEC(International Electro technical Commission) 61131-3标准,IEC61131-3本身只作为PLC的编程指导,而不是强制的规则。它是IEC 61131国际标准的第三部分,是第一个为工业自动化控制系统的软件设计提供标准化编程语言的国际标准。
IEC61131-3的优势:
- 国际上承认的标准
- 逐步的在不远的将来所有供应商将采用它
- 统一的结构, 语言和操作处理方式将来自所有供应商
- 它节省你的时间
- 统一的软件模式和数据类型概念
- 对来自不同的PLC类型你只需学习一次
- 减少了误解和错误
- 标准的函数和功能块
- 测试软件的可重复使用性
- 支持安全和高质量编程设计
- 轻松和舒适的结构
- 数据类型避免了编程错误
- 对每个问题提供了最佳编程语言
- 一致的5种编程语言规范
- 文本和图形语言
- 高级语言的可用性
- 不同语言混合编程
一、IEC 61131 软件模型
配置Configuration
- 最高层,描述了整个控制系统的架构。
- 一个配置可被比作一个可编程控制器系统。
- 在一个配置里可以定义一个或几个资源。
- 在TwinCAT: 就是中 一个或多个PLC
资源Resource
- 一个配置有一个或多个资源
- “实质”PLC: 自己的全局变量, POU, 任务等.
- 在TwinCAT: 就是一个 PLC 运行核 (Runtime)
任务Task
- 对一个相关程序的周期的执行,实施控制单元
- 一个资源有一个或多个任务
- 有优先级时序排列(0~3,共四个级别)
- 优先权和循环时间
- 任务调用一个或多个程序
- 任务决定了所关联程序的时间调度。
程序Program
- 程序是根据控制器过程的需要,包含了函数和功能块的一个逻辑组合的POU。
- 由任务调用程序
- 可以调用: 功能块(Functon Block)、函数(Function)和其它程序(Program)。
- 局部变量: 静态, 即局部变量的数据可以在下个周期使用。
- 输入: 一般是没有输入的变量, 在VAR_INPUT可以定义输入变量的。
- 输出: 一般是没有输出的变量, 在VAR_OUTPUT可以定义输出变量的。
- 输入输出型变量:可以在VAR_IN_OUT定义的。
- 监视: 在线状态下变量的值是实时可见的。
功能块(FB) Function Block
- 由程序或者其他功能块调用
- 可以调用: 功能块(Functon Block)和函数(Function)
- FB 有运算法则:每次FB被执行,就是运行一段程序编码
- 局部变量: 静态, 即局部变量数据可以在下个运行周期使用,多个功能块 中,每个FB都有自己的局部变量。
- 输入: 多个输入,在VAR_INPUT中定义。
- 输出: 多个输出,在VAR_OUTPUT中定义。
- 输入输出:多个输入输出变量。在VAR_IN_OUT中定义。
- 监视:在线状态下对于每个指定的功能块,局部变量是可见的。
函数Function
- 由程序、功能块和其他函数调用
- 可以调用:函数(Function)
- 函数有运算法则:每次函数被执行,就是运行一段程序编码
- 局部变量: 临时的, 即局部变量数据仅在当前function 执行时可以使用, 之后这个数据区就被用于其它functions。
- 输入: 多个输入变量,在VAR_INPUT中定义。
- 输出:只有1个!
- 输入输出:可以有多个输入输出型变量,在 VAR_IN_OUT中定义。
- 监视: 在线状态下仅能看到 “???”。使用断点可以监视其局部变量值。
内存地址 | 输出变量 | 调用 | |
功能块 | 全部数据分配内存地址 | 多个输出变量或没有输出变量 | 可调用功能块或函数 |
函数 | 没有指定的内存分配地址 | 一个输出变量 | 可调用函数,但不能调用功能块 |
二、标识符、关键字和注释
2.1标识符
- 由字母(A到Z,a到z)、数字(0到9)或下划线组成
- 首字符必须是字母或者下划线
- 字母不分大小写(abc = Abc = ABC)
- 不可以使用特殊字符、空格、连续的下划线。
注: 如有可能,避免定义由下划线,struct,enum,或者command开始的标识符。虽然这些是有效的标识符,当你下载技术包时可能导致出现错误。在基础系统和在技术包 中命令词语,参数或数据类型以这些字符开始。
2.2关键字
语法关键字区分大小写,总是大写字母
例如:
- 标准的逻辑运算操作: AND,OR,NOT…
- 标准的数据类型关键字: BOOL,INT,REAL…
- 程序和功能块的关键字: FUNCTION,FUNCTION_BLOCK,PROGRAM
- 结构体数据相关的关键字:TYPE,STRUCT
注:关键字是不可以声明做变量名的。
2.3注释
- 线注释:“ // ” ,单行且放在行尾。
- 块注释:由“(*”开始,由“*)”结束,可以多行。
- 注释可以放在行首,行尾,也可以放在程序语句中间,但是不能放在字符串中间。
- 注释不允许嵌套。
二、数据类型
2.1 基本数据类型
数据类型有整数, 浮点数, 位和位组, 时间和日期值 和字符串
2.1.1基本数据类型的数据范围和大小
数据类型 | 数据范围 | 数据大小 | |||
类型 | 类型名 | 描述 | 最小值 | 最大值 | |
位和位组数据类型 | BOOL | 位 | False | True | 1bit |
BYTE | 字节 | 0 | 255 | 8bit | |
WORD | 字 | 0 | 65535 | 16bit | |
DWORD | 双字 | 0 | 4294967295 | 32bit | |
整型 | SINT | 短整型 | -127 | 127 | 8bit |
USINT | 无符号短整型 | 0 | 255 | 8bit | |
INT | 整型 | -32768 | 32767 | 16bit | |
UINT | 无符号整型 | 0 | 65535 | 16bit | |
DINT | 双整型 | -2147483648 | 2147483647 | 32bit | |
UDINT | 无符号双整型 | 0 | 4294967295 | 32bit | |
LINT | 长整型 | -2^63 | 2^63-1 | 64bit | |
ULINT | 无符号长整型 | 0 | 2^64-1 | 64bit | |
日期和时间表示 | TIME_OF_DAY | 时间 | TOD#00:00:00 | TOD#23:59:59 | 32bit |
DATE | 日期 | D#1970-01-01 | D#2106-02-07 | 32bit | |
DATE_AND_TIME | 日期和时间 | DT#1970-01-01- 00:00:00 | DT#2106-02-07- 06:28:15 | 32bit | |
时间数据类型 | TIME | 时间 | T#0s | T#49D17H2M47S295MS | 32bit |
浮点数类型 | REAL | 实数 | -3.4*1038 | 3.4*1038 | 32bit |
LREAL | 长实数 | -1.7*10308 | 1.7*10308 | 64bit | |
字符串数据类型 | STRING | 字符串 | ASCII码的字符串数据类型,一个字符占一个BYTE位。最 多可以有255个字符。 | ||
WSTRING | Unicode编码的字符串,一般情况下一个字符占两个BYTE 位。它本身对字符串长度没有限制。 |
注:声明变量时需注意不要超过数据类型的大小。
2.1.2基本数据类型的表示
语法:<类型>#<数值>
<类型>指定所需的数据类型;可能 BOOL, SINT, USINT, BYTE, INT, UINT,
WORD, DINT, UDINT, DWORD, REAL, LREAL 该类型必须是书面的大写字母。
举例:
2#0101,10#123,16#FF,BOOL#TURE,BYTE#12,INT#255,T#12h23m30s90ms,
D#2023-06-16
整型
- 允许十进制、十六进制(16#)、八进制(8#) 和 二进制(2#) 表示。
- 可用下划线(_) 作单元分隔
十进制 | 十六进制 | 八进制 | 二进制 |
-32768 | 16#8001 | 8#77777 | 2#1000_0000_0000_0001 |
123 | 16#7b | 8#173 | 2#1111011 |
浮点数(实数) 表示
- 十进制小数或指数表示
十进制 | 指数表示 |
1230 | 1.23e3 |
0.3333 | 3.333e-1 |
时间数据类型
- 在文字前加TIME#, t# 或 T#
- 允许溢出(例如25 小时)
- 使用d 表示天, h 表示小时, m 表示分, s 表示秒和 ms 表示毫 秒
- 可使用下划线(_) 作单元分隔
- T#2d_26h_4m_12s_123ms
日期和时间数据类型
- 用DATE#或D# 表示日期
- 用TIME_OF_DAY# 或 TOD# 表示一天中的时间
- 用DATE_AND_TIME# 或 DT# 表示日期和时间
- 日期:D#1998-12-07 表示7th July 1998
- 一天中的时间: TOD#12:00:00.123
- 日期和时间: DT#1998-12-07-12:00:00.123
字符串
- 用''引括字符
- 用$插到特殊字符前(换行$L ,制表$T等)
- 字符串表示: 'this is a line feed character $L'
- 空字符串: ''
位和位组
- TRUE 或 1
- FALSE 或 0
- 用十进制,十六进制, 八进制或二进制表示
- 45054、16#AFFE、2#1010_1111_1111_1110
2.2 用户定义的数据类型(DUT)
- 根据基本数据类型或其它用户定义的数据类型建立自己的数据类型
- 可通过关键字TYPE…END_TYPE来声明结构体数据、共用体数据和一些自定义数据。
TwinCAT3提供默认的文件夹分组,如下图所示:
- DUTs :用户自定义数据类型
- GVLs:全局变量
- POUs:程序、功能块、函数
- VISUs:可视化界面
声明步骤为:
- 右击DUTs,
- 点击Add,
- 选择DUT出现Creat type对话框。
- 选择相应的数据结构进行编程。
2.2.1 结构体数据(Struct)
对于同一组的变量,可以用结构体数据类型进行定义,每个元素的数据类型可以不同,结构体可以做成需要的数据类型,并且结构体数据可以方便进行数据的修改,以方便日后的使用。
1. 声明方式
TYPE 标识符 :
STRUCT
变量名1 : 数据类型 [:= 初值1];
变量名2 : 数据类型 [:= 初值2];
...
变量名n : 数据类型 [:= 初值n];
END_STRUCT
END_TYPE
2. 调用
-
调用前需要实例化
-
实例化语法:变量名 : 结构体名;
3.例子
结构体S1定义:
TYPE S1:
STRUCT
Var1 : INT;
Var2 : WORD := 16#AFA1;
Var3 : BYTE := 16#FF;
END_STRUCT
END_TYPE
MAIN函数
VAR
myS1 : S1;
END_VAR
myS1.Var1 := -4;
myS1.Var3 := 16#22;
调试结果:
(1)初始状态
(2)执行第一条语句
(3)执行第二条语句
2.2.2 枚举变量(Enumeration)
枚举变量是多个有固定数值的一类变量的集合。
当程序中有多个固定值输入,若将其声明为枚举变量后,则可以使用助记词帮助了解这些固定值输入的用处,这比看数字清楚很多。且在数据修改时,只需在声明枚举变量处改动即可对于多处使用到的数据进行修改。
1. 声明方式:
TYPE 枚举名 :
(
成员名1 [:= 初值1],
成员名2 [:= 初值2],
...
成员名n [:= 初值n]
);
END_TYPE
注:如果没有设置初值,各成员的初值默认为前一个成员的初值加1,第一个成员默认为0。
2. 调用
-
调用前需要实例化
-
实例化语法:变量名 : 枚举名;
3.例子
注:枚举变量的创建都会自动加上属性编译:{attribute 'qualified_only'} 其含义是为了区分各个枚举变量的成员名之间的重名问题,因此调用枚举变量的成员时需要在成员名前加上枚举变量名。
调试结果:
(1)初始状态
(2)执行第一条语句
(3)执行所有语句
2.2.3 别名(Alias)
用别名(Alias)来为变量、函数等创建一个新的名称。这可以使代码更易读,也可以用于创建引用或者简化一些表达式。
1. 声明方式:
TYPE
别名 : 基本数据类型[:= initialization ] ;
END_TYPE
或:
TYPE
别名 : 用户定义数据类型[:= initialization ] ;
END_TYPE
2. 调用
-
调用前需要实例化
-
实例化语法:变量名 : 别名;
3.例子
TYPE
R1 : REAL;
R2 : R1; //可以用派生的数据类型标识符声明变量
END_TYPE
2.2.4 共用体(Union)
共用体中的多个数据共用一段内存地址,一旦其中一个变量数值发生了变化,其他变量会一致变化。
1、声明方式:
TYPE 共用体名 :
UNION
变量声明
END_UNION
END_TYPE
2. 调用
-
调用前需要实例化
-
实例化语法:变量名 : 共用体名;
3.例子
调试结果:
注:sVar和aVar变量共用一段内存地址,在使用过程中,这段地址中的字符串可以以字符串方式访问又可以字符数组方式访问,简化了对数据的处理方式并且节省内存。
三、变量声明
- 具有基本类型或用户定义类型的局部和全局变量
- 用一个VAR..END_VAR 结构框架声明变量
- 同样数据类型的变量用逗号列出
- 运行过程中变量是允许变化的
- 变量的地址一般是不需要知道的
- 如果指定初始值,变量值默认为0,时间数据类型和枚举数据类型除外。
3.1 基本数据类型
1、声明方式
变量名 : 数据类型 [:= 初值];
2、例子
VAR
a : BOOL;
b,c : INT := 1;
d : STRING := ‘this isastring‘;;
e : REAL := 1.1;
f,g : REAL;
END_VAR
3.2 数组
1、一维数组声明方式:
变量名 : ARRAY[M..N] OF 数据类型[:= 初值];
阵列限制[M..N]
- 声明了索引值的最小值M和最大值N
- 阵列限制必须用两个句号隔开
- 全部的索引声明须在方括号中
- 索引可以为一个数据类型DINT(或转化为DINT)的整数值
2、二维数组声明方式
变量名 : ARRAY[M..N] OF 一维数组变量名;
3、避免数组超限
使 用数组变量编程时,可以使用TwinCAT3提供的Checkbounds 这个功能块来防止 访问数组变量时,索引值超出数组长度的情况。功能块的使用非常简单,只要按 下述方式添加到程序中即可:
出现以下函数即可,无需调用,它会自动识别是否有数组索引超限,如果有超限的将数组索引指向数组最后一位。
以下列循环为例,数组a索引范围为[0,2],但循环存在a[3]。
运行结果如下:
当i = 3时,实际运行a[2]=i;
4、例子
VAR
a : ARRAY[0..10] OF BOOL;
b : ARRAY[1..10] OF INT := 1, 2, 2, 4,5, 6, 7, 8, 9,10;
c : ARRAY[0..100] OF DWORD;
d : ARRAY[0..1] OF DATE;
e : ARRAY[0..17] OF REAL;
END_VAR
a[1]:=1;
b[2]:=100;
VAR
a : ARRAY[1..3] OF INT;
b : ARRAY[1..4] OF a;
END_VAR
b[4][3]:=9;
3.3 变量作用域
关键词 | 意义 | 声明 |
VAR | 暂时或静态变量 | 程序 功能 功能块 表达式 |
VAR_GLOBAL | 全局变量 | |
VAR_IN_OUT | 输入/输出变量 POU直接访问变量,也可直接修改。 | 功能 功能块 表达式 |
VAR_INPUT | 输入变量 数值 是外部赋予,不能在POU里 面改变这个数值。 | 功能 功能块 表达式 |
VAR-OUTPUT | 输出变量 数值是从功能块传递的。 | 功能块 |
VAR_TEMP | 临时变量 | 程序 功能块 |
3.3.1 局部变量
- 变量在一个POU(程序,功能块或函数)中的变量声明区域VAR声明
- 只能在这个POU中访问。
- 声明在在程序和功能块(FB)中相应关键字区域。
- VAR...END_VAR中声明本地变量
- VAR_INPUT...END_VAR中声明输入型变量。
- VAR_OUTPUT...END_VAR中声明输出型变量。
- VAR_IN_OUT...END_VAR中声明输入输出型变量,这里的变量即是输入也是输出。
例:
VAR
I AT%I* : BYTE;
END_VAR
VAR_INPUT
a : BYTE;
END_VAR
VAR_OUTPUT
b : INT;
END_VAR
VAR_IN_OUT
c : BYTE;
END_VAR
3.3.2 全局变量
- 变量在一个资源(PLC 运行核-runtime)中定义说明
- 每个POU均可访问
1、声明方式
全局变量在TwinCAT3中变量声明的方式如下:
- 在GVLs上右击
- 点击Add
- 选择Global Variable List
- 在这里个文件的 VAR_GLOBAL 中声明对应的全局变量
2、例子:
VAR_GLOBAL
a : BOOL;
b : INT;
c : BYTE;
END_VAR
注:在 TC3.1.4020 版本开始,全局变量的创建都会自动加上属性编译:{attribute 'qualified_only'} 其含义是为了区分全局变量与各局部变量重名的问题,因此现在调用全局变量都 需要在变量名前加上全局变量的命名空间。
例如:GVL.a,GVL.b,GVL.c,但如果 感觉这种方式不习惯或者麻烦,也可以把属性编译行删除,就可以回到以前的直 接调用的方式了。
3.4 变量属性
3.4.1 常数
常量在系统初始化完成后,便只能进行读操作,不能进行赋值。
1、声明方式
常量的声明方式是在变量声明区域关键字后加相应的关键字CONSTANT。
VAR CONSTANT
标识符 : 数据类型 := 初值;
END_VAR
注:常量必须有初始值。
2、例子
VAR CONSTANT
PI : REAL:=3.1415926;
END_VAR
3.4.2 断电保持型数据
断电保持型变量是将变量值保存到本地硬盘中,设备重新上电后自动进行读取。
1、声明方式1
断电保持型数据的声明方式是在变量声明区域关键字后加相应的关键字PERSISTENT。
VAR PERSISTENT
标识符 : 数据类型 [:= 初值];
END_VAR
2、例子
VAR PERSISTENT
iount : INT;
END_VAR
注:定义PERSISTENT 类型变量需要配合UPS才可以保存变量,因为此类变量只 有当PLC停止的时候才会进行保存,不然断电重启还是会丢失。
3、声明方式2
调用Tc2_Utilities.LIB 库文件中的写掉电保持数据功能块FB_WritePersistentData。
3.4.3 指定内存地址
具有固定地址的变量
1、自动分配内存地址
VAR
变量名1 : AT%I* 数据类型; //输入
变量名2 : AT%I* 数据类型; //输入
变量名3 : AT%Q* 数据类型; //输出
变量名4 : AT%Q* 数据类型; //输出
END_VAR
输入输出变量通过“AT%I(Q)*”来进行声明。
- AT% 是关键字
- I 表示输入
- Q 表示输出
- * 表示自动分配一个内存地址给这个变量。也可以指定一个内存地址给这个变量。
注:只有加上“AT%I(Q)*”,TwinCAT3中的Instance中才能识别到!
2、指定内存地址
语法: %<存储器区前缀><长度前缀><数字1>.<数字2>
- %:表示操作数。
- 存储器区前缀: 表示该操作数的存储位置区域:
- I :输入区域, 物理输入;
- Q:输出区域,物理输出;
- M:内存区域。
- 长度前缀: 表示操作数的长度
- X: Bit,一位,X类型变量类型:BOOL
- B: Byte,一个字节,B类型变量类型:BYTE,SINT,USINT,BOOL
- W: Word,一个字,W类型变量类型:WORD,INT,UINT
- D: Double Word,两个字,D类型变量类型:DWORD,DINT,UDINT、REAL
- 数字1:变量对应内存的偏移地址
- 数字2:作为BOOL或者word等类型时,偏移地址后的第几位
例如:%IX0.0表示输入存储区域的二进制数第0个字节第0个bit位。
四、函数,功能块和程序
函数(FC)是指无静态数据的编码块。当退出功能时所有的本地变量丢失数值,下次再调用功能时再重新启动。
功能块(FB)是指带静态数据的编码块。FB 有内存,所以在用户程序中可以随时访问输出参数。本地变量在调用之间保持原值。
程序与FB类似,但是没有参数。然而,却可以指定执行级别和任务。
FC 和FB有着可以重新使用的优势。
4.1 函数(FC)
4.1.1 函数声明
FUNCTION 函数名 : 数据类型
(* 变量声明 *)
VAR_INPUT
变量声明
END_VAR
(* ST编程中的代码体 *)
代码
END_FUNCTION
- 如果函数没有返回值,则输入VOID为数据类型。
- 内部数据不存储,所有的本地变量(VAR ... END_VAR)在FC中都是临时的,当FC结束时,它们的数值将丢失。下次调用FC时,将重新启动。
- 几个输入值
- 一个输出值
- 用户定义的函数可以用各种语言编辑代码(除了SFC)
- 函数名必须和返回值的名称相同
例:
FUNCTION Average : REAL //返回值的数据类型
VAR_INPUT
IN1, IN2 : REAL ;
END_VAR
Average:= (IN1 + IN2)/2; //返回值名 = 函数名
END_FUNCTION
4.1.2 函数的调用
变量名 := 函数名(输入参数1 := 值1,输入参数2 := 值2,...,输入参数n := 值n,
输入输出参数1 := 值1,输入输出参数2 := 值2,...,输入输出参数n := 值n);
注:没有输出参数
4.2 功能块
- 设立输入, 输出和内部变量
- 运算法则建立新的输出和内部变量
- 参数被保持到下次执行(在存储器中),关闭FB时,静态数据(VAR ... END_VAR)保持原值。
- 关闭FB时,临时变量(VAR_TEMP ... END_VAR)数值丢失。
- 功能块例程是一个结构, 包含所有输入, 输出和内部变量
- 一个功能块FB 允许建立多个实例
- 在FB 的外部只有输入, 输出和输入/输出变量传递参数
- 在其它FB或程序中, 以不同的编程语言使用FB调用
- 一个FB 例程在调用的FB/程序中是一个变量或全局变量
- FB例程可以是对其它FB/程序的输入变量
4.2.1 功能块声明
FUNCTION_BLOCK 功能块名
(* 输入 *)
VAR_INPUT
变量声明
END_VAR
(* 输出 *)
VAR_OUPUT
变量声明
END_VAR
(* ST编程中的代码体 *)
代码
END_FUNCTION_BLOCK
例:
FUNCTION_BLOCK Counter
VAR_INPUT
Mode : INT; (* 0 = 重置,1 = 计数*)
END_VAR
VAR_OUPUT
Out : INT; (*实际计数器值*)
END_VAR
IF Mode = 0 THEN
Out:= 0;
ELSIF Mode = 1 THEN
Out:=Out+ 1;
END_IF;
END_FUNCTION_BLOCK
4.2.2 功能块的调用
在调用一个功能块(FB)之前,你必须声明一个实例。
实例名 : 功能块名;
调用功能块:
实例名(输入参数1 := 值1,输入参数2 := 值2,...,输入参数n := 值n,
输入输出参数1 := 值1,输入输出参数2 := 值2,...,输入输出参数n := 值n,
输出参数1 => 值1,输出参数2 => 值2,...,输出参数n => 值n);
//调用功能块后,可以读出FB的输出参数
变量名 := 实例名.输出变量;
- 使用=>运算符来指定输出参数
- 变量名为上述声明的实例
4.2.3 TwinCAT快捷键调用功能块
(1)首先在变量声明区域,声明一个功能块实例。
(2)然后在程序编写区域按键盘的 F2 键选择声明好的实例,会自动调出相应的功能块。将所需参数填写入相应位 置,就完成了功能块的调用。
下面是调用功能块的例子:
- fb_TON1 便是功能块TON的实例
- ()中的内容是输入输出参数,根据自己的需要进行读写
对功能块中的单独项可以通过下面方式访问:
4.3、函数和功能块的比较
4.3.1 功能块案例
FUNCTION_BLOCK Circle1
(* 常数声明 *)
VAR CONSTANT
PI : LREAL := 3.1415;
END_VAR
(* 输入参数 *)
VAR_INPUT
Radius : LREAL;
END_VAR
(* 输入输出参数 *)
VAR_IN_OUT
Circumference : LREAL;
END_VAR
(* 输出参数 *)
VAR_OUTPUT
Area : LREAL;
END_VAR
(* 局部变量、静态 *)
VAR
Counter : DINT; //每次调用,变量保留其值
END_VAR
Counter := Counter + 1;
Circumference := 2 * PI * Radius;
Circle2 := PI * Radius**2; //函数返回值
END_FUNCTION_BLOCK
PROGRAM CircleCalc1
VAR
myCircle1 : Circle1; //声明Circle1功能块实例
myArea1, myArea2 : LREAL;
myCircf : LREAL;
END_VAR
myCircle1(Radius := 3,Circumference := myCircf, Area => myArea1); //功能块调用
myArea2 := myCircle1.Area;
//myCircf的值为18869
//myArea1的值为28274
//myArea2的值为28274
END_PROGRAM
4.3.2 函数案例
FUNCTION Circle2 : LREAL
(* 常数声明 *)
VAR CONSTANT
PI : LREAL := 3.1415;
END_VAR
(* 输入参数 *)
VAR_INPUT
Radius : LREAL;
END_VAR
(* 输入输出参数 *)
VAR_IN_OUT
Circumference : LREAL;
END_VAR
(* 输出参数 *)
VAR_OUTPUT
Area : LREAL;
END_VAR
(* 局部变量、静态 *)
VAR
Counter : DINT; //每次调用,变量会初始化为0
END_VAR
Counter := Counter + 1;
Circumference := 2 * PI * Radius;
Circle2 := PI * Radius**2; //函数返回值
END_FUNCTION
PROGRAM CircleCalc2
VAR
myArea : LREAL;
myCircf : LREAL;
END_VAR
myArea := Circle2(Radius := 3,Circumference := myCircf); //函数调用
//myCircf的值为18869
//myArea的值为28274
END_PROGRAM
4.3.3 对比
功能块FB | 函数FC |
没有返回值 | 返回值的数据类型必须在名称后说明。如果没有返回值,数据类型为VOID |
可以使用In/out参数来读写转移变量 | |
可以使用输出参数从FB回送数值 | 不允许输出参数 |
本地变量是静态的,在FB调用中保留数值计数器本地变量增加;当FB结束时保留其数值。因此每次调用FB时变量增加。 | 本地变量是临时的当功能结束后数值丢失。虽然计数器本地变量增加,当FC退出时数值丢失。下次调用FC时变量重新恢复(例子中变为0) |
在语句部分,结果(回送值)被指定为输出 或in/out参数 | 在语句部分,结果(回送值)被指定为功 能名称(除了特别指定VOID数据类型之 外) |
调用功能块前,需要声明FB的一 个实例。(声明一个变量,指定FB名称为它 的数据类型。) | |
读出FB的输出参数 | 读出FC的返回值 |
4.4 程序
- 程序可以有输入, 输出, 局部变量和算法的程序代码部分
- 不同于FB: 程序没有例程
- 程序没有存储器
- 在程序中使用各种语言
- 程序由任务来调用(TwinCAT: 可由其它程序调用)
4.4.1 程序声明
PROGRAM 程序名
VAR
变量声明
END_VAR
代码
END_PROGRAM
例子:
PROGRAM Main
VAR
counter_1 : Counter; (* FB 计数器实例 *)
actCount : INT;
END_VAR
IF bfirstCycle
THEN
counter_1(Mode := 0);(* callFB带复位模式 *)
ELSE
counter_1(Mode := 1);(* callFB带计数模式*)
END_IF
actCount:=counter_1.Out; (*使用counter_1*的输出变量*)
END_PROGRAM
五、语法
5.1表达式
5.1.1 操作符
5.1.2 赋值
赋值指令用于变量赋值,也就是赋值关键字的左边是变量,右侧为要赋的值,通过赋值关键字进行赋值。
其中的赋值关键字包含三种:“:=”、“S=”、“R=”。
- “:=”为一般赋值,右值直接赋给左值,左值和右值相等。
- “S=”为置位赋值,表示如果右值为TRUE,左值变量变为TRUE(置位), 直到调用R=命令来初始化。
- “R=”为复位赋值,表示如果右值为TRUE,左值变量变为FALSE(复位)。用于复位S=指令置位的变量。
变量名:= 值;
例如:a S= b; 一旦b为 TRUE后,a会保持 TRUE, 即使b 变为 FALSE后。
5.1.3 位访问
变量1 := 变量2.位;
变量1.位 := 变量2;
变量1 := 变量2[位];
变量1[位] := 变量2;
注:访问I/O变量或者系统变量的二进制数字可以被其他任务干扰,所以不能保证一致性。
例:
A[i] := B; //数组A
A[i+1] := SIN(SQRT(A[i+3]));
C :=timer.Q; (* timer 是 FB TOF 的一个实例 *)
D := E/F + COS(A[i+1]);
bFlag:= X AND Y OR Z;
5.2 条件选择
5.2.1 IF条件语句
IF条件语句,在布尔表达式的值为TRUE时执行(执行语句)。布尔表达式可以是布尔类型的变量(bVar..),可以是比较判断的值(a>b),功能块的值的判断(LEFT(STR:= strVar, SIZE:=7) = 'TwinCAT')等等。
1、声明方式
IF <布尔表达式> THEN
<执行语句>
ELSIF (布尔表达式> THEN
<执行语句>
ELSE
<执行语句>
END_IF
2、例子
IF temp<20 THEN
heating_on := TRUE;
ELSE
heating_on := FALSE;
END_IF;
(*温度低于20度,加热器打开,否则关闭。*)
5.2.2 CASE选择语句
CASE选择语句根据选择条件中的值,执行相应执行语句。
1、声明方式
CASE <整数表达式> OF
<整数选择值1> : <语句>
<整数选择值2> : <语句>
...
<整数选择值n> : <语句>
ELSE
<语句>
END_CASE;
2、例子
CASE INT1 OF
1: BOOL1 := TRUE;
BOOL2 := FALSE;
2: BOOL1 := FALSE;
BOOL2 := TRUE;
ELSE
BOOL1 := FALSE;
BOOL2 := FALSE;
END_CASE;
5.3 循环语句
5.3.1 FOR循环语句
循环次数已知时,你可以使用FOR语句。如果循环通过次数未知,WHILE 或REPEAT语句更适合。
1、声明方式
FOR <变量名>:=<起始值> TO <终值表达式> BY <增量表达式> DO
<语句>
END_FOR;
- 可以省略BY(增量)说明。如果没有指定增量,那么默认为+1。
- 起始值,结束值和增量为表达式时,在开始FOR语句时会估算表达式。
- 如果起始值和结束值为 DINT 数据类型,数值(结束值—起始值)必须小于双整数的最 大值的范围,即小于2^31-1
2、例子
FOR i:=1 TO 12 BY 2 DO
Feld:=i*2
END_FOR
5.3.2 WHILE 循环语句
当循环通过次数未知时,可以使用WHILE语句。如果次数已知,FOR 语句更为适合。
1、声明方式
WHILE <布尔表达式> DO
<语句>
END_WHILE;
- 每次在执行语句部分之前估算迭代条件
- 如果值为TRUE,执行语句部分
- 如果值为FALSE, 结束WHILE语句
2、例子
i:=0;
WHILE i<100 DO
Feld:=i*2;
i:=i+1;
END_WHILE
5.3.3 REPEAT 循环语句
当循环通过次数未知时,可以使用REPEAT语句。如果次数已知,FOR 语句更为适合。
1、声明方式
REPEAT
<语句>
UNTIL <布尔表达式>
END_REPEAT;
- 重复执行 REPEAT到UNTIL之间顺序的语句
- 每次在执行语句部分之后估算迭代条件,这意味着语句部分至少要执行 1 次
- 如果值为FALSE,再次执行语句部分
- 如果值为TRUE, 结束REPEAT语句的执行
2、例子
i:=0;
REPEAT
Feld:=i*2;
i:=i+1;
UNTIL i>100
END_REPEAT
5.4 跳转语句
5.4.1 EXIT语句
EXIT 语句被用作在任何点退出一个循环(FOR, WHILE 或 REPEAT 循环),不考虑结束条 件是否为TRUE或FALSE。
例:
FOR i := 0 TO 9 DO //FOR循环开始
IF (Input[i] = TRUE) THEN //条件判断
EXIT; //条件若成立,执行EXIT语句,退出FOR循环控制语句。
END_IF;
END_FOR;
5.4.2 CONTINUE
CONTINUE语句用于提前结束本轮循环,并重新开始下一轮循环。
5.4.3 RETURN语句
RETURN 语句引起结束现在运行(程序,功能,功能块)的POU。
例:
Index:= 1;
FOR Index := 1 to 51 BY 2 DO
IF Index=4 THEN
RETURN;
END_IF;
END_FOR;