3、数据类型与变量:基本数据类型(BOOL, INT, REAL, TIME),用户自定义数据类型(UDT),全局变量与局部变量。

各位好,咱们今天聊聊数据类型和变量。这玩意儿,说白了就是PLC程序的“砖瓦”。砖瓦没选对,墙砌得再高也得塌。我见过太多新手,甚至一些老手,在这上面栽跟头。

你想想看,一个BOOL变量你非要用INT去存,不是不行,但浪费内存不说,别人看你的程序还得猜半天。咱们做工程,讲究的是“精准”和“规范”。

3.1 基本数据类型:PLC的“原子”

基本数据类型,就是PLC能认识的最基础的数据单元。就像化学里的元素周期表,你得先认识它们。

数据类型 关键字 位宽 取值范围 典型用途
布尔型 BOOL 1位 TRUE / FALSE 开关、按钮、指示灯、状态位
整型 INT 16位 -32768 ~ 32767 计数器、小范围数值、索引
浮点型 REAL 32位 ±1.18e-38 ~ ±3.40e+38 模拟量、PID运算、温度压力
时间型 TIME 32位 0ms ~ 24天20小时31分23秒647ms 定时器设定值、延时时间

核心要点: 选型时,能用BOOL别用INT,能用INT别用REAL。REAL运算会占用更多CPU资源,而且有精度问题。我在一个高速包装机上吃过亏,用REAL做累加,结果小数点后几位累积误差导致定位不准。后来全改成DINT,问题就解决了。

3.1.1 BOOL:最基础的“是”与“否”

BOOL变量,就两个值:TRUE和FALSE。在PLC里,它对应一个“位”。

我个人习惯,把所有BOOL变量命名时都加上“b”前缀,比如 bStartButtonbMotorRunning。这样一眼就能看出这是个开关量。

// 声明一个BOOL变量
VAR
    bConveyorRun : BOOL := FALSE;  // 传送带运行状态,初始为停止
END_VAR

// 使用
IF bConveyorRun THEN
    // 传送带正在运行,执行相关逻辑
ELSE
    // 传送带停止
END_IF

3.1.2 INT:整数世界的主力军

INT是16位有符号整数。在大多数中小型项目中,它够用了。但要注意,别让它溢出。

我曾经遇到一个案例:一个计数器用INT,计数值到了32767后,再加1就变成了-32768。结果设备莫名其妙地反转。排查了半天,才发现是数据类型溢出了。从那以后,凡是可能超过10000的计数,我全用DINT(32位整数)。

VAR
    iProductCount : INT := 0;  // 产品计数
END_VAR

// 计数逻辑
iProductCount := iProductCount + 1;

// 安全处理:防止溢出
IF iProductCount > 32000 THEN
    iProductCount := 0;  // 或者报警
END_IF

3.1.3 REAL:模拟量的“翻译官”

REAL是浮点数,用来处理带小数点的数值。比如温度25.5℃,压力0.35MPa。

嗯,这里要注意:REAL不能用来做精确的等于比较。因为浮点数在计算机里是近似存储的。你写 IF MyReal = 0.1 THEN,很可能永远不成立。

避坑指南: 我曾经在PID调节时,用REAL做积分项累加。结果因为浮点精度问题,积分项始终无法归零,导致系统有静差。后来改用DINT做累加,最后再转成REAL输出,问题才解决。所以,做精确计算时,尽量用整数,最后再转换。

VAR
    rTemperature : REAL := 25.0;  // 温度值
END_VAR

// 正确的比较方式:用范围判断
IF rTemperature >= 24.9 AND rTemperature <= 25.1 THEN
    // 认为温度在25℃附近
END_IF

3.1.4 TIME:时间管理的利器

TIME类型,专门用来存时间长度。比如定时器设定值T#5s,延时时间T#100ms。

我个人习惯,所有时间常量都用TIME类型,而不是用INT乘以1000。这样代码可读性高,别人一看就知道是时间。

VAR
    tDelayTime : TIME := T#2s500ms;  // 延时2.5秒
    tElapsed   : TIME := T#0ms;      // 已过时间
END_VAR

// 使用TON定时器
TON_1(IN := bStart, PT := tDelayTime, Q => bDone, ET => tElapsed);

3.2 用户自定义数据类型(UDT):你的“乐高积木”

基本数据类型就像单个的乐高颗粒。但你要搭一个复杂的机器人,总不能一个个颗粒去拼吧?UDT就是让你把多个颗粒拼成一个“模块”。

说白了,UDT就是结构体。你可以把相关的数据打包在一起。

我的经验: 在大型项目中,我几乎每个设备都会定义一个UDT。比如一个电机,我会把它的启动按钮、停止按钮、运行反馈、故障信号、电流值、转速值全部打包成一个 Motor_DataType。这样管理起来,清爽得很。

// 定义一个电机数据类型的UDT
TYPE Motor_DataType :
STRUCT
    bStartCmd    : BOOL;   // 启动命令
    bStopCmd     : BOOL;   // 停止命令
    bRunFeedback : BOOL;   // 运行反馈
    bFault       : BOOL;   // 故障信号
    rCurrent     : REAL;   // 电流值
    rSpeed       : REAL;   // 转速值
    tRunTime     : TIME;   // 累计运行时间
END_STRUCT
END_TYPE

// 使用UDT声明变量
VAR
    stMotor1 : Motor_DataType;  // 1号电机
    stMotor2 : Motor_DataType;  // 2号电机
END_VAR

// 访问UDT成员
stMotor1.bStartCmd := TRUE;
stMotor1.rCurrent := 12.5;

3.3 全局变量与局部变量:你的“领地”意识

变量放在哪,决定了谁能访问它。这就像公司的文件柜——全局变量是公共区域,谁都能翻;局部变量是你抽屉里的私人物品。

变量类型 声明位置 作用域 生命周期 典型用途
全局变量 全局变量列表(GVL) 整个项目所有POU 程序运行期间一直存在 系统参数、设备状态、共享数据
局部变量 POU内部(程序/功能块/函数) 仅限当前POU POU执行时存在,结束后释放 临时计算、中间结果、循环变量

3.3.1 全局变量:方便,但别滥用

全局变量确实方便。你在任何地方都能读写它。但方便的背后是风险。

我见过一个项目,全局变量用了上千个。结果呢?程序里到处都是 g_xxx,改一个变量,不知道会影响多少地方。调试时,一个变量莫名其妙变了,查都查不到是谁改的。

避坑指南: 我曾经接手一个项目,全局变量满天飞。一个电机状态,有3个不同的全局变量在控制。结果现场调试时,电机时转时停,查了整整两天。最后发现是不同程序段对同一个全局变量做了冲突操作。从那以后,我定了个规矩:能用局部变量,绝不用全局变量。非要用的,必须加注释说明用途和修改者。

// 全局变量列表(GVL)
VAR_GLOBAL
    g_bSystemRun    : BOOL := FALSE;  // 系统运行标志
    g_rSystemTemp   : REAL := 25.0;   // 系统温度
    g_iAlarmCount   : INT := 0;       // 报警计数
END_VAR

3.3.2 局部变量:你的“私人空间”

局部变量只在你当前写的程序、功能块或函数里有效。别人看不到,也改不了。这大大降低了程序的耦合度。

我个人习惯,所有临时计算、循环变量、中间状态,全用局部变量。这样,我写的功能块拿到别的项目里,直接复制粘贴就能用,不用担心变量冲突。

// 在功能块内部声明局部变量
FUNCTION_BLOCK FB_MotorControl
VAR_INPUT
    bStart : BOOL;  // 输入变量
    bStop  : BOOL;
END_VAR
VAR_OUTPUT
    bRun   : BOOL;  // 输出变量
END_VAR
VAR
    // 局部变量:仅在此功能块内有效
    iCounter    : INT := 0;
    bInternalFlag : BOOL;
    tTimer      : TIME;
END_VAR

// 逻辑处理
IF bStart THEN
    bInternalFlag := TRUE;
END_IF

IF bInternalFlag THEN
    iCounter := iCounter + 1;
    IF iCounter >= 100 THEN
        bRun := TRUE;
    END_IF
END_IF

3.4 知识体系总览

说了这么多,咱们用一张图来梳理一下本章的核心逻辑。数据类型和变量,就像程序的骨架和血肉,选对了,程序就成功了一半。

数据类型与变量知识体系 数据类型 基本数据类型 用户自定义(UDT) BOOL INT REAL TIME Motor_DataType bStartCmd, bStopCmd rCurrent, rSpeed... 变量 全局变量 局部变量 作用域:整个项目 生命周期:一直存在 风险:易被误修改 作用域:当前POU 生命周期:执行时存在 优点:封装性好 核心原则:选对类型、控制作用域、善用UDT 局部优先,全局谨慎,UDT打包,代码清爽

好了,数据类型和变量这块,咱们就聊到这儿。记住,选型时多想想“这个数据到底需要多大范围?会被谁访问?”,养成好习惯,后面写程序会省心很多。


专注资料整理