4、AALC数据类型与变量:基本数据类型、用户自定义数据类型、变量作用域与生命周期

数据类型和变量,是写控制逻辑的基石。说白了,你连数据长什么样、能存多大、活多久都搞不清楚,那程序跑起来肯定要出乱子。我见过不少新手,上来就写逻辑,结果因为数据类型不匹配,现场设备直接罢工。今天咱们就把这块彻底捋清楚。

4.1 基本数据类型:BOOL、INT、REAL 等

AALC 的基本数据类型,跟大多数工业控制器差不多。但有几个细节,我建议你特别留意。

类型 关键字 位宽 取值范围 典型用途
布尔 BOOL 1 bit TRUE / FALSE 开关信号、状态标志
字节 BYTE 8 bit 0 ~ 255 原始数据、通信字节
WORD 16 bit 0 ~ 65535 寄存器值、位组合
双字 DWORD 32 bit 0 ~ 4294967295 累加器、时间戳
短整数 SINT 8 bit -128 ~ 127 小范围计数
整数 INT 16 bit -32768 ~ 32767 常规计数、索引
双整数 DINT 32 bit -2147483648 ~ 2147483647 大范围计数、位置
实数 REAL 32 bit ±1.18e-38 ~ ±3.4e38 模拟量、PID 运算

嗯,这里要注意。很多工程师习惯用 INT 做所有整数运算。但我在项目中遇到过,一个计数器的值超过了 32767,结果直接溢出,设备乱跳。从那以后,凡是可能超过 1000 的计数,我统一用 DINT。你想想看,多花一个字节,换来的是安全,值不值?

核心原则:能用 DINT 就别用 INT,能用 REAL 就别用 INT 做除法。数据类型的选型,决定了你程序的鲁棒性。

4.2 用户自定义数据类型:STRUCT 与 ENUM

基本类型够用吗?说实话,写小项目还行。一旦项目大了,比如一个工位有几十个参数,你再用一堆散乱的 INT、BOOL,那代码简直没法看。这时候,STRUCT 和 ENUM 就派上用场了。

4.2.1 STRUCT(结构体)

STRUCT 就是把多个相关数据打包成一个整体。我个人习惯,把每个电机、每个阀、每个传感器的数据都定义成 STRUCT。这样不仅代码整洁,而且复用性极高。

// 定义一个电机数据结构
TYPE MotorData :
STRUCT
    Speed        : INT;      // 当前转速
    Torque       : REAL;     // 当前扭矩
    Temperature  : INT;      // 绕组温度
    RunHours     : DINT;     // 累计运行时间
    IsRunning    : BOOL;     // 运行状态
    FaultCode    : BYTE;     // 故障代码
END_STRUCT
END_TYPE

// 声明变量
VAR
    ConveyorMotor : MotorData;
    PumpMotor     : MotorData;
END_VAR

// 使用
ConveyorMotor.Speed := 1500;
IF ConveyorMotor.Temperature > 85 THEN
    ConveyorMotor.FaultCode := 16#A1;
END_IF

我曾经接手过一个项目,前任工程师用了几十个散落的变量来管理 8 台电机。我花了三天时间,全部重构为 STRUCT。结果呢?代码量减少了 40%,而且新同事上手快多了。这就是 STRUCT 的魅力。

4.2.2 ENUM(枚举)

ENUM 用来定义一组有名字的常量。比如设备状态、操作模式、故障类型。用 ENUM 代替魔法数字,代码可读性直接拉满。

// 定义设备状态枚举
TYPE MachineState :
(
    Idle       := 0,
    Running    := 1,
    Paused     := 2,
    Error      := 3,
    EmergencyStop := 4
) BYTE;
END_TYPE

// 声明变量
VAR
    CurrentState : MachineState;
END_VAR

// 使用
CASE CurrentState OF
    MachineState#Idle:
        // 待机逻辑
    MachineState#Running:
        // 运行逻辑
    MachineState#Error:
        // 错误处理
END_CASE

我的小技巧:ENUM 的底层类型我习惯用 BYTE 或 SINT,省内存。但如果你枚举值超过 127,记得用 INT。别问我怎么知道的——有一次我枚举了 150 个状态,用了 SINT,结果编译报错,查了半天。

4.3 变量作用域与生命周期

变量不是随便定义的。它在哪里能用,能活多久,这些都有讲究。搞不清楚作用域,程序跑飞了都不知道原因。

4.3.1 变量作用域

AALC 的作用域分三种:局部、全局、模块级。

作用域 关键字 可见范围 生命周期
局部变量 VAR 仅当前 POU(程序组织单元) POU 执行期间
全局变量 VAR_GLOBAL 整个项目 程序运行全程
模块变量 VAR(在 FB 内) 当前功能块实例 功能块实例存活期间

为什么会这样设计?你想想看,如果所有变量都是全局的,那一个地方改了,所有地方都受影响。调试的时候,你根本不知道是谁动了你的数据。所以,我的原则是:能用局部,绝不用全局。

避坑指南:我曾经在一个项目中,为了图方便,把 50 多个变量都定义成了全局。结果现场调试时,一个定时器中断里改了某个全局标志,主程序完全不知道,导致设备顺序错乱。查了整整两天才找到原因。从那以后,全局变量我严格控制,每个全局变量都要写注释说明「谁写、谁读」。

4.3.2 变量生命周期

生命周期,说白了就是变量从出生到死亡的时间。

  • 局部变量:每次进入 POU 时创建,退出时销毁。注意,它不会保持上次的值。如果你需要保持,要用 RETAIN 关键字。
  • 全局变量:程序启动时创建,程序停止时销毁。全程有效。
  • 功能块内部变量:功能块实例化时创建,实例销毁时释放。每个实例都有自己的副本。
  • 保持型变量:用 RETAIN 修饰,断电后数据不丢失。常用于掉电保持的场景。
// 保持型变量示例
VAR_GLOBAL RETAIN
    TotalProductionCount : DINT;  // 总产量,断电不丢失
    LastRecipeID         : INT;   // 上次使用的配方号
END_VAR

重要提醒:RETAIN 变量不是无限用的。每个控制器都有保持型内存上限。我见过有人把整个数据库都声明成 RETAIN,结果内存爆了,程序下载都下不进去。合理规划,只保持真正需要的数据。

4.4 知识体系结构图

下面这张图,把本章的知识脉络串起来了。你可以把它当作一个快速索引。

AALC 数据类型与变量知识体系 基本数据类型 用户自定义数据类型 作用域与生命周期 BOOL INT / DINT REAL BYTE/WORD STRUCT(结构体) ENUM(枚举) 局部变量(VAR) 全局变量(VAR_GLOBAL) 保持型变量(RETAIN) 实践要点:选型合理、封装复用、作用域最小化、RETAIN 节制使用 数据类型是程序的骨架 选对类型,程序就成功了一半

好了,数据类型和变量这块,咱们就聊到这儿。记住一句话:选对类型,程序就成功了一半。剩下的,就是逻辑本身了。


公众号:蓝海资料掘金营,微信deep3321