在结构化文本(ST)编程语言中,STATIC 关键字用于声明静态变量,其核心作用是:在函数块(FB)或功能(FC)多次调用之间,保留上一次执行结束时的值。这与默认的临时变量(TEMP)形成根本区别——后者每次调用都重新初始化,值不延续。掌握 STATIC 是实现状态记忆、计数累计、滤波缓存、步进控制等自动化逻辑的基础能力。
一、为什么需要 STATIC?从一个真实问题出发
假设你正在编写一个电机启停计时器:要求每次按下启动按钮,记录该次运行持续时间;按下停止按钮后,时间冻结并保存;下次再启动时,不是从零开始,而是继续累加(即“断电续计”式总运行时长)。若使用普通变量:
FUNCTION_BLOCK MotorTimer
VAR
runTime_s : TIME := T#0s; // ❌ 错误:未声明为 STATIC,每次调用重置为 0s
startTime : DATE_AND_TIME;
END_VAR
你会发现:只要 PLC 扫描周期刷新一次 FB,runTime_s 就被强制归零——根本无法累积。
根本原因:ST 中变量默认存储在本地堆栈(stack) 上,每次 FB 调用分配新空间,调用结束即释放。而 STATIC 变量则分配在全局数据区(instance DB 或 FB 的静态区),生命周期与 FB 实例绑定,只要实例存在,值就持续保留。
二、STATIC 变量的声明语法与位置规则
STATIC 必须出现在变量声明区(VAR / VAR_INPUT / VAR_OUTPUT / VAR_IN_OUT / VAR_TEMP)的开头,且仅对后续声明的变量生效,直到下一个关键字出现。
✅ 正确写法(推荐显式标注):
FUNCTION_BLOCK MotorRuntimeCounter
VAR
// 输入
bStart : BOOL;
bStop : BOOL;
// 静态变量(保留在实例数据区)
STATIC
tAccumulated : TIME := T#0s;
dtLastStart : DATE_AND_TIME;
bIsRunning : BOOL := FALSE;
// 输出
tTotalRunTime : TIME;
iCycleCount : INT;
END_VAR
⚠️ 注意事项:
STATIC不能用于VAR_TEMP区(TEMP 本意就是临时,与 STATIC 矛盾)。STATIC不能用于全局变量(VAR_GLOBAL),因全局变量本身已具备持久性。- 若省略初始化值(如
tAccumulated : TIME;),系统在首次调用时将其置为默认值(TIME默认为T#0s),之后始终保留上次值。 - 多个
STATIC块可共存,但通常合并为一个更清晰。
三、STATIC 与其它变量类型的本质区别(表格对比)
以下表格明确区分 ST 中五类变量的存储位置、生命周期及初始化行为:
| 变量类型 | 声明关键字 | 存储位置 | 生命周期 | 初始化时机 | 典型用途 |
|---|---|---|---|---|---|
| 静态变量 | STATIC |
实例数据块(DB) | FB 实例存在期间全程保留 | 首次调用前一次性初始化 | 累计值、状态标志、滤波缓存 |
| 输入变量 | VAR_INPUT |
调用方传入 | 单次调用内有效 | 每次调用时由实参赋值 | 接收按钮信号、设定值 |
| 输出变量 | VAR_OUTPUT |
调用方传出 | 单次调用内有效 | 每次调用前未定义(需赋值) | 返回计算结果、状态指示 |
| 输入输出变量 | VAR_IN_OUT |
调用方传入并传出 | 单次调用内有效 | 每次调用时由实参赋初值 | 修改传入的数组/结构体 |
| 临时变量 | VAR_TEMP |
本地堆栈 | 单次调用内有效 | 每次调用开始时随机(未定义) | 中间计算、局部标志位 |
关键结论:只有 STATIC 和全局变量能跨调用保持数据;其余所有变量都是“瞬时”的。
四、STATIC 的典型应用场景(附可直接复用代码)
场景 1:带记忆的上升沿检测(防抖+状态保持)
普通 R_TRIG 功能块在断电重启后丢失 CLK 上次状态,导致误触发。用 STATIC 自建可解决:
FUNCTION_BLOCK StableRisingEdge
VAR_INPUT
CLK : BOOL;
END_VAR
VAR_OUTPUT
Q : BOOL;
END_VAR
VAR
STATIC
bLastCLK : BOOL := FALSE;
END_VAR
// 逻辑:仅当 CLK 从 FALSE → TRUE 时输出 TRUE,且记忆上一状态
Q := NOT bLastCLK AND CLK;
bLastCLK := CLK;
✅ 效果:即使 PLC 断电重启,只要 FB 实例未删除,
bLastCLK在下次上电首周期仍为FALSE(因 DB 中该值被保持),确保首次CLK=TRUE时可靠触发。
场景 2:运行小时计数器(工业设备寿命管理)
FUNCTION_BLOCK RuntimeHourMeter
VAR_INPUT
bPowerOn : BOOL; // 主电源状态(常开触点)
tCycle : TIME := T#1s; // 扫描周期(用于积分)
END_VAR
VAR_OUTPUT
fHours : REAL; // 累计小时数(REAL 支持小数)
END_VAR
VAR
STATIC
tAccum : TIME := T#0s;
fAccum : REAL := 0.0;
END_VAR
// 每周期累加:tCycle 对应的小时数 = tCycle(毫秒)/ 3600000
IF bPowerOn THEN
tAccum := tAccum + tCycle;
fAccum := fAccum + (INT_TO_REAL(TIME_TO_MS(tCycle)) / 3600000.0);
END_IF;
fHours := fAccum;
⚙️ 使用说明:将
tCycle设为 FB 调用周期(如 OB1 中每 100ms 调用一次,则设T#100ms),fHours即为精确到小数点后三位的总运行小时。
场景 3:滑动平均滤波(5 点移动平均)
FUNCTION_BLOCK MovingAverage5
VAR_INPUT
fNewValue : REAL;
END_VAR
VAR_OUTPUT
fFiltered : REAL;
END_VAR
VAR
STATIC
fHistory : ARRAY[0..4] OF REAL := [0.0, 0.0, 0.0, 0.0, 0.0];
iIndex : INT := 0;
END_VAR
// 移动窗口:新值覆盖最老值,索引循环
fHistory[iIndex] := fNewValue;
iIndex := (iIndex + 1) MOD 5;
// 计算平均值(避免除零)
fFiltered := (fHistory[0] + fHistory[1] + fHistory[2] + fHistory[3] + fHistory[4]) / 5.0;
💡 优势:无需外部 DB 存储历史数组;所有数据封装在 FB 内部,实例化多个滤波器互不干扰。
五、STATIC 的底层机制与常见误区
▶ 机制本质
当编译器遇到 STATIC 声明时:
- 为该 FB 自动生成一个隐式结构体(struct),成员包含所有
STATIC变量; - 每次创建 FB 实例(例如在 OB1 中调用
FB100()),系统自动为其分配一块独立的实例数据块(Instance DB); - 所有
STATIC变量的地址映射到该 DB 的固定偏移处; - DB 数据在 PLC 运行期间持续驻留 RAM(若配置为保持性,则掉电不丢失)。
▶ 高频误区澄清
| 误区描述 | 正确理解 |
|---|---|
| “STATIC 变量掉电不丢失” | ❌ 错误。是否掉电保持取决于实例 DB 是否配置为保持性(在 TIA Portal 中勾选 Retentive)。未勾选时,断电后值清零。 |
| “在 FC(功能)中也能用 STATIC” | ❌ 错误。FC 无实例 DB,不支持 STATIC。若需类似效果,必须将变量声明为 IN_OUT 并由调用方提供保持性变量。 |
“STATIC 变量可以初始化为表达式,如 STATIC x : INT := 2*3;” |
✅ 正确。编译期计算,等效于 := 6。但不可含运行时函数(如 := GVL_SystemTime)。 |
| “多个 FB 实例共享同一个 STATIC 变量” | ❌ 错误。每个 FB 实例拥有独立副本。FB1 和 FB2 的 STATIC y 完全无关。 |
六、调试 STATIC 变量的实用技巧
- 在线监控 DB:在 TIA Portal 中打开 FB 对应的 Instance DB,勾选“显示静态变量”,直接观察各
STATIC字段实时值变化。 - 强制写入测试:右键 DB 中某
STATIC变量 → “强制值”,输入T#10s,再次调用 FB,验证其是否真正被继承。 - 对比 TEMP 行为:将
STATIC tAccum : TIME;临时改为VAR_TEMP tAccum : TIME;,观察运行时间是否归零——这是验证STATIC是否生效的最快方式。 - 检查编译警告:若声明
STATIC在VAR_TEMP区,编译器会报错Error 4021: STATIC not allowed in VAR_TEMP section。
七、性能与内存影响评估
- 内存开销:每个
STATIC变量占用实例 DB 空间。例如ARRAY[0..999] OF REAL占用 4 KB。务必评估总 DB 大小是否超出 CPU 限制。 - 执行效率:访问
STATIC变量与访问普通 DB 变量相同,均为直接地址读写,无额外运行时开销。 - 最佳实践:
- 仅对确实需要跨周期保持的变量使用
STATIC; - 避免在高频调用(如 1ms OB)的 FB 中声明大型
STATIC数组; - 对只读配置参数,优先使用
VAR_IN_OUT+ 全局常量,而非STATIC。
- 仅对确实需要跨周期保持的变量使用
八、替代方案对比:STATIC vs 全局变量 vs IN_OUT
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
STATIC |
封装性强、多实例隔离、无需手动管理地址 | 占用实例 DB 空间 | 标准 FB 内部状态管理(推荐首选) |
全局变量(VAR_GLOBAL) |
显式可见、跨 FB 共享方便 | 全局污染风险高、多实例无法隔离 | 系统级配置(如 GVL_MachineConfig.MaxSpeed) |
VAR_IN_OUT |
灵活控制存储位置(可用 DB 或全局变量)、FC 中唯一选择 | 调用方需主动提供变量、易出错 | FC 中需保持状态;或需将状态存入指定 DB |
✅ 结论:对于 FB,无条件优先使用 STATIC;对于 FC,必须用 IN_OUT 并由调用方传入保持性变量。
九、完整可运行示例:带启停记忆的步进电机控制器
FUNCTION_BLOCK StepperController
VAR_INPUT
bEnable : BOOL;
bStepClock : BOOL; // 上升沿驱动一步
bReset : BOOL;
iTargetPos : INT; // 目标位置(脉冲数)
END_VAR
VAR_OUTPUT
bAtTarget : BOOL;
iCurrentPos : INT;
END_VAR
VAR
STATIC
iPos : INT := 0;
bLastClk : BOOL := FALSE;
bWasReset : BOOL := FALSE;
END_VAR
// 复位优先级最高
IF bReset THEN
iPos := 0;
bWasReset := TRUE;
ELSIF bEnable THEN
// 检测上升沿(抗抖动)
IF NOT bLastClk AND bStepClock THEN
iPos := iPos + 1;
bWasReset := FALSE;
END_IF;
END_IF;
bLastClk := bStepClock;
// 输出更新
iCurrentPos := iPos;
bAtTarget := (iPos >= iTargetPos) AND bEnable AND NOT bWasReset;
📌 部署说明:
- 在主程序 OB1 中调用:
Stepper1( bEnable:=M1_Enable, bStepClock:=M1_Pulse, ... );- 每个电机实例(
Stepper1,Stepper2)拥有独立iPos,互不干扰;- 断电重启后,若 DB 配置为保持性,
iPos值自动恢复,位置记忆不丢失。
十、最后确认清单(部署前必查)
STATIC关键字位于VAR区内,且在其修饰的变量之前;- 所有需保持的变量均已声明为
STATIC,无遗漏; - 实例 DB 已在 TIA Portal 中启用
Retentive属性(如需掉电保持); - 未在 FC 中误用
STATIC; - 无大型
STATIC数组滥用(单个 FB 的静态区建议 < 16 KB); - 在线调试中已验证变量值跨调用持续存在。

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