3、功能块(FB)与函数(FC)的区别:定义、特性、实例化、存储区与调用方式
在PLC程序设计中,功能块(FB, Function Block)与函数(FC, Function)是两种最基础也最容易被混淆的代码封装单元。很多工程师在编写结构化程序时,往往凭直觉选择FB或FC,却说不清背后的机制差异。实际上,FB与FC的本质区别不在于“谁更高级”,而在于它们对数据存储和调用上下文的处理方式完全不同。FB拥有独立的实例数据区(Instance Data Block),而FC仅依赖临时变量和全局数据。这一差异直接决定了它们的适用场景、调用方式以及资源占用特性。本章将从定义、特性、实例化机制、存储区分配和调用方式五个维度,系统剖析FB与FC的核心区别,并结合工程案例给出选型判断方法。
上图从宏观层面展示了FB与FC在定义、特性、实例化、存储区和调用方式上的差异。接下来,我们将逐一深入剖析每个维度的技术细节,并结合Siemens TIA Portal环境(以S7-1200/1500为例)进行说明,同时兼顾其他主流PLC平台(如Rockwell、Mitsubishi)的对应概念。
3.1 核心原理:定义与本质特性
功能块(FB)是一种带有静态存储区的代码封装单元。它在PLC的全局数据块(DB)中拥有一个独立的、持久的实例数据区。这意味着FB内部的变量(如静态变量、输出变量、保持性变量)在两次调用之间能够保持其值不变。FB的典型代表是Siemens中的定时器(TON、TOF)、计数器(CTU)以及PID控制块。FB的本质是“对象”——它将算法逻辑与状态数据绑定在一起,每次调用都操作自己专属的数据副本。
函数(FC)则是一种无静态存储区的代码封装单元。它仅使用局部堆栈(L Stack)中的临时变量(TEMP)和全局DB中的共享数据。FC在调用结束后,其局部变量占用的内存即被释放,不保留任何状态。FC的典型代表是数学计算(如浮点运算、开方)、数据转换(如BCD转二进制)以及简单的逻辑判断。FC的本质是“函数”——它接收输入参数,经过计算后返回输出结果,不依赖也不修改内部状态。
| 对比维度 | FB(功能块) | FC(函数) |
|---|---|---|
| 数据存储 | 拥有独立的实例DB,数据持久保持 | 仅使用临时变量,调用后数据丢失 |
| 状态保持 | 支持(如累计值、运行状态) | 不支持(除非使用全局DB) |
| 定时器/计数器 | 可直接集成,无需额外参数传递 | 必须通过参数传递定时器ID或引用 |
| 多实例能力 | 支持(一个FB可生成多个背景DB) | 不支持(无实例概念) |
| 内存占用 | 每个实例占用独立DB空间 | 仅占用临时堆栈,调用后释放 |
| 调用方式 | 必须指定背景DB(CALL FB, DB) | 直接调用(CALL FC) |
3.2 实例化机制:从模板到实体
实例化是FB与FC最显著的操作差异。FB的实例化过程类似于面向对象编程中“从类创建对象”。在TIA Portal中,当你编写一个FB后,必须在OB1或其他FB/FC中调用它,并为其分配一个背景数据块(Instance DB)。这个背景DB就是该FB的“实体”,它存储了该FB所有静态变量和输出变量的当前值。你可以为一个FB创建多个背景DB,每个DB对应一个独立的控制对象。例如,你编写了一个“电机控制”FB,然后为车间里的三台电机分别创建三个背景DB(DB_Motor1、DB_Motor2、DB_Motor3),它们共享同一套控制逻辑,但各自维护自己的运行状态、电流值、故障标志等。
FC则没有实例化过程。它就像一个纯函数,每次调用时,PLC在局部堆栈中为它的临时变量分配内存,执行完毕后立即释放。因此,FC无法直接保存状态。如果你需要在FC中实现类似“累加”的功能,必须将累加值存储在全局DB中,并在FC内部通过指针或符号访问该全局DB。这种设计虽然增加了编程的灵活性,但也带来了数据管理的风险——全局DB的滥用会导致程序耦合度升高,难以维护。
工程经验:在Siemens S7-1500中,FB的背景DB支持“多重背景”和“参数实例”两种高级用法。多重背景允许在一个FB内部声明另一个FB的静态调用(即FB嵌套),从而减少背景DB的数量。参数实例则允许将背景DB作为参数传递给其他块,实现更灵活的模块间通信。这些特性进一步强化了FB的“对象化”特征。
3.3 存储区分配与生命周期
FB的存储区分配是静态且持久的。每个背景DB在PLC启动时即被分配固定大小的内存区域,该区域在PLC运行期间一直存在,直到程序被修改或下载。背景DB中的数据可以设置为保持性(Retain),这意味着在PLC断电重启后,这些数据仍然能够恢复。这对于需要记忆历史状态的应用(如批次计数、累计运行时间)至关重要。
FC的存储区分配是动态且临时的。它使用CPU的局部数据堆栈(L Stack)。当FC被调用时,CPU将当前任务的局部数据压入堆栈;当FC返回时,堆栈指针恢复,这些数据即被覆盖。因此,FC的临时变量生命周期极短,仅在当前扫描周期内有效。此外,FC的临时变量不支持保持性,也无法在两次调用之间保留任何信息。
// 示例:FB与FC的存储区使用对比(结构化文本ST)
// FB定义
FUNCTION_BLOCK FB_MotorControl
VAR_INPUT
StartCmd : BOOL;
StopCmd : BOOL;
END_VAR
VAR_OUTPUT
Running : BOOL;
Fault : BOOL;
END_VAR
VAR
TimerOn : TON; // 内部定时器实例
RunCount : INT; // 运行次数计数(保持性)
END_VAR
// 背景DB自动生成,存储以上所有变量
// FC定义
FUNCTION FC_CalculateSpeed : REAL
VAR_INPUT
Frequency : REAL;
PolePairs : INT;
END_VAR
VAR_TEMP
TempSpeed : REAL; // 临时变量,调用后释放
END_VAR
BEGIN
TempSpeed := Frequency * 60.0 / PolePairs;
FC_CalculateSpeed := TempSpeed;
END_FUNCTION
3.4 调用方式与参数传递
FB的调用必须使用CALL指令,并指定背景DB。在TIA Portal中,调用FB时,背景DB的符号名会出现在调用指令中,例如:CALL FB_MotorControl, DB_Motor1。FB的输入/输出参数通过背景DB的接口区进行传递。这种调用方式确保了每个实例的数据隔离性。此外,FB支持复杂数据类型(如结构体、数组)作为参数,这使得FB能够处理更复杂的工程对象。
FC的调用同样使用CALL指令,但无需指定背景DB。FC的参数传递通过局部堆栈完成。FC也支持复杂数据类型,但需要注意的是,如果FC需要访问全局DB,必须在FC内部显式声明全局DB的引用(如"GlobalDB".Variable)。这种设计使得FC在数据访问上更加“透明”,但也更容易产生数据竞争和耦合问题。
风险提示:在Siemens S7-300/400系列中,FC的临时变量(TEMP)在递归调用或中断嵌套时可能被覆盖,导致数据丢失。因此,在编写FC时,应尽量避免使用过多的临时变量,或者确保临时变量在调用期间不被中断程序修改。对于S7-1200/1500,由于采用更先进的堆栈管理,此问题已大幅缓解,但仍需注意中断优先级对数据一致性的影响。
3.5 案例拆解:电机控制模块的选型与实现
假设我们需要为一个自动化产线设计一个电机控制模块,要求支持启动、停止、故障复位、运行时间累计,并且需要控制三台不同功率的电机。每台电机都有独立的启动/停止按钮、热继电器反馈和运行状态指示灯。
方案A:使用FB实现
- 创建FB_MotorControl,定义输入(StartCmd, StopCmd, FaultReset)、输出(Running, Fault, RunHours)和静态变量(内部定时器、累计时间)。
- 创建三个背景DB:DB_Motor1、DB_Motor2、DB_Motor3。
- 在OB1中调用三次,分别传入对应的I/O地址和背景DB。
- 优点:代码复用率高,数据隔离性好,运行时间累计自动保持,故障状态独立存储。
- 缺点:占用三个DB空间,内存开销相对较大。
方案B:使用FC实现
- 创建FC_MotorControl,定义输入(StartCmd, StopCmd, FaultReset, MotorID)和输出(Running, Fault)。
- 在FC内部访问全局DB,例如
"MotorData".RunHours[MotorID]用于累计时间。 - 在OB1中调用三次,每次传入不同的MotorID和I/O地址。
- 优点:节省DB空间,调用灵活。
- 缺点:全局DB数据管理复杂,容易出错;运行时间累计需要手动编写保持逻辑;故障状态容易混淆。
分析结论:对于这种需要状态保持和多实例复用的场景,FB是更合理的选择。虽然FC也能实现,但代码的可读性、可维护性和数据安全性均不如FB。在实际项目中,我们通常遵循以下判断流程:
选型判断流程:
- ① 是否需要保持数据(如累计值、状态)? → 是:选FB;否:进入下一步。
- ② 是否需要多个独立实例? → 是:选FB;否:进入下一步。
- ③ 是否使用了定时器/计数器? → 是:选FB;否:进入下一步。
- ④ 是否仅做纯计算/转换? → 是:选FC。
3.6 常见误区与总结复盘
误区一:认为FC比FB执行速度快。实际上,FB和FC的执行速度差异微乎其微,主要取决于代码复杂度而非封装类型。FB的额外开销在于背景DB的寻址,但现代PLC的寻址速度极快,可以忽略不计。
误区二:在FC中大量使用全局DB导致程序混乱。这是最常见的工程问题。FC本身无状态,但为了保持状态,工程师往往将状态变量放在全局DB中,导致全局DB被频繁读写,程序耦合度急剧上升。正确的做法是:如果发现FC需要频繁访问全局DB来保持状态,就应该考虑将其重构为FB。
误区三:忽视FB背景DB的保持性设置。在Siemens中,背景DB默认是非保持的。如果应用需要断电保持(如累计运行时间),必须在DB属性中勾选“保持性”。否则,PLC重启后数据将清零,可能导致生产事故。
误区四:在FC中使用定时器时直接调用定时器功能块。在Siemens中,定时器(如TON)本身是一个FB。如果在FC中直接调用TON,必须为TON分配一个背景DB或使用多重背景。这会导致FC的调用复杂度增加。更合理的做法是:将定时器逻辑封装在FB中,或者将定时器的背景DB作为参数传递给FC。
总结复盘:FB与FC的选择本质上是“状态”与“无状态”的选择。FB适合描述具有持续行为和内部状态的对象(如电机、阀门、PID回路),FC适合描述纯函数式的算法(如数学计算、数据转换)。在大型项目中,建议以FB为主构建控制逻辑层,以FC为辅构建工具函数层。同时,应严格控制全局DB的使用范围,尽量通过FB的实例DB实现数据隔离,从而提高程序的健壮性和可维护性。