4. 结构化编程思想
PLC 程序从传统的线性化编写走向工程化、团队化协作,其核心驱动力并非某一条指令或某一个功能块,而是一整套结构化编程思想。许多工程师在掌握 FB(Function Block)的语法后,仍然写出“面条式”程序,根源在于缺乏对模块化、封装、复用、抽象和接口设计这五大原则的系统理解。这五项原则不是孤立的技术点,而是一个有机整体:模块化是程序的组织形态,抽象是认知问题的方式,封装是隐藏复杂性的手段,接口是模块间协作的契约,复用则是前四者共同追求的价值目标。本章将深入剖析这五大原则在 PLC 工程中的具体落地方法,帮助读者建立真正的“结构化思维”。
4.1 模块化:从“程序”到“系统”的分解艺术
4.1.1 核心原理:粒度与边界的平衡
模块化是将一个复杂的自动化任务分解为若干独立、可管理、职责单一的子模块的过程。在 PLC 编程中,模块化的最小单元是 FC/FB,但模块化思想远不止于此——它要求工程师从整个控制系统的视角出发,将设备层(传感器/执行器)、控制层(逻辑/算法)、管理层(配方/报表)进行纵向切分,再将每个层面横向拆分为功能内聚的模块。模块化的核心难点在于粒度控制:模块过大则失去复用价值,模块过小则导致接口爆炸和调用关系混乱。一个经验法则是:一个 FB 的代码量控制在 50~200 行之间,输入输出参数不超过 10 个,内部状态变量不超过 20 个。
4.1.2 判断方法:模块划分的“高内聚、低耦合”检验
判断模块划分是否合理,可以运用以下三个检验标准:
- 单一职责检验:用一句话描述该模块的功能,如果描述中出现了“和”、“以及”、“同时”等连接词,说明职责过多,需要拆分。
- 修改影响面检验:当某个机械部件(如气缸)更换为不同品牌时,需要修改的模块数量。理想情况下只修改一个 FB 的内部实现,而不影响调用方。
- 复用场景检验:问自己“这个模块除了当前项目,未来是否可能被其他项目复用?”如果答案是否定的,考虑是否应该下沉为更通用的模块。
4.1.3 案例拆解:包装线贴标机控制模块划分
以一条典型的包装线贴标机为例,其控制需求包括:输送带启停与调速、贴标头动作、标签检测、计数统计、报警处理、配方切换。若将所有逻辑写在一个 OB1 中,程序将超过 2000 行,难以调试和维护。合理的模块化划分如下:
| 模块名称 | 类型 | 职责 | 关键接口 |
|---|---|---|---|
| FB_Conveyor | FB | 输送带速度控制、启停逻辑 | i_Start, i_Stop, i_SpeedSet, q_Running |
| FB_LabelHead | FB | 贴标头动作时序、气缸控制 | i_Trigger, i_HomeSensor, q_Extend, q_Retract |
| FB_LabelDetect | FB | 标签有无检测、计数 | i_Sensor, q_Count, q_Error |
| FB_RecipeMgr | FB | 配方参数存储与切换 | i_RecipeNo, q_Params |
| FB_AlarmMgr | FB | 报警收集、显示、确认 | i_AlarmCode, q_AlarmActive |
该划分使得每个模块职责单一,输送带模块与贴标头模块之间通过“标签到位信号”进行松耦合交互,任何模块的修改都不会波及其他模块。
4.2 抽象:从“具体设备”到“通用模型”的思维跃迁
4.2.1 核心原理:剥离细节,抓住本质
抽象是结构化编程中最具思维深度的一环。它要求工程师忽略设备的具体型号、通信协议、物理特性,而关注其行为特征和控制需求。例如,无论是西门子 V90 伺服还是三菱 MR-JE 伺服,在抽象层面它们都是“带位置反馈的速度可控执行器”,拥有“使能”、“速度设定”、“当前位置”、“到位信号”等共性属性。抽象能力决定了 FB 的通用性——优秀的 FB 库往往能覆盖 80% 以上的同类设备,而无需修改代码。
4.2.2 操作流程:从具体案例中提炼抽象模型
抽象模型的建立通常遵循以下四步流程:
- 收集具体实例:列出项目中所有同类设备(如所有气缸、所有变频器、所有模拟量输入)。
- 提取共性行为:分析这些设备在控制层面有哪些相同的操作序列(如气缸都是“伸出-缩回-到位检测”)。
- 定义状态与事件:将共性行为转化为状态机(如气缸的 Idle、Extending、Extended、Retracting、Retracted 状态)。
- 参数化差异点:将不同设备的差异(如气缸行程时间、变频器加减速时间)转化为 FB 的输入参数。
4.2.3 实战场景:模拟量采集的抽象设计
假设项目中需要采集 4~20mA 压力传感器、PT100 温度传感器和 0~10V 液位传感器。如果为每种传感器单独编写 FC,将产生大量重复代码。通过抽象,可以设计一个通用的 FB_AnalogInput:
FUNCTION_BLOCK FB_AnalogInput
VAR_INPUT
i_RawValue : INT; // 原始AD值
i_ScaleMin : REAL; // 工程量下限
i_ScaleMax : REAL; // 工程量上限
i_RawMin : INT; // AD值下限(对应4mA或0V)
i_RawMax : INT; // AD值上限(对应20mA或10V)
i_FilterTime : TIME; // 滤波时间常数
END_VAR
VAR_OUTPUT
q_EngValue : REAL; // 工程量值
q_Alarm : BOOL; // 超限报警
END_VAR
该 FB 通过 i_RawMin/i_RawMax 参数适配不同量程的传感器,通过 i_ScaleMin/i_ScaleMax 完成工程换算,内部实现一阶惯性滤波。三种传感器只需实例化三个 FB 并设置不同参数即可,实现了“一次编写,处处使用”的抽象价值。
4.3 封装:信息隐藏与访问控制
4.3.1 核心原理:对外暴露接口,对内隐藏实现
封装是模块化的自然延伸,其核心思想是“最小知识原则”——调用方只需要知道 FB 的输入输出接口和功能描述,而不需要了解内部的数据结构、算法实现和中间变量。在 IEC 61131-3 标准中,FB 的 VAR 区天然实现了封装:VAR_INPUT/VAR_OUTPUT 是公共接口,VAR 是私有存储区,VAR_IN_OUT 是特殊引用传递。封装带来的直接好处是:修改 FB 内部实现(如优化算法、更换滤波方式)时,只要接口不变,所有调用方代码无需任何修改。
4.3.2 判断方法:封装质量的评估维度
- 接口完整性:调用方是否只需通过接口就能完成所有操作?如果调用方需要直接访问 FB 的私有变量(如通过全局变量桥接),说明封装被破坏。
- 状态可见性:FB 的内部状态(如当前处于哪个步骤)是否通过输出参数或状态枚举对外可见?良好的封装应提供只读的状态输出。
- 错误处理能力:FB 是否在内部完成输入合法性校验和错误检测?而不是将错误数据传递给下游。
4.3.3 案例拆解:电机控制 FB 的封装演进
以一个电机启停控制为例,展示封装从劣到优的演进过程:
| 封装级别 | 实现方式 | 问题 |
|---|---|---|
| 无封装 | 在 OB1 中直接写接触器线圈输出、热继电器输入、定时器逻辑 | 每个电机重复编写,修改逻辑需逐处修改 |
| 初级封装 | 建立 FB_Motor,包含启停逻辑和定时器 | 但定时器变量使用全局 T 编号,多实例冲突 |
| 标准封装 | FB_Motor 内部使用自身实例的 TON 实例化定时器,通过 i_Start/i_Stop/i_Fault 接口控制,输出 q_Running/q_Error | 接口清晰,内部实现完全隐藏,多实例互不干扰 |
标准封装后的 FB_Motor 内部包含:启动自锁逻辑、停止优先逻辑、故障复位逻辑、运行超时检测。调用方只需 3 行代码即可完成一个电机的完整控制,且所有内部细节(如定时器编号、中间继电器)均被隐藏。
VAR CONSTANT 定义内部常量;对于需要跨扫描周期保持的数据,使用 FB 的静态变量而非全局 DB。封装不仅是技术行为,更是团队协作的契约——接口一旦发布,变更需走评审流程。
4.4 接口设计:模块间协作的“合同”
4.4.1 核心原理:接口是模块的“外交官”
接口设计是结构化编程中最容易被低估的环节。一个设计良好的接口,应当具备自文档化(通过参数名即可理解含义)、最小化(只暴露必要的交互点)、稳定性(不随内部实现频繁变动)和容错性(对非法输入有明确处理策略)四个特征。在 PLC 中,接口设计具体体现在 FB 的引脚定义、UDT(用户自定义类型)结构设计以及全局变量命名规范上。
4.4.2 操作流程:接口设计的五步法
- 识别交互场景:列出该模块与外部环境的所有交互(数据输入、命令触发、状态反馈、报警输出)。
- 定义数据类型:为每个交互点选择合适的数据类型,优先使用 REAL/INT/BOOL 等基础类型,复杂数据使用 UDT。
- 设计参数方向:明确每个参数是 INPUT(只读)、OUTPUT(只写)还是 IN_OUT(读写)。
- 设置默认值:为 INPUT 参数设置合理的默认值,使 FB 在“零配置”下也能安全运行。
- 编写接口文档:在 FB 的注释区详细说明每个参数的单位、范围、含义和典型值。
4.4.3 实战场景:PID 温控 FB 的接口设计对比
以 PID 温度控制 FB 为例,对比糟糕与优秀的接口设计:
// 糟糕的接口设计
FUNCTION_BLOCK FB_PID_Bad
VAR_INPUT
i_Param1 : REAL; // 这是什么?
i_Param2 : REAL; // 完全无法理解
i_Param3 : TIME; // 含义不明
END_VAR
// 优秀的接口设计
FUNCTION_BLOCK FB_PID_Good
VAR_INPUT
i_Setpoint : REAL; // 温度设定值(单位:℃)
i_ProcessValue: REAL; // 当前温度反馈(单位:℃)
i_Kp : REAL; // 比例增益(默认1.0)
i_Ki : REAL; // 积分时间(单位:秒,默认60)
i_Kd : REAL; // 微分时间(单位:秒,默认0)
i_SampleTime : TIME; // 采样周期(默认T#100MS)
i_Enable : BOOL; // PID使能
END_VAR
VAR_OUTPUT
q_ControlOut : REAL; // 控制输出(0~100%)
q_AtSetpoint : BOOL; // 到达设定值
q_Error : BOOL; // 输入异常
END_VAR
优秀的接口设计让调用方无需查看内部代码即可正确使用。此外,接口设计还应考虑扩展性——例如预留 i_FeedForward(前馈量)输入,以便未来增加前馈控制而不破坏现有接口。
i_StartCmd、q_ActualSpeed、io_ParameterPtr。避免使用 a、b、c 等无意义命名。
4.5 复用:从“项目开发”到“知识积累”的转变
4.5.1 核心原理:复用的三个层次
复用是结构化编程的终极目标,它分为三个层次:代码复用(直接复制 FB 到新项目)、模块复用(将 FB 打包为库文件,通过库管理器引用)、架构复用(将整个项目的程序框架、命名规范、错误处理策略作为模板)。最高层次的复用是架构复用——它不仅仅是代码的搬运,更是工程经验和设计模式的传承。要实现有效复用,FB 必须做到“与项目无关”——不引用任何全局变量、不依赖特定硬件地址、不包含项目特定的业务逻辑。
4.5.2 操作流程:建立企业级 FB 库的步骤
- 步骤一:盘点现有资产:从历史项目中提取可复用的 FB,按功能域分类(运动控制、过程控制、逻辑控制、通信)。
- 步骤二:标准化重构:统一接口命名规范、注释格式、错误处理方式,去除项目特定代码。
- 步骤三:版本管理:使用库文件(如 TIA Portal 的 .zal 文件、TwinCAT 的 .library 文件)进行版本控制,记录变更日志。
- 步骤四:验证与文档:为每个库 FB 编写测试用例和用户手册,包含典型接线图、参数说明、示例代码。
- 步骤五:持续改进:在项目使用中收集反馈,定期更新库版本,但保持向后兼容。
4.5.3 案例拆解:某设备商 FB 库的复用收益
某包装设备制造商在三个同类项目中应用了统一的 FB 库,包含 15 个核心 FB(气缸控制、伺服定位、模拟量处理、报警管理等)。通过复用,取得了以下量化收益:
| 指标 | 项目一(无库) | 项目二(有库) | 项目三(有库) |
|---|---|---|---|
| PLC 程序代码量 | 8500 行 | 4200 行 | 3900 行 |
| 调试时间 | 6 周 | 2.5 周 | 2 周 |
| 现场故障数 | 23 个 | 7 个 | 5 个 |
| 新工程师上手时间 | 4 周 | 1 周 | 1 周 |
复用不仅减少了编码量,更重要的是降低了调试难度和现场故障率。项目三中,工程师甚至不需要打开 FB 内部代码,仅通过接口文档和 HMI 调试画面即可完成大部分调试工作。
4.6 五大原则的综合应用与复盘
在实际工程中,五大原则并非按顺序执行,而是相互交织、迭代演进的。一个典型的开发流程是:首先通过抽象理解设备本质,然后进行模块化划分,接着为每个模块设计接口,再通过封装实现内部逻辑,最后在多个项目中验证和提升复用价值。下表总结了五大原则的核心要点和常见误区:
| 原则 | 核心问题 | 关键动作 | 常见误区 |
|---|---|---|---|
| 模块化 | 如何分解系统? | 按物理边界和功能内聚划分 | 模块粒度过大或过小 |
| 抽象 | 如何忽略差异? | 提取共性行为,参数化差异 | 过度抽象导致配置复杂 |
| 封装 | 如何隐藏细节? | 仅暴露接口,隐藏内部实现 | 调用方直接访问内部变量 |
| 接口设计 | 如何定义协作? | 明确参数方向、类型、默认值 | 接口命名模糊、无文档 |
| 复用 | 如何积累资产? | 建立库文件,版本管理 | 盲目复制未验证的代码 |
结构化编程的最终目标是让 PLC 程序从“个人手工作品”转变为“团队工程产品”。当工程师能够熟练运用这五大原则时,程序的可读性、可维护性和可扩展性将得到质的飞跃。建议读者从下一个项目开始,有意识地运用本章的方法论:先画模块划分图,再定义接口清单,然后才动手编写代码。经过两到三个项目的实践,结构化思维将内化为一种本能反应,届时你会发现,编程不再是“写代码”,而是“设计系统”。