『7x24小时有问必答』

3个FB变量陷阱+4个实战技巧,R系列入门先避坑

你写了一个电机控制FB,第一次运行完美,第二次调用电机突然不动了。排查三小时,发现是静态变量“记住”了上次的中间状态。90%的R系列初学者在FB上栽过这个跟头。FB的本质是“带记忆的函数”,但记忆在哪里、怎么管理、如何清零?这3个变量陷阱,今天一次讲透。

一、FB变量类型:不是所有变量都“活”在同一个世界

1.1 四类变量,各自为政

MELSEC iQ-R的FB支持四种变量声明区,每个区的生命周期和作用域不同。一张表秒懂:
变量类型关键字数据流向外部访问保持特性典型用途
输入参数VAR_INPUT外部→内部只读每次调用刷新设定值、使能信号
输出参数VAR_OUTPUT内部→外部只写每次调用刷新结果、状态位
双向参数VAR_IN_OUT双向传递读写与外部变量绑定累加器、状态机
内部变量VAR仅内部不可见保持(不掉电)中间变量、计数器
核心差异:
只有
VAR
区的变量会“记住”上次调用的值,
VAR_INPUT
VAR_OUTPUT
每次调用时根据实参刷新。
VAR_IN_OUT
则直接操作外部变量本身。

1.2 代码示例:温度监控FB

( FB定义:温度监控功能块 )
FUNCTION_BLOCK FB_TempMonitor
VAR_INPUT
Temp_Actual : REAL;       ( 实际温度,单位℃ )
Temp_Set : REAL := 100.0; ( 目标温度,默认100℃ )
END_VAR
VAR_OUTPUT
Heater_On : BOOL;         ( 加热器控制信号 )
Alarm : BOOL;             ( 超限报警 )
END_VAR
VAR
Counter : INT := 0;       ( 内部计数器,保持型 )
Previous_Temp : REAL;     ( 上一次温度值,用于判断变化率 )
END_VAR
( 逻辑:偏差大于5℃开启加热,计数器记录连续超限次数 )
IF (Temp_Actual < Temp_Set - 5.0) THEN
Heater_On := TRUE;
Counter := Counter + 1;
ELSE
Heater_On := FALSE;
Counter := 0;
END_IF;
IF Counter > 10 THEN          ( 连续11次检测到超差 )
Alarm := TRUE;
END_IF;
Previous_Temp := Temp_Actual; ( 保存当前值用于下一周期 )
END_FUNCTION_BLOCK

1.3 调用时别踩坑

( 主程序调用:实例化两个温度监控块 )
PROGRAM Main
VAR
Mon1 : FB_TempMonitor;    ( 实例1 )
Mon2 : FB_TempMonitor;    ( 实例2 )
ActualTemp1 : REAL := 98.0;
ActualTemp2 : REAL := 95.0;
Output1 : BOOL;
Output2 : BOOL;
Alarm1 : BOOL;
Alarm2 : BOOL;
END_VAR
Mon1(Temp_Actual := ActualTemp1,   ( 输入赋值 )
Temp_Set := 100.0,
Heater_On => Output1,         ( 输出赋值 )
Alarm => Alarm1);
Mon2(Temp_Actual := ActualTemp2,
Temp_Set := 100.0,
Heater_On => Output2,
Alarm => Alarm2);
END_PROGRAM
注意:
Mon1
Mon2
各自拥有独立的内部变量
Counter
。即使
Mon1
的计数器到10报警,
Mon2
的计数器仍然从0开始。这就是FB的封装性——也是“记忆”的来源。
悬念:
既然内部变量会保持,那如果我想让FB每次调用都从初始状态开始,是不是给变量赋初值就行了?答案会让你吃惊——往下看。

二、静态变量的保持陷阱:初值不等于每次复位

2.1 你以为的初值,只在第一次起作用

大多数工程师在FB内部声明变量时写Counter : INT := 0;,然后默认每次调用都会把Counter重置为0。错! R系列的FB内部变量初值只在第一次实例化时赋值,后续调用都不会重新初始化。也就是说:
第一次调用:Counter = 0 → 执行逻辑,可能变成1。
第二次调用:Counter = 1 → 不会变回0,直接从上一次结果继续。
这个特性在累计计数、状态保持等场景很有用,但在“每次独立计算”的场景就是灾难。

2.2 实战案例:为啥我的脉冲计数器不准?

需求:
一个FB统计外部脉冲上升沿次数,每调用一次输出当前计数。
错误写法:
FUNCTION_BLOCK FB_PulseCounter_ERR
VAR_INPUT
Pulse : BOOL;         ( 脉冲信号 )
END_VAR
VAR_OUTPUT
Count : INT := 0;     ( 输出计数 )
END_VAR
VAR
Edge_Detect : BOOL;   ( 上升沿检测变量 )
Temp : INT := 0;
END_VAR
( 上升沿检测 )
IF (Pulse AND NOT Edge_Detect) THEN
Temp := Temp + 1;
END_IF;
Edge_Detect := Pulse;
Count := Temp;            ( 输出内部临时变量 )
END_FUNCTION_BLOCK
调用结果为:
每次扫描周期,
Count
持续累加,不会清零。如果你希望每次调用独立累计(比如在多个不同任务中分别计数),则每个实例的计数器独立,但若在一个循环中连续调用多次,则
Count
会不断增长,输出值不是预期。
正确做法:
Count
放到
VAR_OUTPUT
中,让外部变量决定是否重置。
FUNCTION_BLOCK FB_PulseCounter_OK
VAR_INPUT
Pulse : BOOL;
Reset : BOOL;         ( 复位信号 )
END_VAR
VAR_OUTPUT
Count : INT;          ( 每次调用输出当前累计值 )
END_VAR
VAR
Edge_Detect : BOOL;
InternalCount : INT := 0;
END_VAR
IF Reset THEN
InternalCount := 0;
ELSIF (Pulse AND NOT Edge_Detect) THEN
InternalCount := InternalCount + 1;
END_IF;
Edge_Detect := Pulse;
Count := InternalCount;
END_FUNCTION_BLOCK
关键点:
只有
VAR
区的变量保持,
VAR_OUTPUT
每次调用根据逻辑刷新。如果希望外部能随时清零,就用显式复位引脚。

2.3 数据对比:保持型 vs 非保持型

场景使用VAR使用VAR_OUTPUT结果
状态机保持状态每次输出瞬时值正确
累计计数器保持累计输出当前累计正确
一次性数学运算错误保持中间值正确输出结果运算错误
悬念:
内部变量保持特性已经让很多人头疼,但更隐蔽的是
VAR_IN_OUT
——它直接把外部变量的“命”交到FB手里,操作不当会导致整个程序逻辑混乱。下一章揭露它的致命陷阱。

三、VAR_IN_OUT的秘密:你不能传入常数的真正原因

3.1 双向传递,而非只读/只写

VAR_IN_OUT
是三菱FB中最容易被误解的类型。它不是
VAR_INPUT
的加强版,也不是
VAR_OUTPUT
的复制品。它的本质是
引用(Reference)
:FB内部直接操作外部变量的内存地址。这意味着:
你不能传入常数(如123TRUE),因为常数没有内存地址。
外部变量的任何修改都会立刻反馈到内部,反之亦然。
一个VAR_IN_OUT变量在调用时只能映射到一个全局标签或另一个实例的输出,不能是临时变量。

3.2 代码对比:用VAR_IN_OUT实现数据交换

需求:
两个电机速度互相取最大值,且一个电机速度变化时另一个自动跟踪。
FUNCTION_BLOCK FB_SpeedSync
VAR_IN_OUT
Speed_A : INT;      ( 电机A速度,双向传递 )
Speed_B : INT;      ( 电机B速度 )
END_VAR
VAR
MaxSpeed : INT;
END_VAR
MaxSpeed := MAX(Speed_A, Speed_B);
Speed_A := MaxSpeed;    ( 修改外部变量A )
Speed_B := MaxSpeed;    ( 修改外部变量B )
END_FUNCTION_BLOCK
调用:
PROGRAM Main
VAR
M1_Speed : INT := 1000;
M2_Speed : INT := 1200;
Sync1 : FB_SpeedSync;
END_VAR
Sync1(Speed_A := M1_Speed,   ( 必须传变量,不能传1000 )
Speed_B := M2_Speed);
( 执行后,M1_Speed和M2_Speed都变为1200 )
END_PROGRAM

3.3 常见错误:误用VAR_INPUT导致数据不同步

如果改用VAR_INPUT传入两个速度,FB内部只能读取,无法改写外部变量。要实现相同功能,必须增加两个VAR_OUTPUT,然后外部再赋值回去。代码膨胀一倍,效率降低。
性能对比:
使用
VAR_IN_OUT
时,CPU只需一次内存地址传递;使用
VAR_INPUT
+
VAR_OUTPUT
需要两次数据拷贝和一次外部赋值。对于高速控制周期(如0.5ms),这种浪费可能引发任务超时。
悬念:
变量陷阱解决了,如何利用FB的“记忆”特性设计高级功能?接下来用3段代码教你用FB实现状态机——这是入门到进阶的分水岭。

四、实战技巧:用FB静态变量实现状态机(认知缺口)

4.1 状态机的本质:依赖“记忆”

状态机需要记住当前处于哪个状态,并根据输入和当前状态决定下一状态和输出。这个“记忆”正是FB内部变量VAR的强项。

4.2 三段代码:状态机FB

Step 1: 定义状态类型和FB
( 枚举状态类型 )
TYPE Enum_State : (IDLE, RUNNING, ERROR) := IDLE; END_TYPE
FUNCTION_BLOCK FB_StateMachine
VAR_INPUT
Start : BOOL;
Stop : BOOL;
Fault : BOOL;
Reset : BOOL;
END_VAR
VAR_OUTPUT
Ready : BOOL;
Running : BOOL;
Error : BOOL;
END_VAR
VAR
CurrentState : Enum_State := IDLE;  ( 保持当前状态 )
Timer : UINT := 0;                  ( 状态延时计数器 )
END_VAR
Step 2: 实现状态转移逻辑
CASE CurrentState OF
IDLE:
Ready := TRUE;
Running := FALSE;
Error := FALSE;
IF Start AND NOT Fault THEN
CurrentState := RUNNING;
Timer := 0;
END_IF;
RUNNING:
Ready := FALSE;
Running := TRUE;
Error := FALSE;
Timer := Timer + 1;
IF Stop OR Fault THEN
CurrentState := ERROR;
ELSIF Timer > 1000 THEN   ( 运行超时保护 )
CurrentState := ERROR;
END_IF;
ERROR:
Ready := FALSE;
Running := FALSE;
Error := TRUE;
IF Reset THEN
CurrentState := IDLE;
Timer := 0;
END_IF;
END_CASE;
END_FUNCTION_BLOCK
Step 3: 主程序调用并观察结果
PROGRAM Main
VAR
FSM1 : FB_StateMachine;
Cmd_Start : BOOL := TRUE;
Cmd_Stop : BOOL := FALSE;
Fault_Signal : BOOL := FALSE;
Reset_Signal : BOOL := FALSE;
Out_Ready : BOOL;
Out_Running : BOOL;
Out_Error : BOOL;
END_VAR
FSM1(Start := Cmd_Start,
Stop := Cmd_Stop,
Fault := Fault_Signal,
Reset := Reset_Signal,
Ready => Out_Ready,
Running => Out_Running,
Error => Out_Error);
END_PROGRAM
关键点:
CurrentState
VAR
区变量,它在每个扫描周期都保持当前状态,不会因为调用结束而丢失。这正是FB实现状态机的核心——用一个变量跨越多个扫描周期。

4.3 参数验证表

调用周期StartStopFaultReset当前状态输出
1TRUEFALSEFALSEFALSEIDLE→RUNNINGRunning=TRUE
2FALSETRUEFALSEFALSERUNNING→ERRORError=TRUE
3FALSEFALSEFALSETRUEERROR→IDLEReady=TRUE
认知深化:
许多人以为状态机必须用全局变量或专门的库,但其实一个FB内部声明一个枚举变量就够了。三菱R系列的FB天然支持这种“跨周期记忆”,无需额外成本。
悬念:
既然FB内部变量一直保持,那如果CPU断电会怎样?答案:背景数据块会由电池备份保存,除非你手动清除。想知道如何安全地初始化所有FB背景数据?下回分解。

总结:5个关键点 + 互动问题

你必须记住的5条铁律:
1.VAR_INPUT和VAR_OUTPUT是“一次性”的:每次调用根据实参刷新,不保存历史。2.VAR是“永久记事本”:只有它能在不同扫描周期之间保持值,初值只在实例化时有效。3.VAR_IN_OUT是“共享内存”:直接操作外部变量地址,不能传入常量,修改会实时反馈。4.FB实例化自动分配背景数据块:每个实例独立持有内部变量,相互隔离。5.状态机=VAR+枚举+CASE:不要写全局变量,一个FB内部搞定。
互动问题:
你的FB背景数据块占了多少内存?有没有算过最大实例化数量?你认为R系列200万个FB实例的限制是瓶颈还是冗余?留言区说出你的项目规模。
推荐阅读
摘要:今天深入学习静态代码分析技术,这是安全审计的核心技能。从 Python AST 模块到检测模式设计,收获满满!
发布于 202603
01-Python 环境搭建与第一个脚本
发布于 202603
【优化】Python代码优化与调试技巧
发布于 202603
KEYWORDS
MELSEC, iQ-R, IL, 三菱, 函数
如果你觉得这篇文章有帮助,请点个在看,分享给更多需要的人!
关注我,获取更多实用干货~
有问题欢迎评论区留言交流!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码