Running code every x seconds, no matter how long the loop runs

I'm trying to make the LED blink to the beat of a specific song. The song has exactly 125 bpm.
The code I wrote seems to work first, but the longer it works, the greater the time difference between flashing LEDs and the start of the next hit. It seems that the LED is blinking slightly too slowly.

I think this is because lastBlink is partly dependent on the blinking that happened before to stay in sync, instead of using one static seed value to sync with ...

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 } } 
+5
source share
7 answers

Add a value to lastBlink , rather than reread it, as getTickCount may miss more than the exact hits you want to wait.

 lastblink+=1000/(bpm/60); 
+3
source

Waiting in standby mode is bad, it does not cause any problems, and in most OSs this will lead to the punishment of your process - the OS will notice that it uses a lot of processor time and dynamically reduces priority so that other, less greedy programs receive the first bugs during the processor. It is much better to sleep before the appointed time.

The trick is to dynamically calculate the amount of time before bedtime until the next time it starts to blink based on the current time of the system time. (A simple delay for a certain period of time means that you inevitably drift, since each iteration of your loop takes a non-zero and somewhat indefinite time to complete).

Sample code (tested on MacOS / X, it may also compile under Linux, but can be adapted for any OS with some changes):

 #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; } } 
+3
source

I think that the drift problem can be rooted in the use of relative time delays if you sleep for a fixed duration and not sleep until an absolute point in time. The problem is that threads do not always wake up right on time due to scheduling issues.

Something like this solution might work for you:

 // 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
source

If your problem comes down to synchronization rather than delay, I would suggest measuring time from a given start, and not from the last blink.

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

Since you did not specify C ++ 98/03, I assume at least C ++ 11, and thus <chrono> is available. This is consistent with Galik's answer so far . However, I would configure it to more accurately use the capabilities of the <chrono> transformation and without the need to manually enter conversion factors, with the exception of the description of “beats / minutes” or actually in this answer, the opposite: “minutes / bits” ,.

 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); } 

The first thing to do is to indicate the duration of the strike, which is "minutes / 125". This is what mpb does. I used minutes::period as a stand for 60 , just to improve readability and reduce the number of magic numbers.

Assuming C ++ 14, I can give flashDuration real units (milliseconds). In C ++ 11, this should be written using this more detailed syntax:

 constexpr auto flashDuration = milliseconds{10}; 

And then a loop: this is very similar to the Galik answer design, but here I only increase the time to start blinking once per iteration, and each time, more precisely 60/125 seconds.

time_point to a specified time_point , unlike a specific duration , ensures that there is no accumulated rounding over time. And, working in units that accurately describe your desired duration interval, there is also no rounding error in terms of calculating the start time of the next interval.

No traffic in milliseconds needed. And there is no need to calculate how long you need to delay. Only the need to symbolically calculate the start time of each iteration.

Um ...

Sorry for choosing Galik's answer , which, in my opinion, is the second best answer next to mine, but it shows an error that my answer not only does not have but is intended to prevent. I did not notice this until I dug a calculator into it, and it is thin enough that testing can skip it.

In response to Galik :

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

And the total time iteration takes is on_wait + off_wait , which is 440ms , almost imperceptibly close to total_wait ( 480ms ), which makes debugging very difficult.

In contrast, my answer increases ready (beginBlink) only once and exactly 480ms .

My answer is more plausible for the simple reason that it delegates more of its computations to the <chrono> library. And in this particular case, this probability has paid off.

Avoid manual conversions. Instead, let the <chrono> library do them for you. Manual conversions result in an error.

+1
source

You must calculate the time spent on the process and subtract it by the flashDuration value.

0
source

The most obvious problem is that you lose accuracy when splitting bpm / 60. This always gives an integer (2) instead of 2.08333333 ...

Calling getTickCount () twice can also lead to some drift.

0
source

Source: https://habr.com/ru/post/1243261/


All Articles