3. 模块化设计原则与粒度划分

在 Studio 5000 中做程序复用,真正决定成败的不是“会不会建 AOI”,而是模块边界划在哪里。边界划得太粗,一个 AOI 里塞进电机、阀门、PID 和报警,任何一处改动都会牵动全局;边界划得太细,一个启动按钮也做成 AOI,调用关系会变得比梯形图本身还复杂。本节围绕三条核心原则——高内聚低耦合、单一职责、接口最小化——并结合电机、阀门、PID 回路三类典型设备,说明如何把原则落到实际的粒度划分上。

三条原则的工程含义

高内聚指一个模块内部的所有逻辑都服务于同一个明确目标。例如“电机控制”模块内部应包含启动、停止、故障复位、运行反馈判断、过载处理,这些逻辑彼此紧密相关,改动时通常一起改。低耦合指模块之间尽量少知道对方的内部细节,只通过清晰的接口交互。电机模块不应直接读写阀门模块的内部标签,而应通过输入输出参数传递状态。

单一职责是判断粒度的第一把尺子:如果一个模块存在两个以上“因为不同原因而修改”的理由,就应考虑拆分。典型反例是把“PID 运算”和“PID 输出限幅与手自动切换”混在一个 AOI 中——整定参数变化和工艺联锁变化是两类完全不同的修改动因。

接口最小化要求只暴露必要参数。一个电机 AOI 若把内部定时器预设值、中间故障位全部开放为 InOut,调用者就会依赖这些内部实现,复用性反而下降。原则是:能通过 Input/Output 表达的,不用 InOut;能由模块内部计算的,不要求外部传入。

粒度划分的判断依据

实际划分时,可用四个问题快速判断:该对象是否会被多处重复调用?它的状态是否独立?它的修改动因是否单一?它的接口是否稳定?四问皆“是”,就适合做成独立模块。反之,若某段逻辑只在一个工位出现一次、且与周围逻辑强耦合,强行模块化只会增加间接层。

粒度还受设备物理边界影响。电机、阀门、PID 回路在 P&ID 和电气图上本身就是独立设备,把它们各自封装为一个 AOI,与工程师的思维习惯一致,也便于按设备类型批量复用。下面这张图给出三类典型设备的模块边界与接口关系,可作为划分时的参照。

典型设备模块边界与接口关系 输入参数 → 模块内部逻辑 → 输出状态,InOut 仅用于必须双向交互的对象 电机控制模块 Motor_AOI Input:Start / Stop / Reset / 过载反馈 内部:启保停 / 故障锁存 / 延时判断 Output:Run / Fault / Ready / 电流值 阀门控制模块 Valve_AOI Input:Open / Close / 开到位 / 关到位 内部:开闭互锁 / 动作超时 / 故障判定 Output:Opened / Closed / Fault / 状态字 PID 回路模块 PID_AOI Input:PV / SP / 手自动 / 手动输出 内部:PID 运算 / 限幅 / 无扰切换 Output:OUT / 偏差报警 / 饱和标志 调用层与数据流 主程序 MainRoutine 按设备实例调用各 AOI 设备实例数据 M1 / V1 / PID1 独立标签 HMI / 上位接口 只读 Output 与必要 InOut 粒度判断要点 • 电机、阀门、PID 各自状态独立、修改动因单一 → 适合独立 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 三类设备之所以适合独立成模块,是因为它们在物理上独立、状态上独立、修改动因上独立。若某个逻辑只服务于单一工位且与周围强耦合,保留在调用层反而更清晰。划分的目标是让复用自然发生,而不是为了模块化而模块化。