3. AOI功能块的数据类型设计
在Studio 5000中创建一个Add-On Instruction(AOI),本质上是在设计一个“可被反复调用的逻辑封装体”。而决定这个封装体是否真正可复用、是否容易被误用、是否会在多实例调用时产生数据串扰的关键,并不在于内部梯形图或结构化文本写得多巧妙,而在于参数类型的选择与数据流边界的划分。AOI的参数定义界面提供了四类参数:Input、Output、InOut和Local Tag。它们不是简单的“输入输出”标签,而是决定了数据在AOI内外之间如何传递、谁拥有数据、谁可以修改数据、以及数据在多个实例之间是否共享。
四类参数的本质差异
Input参数是AOI的只读入口。外部调用者把值或标签引用传入AOI,AOI内部逻辑可以读取它,但不应在内部对它赋值。在Studio 5000中,Input参数默认按值传递(对于原子数据类型)或按引用传递(对于数组、结构体等复杂类型,实际表现为传入标签引用)。它的典型用途是接收命令位、设定值、模式选择、外部状态等。例如一个电机控制AOI,Input可以包括Start_Cmd、Stop_Cmd、Speed_SP、Fault_Reset。
Output参数是AOI的输出结果。AOI内部逻辑对它赋值,外部调用者读取它。Output参数在AOI内部是“可写”的,在外部是“可读”的。它适合承载状态字、报警位、计算结果、完成标志等。继续电机控制的例子,Output可以包括Run_Status、Fault_Active、Ready、Actual_Speed。
InOut参数是最容易被误用、也最需要谨慎设计的一类。它表示“传入一个标签引用,AOI内部可以直接读写这个标签本身”。也就是说,InOut不是传值,而是传地址。AOI内部对InOut参数的修改会直接作用到外部那个标签上。它适合用于需要被AOI直接操作的复杂对象,例如一个结构体Motor_UDT,或者一个需要被AOI内部逻辑直接置位/复位的定时器、计数器实例。InOut的典型场景是:AOI需要操作一个外部已经存在的对象,而不是产生一个副本。
Local Tag参数是AOI内部的私有变量。它不暴露给外部调用者,外部看不到也改不了。Local Tag用于保存AOI内部的中间状态、边沿检测标志、内部定时器、内部计数器、临时计算值等。它的关键价值在于“每个AOI实例拥有自己独立的一份Local Tag”,因此不会在多个实例之间产生数据串扰。例如一个AOI内部需要做上升沿检测,就必须用一个Local Tag来保存上一次的扫描状态。
可复用接口的设计原则
理解了四类参数的行为差异之后,设计可复用接口就有了明确的判断依据。第一条原则是“Input只读、Output只写、InOut慎用、Local自包含”。Input参数在AOI内部不应被赋值,否则调用者传入的值会被意外覆盖,导致逻辑不可预测。Output参数在AOI内部不应被读取作为判断条件,因为它的值可能尚未被本次扫描更新,读取它容易产生时序错误。InOut参数虽然灵活,但滥用会让AOI与外部标签产生强耦合,降低可复用性。Local Tag则应该承担所有内部中间状态,确保每个实例独立运行。
第二条原则是“接口最小化”。一个AOI暴露给外部的参数越多,调用者需要理解的信息就越多,误用的概率也越大。设计时应只暴露真正必要的Input和Output,把可以内部计算得到的值放进Local Tag。例如,如果Ready状态可以由Run_Status和Fault_Active推导出来,就不必单独暴露一个Output,而是在AOI内部用Local Tag计算后,再决定是否输出。
第三条原则是“复杂对象用InOut,简单值用Input/Output”。当一个AOI需要操作一个结构体、一个数组、一个定时器实例或一个PID实例时,应该使用InOut参数传入该对象的标签引用,而不是把结构体的每个成员拆成单独的Input/Output。这样既减少了参数数量,又保证了AOI内部对对象的修改能够直接反映到外部。例如,电机控制AOI可以接收一个Motor_UDT类型的InOut参数,内部直接读写Motor_UDT.Status、Motor_UDT.Fault、Motor_UDT.Speed等成员。
数据流边界的划分方法
数据流边界划分的核心问题是:哪些数据应该跨越AOI边界,哪些数据应该留在AOI内部。一个实用的判断方法是问三个问题:第一,这个数据是否由外部决定?如果是,它应该是Input。第二,这个数据是否需要被外部使用?如果是,它应该是Output。第三,这个数据是否需要在多个扫描周期之间保持,并且只属于当前AOI实例?如果是,它应该是Local Tag。
以电机控制AOI为例,可以这样划分边界。外部决定的数据:启动命令、停止命令、速度设定值、故障复位命令,这些是Input。外部需要使用的数据:运行状态、故障状态、就绪状态、实际速度,这些是Output。需要跨周期保持且只属于当前实例的数据:上升沿检测标志、内部启动延时定时器、内部故障锁存位,这些是Local Tag。需要直接操作的外部对象:电机结构体Motor_UDT,这是InOut。
一个可参考的参数定义示例
下面给出一个电机控制AOI的参数定义表,用于说明四类参数在实际工程中的分配方式。该示例中的参数名称、数据类型和用途均为教学示例,实际项目应根据设备手册和控制需求调整。
| 参数名 | Usage | 数据类型 | 说明 |
|---|---|---|---|
| Start_Cmd | Input | BOOL | 启动命令,外部只读传入 |
| Stop_Cmd | Input | BOOL | 停止命令,外部只读传入 |
| Speed_SP | Input | REAL | 速度设定值,单位rpm |
| Fault_Reset | Input | BOOL | 故障复位命令 |
| Run_Status | Output | BOOL | 运行状态输出 |
| Fault_Active | Output | BOOL | 故障激活输出 |
| Ready | Output | BOOL | 就绪状态输出 |
| Actual_Speed | Output | REAL | 实际速度反馈 |
| Motor_UDT | InOut | Motor_UDT | 电机结构体,内部直接读写 |
| Edge_Flag | Local Tag | BOOL | 上升沿检测标志,内部私有 |
| Start_Delay | Local Tag | TIMER | 启动延时定时器,内部私有 |
| Fault_Latch | Local Tag | BOOL | 故障锁存位,内部私有 |
常见错误与规避
在实际工程中,参数类型误用是AOI复用失败的主要原因之一。最常见的错误包括:把需要跨周期保持的状态放在Input参数中,导致每次调用时状态被外部覆盖;把应该用Local Tag的中间变量暴露成Output,导致多个实例之间互相干扰;把简单值用InOut传递,造成不必要的耦合;以及在AOI内部对Input参数赋值,导致调用者的数据被意外修改。
规避这些错误的方法是在AOI设计阶段就明确每个参数的“数据流方向”和“生命周期”。数据流方向决定了它是Input、Output还是InOut;生命周期决定了它是Local Tag还是需要暴露到外部。对于需要在多个扫描周期之间保持、且只属于当前实例的数据,一律使用Local Tag。对于需要被外部读取的结果,使用Output。对于需要被AOI直接操作的外部对象,使用InOut。对于由外部决定的值,使用Input。