ST静态变量STATIC:如何在多次调用中保持数据记忆

发布于 2026-03-18 06:52:43 · 浏览 4 次 · 评论 0 条

在结构化文本(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 变量的实用技巧

  1. 在线监控 DB:在 TIA Portal 中打开 FB 对应的 Instance DB,勾选“显示静态变量”,直接观察各 STATIC 字段实时值变化。
  2. 强制写入测试:右键 DB 中某 STATIC 变量 → “强制值”,输入 T#10s,再次调用 FB,验证其是否真正被继承。
  3. 对比 TEMP 行为:将 STATIC tAccum : TIME; 临时改为 VAR_TEMP tAccum : TIME;,观察运行时间是否归零——这是验证 STATIC 是否生效的最快方式。
  4. 检查编译警告:若声明 STATICVAR_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 值自动恢复,位置记忆不丢失。

十、最后确认清单(部署前必查)

  1. STATIC 关键字位于 VAR 区内,且在其修饰的变量之前;
  2. 所有需保持的变量均已声明为 STATIC,无遗漏;
  3. 实例 DB 已在 TIA Portal 中启用 Retentive 属性(如需掉电保持);
  4. 未在 FC 中误用 STATIC
  5. 无大型 STATIC 数组滥用(单个 FB 的静态区建议 < 16 KB);
  6. 在线调试中已验证变量值跨调用持续存在。

评论 (0)

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

扫一扫,手机查看

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