用C#模拟PLC定时器?TON/TOF一次搞定,附完整源码
大家好!我是付工。
在上位机开发中,你一定遇到过这样的需求:设备启动后延迟 3 秒才执行动作,或者信号消失后输出还要保持 5 秒再断开。在 PLC 里,我们可以使用 TON 和 TOF 定时器。
如果使用Thread.Sleep或Timer 来实现,结果要么阻塞线程,要么逻辑越写越乱。
今天给大家分享一下如何结合 IEC 61131-3 标准,实现TON(接通延时) 和 TOF(断开延时)定时器。
IEC定时器
IEC 61131-3 标准定义了三种基本定时器:

本文重点实现 TON 和 TOF,做过PLC的都知道,定时器有四个核心要素:

在 PLC 里,定时器由扫描周期驱动——每个扫描周期判断一次输入,计算一次 ET,更新一次输出。
但在C# 上位机,并没有扫描周期的概念,我们需要用Stopwatch 实现计时,在每次读取Input 时动态计算结果。
TON实现过程
TON 的实现思路如下:
-
输入 IN = false → 计时停止,输出 Q = false,ET = 0
-
IN 上升沿(false → true) → 启动计时
-
计时中判断(IN = true,ET < PT) → 输出 Q = false
-
计时到达(ET ≥ PT)→ 输出 Q = true,计时停止
-
IN 下降沿(true → false) → 停止计时,重置 ET = 0
TON定时器类完整代码如下:
using System.Diagnostics;
public class IEC_Timer_TON
{
private readonly object _lock = new object();
private readonly Stopwatch _sw = new Stopwatch();
public IEC_Timer_TON(long PT)
{
this.PT = PT;
}
public long PT { get; set; }
private bool _input;
public bool Input
{
get { lock (_lock) { return _input; } }
set
{
lock (_lock)
{
if (_input != value)
{
_input = value;
if (_input)
_sw.Restart();
else
{
_sw.Stop();
_sw.Reset();
}
}
}
}
}
public bool Output
{
get
{
lock (_lock)
{
if (!_input) return false;
if (_sw.ElapsedMilliseconds >= PT)
{
_sw.Stop();
return true;
}
return false;
}
}
}
public long ET
{
get { lock (_lock) { return _sw.ElapsedMilliseconds; } }
}
public bool IsRunning
{
get { lock (_lock) { return _sw.IsRunning; } }
}
}
TOF实现过程
TOF的实现思路和TON相反:
-
IN = true → 输出 Q = true,ET = 0
-
IN 下降沿(true → false) → 启动计时
3 计时中(IN = false,ET < PT) → 输出 Q = true
-
计时到达(ET ≥ PT) → 输出 Q = false,计时停止
-
IN 重新变为 true → 停止计时,ET = 0
TOF定时器类完整代码如下:
using System.Diagnostics;
public class IEC_Timer_TOF
{
private readonly object _lock = new object();
private readonly Stopwatch _sw = new Stopwatch();
public IEC_Timer_TOF(long PT)
{
this.PT = PT;
}
/// <summary>
/// 设定时间(毫秒)
/// </summary>
public long PT { get; set; }
private bool _input;
/// <summary>
/// 输入条件
/// </summary>
public bool Input
{
get { lock (_lock) { return _input; } }
set
{
lock (_lock)
{
if (_input != value)
{
_input = value;
if (_input)
{
// 上升沿:停止计时并重置
_sw.Stop();
_sw.Reset();
}
else
{
// 下降沿:启动计时
_sw.Start();
}
}
}
}
}
/// <summary>
/// 定时器输出:输入为 true 时立即输出 true;输入断开后延时 PT 才变为 false
/// </summary>
public bool Output
{
get
{
lock (_lock)
{
// 输入为 true,输出立即为 true
if (_input) return true;
// 输入为 false,计时未到 PT,输出仍为 true
if (_sw.ElapsedMilliseconds < PT) return true;
// 计时已到,输出变为 false
return false;
}
}
}
/// <summary>
/// 当前已延时的时间(毫秒)
/// </summary>
public long ET
{
get
{
lock (_lock)
{
if (_input) return 0;
return _sw.ElapsedMilliseconds;
}
}
}
/// <summary>
/// 是否正在断开延时中
/// </summary>
public bool IsRunning
{
get { lock (_lock) { return _sw.IsRunning; } }
}
}
使用示例
场景一:使用TON实现设备延时启动
// 启动按钮按下后,延迟3秒才启动电机
var startDelay = new IEC_Timer_TON(3000);
startDelay.Input = startButton.IsPressed; // 启动按钮按下
if (startDelay.Output)
{
motor.Start(); // 3秒后自动启动
}
场景二:使用TOF实现设备延时关闭
// 停止按钮按下后,电机继续运行5秒再停止
var stopDelay = new IEC_Timer_TOF(5000);
stopDelay.Input = !stopButton.IsPressed; // 停止按钮未按下时为true
if (stopDelay.Output)
{
motor.KeepRunning(); // 保持运行
}
else
{
motor.Stop(); // 5秒后自动停止
}
使用时注意:
1、多个功能尽量不要重复使用同一个实例
2、需要在主循环中持续给Input赋值
3、如果涉及较多定时器,可以封装一个专门的TimeManager来管理
public class TimerManager
{
private readonly Dictionary<string, IEC_Timer_TON> _tonTimers = new();
private readonly Dictionary<string, IEC_Timer_TOF> _tofTimers = new();
public IEC_Timer_TON CreateTON(string name, long pt)
{
var timer = new IEC_Timer_TON(pt);
_tonTimers[name] = timer;
return timer;
}
public IEC_Timer_TOF CreateTOF(string name, long pt)
{
var timer = new IEC_Timer_TOF(pt);
_tofTimers[name] = timer;
return timer;
}
public IEC_Timer_TON GetTON(string name)
=> _tonTimers.TryGetValue(name, out var t) ? t : null;
public IEC_Timer_TOF GetTOF(string name)
=> _tofTimers.TryGetValue(name, out var t) ? t : null;
}