无论循环内执行多长时间,每x秒运行一次代码

时间:2016-02-17 21:23:13

标签: c++ loops

我正试图让某个LED闪烁到某首歌的节拍。这首歌正好是125 bpm 我写的代码似乎首先工作,但运行的时间越长,LED闪烁和下一个节拍之间的时间差就越大。 LED似乎眨了一下太慢了。

我认为这是因为lastBlink取决于之前发生的眨眼以保持同步,而不是使用一个静态初始值来同步...

unsigned int bpm = 125;
int flashDuration = 10;
unsigned int lastBlink = 0;
for(;;) {
    if (getTickCount() >= lastBlink+1000/(bpm/60)) {
        lastBlink = getTickCount();
        printf("Blink!\r\n");
        RS232_SendByte(cport_nr, 4); //LED ON
        delay(flashDuration);
        RS232_SendByte(cport_nr, 0); //LED OFF
    }
}

7 个答案:

答案 0 :(得分:3)

lastBlink添加值,而不是重新读取它,因为getTickCount可能跳过了超过想要等待的确切节拍的数量。

lastblink+=1000/(bpm/60);

答案 1 :(得分:3)

忙碌等待很糟糕,它无缘无故地驱动CPU,并且在大多数操作系统下它会导致你的进程受到惩罚 - 操作系统会注意到它占用了大量的CPU时间和动态降低其优先级,以便其他不那么贪婪的程序在CPU时间获得第一个dib。在指定的时间之前睡觉要好得多。

诀窍是根据当前的系统时钟时间动态计算睡眠时间,直到下一次闪烁。 (简单地延迟一段固定的时间意味着你将不可避免地漂移,因为你的循环的每次迭代都需要一个非零且有点不确定的时间来执行)。

示例代码(在MacOS / X下测试,可能也在Linux下编译,但可以适用于任何有一些更改的操作系统):

#include <stdio.h>
#include <unistd.h>
#include <sys/times.h>

// unit conversion code, just to make the conversion more obvious and self-documenting
static unsigned long long SecondsToMillis(unsigned long secs) {return secs*1000;}
static unsigned long long MillisToMicros(unsigned long ms)    {return ms*1000;}
static unsigned long long NanosToMillis(unsigned long nanos)  {return nanos/1000000;}

// Returns the current absolute time, in milliseconds, based on the appropriate high-resolution clock
static unsigned long long getCurrentTimeMillis()
{
#if defined(USE_POSIX_MONOTONIC_CLOCK)
   // Nicer New-style version using clock_gettime() and the monotonic clock
   struct timespec ts;
   return (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) ? (SecondsToMillis(ts.tv_sec)+NanosToMillis(ts.tv_nsec)) : 0;
#  else
   // old-school POSIX version using times()
   static clock_t _ticksPerSecond = 0;
   if (_ticksPerSecond <= 0) _ticksPerSecond = sysconf(_SC_CLK_TCK);

   struct tms junk; clock_t newTicks = (clock_t) times(&junk);
   return (_ticksPerSecond > 0) ? (SecondsToMillis((unsigned long long)newTicks)/_ticksPerSecond) : 0;
#endif
}

int main(int, char **)
{
   const unsigned int bpm = 125;
   const unsigned int flashDurationMillis = 10;
   const unsigned int millisBetweenBlinks = SecondsToMillis(60)/bpm;
   printf("Milliseconds between blinks:  %u\n", millisBetweenBlinks);

   unsigned long long nextBlinkTimeMillis = getCurrentTimeMillis();
   for(;;) {
       long long millisToSleepFor = nextBlinkTimeMillis - getCurrentTimeMillis();
       if (millisToSleepFor > 0) usleep(MillisToMicros(millisToSleepFor));

       printf("Blink!\r\n");
       //RS232_SendByte(cport_nr, 4); //LED ON
       usleep(MillisToMicros(flashDurationMillis));
       //RS232_SendByte(cport_nr, 0); //LED OFF
       nextBlinkTimeMillis += millisBetweenBlinks;
   }
}

答案 2 :(得分:3)

我认为漂移问题可能源于您使用相对时间延迟,通过将暂停一段固定的持续时间而不是睡眠直到一个绝对时间时间点。问题是由于调度问题,线程并不总是按时准确唤醒。

像这个解决方案可能适合你:

// for readability
using clock = std::chrono::steady_clock;

unsigned int bpm = 125;
int flashDuration = 10;

// time for entire cycle
clock::duration total_wait = std::chrono::milliseconds(1000 * 60 / bpm);

// time for LED off part of cycle
clock::duration off_wait = std::chrono::milliseconds(1000 - flashDuration);

// time for LED on part of cycle
clock::duration on_wait = total_wait - off_wait;

// when is next change ready?
clock::time_point ready = clock::now();

for(;;)
{
    // wait for time to turn light on
    std::this_thread::sleep_until(ready);

    RS232_SendByte(cport_nr, 4); // LED ON

    // reset timer for off
    ready += on_wait;

    // wait for time to turn light off
    std::this_thread::sleep_until(ready);

    RS232_SendByte(cport_nr, 0); // LED OFF

    // reset timer for on
    ready += off_wait;
}

答案 3 :(得分:2)

如果你的问题不同步而不是延迟,我会建议从给定的开始而不是从最后一次闪烁开始测量时间。

start = now()
blinks = 0
period = 60 / bpm
while true
    if 0 < ((now() - start) - blinks * period)
        ledon()
        sleep(blinklengh)
        ledoff()
        blinks++

答案 4 :(得分:1)

由于你没有指定C ++ 98/03,我假设至少是C ++ 11,因此<chrono>可用。到目前为止,这与Galik's answer一致。但是我会设置它以便更精确地使用<chrono>的转换能力,而不必手动输入转换因子,除了描述“节拍/分钟”,或者实际上在这个答案中,倒数:“分钟/节拍“。

using namespace std;
using namespace std::chrono;
using mpb = duration<int, ratio_divide<minutes::period, ratio<125>>>;
constexpr auto flashDuration = 10ms;
auto beginBlink = steady_clock::now() + mpb{0};
while (true)
{
    RS232_SendByte(cport_nr, 4); //LED ON
    this_thread::sleep_until(beginBlink + flashDuration);
    RS232_SendByte(cport_nr, 0); //LED OFF
    beginBlink += mpb{1};
    this_thread::sleep_until(beginBlink);
}

要做的第一件事是指定节拍的持续时间,即“分钟/ 125”。这就是mpb的作用。我使用minutes::period作为60的替代品,只是为了提高可读性并减少幻数的数量。

假设C ++ 14,我可以给flashDuration个实数单位(毫秒)。在C ++ 11中,需要使用这种更详细的语法拼写:

constexpr auto flashDuration = milliseconds{10};

然后是循环:这在设计上与Galik's answer非常相似,但在这里我只增加每次迭代开始闪烁一次的时间,每次都精确地为60/125秒。

通过延迟到指定的time_point而不是特定的duration,可以确保随着时间的推移没有舍入累积。通过精确描述所需持续时间间隔的单位工作,在计算下一个时间间隔的开始时间方面也没有出现舍入错误。

无需以毫秒为单位的流量。而且无需计算需要延迟的时间。只需要符号计算每次迭代的开始时间。

<强>嗯...

很抱歉选择Galik's answer,我认为这是我旁边的第二个最好的答案,但它出现了一个错误,我的答案不仅没有,而且旨在防止。直到我用计算器挖到它之前我才注意到它,并且它很微妙,测试可能会错过它。

Galik's answer中:

total_wait =  480ms;  // this is exactly correct
off_wait   =  990ms;  // likely a design flaw
on_wait    = -510ms;  // certainly a mistake

迭代所花费的总时间为on_wait + off_wait 440ms,几乎不知不觉地靠近total_wait480ms),使得调试非常具有挑战性。

相比之下,我的答案只会增加ready (beginBlink)一次,而且恰好480ms

我的答案更可能是正确的,原因很简单,因为它将更多的计算委托给<chrono>库。在这种特殊情况下,这种可能性得到了回报。

  

避免手动转换。而是让<chrono>库为您完成它们。手动转换会引发错误。

答案 5 :(得分:0)

您应该计算在流程上花费的时间并将其减去flashDuration值。

答案 6 :(得分:0)

最明显的问题是,当你将bpm / 60分开时,你会失去精确度。这总是产生一个整数(2)而不是2.08333333 ......

两次调用getTickCount()也可能导致一些漂移。