3. PLC 编程基础回顾与结构化思维
在正式进入 FB 功能块与结构化编程之前,有必要把最基础的编程方式重新梳理一遍。很多工程师在 TIA Portal 中写程序时,习惯把所有逻辑都堆在 OB1 里:一个网络接一个网络,位逻辑、定时器、计数器全部混在一起。程序能跑,但一旦设备动作变多、需要复用同一段逻辑,或者要交给别人维护,问题就会迅速暴露。本节的目标不是重新讲一遍指令,而是通过对比“线性编程”和“结构化编程”,说明 FC、FB、DB 三者为什么必须分工。
一、位逻辑、定时器、计数器在 OB1 中的直接编程
OB1 是循环扫描的主程序块,PLC 每个扫描周期都会从头到尾执行一次。最直观的编程方式,就是把输入信号、逻辑运算、输出控制全部写在 OB1 里。例如一台简单的输送带控制:启动按钮 I0.0、停止按钮 I0.1、电机输出 Q0.0,用位逻辑可以写成“启动并保持、停止断开”的自锁回路。在梯形图中,就是 I0.0 常开与 Q0.0 常开并联,再串 I0.1 常闭,最后驱动 Q0.0。
当需要延时启动时,就插入一个 TON 定时器。例如要求按下启动后 5 秒电机才运行,可以给 TON 的 IN 端接启动条件,PT 设为 T#5S,再用定时器的 Q 输出去驱动电机。计数器也类似:用 CTU 统计产品数量,CU 端接光电传感器脉冲,PV 设为 100,当计数值达到 100 时输出一个信号去触发报警或换箱。这些指令本身并不复杂,问题在于它们全部平铺在 OB1 中。
假设这条输送带还要控制分拣气缸、扫码枪、报警灯,每个设备都有自己的定时和计数。如果继续在 OB1 里往下写,程序会变成几十甚至上百个网络。此时会出现几个典型症状:第一,同一个 5 秒延时逻辑在三个地方重复出现,修改时间要改三处;第二,定时器编号 T1、T2、T3 与设备对应关系全靠记忆;第三,想单独测试分拣逻辑,必须把整条 OB1 都下载运行;第四,别人接手时,需要从头读到尾才能理解某一段逻辑属于哪个设备。
线性编程的典型特征:所有逻辑集中在 OB1,指令按扫描顺序排列,输入、输出、定时器、计数器、中间变量混用,程序规模随设备数量线性增长,复用性差,调试和维护成本高。
二、线性编程与结构化编程的差异
结构化编程的核心思想是“分而治之”:把一台设备、一个工艺动作或一段可复用逻辑,封装成独立的程序块,OB1 只负责按顺序调用这些块。这样做的直接好处是,逻辑边界清晰,同一段代码可以被多次调用,参数通过接口传递,而不是依赖全局地址硬连接。
两者的差异可以从几个维度来看。在组织方式上,线性编程是“一条主线到底”,结构化编程是“主程序调用子程序”。在复用性上,线性编程靠复制粘贴,结构化编程靠块调用和参数传递。在数据管理上,线性编程大量使用 M 存储区和全局 DB,结构化编程倾向于把数据封装在背景 DB 或专用数据块中。在调试上,线性编程只能整体监控,结构化编程可以单独打开某个 FC 或 FB 在线监控,甚至只测试某一个设备块。
需要强调的是,结构化编程并不是“不用 OB1”,而是让 OB1 回归它本来的角色——调度器。OB1 里应该只有调用语句和少量顶层协调逻辑,而不是具体的设备动作细节。判断一个程序是否结构化,一个简单的标准是:如果某个设备的控制逻辑在 OB1 中占了超过二十个网络,就应该考虑把它拆出去。
三、FC、FB、DB 的分工
结构化编程在 TIA Portal 中主要依靠三种块来实现:FC、FB 和 DB。理解它们的分工,是后续所有内容的基础。
FC(Function,函数)是没有记忆的代码块。它接收输入参数,执行运算或逻辑判断,然后通过输出参数返回结果。FC 不保存任何内部状态,每次调用都从零开始。适合用 FC 的场景包括:单位换算、报警判断、数据比较、简单的组合逻辑。例如把“摄氏度转华氏度”写成一个 FC,输入是温度值,输出是转换结果,调用一百次也不会互相干扰。FC 的临时变量在调用结束后不保留,因此不能用来做自锁或计数。
FB(Function Block,功能块)是带记忆的代码块。它除了输入输出参数外,还拥有静态变量,这些静态变量保存在背景 DB 中,调用结束后仍然保留。这使得 FB 天然适合描述设备控制:启动、停止、运行状态、故障标志、定时器实例、计数器实例,都可以作为静态变量保存下来。同一个 FB 可以被多次调用,每次调用生成一个独立的背景 DB,互不影响。例如写一个“电机控制 FB”,输入是启动、停止、故障复位,输出是运行、故障,静态变量里放一个 TON 定时器实例和一个运行标志。调用两次,就得到两个独立的电机控制实例。
DB(Data Block,数据块)是数据存储区。它分为全局 DB 和背景 DB。全局 DB 用于存放与具体 FB 无关的共享数据,例如配方参数、报警阈值、通信缓冲区。背景 DB 则是 FB 调用时自动生成的,专门保存该实例的静态变量。全局 DB 可以手动创建和编辑,背景 DB 通常由系统管理,不建议手动修改其结构。
| 块类型 | 是否有记忆 | 典型用途 | 数据存放位置 |
|---|---|---|---|
| FC | 无 | 运算、判断、组合逻辑 | 临时变量,调用结束释放 |
| FB | 有 | 设备控制、状态机、定时计数 | 背景 DB 中的静态变量 |
| 全局 DB | 有 | 共享参数、配方、通信数据 | 全局数据区 |
选择原则:如果一段逻辑不需要记住上一次的结果,用 FC;如果需要记住状态、需要多次调用且各自独立,用 FB;如果数据要在多个块之间共享,用全局 DB。OB1 只负责按工艺顺序调用这些块。
四、从线性到结构化的实际改造思路
假设已经有一段线性程序,控制两台相同的电机,每台电机都有启动、停止、过载故障和运行指示。线性写法是复制两遍逻辑,分别用 I0.0/I0.1/I0.2/Q0.0 和 I0.3/I0.4/I0.5/Q0.1。改造为结构化写法的过程是:先创建一个 FB,命名为“MotorControl”,接口区定义输入 Start、Stop、Fault,输出 Run、FaultActive,静态变量放一个 TON 用于故障延时确认。然后在 OB1 中调用两次,第一次分配背景 DB“Motor1_DB”,实参接 I0.0、I0.1、I0.2、Q0.0;第二次分配背景 DB“Motor2_DB”,实参接 I0.3、I0.4、I0.5、Q0.1。
改造完成后,如果两台电机的故障延时都需要从 3 秒改成 5 秒,只需要打开 FB 修改一次 PT 值,两个实例同时生效。如果只想测试第一台电机,可以在 OB1 中临时禁用第二次调用,不影响其他逻辑。这就是结构化编程带来的实际收益:修改集中、测试隔离、复用自然。
需要提醒的是,结构化不等于过度拆分。一个只有三个网络的简单逻辑,硬要拆成 FC 加 FB 加 DB,反而增加调用层级和阅读负担。合理的粒度是:一个 FB 对应一个可独立描述的设备或功能单元,一个 FC 对应一段可复用的无状态计算,一个全局 DB 对应一组确实需要共享的数据。掌握这个尺度,后续学习 FB 的多重背景、嵌套调用和复杂数据类型时,才不会觉得是在堆砌概念。