3. 功能块(FB)与函数(FC)的区别
在PLC结构化编程中,功能块(FB, Function Block)与函数(FC, Function)是两种最基础也最容易被混淆的代码封装单元。很多工程师在编写程序时,往往凭直觉选择FB或FC,却说不清背后的设计逻辑。实际上,FB与FC在定义、存储机制、调用方式以及适用场景上存在本质差异。理解这些差异,不仅有助于编写更规范的程序,更能直接影响CPU的存储资源占用、程序的可维护性以及团队协作效率。本章将从底层机制出发,结合工程实例,系统剖析FB与FC的核心区别,并给出选型判断路径。
上图从宏观层面展示了FB与FC在存储机制和调用方式上的核心差异。接下来,我们将从定义、特性、实例化、存储区、调用方式及适用场景六个维度逐一展开深度解析。
3.1 定义与核心特性:从“记忆”与“无记忆”说起
功能块(FB)是一种带有“记忆”功能的代码封装单元。它拥有独立的背景数据块(Instance DB),用于存储静态变量(Static)。这意味着,当FB被调用一次后,其内部变量的值会在下一次调用时被保留。例如,一个电机启动FB,其内部计时器的当前值、累计运行时间等状态信息,都会保存在背景DB中,不会因调用结束而丢失。
函数(FC)则是一种“无记忆”的代码封装单元。它没有独立的存储区,仅使用局部堆栈(L堆栈)中的临时变量(Temp)。当FC执行完毕后,其内部所有临时变量的值都会被释放。因此,FC无法在两次调用之间保持任何状态信息。例如,一个简单的数值转换FC,输入一个实数,输出一个整数,其内部计算过程不需要记忆任何历史状态。
3.2 实例化与存储区:背景DB与L堆栈的博弈
FB的实例化是其最显著的特征。每调用一次FB,用户必须在程序中指定一个背景DB(或使用多重背景方式在调用方FB的静态区中分配)。这个背景DB是FB的“私有仓库”,其中存放着该实例的所有静态变量。例如,在一条生产线上有3台相同的电机,若使用同一个电机控制FB,则需要创建3个不同的背景DB(如“电机1_DB”、“电机2_DB”、“电机3_DB”),每个DB独立存储各自电机的运行状态。
FC则不存在实例化概念。它就像一个“公共函数”,任何OB、FB或FC都可以直接调用,无需分配任何额外存储空间。FC的临时变量存储在调用方的局部数据堆栈(L堆栈)中。需要注意的是,L堆栈的容量是有限的(通常为几KB),且嵌套调用层级越深,L堆栈消耗越大。因此,在编写FC时,应避免使用过大的局部数组或结构体,以免导致堆栈溢出。
| 对比维度 | FB(功能块) | FC(函数) |
|---|---|---|
| 存储区类型 | 背景数据块(Instance DB) | 局部数据堆栈(L Stack) |
| 变量保持性 | 静态变量(Static)具有保持性 | 临时变量(Temp)无保持性 |
| 实例化要求 | 必须创建背景DB或使用多重背景 | 无需实例化,直接调用 |
| 存储资源占用 | 每个实例占用独立DB空间 | 仅占用调用时的临时堆栈空间 |
| 访问方式 | 可通过背景DB在线监控内部变量 | 无法在线监控内部临时变量 |
从存储资源角度看,FB的每个实例都会占用额外的DB存储空间,而FC几乎不占用额外资源。因此,在存储资源受限的紧凑型CPU(如S7-1200系列)中,如果逻辑不需要保持状态,优先使用FC可以显著节省存储空间。
3.3 调用方式与参数传递:接口设计的差异
FB与FC在调用方式上也有明显区别。FB的输入参数(Input)、输出参数(Output)以及输入输出参数(InOut)都映射到背景DB中。这意味着,调用FB时,不仅可以通过接口传递数据,还可以在程序运行过程中随时读取或修改FB内部静态变量的值(通过访问背景DB)。这种特性使得FB非常适合用于需要外部监控或干预的控制对象。
FC的调用则更接近传统高级语言中的函数调用。其输入参数通过值传递(或引用传递,取决于PLC品牌),输出参数通过引用传递。由于FC没有背景DB,其内部临时变量对外部完全不可见。因此,FC的接口设计必须完整地定义所有输入和输出,不能依赖内部状态。
// 以西门子TIA Portal为例,FB与FC的调用对比
// FB调用(需要指定背景DB)
CALL "Motor_Control", "Motor1_DB" (
Start := I0.0,
Stop := I0.1,
Running => Q0.0
);
// FC调用(直接调用,无需背景DB)
CALL "Scale_Convert" (
RawValue := MW10,
ScaledValue => MW20
);
在实际工程中,FB的调用通常伴随着背景DB的分配,这使得程序结构更加清晰,但同时也增加了编程工作量。而FC的调用则更加轻量,适合频繁调用的通用算法。
3.4 适用场景对比:选型判断路径
基于上述差异,我们可以总结出FB与FC的典型适用场景。FB适用于需要状态保持、多实例复用、以及需要外部监控内部状态的场合。例如:电机启停控制(需要记忆运行状态)、PID调节(需要累积积分值)、阀门定位控制(需要记忆当前位置)、以及步进顺序控制(需要记忆当前步号)。
FC则适用于无状态保持的纯逻辑转换、数学计算、数据类型转换、报警条件判断等场景。例如:模拟量工程量转换、BCD码转二进制、设备运行时间计算(仅需当前扫描周期的值)、以及简单的逻辑与或非运算。
1. 这段逻辑是否需要跨扫描周期保持内部状态?——是,则选FB;否,则进入下一步。
2. 这段逻辑是否会被多个设备或对象复用,且每个对象需要独立的状态?——是,则选FB;否,则进入下一步。
3. 这段逻辑是否只是纯粹的输入-输出转换,不涉及任何历史数据?——是,则选FC。
4. 如果以上都不确定,建议优先选择FB,因为FB可以包含FC,而FC不能包含FB(在标准IEC 61131-3中,FC不能调用FB)。
3.5 案例拆解:电机控制与温度转换的对比
为了更直观地理解FB与FC的差异,我们以一个实际项目中的两个功能为例进行拆解。假设某设备需要控制一台带抱闸的电机,同时需要将PT100热电阻的阻值转换为温度值。
案例背景与约束:设备采用西门子S7-1500 PLC,CPU内存充足,但要求程序结构清晰、便于维护。电机控制需要实现启动、停止、抱闸延时释放、过载报警复位等功能,且现场有4台完全相同的电机。温度转换仅需在HMI上显示,无历史记录要求。
初始现象与目标:工程师最初尝试用FC编写电机控制逻辑,但发现由于FC无法保持抱闸延时计时器的当前值,导致每次扫描周期计时器都被重置,电机无法正常启动。随后改用FB,将计时器作为静态变量存储在背景DB中,问题解决。
关键输入数据与参数:电机控制FB的输入参数包括启动按钮(I0.0)、停止按钮(I0.1)、过载信号(I0.2);输出参数包括接触器控制(Q0.0)、抱闸控制(Q0.1)、报警指示(Q0.2)。内部静态变量包括抱闸延时计时器(TON实例)、当前状态字(Byte)。温度转换FC的输入为PT100阻值(Real),输出为温度值(Real),内部仅使用临时变量进行线性插值计算。
分析判断过程:电机控制逻辑中,抱闸延时需要在停止指令发出后保持2秒,这要求计时器在多个扫描周期内持续运行,因此必须使用FB。而温度转换仅需根据当前阻值计算温度,不涉及历史数据,使用FC即可满足要求。
实际操作步骤:1. 创建电机控制FB,定义接口及静态变量;2. 创建4个背景DB(Motor1_DB至Motor4_DB);3. 在OB1中分别调用4次FB,并传入对应的I/O地址;4. 创建温度转换FC,定义输入输出接口;5. 在OB1中调用FC,将MW10中的阻值转换为温度值存入MW20。
结果对比:使用FB后,4台电机的控制逻辑完全独立,互不干扰,且通过在线监控背景DB可以实时查看每台电机的内部状态(如当前处于启动、运行、停止还是报警状态)。温度转换FC则简洁高效,不占用额外DB空间。
风险与代价:FB方案占用了约2KB的DB存储空间(4个背景DB),但换来了极高的可维护性。如果当初坚持使用FC,则必须将计时器变量存储在全局DB中,并手动管理每个电机的计时器实例,不仅编程繁琐,且容易出错。
复盘经验:对于涉及时间累积、步进状态、或需要多实例独立运行的控制逻辑,FB是唯一合理的选择。对于纯计算或转换逻辑,FC是更经济的选择。在项目初期,应通过功能分析明确哪些逻辑需要“记忆”,从而决定封装类型。
3.6 常见误区与总结复盘
在实际工程中,工程师常犯以下误区:
- 误区一:所有逻辑都用FB封装。这会导致背景DB数量过多,占用大量存储空间,且程序结构臃肿。正确做法是仅对需要状态保持的逻辑使用FB。
- 误区二:在FC中使用全局DB变量来模拟静态变量。这种做法虽然能实现状态保持,但破坏了FC的“无状态”特性,导致程序耦合度增加,且不利于代码复用。如果确实需要状态保持,应改用FB。
- 误区三:忽视L堆栈容量限制。在FC中定义大型局部数组或结构体,可能导致堆栈溢出,引起CPU停机。应尽量控制FC的局部变量大小。
- 误区四:在FC中调用FB。根据IEC 61131-3标准,FC不能调用FB,因为FB需要背景DB,而FC没有能力提供。如果遇到此类需求,应将外层逻辑改为FB。
总结而言,FB与FC的选择本质上是“状态保持”与“资源效率”之间的权衡。FB通过牺牲存储空间换取状态保持能力和程序可读性,FC则通过牺牲状态保持能力换取资源效率和调用灵活性。掌握这一核心区别,您就能在PLC结构化编程中做出正确的封装决策。