资讯动态

一个设备模板FB覆盖8种机型:ST代码复用架构完整拆解

发布时间:2026/10/21 10:32:36 来源:尧图企业网站定制
1. 为什么我们要聊“ST代码复用架构”做自动化设备编程的朋友应该都有过这种经历项目越做越多程序版本五花八门同一个功能在不同机型里各写一套最后改需求的时候恨不得把自己埋进PLC里。我前几年接手一个产线项目现场六台设备硬件配置大同小异程序却是三个工程师各写各的逻辑风格完全不同后来客户提了一个统一优化的需求我硬是在现场蹲了一周一边翻老代码一边改痛苦得刻骨铭心。也就是从那个项目之后我彻底想明白了一件事设备可以不一样但控制逻辑里的“共性”应该被提炼出来复用。ST结构化文本语言最大的优势就是能把逻辑写成真正意义上的“代码”而不是散落在梯形图里的触点线圈。有了ST我们就能做到一个功能块FB吃透一个设备族的多款机型。标题里说的“一个设备模板FB覆盖8种机型”听起来像是“神优化”但实际上是一套可以稳定落地的工程方法。这篇文章我想完整拆解一下这套复用架构是怎么做的从功能块设计到参数化配置再到实际调试中的坑全部摊开来讲。先给还不熟悉的朋友补个底STStructured Text是IEC 61131-3标准中的文本化编程语言适合写复杂算法、状态机、数据处理逻辑。FBFunction Block功能块是PLC里的核心复用单元相当于一个带记忆的“黑盒”你把输入参数丢进去它按内部逻辑跑一遍然后把输出结果给你。复用架构的核心就是让这个“黑盒”足够通用无论外面接的是哪种机型它都能正常工作。2. 复用架构的整体设计与拆解思路2.1 先搞清楚“8种机型”背后的共性逻辑接到“覆盖8种机型”这个需求时第一反应千万别是找一台最复杂的机型写一个FB然后指望其他机型自动适配——那是典型的“一厢情愿式复用”。正确做法是先把8种机型的控制需求全部拉出来做一次彻底的“求同存异”分析。我当时整理了一下发现不同机型之间变量无非三个层面的差异硬件差异轴数量不同、IO点数量不同、传感器型号不同。这些差异反映到程序里本质是“地址映射”不同。工艺差异节拍时间不同、参数范围不同、动作顺序有些微差别。这些差异体现在“工艺参数”和“运行模式”上。逻辑差异某些机型有特殊功能比如二次定位、自动换型而另一些没有。这要求功能块必须支持“可选功能段”。共性是哪来的所有机型本质上都遵循一条主流程上料、定位、加工、检测、下料。哪怕细节不同流程骨架一致。所以FB设计的第一原则就是定义一个稳定的主状态机把公共流程固化成框架差异性全部下沉到子功能或参数配置里。2.2 为什么ST是实现复用的最佳选择之一很多老工程师习惯用梯形图写逻辑这没毛病简单逻辑用梯形图确实直观。但到了“一台设备多种机型”这种场景梯形图的劣势就暴露得很明显逻辑“散”在网络上没法形成真正的封装大量重复的置位复位代码改一个分支要动一片不同机型的差异逻辑混在一起读代码的人根本分不清哪些是公共部分、哪些是机型特有部分。ST写FB相当于给控制逻辑做了一次“面向对象设计”。FB内部有独立的变量空间输入输出接口清晰逻辑以文本形式呈现天然支持条件分支、循环、状态枚举这些高级表达。用ST写代码本质上不是换一种语言而是换一种思维方式——从“画电路图”变成“写软件”。2.3 架构设计的核心分层整个复用架构我习惯分成三层设备层直接对接硬件包括IO映射函数、轴使能、模拟量采集。这一层是最“脏”的因为硬件型号千差万别但这一层的变化不应该往上传播。控制层这就是核心模板FB所在的位置承载工艺流程、状态切换、报警处理。应用层操作界面、配方管理、模式选择这一层跟设备的操作方式相关。复用设计的精髓就在控制层。设备层的差异在调用FB之前就已经被“抹平”——轴到位信号、气缸到位信号、传感器触发信号全都统一成标准格式FB看到的都是“标准化的输入”同样FB输出的也是标准化的命令具体驱动哪个轴、哪个阀由设备层去解析。提示这里最关键的一条原则是“共用代码不能反过来迁就硬件差异”。硬件差异如果突破了抽象层复用架构立刻崩盘。2.4 参数化和机制化让“一套代码”适配8种机型为了真正做到一个FB对应8种机型我把灵活性拆成了两个维度参数化机型代号、运行速度、气缸到位超时时间、定位精度等等全部映射到FB的“配置结构体”里。换机型时只需要在启动阶段调用一次配置加载把不同机型的配方数据填入结构体后续逻辑统一读取。机制化某些机型多一个轴某些流程多跨一步这些不是简单的参数能解决的而是需要机制来支持。机制化的常用做法有两个一是**“功能块内部条件编译”**。虽然工业PLC大多不支持传统意义的条件编译但可以通过枚举型配置参数让FB内部某些逻辑分支“激活/屏蔽”。比如配置参数USE_AXIS_5为TRUE时FB才会执行第5轴相关的控制逻辑为FALSE时相关代码段被跳过但所有接口仍然保留。二是**“外部钩子”机制**。将机型特有逻辑从通用逻辑中“摘”出来用可选调用指针或步骤钩子函数的方式暴露出来机器执行到对应步骤时功能块会自动调用外部钩子。不过第二点在实际项目中往往会增加调试复杂度如果你第一次做复用架构建议优先贯彻参数化机制化只保留“分支屏蔽”这一种手段等架构跑顺了再考虑外部钩子。3. 核心细节解析一个通用模板FB的完整设计3.1 FB输入输出接口设计抽象的统一契约接口设计是整套复用架构里最重要的一环。接口太少FB做不了太多事接口太多调用者会崩溃。这块没有标准答案但有一个很好的指导原则接口尽量表达“机器在做什么”而不是“某个硬件在做什么”。举个例子不要这样设计输入INPUT_CYLINDER_A_EXTENDED : BOOL;要这样设计FEED_CYLINDER_EXTENDED : BOOL;前者的接口绑定了一个具体的缸一旦换机型缸的编号变了整个程序调用处全部要改后者描述的是“进料缸伸出到位”换机型之后只需要在设备层把接线的地址重新映射给这个信号FB本身一行都不用改。下面给出我当时设计的FB接口骨架可以作为模板参考FUNCTION_BLOCK FB_DeviceTemplate VAR_INPUT bEnable : BOOL; // 主使能 bStartCycle : BOOL; // 启动一次循环 bAbort : BOOL; // 紧急中断回到待机 eMachineType : BYTE; // 机型代号1~8 stCfg : ST_DeviceCfg; // 配置结构体 bWorkpieceReady : BOOL; // 工件就位 bClampDone : BOOL; // 夹紧到位 bToolAtTarget : BOOL; // 动作机构到位 bSafetyOverridden : BOOL; // 安全旁路调试用 END_VAR VAR_OUTPUT bBusy : BOOL; // 正在运行 bDone : BOOL; // 单循环完成 eStep : BYTE; // 当前状态机步骤编号 bClampCmd : BOOL; // 夹紧/放松命令 bToolForwardCmd : BOOL; // 前进命令 bToolBackwardCmd : BOOL; // 后退命令 wAlarmCode : WORD; // 报警码 END_VAR3.2 配置结构体的设计极简但完整配置结构体是整个复用架构的“遥控器”。定义得好换机型只是换一张“配置卡”。这里最忌讳的是把所有参数塞进同一个结构体导致FB内部充满了一大堆“这个机型用不到”的字段。我当时用的做法是公共配置 机型扩展配置。短小精悍的公共配置存放所有机型都需要的参数比如各步骤超时时间、运行速度倍率、单循环计数目标机型扩展配置则是一个足够大的联合体/大结构体内部存放不同机型特有的数据。FB在初始化时只读取与当前机型相关的部分。TYPE ST_DeviceCfg : STRUCT sMachineName : STRING(20); uiVersion : UINT; rCycleTimeTarget : REAL; // 目标节拍 rTimeoutBase : REAL; // 基础超时时间 // 机型特有扩展区直接看 eMachineType 来决定哪个数据有效 stExtInfo : ST_ExtInfo; END_STRUCT END_TYPE TYPE ST_ExtInfo : STRUCT rSpeedAxis1 : REAL; rSpeedAxis2 : REAL; rPressForce : REAL; // 只有带压装功能的机型才用 bHasPressFun : BOOL; bHasVisionCheck : BOOL; END_STRUCT END_TYPE之所以不把每个机型单独建一个结构体是因为FB内部要用“统一入口”访问数据如果真的为每个机型做不同输入接口那就失去复用意义了。3.3 主状态机的实现用枚举把流程“焊死”状态机是实现工艺逻辑的骨架。设备逻辑写得乱绝大多数是因为状态散落、跳转随意。使用ST状态机可以写成一段非常规整的代码。我习惯用枚举类型定义状态比如TYPE E_Step : ( STEP_IDLE, STEP_WAIT_WORKPIECE, STEP_CLAMPING, STEP_MACHINING, STEP_UNCLAMPING, STEP_LOWERING, STEP_RESET ) : STEP_IDLE; END_TYPE然后在FB内部用CASE语句集中管理CASE eStep OF STEP_IDLE: IF bStartCycle THEN eStep : STEP_WAIT_WORKPIECE; END_IF; STEP_WAIT_WORKPIECE: IF bWorkpieceReady THEN bClampCmd : TRUE; eStep : STEP_CLAMPING; END_IF; STEP_CLAMPING: IF bClampDone THEN eStep : STEP_MACHINING; // 根据机型决定是否要调用压装功能 IF stCfg.stExtInfo.bHasPressFun THEN // 额外压装逻辑 END_IF; END_IF; // 超时判断 IF eStepTimer stCfg.rTimeoutBase THEN wAlarmCode : 16#1001; // 夹紧超时报警 eStep : STEP_IDLE; END_IF; // ... 其他状态类似 END_CASE;这里的核心技巧是状态机永远是同一套骨架每个状态内部都做一次“机型判断”但判断不会改变状态流转顺序。有特殊功能的机型在状态内部多执行几步没有的直接跳过。这样做的最大好处是维护人员看流程图的时候永远看到的是同一个流程图不会因为机型不同出现两套流程描述。3.4 报警与诊断信息的标准化8种机型共用一个报警码表听起来很美好但实际情况往往很“魔幻”。A机型报警12号是“夹紧超时”到了B机型报警12号却变成了“温度过高”这就没法统一了。我的建议是报警码表必须是全局唯一的预留扩展区段但语义不得漂移。功能块内部私有报警区比如16#1000~16#1FFF只归它们自己用设备层的报警则从16#8000开始向上分配。即便某台机器没有某个报警条件对应报警位也要保留占位确保整个项目家族里同一个码永远代表同一个意思。报警输出至少同时包含两个信息报警码给HMI弹窗用的内部错误原因枚举给程序调试用的后者特别重要。因为你现场排查时一台设备报“0001 夹紧超时”你根本不知道是缸没动、磁性开关没感应到、还是气缸根本没有气压而内部错误变量会记录得更细。4. 从代码到实战实现流程与关键步骤4.1 第一步统一输入输出信号命名规范动手写代码之前先把信号命名规范定好。这套规范的价值在写代码时感受不到等设备交货后、维护工程师进场时才有切身体会。我当时定了四条铁律所有跨层传递的布尔信号一律采用 “动作对象状态” 的格式比如Clamp_Extended、Tool_Forward_Done。命令信号用“动作”开头Cmd_Clamp_On、Cmd_Tool_Forward。中间变量统一加前缀v_全局变量统一加g_看一眼就知道变量的作用域。机型的差异变量必须以cfg_开头任何代码里看到带cfg_的都明白这属于配置区。命名规范最大的意义是8种机型的程序合并在一起任何人打开都能在五分钟内定位到某个信号。这个共识不建立起来复用就是场面话。4.2 第二步做一次彻底的“机型差异清单”动手写代码之前我用Excel拉了一张表。这张表长什么样左边一列是所有可能的控制功能项上面一行是8个机型的代号然后逐格圈定哪些机型有这个功能哪些没有。没有这个清单打底直接开始写代码你写出来的所谓“通用功能块”其实只是其中一个机型代码的复制品。做清单时注意要把“硬件差异”和“逻辑差异”分开列。硬件差异走参数映射解决逻辑差异走功能块内部条件分支解决。两类问题混在一起后面会非常痛苦。把“当前差异”和“未来可能差异”也分开。只考虑当下8种机型的差异来做设计不要让幻想中的第9种机型干扰现在的代码结构。4.3 第三步从“最全机型”提炼主版本代码写模板FB时不要从一个简单机型开始慢慢加功能那样代码会被改得面目全非也不要挑最复杂的机型机型作为基准把“特定功能”和“公共框架”混在一起。正确做法是先虚拟一台“完全体”设备包含所有机型可能出现的一切功能段然后要求每一步执行前判断“当前机型是否启用该功能”。这句话听起来简单实际操作却非常考验定力。因为你会不停面临诱惑这个功能只有一种机型有不如直接在外面IF一下不行的“IF一下”在局部会很简单但当8个机型各自“IF一下”叠加起来代码就变成了意大利面条。我给自己立了一条规矩FB内部所有功能段必须属于“公共框架的一部分”开启条件只能是配置参数而调用FB之前允许预处理程序做不同机型的信号映射FB内部不做“换机型就换逻辑”的事。4.4 第四步调用与映射层的实现FB本身写好后剩下的工作就是为每个机型建立调用映射。以下是我当时写的简化示例CASE g_iMachineType OF 1: // 机型1 FB_Dev.bWorkpieceReady : g_IO_WorkpieceDetect1; FB_Dev.bClampDone : g_IO_ClampMagnetic1; FB_Dev.bToolAtTarget : g_IO_ToolAtHome1 AND g_IO_ToolAtWork1; 2: // 机型2采用了不同信号的传感器 FB_Dev.bWorkpieceReady : g_IO_WorkpieceDetect2; FB_Dev.bClampDone : g_IO_ClampMagnetic2; FB_Dev.bToolAtTarget : g_IO_ToolAtWork2; 3: // 机型3带压装功能多了一个压装到位信号 FB_Dev.bWorkpieceReady : g_IO_WorkpieceDetect3; FB_Dev.bClampDone : g_IO_ClampMagnetic3 AND g_IO_PressMagnetic3; FB_Dev.bToolAtTarget : g_IO_ToolAtHome3; // ... 其他机型 END_CASE;这种写法的好处是什么把硬件差异全部隔离在这个映射段里。映射段是唯一允许“一机器一份”的地方而控制层的FB代码始终唯一。4.5 第五步仿真验证与半实物测试在线调试之前必须先在仿真环境里跑通所有8种机型但这还不够。仿真环境里一切都听你的现实不是。所以我会把测试分成三条路径纯仿真所有输入强制给定验证状态机流转是否包得住每种机型的工艺路径。半实物PLC接一部分真实IO另一部分仿真模拟重点验证信号映射层有没有接错线。真机空跑不带产品跑一次空循环检查动作顺序、超时设定、报警是否能正常产生和复位。这一步的建议是把“8种机型的仿真配置”全部存档。下一次改代码时9种场景8种机型1种虚拟全功能一键切换跑完看结果比现场反复搬实物要省力得多。5. 踩坑记录这套架构在实战中遇到的5个典型问题5.1 问题一忘记给FB加“机型标识”导致碰撞报警第一次联调时同事同时加载了两台设备的程序一台机器上电后立刻报“机型不支持”报警。排查后发现FB内部对机型代号做了范围校验但代号是从HMI配方界面读取的HMI默认值写的00不在1~8范围内所以直接报警。解决办法机型代号增加合法性默认值加载时若读到0或非法值自动切换为默认机型并提示操作员确认。这个“安全默认值”的思路在整个架构里非常实用。5.2 问题二结构体里的BOOL位被“跨机型污染”因为扩展配置结构体是共享的A机型运行时把bHasPressFun置了TRUENormal状态下没问题可当切换成B机型时B机型没有压装功能但由于这个BOOL值没有在初始化时被刷新成FALSE导致B机型误入压装逻辑。排查了一个下午。解决思路每次调用FB之前固定对配置结构体做一次MemReset清零然后根据机型重新填充。结构体在切换机型时必须严格遵循“先清后写”原则不要寄希望于上一次调用留下的数据是对的。5.3 问题三超时时间一刀切导致部分机型误报警通用逻辑里的超时时间如果设成固定值低速机型动作还没到位就被判超时报警频发而高速机型则需要等待很久才能触发超时失去了保护意义。解决方案超时时间全部拆成“基础值机型倍率”公式是实际超时 基础值 × 机型倍率。倍率存在配置结构体里按机型的不同填入1.2、1.5等。5.4 问题四报警码虽然有但缺少“现场排除指引”报警输出一个码HMI上只显示一行字维护人员到现场依然不知道先查什么。这也是复用架构推广时最大的阻力——操作员会觉得“原来每个机型有自己单独的报警说明现在变成一个大而全的模板查起来更累”。对策为每个报警码配套一段简短排查指引存成字符串数组HMI扫描到报警码后同时显示指引文本。比如报警16#1001对应的指引是“1. 检查夹紧气缸磁性开关2. 检查气源压力3. 检查工件是否卡滞。”这玩意儿花不了多少功夫但对现场维护效率的提升是立竿见影的。5.5 问题五程序被多人修改后配置结构体“加字段”引发旧程序不兼容复用架构代码往往不止一个人维护。某天一位工程师往配置结构体里加了一个新字段结果旧机型PLC里的数据块长度不一致程序启动直接报长度错误。经验教训给配置结构体做版本号字段每次结构体发生变更时手动递增版本号同时在启动时校验版本号一致才允许运行。另外新增字段只能追加在结构体末尾绝对不允许在中间插入字段。6. 常见问题速查表问题现象可能原因排查思路与建议切机型后部分气缸不动作配置结构体内的BOOLean类型残留旧值先清后写启动时整体复位配置结构体无论选哪个机型状态机总在同一个步骤卡住映射层信号地址接错或信号未更新在线监控该步骤的输入条件逐一对比地址映射运行速度参数在A机型正常但B机型异常参数超时倍率未按机型配置确认配置结构中倍率字段按机型差异化填写报警码相同但不同机型触发条件不一致报警判断条件没有跟随机型参数变化将报警判定阈值提取成配置参数而不是硬编码程序升级后旧PLC报数据块长度错误配置结构体字段顺序有变动保持结构体版本号新字段一律追加在末尾调试中修改代码后其他机型故障公共代码改动影响了所有机型建立“回归测试清单”每次改动跑一遍所有机型仿真7. 这套复用架构的利润公式投入与回报很多人看完会说一个模板FB覆盖8种机型前期的设计工作量比单独写8份程序还要大很多这真的划算吗划算但要算大账不能算小账。前期做复用架构比“各写各的”大概多投入一倍的时间。但项目交付之后的账完全不一样维护成本改动公共逻辑只改一处8种机型同时生效。如果是8份程序你得改8遍而且极容易出现改漏一台的情况。人员培训成本维护工程师只需要学懂一套FB的工作原理就可以处理全家族设备不需要记8套不同风格的代码逻辑。备件与升级成本新机型诞生时大部分FB直接复用新增一个机型配置和映射即可开发周期能缩短30%~50%。质量可靠性公共代码经历了8种机型的现场验证迭代出来的成熟度远高于单机型代码的成熟度。从上线初期算起基本上第一个新机型的开发节省的时间就能把前期搭架构的额外投入抹平后续每一次家族扩种都是纯赚。8. 亲手实操后的一些翻新技巧最后再分享一些经验性的技巧这些在我多次实战中起到了关键作用。第一个是关于代码注释的写法。很多人注释喜欢写“这行代码是在做什么”但复用架构里更需要写的是“这行代码为什么这样做”以及“它和哪几个机型相关”。原因很简单8种机型共用一份代码后代码的量级和复杂度都上来了看代码的人最怕的就是遇到没有上下文的改动。第二个是关于FPGA/仿真环境的利用。现在的控制器基本都支持仿真运行把8种机型的配置全部建成仿真工程改一次代码就把8种机型全都跑一遍这是确保改动不破坏其他机型的最笨但最有效的方法。第三个关于命名不妨与机型的工艺编号挂钩而不是PLC的站点编号。设备复用架构的终极目标是对使用者友好如果一个维护工人看到变量名就知道它对应的工艺环节这架构才算真正成功了一半。第四个也是我认为最重要的在任何一次改动之前先跑一遍“回归清单”把8种机型全部走一遍空循环。哪怕你觉得改动无关紧要也要跑。因为我踩过太多次“只改A机型结果B机型报警”的坑了回归测试是复用架构唯一的防护网。第五个小技巧在配置结构体里放一个“架构版本号”字段每次升级公共模板时在说明文档里记录变化点并把版本号同步更新到所有配方的默认值里。如果没有这个习惯半年后你就会发现现场跑着好几种不同年代的通用模板而你已经分不清哪个版本对应哪台机器的实际状态。这就是复用架构“灾难性腐化”的开始。技术推动的复用从来都不只是省时间和省精力它更是一种工程素养的体现——把混乱的碎片化设备家族梳理成一套有秩序、有层次的体系。能做到一个FB覆盖8种机型对工程师而言真正有价值的地方在于你开始从“写代码的人”变成“建体系的人”这个转变本身就已经值回所有的前期投入。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑