博图 SCL 编程技巧:灵活实现上升沿与下降沿检测案例分享(下)
应用案例分享:电机启停与状态记录
假设我们有一个电机控制场景:
-
按下启动按钮 (
StartButton
) 的上升沿启动电机 (MotorRun
)。 -
按下停止按钮 (
StopButton
) 的上升沿停止电机。 -
记录电机启动成功的时刻(启动命令发出后,当运行反馈
MotorFeedback
变为TRUE
的上升沿)。 -
记录电机停止完成的时刻(停止命令发出后,当运行反馈
MotorFeedback
变为FALSE
的下降沿)。
SCL 实现代码示例
VAR_INPUTStartButton: BOOL; // 瞬动按钮 (上升沿有效)StopButton: BOOL; // 瞬动按钮 (上升沿有效)MotorFeedback: BOOL; // 电机运行反馈信号ResetLog: BOOL; // 复位记录的时间戳 END_VAR
VAR_OUTPUTMotorRun: BOOL; // 电机运行命令输出StartTime: DTL; // 电机启动成功时间StopTime: DTL; // 电机停止完成时间 END_VAR VAR// 边沿检测实例StartEdge: R_TRIG; // 启动按钮上升沿StopEdge: R_TRIG; // 停止按钮上升沿FeedbackRising: R_TRIG; // 反馈信号上升沿 (用于启动完成)FeedbackFalling: F_TRIG; // 反馈信号下降沿 (用于停止完成) END_VAR
// 主执行逻辑 // 1. 检测按钮边沿 #StartEdge(CLK := #StartButton); #StopEdge(CLK := #StopButton);// 2. 控制电机启停 (启保停逻辑) #MotorRun := (#MotorRun OR #StartEdge.Q) AND NOT #StopEdge.Q;// 3. 检测电机反馈边沿 #FeedbackRising(CLK := #MotorFeedback); #FeedbackFalling(CLK := #MotorFeedback);// 4. 记录启动/停止完成时间 (使用DTL类型) IF #FeedbackRising.Q THEN#TempTime[0]:= "Date_And_Time_Long".StartTime[0]; // 读取当前本地时间#StartTime := #TempTime[0]; // 直接存储DTL类型 END_IF;IF #FeedbackFalling.Q THEN#TempTime[1] := "Date_And_Time_Long".StartTime[0];; // 读取当前本地时间#StopTime := #TempTime[1]; // 直接存储DTL类型 END_IF;// 5. 复位记录 IF #ResetLog THEN// 使用DTL类型默认值#StartTime := DTL#1970-01-01-00:00:00.000;#StopTime := DTL#1970-01-01-00:00:00.000; END_IF; OB1
![]()
![]()
![]()
启动调试
停止调试
复位调试
代码关键点解析
-
边沿检测: 使用了四个
R_TRIG
和F_TRIG
实例分别检测启动按钮、停止按钮的上升沿以及电机反馈信号的上升沿和下降沿。这清晰地分离了不同信号的检测逻辑。 -
电机控制: 使用标准的启保停逻辑 (
MotorRun := (MotorRun OR StartEdge.Q) AND NOT StopEdge.Q;
)。StartEdge.Q
和StopEdge.Q
只在对应按钮按下时的一个扫描周期内为TRUE
,确保了单次触发。 -
时间记录:
-
当检测到电机反馈信号的上升沿 (
FeedbackRising.Q = TRUE
),意味着电机实际启动成功,此时记录StartTime
。 -
当检测到电机反馈信号的下降沿 (
FeedbackFalling.Q = TRUE
),意味着电机实际停止完成,此时记录StopTime
。 -
使用
LOCAL_TIME
(返回DTL
类型) 配合DTL_TO_DT
函数获取当前时间戳 (转换为DT
类型存储)。DT
类型在 TIA Portal 中常用于时间戳记录。
-
-
复位:
ResetLog
输入允许外部复位记录的时间戳。
重要注意事项
-
存储位置:
-
对于方法 1 (
R_TRIG
/F_TRIG
),函数块的内部状态(相当于LastState
)存储在实例 DB 中。确保实例 DB 被正确分配和保持。 -
对于方法 2 (纯 SCL),
LastState
必须声明为STATIC
或位于FB
的非保持性VAR
区域(但需要确保 FB 实例 DB 保持),或者在FC
中声明为STATIC
。关键是其值必须在扫描周期之间保持。
-
-
扫描周期: 边沿检测依赖于比较当前值和上一扫描周期的值。务必在同一个块内、按正确的顺序(先比较,再更新
LastState
)执行逻辑。 -
单周期脉冲: 所有方法产生的边沿检测输出 (
Q
或Out
) 都只在一个 PLC 扫描周期内为TRUE
。如果你的动作需要维持更长的时间(如点亮一个指示灯表示“检测到过边沿”),你需要自行添加锁存逻辑(例如使用SET
指令或简单的自锁)。 -
初始化:
-
R_TRIG
/F_TRIG
块在第一次扫描或冷启动时,其内部状态通常初始化为0
。 -
在纯 SCL 方法中,务必初始化
LastState
(如LastState: BOOL := FALSE;
)。未初始化的静态变量值是不确定的!
-
-
输入信号抖动: 对于物理按钮或传感器,可能存在抖动(短时间内多次跳变)。边沿检测块会对每次跳变都产生一个脉冲。如果业务逻辑要求忽略抖动,需要在边沿检测之前添加去抖动逻辑(例如使用定时器)。
-
多任务环境: 如果边沿检测的输入信号在一个扫描周期内被多个任务或中断访问并修改,可能导致边沿检测逻辑失效或产生意外脉冲。确保信号状态在单个任务周期内是稳定的。
总结
在 TIA Portal SCL 中实现上升沿和下降沿检测,推荐优先使用系统函数块 R_TRIG
和 F_TRIG
。它们标准化、易用、封装性好,是多信号检测场景的首选。理解其背后的原理(比较当前值和上一周期值)对于调试和理解 PLC 扫描机制至关重要。
纯 SCL 代码实现适用于简单、临时的检测需求,或者作为理解底层机制的学习工具。务必注意静态变量的初始化和生命周期管理。
选择哪种方法取决于项目的复杂度、可读性要求、个人习惯以及对资源管理的考量。希望本文的案例和解释能帮助你在实际博图 SCL 项目中更得心应手地处理边沿检测问题!
进阶思考:
-
如何用 SCL 实现一个既能检测上升沿也能检测下降沿的通用边沿检测块 (
ANY_EDGE
)? -
如何在 FC (函数) 中安全地实现边沿检测(注意 FC 无状态,需通过 INOUT 参数传递
LastState
)? -
如何处理边沿检测与 PLC 任务扫描时间的关系,特别是在高速应用中?