今天继续我们的小白教程,老鸟就不要在这浪费时间了😊。
前面一期我们讲了CODESYS工程的基本组成,我想经过几期的学习大家应该对如何使用CODESYS的开发环境有了基本的了解,从这一期开始我们介绍一下CODESYS的ST语言相关的基础知识。本期主要介绍一下ST中的主要关键字和变量类型,以及这些类型使用,顺便会介绍一下CODESYS与C语言的一些使用上的差异。
一、基本变量类型
CODESYS的ST变量类型跟其它编程语言类似,包括基本类型、数组、枚举、结构、联合等,还有指针、引用,另外功能块也算是一个类型。
1.基本类型
基本数据类型如下表:
数据类型 | CODESYS-ST | 32位平台 |
布尔 | BOOL | 8Bit(0或1) |
整型 | BYTE | 8Bit(0~255) |
WORD | 16Bit(0~2^16-1) | |
DWORD | 32Bit(0~2^32-1) | |
LWORD | 64Bit(0~2^64-1) | |
SINT | 8Bit(-128~127) | |
UINT | 8Bit(0-255) | |
INT | 16Bit(-2^15~2^15-1) | |
UINT | 16Bit(0~2^16-1) | |
DINT | 32Bit(-2^31~2^31-1) | |
UDINT | 32Bit(0~2^32-1) | |
LINT | 64Bit(-2^64-1~2^64-1) | |
ULINT | 64Bit(0~2^64-1) | |
实数 | REAL | 32Bit |
LREAL | 64Bit | |
字符串 | STRING | 1~255,默认80 |
这里需要注意的是CODESYS的基本数据类型与C语言类似,但是在字长上有差别。CODESYS中的INT在32位平台上默认为16位,而C语言的int在32位平台上默认为32位。另外对于浮点数,REAL与C语言的float对应,LREAL与C语言的double对应。
2.数组
数组定义的形式如下:
变量名 : ARRAY[0..N] OF 变量类型;
变量名 : ARRAY[0..N,0..M] OF 变量类型;
注意:
(1)变量名不区分大小写。
(2)变量类型可以是基本类型、结构、联合或者功能块。
(3)数组可以下标可从0开始,也可以不从0开始(这一点比C语言方便)。
(4)N和M可以用常量来代替。
数组初始化方式:
rPos : ARRAY[0..6] OF LREAL:=[7(5.0)];
rPos : ARRAY[0..6, 0..6] OF LREAL:=[7(5.0), 7(7.0)];
rPos : ARRAY[0..2] OF LREAL:=[5.0, 6.0, 7.0];
rPos : ARRAY[0..2, 0..2] OF LREAL:=[5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
注意:
(1)默认会初始化为0。
(2)CODESYS还有一个数组的数组的定义方式也可以定义多维数组。
二维数组定义如下:
aiPoints : ARRAY[1..2,1..3] OF INT := [1,2,3,4,5,6];
数组的数组定义如下:
ai2Boxes : ARRAY[1..2] OF ARRAY[1..3] OF INT := [ [1, 2, 3], [ 4, 5, 6]];
这里需要注意不要把数组的引用方式aiPoints[1, 2]和ai2Boxes[1][2]混淆了,这两种引用方式是对应不同的定义方式。
3.枚举
枚举由用户定义的数据类型,定义后可以在整个工程中使用。枚举定义可以像功能块和函数一样在“Applicationà添加对象”中选择添加。
TYPE ErrID :
(
name1 :=0,
…
);
END_TYPE
注意:
(1)在枚举声明中,通常至少声明2个成员。
(2)枚举的数据类型默认为INT,但用户可以指定其他数据类型。支持的类型包括:INT | UINT | SINT | USINT | DINT | UDINT | LINT | ULINT | BYTE | WORD | DWORD | LWORD
4.结构和联合
结构和联合是由用户定义的数据类型,将不同的变量组合成一个独立单元。结构和联合也可以在“Applicationà添加对象”中添加。
结构的定义方式如下:
TYPE name:
STRUCT
…
END_STRUCT
END_TYPE
联合的定义方式如下:
TYPE name :
UNION
…
END_UNION
END_TYPE
注意:
(1)结构定义后,结构名称在整个工程中可以作为基本数据类型来定义变量、数组或指针等。
(2)结构也可以作为其他结构的成员。
(3)可以通过EXTENDS扩展现有结构,方式如下:
TYPE name EXTENDS basename :
STRUCT
…
END_STRUCT
END_TYPE
扩展后的结构同时拥有name和basename结构的成员。
(4)结构还有一个专属数据类型“BIT”,用于定义位变量,其值只能是TRUE和FALSE。
5.指针
指针用于在运行时存储对象的内存地址。定义方式如下:
name: POINTER TO 数据类型/结构/功能块;
实例如下:
a : INT;
b : INT;
pt : POINTER TO INT;
pt := ADR(a); //变量a的地址赋值给指针pt
b := pt^; //把a的值赋值给b
注意:
(1)指针在定义是必需指定类型,不同类型指针不能互相转换。
(2)指针本身是DWORD类型。
(3)指向IO的指针会报错。
(4)指针指向的数据可以通过[]来进行索引访问。索引组件的数据类型和大小由定义的数据类型决定,索引访问是通过将索引相关的偏移量i*SIZEOF(<base type>)添加到指针的地址来实现的。
正确方式:piData[i] := (piData + i * SIZEOF(INT))^;
错误方式:piData[i] != (piData + i)^
6.引用
引用隐式引用另一个对象。访问时,引用被隐式取消引用,因此不需要特殊的内容运算符^(如指针)。定义方式如下:
name : REFERENCE TO <data type> ;
注意:
(1)编译器版本>= V3.3.0.0,引用默认会初始化。
(2)最好不要对设备输入进行引用,没有写权限会让编译器报警。
(3)引用类型不能用作数组、指针或引用的基类型。
(4)不能对BIT类型进行引用。
以下为引用的错误用法:
在函数或功能块传递参数时,可以使用引用从而避免使用指针。
7.功能块
功能块应该说是由用户定义的一个独立的功能单元,类似于C++ 的类。它是一个具有完整功能的模块,使用时跟定义各种变量类型一样需要先声明实例才能使用。功能块的定义同样可以在“Applicationà添加对象”中添加。
其实这5、6、7这几个内容我在写的时候一直犹豫要不要讲,尤其是指针,对于新手来说完全可以不用,以避免出错^o^。为了保持内容的完整性,上面还是做一个简单的介绍。
二、变量作用域
常用的变量作用域包括全局变量、局部变量、静态变量等。
1.全局变量
全局变量通常在整个程序范围内定义,会放在“Application”树下面。其添加方法是在“Application”上点右键,在“添加对象”中选择“全局变量列表…”。在弹出的窗口中输入全局变量的定义域名称,默认是“GVL”。
在左侧设备树“Application”下会增加“GVL”,双击打开后可以看到:
全局变量的定义必须是在字段“VAR_GLOBAL…END_VAR”之内。全局变量在整个工程范围内有效,定义后工程内所有的程序、函数、功能块都可以访问。这里需要注意的是,在全局变量上方有一个宏定义“{attribute 'qualified_only'}”,其作用是限定全局变量的使用必须指明定义域,即上面新建全局变量时输入的名称“GVL”。比如上图中定义的“rPoint”变量在程序中使用时需要写“GVL.rPoint”,否则会报错。如果觉得这样用起来麻烦,可以把这个宏注释掉,这样可以直接使用“rPoint”。
需要注意的是如果在程序块中局部变量与全局变量重名,则在该块中优先使用局部变量。建议在使用时局部变量尽量不要与全局变量重名,要么就给全局变量指明定义域,这样方便区分。
对于编译器版本>=3.2.0.0,CODESYS总是在本地POU变量之前初始化全局变量。
2.局部变量
局部变量通常在程序、函数或功能块内部定义。局部变量的定义必须是在字段“VAR…END_VAR”之内。比如上一期工程里面main函数的变量定义都属于局部变量。
局部变量只在定义的程序块范围内有效。
3.静态变量
静态变量的定义必须是在字段“VAR_STAT…END_VAR”之内。
注意:
(1)CODESYS在第一次调用每个块时初始化静态变量。
(2)静态变量只能从声明变量的命名空间中访问,类似C语言中的静态变量。
(3)当应用程序离开块时,静态变量保留其值。例如,可以使用静态变量作为函数调用的计数器。
4.输入/输出变量
输入/输出变量是程序块(POU)接口的一部分,用于声明程序块的输入/输出变量。
定义方式如下:
关键字 name
VAR_IN_OUT
<variable name> : <data type>;
END_VAR
关键字:FUNCTION、FUNCTION_BLOCK、METHOD、PRG
注意:
(1)程序块调用时不能直接将常量(文字)指定给VAR_IN_OUT类型的参数。VAR_IN_OUT变量通过引用方式传递(类似C语言的引用),直接使用对应的变量,在程序块内部不会生成副本。
(2)VAR_IN_OUT变量在其使用的程序块内是可读可写的,内部对变量的修改在程序块执行完成后会保留。
(3)VAR_IN_OUT变量不能通过“功能块实例名称”直接远程读或写。功能块中只有VAR_INPUT和VAR_OUTPUT变量可以这样使用。
(4)如果字符串变量作为参数传递,则实际变量和形式变量的长度应该相同。否则,传递的字符串可能会被无意操作。VAR_OUTPUT CONSTANT参数不会出现此问题。
(5)程序块调用时不能将位变量直接指定给VAR_IN_OUT类型的参数,因为位变量没有直接的内存地址,无法通过引用方式传递。位变量的传递需要通过一个中间变量。
5.功能块输入和输出变量
输入输出变量用于功能块的输入和输出定义。输入变量的定义必须是在字段“VAR_INPUT…END_VAR”之内。输出变量的定义必须是在字段“VAR_OUTPUT…END_VAR”之内。
6.功能块临时变量
临时变量定义必须是在字段“VAR_TEMP…END_VAR”之内。
注意
(1)临时变量只能在程序块和功能块中使用。
(2)每次调用块时,CODESYS都会初始化临时变量。
三、变量保持类型
1.常量
常量变量在全局变量列表或编程对象的声明部分中声明。在实现中,常量变量可以通过实例路径以只读方式访问。大部分时候是用来定义一些需要改但是不经常改的值。
定义方式如下:
作用域 CONSTANT
name : 数据类型 := 初始值;
END_VAR
作用域:VAR、VAR_INPUT、VAR_STAT、VAR_GLOBAL
VAR_GLOBAL:全局常量,在整个工程中有效。
VAR:局部常量,仅在定义的程序块内有效。
VAR_STAT:静态变量
注意在运行过程中常量值不能修改。
2.保留变量
保留变量由关键字RETAIN声明。保留变量在热启动后会保留其值,但在重新加载程序、下载或冷启动后不会保留。
定义方式如下:
作用域 RETAIN
<identifier>: <data type>;
END_VAR
作用域类型:VAR、VAR_INPUT、VAR_OUTPUT、VAR_IN_OUT、VAR_STAT、VAR_GLOBAL
不允许使用AT关键字分配输入、输出或内存地址。
3.持久变量
持久变量由PERSISTENT关键字声明。持久变量会存储在外部文件中,因此会一直保留。
全局持久变量定义方式如下:
VAR_GLOBAL PERSISTENT RETAIN
<identifier> : <data type> (:= <initialization>)?;
<instance path to POU variable>
END_VAR
程序块中的定义方式如下:
作用域 PERSISTENT RETAIN
name : <data type>;
END_VAR
作用域:VAR、VAR_INPUT、VAR_OUTPUT、VAR_IN_OUT、VAR_STAT、VAR_GLOBAL
注意:
(1)不允许使用AT关键字分配输入、输出或内存地址。
(2)在持久变量列表中使用不能使用指针(POINTER TO数据类型)。重新下载应用程序时,指针类型的位置可能会改变,将会导致不可预料的结果。
四、后记
因为是介绍ST的基础知识,所以上面把相关的角落都扫了一下,应该说需要用到的地方都过了一下。新手在看的时候,如果发现现在用不到的可以先跳过,只看自己感兴趣的就好了。实际上CODESYS提供了在线帮助文档,这是一个使用CODESYS的百科全书。但是大部分资料是英文的,而且有很多写的真的不太好懂(就是那种单词全认识,什么意思不知道?!!感觉英语全都白学了,好尴尬~~~)。这也是为什么会有这些文章的原因之一,希望对大家有帮助^o^。