当工程师从梯形图转向ST(结构化文本)或SCL(结构化控制语言)编程时,常常会在变量作用域这个看似简单的概念上栽跟头。这不是因为概念本身复杂,而是因为它触及了PLC编程中一个容易被忽视的核心问题:变量的生命周期管理。
为什么要关心变量类型?
在梯形图编程中,几乎所有变量都是"全局"且"持久"的——它们一直存在于数据块中,随时可以访问。但当你开始使用函数块(FB)和函数(FC)构建模块化程序时,这种"一刀切"的方式会带来三个问题:
内存浪费:每个中间计算结果都占用永久存储空间
可维护性差:无法区分哪些数据需要在调用之间保持,哪些只是临时计算
并发风险:多个实例共享临时数据可能导致意外的数据覆盖
理解静态变量(Static)和临时变量(Temp)的区别,本质上是理解如何让PLC像优秀的程序员一样管理内存。
从计算机内存模型说起
PLC虽然是专用控制器,但其内存管理借鉴了经典的计算机架构。理解这个基础模型能帮助我们更好地把握PLC的变量机制:
计算机的四大内存区域
代码区(只读):存储编译后的程序指令,PLC中对应程序块的逻辑代码
静态数据区(持久):全局变量和静态变量的栖息地,程序启动时分配,运行全程保留。这是PLC中Static变量的理论基础
栈区(动态):函数调用时分配,返回时释放。局部临时变量、函数参数都在这里完成它们的"短暂一生"。对应PLC的Temp变量
堆区(手动管理):虽然通用计算机支持动态内存分配,但PLC通常不提供这种机制以保证实时性
PLC中的实际映射
静态变量:数据块的"永久居民"
在西门子TIA Portal中,当你在函数块(FB)中声明静态变量时:
FUNCTION_BLOCK "FB_Counter"VAR // 西门子中等同于StaticiCount : INT := 0; // 累加计数器bFirstScan : BOOL := TRUE; // 首次扫描标志END_VAR
这些变量被存储在实例数据块(Instance DB)中。关键特性:
生命周期:与实例数据块同寿,直到被删除或PLC断电(非保持性变量)
独立性:每个FB实例拥有独立的静态变量副本
临时变量:栈空间的"过客"
同样的函数块中声明临时变量:
VAR_TEMPiTemp : INT; // 临时计算结果rCalculation : REAL; // 中间运算值END_VAR
这些变量存储在本地数据栈(L Stack)中,每次函数调用时分配,返回时自动释放:
生命周期:仅在单次调用期间有效,下次调用时值不可预测
共享性:所有实例共享同一栈空间(但时间错开,互不干扰)
适用场景:循环索引、临时转换变量、中间计算步骤
一个实战案例:滤波器函数块
让我们通过一个移动平均滤波器来看两种变量的协作:
FUNCTION_BLOCK "FB_MovingAverage"VAR_INPUTrInput : REAL; // 当前输入值iWindowSize : INT := 10; // 滑动窗口大小END_VARVAR_OUTPUTrOutput : REAL; // 滤波后输出END_VARVAR // 静态变量 - 需要持久保存arBuffer : ARRAY[0..99] OF REAL; // 历史数据缓冲区iIndex : INT := 0; // 当前写入位置bInitialized : BOOL := FALSE; // 初始化标志END_VARVAR_TEMP // 临时变量 - 仅用于本次计算iLoop : INT; // 循环计数器rSum : REAL := 0.0; // 累加和END_VAR// 首次调用初始化IF NOT bInitialized THENFOR iLoop := 0 TO 99 DOarBuffer[iLoop] := rInput; // 用首个值填充缓冲区END_FOR;bInitialized := TRUE;END_IF;// 更新缓冲区arBuffer[iIndex] := rInput;iIndex := (iIndex + 1) MOD iWindowSize; // 循环索引// 计算移动平均(使用临时变量避免污染状态)FOR iLoop := 0 TO (iWindowSize - 1) DOrSum := rSum + arBuffer[iLoop];END_FOR;rOutput := rSum / INT_TO_REAL(iWindowSize);
设计要点分析:
arBuffer和iIndex必须是静态变量——它们构成了滤波器的"记忆"
iLoop和rSum应该是临时变量——它们只在单次计算中有意义,下次调用不需要保留如果错误地将
rSum声明为静态变量,它会累积上次计算的残留值,导致结果错误
性能与资源权衡
内存占用对比
假设一个FC被10个不同FB调用,其内部声明了100字节的局部变量:
全部使用临时变量:栈空间需求100字节(所有调用共享)
全部使用静态变量:需要10×100 = 1000字节(每个实例独立)
对于S7-1200/1500系列,本地数据栈通常为16KB,静态数据则受工作内存限制(几MB级别)。在资源受限的小型PLC上,合理使用临时变量能显著减少内存压力。
执行效率考量
临时变量:访问栈数据通常比访问数据块更快(寻址方式更简单)
静态变量:需要通过数据块指针间接寻址,但差异在现代PLC上可忽略不计
真正的性能优势在于避免不必要的数据持久化——每次扫描周期末,PLC需要将修改过的静态数据写回,减少静态变量能降低这部分开销。
常见陷阱与最佳实践
陷阱1:误用临时变量保存状态
// 错误示例:试图用临时变量实现计数器VAR_TEMPiCounter : INT; // 每次调用都被重置!END_VARiCounter := iCounter + 1; // 永远等于1
解决方案:任何需要在调用之间保持的数据都必须使用静态变量。
陷阱2:过度使用静态变量
// 低效示例:所有中间变量都声明为静态VARiTempA, iTempB, iTempC : INT; // 实际只用于单次计算rResult : REAL;END_VARiTempA := Input1 * 2;iTempB := Input2 + 5;iTempC := iTempA - iTempB;rResult := INT_TO_REAL(iTempC);
改进方案:将纯计算变量改为临时变量,只保留rResult为静态变量(如果需要输出)。
最佳实践检查清单
在声明变量前问自己三个问题:
这个值需要在下次调用时记住吗?
-
- → 是:Static / 否:Temp
这个值是否属于对象的"状态"?
-
- → 是:Static / 否:Temp
多个实例需要独立维护这个值吗?
- → 是:Static / 否:考虑全局变量或Temp
西门子博途的特殊性
与IEC 61131-3标准ST语言略有差异:
| 标准ST | 西门子SCL | 说明 |
|---|---|---|
| VAR/VAR_STAT | VAR | 静态变量(存储在实例DB) |
| VAR_TEMP | VAR_TEMP | 临时变量(存储在L栈) |
| VAR_INPUT/OUTPUT | VAR_INPUT/OUTPUT | 接口变量(也是静态) |
注意:VAR_INPUT和VAR_OUTPUT在博途中实际上也是静态变量,存储在实例DB中。这意味着即使是输入输出参数,也会在调用之间保持其值。
478