PLC指针和引用类型
使用环境是PLC1500,在1200中可能某些数据类型不可用。
Pointer
Any
Variant
References
数组,数组DB,动态数组
1. Pointer
Pointer实际上会占用6个Bytes地址空间(一个单字指针+一个双字指针组合)
。前两个byte用来放DB块号(B#16#81表示I,82表示Q,83表示M,84表示DB,等等)或者0,后面四个放数据区,字节地址,位地址。
写法如下:
1 |
|
Pointer可以配合AT
使用拆分输入的地址区:
.将输入的Pointer映射给DataStruct的结构。
.DataStruct并不会在FB/FC接口中表现出来。
.如果实际的In_Data比DataStruct长,则DataStruct只会映射出自己已有的内容。
.如果实际的In_Data比DataStruct短,则DataStruct会全部映射In_Data,多出来的部分保持初始值。
2. Any
Any和Pointer有点相似,但是Any不一样的是它带有数据长度
。
Any实际上会占用10个Bytes地址空间
,分别由数据类型,数据长度,DB块号,存储器开始地址组成。
写法如下:
1 |
|
也可用AT
拆分Any指针:
3. Variant
- 相比于Pointer和Any,Variant无疑是更先进和占优的。前两者本质还是对一个简单数据类型的绝对地址寻址,如果是被优化块,或者是一样STRUCT和UDT一类复杂类型,他们就显得要麻烦一些。
- Variant可以指向基本的数据类型(BOOL,INT,WORD)。
- Variant不占用任何背景数据块或者工作存储器的空间,因为Variant的变量不是一个对象,而是实实在在对变量的
引用
。也正是因为如此,所以它不能在函数的static部分被定义,只能定义在输入输出等接口区域或者temp区域。
如图,建立了两个Variant类型变量,它们可以指向任何数据。
有一些常用的指令来处理Variant:
TypeOf()
和VariantGet()
和VariantPut()
1
2
3
4
5
6
7
8
9
10
11//判断time相加
IF TypeOf(#I_var1) = Time AND TypeOf(#I_var2) = Time THEN
VariantGet(SRC := #I_var1,
DST => #statTime1);
VariantGet(SRC := #I_var2,
DST => #statTime2);
VariantPut(SRC:=#statTime2,
DST:=#inout_var);
#out_time := #statTime1 + #statTime2;
END_IF;TypeOfelements()
和CountOfelements
不常用,主要配合数组使用。使用
DB_ANY_TO_VARAINT
把DB_ANY变成VARIANT,然后自动判断引用对象类型。- 在某些场景中,比如物料信息,MES下发报文给PLC的时候,需要PLC自己去判断是哪个类型。在这种情况下自己建立几个表达物料类型的UDT,然后在DB中实例化。当报文进来的时候,去判断相应的类型然后做相应的事件,代码如下:
1
2
3
4
5
6
7
8
9
10//DB_ANY_to_Varaint
#inout_var := DB_ANY_TO_VARIANT(in := #I_DB_Any, err => #statError);
IF #statError = 0 THEN
CASE TypeOf(#inout_var) OF
"_PracticeDBB".UDT_Type_A: //自建类型1
;
"_PracticeDBB".UDT_Type_B: //自建类型2
;
END_CASE;
END_IF;
- 在某些场景中,比如物料信息,MES下发报文给PLC的时候,需要PLC自己去判断是哪个类型。在这种情况下自己建立几个表达物料类型的UDT,然后在DB中实例化。当报文进来的时候,去判断相应的类型然后做相应的事件,代码如下:
用
IS_ARRAY
指令判断是否是数组1
2
3
4
5
6//2.is_array
IF IS_ARRAY(#I_var1) THEN
//...
#temp := 0;
END_IF;
4. References
在优化块中使用Ref()
。
使用范围:
- FC:Input/Output/Temp/Return
- FB:Temp
- OB:Temp
要进行引用声明用
REF_TO
,指定被引用变量所需要的数据类型,不支持bool,如图:使用ref
1
2
3
4
5//ref
//#temp_int是temp区的int类型数据;#my_int是static区的int数据类型
#temp_int := #my_int;//不使用ref直接赋值
#my_ref_int:=#my_int; >>> Error //使用ref后不能直接赋值了,这句话在TIA portail中显示报错。
#my_ref_int := REF(#my_int);//可以使用ref()引用.
#my_ref_int:=#my_int
在博图中是编程错误的。ref的引用场景
用^
解引用,完成地址到值得转换。类似C语言中的p和*p。
建立如下变量:
对应测试代码如下:1
2
3
4
5
6//ref应用
#c := #a + #b;//此处c=15
#my_ref_int := REF(#a);//把#a的地址给引用
#my_ref_int^ := 20;//因为引用类型指向#a,所以现在#a=20
#c := #a + #b;//由此,#c=25
#c := #my_ref_int^ + #b;//等同于c=a+bref不支持ARRAY[*]
。尝试用
?=
指令将varaint分配给一个ref,可以使得程序更加灵活。1
2
3
4
5//尝试结合varaint使用
#my_ref_UDT ?= #inout_var;
IF #my_ref_UDT <> null THEN
#my_ref_UDT^.Array_byte[15] := #my_UDT.Array_byte[15];
END_IF;
5. 数组,数组DB,动态数组
- 数组本身没啥好说的,常用数据类型,格式如下:
1
Array [n..j] of <数据类型>
- 数组DB
在项目新添加块中添加自己想要的数组DB。注意的是数组DB的属性始终是优化访问
的。
创建如下图:.选择想要建立的数据类型,基本类型和复杂类型都可以
.选择需要的数组限值
.它用来存初始化阶段建立的类型和数量,后期只能修改数组元素个数
。适合用来做固定不变的,比如物料信息一类的存储。
- 数组DB的两个重要方法
ReadFromArrayDB
WriteToArrayDB
1
2
3//数组DB
#TmpInt:=ReadFromArrayDB(db := "my_array_DB", index := 10,value =>#my_IM0);
#TmpInt:=WriteToArrayDB(db := "my_array_DB", index := 5, value := #my_IM0);
- 在数组DB的
属性
设置中,有一个勾选项叫做仅存储在装载存储器中
。这个是为了不占用数据工作存储器
资源而设置的。因为如配方一类的数据量会比较大,用工作存储器存太浪费了,存在装载存储器中,内存不足还可以更换。
选择数据工作存储器
之后,两个指令要换成ReadFromArrayDBL
和WriteToArrayDBL
。
- 动态数组
动态数组(可变数组)的结构如下:1
Array [*] of <数据类型>
动态数组可以被定义为多维化。
动态数组因为是基于引用做的,一般只能用In/InOut类型接口创建。
使用案例
在创建FB的时候,有些时候可能并不是很清楚外部传入的数组长度到底是多长的(取决于外部调用FB时连接的管脚)。但是如果某一个计算需要用到数组边界(比如算平均值或者其他的),这时候动态数组的灵活性就显现出来了。我们可以用LOWER_BOUND
和UPPER_BOUND
来动态化我们的边界值。需要注意的是,计算出来的边界值是外部连接的固定数组的边界值,相当于外部管脚连接了数组长度就固定了,并不是高级语言里面的那种动态分配内存
的概念,我因理解错误在这里踩过坑
代码如下:1
2
3
4//动态数组方法
//DIM表示数组维度,1=一维数组
#L_value := LOWER_BOUND(ARR := #array_var, DIM := 1);
#U_value := UPPER_BOUND(ARR := #array_var, DIM := 1);
6. 总结
这就是一些指针数据类型的应用了,其实PLC里面还有些指令是用来操作地址的,比如PEEK和POKE
。以后再讲。