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来保存上一次的扫描状态。

AOI 参数类型与数据流边界 外部调用者 (主程序 / 其他AOI) Start_Cmd Speed_SP Fault_Reset Run_Status Fault_Active Motor_UDT AOI 内部逻辑 (Motor_Control_AOI) Input 只读入口 Start_Cmd Speed_SP Fault_Reset Output 内部可写 / 外部可读 Run_Status Fault_Active Ready InOut 传引用 / 直接读写 Motor_UDT Local Tag 私有 / 每实例独立 Edge_Flag 内部逻辑运算 梯形图 / ST / FBD 数据流边界 Input:外部→内部 只读,不修改 Output:内部→外部 内部赋值,外部读取 InOut:双向引用 直接操作外部标签 Local:内部私有 不跨边界,实例独立 边界原则: Input不写、Output不读 InOut慎用、Local自包含

可复用接口的设计原则

理解了四类参数的行为差异之后,设计可复用接口就有了明确的判断依据。第一条原则是“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。

在Studio 5000中定义AOI参数时,每个参数都需要指定数据类型(DataType)、用法(Usage)和描述(Description)。Usage下拉框中的选项正是Input、Output、InOut和Local Tag。对于InOut参数,数据类型通常选择UDT或数组;对于Local Tag,数据类型可以是原子类型、UDT或数组,但它的可见性仅限于AOI内部。

一个可参考的参数定义示例

下面给出一个电机控制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。

在Studio 5000中,AOI的Local Tag不会出现在调用者的标签数据库中,也不会占用控制器作用域的标签空间。但每个AOI实例都会独立分配一份Local Tag,因此如果Local Tag中定义了大型数组或结构体,会显著增加每个实例的内存占用。设计时应避免在Local Tag中放置不必要的大对象。