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
2
3
4
5
6
7
8
9
//指向db2.dbx12.0
P# DB2.DBX12.0

//也可以不带P#
DB2.DBX12.0
M12.0

//指向M12.0
P# M12.0

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
2
3
4
5
6
//从db2.dbx12.0开始,往后数100个字节
P# DB2.DBX12.0 BYTE 100
P# DB2.DBB12 BYTE 100

//从M12.1开始,往后数10个位长度
P# M12.1 BOOL 1O

也可用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;
  • 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+b
  • ref不支持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的属性始终是优化访问的。
    创建如下图:

    .选择想要建立的数据类型,基本类型和复杂类型都可以
    .选择需要的数组限值
    .它用来存初始化阶段建立的类型和数量,后期只能修改数组元素个数。适合用来做固定不变的,比如物料信息一类的存储。

  1. 数组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);
  1. 在数组DB的属性设置中,有一个勾选项叫做仅存储在装载存储器中。这个是为了不占用数据工作存储器资源而设置的。因为如配方一类的数据量会比较大,用工作存储器存太浪费了,存在装载存储器中,内存不足还可以更换。
    选择数据工作存储器之后,两个指令要换成ReadFromArrayDBLWriteToArrayDBL
  • 动态数组
    动态数组(可变数组)的结构如下:
    1
    Array [*] of <数据类型>
  1. 动态数组可以被定义为多维化。

  2. 动态数组因为是基于引用做的,一般只能用In/InOut类型接口创建。

  3. 使用案例
    在创建FB的时候,有些时候可能并不是很清楚外部传入的数组长度到底是多长的(取决于外部调用FB时连接的管脚)。但是如果某一个计算需要用到数组边界(比如算平均值或者其他的),这时候动态数组的灵活性就显现出来了。我们可以用LOWER_BOUNDUPPER_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。以后再讲。


PLC指针和引用类型
http://example.com/2024/07/21/PLC指针和引用类型/
作者
xiao cuncun
发布于
2024年7月21日
许可协议