项目实战 · 2026-05-18

用C#模拟PLC定时器?TON/TOF一次搞定,附完整源码

大家好!我是付工。

在上位机开发中,你一定遇到过这样的需求:设备启动后延迟 3 秒才执行动作,或者信号消失后输出还要保持 5 秒再断开。在 PLC 里,我们可以使用 TON 和 TOF 定时器。

如果使用Thread.Sleep或Timer 来实现,结果要么阻塞线程,要么逻辑越写越乱。

今天给大家分享一下如何结合 IEC 61131-3 标准,实现TON(接通延时) 和 TOF(断开延时)定时器。

IEC定时器

IEC 61131-3 标准定义了三种基本定时器:

原文配图 1

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

原文配图 2

在 PLC 里,定时器由扫描周期驱动——每个扫描周期判断一次输入,计算一次 ET,更新一次输出。

但在C# 上位机,并没有扫描周期的概念,我们需要用Stopwatch 实现计时,在每次读取Input 时动态计算结果。

TON实现过程

TON 的实现思路如下:

  1. 输入 IN = false → 计时停止,输出 Q = false,ET = 0

  2. IN 上升沿(false → true) → 启动计时

  3. 计时中判断(IN = true,ET < PT) → 输出 Q = false

  4. 计时到达(ET ≥ PT)→ 输出 Q = true,计时停止

  5. 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相反:

  1. IN = true → 输出 Q = true,ET = 0

  2. IN 下降沿(true → false) → 启动计时

3 计时中(IN = false,ET < PT) → 输出 Q = true

  1. 计时到达(ET ≥ PT) → 输出 Q = false,计时停止

  2. 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;
}
本文为原文文字快照,已收录 2 张技术配图;二维码与推广图未收录。
标签:#C# #PLC定时器