Why inaccurate tempo?

Hi everyone,
First time posting and Arduino user!
Ok I need some advice as to why this is happening and whether what I’m trying to do is actually possible.

I can’t seem to make my Uno output an accurate tempo in the form of a flashing light. I’ve written a whole heap of code which all works fine but it requires the tempo to be dead accurate when flashing.
So after writing all this code I decided to make sure it was accurate, and it’s not. If I watch the light for a few minutes and listen to a metronome and watch an accurate flashing light on another device at the same time, it goes slightly off over time. This is with the milliseconds set exactly the same.

Then I thought it could be my code bogging it down, but it’s not.
I uploaded the BlinkWithoutDelay example and it is still out. So I modified the example to change the ports directly in order to save time and it is still out.
Then I decided to print the time when the LED is turned on, and it showed that the difference in time increased over time, with some tempos being worse than others.
So then I added some code to rectify this every time the difference was increased or decreased.
Now the time is printing perfectly (apart from before it is rectified), BUT the flashing LED is still out!!

Here’s my code:

unsigned long currentMillis = millis();
 
  if(currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED 
    // allow for innacurate times
    int actual_interval = currentMillis - previousMillis;
    if (actual_interval != interval)
    {
      if (actual_interval > interval)
      {
        int difference = actual_interval - interval;
        previousMillis = currentMillis - difference;   
      }
      if (actual_interval < interval)
      {
        int difference = actual_interval - difference;
        previousMillis = currentMillis + difference;   
      }
    }
    else
    {
      previousMillis = currentMillis;
    }
    // if the LED is off turn it on and vice-versa:
    if (ledState == HIGH)
    {
      ledState = LOW;
      PORTD = PORTD | 0b00001000;    // pin 3 on
    }
    else
    {
      ledState = HIGH;
      PORTD = PORTD & 0b11110111;    // pin 3 off
    }

    Serial.println(millis());

Don’t get me wrong, it’s fairly accurate and most people probably wouldn’t notice a change, but this light needs to stay exactly on the beat as it will be syncing up with other lights from other devices. When this happens it becomes obvious that it is out.

Has anyone else found this?
Is it possible for the atmega328 to do this accurately or should I be looking into a different controller?

actual_interval >= interva

Has anyone else found this?

Yes everybody has.
No two independent free running oscillators will stay in sync, that is physics for you.
Basically that applies to the crystal oscillators running the arduino and everything else trickles down from that.

Unless you can synchronize your your two systems with a common reference they will always drift.

I was taken to task recently for using previousMillis = currentMillis when I should have been using perviousMillis = previousMillis + interval.

If currentMillis is a tiny bit late an error will start to accumulate. If you just add the interval to previousMillis all the timing is related to the starting point.

If this doesn't make any difference in your case then the problem is probably down to variations in the oscillator.

You don't say how long you want to stay in step or what error would be acceptable. There is no such thing as no error.

...R

I was taken to task recently for using previousMillis = currentMillis when I should have been using perviousMillis = previousMillis + interval.

That is only because it does not automatically compensate for the wrap round condition that occurs every 50 days or so of continuous running. It will not affect the accuracy here.

Thanks for the info guys.

Why is it then that other devices can stay in time with an external metronome but this one can't?

Are you telling me that these other devices are just more accurate and that they will also go out of time eventually?
Are you also saying that the millis() function does not return an accurate result?

If it is not possible for 100% accuracy then my aim would be to have it stay in time for 2 hours rather than a 2 minutes. Approx

Are you telling me that these other devices are just more accurate and that they will also go out of time eventually?

I am not sure what these "other devices" are but as I said any two un-synchronized oscillators will always drift apart.

Are you also saying that the millis() function does not return an accurate result?

Of course it does not how could it? It only counts down from the processor's clock. If that is "out" then so is everything else.

You can do things to make the oscillators more accurate, such as trimming with capacitors, but then they will also drift with temperature.

Here's my code:

No it wasn't it was only a bit of your code. It is imposable to see what the rest of it is doing, you might indeed have some error, that is why we ask for all your code.

then my aim would be to have it stay in time for 2 hours rather than a 2 minutes.

You have to work out how far out the time can be and still be considered to be "in time" then you work out how close your processor's 16MHz clock as to be in order to achieve this. Then you can see if it is practical or not. A normal crystal has an accuracy of about 30ppm ( parts per million ), you start pushing that and it can get expensive.

I would suggest that a free running oscillator is not the way to keep anything in sync, you have to be a bit smarter than that.

Grumpy_Mike:

I was taken to task recently for using previousMillis = currentMillis when I should have been using perviousMillis = previousMillis + interval.

That is only because it does not automatically compensate for the wrap round condition that occurs every 50 days or so of continuous running. It will not affect the accuracy here.

As far as I can see you didn't participate in that discussion. The problem was not concerned with rollover which is handled by subtracting from millis().

The problem in that discussion centered round the fact that any particular event that is measured by millis() could be a little later than it should be as micros() (on which millis() is based) only increments every 4 usecs. Also there may be some other delay before the code actually checks that the interval has elapsed. This can lead to a cumulative drift. By defining the next step from the time when the previous step should have happened that drift is eliminated.

This may have a bearing on the OPs problem.

...R

elecopper:
watch an accurate flashing light on another device at the same time, it goes slightly off over time

Can you put some numbers to this? Is the 'other device' another Arduino running similar code, or some other device - and if so, what?

What sort of timing drift are you experiencing (for example, milliseconds of drift per elapsed second) and how small a drift would be acceptable?

The code in your original post looks horribly complicated and I couldn't guess what it does. The code below will flash pin 13 at a consistent frequency, to within the limits of the Arduino clock. That should produce drift far too small to be detected by the human eye over a period of a few minutes:

static const int LED_PIN = 13;

void setup()
{
  pinMode(LED_PIN, OUTPUT);
}

void loop()
{
  blinkLed();
}

void blinkLed()
{
	static const unsigned long BLINK_INTERVAL = 500; // duration of each blink phase
	static unsigned long lastBlinkTime = 0;
	
	if(millis() - lastBlinkTime >= BLINK_INTERVAL)
	{
		digitalWrite(LED_PIN, !digitalRead(LED_PIN));
		lastBlinkTime += BLINK_INTERVAL;
	}
}

Of course it does not how could it?

milis() - "Returns the number of milliseconds since the Arduino board began running".
Being new to this I assumed that this statement would be true and that counting in milliseconds would be accurate enough for my application.

No it wasn't it was only a bit of your code.

As I stated, I only modified the BlinkWithoutDelay example, the rest remains unchanged and is not my code.

The code in your original post looks horribly complicated and I couldn't guess what it does.

It does the same thing as the BlinkWithoutDelay example but also eliminates the drift that occurs when running the example as it stands. I ran your code and and there is no printable drift so it seems to be much better! But it still does drift visually and that is the problem I'm wanting to eliminate. It needs to 'appear' to last much longer than a few minutes.

By defining the next step from the time when the previous step should have happened that drift is eliminated.

I have done this with millis() and it still drifts visually but I will try to work with micros() as you suggest and see how it goes

Is the 'other device' another Arduino

No. Just standard metronomes and also running clicks from a PC.

What sort of timing drift are you experiencing (for example, milliseconds of drift per elapsed second) and how small a drift would be acceptable?

I haven't measured it yet and different tempos seem to produce different drifts.

You have to work out how far out the time can be and still be considered to be "in time"

This will be my next step now that I know that it can't be accurate. If I can't get it to work within my threshold I will have to rethink the hardware side of things

Thanks for the help! :slight_smile:

milis() - "Returns the number of milliseconds since the Arduino board began running".
Being new to this I assumed that this statement would be true and that counting in milliseconds would be accurate enough for my application.

It can only return the number to a given accuracy, in effect it is counting clock pulses. If you have a different frequency clock then the value returned by this very wrong.
For a clock that is nominally at the right frequency the value is nominally correct.
Even an atomic clock has it's tolerance or accuracy and two free running atomic clocks will drift over time depending on the absolute accuracy.

As I said while a crystal is accurate and stable, it is only so within limits.

Grumpy_Mike:
As I said while a crystal is accurate and stable, it is only so within limits.

Yes, but the one you wear on your wrist, you expect to be accurate to a few seconds per month, so as PeterH points out:

PeterH:
The code below will flash pin 13 at a consistent frequency, to within the limits of the Arduino clock. That should produce drift far too small to be detected by the human eye over a period of a few minutes:

It all makes sense, thanks for the explanations I appreciate it.
I'll share some rough observations I made in case anyone is interested:

At 60bpm, every minute or so the LED would become inverted. It would be off when it should be on. Then the same time later it would be back to normal.

Slower tempos took longer to reach that point,
I think it was 40bpm it would take about 3 minutes for it to become inverted.

Becoming inverted is obviously the extreme so it was noticeably out of sync before that.

I deleted the notes I made on this so I'm just going off memory.
Also it made no noticeable difference which code I implemented either.

The correct way to run some code on a regular basis is this:

unsigned long previous = millis () ;

void loop ()
{
  unsigned long now = millis () ;
  if (now - previous >= interval)
  {
    previous += interval ;   // increment the target variable directly, do _not_ set from now
    // do the action
  }
  // other stuff in loop
}

This means the target times are exact multiples of interval and any temperary
delays due to other code running will not affect the long-term behaviour.

If you need more accuracy use micros() rather than millis()

The current Unos use a ceramic oscillator which is accurate to only +/- 0.3% or so
which is inadequate for synchronising metronomes longterm - you need quartz crystal
accuracy for 0.001% accuracy ideally. There is space on the Uno board to install a
crystal in place of the ceramic oscillator.

Yes this is much better than the original BlinkWithoutDelay example, it saves all the changes post.
It still goes out of sync within minutes.
I have a quartz crystal that I will try later on and see how much of a difference that makes :slight_smile:

elecopper:
At 60bpm, every minute or so the LED would become inverted. It would be off when it should be on. Then the same time later it would be back to normal.

Sorry if you already answered this, but I couldn't find your answer. What is the 'other device' that you are comparing the Arduino LED against?

You did not post all your code which is makes it more difficult to help you. But that serial,print may be causing alot of your problems as it will block waiting for space in the Tx buffer if its called to often. Try getting rid of it.

Mark

I have compared it against my wrist watch, standalone metronomes and a PC running a metronome. They all go out of sync within a minutes.
Removing println seems to do very little.
My code is just the BlinkWithoutDelay example modified as shown. I've also tried other code posted in this thread.
I'm only listening to clicks and watching lights, so my tests are far from accurate. Still, the changes are noticeable.
So it must come down to the limits of the ardunio clock.
I will be getting my hands on a few other metronomes and click sources to check against too.

Again - if you want real help - post your code in FULL!

Mark

Ok here's the latest one I ran.

int ledState = HIGH;
unsigned long previous = 0;
long interval = 500000;

void setup()
{
  pinMode(9, OUTPUT);
}

void loop ()
{
  unsigned long now = micros();
  if (now - previous >= interval)
  {
    previous += interval;

    if (ledState == HIGH)
    {
      ledState = LOW;
      PORTB = PORTB | 0b00000010;
    }
    else
    {
      ledState = HIGH;
      PORTB = PORTB & 0b11111101;
    }
  }
}