2

在开发程序计算555定时器IC产生的脉冲的频率和脉冲宽度时,通过PC并行端口到达PC。我注意到每次运行代码时都会显示不同的值,因此我开始测试循环和计时器的准确性。我已经运行了以下代码并指出它们不准确(我可能错了,如果我错了,请纠正我!):

对于计时器:

    int sec = 0;
    private void button2_Click(object sender, EventArgs e)
    {
        sec = DateTime.Now.Second;
        i = 0;
        timer1.Enabled = true;
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        if (sec == DateTime.Now.Second)
        {
            i++;
        }
        else
        {
            timer1.Enabled = false;
            MessageBox.Show(i.ToString(),"Timer Output");
        }
    }

输出: 应该相同,但是:

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

对于循环:

    private void button1_Click(object sender, EventArgs e)
    {
        i = 0;
        CheckForIllegalCrossThreadCalls = false;
        Thread t1 = new Thread(LoopTest);
        t1.Start();
    }

    void LoopTest()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        this.Cursor = Cursors.WaitCursor;
        while (true)
        {
            if (sw.ElapsedMilliseconds != 1000)
            {
                i++;
            }
            else
            {
                break;
            }
        }
        sw.Stop();
        this.Cursor = Cursors.Default;
        MessageBox.Show(i.ToString(), "Loop Output");
    }

输出: 应该相同,但是:

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

我应该怎么做才能使循环和计时器准确,有什么办法吗?或者我必须去复杂的C代码和DOS?

我认为这是在这个问题中得到错误值的基本原因:Count Parallel port input frequency - C#

4

4 回答 4

16

1)不要DateTime.Now用于性能测量,使用StopWatch.

2)“ OUTPUT: Should be same, but ..

他们为什么要这样做?您正在非 RTOS(实时操作系统)上运行托管/JIT 代码。如果操作系统愿意,您的代码可以随时被退回。是什么让你相信在这种环境中运行相同的代码 N 次应该总是在如此小的程度上产生相同的结果?

3) Windows 上的定时器的分辨率约为 15 毫秒。对于非常准确的计时,最好的选择是支持它的系统(CPU)上的 HighPerformanceTimer API。你甚至没有向我们展示计时器的时间间隔。

您在这里没有考虑许多变量,并且您的预测基于错误的假设。您甚至测量此代码多少次?您是否考虑了第一次编译所需的时间?你在发布模式下运行吗?通过VS?有很多任务在后台运行吗?我可以继续。

于 2012-07-17T21:32:57.720 回答
12

一个仔细的实现允许在大多数 Windows 平台上测量时间段,精确到几微秒。请注意以下事实:

  1. Windows 不是实时操作系统:这无关紧要!

  2. 利用进程/线程优先级:SetPriorityClass最高REALTIME_PRIORITY_CLASSSetThreadPriority最高THREAD_PRIORITY_TIME_CRITICAL. 确保有安全的代码,因为这些优先级可以在调用线程忙时锁定系统。(Process.PriorityClass 和 Thread.Priority 不太能够将优先级提高到所需的级别。)

  3. 可能通过多媒体定时器增加系统中断周期和时间更新间隔。(有各种.NET 项目封装了这些多媒体计时器功能。)

  4. 在多核系统上,核的选择也会影响精度。强制等待计时器事件的线程在处理器 0 上等待是有利的。SetThreadAffinityMask函数允许将线程绑定到特定的 cpu。(对于 .Net 应用程序,请参阅Thread.ProcessorAffinity。)

  5. 使用QueryPerformanceCounterQueryPerformanceFrequency作为高频时间测量的资源。(对于 .Net 应用程序,请参阅:创建 QueryPerfCounter 包装类

  6. 确保校准了性能计数器频率的值。由偏移和一些热漂移返回的值QueryPerformanceFrequency偏离观察值。这可能/将引入许多我们/秒的错误。请参阅Windows 时间戳项目以了解如何进行此类校准。

并且:是的,您可能需要进行一些硬编码。但是在windows平台上可以观察到微秒级的计时非常可靠。

注意:Windows 不是实时操作系统。但是 Windows 上可用的计时器非常精确。他们完全按照他们应该做的事情去做,并且以非常高的准确性去做。有很多关于 Windows 计时器及其准确性的抱怨的事实是,它们的行为很大程度上取决于底层硬件。这也是文档有很多缺陷的原因。强烈建议对硬件进行诊断,以便找到各个时间服务功能。不幸的是,这会导致任何程序都有一些额外的代码行与平台无关。

于 2012-07-18T08:44:18.523 回答
2

首先,您不知道调用 button2_click 时的当前时间有多远。因此,显示 MessageBox 时剩下多少秒基本上是随机的——这就是您所看到的。

其次,一个循环在一段时间内获得多少 CPU 周期与准确性无关。

任何给定线程获得多少周期取决于系统中发生的其他事情。如果系统决定一大堆周期需要转到另一个进程,那么您的线程将“饿死”一段时间。

也许你可以详细说明你真正想做的事情,有人可以提供一些建议。

于 2012-07-17T21:34:20.873 回答
1

Ed 的回答是正确的:Windows 上的计时器不准确。好吧,肯定不够准确,无法测量硬件生成的信号。

但是,如果我必须测量频率和脉冲宽度,我会取一百个左右的样本并取平均值。也许像这样:

private StopWatch _Sw = new StopWatch();
private List<TimeSpan> _Samples = new List<TimeSpan>();
private Timer _Timer = new Timer(TimerTick, TimeSpan.FromSeconds(1));
private const int RequiredSamples = 100;

private void StartSampling()
{
    // You can change this next line to PriorityClass.RealTime if you're careful.
    System.Diagnostics.Process.GetCurrentProcess().BasePriority 
                              = PriorityClass.High;
    _Samples.Capacity = RequiredSamples;
    _Timer.Start();
    _Sw.Start();
    Hook555Timer(On555Pulse);
}

private void On555Pulse(object sender, EventArgs e)
{
    _Sample.Add(_Sw.Elapsed);
}

private void TimerTick(object sender, EventArgs e)
{
    if (_Samples.Count > RequiredSamples)
    {
        System.Diagnostics.Process.GetCurrentProcess().BasePriority 
                                    = PriorityClass.Normal;
        _Timer.Stop();
        _Sw.Stop();
        UnHook555Timer(On555Pulse);

        // You can now use the time between each TimeSpan 
        // in _Samples to determine statistics about your timer.
        // Eg: Min / Max duration, average and median duration.
        var durations = _Samples
                .Zip(_Samples.Skip(1), 
                    (a,b) => new { First = a, Second = b } )
                .Select(pair => pair.Second.Subtract(pair.First));
        var minTime = durations.Min(ts => ts.TotalMilliseconds);
        var maxTime = durations.Max(ts => ts.TotalMilliseconds);
        var averageTime = durations.Average(ts => ts.TotalMilliseconds);
        // I don't think LINQ has a Median() aggregate out of the box.
        // Some comment about "an exercise for the reader" goes here.
        var medianTime = durations.Median(ts => ts.TotalMilliseconds);

        var frequency = _Samples.Last()
                                 .Subtract(_Samples.First())
                                      .TotalSeconds / _Samples.Count;
    }
}

(注意:用记事本编写的代码,如果不进一步修改就不可能工作)

我的精度现在由StopWatch而不是由确定Timer(请注意,在您的系统上Stopwatch可能不会比 a 更精确Timer,请检查FrequencyandIsHighResolution属性)。而且我在采样期间提高了进程的优先级,以尽量减少其他进程抢占我的进程。

但即便如此,由于 Windows 不是 RTOS,您能做的最好的事情就是采集大量样本并使用一些统计数据来获得答案的近似值。

于 2012-07-17T23:15:05.533 回答