ST局部变量与全局变量:VAR、VAR_INPUT、VAR_OUTPUT的作用域

发布于 2026-03-18 06:12:11 · 浏览 3 次 · 评论 0 条

在结构化文本(ST)编程中,变量作用域是决定程序可维护性、安全性与逻辑清晰度的核心机制。ST 是 IEC 61131-3 标准定义的高级文本语言,广泛用于 PLC 编程(如 Siemens TIA Portal、Codesys、Beckhoff TwinCAT)。理解 VARVAR_INPUTVAR_OUTPUT 的作用域规则,不是语法细节问题,而是避免逻辑错乱、数据覆盖、调试困难的根本前提。


一、什么是作用域?为什么它在 ST 中特别关键?

作用域(Scope)指变量在程序中可见且可访问的代码区域。在 ST 中,变量一旦声明,其生命周期、初始化时机、内存归属、读写权限均由声明位置和关键字严格限定。与 C 或 Python 不同,ST 没有“局部函数作用域”或“动态作用域”,只有静态、显式、块级作用域——即变量从声明起就绑定到特定程序组织单元(POU),且不可跨层级隐式继承。

错误的作用域使用会导致三类典型故障:

  • 值被意外覆盖:多个 POU 同名 VAR 变量指向同一地址(若未正确隔离);
  • 读取未初始化值VAR_INPUT 在调用时未传参,却直接读取,结果为默认值(如 0FALSE),掩盖逻辑缺陷;
  • 无法调试修改:全局变量被多处隐式修改,断点追踪失效。

因此,ST 中没有“默认作用域”——每个变量必须通过明确关键字声明其意图。


二、三大变量区的核心语义与作用域边界

ST 中所有变量声明必须位于 POU(Program、Function Block、Function)的声明区(Declaration Section),以 VAR 开头、END_VAR 结尾。声明区内部可并列多个变量块,每个块由不同关键字引导,含义截然不同:

  1. VAR_INPUT输入参数区

    • 作用域:仅限该 POU 内部可读,值由调用方显式传递
    • 生命周期:每次调用时重新载入,调用结束即释放(对 FB 还涉及背景数据块保留);
    • 初始化:不支持默认值赋值(如 VAR_INPUT x : INT := 10; 是语法错误);
    • 特殊规则:若为 VAR_INPUT REFERENCE TO ...,则传递的是地址而非值,需确保源变量生命周期覆盖调用周期。
  2. VAR_OUTPUT输出参数区

    • 作用域:仅限该 POU 内部可写,值在调用结束后返回给调用方
    • 生命周期:与 VAR_INPUT 同步,在调用退出前完成赋值;
    • 初始化:声明时不初始化,首次执行时内容为数据类型默认值(INT0BOOLFALSESTRING'');
    • 关键限制:禁止在 POU 内部读取未写入的 VAR_OUTPUT(部分平台报编译警告,运行时行为未定义)。
  3. VAR局部变量区

    • 作用域:仅在本 POU 声明区以下的 ST 代码块内可见且可读写
    • 生命周期:FB 类型中随背景数据块持久化;Program 类型中随循环扫描周期重置(具体取决于 PLC 实现,TIA Portal 中 Program 的 VAR 默认每周期清零);
    • 初始化:支持默认值(如 VAR count : INT := 0;),且该初始化仅在 POU 首次加载或复位时执行一次;
    • 本质:这是真正的“私有变量”,调用方完全不可见,也无法通过任何方式间接访问。

✅ 正确示例(Function Block FB_MotorCtrl):

FUNCTION_BLOCK FB_MotorCtrl
VAR_INPUT
start   : BOOL;
speed   : REAL;
timeout : TIME;
END_VAR
VAR_OUTPUT
running : BOOL;
error   : WORD;
END_VAR
VAR
timer   : TON;
state   : INT := 0;  // 初始值仅在首次实例化时生效
retry   : INT := 3;
END_VAR

❌ 典型错误:

VAR_INPUT
flag : BOOL := TRUE;  // 编译失败:VAR_INPUT 不允许初始化
END_VAR
VAR_OUTPUT
result : INT;
// result := result + 1;  // 危险:读取未写入的 VAR_OUTPUT,值不确定
END_VAR

三、作用域对比表:谁能在哪读/写?何时存在?

以下表格严格按 IEC 61131-3 标准定义,适用于所有合规 ST 环境(TIA Portal V18+、Codesys 3.5+、TwinCAT 3):

变量区 调用方能否访问 POU 内能否读 POU 内能否写 是否支持默认值 生命周期起点 生命周期终点
VAR_INPUT 必须传参才能访问(调用方提供) ✅ 可读 ❌ 不可写 ❌ 禁止 调用指令执行瞬间 本 POU 执行结束
VAR_OUTPUT 调用后可通过变量名获取结果 ⚠️ 不推荐读(未写入前值未定义) ✅ 可写 ❌ 禁止 调用指令执行瞬间 本 POU 执行结束(值传出)
VAR 完全不可见 ✅ 可读 ✅ 可写 ✅ 允许 POU 首次加载或复位时 POU 销毁(FB 实例删除)或周期重置(Program)

注意:VAR_IN_OUT 是第四类,但本质是 VAR_INPUTVAR_OUTPUT 的组合(双向传递引用),不在本文范围,故不展开。


四、真实场景下的作用域陷阱与规避策略

场景 1:Function 中误用 VAR 替代 VAR_INPUT

问题:编写一个计算平均值的 Function F_Avg,开发者将采样数组声明为 VAR arr : ARRAY[0..9] OF REAL;,并在调用前手动填充 arr
后果:arr 是纯局部变量,调用方对 arr 的任何赋值均无效;实际运行时 arr 始终为全 0
✅ 正解:必须声明为 VAR_INPUT arr : ARRAY[0..9] OF REAL;,调用时传入实参。

场景 2:FB 的 VAR 被多实例共享(隐式全局化)

问题:在 TIA Portal 中创建 FB_PID,其 VAR 区含 integral : REAL;。当两个不同背景数据块(DB1、DB2)调用同一 FB 时,若 integral 声明在 VAR_GLOBAL(错误关键字)或未指定存储类别,可能导致两实例积分项互相干扰。
✅ 正解:确认 integral 位于 VAR 块(非 VAR_GLOBAL),且每个 FB 实例绑定独立 DB——此时 VAR 自动映射至对应 DB 偏移,天然隔离。

场景 3:VAR_OUTPUT 初始化伪需求

问题:需要 error 输出默认为 16#0000(无错误),但直接写 VAR_OUTPUT error : WORD := 16#0000; 报错。
✅ 正解:在代码首行强制赋初值:

IF NOT initialized THEN
    error := 16#0000;
    initialized := TRUE;
END_IF

其中 initializedVAR 区布尔变量(默认 FALSE),确保只初始化一次。


五、作用域与编译器行为的底层关联

ST 编译器将不同变量区映射到 PLC 内存的不同物理段:

  • VAR_INPUT / VAR_OUTPUT → 映射至调用栈帧(Stack Frame),地址由调用指令动态分配,生命周期严格绑定调用;
  • VAR → 映射至背景数据块(Instance DB)(FB)或循环组织块 OB1 的本地数据栈(Program),地址静态固定;
  • 若声明 VAR RETAIN,则 VAR 区变量内容在 PLC STOP→RUN 切换时保持(写入保持性存储区)。

这意味着:作用域不仅是语法约定,更是内存布局契约。越界访问(如在 Function 中读 VAR_OUTPUT)在编译阶段即被阻止,因为编译器无法生成合法内存寻址指令。


六、最佳实践清单(可直接嵌入团队编码规范)

  1. 输入必用 VAR_INPUT:任何需由调用方控制的数据,无论简单类型或数组,一律声明为 VAR_INPUT
  2. 输出必用 VAR_OUTPUT:任何需反馈给调用方的结果,禁止用 VAR + 外部读取替代;
  3. 状态变量归 VAR:计数器、定时器实例、中间计算结果、标志位,全部置于 VAR 区;
  4. 禁用裸 VAR 传参:绝不通过全局变量或 VAR 区变量“隐式通信”,所有交互必须显式通过 INPUT/OUTPUT
  5. 初始化检查前置:对依赖初始值的 VAR,用 IF NOT init_flag THEN ... END_IF 封装,而非依赖声明赋值(因某些平台重启后声明赋值不触发);
  6. 命名体现作用域in_Speedout_Runningvar_Counter,从名称即可判断归属,降低阅读成本。

七、调试验证方法:三步确认作用域生效

  1. 编译检查:尝试在 VAR_INPUT x : INT; 下方写 x := 10;,编译器必须报错(“Assignment to input variable not allowed”);
  2. 在线监控:在 TIA Portal 中打开 FB 实例的背景 DB,在线查看 VAR 区变量是否随周期变化、VAR_INPUT 是否与调用端变量值实时同步;
  3. 断点验证:在 FB 内设置断点,观察 VAR_OUTPUT y 在首次进入时的值——应为类型默认值(如 0),而非随机数或上周期残留值。

以上任一环节异常,即表明作用域机制未被正确遵循。


作用域不是约束,而是保护壳。VAR_INPUT 划定责任边界,VAR_OUTPUT 明确交付承诺,VAR 构建逻辑堡垒。把变量放进正确的 VAR_XXX 里,等于为每一行代码签发了内存主权证书。

评论 (0)

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

扫一扫,手机查看

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