菜单

第七章:公共元素及变量

 

本章主要知识点
  • MetaFacture 公共元素
  • 注释的表示
  • 常量、变量的声明
  • 数据类型
  1. 变量类型和属性

7.1 公用元素

PLC 程序是由一定数量的基本语言元素(最小单元)组成的,把它们组成在一起以形成说明或语句。它包括分界符、关键字、标识符和注释等。

7.1.1 字符集

根据国家标准的 GB/T15969.7-2005,可编程控制器使用的文本和图形类编程语言中的文本元素应依据国家标准 GB1988 字符的“基本代码表”的 3~7 列字符组成,并根据 GB2312-1980《信息交换用汉字编辑字符集 基本集》来表示汉字。
支持小写字母时,字母的大小写具有相同的意义。例如,Control 与 CONTROL 是相同的变量名或标识符。PLC 制造商根据表 7-1 字符集特性的规则选择。
表7-1 字符集特征
1)英镑符号应使用在数符号的位置,前者占据国家执行 GB1988 字符集的 2/3 字符位置。
2)货币符号应使用在美元符号的位置,前者占据国家执行 GB1988 字符集的 2/4 字符位置。
3)当 GB1988 字符集中的 7/12 字符位置被国际字符集中的另外字符使用时,在 2/1 位置处的惊叹号!被用于表示垂直线。
4)汉字字符集依据 GB2312-1980。国家标准字符集中字符的使用是典型的扩展应用。

7.1.2 分界符

分界符(Delimiter)用于分隔程序言语元素的字符或字符组合。它是专用字符,不同的分界符具有不同的含义。表 7-2 列出了各种分界符的应用示例。
表7-2 分界符
注意:
用于逻辑运算和算术运算等的操作符号为中间操作符,如 NOT、MOD、+、-、*、/、<、>、&、ANDOR、XOR。
用于表示时间、时刻等时间文字的操作符号为时间文字分界符,如 T#、D、H、M、S、MS、
DATE#、D#、TIME_OF_DAY#、TOD#、DATE_AND_TIME#、DT#。

7.1.3 关键字

关键字是语言元素特征化的词法单元。在 IEC61131-3 标准中,关键字作为编程语言的字,被用于定义不同结构或启动和中指特定的软件元素。
部分关键字配对使用,如“FUNCTION”与“END_FUNCTION”等。部分关键字单独使用,如“ABS”等。关键字不能用于任何其他目的,如不能作为变量名或扩展名,既不能用 TON 作为变量名,也不能用 VAR 作为扩展名。
关键字是标准标识符,因此不能包含空格。表 7-3 列出了MetaFacture中常用的关键词和示例。
表7-3 常用关键字和示例
此外,下列功能模块和函数胡的标识符也被保留作为关键字
1.标准数据类型:BOOL、REAL 或 INT 等;
2.标准函数名和功能块名:SIN、COS、RS 或 TON 等;
7.指令表语言中的文本操作符:LD、ST、ADD 或 GT 等;
4.结构化文本语言中的文本操作符:NOT、MOD 或 AND 等。

7.1.4 常数

数值文字(Numeric Literal)用于定义一个数值,它可以是十进制或其他进制的数。数值文字分为两类:整数文字和实数文字。书写格式如下:
<类型># <数值>
<类型>:指定所需的数据类型。支持的类型有 BOOL、SINT、USINT、BYTE、INT、UINT、WORD、DINT、UDINT、DWORD、REAL 和 LREAL。
<数值>:指定常数。输入的数据必须符合指定的数据类型<类型> 。
  1. 数值常数

数值常数可用二进制、十进制和十六进制数表示。假如一个整数不是十进制数,你必须在该整数常数之前写出它的基数,并加上数字符号(#),用户可在数值中使用下划线,例如 2#101。
  1. BIN(2 进制)

二进制的 1 位(bit)只能取 0 或 1,用来表示开关量(或称数字量)的两种不同的状态。例如,若 QX0.0:=1,表示线圈“通电”状态,称改编成元件为 ON 或 1 状态;若 QX0.0:=0,则表示线圈为“断电”状态,称该编程元件为 OFF 或 0 状态。
二进制数直接表示可用 2#前缀表示,例如 2#1111 1111 1111 0000 表示 16 位二进制常数。在编程时,位编程元件的 1 状态和 0 状态常用 TRUE 和 FALSE 来表示。
  1. DEC(十进制)

十进制数是组成以 10 为基础的数字系统,满十进一。十进制数前缀 10#不需要表示,可直接表示,例如 255,则直接表示 10 进制数的 255。
  1. HEX(十六进制)

十六进制的 16 个数字由 0~9 和 A~F(对应于十进制数 10~15)组成,其运算规则为逢 16 进1,每个数字占二进制数的 4 位。在 MetaFacture 中 16#用来表示十六进制数的前缀。例如16#E5。
数值文字和性能的示例详见表 7-4 所示,这些数值可以是变量类型 BYTE、WORD、DWORD、SINT、USINT、INT、UINT、DINT、UDINT、REAL 或 LREAL。
表7-4 数值文字的性能和示例
在 MetaFacture 中可以设置数值的显示方式,可在监视窗口单击鼠标右键,选择“显示模式”进行显示的选择,如图 7.1 所示。
 
 
图 7.1 数值显示模式更改
  1. BOOL 常数

BOOL 常数为逻辑值 TRUE(真)与逻辑值 FALSE(假)。在 MetaFacture 中也可以用
1(真)或0(假)表达相同的意思。
  1. TIME 常数

一个 TIME 常数总是由一个起始符 T 或 t (或用 TIME 或 time )和一个数字标识符 # 组成。然后,是跟随的实际时间声明,包括天数(d)、小时(h)、分钟(m)、秒(s)和毫秒(ms)。请注意时间各项必须根据时间长度单元顺序进行设置(d 在 h 之前,h 在 m 之前,m 在 s 之前,s 在 ms 之前),但无须包含所有的时间长度单位。
在 ST 语言的赋值语句中,正确使用时间常数的示例 如下:
TIME1 := T#14ms;
TIME1 := T#100S12ms;(*最高单位的值可以超出其限制*)
TIME1 := t#12h34m15s;
使用时间常数的示例 :
TIME1 := t#5m68s;(*最低单位的值溢出*)
TIME1 := 15ms;(*没有 T#*)
TIME1 := t#4ms13d;(*输入的顺序错误*)
  1. DATE 常数

这些常数可用来输入日期。声明一个 DATE 常数时,起始符为 d , D , DATE 或 date 后跟随一个 # 号。然后你就可按照年-月-日的格式输入任何日期。例如,
DATE#1996-05-06
d#1972-07-29
  1. TIME_OF_DAY 常数

使用这种类型常数可保存一天中的不同的时间。一个 TIME_OF_DAY 常数的声明使用起始符 tod# 、 TOD# 、TIME_OF_DAY# 或 time_of_day# ,后跟随一个时间格式为:小时:分:秒的时间。秒值可以是实数也可是小数,例如,
TIME_OF_DAY#15:36:30.123
tod#00:00:00
  1. DATA_AND_TIME 常数

日期常数和一天中的时间常数可合并起来构成一个所谓的 DATE_AND_TIME 常数。DATE_AND_TIME 常数的起始符为 dt# , DT# , DATE_AND_TIME# 或 date_and_time# 。在日期之后用(-)字符连接时间,例如,
DATE_AND_TIME#1996-05-06-15:36:30
dt#1972-07-29-00:00:00
  1. REAL / LREAL 常数

REAL 和 LREAL 常数可使用十进制小数和指数形式表示。使用带小数点的美国格式表示实数(REAL / LREAL),例如,
7.4 取代 7,4
1.64e+009 取代 1,64e+009
  1. STRING 常数

一个字符串是一个字符队列。STRING(字符串)常数使用一个单引号作为其前缀和后缀。也可以输入空格和专用字符(如音符),这些字符将同所有其它字符一样进行处理。字符的表达式例如,
‘MetaFacture!!’
‘Sinsegye’
‘:-)’
在字符对列中,($)号和后面跟随的两个十六进制数组合被解释为八位字符码的十六进制表示。此外,以$作为起始符的两个字符的组合,其含义如表 7-5 所示,并以字母的顺序排列。
表7-5 字符串特性和示例
  1. 类型符

通常,对 IEC 常数,有可能使用最小的数据类型。如必需使用另一种数据类型。则可借助于类型符而不需要显示地声明常数。为此,常数可使用一个前缀表示,该前缀决定了其类型。
格式:<Type>#<Literal>
<Type>:指定所要求的数据类型有 BOOL、SINT、USINT、BYTE、INT、UINT、WORD、DINT、 UDINT、DWORD、REAL 及 LREAL。
<Literal>:指定常数。输入的数据必须与<Type>下指定的数据类型相匹配。
Var1:=DINT#34;
如果常数不能保证在不丢失数据的情况下转换为目标类型,系统将发出一个出错信息。类型符可用于一般常数。

7.1.5 句法颜色

所有编辑器中不同的文本带有不同的颜色,不但可以变面错误,而且有助于快速的发现错误。例如,注释没有被括上,从字体颜色上就会立即得到提示。句法颜色示例如图 7.2 所示。
  • 蓝色:关键字。
  • 绿色:编辑器中的注释。
  • 灰色:常数(例如 TRUE/FALSE、T#3s、%IX0.0)。
  • 黑色:变量、常数、标点符号。
图 7.2 句法颜色

7.1.6 空格和注释

  1. 空格

在程序文本的任何地方允许插入一个或多个空格。但在关键字、标识符、分界符等内部允许包含空格。表 7-6 是空格的应用和示例。
表7-6 空格的应用及示例
  1. 注释

通常在程序中在我们认为逻辑性较强的地方需要加入注释,以说明这段程序的逻辑是怎样的,方便我们自己后来的理解以及其他人的理解,合理的添加注释可以增加代码的可读性。
在所有的文本编辑器、声明编辑器、语句表、结构化文本语言和在自定义数据类型中都允许使用注释。如果工程使用一个模板来打印输出,在变量声明过程中输入的注释出现在每个变量后的基于文本编程组件中。在MetaFacture针对不同的编程语言及编辑器拥有不同的注释方法,如下会做详细分解。
  1. 结构化文本 ST 的注释

在结构化文本中分有单行注释和多行注释两种。
多行注释
以(*开始,以*)结束,这允许注释跨越多行。例如:“ (*This is a comment.*)”。使用实际效果如图 7.3 所示。
图 7.3 多行注释
单行注释
行注释,使用符号“//”来进行标注,使用该注释方法,可注释一行。例如: “// This is a comment.”。使用实际效果如图 7.4 所示。
图 7.4 单行注释
注释的嵌套
在日常的使用中,常会遇到需要将注释嵌套的应用,MetaFacture 支持此种注释方法,且上文绍到的两种注释方法在同一程序段内可以混合使用,如图中介7.5 所示。
图 7.5 注释的嵌套
  1. FBD、LD 和 IL 中的注释

在 “工具”-->“选项”-->“FBD、LD 和 IL 编辑器”选项中选择常规可以对 FBD、LD 和 IL 编程语言的注释试图进行设置,如图 7.6 所示。
图 7.6 注释常规设置
将“显示节注释”选项勾选后,既可在对应的编程语言中插入/显示节注释,如图 7.7 为使用FBD 编程语言,在网络节中添加注释的截图。
图 7.7 FBD 中的注释显示
  1. CFC 中的注释

在 CFC 中可随意放置注释,在“工具箱”中-->选择 “
”进行添加注释,如图 7.8 的 a)所示。图 7.8 的 b)为在 CFC 编程语言中显示的结果。
 
 
a ) b )
图 7.8 CFC 中注释的显示
 
a )工具箱中添加注释 b )CFC 中使用注释
  1. SFC 中的注释
在 SFC 编程语言中,能在编辑步属性对话中输入关于步的注释,如图 7.9 所示。
 
图 7.9 SFC 中注释的显示

7.2 变量的表示和声明

变量可以用来表示一个数值,一个字符串值或一个数组等。MetaFacture 将变量的数据类型分为了标准数据类型、扩展数据类型及自定义数据类型三大类。

7.2.1 变量

变量是保存在存储器中待处理的抽象数据,是为了识别 PLC 的输入/输出、PLC 内部的存储区域而使用的名称,可以代替物理地址在程序中的使用。
可以根据需要随时改变变量中所存储的数据值。在程序执行过程中,变量的值可以发生变化。使用变量之前必须先声明变量,及指定变量的类型和名称。变量具有名称,类型和值。变量的数据类型确定它所代表的内存大小和类型。变量名即指在程序源代码中的标识符。

7.2.2 标识符

标识符就是变量的名称。在定义标识符时,根据 IEC 61131-3 标准,必须由字母、数字和下划线字符组成。此外,含应遵循如下规则:
标识符的首字母必须是字母或下环线,最后一个字符必须是字母或数字,中间允许字母、数字、下划线。
标识符中不区分字母的大小写。
下划线是标识符的一部分,但标识符中不允许有两个或两个以上连续的下划线。
不得含有空格
例如 ab_c、AB_de 和_AbC 是允许的标识符,而 1abc、 __abc 和 a__bc 均不允许。

7.2.3 变量声明

变量声明就是指定变量的名称、类型和赋初始值,变量的声明非常重要,未经声明的变量是不能通过编译的,所以也无法在程序中使用。用户可以在程序组织单元(POU)、全局变量列表(GVL)和自动声明对话框中进行变量的声明。在MetaFacture 中变量声明分为两类,普通变量声明和直接变量。
  1. 普通变量声明

最常用的变量声明,不需要和硬件外设或通讯进行关联的变量,仅供项目内部逻辑使用。普通声明须符合以下规则:
<标识符>:<数据类型> {:=<初值>};
{}中的部分是可选部分。
如 nTest:BOOL;,nTest:BOOL:=TRUE;
  1. 直接变量声明

在 MetaFacture 应用中,当需要和可编程逻辑控制器的 I/O 模块进行变量映射或和外部设备进行网络通讯时,需要采用此声明方法。
使用关键字 AT 把变量直接联结到确定地址,直接变量须符合以下规则:
AT<地址>:
<标识符> AT <地址>:<数据类型> {:=<初始化值>};
{}中的部分是可选部分。
使用“%”开始,岁后是位置前缀符号和大小前缀符号,如果有分级,则用整数表示分级,并用小数点符号“.”表示,如%IX0.0,%QW0。直接变量声明的具体格式如图 7.11 所示。
图 7.11 直接变量的表示方法
图 7.11 中的位置前缀的定义如下:
I:表示输入单元。
Q:表示输出单元。
M:表示存储区单元。
大小前缀的定义如表 7-7 所示。
表7-7 大小前缀定义
【例 7.1】在程序中定义了变量双字型 Var1,如需拿取该变量其中的一部分数据,将其转换成布尔/字节/字类型的变量,其首地址为多少,该如何换算:
VAR
Var1 AT%ID48:DWORD;
END_VAR
%I 说明了该变量属于输入单元,具体的地址为%ID48。在表 7-8 列出了 MetaFacture在寻址时,系统会根据数据类型的大小(X:bit,B:byte,W:word,D:dword)来进行分配。
该地址内存映射表中,字地址%IW96 和%IW97 两个字组合后对应%ID48,因为 48* 2(字节)后的字节首地址为 96。同样的道理,字节地址%IB192、%IB 193、%IB 194 和%IB 195 这四个字节变量组合后对应%ID48, 因为 48 * 4(字节) 后对应的字节首地址正好为 192。
表 7-8 内存映射表
【例 7.2】相信有了例 2.X 的基础,就很容易理解如下的地址映射关系。
1.%MX12.0:是%MB12 的第一位。
2.%IW4:表示输入字单元 4(字节单元 8 和 9)。
7.%Q*:输出在一个为特定的位置。
4.%IX1.3:表述输入第 1 字节单元的第三位。

7.3 数据类型

无论声明的是变量还是常量,都必须使用到数据类型。数据类型的标准化是编程语言开放性的重要标志,在 MetaFacture 中数据类型完全符合 IEC61131-3 所定义的标准, MetaFacture 将数据类型分为标准数据类型、IEC1131-3 标准的扩展数据类型和自定义数据类型,数据类型决定了它将占用多大的存储空间以及将存储何种类型的值。

7.7.1 标准数据类型

MetaFacture 标准数据类型共分为 5 大类,分别为布尔类型、整数类型、实数类型、字符串类型和时间数据类型,表 7-9 将 MetaFacture 所支持的标准数据类型列举出来。
表7-9 标准数据类型
  1. 布尔

布尔型变量用来表示 TRUE/FALSE 值,一个布尔型变量只有 TRUE 或 FALSE 两种状态,在MetaFacture 还可以使用 0 或 1 来表示。
【例 7.3】将开门信号和取料信号的与逻辑结果赋值给布尔型变量 bReady,结构化文本语言代码如下。
VAR
bReady, bDoors_Open, bGrip:BOOL;
END_VAR
bReady:=(bDoors_Open and bGrip);
在 MetaFacture 中允许将相同类型的变量进行统一声明,用“,”进行分割。
【例 7.4】将十进制数 211 赋值给 bReady 变量,结构化文本语言代码如下:
VAR
bReady:BOOL;
END_VAR
bReady:=211;
将一个整型数据赋值给布尔型数据,显然这种赋值方式是错误的,通过程序编译后,编译会返回错误提示“C0032:不能将类型‘USINT’转化为类型‘BOOL’”。
布尔型变量是使用最多的一种变量类型。所以在流程控制语句中(如 IF,CASE,循环)中时常会用到,所以学会正确使用布尔变量至关重要。
注意:
如果在内存中的最低位被置位(例如 2#00000001 ),则 BOOL 类型变量为“true”(真)。如果
内存中最低位没有被置位,则 BOOL 变量为 FALSE 例如 2#00000000。所有其它值都不能被正确
地进行转换,并显示为 (***INVALID:16#xy *** ,联机监视时)。类似的问题是可能出现的,例如,
PLC 程序中使用了重叠的内存范围。
例如,如果定义了一个布尔数组, A:ARRAY[0..7] OF BOOL,在系统中,它所占用的总内存
不是一个8 位字节,而是占用了 8 个 8 位字节。

2. 整型

整型类型代表了没有小数点的整数类型,MetaFacture 中支持的整型类型在表 7.9 中所示。
在 MetaFacture 中,整型是一个最大的标准类,其成员最多,没有必要将每个类型的关键字死记硬背,只要了解其中的规律,就非常容易记忆,下面简单的说明了整型前缀其中的规律。
U___表示无符号数据类型,U 为 Unsigned 的缩写。
S___表述短数据类型,S 为 Short 的缩写。
D___表述双数据类型,D 为 Double 的缩写。
L___表述长数据类型,L 为 Long 的缩写。
如 UINT 为无符号的整型数据,USINT 即为无符号的短整型数,LINT 表示长整型数据。
【例 7.5】整型举例。
VAR
nValue1:USINT; nValue2:LINT;
nValue3:WORD;
END_VAR
 
nValue1:=4;
nValue3:=16;
nValue2:=nValue1+nValue3;
运行程序后,nValue2 最终的输出结果为 20。
无符号(Unsigned)和有符号(Signed)的区别是最高位的区别。
无符号类型数据将全部存储空间全部储存数据本身,没有符号位。如 UINT 型将 16 为全部存储数据本身,即数据范围可以从0~(216 -1),即0~65535。
有符号类型数据牺牲最高位作为符号位。如 INT 型变量牺牲最高位作为符号位,剩下 15 位作为数据存储,故该数据范围为-215~(215-1),即-32768~32767。所以有符号整型变量中可以存放的正数的范围比无符号的整形变量中正数的范围缩小一倍。
如下通过两个变量对无符号和有符号进行说明:
nValue1:UINT;
nValue2:INT;
图 7.12 无符号与有符号数据结构
  1. 实数

实数,有的教科书称浮点数,这里主要是用于处理含有小数的数值数据,实数类型包含了 REAL 及 LREAL 这 2 种数据类型。REAL 实数占用 32 位存储空间,而 LREAL 长实数占用 64 为存储空间。在 MetaFacture 中,实数和长实数常量有两种表示形式。
  1. 十进制小数形式。它由数字和小数点组成。0.123、127.1、0.0 都是十进制小数表现形式。
  2. 指数形式。如 123e3 或 123E3 都代表 123×103。但注意字母 e(或 E)之前必须有数字,且 e 后面的指数必须为整数,如 e3、2.1e7.5、.e3、e 等都是不合语法的指数形式。
一个浮点数可以有多种指数表示形式,如 127.456 可以表示为 127.456e0、12.3456e1、 1.23456e2 等。其中的 1.23456e2 称为“规范化的指数形式”。即在字母 e(或 E)之前的小数部分中,小数点左边应有一位(且只能有一位)非零的数字。
【例 7.6】将 12.3 赋值给 rRealVar1 变量,结构化文本语言代码如下:
VAR
rRealVar1:REAL;
END_VAR
rRealVar1:=1.23e1;
在例 7.6 中,1.23e1 表示就是 12.3,当然也可以直接通过表达式 RealVar1:=12.3 完成上例要求。
此时如果将要求更换为将 0.123 赋值给 rRealVar1 变量,通过上面提到的规律,只需将表达式更换为,
rRealVar1:=1.23e-1;
或,
rRealVar1:=0.123;
 
注意:
数据类型 LREAL 的支持取决于目标设备。在编译时 64 位类型的 LREAL 是被转换为 REAL(可
能有信息丢失),还是保持不变,需参考不同硬件产品的相应文档。
如果 REAL 或 LREAL 类型转换成 SINT, USINT, INT, UINT, DINT, UDINT, LINT 或 ULINT 类型,
且实型数据的值超出了整形的范围,结果将会是不确定的并且该值取决于目标系统。这种情况有
可能产生异常! 为了获取与目标无关的代码,应由应用程序处理所有值域越界问题。如果
REAL/LREAL 型数据在整形的值域范围内,他们之间的转换在所有系统上都可以进行。
 

4. 字符串

一个字符串是一个字符队列。字符串(STRING)常数使用一个单引号作为其前缀和后缀。也可以输入空格和专用字符(如音符)。这些字符将同所有其它字符一样进行处理。
在 MetaFacture 中,字符串类型变量可以包含任意一串字符,使用单撇号括起来的一个字符串,如’Hello‘、‘How are you‘、‘MetaFacture、‘why?‘等都是字符常串量,声明时的大小决定了存储变量所需要的存储空间。这里的存储空间它是指字符串中字符的数量,用圆括号或方括号括起来,具体计算及声明方法如下。
如果在定义变量是没有指定字符串大小,系统则默认分配 80 个字符给该变量,系统
中实际占用存储空间=[80+1] BYTE。
例如在变量声明中定义的是 Str1:STRING:=‘a‘;虽然实际的 Str1 变量初始值只包含一个字
符,但由于再声明中未使用括号来限定字符串大小,故 Str1 在系统内占用的内存空间为
80+1 个字节。
如果定义了大小,则系统中实际占用存储空间=[空间的大小为(定义字符串大小)+1]
BYTE。MetaFacture中一般不限制字符串的长度,但是字符串函数只能处理长度在
1-255 个字符串。
例如,定义两个字符串,其语句如下:符之间的字
Str1:STRING[10]:='a';
Str2:STRING:='a';
上述两句声明语句中内容是一样的,区别在于多了一个[10]的存储空间限定,图 7.13 中为这两种声明语句的方式在程序的内存部分的区别,左边由于 Str1 限定了大小为 10,故在程序中实际占的字节大小为 10+1,即 11 个。而 Str2 默认分配的是 80 个字符,实际占的大小为 80+1,即 81 个。
图 7.13 字符串的存储方式
通常而言,使用默认的 80 个字符能满足大部分的应用,但是如果应用中有大量的字符串数据,但每个字符串中实际的字符数据却又很小,这样就会造成数据存储区的大量浪费,限定大小后能够节省很多存储空间供其他变量使用。
如果一个变量用字符串初始化,而字符串对于变量的数据类型来说又太长,字符串将会从右至左相应的截断。
字符串在程序中的表示时,为了和普通的变量区分开,需要加单引号‘XXX‘。 【例 7.7】将字符串‘Hello MetaFacture赋值给 str 变量。
VAR
str:STRING;
nNum: WORD;
END_VAR
str:='Hello MetaFacture';
nNum:=SIZEOF(str); (*使用 SIZEOF 指令查看存储空间占用量*)
程序运行的结果如图 7.14 所示,'Hello MetaFacture'实际字符数为 13 个字符,占存储空间的大小为 14 个字节,但是使用 SIZEOF 指令看到的输出结果为 81 个 BYTE,其原因就是因为没有指定字符串大小,系统自动分配了 80 个字符给 str 变量。
图 7.14 字符串的实例运行结果图
【例 7.8】将字符串‘Hello MetaFacture赋值给 str 变量,str 定义为 12 个字符大小。
VAR
str:STRING[12];
nNum: WORD;
END_VAR
str:='Hello MetaFacture'; nNum:=SIZEOF(str);
程序实际运行的结果如图 7.15 所示。
图 7.15 字符串实例运行结果图
从运行的结果来看,str 只显示了‘Hello MetaFa‘,缺少了‘ture‘,即多余部分已被系统自动截断。字符串占用的存储空间为 13 个 BYTE。

5. 时间数据

时间数据类型包括 TIME、TIME_OF_DAY/TOD、DATE 和 DATE_AND_TIME/DT。系统内部处理这些数据的方式与双字(DWORD)类型相似。
  1. TIME,时间,精度为毫秒,范围从 0~71582m47s295ms。语法格式如下,
t#<时间声明>
一个 TIME 常数总是由一个起始符 T 或 t (或用 TIME 或 time )和一个数字标识符 # 组成。然后,是跟随的实际时间声明,包括天数( d 标识)、小时( h 标识)、分钟( m 标识)、秒( s 标识)和毫秒( ms 标识)。请注意时间各项必须根据时间长度单元顺序进行设置(d 在 h 之前,h 在 m 之前,m 在 s 之前,s 在 ms 之前),但无须包含所有的时间长度单位。
在 ST 语言的赋值语句中,正确使用时间常数的示例 :
TIME1 := T#14ms;
TIME1 := T#100S12ms; (*最高单位的值可以超出其限制*)
TIME1 := t#12h34m15s;
【例 7.9】时间类型变量的定义和使用。
VAR
tTime:TIME;
END_VAR
tTime:= T#3d19h27m41s1ms;
 
注意:
时间可以溢出,如小时可以超过 24h,如赋值时写入 T#3d29h27m41s1ms,系统会自动校正
最终输出的结果为 T#4d5h27m41s1ms。以下的时间常量赋值是不正确的。
tTime:= 15ms; (*缺少 T# *)
tTime:= t#4ms13d; (*顺序错误*)
 
  1. TIME_OF_DAY/TOD,时刻,精度为毫秒,范围从 0:0:0~1193:02:47.295。时刻声明使用 “<时:分:秒>”的格式。语法格式如下。
tod#<时间声明>
除了“tod#”,也可以使用“TOD#”、“time_of_day”、“TIME_OF_DAY”表示。
【例 7.10】时刻类型变量的定义和使用。
VAR tTime_OF_DAY:TIME_OF_DAY;
END_VAR
tTime_OF_DAY:= TOD #21:32:27.123;
上式中,表示的时间为 21 点 32 分 23 秒 123 毫秒。
  1. DATE,日期,精度为天,范围从 1970-01-01~2106-02-06。日期声明使用“<年-月-日>” 的格式。语法格式如下。
dt#<日期声明>
除了“d#”也可以使用也可以用“D#”、“date”、 “DATE”表示。
这些常数可用来输入日期。声明一个 DATE 常数时,起始符为 d 、D、DATE 或 date 后跟随一个 # 号。然后你就可按照年-月-日的格式输入任何日期。
【例 7.11】日期类型变量的定义和使用。
VAR
tDate:DATE;
END_VAR
 
tDate:=D#2014-07-09;
表示的时间为 2014 年 3 月 9 日。
  1. DATE_AND_TIME/DT,日期和时间,精度为秒,范围从 1970-01-01-00:00~2106-02-06-06:28:15。日期和时间的声明使用“<年-月-日-时:分:秒>”的格式,语法格式如下。
dt#<日期和时间声明>
除了“dt#”,也可以使用“DT#”、“date_and_time”、“DATE_AND_TIME” 表示。
【例 7.12】日期和时间类型变量的定义和使用。
VAR tDT:DATE_AND_TIME;
END_VAR
 
tDT:=DT#2014-07-09-16:22:31.223;
表示时间为 2014年3月9日16点22分31秒223毫秒。

7.7.2 标准的扩展数据类型

作为对 IEC61131-3 标准中数据类型的补充,MetaFacture隐含一些标准的扩展数据类型有联合、长时间类型、双字节字符串、引用、指针等,这些扩展类型如表7-10所示。
表7-10 IEC1131-3标准的扩展数据类型

1. 联合体

  1. 联合体概念
有时需要使几种不同类型的变量存放到同一段内存单元中,比如可以把一个 INT、一个 BYTE 和一个 DWORD 型变量放在同一地址开始的内存单元中,如表 7-11 所示,从同一地址 16#100 开始存放。
表7-11 联合体内存映射表
上表中,有填充色部分为覆盖区域,这种使几个不同的变量共占同一段内存的结构成为联合。 联合体声明的语法如下:
TYPE <联合体名>:
UNION
<变量的声明 1>
.
.
<变量声明 n>
END_UNION
END_TYPE
【例 7.13】定义一个联合体NAME,对其中的某一个成员进行赋值,试比较结果。
首先,先在数据单元类型中新建一个名为NAME的联合,选中“Application”右键-->“添加对象”-->“DUT”,如图 7.16 所示,确定后在联合编辑器中输入如下内容。
图 7.16 新建联合体
TYPE NAME :
UNION
var1:STRING(20); var2:STRING(20); var3:STRING(20);
END_UNION
END_TYPE
在程序中写的代码如下,
VAR
nName:NAME;
END_VAR
 
nName.var1:='Zhang San';
最终输出的结果如图7.17所示,即所有nName中的成员的数值都被统一的写入'Zhang San'。
图 7.17 联合实例运行结果图 1
此外,联合体内的成员数据类型可以不一样,通过如下的例子,让读者对联合体有更深一步的理解。
【例7.14】使用联合体,实现将2个字节变量整合成一个字变量的功能。首先,先在数据单元类型中新建一个名为Un_WORD的联合,内容如下,
TYPE Un_WORD : UNION wWord:WORD; nByte:ARRAY [0..1] OF BYTE;
END_UNION
END_TYPE
程序及声明如下:
VAR
UN_Word_test:Un_WORD; nByte_Low:BYTE:=16#12;
nByte_Hight:BYTE:=16#34;
END_VAR
 
UN_Word_test.nByte[0]:=nByte_Hight;
UN_Word_test.nByte[1]:=nByte_Low;
结果如图7.18中的nWord 所示。
图 7.18 联合实例运行结果图 2
在联合中使用成员nWord作为整合后WORD变量存放的地址,由于联合体内所有成员数据结果都一样的特性,决定了WORD和数组中2个BYTE的映射关系,具体映射关系如表7-12所列。
表7-12 地址映射关系表
如上nByte[1]对应nWord的低8位,nByte[0]对应nWord的高8位。所以只需在程序内,分别将2个 BYTE分别对应低8为和高8位即可实现分别将两个BYTE的数值整合在一个WORD型变量内的功能。
在图7.17中可以看出,给其中的某一个变量var1进行赋值,相当于给联合中所有的变量都进行了赋值。但需要注意到是,在此例中,NAME下的三个变量类型都定义为REAL,如类型不同,首先要确保数据占用存储空间相同,如使用不当,数据可能会出现错乱。

2. 长时间类型

提供长时间类型数据作为高精度计时器的时间基量。与TIME类型不同的是:TIME的长度为32位且精度为毫秒,LTIME的长度为64位且精度为纳秒。
LTIME:长时间,精度为纳秒。可以用LTIME#表示日期和时间,语法格式如下。
LTIME#<长时间声明>
【例 7.15】长时间类型变量赋值。
VAR
tLT:LTIME;
END_VAR
 
tLT := LTIME#1000d15h23m12s34ms2us44ns;

7. 宽字符串

与字符串类型数据(ASCII)不同的是,这一数据类型由Unicode解码。
每个字符串占的存储空间为 2*N+2;
wstr:WSTRING:='This is a WString';

4. 引用

引用是一个对象的别名。这个别名可以通过变量名读写。与指针不同的是,引用所指向的数据将被直接改变,因此引用的赋值和所指向的数据是相同的。设置引用的地址用一个特定的赋值操作完成。一个引用是否指向一个有效的数据(不等于 0),可以使用一个专门的操作符来检查,如下所示。
用以下语法声明引用,语法格式如下。
<标识符> : REFERENCE TO <数据类型>
 
【例 7.16】引用样例程序。
VAR
REF_INT : REFERENCE TO INT;
Var1: INT;
Var2 : INT;
END_VAR
 
REF_INT REF= Var1; (* 此时 REF_INT 指向 Var1 *)
REF_INT := 12; (* 此时 Var1 的值为 12 *)
Var2:= REF_INT * 2; (* 此时 Var2 的值为 24 *)
程序最终输出结果如图 7.19 所示。
图 7.19 引用的例程输出结果
此外,可以通过专用指令“__ISVALIDREF”检查变量是否已经被正确引导。
具体用法如下:
<布尔变量> := __ISVALIDREF(<数据类型>声明为 REFERENCE 类型);
如果引用指向一个有效值,则返回值<布尔变量>为真(TRUE),否则为假(FALSE)。
如上<数据类型>必须被声明为引用类型,即为“REFERENCE”,否则该指令无效。
【例 7.17】 有效引用检查的样例程序。
VAR
REF_INT : REFERENCE TO INT;
Var1: INT; Var2 : INT;
bTestRef: BOOL := FALSE;
END_VAR
REF_INT REF= Var1; (* 此时 REF_INT 指向 Var1 *) REF_INT := 0; (* 此时 Var1 的值为 12 *)
bTestRef := __ISVALIDREF(REF_INT); (* 为 TRUE,因为 rREF_INT 指向 Var1,它不等于0 *)

5. 指针

  1.   指针的概念

所谓指针就是一个地址。如果在 MetaFacture 中定义了一个变量,然后对其进行编译,系统给这个变量分配相应的内存单元。系统根据变量的类型分配相应的内存空间,如一个则会BYTE 变量,系统则为其分配 1 个字节的存储空间,如果是一个 REAL 类型变量,系统则为其分配 4 个字节的内存存储空间。内存以字节为单位,以“地址”进行编号,可以理解为酒店的房间号,地址所标志的内存单元中存放着具体的数据,这就相当于在宾馆中居住的客人。
一般而言,程序都是通过变量名来对内存单元进行存储操作的。这是因为程序经过 MetaFacture 编译后,已经将变量名转换为变量的内存地址,对变量的存储其实都是通过内存地址所进行的。如假设在程序中定义了 var1,var2 和 var3 三个变量,在声明时将其都定义为 WORD 类型,经过系统
编译后,系统分配给var1的内存空间为两个字节,地址分别为1000和1001,var2为1002和1003,var3为 1004和1005。1000 和 1001 内存中的具体数据则是var1内的具体数据,var2和var3也是相同的道理,示意图如图7.20所示。此种按变量地址存储数据的方式在高级语言中也称之为“直接访问”方式。
图 7.20 变量名对应内存地址查询变量
  1.   指针变量

至此,读者应该对指针有了初步的概念,一个变量的地址称为该变量的“指针”。如地址为1000和1001的变量是var1的指针,如图 7.20 所示。
如果有一个变量专门用来存放另一个变量的地址(指针),此时,我们则称它为“指针变量”。为了更好的理解指针变量的概念,在此举一个例子,为了打开A抽屉有两种方法,一是将A钥匙带在身上,需要时直接找出该钥匙打开抽屉,取出所需的东西,也就是之前所提到的“直接访问”。还有一种方法是,为了安全,可以将 A 钥匙放到另一个抽屉B中所起来。如果需要打开A抽屉,就需要先找出B钥匙,打开B抽屉,取出A钥匙,再打开A抽屉,取出物品。这就是所谓的“间接访问”的概念。在 MetaFacture 中使用关键字“POINT TO+类型”对指针变量进行声明。类型可以为变量、程序、功能块、方法和函数的内存地址。它可以指向上述的任何一个对象以及任意数据类型,包括用户定义数据类型。
声明指针的语法如下:
<标识符>: POINTER TO <数据类型 | 功能块 | 程序 | 方法 | 函数>;
取指针地址内容即意味着读取指针当前所指地址中存储的数据。通过在指针标识符后添加内容操作符“^”,可以取得指针所指地址的内容。通过下面的例子,希望读者对指针能有更深刻的理解。
【例 7.18】指针举例,
VAR
PointVar:POINTER TO INT;
var1:INT := 5; var2:INT;
END_VAR
PointVar := ADR(var1);
var2:= PointVar^;
图 7.21 指针示例
程序输出的结果如 7.21 所示,在声明中先定义 PointVar 变量为指针变量,该变量将来用于存储地址数据。
程序中使用了 ADR 指令,该指令是用来获取变量内存地址的操作符,执行完第一条指令后,PointVar 内就已经获取了 var1 的内存地址信息(16 进制的 13B7143A)。
PointVar^指的是该内存地址中对应的具体数据(16#13B7143A中的数据),即var1中5。第二条指令执行后,就将PointVar^赋值给了var2,故ivar2=5。
【例 7.19】使用指针,将INT型变量nIntValue中的低 8 位数据和高8位数据分别赋值给BYTE型变量nByte_low和nByte_high。
VAR
PointVar_int:POINTER TO INT;
PointVar_byte_low:POINTER TO BYTE; PointVar_byte_High:POINTER TO BYTE;
nIntValue:INT := 16#1234; nByte_low:BYTE;
nByte_high:BYTE;
END_VAR
 
PointVar_int := ADR(nIntValue);
PointVar_byte_low:=PointVar_int+1; PointVar_byte_High:=PointVar_int; nByte_high:=PointVar_byte_low^; nByte_low:=PointVar_byte_High^;
图 7.22 指针程序举例
输出结果如图7.22所示,根据要求得知原变量为WORD型,故可推算出系统分配内存时会分配给它2个BYTE的存储空间,从图7.22中可以看出,该WORD变量 nIntValue的地址为16#13B71438,故完整的内存空间应该为16#13B71438和16#13B71439。所以在程序中用到取低8位BYTE的地址时需要在原地址的基础上+1,PointVar_byte_low:=PointVar_int+1; 高8位BYTE的地址为可以直接使用 16#13B71439。最终分别将低8位字节的16#34赋值给nByte_low,高8为字节的 16#12赋值给nByte_high。
  1.   指针校验函数

当在程序中大量使用指针时,会涉及到大量内存地址数据,如使用不当,会导致严重的内存错误,故 MetaFacture 系统中自带“指针校验”函数“CheckPointer Function”。指针校验函数需要检查指针指向的地址是否在有效的存储范围之类,另外还需要检查引用的连续内存空间与指针所指的变量的数据类型是否匹配。若满足上述两个条件,指针校验应当返回这个输入指针。出现错误时则交由用户进行适当的处理。
为了在程序运行时检查指针的指向,可以在每次访问指针的地址之前使用隐含的“指针校验”功能。您可以通过添加对象对话框向应用程序中添加“用于隐含检查的 POU”对象。如图 7.23 的 a)所示,其次弹出对话框,如图 7.23 的 b)所示,,在其中选择“CheckPointer”,点击 “打开”。
a ) b)
图 7.23 添加 Check Pointer 功能
a )添加“用于隐含检查的 POU” b )选择功能
点选指针校验的复选框,选择一种实现语言,确认无误后点击打开。校验功能将会在编辑器中以您选择的语言打开。声明部分是预先设置的,与前述选项的选择无关,并且只有添加了其它局部变量之后才可以更改。与其它校验函数不同的是,没有提供指针校验函数的默认实现,这一部分需要由用户编写!

7.7.3 自定义数据类型

1. 数组

数组类型在 MetaFacture 中被大量使用,使用数组可以有效的处理大批量数据,可以大大提高工作效率。
数组是有序数据的结合,其中的每一个元素都拥有相同的数据类型。例如一台设备共有 20 个需要测量温度的点,如不使用数组,需要声明 nTemp1,nTemp2,……,nTemp20 共 20 个变量作为测量点对应的具体温度值,而此时,如使用数组,只需定义一个数组 nTemp,将它的成员定义为20 个,即可完成这 20 个温度变量的定义。具体指令如下所示。
nTemp :ARRAY [1..20] OF REAL;
中括号内的数据表示 20 个测量点,例如 nTemp[17]就表示第 17 个测量点的温度值。
在 MetaFacture中可以直接定义一维、二维和三维数组作为数据类型。您可以在 POU 的声明部分或者全局变量表中定义数组,声明数组的语法如下。
<数组名>:ARRAY [<ll1>..<ul1>,<ll2>..<ul2>,<ll3>..<ul3>] OF <基本数据类型>
ll1, ll2, ll3 表示字段范围的最小值,ul1, ul2 和 ul3 表示字段范围的最大值。字段范围必须是整数。
数组的标签描述如图 7.24 中的 a)所示。访问个数组的元素,都具有相同的形式,所以数组特别适合一个对象的多重描述,如图 7.24 中的 b)所示。
a ) b )
图 7.24 数组结构
a )数组结构 b )一维/二维/三维数组
在定义数组变量时可以借助 MetaFacture 内部的输入助手提高效率,在输入助手中选择 ”
“,点击“数组向导(A)”,如图 7.25 所示。
 
图 7.25 数组自动声明
在定义数组时,需要指定数组中元素的个数,中括号的常量表达式用来表示元素的个数,即数组长度。例如 a[1..5],表示 a 数组共有 5 个元素。
注意:
如果下标在定义时是从 0 开始的,如 a[0..5]则表示其中的元素有 a[0],a[1] ,a[2],a[3],a[4],不存数组元素 a[5] 。
由于 MetaFacture 最大支持三维数组,在点击确定后弹出的对话框中输入各维度的上限及下线并且设置基本类型,如图 7.26 所示。
图 7.26 数组输入向导
  1. 一维数组
一维数组是最常用的一种数据类型,通过如下举例对一维数组进行说明。
VAR
NumVar: ARRAY [1..5] OF INT;
END_VAR
程序运行后,结果如图 7.27 所示。
图 7.27 一维数组显示
它表示了一个元素为 INT 类型的数组,数组名为 NumVar,此数组共有 5 个元素(1~5),成员分别为 NumVar[1], NumVar[2],NumVar[3],NumVar[4],NumVar[5] 。
定义数组时,需要指定数组中元素的个数,方括号中的常量表达式表示的即为个数,也可以理解为数组的长度。如 NumVar[3]。
  1. 二维数组
二维数组可以看作是一个特殊的一维数组,它自身的元素可以理解为又是一个新的一维数组。例如,可以把 a 看作是一个一维数组,它有三个元素,a[0] ,a[1] 及 a[2],而其中的每个元素又包含 4 个元素的一维数组,如图 7.28 所示。
图 7.28 二维数组概念
可以把 a[0] ,a[1] 及 a[2]看作是一个 3 个一维数组的名字。如上所定义的二维数组可以理解为定义了 3 个一维数组,相当于:
VAR
a[0]: ARRAY [0..3] OF INT;
a[1]: ARRAY [0..3] OF INT;
a[2]: ARRAY [0..3] OF INT;
END_VAR
此处,把带有下划线的 a[0],a[1]和 a[2]看作一维数组名。使用此种方法在数组初始化和用指针表示时会显得非常方便。
【例 7.20】如下为二维数组的应用举例,
VAR
Card_game: ARRAY [1..2, 1..4] OF INT;
END_VAR
图 7.29 二维数组举例
通过如上的结果可以看出,第一维的成员有两个,第二维的成员有 4 个,故在数学上,相当于绘制了一个 2×4 的 2 维矩阵表格。
  1. 三维数组
MetaFacture允许使用多维数组,有了二维数组的基础,再掌握多维数组是不困难的,如下为三维数组定义的举例。
arr3 : ARRAY [1..2,2..3,7..4] OF INT ;
  1. 数组的初始化对数组元素的初始化可用以下方法实现。
  2. 在定义数组时对数组元素赋予初值,例如:
arr1 : ARRAY [1..5] OF INT := [1,2,3,4,5];
将数组元素的初值依次列举,经过如上的定义和初始化后,arr1[1]=1,arr1[2]=2,arr1[3]=3, arr1[4]=4,arr1[5]=5。
  1. 只给一部分元素赋值,例如:
arr1 : ARRAY [1..5] OF INT := [1,2];
定义 arr1 数组有 5 个元素,但中括号中只提供 2 个初值,这表示只有前两个元素被赋初值,没有预置的数组元素,则使用其基本类型的默认初始值进行初始化。在本例中,数组成员 arr1[3] 到 arr1[5]均被初始化为 0。
  1. 对于重复的初值,可以批量定义,只需在括号前加上数量,例如:
arr1 : ARRAY [1..5] OF INT := [1,2(3)];
“2(3)”表示 2 个 3,经过上述初始化命令后,数组的初值情况为,arr1[1]=1,arr1[2]=3, arr1[3]=3,arr1[4]=0,arr1[5]=0。
  1. 针对二维/三维数组,可以将所有数据写在中括号内,按数组排列的顺序对个元素赋初值,例如:
arr2 : ARRAY [1..2,7..4] OF INT := [1,3(7) ];
定义一个二维数组,第一个元素的初值为 1,后三个的初值为 7,最终输出的结果为, arr2[1,3]=1,arr2[1,4]=7,arr2[2,3]=7,arr2[2,4]=7。
arr3 : ARRAY [1..2,2..3,7..4] OF INT := [2(0),4(4),2,3];
最终输出的结果为,
arr3[1,2,3]=0 , arr3[1,2,4]=0 , arr3[1,3,3]=4 , arr3[1,3,4]=4 , arr3[2,2,3]=4 , arr3[2,2,4]=4 ,arr3[2,3,3]=2,arr3[2,3,4]=3。
  1. 数组的引用

数组必须先定义,然后再使用,MetaFacture 规定只能逐个引用数组元素,而不能一次引用整个数组。
数组的引用形式为:
<数组名>[Index1,Index2,Index3]
下标可以是整型常量也可以是变量表达式,例如:
a[i+1,2,2]:= a[0,1,1]+ a[0,0,0]+ a[i,2,2];
  1. 数组变量的存储结构

程序运行中,要访问数组元素时,首先需要了解数组变量在内存中的是如何存储的。如下还会介绍数组变量在 MetaFacture 的内存中是如何进行存储的。
数组变量从字边界开始,也就是说,起始地址为偶数 BYTE 地址。随后,每个结构元素以其声明时的顺序依次存储到内存中。数据类型为 BOOL,BYTE 的结构元素从偶数字节开始存储,其他数据类型的数组元素从字地址开始存储,如图 7.31 中一维数组 BYTE 和 WORD 数据类型所示。
二维数组中元素的排列顺序是按行存放的,即在内存中先顺序存放第一行的元素,在存放第二行的元素,图 7.30 表示了二维数组 a [0..2, 0..3]数组存放的顺序。
图 7.30 二维数组变量的存储结构
三维数组在内存中的排列顺序:第一维的下标变化最慢,最右边的下标变化最快。例如,假定有三维数组 a[0..1,0..2,0..3],其内部的元素的内存排列顺序为:
a[0,0,0]—> a[0,0,1] —> a[0,0,2] —> a[0,0,3] —> a[0,1,0] —> a[0,1,1] —> a[0,1,2] —> a[0,1,3] —> a[0,2,0] —> a[0,2,1] —> a[0,2,2] —> a[0,2,3] —> a[1,0,0] —> a[1,0,1] —> a[1,0,2] —> a[1,0,3] —> a[1,1,0] —> a[1,1,1] —> a[1,1,2] —> a[1,1,3] —> a[1,2,0] —> a[1,2,1] —> a[1,2,2] —> a[1,2,3]。不同维度的数组举例详见图 7.31 所示。
图 7.31 数组变量的存储结构
  1. 数组校验函数

在 MetaFacture 中,为了保证程序运行时能够正确的访问数组中的元素,需要使函数,它可以防止数组成员号超边界从而导致系统故障。用“CheckBounds”
每当向数组类型变量赋值时,该函数就会自动运行。写程序时用户唯一要做的就是添加“用于隐含检查的 POU”,接下来的一切交给系统去执行即可。
通过如下的例子对该函数进行讲解。
若工程中没有上面所述的 CheckBounds 函数,在下面的例子中,A[B]应该为 A[10],从而超出了数组 A 的最大上界值,程序编译出错。但因为工程中定义了上面的 CheckBounds 函数,所以执行下面的程序时,A[B]应该为 A[7]来使用,将布尔量 True 赋值给 A[7],编译时不出错。
图 7.32 CheckBounds 函数示例

2 结构

  1.   结构的概念
迄今为止,已经介绍了基本类型的变量(如整型、实数、字符串等),也介绍了数组,数组中的个元素属于同一个类型的。
但光有这些数据类型是不够的,有时需要将不同类型的数据组合成一个有机的整体,以便于引用。结构(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合,也叫结构体。
例如,一台电机通常都有其对应的信息,如产品型号(Product_ID)、生产厂家(Vendor)、额定电压(Nominal Voltage)、额定电流(Nominal Current)、极对数(Poles),是否带刹车(Brake)等信息。这些信息都和这台电机相关联,见表 7-12 所示。可以看出,如果将这些信息分别以独立的变量进行声明,很难反应出它们和电机的内在联系。如果有一种数据类型可以将它们组合起来的话,那就可以解决这个问题,在 MetaFacture 中结构体就能起到这样的功能。
表 7-12 电机结构参数
结构体和其他类型基础数据类型一样,例如 int 类型,char 类型,只不过结构体可以做成你想要的数据类型。以方便以后的使用。在实际项目中,结构体是大量存在的。工程人员常使用结构体来封装一些属性来组成新的类型。所以在项目中通过对结构体内部变量的操作将大量的数据存储在内存中,以完成对数据的存储和操作。结构体其最主要的作用就是封装。封装的好处就是可以再次利用。
结构体声明的语法如下:
TYPE <结构名>:
STRUCT
<变量的声明 1>
.
.
<变量声明 n>
END_STRUCT
END_TYPE
<结构名>是一种可以在整个工程中被识别的数据类型,可以像标准数据类型一样使用。
结构体可以实现嵌套,如图 7.33 所示,其中子结构也是一个结构体。
图 7.33 数据结构体
图 7.33 说明了结构体的复杂结构,其中包含了多个基本数据类型,并包含其他子结构体,数据的结构复杂程度依次从右至左递增。
  1.   结构体添加
选中“Application”右键-->“添加对象”-->“DUT”,如图 7.34 所示。
 
图 7.34 添加 DUT
打开 DUT 添加窗口后类型选择“结构(S)”点击“打开”,如图 7.35 的 a)所示。确定后,系统则会自动进入 DUT 编辑器。
a ) b )
图 7.35 结构体的创建
a )DUT 创建窗口 b )DUT 编辑器
针对图 7.33 的要求,在数据单元类型中新建一个名为 Motor 的结构体,具体内容如下,如图7.35 中的 b)所示。
TYPE Motor :
STRUCT
Product_ID:DWORD;
Vendor:STRING(20);
Nominal_Voltage:REAL;
Nominal_Current:REAL;
Poles:INT;
Brake:BOOL;
END_STRUCT END_TYPE
建立完结构体后,只需在程序中新建一个变量,类型为刚刚建立的<结构名>,即 Motor。在程序中键入“变量名.”后,系统则会自动弹出结构体内具体对应的信息,见图 7.36,通过点击鼠标选择,再配合赋值语句即可实现对结构体的读写操作。
 
图 7.36 结构体应用举例
【例 7.21】根据图 4.36 的内容给结构体赋值。
VAR
Motor_1:Motor;
END_VAR
 
Motor_1.Product_ID:=11000;
Motor_1.Vendor:='FESTO';
Motor_1.Nominal_Voltage:=380;
Motor_1.Nominal_Current:=5.2;
Motor_1.Poles:=4;
Motor_1.Brake:=TRUE;
运行结果如图 7.37 所示。
图 7.37 运行结果
  1.   结构变量的存储结构
结构变量从字边界开始,也就是说,起始地址为偶数字节地址。随后,每个结构元素以其声明时的顺序依次存储到内存中。
数据类型为 BOOL,BYTE 的结构元素从偶数字节开始存储,其他数据类型的数组元素从字地
址开始存储。

7. 结构体数组

一个结构体变量可以存放一组数据(如一个设备的安装位置、湿度、温度等数据)。如果有 10 个设备的数据需要参加运算,显然应该用数组,这就是结构体数组。结构体数组与以前介绍过的数值型数组不同支持在于每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员(分量)项。
定义结构体数组和定义结构体变量的方法相似,只需要说明其为数组即可,例如:
TYPE Machine : STRUCT
sDeviceName:STRING;
nInstallLocation:INT;
bWorkStatus:BOOL;
rHumidity:REAL;
rTemperature:REAL;
END_STRUCT
END_TYPE
 
VAR
arrMachineStatus : array [0..2]of Machine;
END_VAR
以上定义了一个数组 arrMachineStatus,数组中有 3 个元素,均为 Machine 类型数据,如图7.38 所示。
图 7.38 结构体数组举例

4. 枚举

  1. 枚举的概念
如果一种变量有几种可能的值,可以定义为枚举类型。所谓“枚举”是将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。例如,必须定义一个变量,该变量的值表示一周中的一天。 该变量只能存储七个有意义的值。 若要定义这些值,可以使用枚举类型。如一周内星期可能取值的集合为:
{Sun,Mon,Tue,Wed,Thu,Fri,Sat}
该集合可定义为描述星期的枚举类型,该枚举类型共有七个元素,因而用枚举类型定义的枚举变量只能取集合中的某一元素值。枚举类型声明的语法如下:
TYPE <标识符>:
(<Enum_0> ,
<Enum_1>,
...,
<Enum_n>) |<基本数据类型>;
END_TYPE
<基本数据类型>为可选项,如果不填写数据类型,系统默认为 INT 类型。
Workday 和 Week-end 被定义为枚举变量,它们的值只能是 sun 到 sat 之一。例如:
Workday:=mon; Week-end:=sun;
这些是用户定义的标识符。这些标识符并不自动地代表什么含义。例如,不能因为写成 sun,就自动代表“星期天”。其实不写 sun 而写成 Sunday 也可以。用什么标识符代表什么含义,完全由程序员决定,并在程序中做相应处理。
  1. 枚举类型的基本数据类型-默认是 INT 类型,也由用户明确指定,例如:
TYPE BigEnum : (yellow, blue, green:=16#8000) DINT;
END_TYPE
  1. 枚举值可以直接用来做判断条件,例如:
IF nWeekday>5 THEN nWeekday:=0;
  1. 在 MetaFacture 中,整数可以直接赋给一个枚举变量,例如:
nWeekday:=10;
  1. 同样的 POU 内,同样的枚举值不得用两次,在枚举或在所有枚举内,例如:
TRAFFIC_SIGNAL: (red, yellow, green);
COLOR: (blue, white, red);
Error: red may not be used for both TRAFFIC_SIGNAL and COLOR..
【例 7.22】使用枚举类型,将一周中的 7 天按照 Sun,Mon,Tue,Wed,Thu,Fri,Sat 的形式显出,并每个程序周期进行一次变化。
TYPE Weekday :
(Sun:=0,
Mon:=1, Tue:=2,
Wed:=3,
Thu:=4,
Fri:=5,
Sat:=6);
END_TYPE
VAR
nWeekday:Weekday;
END_VAR
IF nWeekday>5 then nWeekday:=0;
ELSE
nWeekday:=nWeekday+1;
END_IF
输出结果如图 7.39 所示,该例程可在\ 01_ENUM_Week\ 打开。
图 7.39 枚举类型变量应用举例

5. 子范围

子范围是一种用户自定义类型,该类型定义了某种数据类型的取值范围。其值的范围是基本数据类型的一个子集。如取值从 1 到 10 或从 100 到 1000 等。
声明子范围类型时,先确定基本类型(整型),再提供该类型的两个常数。子范围声明的语法如下:
<标识符> : <数据类型> (<下限>..<上限>);
<数据类型>以下数据类型中的一个:SINT、USINT、INT、UINT、DINT、UDINT、BYTE、WORD、DWORD。
<下限>定义了该数据类型的下限,下限本身也属于这个范围。
<上限>定义了该数据类型的上限,上限本身也属于这个范围。
子范围的实际使用例如:
VAR
nPosition:INT(0..90);
END_VAR
nPosition:=99;
如果使用上式进行编译,编译会出现错误“C0032:不能将类型’99’转换为类型’INT(0..90)’”99 已经超出所定义的子范围的范围。为了防止这种现象在程序运行中出现,MetaFacture 拥有对应的范围边界函数来解决。
如果通过一个中间变量在进行赋值将 99 间接的赋值给 nPosition 则编译系统不会报错,但 99 其实已超过了 nPosition 原始定义的边界,故 MetaFacture 为了对数据的边界进行绝对的保障,防止超限,定义了范围边界校验函数(Range boundary Checks)对变量的边界进行校验。
使用此函数需要在项目中“添加对象”,选择“用于隐含检查的 POU ”,将 “CheckRangeSigned”和“CheckRangeUnsigned”的勾都打上,然后重新编译程序,再登入即可,在此过程中,对新添加的函数不需要进行任何修改。通过下面的例子对该函数进行说明。
VAR
nPosition:INT(0..90);
nPosition1:INT;
END_VAR
nPosition1:=99; nPosition:=nPosition1;
图 7.40 范围边界校验
程序运行后,结果如图 7.40 所示。从图可以看出,nPosition 的值最后为 90,确保了数据在 0~90 的范围内。
该数据类型被广泛的应用,如在针对模拟量输出模块时,需要对输出电压/电流进行限制,则可以直接采用此数据类型进行声明,可以对数据进行软件有效的钳位。
【例 7.23】通过定义子范围类型变量防止程序出现死循环。
VAR
nCounter : UINT (0..10000);
END_VAR
 
FOR nCounter:=0 TO 10000 DO
...
END_FOR
由于校验函数确保了变量 nCounter 的值绝对不会超过 10000,故程序永远无法跳出这个 FOR 循环。该功能类似于 LIMIT 函数。

7.4 变量的类型和初始化

MetaFacture 根据 IEC 61131-3 标准对变量定义了属性。通过设置变量属性将他们的有关性能赋予变量。变量可根据他的应用范围进行分类,如表 7-13,此外变量类型之外,MetaFacture 还提供了变量的附加属性,在表 7-14 中已罗列。

7.4.1 变量的类型

MetaFacture 所支持的变量类型如表 7-13 所示:

表7-13 变量的类型

VAR_EXTERNAL 外部变量,能在程序内部修改,但需由全局变量提供
VAR、VAR_INPUT、VAR_OUTPUT 和 VAR_IN_OUT 是在程序组织单元(POU)中被应用的最多的几种变量类型。
VAR_GLOBAL 全局变量在实际的工程项目中也需要被大量的使用。
变量的属性:
在 MetaFacture 中,支持的变量属性在表 7-14 中已罗列。

表7-14 变量的属性一览

RETAIN

以关键字 RETAIN 声明类型变量。RETAIN 型变量在控制器正常关闭、打开(或收到在线命令 “热复位”),甚至意外关闭之后这类变量仍然能保持原来的值。随着程序重新开始运行,存储的值能继续发挥作用。
RETAIN 类型变量声明格式如下:
VAR RETAIN
<标识符>:<数据类型>;
END_VAR
但 RETAIN 变量在“初始值位”、“冷复位”和程序下载之后将会重新初始化
内存存储位置: RETAIN 型变量仅仅被存储在一个单独的内存区中。
在实际的工程应用中,如生产线上的计件器便是一个典型的例子:电源被切断之后,它仍然可以在再次启动时继续计数。而其它所有变量此时都将被重新初始化,变为指定初始值或标准初始化的值。
PERSISTENT
目前只有少数 PLC 还保留独立的内存区域用于存放 PERSISTENT 类型数据,在 MetaFacture 中,取消了其原掉电保持的功能,取而代之的是通过VAR RETAIN PERSISTENT 或 VAR PERSISTENT RETAIN 来实现,两者从功能上完全一样。
PERSISTENT 类型变量声明格式如下:
VAR GLOBAL PERSISTENT RETAIN
<标识符>:<数据类型>;
END_VAR
内存存储位置:与 RETAIN 变量一样,RETAIN PERSISTENT 和 PERSISTENT RETAIN 变量也存储在一个独立的内存区中。
CONSTANT
常量,在程序运行过程中,只能对其读取数据而不能进行修改的量称之为常量,关键字为CONSTANT。可以将常量声明为局部常量,也可以为全局常量。
CONSTANT 常量声明格式如下。
VAR CONSTANT
<标识符>:<数据类型> := <初始化值>;
END_VAR
在实际应用中,通常可以将一些重要参数或系数设为常量,这样可以有效的避免其他变量对其修改最终影响系统整体稳定性及安全性。举例如下。
VAR CONSTANT
pi:REAL:= 7.1415926;
END_VAR
程序一旦开始运行,通过 CONSTANT 声明的变量在程序运行过程中,是不允许被修改的,如强制修改系统会出现如图 7.41 所示的系统错误。
图 7.41 常量强制写入数据错误

7.4.2 变量的初始化

在程序中,有时需要对一些变量预先赋予初值,MetaFacture 允许在定义变量时对变量进行赋初值处理。在赋值运算符“:=”的左边是变量及变量类型,该变量接受右边地址或表达式的值如:
VAR
bBoolValue:BOOL:=TRUE; (*定义 bBoolValue 为 BOOL 变量,初值为 TRUE*)
rRealValue:REAL:=7.1415926; (*定义 rRealValue 为 REAL 变量,初值为 7.1415926*)
nIntValue:INT:= 6; (*定义 nIntValue 为 INT 变量,初值为 6*)
strValue:STRING:='Hello MetaFacture'; (*定义 strValue 为 STRING 变量,初值为 Hello MetaFacture *)
END_VAR
此外,也可以对同一类型的多个变量进行同时赋初值,例如:
bBoolValue1,bBoolValue2,bBoolValue3,bBoolValue4:BOOL:=TRUE;
其运行结果为当程序运行后,bBoolValue1,bBoolValue2,bBoolValue3,bBoolValue4 这四个变量的初值都为 TRUE。
数组,结构体等赋初值相对复杂,数组的赋初值如下。
VAR
arr1 : ARRAY [1..5] OF INT := [1,2,3,4,5];
arr2 : ARRAY [1..2,7..4] OF INT := [1,3(7)]; (*初值为 1,7,7,7 的缩写形式 *)
arr3 : ARRAY [1..2,2..3,7..4] OF INT := [2(0),4(4),2,3]; (*初值为 0,0,4,4,4,4,2,3 的缩写形式 *)
END_VAR
结构体可以只对其中部分的成员进行初始化,针对例 4.x 中的结构体在程序中进行赋值,声明如下:
VAR
Motor_1:Motor:=(Vendor:='FESTO',Brake:=TRUE); (*Vendor 的初值为 FESTO,Brake 的初值为
TRUE *)
END_VAR
用户自定义的初值只在控制器刚启动后的第一个任务周期对该变量会写入一次,在程序中如果直接在变量声明区修改了初始值, “登入到”PLC 后选择 “在线修改”并不会对该变量的数据有任何改变,只有在 PLC 复位后重新运行程序才有用。

7.5 变量声明及字段指令

7.5.1 变量匈牙利命名法

匈牙利命名法是一种编程时的命名规范。它是由 1972 年至 1981 年在施乐帕洛阿尔托研究中心工作的程序员查尔斯·西蒙尼发明。此人后来成了微软的总设计师。其基本原则是:变量名=属性+类型+对象描述,其中每一对象的名称都要求有明确含义,可以取对象名字全称或名字的一部分。命名要基于容易记忆,容易理解的原则。保证名字的连贯性是非常重要的。
MetaFacture 中的所有标准库也采用匈牙利命名法则。在声明变量、用户自定义数据类型和创建POU(函数、功能块、程序)时定义标识符。为了使标识符的名称尽量不与其他名称重复,除了必须遵守的事项之外,您可能还需要参考以下一些建议。

1. 变量命名

给应用程序和库中的变量命名时应当尽可能地遵循匈牙利命名法。每一个变量的基本名字中应该包含一个有意义的简短描述。
基本名字中每一个单词的首字母应当大写,其它字母则为小写,例如:FileSize。
再根据变量的数据类型,在基本名字之前加上小写字母前缀。请看表 7.15 中列出的一些特定数据类型的推荐前缀和其它相关信息。
每一个变量的基本名字中应该包含一个有意义的简短描述;
基本名字中每一个单词的首字母应当大写,其它字母则为小写;
依据变量的数据类型,在基本名字之前加上小写字母前缀。
表7-15 匈牙利标准类型变量命名法
在嵌套声明中,按照声明顺序连接前缀,例如:
pabyTelegramData: POINTER TO ARRAY [0..7] OF BYTE;
根据表 7-15 中可以得知, p:表示指针。 a:表示数组。
by:表示 BYTE 配型。
TelegramData:表示变量名。

2. 程序、功能块和函数的命名

在 MetaFacture 中除了有标准变量,还有程序、功能块、函数及全局变量变量,数据结构等,他们的命名标准也有供参考的法则。
每种数据的命名(如程序组织单元,数据结构,全局变量列表等)总以它相对应的前缀开始,如程序 Program 用“PRG_”开始,功能块 Function Block 则以“FB_”前缀开始,函数 Function 则以 “FUN_”或“FC_”,“FUNC_” 前缀开始定义,全局变量列表则以“GlobVar”前缀进行定义,不同的功能逻辑部分用“_”来进行分割,,具体格式可参考表 7-16 所示。
表7-16 程序、功能块及函数的命名法则
前缀
举例
Program
PRG_
PRG_ManualControl,
FunctionBlock
FB_
FB_VerifyComEdge
Function
FC_,FUN_
FUNC_
FC_Scale
ListofGlobalVariables
GlobVar
GlobVar_IOMapping,GlobVar_Remote
 

7.5.2 字段Pragma指令

通过 Pragma 命令可以改变一个或几个变量的字段,而这些字段影响着编译和预编译过程。这就意味着 Pragma 命令可以影响代码的生成。
例如,它可以决定是否对某个变量进行初始化和监控、是否将其加入参数列表或符号表设置,也可以决定是否令其在库管理器中可见。用户可以令系统在编译过程中输出信息,也可以选择条件 pragmas。这些条件 pragma 定义了如何根据各种特定条件来处理变量。用户还可以把这些 pragma 作为“定义”输入到特定对象的编译属性中。
把字段 Pragmas 指定给签名之后,可以影响编译和预编译(如代码的生成)过程。其语法格式为{Attribute ‘<COMMAND>‘ } 。详细的命令表如表 7-17 所示。
表7-17 字段Pragma命令表
 
【例 7.24】使用 Displaymode 指令,将变量 a 使用 10 进制的方式显示,变量 b 用 16 进制显示。
VAR
{attribute 'displaymode':='hex'}
a : INT:=44 ;
{attribute 'displaymode':='dex'}
b : INT:=44;
END_VAR
图 7.42 常量强制写入数据错误
其最终的运行结果如图 7.42 所示。运行结果同样是 44,变量 a 直接用 16 进制的方式显示为 16#2C,而变量 b 继续为默认的 10 进制的显示方式。通过在监视表中单击鼠标右键也能修改显示模式,但是两者的区别是 Displaymode 指令能够将每个独立的变量进行 16/10/2 进制显示,而在监视表中修改显示模式则是批量的修改,一经修改,整个项目的所有变量都会相应修改,效果如图 4.43 所示。
图 7.43 监视表中设置显示模式
【例 7.25】隐含初始化指令。
VAR
A: STRING:='Hello';
B: INT:=9;
{attribute 'noinit'}
C: STRING:='Hello';
{attribute 'noinit'}
D: INT:=9;
END_VAR
当程序运行后,虽然在程序声明中已经赋有初值,但因为有了隐含初始化指令,故最终忽略赋初值指令,最终效果如图 7.44 所示。
图 7.44 隐含初始化指令
最近修改: 2025-11-20