第四章 数据块与变量表:PLC的“记忆仓库”与“通讯录”

各位工程师朋友,大家好。欢迎来到第四章。

这一章,咱们聊聊PLC程序里最基础、也最核心的东西——数据。你想想看,程序跑得再花哨,最终不就是为了处理数据吗?S7-1200和1500里,数据怎么存、怎么管、怎么找,全靠数据块和变量表。我刚开始用西门子的时候,也在这上面栽过跟头,今天就把这些经验掰开揉碎了讲给你听。

核心一句话:数据块是仓库,变量表是通讯录。仓库里放什么、怎么放,通讯录怎么编,决定了你的程序好不好维护、会不会出bug。

第四章 数据块与变量表 知识体系 PLC数据管理 全局数据块 (DB) 共享数据,全局访问 可定义数组、结构体 背景数据块 (Instance DB) 跟随FB/FC自动生成 存储FB的静态数据 PLC变量表 定义I/O、M、T、C符号 数据类型:Bool, Int, Real... 数组与结构体:让数据组织更高效

4.1 全局数据块(DB)—— 你的“共享仓库”

全局数据块,英文叫Data Block,简称DB。说白了,就是一块专门用来存数据的“共享内存区”。

我个人习惯,把所有需要跨程序块共享的数据,都放在全局DB里。比如设备的状态字、工艺参数、配方数据等等。为什么?因为全局DB里的数据,整个PLC程序都能访问,不用像M区那样担心地址冲突。

我的经验:我曾经在一个大型项目中,把所有模拟量通道的标定值都放在一个全局DB里。后来现场要改量程,只需要在线修改DB里的几个Real数,不用动程序。甲方看了直呼内行。

创建全局DB很简单:在项目树里右键“添加新块”,选“数据块(DB)”,类型选“全局DB”。然后你就可以在里面定义变量了。

举个例子,一个电机控制的数据块:

// 电机控制数据块 (DB_MotorControl)
DATA_BLOCK "DB_MotorControl"
{ S7_Optimized_Access := 'TRUE' }  // 优化访问,性能更好
VERSION : 0.1
   STRUCT
      Start_Button : Bool;        // 启动按钮
      Stop_Button  : Bool;        // 停止按钮
      Motor_Running : Bool;       // 电机运行状态
      Speed_Setpoint : Real;      // 速度设定值 (rpm)
      Speed_Actual : Real;        // 速度实际值 (rpm)
      Run_Hours : UDInt;          // 累计运行时间 (秒)
      Fault_Code : Word;          // 故障代码
   END_STRUCT;
END_DATA_BLOCK

注意:S7-1200/1500默认开启了“优化块访问”。这意味着你不能像200 SMART那样直接用“DB1.DBW0”这种绝对地址来访问。必须用符号名,比如“DB_MotorControl.Speed_Setpoint”。刚开始可能不习惯,但相信我,这能让你少掉很多头发。

4.2 背景数据块——FB的“私人秘书”

背景数据块,也叫Instance DB。它和全局DB最大的区别是:它不独立存在,必须依附于一个函数块(FB)。

你想想看,FB就像一个“模板”,它定义了逻辑,但每个实例需要自己的数据存储空间。这个空间,就是背景数据块。

比如你写了一个“电机控制”的FB,里面有启动、停止、速度控制逻辑。然后你现场有10台电机,每台电机都需要独立的运行状态、速度值。这时候,你只需要调用10次这个FB,每次调用时系统自动生成一个背景数据块。每个背景数据块里存的是对应电机的私有数据。

核心区别:全局DB是“公共储物柜”,谁都能用。背景DB是“私人保险箱”,只有对应的FB实例才能用。

我记得有一次,一个新手同事把所有电机数据都放在一个全局DB里,然后FB里用数组索引来区分。结果程序一复杂,索引搞错了,电机乱转。后来我让他改成背景数据块,每个电机一个实例,清清楚楚,再也没出过问题。

4.3 PLC变量表——你的“通讯录”

PLC变量表,也叫符号表。它的作用,就是给PLC的物理地址起个“人话”名字。

比如,数字量输入I0.0,你给它起名叫“急停按钮”。这样在程序里写“急停按钮”比写“I0.0”直观多了吧?

变量表里可以定义:

  • 输入(I):传感器、按钮等输入信号
  • 输出(Q):继电器、指示灯等输出信号
  • 位存储(M):中间变量
  • 定时器(T)、计数器(C):时间与计数
  • 数据块(DB):也可以在这里给DB起别名

来看一个典型的变量表定义:

符号 地址 数据类型 注释
急停按钮 I0.0 Bool 红色蘑菇头按钮,常闭
启动按钮 I0.1 Bool 绿色按钮,常开
电机运行灯 Q0.0 Bool 绿色指示灯
故障灯 Q0.1 Bool 红色指示灯
模拟量输入1 IW64 Int 4-20mA压力传感器
模拟量输出1 QW64 Int 变频器速度给定

我的习惯:变量表里的注释一定要写清楚。别偷懒。我曾经在调试一个老外的项目时,看到注释写的是“Don't touch this”,结果我查了半天才发现是个安全互锁。从那以后,我要求团队所有注释必须写明白“是什么、为什么、怎么用”。

4.4 数据类型——数据的“身份证”

PLC里的数据类型,决定了数据怎么存、占多大空间、能干什么运算。S7-1200/1500支持的数据类型很丰富:

  • 基本类型:Bool(位)、Byte(字节)、Word(字)、DWord(双字)、Int(整数)、DInt(双整数)、Real(浮点数)
  • 时间类型:Time(时间,单位ms)、Date(日期)、TOD(一天中的时间)
  • 字符串:String、WString(Unicode字符串)
  • 复合类型:Array(数组)、Struct(结构体)

这里有个坑,我必须要说:数据类型不匹配,是PLC程序最常见的bug之一

比如,你把一个Int(-32768到32767)的值赋给一个Real变量,没问题。但反过来,你把一个Real(比如3.14)赋给一个Int,PLC会直接截断小数部分,变成3。如果你没意识到,后面算比例、算累计,结果全错。

避坑指南:我曾经在做一个流量累计程序时,把累计值定义成了Int。结果流量一超过32767,累计值直接变成负数。现场操作员以为管道倒流了,吓得按了急停。后来我改成DInt,再也没出过这种笑话。

4.5 数组——数据的“书架”

数组,就是把同一类型的数据排成一排,用索引来访问。比如,你有10个温度传感器,不用定义10个单独的变量,定义一个数组就够了:

// 温度数组定义
Temp_Sensor : Array[1..10] of Real;  // 10个温度值,索引1到10

访问的时候:

#Temp_Value := "DB_Temp".Temp_Sensor[3];  // 读取第3个温度传感器

数组在批量处理时特别有用。比如用FOR循环遍历所有温度:

FOR #i := 1 TO 10 DO
    IF "DB_Temp".Temp_Sensor[#i] > 100.0 THEN
        // 超温报警逻辑
    END_IF;
END_FOR;

注意:S7-1200/1500的数组索引可以从任意整数开始,不一定非要从0或1开始。我个人习惯从1开始,更符合工程直觉。

4.6 结构体——数据的“文件柜”

结构体,就是把不同类型的数据打包成一个整体。比如,一个“电机”的数据,有启动按钮(Bool)、速度(Real)、运行时间(UDInt)……用结构体把它们装在一起:

// 电机数据结构体
TYPE "Motor_DataType"
   STRUCT
      Start_Button : Bool;
      Stop_Button : Bool;
      Speed : Real;
      Run_Hours : UDInt;
      Fault_Code : Word;
   END_STRUCT;
END_TYPE

然后在DB里直接使用这个结构体:

DATA_BLOCK "DB_MotorData"
   Motor1 : "Motor_DataType";  // 电机1
   Motor2 : "Motor_DataType";  // 电机2
   Motor3 : "Motor_DataType";  // 电机3
END_DATA_BLOCK

访问的时候:

"DB_MotorData".Motor1.Speed := 1500.0;  // 设置电机1的速度

结构体最大的好处是:复用性和可读性。你定义一次结构体,可以在多个DB、多个FB里反复使用。而且程序里一看就知道这个变量属于哪个设备,逻辑清晰得很。

我的建议:在项目开始前,花半小时把常用的结构体定义好。比如“电机数据类型”、“阀门数据类型”、“模拟量通道数据类型”。这半小时,能让你后面省下几十个小时的调试时间。

4.7 数组与结构体组合——真正的“数据矩阵”

把数组和结构体组合起来,威力更大。比如,一个生产线有20台电机,每台电机都有相同的属性:

DATA_BLOCK "DB_LineMotors"
   Motors : Array[1..20] of "Motor_DataType";  // 20台电机的数据
END_DATA_BLOCK

访问第5台电机的速度:

"DB_LineMotors".Motors[5].Speed := 1200.0;

用循环批量处理所有电机:

FOR #i := 1 TO 20 DO
    IF "DB_LineMotors".Motors[#i].Fault_Code <> 0 THEN
        // 记录故障电机编号
    END_IF;
END_FOR;

这种结构,在大型项目中几乎是标配。我做过一个汽车焊装线的项目,光电机就300多台,全是靠这种“结构体数组”来管理的。程序写起来快,查起来也快。


好了,这一章的内容就到这里。数据块和变量表是PLC编程的基石,你花多少时间在上面都不为过。记住:好的数据结构,是优秀程序的一半

下一章,咱们聊聊函数块和函数——怎么把逻辑封装成可复用的“积木”。

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