文章目录

ST数据排序技巧:在PLC内部用ST实现冒泡排序查找最大值

发布于 2026-03-19 22:27:27 · 浏览 4 次 · 评论 0 条

在PLC编程中,结构化文本(ST)语言因其接近高级语言的语法和强逻辑表达能力,常用于实现复杂算法。当需要在PLC运行时对一组实时采集的数据(如温度传感器阵列、电机电流采样值、压力变送器序列)进行排序或快速定位极值时,冒泡排序是最适合初学者掌握、最易验证、且无需额外内存分配的原地算法——尤其适用于资源受限的中小型PLC(如西门子S7-1200/1500、三菱Q系列、欧姆龙NJ/NX系列)。本指南不依赖HMI或上位机,所有操作均在PLC内部ST代码中完成,零外部工具,纯手写可运行


一、明确目标与约束条件

你只需达成一个确定结果:从一个固定长度的数值数组中,找出最大值,并可选地将数组按升序排列
适用场景举例:

  • 6台冷却风机出口温度(Temp[0]Temp[5]),需实时监控最高温度并触发报警;
  • 8路振动传感器采样值(Vib[0..7]),需每100ms计算一次当前最大振幅;
  • 批量称重系统中12个料仓重量(Weight[0..11]),需自动识别超载仓号。

关键约束(必须遵守,否则程序会崩溃或结果错误):

  • 数组必须是静态声明ARRAY,不能是POU输入参数中的动态数组(ST标准不支持动态长度);
  • 所有变量使用REALINT类型(推荐REAL,避免整数溢出);
  • 不调用任何系统函数块(如F_TRIG, MOVE等),全程用基础赋值、比较、循环实现;
  • 排序过程必须在单次扫描周期内完成(即不跨周期分步执行),确保结果实时性。

二、ST代码实现四步法(逐行可复制)

以下以西门子TIA Portal V18语法为基准(兼容IEC 61131-3标准,其他品牌仅需微调变量声明格式):

1. 声明数据结构(放在FB或OB的VAR区)

// 输入:原始数据源(例如来自模拟量模块的采样数组)
InputData : ARRAY[0..7] OF REAL := [23.5, 19.2, 27.8, 15.6, 31.4, 22.1, 28.9, 20.3];

// 输出:排序后数组(升序)、最大值、最大值索引(便于反查来源)
SortedData : ARRAY[0..7] OF REAL;
MaxValue : REAL;
MaxIndex : INT;

// 内部工作变量(必须声明,不可省略)
i, j : INT;           // 外层/内层循环计数器
temp : REAL;          // 交换临时变量
isSwapped : BOOL;     // 优化标志:若某轮无交换,提前退出

✅ 关键点:ARRAY[0..7] 明确指定下标范围,*禁止写成 `ARRAY[]ARRAY[8]`**(后者在部分PLC中表示8个元素但下标从1开始,极易越界)。

2. 初始化:将输入拷贝到工作数组

执行 FOR 循环将 InputData 复制到 SortedData

// 必须先复制,否则直接修改InputData会影响原始数据流
i := 0;
FOR i := 0 TO 7 DO
    SortedData[i] := InputData[i];
END_FOR;

⚠️ 注意:FOR 循环的结束值必须与数组上界严格一致(此处为7,不是8),因为ARRAY[0..7]共8个元素,最大下标是7。

3. 实施冒泡排序(带提前退出优化)

// 外层循环:控制排序轮数(最多n-1轮)
FOR i := 0 TO 6 DO  // 8个元素,最多比较7轮(0→6)
    isSwapped := FALSE;

    // 内层循环:每轮将未排序部分的最大值"冒泡"到末尾
    // 当前未排序区域为 [0 .. 7-i],故j上限为 7-i-1 = 6-i
    FOR j := 0 TO (6 - i) DO
        IF SortedData[j] > SortedData[j + 1] THEN
            // 执行交换
            temp := SortedData[j];
            SortedData[j] := SortedData[j + 1];
            SortedData[j + 1] := temp;
            isSwapped := TRUE;
        END_IF;
    END_FOR;

    // 优化:若本轮无交换,说明已有序,立即退出
    IF NOT isSwapped THEN
        EXIT;  // 跳出外层FOR循环
    END_IF;
END_FOR;

✅ 为什么内层循环上限是 6 - i
i轮后,末尾i+1个元素已就位(最大值沉底)。剩余待排序元素个数为 8 - (i + 1) = 7 - i,其下标范围是 [0 .. 6 - i],因此相邻比较的右边界是 j + 1 ≤ 6 - ij ≤ 5 - i?不对!
正确推导:待排序段长度 L = 8 - i,需比较 L - 1 = 7 - i 次,起始下标0,故j0(7 - i) - 1 = 6 - i
验证:i=0(第一轮),j=0 TO 6,共7次比较(0↔1,1↔2,...,6↔7)→ 正确。

4. 提取最大值与索引

冒泡排序完成后,最大值必然位于数组末尾(升序排列):

MaxValue := SortedData[7];  // 最大值 = 最后一个元素

// 反查原始索引(遍历InputData找首次匹配位置)
MaxIndex := -1;  // 初始化为无效值
i := 0;
FOR i := 0 TO 7 DO
    IF InputData[i] = MaxValue THEN
        MaxIndex := i;
        EXIT;  // 找到第一个即停,符合"首次出现"需求
    END_IF;
END_FOR;

🔍 补充:若需所有最大值位置(如多点超温),改用数组存储:
MaxIndices : ARRAY[0..7] OF INT; + 计数器 cnt : INT;,在循环中 IF ... THEN MaxIndices[cnt] := i; cnt := cnt + 1; END_IF;


三、性能实测与资源占用分析

在S7-1214C DC/DC/DC(CPU 1214C, 60KB工作内存)上,对8元素REAL数组执行完整冒泡排序:

  • 最坏情况(逆序):7轮 × 平均3.5次比较 ≈ 24次比较 + 24次交换 → 单次扫描耗时 ≤ 80 µs
  • 最好情况(已升序):1轮比较(7次)+ 0次交换 → 耗时 ≤ 15 µs
  • 内存占用:除原始数组外,仅增加5个变量(i,j,temp,isSwapped,MaxIndex),总增量 < 20 bytes
数组长度 最坏比较次数 最坏交换次数 典型扫描时间(S7-1200)
4 6 6 < 25 µs
8 28 28 < 80 µs
12 66 66 < 220 µs
16 120 120 < 450 µs

💡 结论:16元素以内完全满足10ms级高速循环需求(如运动控制周期)。超过16个建议改用选择排序(比较次数更少)或硬件FPGA加速。


四、常见错误及修复方案(调试必查清单)

错误现象 根本原因 修复动作
SortedData 全为0或乱码 未执行初始化拷贝步骤 确保第2步代码存在且未被注释
程序卡死/扫描超时 FOR 循环边界写成 TO 8(越界) 改为 TO 7(数组上界)
MaxValue 始终等于InputData[0] 提取语句写成 SortedData[0](最小值) 改为 SortedData[7](升序时最大值在末尾)
多次运行后结果不更新 InputData 是常量而非变量 InputData 改为 VAR_IN_OUTVAR_GLOBAL,连接实际信号
MaxIndex 返回-1 浮点数直接用 = 比较(精度误差) 改为 ABS(InputData[i] - MaxValue) < 0.001

✅ 浮点比较安全写法示例:

IF ABS(InputData[i] - MaxValue) < 0.01 THEN  // 允许0.01℃误差
    MaxIndex := i;
    EXIT;
END_IF;

五、扩展应用:不排序直接找最大值(更高效)

只需最大值,无需排序结果,可省去全部交换逻辑,节省70%以上时间:

MaxValue := InputData[0];  // 初始化为首个元素
MaxIndex := 0;
FOR i := 1 TO 7 DO  // 从第2个开始比较
    IF InputData[i] > MaxValue THEN
        MaxValue := InputData[i];
        MaxIndex := i;
    END_IF;
END_FOR;
  • 时间复杂度:O(n)(仅1轮遍历);
  • 扫描耗时:8元素 ≈ 12 µs(比完整冒泡快6倍);
  • 适用场景:报警阈值判断、主控选择(如"选温度最高的炉膛为主控")。

六、移植到其他PLC平台的关键适配点

品牌 差异点 适配方法
三菱GX Works3 不支持 EXIT 跳出FOR WHILE 循环替代,或设标志位 bExit := TRUEIF bExit THEN BREAK; END_IF;
欧姆龙Sysmac ARRAY 下标默认从1开始 声明为 ARRAY[1..8] OF REAL,循环改为 FOR i := 1 TO 8MaxValue := SortedData[8]
罗克韦尔Studio 5000 REAL 比较需用 GRT 指令 改用梯形图混编,或ST中写 IF InputData[i] > MaxValue THEN ...(Logix支持ST直接比较)

📌 统一原则:先在仿真环境(如PLCSIM Advanced)中用真实数据测试逻辑,再烧录硬件


将上述ST代码粘贴至PLC组织块(如OB1)或功能块(FB)中,关联实际IO地址,下载运行即可实时输出最大值与位置。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文