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_ConveyorFB输送带速度控制、启停逻辑i_Start, i_Stop, i_SpeedSet, q_Running
FB_LabelHeadFB贴标头动作时序、气缸控制i_Trigger, i_HomeSensor, q_Extend, q_Retract
FB_LabelDetectFB标签有无检测、计数i_Sensor, q_Count, q_Error
FB_RecipeMgrFB配方参数存储与切换i_RecipeNo, q_Params
FB_AlarmMgrFB报警收集、显示、确认i_AlarmCode, q_AlarmActive

该划分使得每个模块职责单一,输送带模块与贴标头模块之间通过“标签到位信号”进行松耦合交互,任何模块的修改都不会波及其他模块。

经验提醒:模块化不是“越细越好”。在项目启动阶段,先画出设备物理结构图,将物理上相对独立的部件(如伺服轴、气缸组、温控区)作为模块划分的天然边界,再根据控制逻辑的耦合度进行微调。

4.2 抽象:从“具体设备”到“通用模型”的思维跃迁

4.2.1 核心原理:剥离细节,抓住本质

抽象是结构化编程中最具思维深度的一环。它要求工程师忽略设备的具体型号、通信协议、物理特性,而关注其行为特征和控制需求。例如,无论是西门子 V90 伺服还是三菱 MR-JE 伺服,在抽象层面它们都是“带位置反馈的速度可控执行器”,拥有“使能”、“速度设定”、“当前位置”、“到位信号”等共性属性。抽象能力决定了 FB 的通用性——优秀的 FB 库往往能覆盖 80% 以上的同类设备,而无需修改代码。

4.2.2 操作流程:从具体案例中提炼抽象模型

抽象模型的建立通常遵循以下四步流程:

  1. 收集具体实例:列出项目中所有同类设备(如所有气缸、所有变频器、所有模拟量输入)。
  2. 提取共性行为:分析这些设备在控制层面有哪些相同的操作序列(如气缸都是“伸出-缩回-到位检测”)。
  3. 定义状态与事件:将共性行为转化为状态机(如气缸的 Idle、Extending、Extended、Retracting、Retracted 状态)。
  4. 参数化差异点:将不同设备的差异(如气缸行程时间、变频器加减速时间)转化为 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 并设置不同参数即可,实现了“一次编写,处处使用”的抽象价值。

风险提示:过度抽象是常见陷阱。如果为了追求通用性而引入过多的配置参数和分支判断,FB 的内部逻辑会变得难以理解和调试。建议遵循“80/20 法则”——只抽象覆盖 80% 场景的共性,剩余 20% 的特殊场景通过独立 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 行代码即可完成一个电机的完整控制,且所有内部细节(如定时器编号、中间继电器)均被隐藏。

封装实践要点:在 TIA Portal 或 TwinCAT 中,为 FB 添加版本号和作者信息;使用 VAR CONSTANT 定义内部常量;对于需要跨扫描周期保持的数据,使用 FB 的静态变量而非全局 DB。封装不仅是技术行为,更是团队协作的契约——接口一旦发布,变更需走评审流程。

4.4 接口设计:模块间协作的“合同”

4.4.1 核心原理:接口是模块的“外交官”

接口设计是结构化编程中最容易被低估的环节。一个设计良好的接口,应当具备自文档化(通过参数名即可理解含义)、最小化(只暴露必要的交互点)、稳定性(不随内部实现频繁变动)和容错性(对非法输入有明确处理策略)四个特征。在 PLC 中,接口设计具体体现在 FB 的引脚定义、UDT(用户自定义类型)结构设计以及全局变量命名规范上。

4.4.2 操作流程:接口设计的五步法

  1. 识别交互场景:列出该模块与外部环境的所有交互(数据输入、命令触发、状态反馈、报警输出)。
  2. 定义数据类型:为每个交互点选择合适的数据类型,优先使用 REAL/INT/BOOL 等基础类型,复杂数据使用 UDT。
  3. 设计参数方向:明确每个参数是 INPUT(只读)、OUTPUT(只写)还是 IN_OUT(读写)。
  4. 设置默认值:为 INPUT 参数设置合理的默认值,使 FB 在“零配置”下也能安全运行。
  5. 编写接口文档:在 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_/q_/io_),名称使用驼峰式或下划线式。例如 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 调试画面即可完成大部分调试工作。

复用陷阱警示:盲目复用是危险的。在将 FB 从一个项目复制到另一个项目前,必须检查:①是否引用了原项目的全局 DB 或硬件地址?②是否依赖特定 PLC 型号的专用指令?③内部定时器/计数器是否使用了全局编号?④注释中的单位、量程是否与当前项目一致?建议在 FB 头部添加“适用 PLC 型号”、“依赖库版本”、“最后验证日期”等元信息。

4.6 五大原则的综合应用与复盘

在实际工程中,五大原则并非按顺序执行,而是相互交织、迭代演进的。一个典型的开发流程是:首先通过抽象理解设备本质,然后进行模块化划分,接着为每个模块设计接口,再通过封装实现内部逻辑,最后在多个项目中验证和提升复用价值。下表总结了五大原则的核心要点和常见误区:

原则核心问题关键动作常见误区
模块化如何分解系统?按物理边界和功能内聚划分模块粒度过大或过小
抽象如何忽略差异?提取共性行为,参数化差异过度抽象导致配置复杂
封装如何隐藏细节?仅暴露接口,隐藏内部实现调用方直接访问内部变量
接口设计如何定义协作?明确参数方向、类型、默认值接口命名模糊、无文档
复用如何积累资产?建立库文件,版本管理盲目复制未验证的代码

结构化编程的最终目标是让 PLC 程序从“个人手工作品”转变为“团队工程产品”。当工程师能够熟练运用这五大原则时,程序的可读性、可维护性和可扩展性将得到质的飞跃。建议读者从下一个项目开始,有意识地运用本章的方法论:先画模块划分图,再定义接口清单,然后才动手编写代码。经过两到三个项目的实践,结构化思维将内化为一种本能反应,届时你会发现,编程不再是“写代码”,而是“设计系统”。