ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CODESYS ST语言数组操作指南:从基础声明到实战应用

CODESYS ST语言数组操作指南:从基础声明到实战应用 在工业自动化项目中处理批量数据是家常便饭比如管理一组传感器的实时读数、控制一排电机的速度、或者记录一条生产线的历史状态。如果每个变量都单独声明代码会变得冗长且难以维护。最近在为一个包装线项目编写逻辑时我再次深刻体会到在 CODESYS 环境下将 ST结构化文本语言与数组结合使用能极大地提升编程效率和代码可读性。无论是处理一维的流水线工位状态还是管理二维的配方参数表数组都能提供清晰、高效的数据组织方式。本文将系统性地讲解如何在 CODESYS 中使用 ST 语言操作数组从基础声明、初始化到高级应用如遍历、排序及与函数块FB结合并附带可直接复用的实战代码示例帮助您从入门到精通。1. 背景与核心概念在深入代码之前我们有必要厘清几个核心概念这有助于理解为什么“数组ST”是 CODESYS PLC 编程中的利器。CODESYS是一个独立的、硬件厂商中立的 IEC 61131-3 编程系统开发平台。它不是一个特定的 PLC 品牌而是一个软件框架。像倍福Beckhoff、施耐德Schneider等许多厂商的 PLC 都支持使用 CODESYS 作为编程环境。这意味着在这里学到的数组和 ST 语言知识可以迁移到众多品牌的控制器上。STStructured Text结构化文本是 IEC 61131-3 标准定义的五种编程语言之一其语法高度类似于 Pascal 或 C 语言。它非常适合处理复杂的数学运算、算法逻辑以及像数组、结构体这样的数据结构。相比于梯形图LD或功能块图FBDST 在处理批量数据和循环逻辑时更加简洁和强大。数组是一种数据结构用于存储相同数据类型的多个元素并通过一个索引下标来访问每个元素。想象一下一排整齐的邮箱每个邮箱都有唯一的编号索引里面存放着同一种类型的信件数据。在 PLC 控制中这排“邮箱”可以是一组温度值、一批产品代码或一系列电机控制字。为什么结合使用非常方便代码精简无需为 100 个传感器声明 100 个变量只需一个数组。逻辑清晰使用循环可以统一处理所有数组元素使算法逻辑一目了然。易于维护修改数组大小或处理逻辑时只需改动少数几处代码。高效数据交换数组可以方便地与 HMI、SCADA 系统进行批量数据交换。2. 环境准备与版本说明本文的示例基于CODESYS Development System V3.5 SP11版本创建。请注意不同的小版本如 SP10, SP12在界面和部分高级功能上可能略有差异但核心的 ST 语言和数组语法是完全遵循 IEC 61131-3 标准的具有高度的通用性。关键环境配置编程系统CODESYS Development System (任何 V3.x 版本均可推荐 SP9 以上)。PLC 型号示例以“CODESYS Control Win V3 x64”作为仿真目标这意味着你可以在没有实体 PLC 的情况下运行和测试所有代码。项目类型标准工程Standard project。编程语言主要使用 ST结构化文本。创建测试项目打开 CODESYS新建一个项目。选择“标准工程”命名如ArrayDemo。在“设备”树中右键点击“设备”选择“添加设备”在弹出的对话框中在“CODESYS”目录下选择“CODESYS Control Win V3”或“CODESYS Control for Raspberry Pi”等仿真设备点击“添加设备”。在“应用”树中会自动创建一个名为“PLC_PRG”的程序。本文的示例代码将主要在这里编写和测试。3. 数组的核心语法与操作3.1 数组的声明与初始化在 ST 语言中数组的声明需要指定数据类型、数组维度和大小。基本声明语法变量名 : ARRAY [下界..上界] OF 数据类型;下界和上界定义了数组的索引范围。在 IEC 61131-3 中下标可以是任意整数包括负数但通常从 0 或 1 开始。OF 数据类型指定数组所有元素的数据类型如INT,REAL,BOOL,STRING或自定义结构体。示例1声明不同维度的数组PROGRAM PLC_PRG VAR // 一维数组10个整数索引从0到9 aiSensorValues : ARRAY[0..9] OF INT; // 一维数组5个布尔量索引从1到5更符合人类习惯 abMotorStatus : ARRAY[1..5] OF BOOL; // 二维数组一个3行4列的实数矩阵常用于存储配方参数 arRecipeParams : ARRAY[1..3, 1..4] OF REAL; // 字符串数组存储7个设备名称 asDeviceNames : ARRAY[0..6] OF STRING(20); // STRING(20)表示最大20个字符 // 常量数组在声明时直接初始化 cnDaysInMonth : ARRAY[1..12] OF INT : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; END_VAR初始化数组可以在声明时初始化也可以在程序运行时赋值。VAR // 声明时初始化一维数组 aiInitArray : ARRAY[0..2] OF INT : [10, 20, 30]; // 声明时初始化二维数组按行展开 aiInitMatrix : ARRAY[1..2, 1..3] OF INT : [1,2,3, 4,5,6]; // 第一行[1,2,3]第二行[4,5,6] END_VAR // 在ST代码中动态赋值 aiSensorValues[0] : 100; abMotorStatus[1] : TRUE; arRecipeParams[2, 3] : 45.67; asDeviceNames[0] : ‘Conveyor_01’;3.2 数组的遍历与循环处理数组最强大的工具是循环。ST 语言提供了FOR,WHILE,REPEAT循环最常用的是FOR循环。示例2使用 FOR 循环遍历一维数组// 假设我们需要计算aiSensorValues数组中所有值的总和与平均值 VAR iSum : INT : 0; iCount : INT; rAverage : REAL; iIndex : INT; // 循环计数器 END_VAR iCount : UPPER_BOUND(aiSensorValues, 1) - LOWER_BOUND(aiSensorValues, 1) 1; // 计算元素个数 iSum : 0; // 清零 FOR iIndex : LOWER_BOUND(aiSensorValues, 1) TO UPPER_BOUND(aiSensorValues, 1) DO iSum : iSum aiSensorValues[iIndex]; END_FOR IF iCount 0 THEN rAverage : INT_TO_REAL(iSum) / INT_TO_REAL(iCount); END_IF代码解释LOWER_BOUND(arr, dim)和UPPER_BOUND(arr, dim)是 CODESYS 的内置函数用于获取数组指定维度dim的下界和上界。使用它们而不是硬编码索引如0 TO 9能使代码更具适应性当数组大小改变时无需修改循环条件。INT_TO_REAL()是类型转换函数因为iSum和iCount是INT直接相除会进行整数除法转换为REAL后才能得到精确的平均值。示例3遍历二维数组// 遍历并打印arRecipeParams矩阵的所有元素 VAR iRow, iCol : INT; sMessage : STRING; END_VAR sMessage : ‘’; FOR iRow : LOWER_BOUND(arRecipeParams, 1) TO UPPER_BOUND(arRecipeParams, 1) DO FOR iCol : LOWER_BOUND(arRecipeParams, 2) TO UPPER_BOUND(arRecipeParams, 2) DO sMessage : CONCAT(sMessage, ‘Row’, INT_TO_STRING(iRow), ‘ Col’, INT_TO_STRING(iCol), ‘ ‘, REAL_TO_STRING(arRecipeParams[iRow, iCol]), ‘; ‘); END_FOR sMessage : CONCAT(sMessage, ‘\n’); // 换行 END_FOR // 在实际项目中可以将sMessage发送到日志或HMI3.3 数组的常用操作与函数除了基本的存取和遍历CODESYS 的 ST 语言还支持一些高级操作。1. 部分赋值与数组切片新版本支持较新版本的 CODESYS 支持类似数组切片的部分赋值但语法不如高级语言灵活。通常通过循环实现。// 将数组aiSource的前5个元素复制到aiTarget的前5个位置 VAR aiSource : ARRAY[0..9] OF INT : [0,1,2,3,4,5,6,7,8,9]; aiTarget : ARRAY[0..9] OF INT; i : INT; END_VAR FOR i : 0 TO 4 DO aiTarget[i] : aiSource[i]; END_FOR2. 使用MEMCPY函数进行快速内存复制对于大型数组或结构相同的数组MEMCPY函数效率更高。MEMCPY( destAddr : ADR(aiTarget), // 目标数组地址 srcAddr : ADR(aiSource), // 源数组地址 n : SIZEOF(aiSource) // 复制的字节数 );注意MEMCPY是底层操作务必确保源和目标内存区域有效且不重叠否则可能导致未定义行为。3. 查找最大值/最小值这是一个经典的数组算法。VAR aiData : ARRAY[1..8] OF INT : [12, 45, 2, 67, 33, 9, 88, 14]; iMaxValue : INT; iMaxIndex : INT; i : INT; END_VAR iMaxValue : aiData[LOWER_BOUND(aiData, 1)]; // 假设第一个元素最大 iMaxIndex : LOWER_BOUND(aiData, 1); FOR i : LOWER_BOUND(aiData, 1)1 TO UPPER_BOUND(aiData, 1) DO IF aiData[i] iMaxValue THEN iMaxValue : aiData[i]; iMaxIndex : i; END_IF END_FOR // 循环结束后iMaxValue为最大值iMaxIndex为其索引4. 完整实战案例生产线工位状态监控系统让我们通过一个模拟的小型生产线案例综合运用数组和 ST 语言。系统有 5 个工位每个工位有“空闲”、“工作中”、“故障”三种状态我们需要监控状态变化、统计数量并触发报警。4.1 定义数据类型与全局变量首先在 CODESYS 的“数据类型”DUT中创建一个枚举类型使代码更易读。在项目树中右键“数据类型”选择“添加对象” - “DUT”。名称输入E_StationState类型选择“枚举”。定义枚举值TYPE E_StationState : ( Idle : 0, // 空闲 Working : 1, // 工作中 Fault : 2 // 故障 ); END_TYPE在PLC_PRG的 VAR 区声明变量PROGRAM PLC_PRG VAR // 工位状态数组索引1-5 aStationState : ARRAY[1..5] OF E_StationState; // 工位心跳计时模拟传感器单位毫秒 aiHeartbeatTimer : ARRAY[1..5] OF INT; // 统计数量 iIdleCount : INT; iWorkingCount : INT; iFaultCount : INT; // 故障报警标志 bFaultAlarm : BOOL; // 循环计数器 i : INT; // 模拟心跳的定时器使用TON功能块 tmrSimulateHeartbeat : TON; END_VAR4.2 编写监控逻辑在PLC_PRG的主体部分VAR...END_VAR之后编写执行逻辑。// 模拟工位心跳更新每1秒随机更新一个工位的状态 tmrSimulateHeartbeat(IN:NOT tmrSimulateHeartbeat.Q, PT:T#1S); IF tmrSimulateHeartbeat.Q THEN tmrSimulateHeartbeat(IN:FALSE); // 复位定时器准备下一次触发 i : INT_TO_RANDOM(1, 5); // 模拟随机选择一个工位1-5 aiHeartbeatTimer[i] : 0; // 重置该工位计时器模拟收到心跳 // 随机改变该工位状态仅用于模拟实际中由硬件信号决定 CASE INT_TO_RANDOM(0, 2) OF 0: aStationState[i] : E_StationState.Idle; 1: aStationState[i] : E_StationState.Working; 2: aStationState[i] : E_StationState.Fault; END_CASE END_IF // 工位心跳超时检测假设超过3秒无心跳视为故障 FOR i : 1 TO 5 DO aiHeartbeatTimer[i] : aiHeartbeatTimer[i] 1; // 每个周期递增假设周期为100ms IF aiHeartbeatTimer[i] 30 THEN // 100ms * 30 3秒 aStationState[i] : E_StationState.Fault; END_IF END_FOR // 统计各状态工位数量 iIdleCount : 0; iWorkingCount : 0; iFaultCount : 0; FOR i : 1 TO 5 DO CASE aStationState[i] OF E_StationState.Idle: iIdleCount : iIdleCount 1; E_StationState.Working: iWorkingCount : iWorkingCount 1; E_StationState.Fault: iFaultCount : iFaultCount 1; END_CASE END_FOR // 触发故障报警任意工位故障即报警 bFaultAlarm : (iFaultCount 0);4.3 创建可视化界面HMI 变量关联为了直观看到效果我们可以添加简单的可视化。在 CODESYS 中可以添加“可视化”编辑器。为每个aStationState[i]创建一个指示灯根据枚举值改变颜色如绿色空闲黄色工作红色故障。创建数值显示框关联iIdleCount,iWorkingCount,iFaultCount。创建一个报警指示灯关联bFaultAlarm。4.4 运行与验证登录到 PLC仿真器。将程序下载到 PLC。切换到运行模式。打开可视化界面你将看到工位状态随机变化计数器动态更新当有工位“故障”时报警灯亮起。这个案例展示了如何用数组高效管理多个同类对象工位的状态并通过循环简化统计和检测逻辑。代码结构清晰易于扩展如需增加工位只需修改数组上下界和循环范围。5. 常见问题与排查思路在使用数组时新手常会遇到一些典型错误。问题现象可能原因排查思路与解决方案编译错误“数组索引越界”访问数组时使用的索引号不在声明的[下界..上界]范围内。例如声明了ARRAY[1..10]却尝试访问arr[0]或arr[11]。1.检查循环边界确保FOR循环的起止值使用LOWER_BOUND和UPPER_BOUND函数。2.检查硬编码索引审查所有直接使用数字索引的地方。3.使用条件判断在访问不确定的索引前用IF语句检查其有效性。运行时值不正确或为01. 数组未初始化元素为默认值0, FALSE等。2. 对数组的赋值操作发生在错误的程序位置或条件下。3. 多个代码段竞争修改同一数组元素。1.初始化数组在声明时或程序开始时赋予初始值。2.检查执行顺序确认赋值逻辑在读取逻辑之前执行。3.避免竞争对于在多个地方修改的数组确保逻辑互斥或使用临时变量。使用MEMCPY后程序崩溃1.destAddr或srcAddr地址错误。2.n字节数参数大于目标或源数组的实际大小。3. 源和目标内存区域重叠。1.检查地址使用ADR()函数获取数组地址确保是目标数组本身。2.计算正确大小使用SIZEOF()函数获取数组总字节数。3.避免重叠如果必须重叠复制使用循环逐元素赋值。二维数组访问逻辑混乱混淆了行和列的顺序。arr[row, col]和arr[col, row]指向不同元素。明确你的数据模型。通常第一个索引表示行第二个表示列。在声明和注释中明确约定。例如ARRAY[1..NumRows, 1..NumCols]。字符串数组赋值被截断声明字符串数组时指定了长度如STRING(10)但赋值时字符串超过10个字符。1. 声明足够长的字符串长度。2. 在赋值前检查字符串长度或使用LEFT,MID等函数截断。3. 考虑使用更动态的字符串类型如果PLC支持。6. 最佳实践与工程建议将数组用好不仅能实现功能还能写出健壮、可维护的工业级代码。命名规范使用有意义的名称并加上前缀表示数组类型。例如ai前缀Array of Integer (aiTemperatures)ab前缀Array of Boolean (abValveOpen)ar前缀Array of Real (arSetpoints)as前缀Array of String (asAlarmMessages)对于二维数组可以在名称中体现维度如armRecipeMatrix。使用常量定义数组边界避免在代码中多次出现“魔法数字”。VAR CONSTANT cNumStations : INT : 10; cFirstIndex : INT : 1; END_VAR VAR aStationData : ARRAY[cFirstIndex..cNumStations] OF REAL; END_VAR FOR i : cFirstIndex TO cNumStations DO // ... 使用 cNumStations 而不是 10 END_FOR当需要修改工位数量时只需修改cNumStations常量的值。封装数组操作为函数/功能块将常见的数组操作如求和、求平均、查找、排序封装成可重用的函数FUNCTION或功能块FUNCTION BLOCK。这极大提高了代码的复用性和可测试性。// 定义一个求整数数组平均值的函数 FUNCTION F_CalculateAverage : REAL VAR_INPUT arrInput : ARRAY[*] OF INT; // 可变长度数组VAR_IN_OUT 更佳但此处示例简化 iStartIdx : INT; iEndIdx : INT; END_VAR VAR iSum : INT : 0; i : INT; iCount : INT; END_VAR iCount : (iEndIdx - iStartIdx) 1; IF iCount 0 THEN F_CalculateAverage : 0.0; RETURN; END_IF FOR i : iStartIdx TO iEndIdx DO iSum : iSum arrInput[i]; END_FOR F_CalculateAverage : INT_TO_REAL(iSum) / INT_TO_REAL(iCount); END_FUNCTION注意数组的内存与性能大型数组尤其是多维数组会占用大量 PLC 内存。在资源受限的控制器上需谨慎设计。在循环内频繁调用UPPER_BOUND/LOWER_BOUND可能影响性能通常可忽略。对于固定边界的数组可在循环外计算并存储其边界值。对时间要求极其苛刻的任务考虑使用指针或MEMCPY但必须充分理解其风险。数组与 HMI/SCADA 数据交换在 CODESYS 中可以将整个数组变量公开为 HMI 可访问的变量。HMI 通常可以方便地显示数组内容如列表、表格或趋势图。确保 HMI 读取的数组索引与 PLC 程序中的逻辑一致避免出现差一错误Off-by-one error。掌握 CODESYS 中 ST 语言与数组的结合使用是迈向高效、结构化 PLC 编程的关键一步。从管理简单的传感器列表到处理复杂的生产配方矩阵数组都是不可或缺的工具。建议从文中的小例子开始动手实践逐步尝试将其应用到自己的项目中例如用数组管理一条柔性输送线上的托盘信息或者用二维数组存储不同产品的工艺参数。当你习惯用数组思维来组织数据时你会发现 PLC 程序也能写得像高级语言一样清晰、优雅。
返回列表