3. 模块化设计原则与粒度划分
在 Studio 5000 中做程序复用,真正决定成败的不是“会不会建 AOI”,而是模块边界划在哪里。边界划得太粗,一个 AOI 里塞进电机、阀门、PID 和报警,任何一处改动都会牵动全局;边界划得太细,一个启动按钮也做成 AOI,调用关系会变得比梯形图本身还复杂。本节围绕三条核心原则——高内聚低耦合、单一职责、接口最小化——并结合电机、阀门、PID 回路三类典型设备,说明如何把原则落到实际的粒度划分上。
三条原则的工程含义
高内聚指一个模块内部的所有逻辑都服务于同一个明确目标。例如“电机控制”模块内部应包含启动、停止、故障复位、运行反馈判断、过载处理,这些逻辑彼此紧密相关,改动时通常一起改。低耦合指模块之间尽量少知道对方的内部细节,只通过清晰的接口交互。电机模块不应直接读写阀门模块的内部标签,而应通过输入输出参数传递状态。
单一职责是判断粒度的第一把尺子:如果一个模块存在两个以上“因为不同原因而修改”的理由,就应考虑拆分。典型反例是把“PID 运算”和“PID 输出限幅与手自动切换”混在一个 AOI 中——整定参数变化和工艺联锁变化是两类完全不同的修改动因。
接口最小化要求只暴露必要参数。一个电机 AOI 若把内部定时器预设值、中间故障位全部开放为 InOut,调用者就会依赖这些内部实现,复用性反而下降。原则是:能通过 Input/Output 表达的,不用 InOut;能由模块内部计算的,不要求外部传入。
粒度划分的判断依据
实际划分时,可用四个问题快速判断:该对象是否会被多处重复调用?它的状态是否独立?它的修改动因是否单一?它的接口是否稳定?四问皆“是”,就适合做成独立模块。反之,若某段逻辑只在一个工位出现一次、且与周围逻辑强耦合,强行模块化只会增加间接层。
粒度还受设备物理边界影响。电机、阀门、PID 回路在 P&ID 和电气图上本身就是独立设备,把它们各自封装为一个 AOI,与工程师的思维习惯一致,也便于按设备类型批量复用。下面这张图给出三类典型设备的模块边界与接口关系,可作为划分时的参照。
电机模块的粒度实战
以一台 7.5 kW 三相异步电机为例,其控制需求通常包括:现场/远程启停、运行反馈、过载故障、故障复位、运行累计时间。划分时,把“启保停逻辑 + 故障锁存 + 反馈超时判断”封装为一个 Motor_AOI,接口设计如下:
Input: Start, Stop, Reset, OverloadFdbk, RunFdbk
Output: RunCmd, RunStatus, Fault, FaultCode, RunHours
InOut: MotorData (UDT: 内部定时器、锁存位、累计时间)
这里 MotorData 使用 UDT 作为 InOut,是为了让每个电机实例拥有独立的状态存储,而不是共享全局标签。若把定时器预设值也放进 InOut,调用者就可能随意修改,破坏模块一致性——因此预设值应作为模块内部常量或 Input 参数并给出默认值。
阀门模块的粒度实战
阀门与电机的关键差异在于“动作到位”判断。以气动切断阀为例,模块内部需要处理开阀输出、关阀输出、开到位反馈、关到位反馈、动作超时报警。接口可设计为:
Input: OpenCmd, CloseCmd, OpenedFdbk, ClosedFdbk, TimeoutSP
Output: OpenOut, CloseOut, Opened, Closed, Fault
InOut: ValveData (UDT: 动作计时、故障锁存)
注意 OpenOut 与 CloseOut 必须互锁,且互锁逻辑放在模块内部,而不是让调用者自己保证。这样无论阀门被调用多少次,互锁行为都一致,这正是高内聚带来的复用价值。
PID 回路的粒度实战
PID 回路的划分最容易出问题。常见错误是把 PID 运算、手自动切换、输出限幅、报警判断全部塞进一个模块,导致整定参数与工艺联锁耦合。更合理的做法是:PID 运算与限幅、无扰切换放在同一模块,因为它们共同决定输出质量;而偏差报警、联锁停车放在调用层或独立报警模块,因为它们属于工艺安全逻辑,修改动因不同。
Input: PV, SP, AutoMode, ManualOut, Kp, Ki, Kd, OutMin, OutMax
Output: OUT, Deviation, Saturated
InOut: PIDData (UDT: 积分项、上次PV、上次OUT)
其中 PIDData 保存积分项和上次输出,是实现无扰切换的关键。若把积分项暴露为普通 Output,调用者可能误写,导致切换时输出跳变。因此 InOut 的使用必须严格限定在“模块需要跨扫描周期保持且调用者不应修改”的对象上。
接口最小化的落地检查
完成初版划分后,可用一张检查表快速复核。下表以三个模块为例,列出常见接口问题与修正方向。
| 模块 | 常见冗余接口 | 问题 | 修正 |
|---|---|---|---|
| Motor_AOI | Timer.PRE 作为 InOut | 调用者可改预设,行为不一致 | 改为 Input 并给默认值 |
| Valve_AOI | OpenOut / CloseOut 同时为 InOut | 调用者可能绕过互锁 | 改为 Output,互锁内置 |
| PID_AOI | Integral 作为 Output | 外部误写导致无扰切换失效 | 移入 PIDData UDT,用 InOut |
最后需要强调,模块化不是把程序切得越碎越好。电机、阀门、PID 三类设备之所以适合独立成模块,是因为它们在物理上独立、状态上独立、修改动因上独立。若某个逻辑只服务于单一工位且与周围强耦合,保留在调用层反而更清晰。划分的目标是让复用自然发生,而不是为了模块化而模块化。