如果你在 PLC 编程里还在用一堆零散的变量,每次改个配方或者处理一批数据都要手动写几十行赋值和判断,那 ST 语言里的数组功能,就是你该立刻用起来的工具。它不是什么高深语法,但用好了,能让你的程序结构清晰、维护方便,处理批量数据时效率提升非常明显。不管是处理传感器采集的一批数据、管理几十个工位的配方参数,还是做简单的数据排序查找,数组都能帮你省掉大量重复代码。
这篇文章不讲复杂的理论,就围绕 CODESYS 环境下,ST 语言里数组怎么声明、怎么用、以及实际编程中那些能让代码更利索的技巧。我会从最基本的单维数组讲到多维数组,再结合搜索热词里大家关心的具体场景,比如配方管理、数据采集处理,给出可以直接参考的实例和避坑建议。
1. 先搞清楚数组在 PLC 的 ST 语言里到底解决什么问题
很多人觉得数组就是 C 语言里那种东西,在 PLC 里用不上,或者觉得麻烦。这是一个很大的误解。在工业控制场景里,数组的用武之地非常多,核心是解决“批量”和“有序”数据的处理问题。
1.1 不用数组的典型痛点场景想象一下这些场景:
- 配方管理:一条生产线有20个工位,每个工位有5个参数(温度、压力、速度等)。如果不使用数组,你可能需要声明100个单独的变量:
Station1_Temp,Station1_Pressure, ...Station20_Speed。修改、传递、保存这100个变量将是噩梦。 - 数据采集:从一台设备通过 Modbus TCP 或 Modbus RTU(搜索热词中提到)连续读取50个寄存器值。如果没有数组,你需要写50个独立的读指令和变量。
- 批处理:对100个检测值进行滤波、寻找最大值/最小值、或计算平均值。用单个变量写循环?几乎不可能。只能写冗长重复的逻辑。
这些场景下,代码会变得极其臃肿,难以阅读,更容易出错。添加一个工位或一个参数,都需要大量修改代码。
1.2 数组带来的直接改变使用数组后,上述场景会变得简洁:
- 配方管理:定义一个二维数组
Recipe : ARRAY[1..20, 1..5] OF REAL;, 一个变量就容纳了所有数据。通过Recipe[i, j]就能访问第 i 个工位的第 j 个参数。 - 数据采集:定义一个数组
HoldingRegs : ARRAY[1..50] OF WORD;, 用一个循环就能完成所有寄存器的读取和映射。 - 批处理:将采集值存入数组
Samples : ARRAY[1..100] OF REAL;, 然后用一个FOR循环就能轻松实现求和、找最值等操作。
所以,数组在 PLC ST 编程中的核心价值是:将同类数据组织起来,通过索引(下标)进行统一、高效的操作,极大提升代码的模块化程度和可维护性。它特别适合处理具有相同数据类型、且数量在编程时已知或可预估的批量数据。
2. 从声明到使用:ST语言数组的基础操作拆解
在 CODESYS 的 ST 环境里使用数组,第一步不是写复杂逻辑,而是正确地声明和初始化。
2.1 数组的声明语法ST语言中数组声明的通用格式如下:
VAR // 一维数组声明 数组名 : ARRAY[下标下限..下标上限] OF 数据类型; // 多维数组声明 数组名 : ARRAY[第一维下限..第一维上限, 第二维下限..第二维上限] OF 数据类型; END_VAR关键参数解释:
- 下标下限/上限:定义了数组的索引范围。CODESYS 支持任意整数范围,如
[0..99],[1..50], 甚至[-10..10]。但通常从1或0开始,符合习惯。 - 数据类型:可以是任何基本类型(
BOOL,INT,REAL,WORD等)或派生类型(结构体、枚举等)。数组内所有元素必须是同一类型。
实例声明:
VAR // 声明一个包含10个布尔量的数组,用于表示10个电机的运行状态 MotorRunning : ARRAY[1..10] OF BOOL; // 声明一个包含100个整数的数组,用于存储传感器原始值 SensorRawData : ARRAY[0..99] OF INT; // 声明一个5行3列的实数数组,用于存储一个3D坐标系的5个点坐标 (X, Y, Z) Coordinates : ARRAY[1..5, 1..3] OF REAL; // 声明一个字符串数组(CODESYS中字符串是特殊类型) AlarmMessages : ARRAY[1..20] OF STRING(80); // 每个字符串最大80字符 END_VAR2.2 数组的初始化你可以在声明时直接给数组赋初值。
VAR // 初始化一维数组 DefaultSpeeds : ARRAY[1..5] OF REAL := [10.5, 20.0, 15.0, 18.5, 12.0]; // 初始化二维数组 IdentityMatrix : ARRAY[1..3, 1..3] OF REAL := [ [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0] ]; END_VAR注意:初始化的值必须与声明类型匹配,且数量与数组大小一致。
2.3 数组元素的访问与赋值访问数组元素使用下标(索引)。
// 赋值 MotorRunning[3] := TRUE; // 将第3个电机的运行状态设为TRUE SensorRawData[0] := 2047; // 给第0个传感器数据赋值 Coordinates[2, 1] := 100.5; // 将第2个点的X坐标设为100.5 // 读取并使用 IF MotorRunning[5] THEN // 如果第5个电机在运行 END_IF CurrentSpeed := DefaultSpeeds[2]; // 读取第2个默认速度值重要提醒:务必确保索引在声明的范围内,访问越界(例如MotorRunning[11])在 CODESYS 运行时通常会导致程序错误甚至 PLC 停机。这是使用数组最常见的错误之一。
3. 结合循环与数组:实现高效批量数据处理
数组单独使用的威力有限,一旦与FOR、WHILE循环结合,才能发挥最大效能。这是处理“批量”操作的核心模式。
3.1 基础循环遍历这是最常用的模式,用于对数组所有元素执行相同操作。
VAR i : INT; Total : REAL := 0; Average : REAL; Temperatures : ARRAY[1..50] OF REAL; END_VAR // 示例1:计算50个温度值的总和与平均值 FOR i := 1 TO 50 BY 1 DO Total := Total + Temperatures[i]; END_FOR Average := Total / 50; // 示例2:将数组所有元素清零 FOR i := 1 TO 50 BY 1 DO Temperatures[i] := 0.0; END_FOR // 示例3:批量复位一组输出点(假设已映射到数组) FOR i := 1 TO 16 BY 1 DO OutputCoils[i] := FALSE; END_FOR3.2 在数据采集与通信中的应用搜索热词中提到了ModbusRTU、数据采集,数组在这里是必需品。
VAR mbRegIndex : INT; HoldingRegisterBuffer : ARRAY[1..30] OF WORD; // 缓冲数组 ProcessValues : ARRAY[1..30] OF REAL; // 处理后的工程值数组 ScaleFactor : REAL := 0.1; END_VAR // 假设通过一个功能块一次性读取了30个保持寄存器到 HoldingRegisterBuffer // 然后将其转换为工程值 FOR mbRegIndex := 1 TO 30 BY 1 DO ProcessValues[mbRegIndex] := WORD_TO_REAL(HoldingRegisterBuffer[mbRegIndex]) * ScaleFactor; END_FOR // 现在 ProcessValues 数组中就是可用的30个实数过程值这种方式代码清晰,添加或减少采集点只需修改数组大小和循环上限。
3.3 实现简单算法:查找与排序虽然 PLC 不常做复杂排序,但找最大值、最小值或简单滤波很常见。
VAR j : INT; MaxValue : REAL := -9999.0; MaxIndex : INT := 0; DataSet : ARRAY[1..100] OF REAL; END_VAR // 查找最大值及其位置 FOR j := 1 TO 100 BY 1 DO IF DataSet[j] > MaxValue THEN MaxValue := DataSet[j]; MaxIndex := j; END_IF END_FOR // 循环结束后,MaxValue是最大值,MaxIndex是其在数组中的位置对于排序,在 PLC 中若有必要,可以实现简单的冒泡排序,但要注意循环次数对扫描周期的影响。
4. 进阶技巧与实战避坑指南
掌握了基础,再看一些能让代码更健壮、更优雅的实践和常见问题。
4.1 使用常量定义数组边界这是一个至关重要的好习惯。不要将数组大小(如1..50)直接硬编码在代码各处。
VAR_CONSTANT MAX_STATIONS : INT := 20; PARAMS_PER_STATION : INT := 5; END_VAR VAR Recipe : ARRAY[1..MAX_STATIONS, 1..PARAMS_PER_STATION] OF REAL; i : INT; END_VAR // 循环时也使用常量 FOR i := 1 TO MAX_STATIONS BY 1 DO // 处理每个工站 END_FOR好处:当需要修改工站数量或参数数量时,只需修改VAR_CONSTANT中的常量值,所有相关代码(声明、循环)会自动生效,避免遗漏和错误。
4.2 数组作为函数/功能块的参数你可以将整个数组传递给函数或功能块,这是实现模块化编程的关键。
// 定义一个功能块,计算整型数组的平均值 FUNCTION_BLOCK ArrayAverage VAR_INPUT InputArray : ARRAY[*] OF INT; // 使用“*”表示可变大小的数组参数 ArraySize : INT; END_VAR VAR_OUTPUT AverageValue : REAL; END_VAR VAR Sum : LINT := 0; // 使用长整型防止求和溢出 k : INT; END_VAR Sum := 0; FOR k := 1 TO ArraySize BY 1 DO Sum := Sum + InputArray[k]; END_FOR AverageValue := LINT_TO_REAL(Sum) / INT_TO_REAL(ArraySize);调用时:
MyAverageBlock(InputArray := MyDataArray, ArraySize := 100, AverageValue => result);使用可变数组参数(ARRAY[*])增加了功能块的通用性。
4.3 数组与结构体结合使用这是管理复杂数据的终极利器。例如,每个工站不是一个简单的实数,而是一个包含多个参数的结构体。
TYPE StationData : STRUCT Temperature : REAL; Pressure : REAL; Speed : INT; IsRunning : BOOL; AlarmCode : WORD; END_STRUCT END_TYPE VAR // 声明一个由20个StationData结构体组成的数组 ProductionLine : ARRAY[1..20] OF StationData; END_VAR // 访问方式非常直观 ProductionLine[3].Temperature := 150.0; IF ProductionLine[5].IsRunning THEN // ... END_IF这种方式逻辑清晰,数据高度组织化,远超使用多个平行数组(如Temp[1..20],Pressure[1..20])。
4.4 常见“坑”与排查点
- 索引越界:这是最严重的运行时错误。务必在循环前确保索引变量被正确初始化,并且循环边界与数组声明一致。使用常量定义边界是预防此问题的最佳实践。
- 循环性能:大型数组(如10000个元素)的紧密循环可能增加扫描周期时间。如果对实时性要求高,考虑分多次扫描处理,或评估是否真的需要如此大的数组。
- 初始化问题:局部变量数组不会自动初始化,其值可能是随机的。确保在首次使用前对其初始化(在声明时初始化或程序开始时用循环清零)。
- 与外部数据交互:当数组用于映射 Modbus 寄存器或 PLC I/O 时(如搜索热词中
PLC数据采集),要特别注意数据类型的匹配(WORD,INT,DINT,REAL的转换)和字节序问题。 - 多维数组的内存布局:理解多维数组在内存中是按行优先存储的,但这在大多数PLC应用层编程中感知不强,主要在涉及底层内存操作或与高级语言交互时需要注意。
4.5 针对搜索热词中具体问题的联想解答
- “汇川ST语言编程手册下载”/“CODESYS编程ST语言实例”:数组的语法在遵循 IEC 61131-3 标准的平台(如 CODESYS、汇川、倍福)上是基本一致的。掌握本文基础后,查阅这些手册的重点应是看平台对数组高级功能(如动态数组、数组函数
LOWER_BOUND,UPPER_BOUND)的支持情况。 - “PLC控制箱一拖二一用一备”:这种逻辑非常适合用数组实现。可以定义一个
Pump : ARRAY[1..2] OF Pump_Struct结构体数组,每个泵结构体包含运行状态、故障状态、累计运行时间等。控制逻辑通过循环遍历数组,根据“一用一备”策略设置数组内各泵的StartCommand等控制位,代码会非常规整。 - “数组去重”、“最大子数组和”:这些是典型的算法问题。在 PLC 中实现“去重”可能需要双重循环比较,复杂度 O(n²),对于大量数据需谨慎。“最大子数组和”这类动态规划问题在 PLC 中不常见,但如果需要,同样可以用数组存储中间结果,通过循环求解。核心是意识到数组是承载这些算法数据的基础。
- “C语言字符数组操作函数”:ST语言字符串处理函数不如C语言丰富,但 CODESYS 提供了基本的
CONCAT,LEFT,RIGHT,MID,FIND等函数。可以将字符串看作特殊的字符数组,但通常直接使用STRING类型及相关函数更安全方便,除非有极特殊的字符级操作。
数组是 ST 语言从“写简单逻辑”到“构建可维护的中大型程序”必须跨越的一道坎。它带来的最大好处不是性能,而是代码的清晰度和可扩展性。开始一个新项目时,如果遇到批量数据,先别急着定义几十个单独变量,停下来想想:“能不能用数组?” 这个习惯,会让你的编程效率和质量上一个台阶。
在实际项目中,我建议先从一维数组配合FOR循环开始实践,用它来管理同类型的设备状态或采集数据。等熟悉了,再逐步引入常量边界、结构体数组等高级用法。你会发现,之前许多繁琐的代码块,可以被简洁的循环和清晰的数组索引所替代。