PLC编程中FC与FB的本质区别:从内存模型到工程选型实战

发布时间:2026/10/11 14:09:27
PLC编程中FC与FB的本质区别:从内存模型到工程选型实战
1. 从一次产线停机说起为什么“功能”和“功能块”总被混为一谈很多做工业自动化控制的朋友在刚接触PLC编程或者DCS组态的时候都会遇到一个绕不开的坎功能FunctionFC和功能块Function BlockFB到底有什么区别我见过不少工作三五年的工程师写出来的程序里FC和FB混用逻辑上看似跑通了但一到产线联调、设备换型或者后期维护的时候问题就全暴露出来了。轻则程序改一处崩三处重则设备在自动运行中突然停机排查半天找不到原因。这个问题的本质其实不是编程语法的问题而是对“状态”和“数据保持”的理解深度问题。FC和FB在PLC编程里看起来都是把一段逻辑封装起来反复调用但它们在内存模型、数据生命周期、调用方式上有着根本性的差异。你如果只是照着教程抄一段电机启停逻辑用FC也能跑用FB也能跑感觉不到区别。但一旦逻辑复杂起来涉及多设备协同、配方管理、累计计时、报警锁存这些场景选错了封装方式程序就会变成一团乱麻。这篇文章我想从实际工程的角度把FC和FB的实质讲透。不是照本宣科地念手册而是结合我这些年踩过的坑、改过的烂摊子把“为什么要这样设计”“什么场景该用哪个”“用错了会怎样”这些问题一次说清楚。无论你用的是哪家品牌的PLC底层逻辑都是相通的理解了实质换平台也就是换个软件界面的事。2. 拆开看内存FC和FB在数据存储上的根本分野2.1 FC的“即用即走”模型没有记忆的纯逻辑FC中文叫“功能”在大多数PLC编程环境里它就是一个无状态的代码块。什么叫无状态就是它自己不保存任何数据。你调用它的时候传入参数它执行逻辑返回结果然后拍拍屁股走人不留下一片云彩。我习惯把FC比作一个纯函数——就像数学里的 y f(x)你给同样的x永远得到同样的y它不会记得你上次调用它的时候传了什么。FC的临时变量在西门子里叫Temp在三菱里叫局部变量是存在堆栈上的调用时分配返回时释放。这意味着两件事第一FC的执行效率通常比FB高因为没有额外的背景数据管理开销第二FC里的临时变量在调用结束后就失效了你下次再调用它又是全新的。这个特性决定了FC的适用场景纯组合逻辑、数学运算、数据转换、条件判断。比如你要写一个“根据温度值判断是否超限”的逻辑输入是温度输出是布尔值这种没有“记忆”需求的场景用FC最合适。代码干净调用方便不占额外的数据块。但这里有个坑很多新手会踩在FC里用临时变量做自锁或者状态保持。比如写一个电机启停逻辑把运行标志位放在Temp变量里想着下次调用还能用。结果程序跑起来电机要么启动不了要么停不下来。原因很简单Temp变量在FC返回后就没了下次调用是全新的值。这种错误在调试时特别隐蔽因为在线监控的时候你看到Temp变量在FC内部是有值的但一出FC就丢了。2.2 FB的“随身携带笔记本”模型自带记忆的逻辑单元FB中文叫“功能块”它的核心特征是拥有自己的背景数据块Instance DB。你可以把FB理解成一个带记忆的逻辑单元每次调用它它都会从自己的背景数据块里读取上次保存的状态执行完逻辑后再把新的状态写回去。这个背景数据块是FB的“随身笔记本”里面记录着这个FB实例的所有静态变量Static和输出变量的值。你可以在一个程序里多次调用同一个FB每次调用分配不同的背景数据块它们之间互不干扰。比如你写了一个“电机控制”FB里面有启动、停止、故障复位、运行计时、报警锁存这些逻辑然后你有10台电机就调用10次分配10个背景数据块每台电机的状态独立维护程序结构非常清晰。FB的这个特性决定了它适合有状态保持需求、需要多次实例化、逻辑相对复杂的场景。比如PID控制、累计流量计算、设备运行时间统计、配方管理、多工位协同控制等等。这些场景的共同点是逻辑需要“记住”之前发生了什么而且同样的逻辑要在多个设备上重复使用。2.3 一张表看清FC和FB的核心差异对比维度FC功能FB功能块数据存储无背景数据块临时变量在堆栈有背景数据块静态变量持久保存状态保持不保持每次调用都是全新状态保持下次调用读取上次的值多次调用同一FC多次调用共享代码无独立状态每次调用分配独立背景DB状态隔离内存开销较小仅代码空间较大每个实例都需要背景DB执行效率通常略高略低有背景数据读写开销适用场景纯逻辑运算、数据转换、条件判断状态控制、累计计算、多实例复用典型误用在Temp变量里做自锁简单逻辑也强行用FB浪费内存这张表建议你存下来下次写程序之前扫一眼能避免80%的选型错误。3. 调用方式背后的设计哲学为什么FB必须带背景块3.1 从“函数指针”到“对象实例”的思维转变如果你有高级语言的编程经验可以把FC理解成静态方法把FB理解成类的实例。FC是面向过程的思维你定义一段逻辑到处调用FB是面向对象的思维你定义一个“模板”然后实例化出多个“对象”每个对象有自己的属性数据和行为逻辑。这个思维转变很重要。很多从传统继电器控制转过来的工程师习惯把所有的逻辑都写成一段一段的FC然后靠全局变量来传递状态。程序小的时候没问题一旦设备多了全局变量满天飞改一个地方要全局搜索维护成本极高。而FB的思路是把相关的数据和逻辑封装在一起形成一个高内聚、低耦合的单元。你需要控制一台电机就实例化一个电机FB你需要控制一个阀门就实例化一个阀门FB。每个FB实例自己管好自己的数据程序结构一目了然。3.2 背景数据块里到底存了什么很多人用FB的时候只知道它“能保持状态”但说不清楚背景数据块里具体存了什么。我拆开来说背景数据块里主要存三类数据。第一类是静态变量Static。这是FB的核心你在FB的变量声明区里定义为Static的变量都会存在背景数据块里。比如电机的运行标志、故障标志、累计运行时间、上次启动时间等等。这些变量在FB的整个生命周期内都存在每次调用FB时PLC会自动从背景DB里读取它们的值执行完再写回去。第二类是输出变量Output。FB的输出变量也会存在背景数据块里这样你可以在FB外部通过背景DB来访问这些输出值而不需要每次都通过FB的引脚来读。第三类是输入变量Input和输入输出变量InOut。这两类变量本身不存储在背景DB里它们是在调用时通过引脚传入的。但它们的值会在FB执行期间被使用如果FB内部修改了InOut变量修改后的值会写回到调用者指定的地址。理解了这个存储模型你就能明白为什么FB能保持状态而FC不能。FC没有背景DB所有的临时变量都在堆栈上调用结束就释放自然无法保持状态。3.3 多重背景FB嵌套调用时的内存管理在实际项目中FB往往不是孤立使用的而是嵌套调用的。比如你有一个“生产线控制”FB里面调用了多个“工位控制”FB每个工位FB又调用了多个“电机控制”FB。这时候如果每个FB都分配独立的背景DB背景DB的数量会爆炸式增长管理起来非常麻烦。为了解决这个问题大多数PLC平台都支持多重背景Multi-Instance。所谓多重背景就是在一个FB的背景DB里为它内部调用的其他FB预留存储空间。这样你只需要为最外层的FB分配一个背景DB里面嵌套的所有FB实例都共用这个背景DB只是偏移地址不同。这大大减少了背景DB的数量也让程序结构更加紧凑。但多重背景也有代价调试的时候你无法单独监控某个内层FB实例的背景数据必须通过外层FB的背景DB来查看。而且如果外层FB的背景DB损坏里面所有的内层FB实例都会受影响。所以多重背景适合逻辑紧密相关、生命周期一致的FB嵌套如果内层FB需要独立调试或者独立复用还是单独分配背景DB更稳妥。4. 选型实战什么场景该用FC什么场景必须用FB4.1 这些场景用FC就够了别过度设计我见过一些工程师学了FB之后恨不得所有逻辑都用FB来写连一个简单的“与或非”逻辑都要封装成FB。这就是典型的过度设计。FC在以下场景里比FB更合适。纯数据转换和计算。比如你把一个模拟量输入值转换成工程值或者做一个单位换算这种逻辑没有状态保持需求输入确定输出就确定用FC最干净。你不需要为它分配背景DB调用的时候直接传参就行。条件判断和报警生成。比如你根据多个条件组合判断是否触发某个报警这种逻辑也是纯组合逻辑用FC写代码清晰执行效率高。报警的锁存和复位可以放在FB里做但判断逻辑本身用FC就够了。一次性执行的初始化逻辑。比如设备上电时的一些初始化操作只执行一次不需要保持状态用FC或者直接用主程序里的网络段都可以。数学运算和字符串处理。这些操作本质上都是纯函数输入输出确定没有状态用FC最合适。用FC的时候有一个铁律不要在FC里用临时变量做状态保持。如果你发现某个逻辑需要“记住”上次的值那就说明它不适合用FC应该改用FB。这个判断标准非常简单但能帮你避免很多隐蔽的Bug。4.2 这些场景必须用FB用FC会出大问题反过来以下场景如果你用FC来写程序迟早会出问题。PID控制。PID控制器需要保持积分项和微分项的历史值这些状态必须跨调用周期保持。用FC写PID你只能把积分值放在全局变量里但这样就没法多次实例化了。用FB写每个PID回路分配一个背景DB积分值、微分值、上次误差都存在背景DB里多个回路互不干扰。累计计时和计数。比如你要统计一台设备的累计运行时间这个时间需要跨扫描周期累加。用FC写你没法在FC内部保存累计值只能靠全局变量。用FB写累计值放在Static变量里每次调用自动累加干净利落。设备状态机和流程控制。设备的运行往往有多个状态待机、启动中、运行、暂停、故障、复位中状态之间的切换需要记住当前状态。这种场景用FB写把当前状态放在Static变量里逻辑清晰调试方便。用FC写状态只能放全局变量程序一复杂就乱。多工位、多设备的复用逻辑。你有10台同样的设备控制逻辑完全一样只是数据不同。用FB写一次调用10次分配10个背景DB程序结构非常优雅。用FC写你要么复制10份代码维护噩梦要么用全局变量数组可读性差。报警锁存和首出记忆。报警需要锁存直到操作员确认才复位首出记忆需要记住第一个触发的报警。这些都需要状态保持用FB写最合适。4.3 一个真实案例电机控制从FC改成FB的代价我之前接手过一个项目前任工程师用FC写电机控制逻辑每台电机的运行标志、故障标志、累计运行时间都放在全局变量里变量名是Motor1_Run、Motor1_Fault、Motor1_Time、Motor2_Run、Motor2_Fault……一共20台电机全局变量表里密密麻麻几百个变量。程序能跑但问题很多。第一个问题是改一处要改二十处。电机的控制逻辑要加一个“启动延时”功能他得在20个FC调用里逐个修改漏一个就出问题。第二个问题是变量名容易写错。有一次他把Motor13_Fault写成了Motor13_Falut编译不报错因为全局变量可以隐式声明但运行的时候故障信号丢了排查了半天。第三个问题是程序可读性极差。新来的工程师看程序根本分不清哪些变量属于哪台电机维护成本极高。后来我把它改成了FB方案写一个MotorControl的FB里面有Run、Fault、RunTime、StartDelay等Static变量然后调用20次分配20个背景DB。改完之后程序从原来的几千行缩减到几百行逻辑清晰维护方便。加功能只需要改FB内部逻辑所有实例自动生效。这个改造花了两天时间但后续维护节省的时间远远超过这个投入。5. 那些年我踩过的FC和FB的坑5.1 坑一在FC里用Temp变量做自锁程序时好时坏这是我刚入行时踩的第一个坑。当时写一个水泵控制逻辑启动按钮按下水泵运行停止按钮按下水泵停止。我用FC写把运行标志放在Temp变量里逻辑是如果启动按钮按下或者运行标志为真则运行标志置位如果停止按钮按下则运行标志复位。逻辑看起来没问题但实际运行时水泵偶尔会自己停偶尔又停不下来。排查了很久才发现Temp变量在FC返回后就失效了下次调用时它的值是随机的。有时候随机到True水泵就意外启动了有时候随机到False水泵就意外停了。这个问题的隐蔽性在于在线监控的时候你看到Temp变量在FC内部是有值的逻辑也跑通了但一出FC就丢了。后来改成FB把运行标志放在Static变量里问题立刻消失。这个坑给我的教训是FC里的Temp变量只能用于当前扫描周期内的临时计算绝对不能用于跨周期保持状态。如果你需要保持状态要么用FB的Static变量要么用全局变量但不推荐没有第三条路。5.2 坑二FB背景DB被意外覆盖设备行为异常有一次调试一条包装线设备运行一段时间后某个工位的动作时序突然乱了。排查发现这个工位的FB背景DB里的某个Static变量被意外修改了。追查下去发现是另一个FB的背景DB地址分配重叠了两个FB共用了同一块内存区域互相覆盖。这个问题的根源是背景DB的地址分配。在有些PLC平台里背景DB的地址是手动分配的如果你分配的时候不小心让两个FB的背景DB重叠了就会出现这种问题。避免的方法是尽量使用符号寻址让编程软件自动分配背景DB地址如果必须手动分配一定要做好地址规划留足余量并且在编译后检查背景DB的大小和地址范围。另外多重背景虽然节省背景DB数量但也要注意内层FB的实例数据不要和外层FB的Static变量冲突。大多数编程软件会自动处理偏移但如果你手动指定了绝对地址就要格外小心。5.3 坑三FC和FB混用时参数传递的陷阱FC和FB混用的时候参数传递也有讲究。FC的输入输出参数是通过引脚传递的调用时传值FB的输入输出参数也是通过引脚传递但FB的Static变量是存在背景DB里的不通过引脚。有一个常见的错误是在FC里调用FB但FC本身没有背景DB所以FB的背景DB必须由FC的调用者提供。如果你在FC里调用了一个FB但没有正确指定背景DB编译会报错。解决方法是要么把FC改成FB让FB的背景DB嵌套在FC的背景DB里多重背景要么在FC的调用者那里为FB分配独立的背景DB然后通过参数传给FC。还有一个错误是在FB里调用FC但FC的Temp变量和FB的Static变量重名了。虽然大多数编程软件会区分作用域但为了代码清晰建议变量命名时加上前缀比如FC的临时变量用tmp_开头FB的静态变量用st_开头避免混淆。5.4 坑四以为FB一定比FC慢结果优化错了方向有些工程师听说FB有背景DB读写开销就认为FB一定比FC慢于是在性能敏感的场景里强行用FC结果程序逻辑复杂到无法维护。实际上在现代PLC上FB的背景DB读写开销非常小对于大多数应用来说FC和FB的执行时间差异可以忽略不计。真正影响性能的是逻辑的复杂度和扫描周期而不是FC和FB的选择。我做过一个测试在同一个PLC上用FC和FB分别实现同样的PID控制逻辑扫描周期差异不到5%。但用FC实现的版本因为要把状态放在全局变量里代码可读性差调试困难后期维护成本高得多。所以不要为了微小的性能差异牺牲代码的可维护性。除非你的扫描周期已经紧张到毫秒级否则优先考虑代码的清晰度和可维护性。6. 从FC到FB的重构思路什么时候该动手改6.1 识别需要重构的信号不是所有的FC都需要改成FB。但如果你的程序里出现了以下信号就该考虑重构了。信号一全局变量泛滥。你的全局变量表里有大量以设备编号命名的变量比如Motor1_Run、Motor2_Run、Valve1_Open、Valve2_Open而且这些变量只在对应的FC里使用。这说明你的逻辑需要封装成FB用背景DB来管理状态。信号二复制粘贴的代码。你有多个FC逻辑几乎一样只是操作的变量不同。这说明你应该把这段逻辑封装成FB然后多次实例化。信号三改一处要改多处。你修改一个FC的逻辑需要同步修改多个调用点或者修改多个类似的FC。这说明你的逻辑没有封装好应该用FB来统一管理。信号四调试时找不到状态变量。你在线监控的时候发现某个状态变量的值不对但不知道它是在哪里被修改的。这说明你的状态管理太分散应该用FB把相关的状态和逻辑封装在一起。6.2 重构的步骤和注意事项重构不是推倒重来而是逐步演进。我的做法是先识别出可以封装的逻辑单元然后写FB再逐步替换原来的FC调用。第一步梳理逻辑找出那些“有状态保持需求、需要多次复用”的逻辑单元。比如电机控制、阀门控制、PID回路、累计计时等。第二步为每个逻辑单元设计FB的接口。输入是什么启动、停止、复位等输出是什么运行、故障、就绪等需要保持哪些状态运行标志、故障标志、累计时间等。第三步写FB的内部逻辑把状态变量定义为Static把输入输出定义为Input/Output/InOut。第四步在程序里逐步替换原来的FC调用。每替换一个就测试一个确保逻辑正确。不要一次性全部替换那样出了问题很难定位。第五步清理不再使用的全局变量和FC。这一步要谨慎确认没有其他地方引用后再删除。重构的过程中有一个注意事项保持接口的兼容性。如果原来的FC已经被其他地方调用你改成FB后调用方式会变需要指定背景DB所以要同步修改调用点。如果调用点很多可以考虑保留原来的FC作为包装层内部调用FB这样外部调用方式不变但内部状态管理已经改成了FB。6.3 重构后的收益一个真实项目的对比前面提到的那个20台电机的项目重构前后的对比如下对比项重构前FC全局变量重构后FB背景DB代码行数约3000行约800行全局变量数量约400个约50个主要是HMI交互添加新电机复制粘贴约150行代码调用一次FB分配一个背景DB修改控制逻辑修改20处修改FB内部一处调试难度高状态变量分散低状态集中在背景DB新人上手时间约2周约3天这个对比不是要证明FB一定比FC好而是说明在适合的场景里选对封装方式收益是巨大的。FC和FB不是对立的而是互补的。纯逻辑用FC状态控制用FB两者配合才能写出既高效又易维护的程序。7. 不同PLC平台上的FC和FB换汤不换药7.1 西门子S7系列FC和FB的经典实现西门子的S7系列PLC里FC和FB的区别非常清晰。FC没有背景DBFB有背景DB。FB的背景DB可以是单独的Single Instance也可以是多重背景Multi-Instance。在博途TIA Portal里你创建一个FB系统会自动生成一个背景DB的数据类型调用的时候可以选择分配单独的DB或者多重背景。西门子还有一个概念叫系统功能块SFB和系统功能SFC这是西门子预置的FC和FB用于实现一些系统级功能比如读写系统时间、处理中断等。SFB和SFC的调用方式和用户自定义的FB/FC类似但它们的背景DB是系统管理的用户不需要手动分配。在西门子里有一个细节要注意FB的Static变量在背景DB里的偏移地址是固定的如果你在FB里添加或删除Static变量背景DB的结构会变已经下载到PLC里的背景DB需要重新下载。如果是在运行中修改可能会导致背景DB数据丢失。所以在修改FB的Static变量之前最好先备份背景DB的数据或者选择在停机时修改。7.2 三菱FX和Q系列FB的另一种表达三菱的PLC里FB的概念叫“功能块”但实现方式和西门子略有不同。三菱的FB也有背景数据但它的背景数据是存在FB内部的不需要单独分配背景DB。调用FB的时候系统会自动为它分配存储空间。三菱的FB有一个特点支持在线修改。你可以在PLC运行的时候修改FB的内部逻辑下载后不需要重启PLCFB会自动使用新的逻辑。这个特性在调试阶段非常方便但也带来一个风险如果你在运行中修改了FB的Static变量可能会导致状态丢失。所以在线修改FB的时候要确认修改的内容不会影响正在运行的状态。三菱还有一个概念叫FB的嵌套和西门子的多重背景类似。你可以在一个FB里调用另一个FB内层FB的背景数据会嵌套在外层FB的背景数据里。三菱的编程软件会自动处理偏移用户不需要手动管理。7.3 欧姆龙和罗克韦尔FB的变体欧姆龙的PLC里FB的概念叫“功能块”和西门子类似也有背景数据。欧姆龙的FB支持变量标签你可以用符号名来访问FB的变量不需要关心绝对地址。这个特性让程序的可读性更好但也要求编程软件支持标签数据库。罗克韦尔的PLC里FB的概念叫“Add-On InstructionAOI”本质上和FB是一样的有输入输出参数有本地标签相当于Static变量可以多次实例化。AOI的特点是支持版本管理你可以定义AOI的版本不同版本可以共存方便程序升级。不管哪个平台FC和FB的核心区别都是一样的FC无状态FB有状态。理解了这一点换平台只是换个软件界面和术语的事。8. 写给新手的建议先想清楚数据再动手写代码8.1 写程序之前先画一张数据流图我见过很多新手拿到需求就开始写代码写着写着发现状态变量没地方放于是到处加全局变量最后程序乱成一团。我的建议是写程序之前先画一张数据流图。数据流图里你要标出哪些数据是输入来自传感器、HMI、其他设备哪些数据是输出去执行器、HMI、其他设备哪些数据需要保持状态运行标志、累计值、报警锁存等。然后根据数据的生命周期和复用需求决定哪些逻辑用FC哪些用FB。如果某个逻辑单元需要保持状态而且可能在多个地方复用那就用FB。如果某个逻辑单元是纯计算输入确定输出就确定那就用FC。这个判断标准简单有效能帮你避免大部分选型错误。8.2 从简单的FB开始练手如果你还没用过FB建议从一个简单的场景开始练手。比如写一个“累计计时器”FB输入是使能信号和复位信号输出是累计时间。内部逻辑是当使能信号为真时累计时间按扫描周期累加当复位信号为真时累计时间清零。累计时间放在Static变量里跨周期保持。这个FB虽然简单但涵盖了FB的核心要素输入、输出、Static变量、背景DB。你把它写出来调用几次观察背景DB里的数据变化就能直观理解FB的工作原理。然后你可以逐步增加复杂度比如加上“计时到达”输出、“计时中”输出、累计时间的高低位处理等。练手的时候有一个技巧在线监控背景DB。在大多数PLC编程软件里你可以打开FB的背景DB实时观察里面每个变量的值。这是理解FB状态保持机制的最直观方式。你会看到每次调用FBStatic变量的值都在变化而且下次调用时读取的是上次的值。这个观察过程比看十遍手册都管用。8.3 不要害怕重构但要控制重构的范围最后一点建议不要害怕重构但要控制重构的范围。如果你发现之前的程序用FC写状态逻辑导致了一堆全局变量不要想着一次性全部改成FB。那样风险太大容易引入新问题。我的做法是新功能用FB写老功能逐步改。每次修改或扩展一个功能的时候顺便把相关的FC改成FB。这样重构的风险被分散到每次小改动里不会影响整体稳定性。而且每次重构后你都能看到收益更有动力继续下去。重构的时候一定要做好版本管理。每次重构前备份当前程序重构后充分测试确认逻辑正确再下载到现场。如果现场设备正在运行重构最好安排在停机检修的时候进行避免影响生产。说到底FC和FB的实质不是语法问题而是数据管理问题。你想清楚数据怎么存、怎么传、怎么保持自然就知道该用FC还是FB了。这个道理放在任何PLC平台上都成立放在任何编程语言里也都成立。