一、Variant 循环队列概述
循环队列是一种基于数组实现的先进先出(FIFO)数据结构,通过首尾指针的循环移动解决数组空间的浪费问题。而Variant 循环队列(又称 “变体循环队列”)则是对传统循环队列的扩展,其核心特性是支持存储不同数据类型的元素(如 Int、Real 等),实现异构数据的有序存储与访问。
核心特点
类型灵活性:无需预先限定存储元素的类型,可动态处理 Int、Real 等多种数据类型;
空间高效性:基于循环队列结构,避免数组 “假溢出” 问题,充分利用存储空间;
类型适配开销:需通过类型检测与转换机制实现多类型支持,可能引入额外的处理开销;
操作原子性:通过脉冲触发(上升沿检测)确保入队、出队等操作的单次执行。
二、实现原理与核心机制
1. 底层数据结构
Variant 循环队列的底层依托数组实现,通过两个指针(Front、Rear)标记队列的头部与尾部:
Front:指向队头元素(出队时读取的位置);
Rear:指向队尾的下一个空闲位置(入队时写入的位置);
队列满判断:(Rear + 1) MOD 数组长度 = Front;
队空判断:Rear = Front。
2. 多类型支持机制
通过 Variant 类型(变体类型)实现多类型兼容,核心依赖两个操作:
VariantGet:从 Variant 变量中提取具体类型数据(如从 Variant 数组中提取 Int 数组);
VariantPut:将具体类型数据写入 Variant 变量(如将修改后的 Int 数组写回 Variant 变量)。
3. 脉冲触发机制
为避免操作信号持续有效导致的重复执行,通过上升沿检测(_P指令)实现脉冲触发,确保每个操作(入队、出队等)仅在信号从 0 变为 1 时执行一次。
三、详细实现步骤
核心逻辑代码(带注释)
以下代码基于结构化文本(ST)编写,实现了 Int 和 Real 类型的循环队列操作:
// 功能块:Variant循环队列实现(支持Int和Real类型)FUNCTION_BLOCK FB_VariantCircularQueueVAR_INPUTvar_arr: Variant; // 存储队列数据的Variant数组(Int或Real)var_data: Variant; // 入队/出队的数据(Variant类型)Join: BOOL; // 入队触发信号(上升沿有效)Leave: BOOL; // 出队触发信号(上升沿有效)Reset: BOOL; // 错误复位信号(上升沿有效)Clear: BOOL; // 队列清零信号(上升沿有效)END_VARVAR_OUTPUTError: BOOL := 0; // 错误标志(1=出错,0=正常)ErrorInfo: WORD := 0; // 错误代码(16#0001=队满,16#0002=队空)END_VARVAR// 脉冲检测器(上升沿检测),确保操作仅执行一次Join_P: R_TRIG; // 入队信号上升沿检测Leave_P: R_TRIG; // 出队信号上升沿检测Reset_P: R_TRIG; // 复位信号上升沿检测Clear_P: R_TRIG; // 清零信号上升沿检测Front: INT := 0; // 队头指针(指向队头元素)Rear: INT := 0; // 队尾指针(指向队尾下一个空闲位置)size: INT; // 数组长度// Int类型临时变量(用于类型转换)arr_int: ARRAY[*] OF INT; // 从var_arr提取的Int数组data_int: INT; // 临时存储Int类型数据// Real类型临时变量(用于类型转换)arr_real: ARRAY[*] OF REAL; // 从var_arr提取的Real数组data_real: REAL; // 临时存储Real类型数据i: INT; // 循环计数器END_VAR// 主逻辑:仅处理数组类型的Variant变量IF IS_ARRAY(var_arr) THEN // 检查输入是否为数组类型// 区域1:脉冲触发处理(上升沿检测)REGION 脉冲触发处理// 对各操作信号进行上升沿检测,Q为1表示检测到上升沿Join_P(CLK := Join); // 入队信号上升沿检测Join := 0; // 重置输入信号(避免持续触发)Leave_P(CLK := Leave); // 出队信号上升沿检测Leave := 0; // 重置输入信号Reset_P(CLK := Reset); // 复位信号上升沿检测Reset := 0; // 重置输入信号Clear_P(CLK := Clear); // 清零信号上升沿检测Clear := 0; // 重置输入信号END_REGION// 区域2:Int类型循环队列处理REGION Int类型循环队列处理IF TypeOfElements(var_arr) = INT THEN // 判断数组元素类型为Int// 1. 获取数组大小(转换为INT类型)size := UDINT_TO_INT(CountOfElements(var_arr));// 2. 从Variant数组中提取Int数组到临时变量arr_intVariantGet(SRC := var_arr, DST => arr_int);// 3. 从Variant数据中提取Int数据到临时变量data_intVariantGet(SRC := var_data, DST => data_int);// 子区域:入队操作(仅在检测到上升沿时执行)REGION 入队操作IF Join_P.Q THEN // 检测到入队信号上升沿// 队满判断:(Rear + 1) MOD 数组长度 = FrontIF (Rear + 1) MOD size = Front THENError := 1; // 置位错误标志ErrorInfo := 16#0001; // 错误代码:队满RETURN; // 终止本次执行END_IF;// 执行入队:将数据写入Rear指向的位置arr_int[Rear] := data_int;// 更新队尾指针:循环移动(取模确保不越界)Rear := (Rear + 1) MOD size;END_IF;END_REGION// 子区域:出队操作(仅在检测到上升沿时执行)REGION 出队操作IF Leave_P.Q THEN // 检测到出队信号上升沿// 队空判断:Rear = FrontIF Rear = Front THENError := 1; // 置位错误标志ErrorInfo := 16#0002; // 错误代码:队空RETURN; // 终止本次执行END_IF;// 执行出队:从Front指向的位置读取数据data_int := arr_int[Front];arr_int[Front] := 0; // 清空出队位置(可选)// 更新队头指针:循环移动Front := (Front + 1) MOD size;// 将出队数据写回Variant变量var_dataVariantPut(SRC := data_int, DST := var_data);END_IF;END_REGION// 子区域:复位操作(清除错误状态)REGION 复位操作IF Reset_P.Q THEN // 检测到复位信号上升沿Error := 0; // 清除错误标志ErrorInfo := 0; // 清除错误代码END_IF;END_REGION// 子区域:清零操作(清空队列并重置指针)REGION 清零操作IF Clear_P.Q THEN // 检测到清零信号上升沿// 遍历数组,清空所有元素FOR i := 0 TO size - 1 DOarr_int[i] := 0;END_FOR;// 重置队头、队尾指针(恢复队空状态)Front := 0;Rear := 0;END_IF;END_REGION// 将修改后的Int数组写回Variant变量var_arrVariantPut(SRC := arr_int, DST => var_arr);END_IF;END_REGION// 区域3:Real类型循环队列处理(逻辑与Int类型一致,仅类型不同)REGION Real类型循环队列处理IF TypeOfElements(var_arr) = REAL THEN // 判断数组元素类型为Real// 1. 获取数组大小size := UDINT_TO_INT(CountOfElements(var_arr));// 2. 从Variant数组中提取Real数组到临时变量arr_realVariantGet(SRC := var_arr, DST => arr_real);// 3. 从Variant数据中提取Real数据到临时变量data_realVariantGet(SRC := var_data, DST => data_real);// 子区域:入队操作REGION 入队操作IF Join_P.Q THEN // 检测到入队信号上升沿// 队满判断IF (Rear + 1) MOD size = Front THENError := 1;ErrorInfo := 16#0001; // 队满RETURN;END_IF;// 执行入队arr_real[Rear] := data_real;Rear := (Rear + 1) MOD size;END_IF;END_REGION// 子区域:出队操作REGION 出队操作IF Leave_P.Q THEN // 检测到出队信号上升沿// 队空判断IF Rear = Front THENError := 1;ErrorInfo := 16#0002; // 队空RETURN;END_IF;// 执行出队data_real := arr_real[Front];arr_real[Front] := 0.0; // 清空出队位置(可选)Front := (Front + 1) MOD size;// 将出队数据写回Variant变量VariantPut(SRC := data_real, DST := var_data);END_IF;END_REGION// 子区域:复位操作REGION 复位操作IF Reset_P.Q THENError := 0;ErrorInfo := 0;END_IF;END_REGION// 子区域:清零操作REGION 清零操作IF Clear_P.Q THEN// 遍历数组,清空所有元素FOR i := 0 TO size - 1 DOarr_real[i] := 0.0;END_FOR;// 重置指针Front := 0;Rear := 0;END_IF;END_REGION// 将修改后的Real数组写回Variant变量VariantPut(SRC := arr_real, DST => var_arr);END_IF;END_REGIONELSE// 输入不是数组类型,直接返回(不执行任何操作)RETURN;END_IF;
2. 数据块(DB)创建
数据块用于存储队列的实例数据(如数组、指针状态等),需根据实际需求定义:
若处理 Int 类型队列,创建包含var_arr(Int 数组)和var_data(Int 变量)的 Variant 类型数据块;
若处理 Real 类型队列,创建包含var_arr(Real 数组)和var_data(Real 变量)的 Variant 类型数据块。
3. 组织块(OB)调用
在组织块(如 OB1,主循环组织块)中调用功能块FB_VariantCircularQueue,并关联数据块:
// OB1:主循环组织块VAR// 实例化功能块queue: FB_VariantCircularQueue;// 数据块(以Int类型为例)db_queue: DB_IntQueue; // 包含var_arr(ARRAY[0..7] OF INT)和var_data(INT)// 操作信号(可通过HMI或其他逻辑触发)enqueue: BOOL; // 入队触发dequeue: BOOL; // 出队触发reset_err: BOOL;// 错误复位clear_queue: BOOL;// 队列清零END_VAR// 调用功能块,关联输入输出queue(var_arr := db_queue.var_arr,var_data := db_queue.var_data,Join := enqueue,Leave := dequeue,Reset := reset_err,Clear := clear_queue,Error => ,ErrorInfo =>);
四、仿真调试要点
类型一致性检查:确保var_arr的元素类型与var_data的类型一致(如均为 Int 或均为 Real),否则会导致类型转换错误;
边界条件测试:队空时执行出队操作,验证是否触发Error=1且ErrorInfo=16#0002;队满时执行入队操作,验证是否触发Error=1且ErrorInfo=16#0001;
指针循环验证:当指针到达数组末尾时,检查是否正确回绕到起始位置(如Rear从 7(数组长度 8)变为 0);脉冲信号测试:确保单次触发信号(上升沿)仅执行一次操作,避免连续触发导致的数据错误。
五、总结
Variant 循环队列通过 Variant 类型的灵活性与循环队列的空间高效性,实现了异构数据的有序管理。其核心在于通过VariantGet/VariantPut实现类型转换,结合脉冲触发确保操作原子性。该实现适用于需要动态处理多类型数据的场景(如工业控制中的混合信号采集),但需注意类型检查与转换的开销,在高性能场景下可针对特定类型优化实现。
2222
