Measurements of periodtime more inaccurate when frequency gets lower

Hello

I'm using a UNO to measure the period time between two rising edges. I'm using micros() to get a "timestamp" to calculate elapsed time. Then converting it to ms with 3 decimal points

But when I'm testing I get some strange results, when the frequency of the input signal gets lower the reading gets more inaccurate. Also the calculated time is faster then the frequency of the signal.

Test Results:

Frequency Calculatedtime(ms)
200Hz 4,992-4,996
100Hz 9,984-9,988
50Hz 19,968-19,972
25Hz 39,940-39,944
10Hz 99,860-99,866

The code:

#include <BigCrystal.h>
#include <BigFont.h>

#include <LiquidCrystal.h>

/************************************
    Title:
    Created:  2018-04-03
    Author:   Martin Augustsson
    ----------------------------

 ************************************/


/************************************
     Pin mapping
 ************************************/

const byte interruptPin = 2;
const byte resetBtnPin = 11;
const byte ledPin = 12;

const int rs = 8, en = 9, d4 = 4, d5 = 5, d6 = 6, d7 = 7;

/************************************
   Global variables
 ************************************/
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
BigCrystal bigCrystal(&lcd);

unsigned long elapsedTime = 0;
volatile unsigned long timeMicros = 0;
volatile bool newPulse = false;
unsigned long time1 = 0;
unsigned long time2 = 0;

int ResetBtnState = 0;

int counter = 0;                                                                // Count how many pulses we have recived
bool hasPrintedResult = false;                                                  // Keep track if we printed the result (avoids multiple writes to LCD)
bool isReady = false;
bool printedReady = false;

float distanceCM = 2.52;                                                        // Distance traveled between pulses in centimeters
float speedKPH = 0;                                                             // Speed in kilometers per hour
float speedMPH = 0;                                                             // Speed in miles per hour


/************************************
   Setup
   Runs once at startup
 ************************************/

void setup() {
  Serial.begin(9600);

  pinMode(interruptPin, INPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(resetBtnPin, INPUT);

  bigCrystal.begin(20, 4);

  //attachInterrupt to right pin and function
  attachInterrupt(digitalPinToInterrupt(interruptPin), ISR1, RISING);

  isReady = true;
}


/************************************
   Main loop
   Runs until powered off
 ***********************************/

void loop() {

  ResetBtnState = digitalRead(resetBtnPin);

  // Show that box is ready, LED? Text?
  if ( counter == 0 && isReady) {
    digitalWrite(ledPin, HIGH);
    if (!printedReady) {
      lcd.print("Ready!");
      printedReady = true;
    }
  } else {
    digitalWrite(ledPin, LOW);
  }

  //Reset for a new reading
  if (ResetBtnState == HIGH) {
    resetValues();
    lcd.clear();
  }

  // If there is a new pulse, store timestamp and update counter and pulse flag
  if (newPulse) {
    if (counter == 0) {
      time1 = timeMicros;
    } else if (counter == 1) {
      time2 = timeMicros;
    }
    counter++;
    newPulse = false;
    hasPrintedResult = false;
  }

  // Wait until we got two pulses to calculate durnation
  if ( (counter != 0) && (counter == 2) && !hasPrintedResult) {
    elapsedTime = time2 - time1;
    printTime(elapsedTime);
    // calculateSpeed();                                                      // Not used right now, period time most important
    hasPrintedResult = true;
  }
}


/************************************
   Functions
 ***********************************/

// Interrupt Service Routine, get timestamp and signal that we got a new pulse
void ISR1() {
  timeMicros = micros();
  newPulse = true;
}

// Prints out the time in microseconds to the LCD
void printTime(unsigned long x) {
  lcd.clear();
  float ms = float(x) / 1000;
  char charBuff[20];

  dtostrf(ms, 5, 3, charBuff);
  Serial.println(x);
  Serial.println(charBuff);
  int i = 0;
  byte w = 0;
  bool secondRow = false;

  while (charBuff[i] != '\0') {

    if ( 20 < (w + bigCrystal.widthBig(charBuff[i])) ) {
      secondRow = true;
      w = 0;
    }

    if (secondRow) {
      bigCrystal.writeBig(charBuff[i], w, 2);
    } else {
      bigCrystal.writeBig(charBuff[i], w, 0);
    }
    w += bigCrystal.widthBig(charBuff[i]);
    i++;
  }
}

// Reset Values for a new reading
void resetValues() {
  counter = 0;
  printedReady = false;
  hasPrintedResult = false;
  isReady = true;
  newPulse = false;
}

As stated on the reference page micros() - Arduino Reference micros() has a resolution of 4 microseconds. So there is no point expecting accuracy better than that.

And I'd say that if anything the accuracy is better for slower frequencies. At 200Hz it's reading 0.9984 of the expected value. At 10Hz it's reading 0.9986 of the expected value.

Steve

The clock of the Arduino is not perfectly accurate, that's one error.

Your signal is also probably not perfectly accurate, that's another error.

Together they add up indeed - to a whopping 0.15%! Do you really expect even better accuracy than this on anything but carefully calibrated lab equipment?

slipstick:
As stated on the reference page micros() - Arduino Reference micros() has a resolution of 4 microseconds. So there is no point expecting accuracy better than that.

And I'd say that if anything the accuracy is better for slower frequencies. At 200Hz it's reading 0.9984 of the expected value. At 10Hz it's reading 0.9986 of the expected value.

Steve

I know that the micros() has a resolution of 4 microseconds. So I was happy with the results at 200Hz, but after som testing I was curious why the time differs "more" when the frequency is lower.

Maybe I was naive that the result should be around 10us "wrong" when doing two micros() that have a resolution of 4 microseconds. For example I was expecting a result of ~99.990 - 100 ms at 10Hz.

wvmarle:
The clock of the Arduino is not perfectly accurate, that's one error.

Your signal is also probably not perfectly accurate, that's another error.

Together they add up indeed - to a whopping 0.15%! Do you really expect even better accuracy than this on anything but carefully calibrated lab equipment?

It's within the requirements, just wondering why. (See above)

In errors there are several factors.
First is the resolution: usually an error of 1 sensor unit - e.g. in a signal measured by the ADC, when the signal is in the middle of two values, say in between 325 and 326, you can get either value. In your case that's an error of up to 4 us, as that's the resolution.
Then there's a proportional error, normally given as percentage. This proportion stays roughly the same over the scale, and in your case is caused mainly by timing errors: the combined errors of the Arduino clock and the frequency generator.
Sampling error. You take distinct samples, the resolution of which even if using an interrupt is limited by the speed of the processor. Every incoming signal has to wait for the next clock pulse for the interrupt to be triggered. That's 1/16th us, but it can by chance mean a difference of 4 us if the same clock pulse causes the microseconds timer to advance.
Then there's noise. Completely random additions to the signal caused by random events and EMI from sources ranging from nearby equipment to solar winds and meteorites. A frequency generator's output is also not perfectly regular. Wires are not connected perfectly.
Related, but more predictable are temperature effects. Both the Arduino clock and the pulse generator will be affected by the temperature of the room, causing both to run slightly faster or slower as temperature changes.
So there you go. Lots of sources of errors, some predictable, others not. Some that can be compensated for, some that can be suppressed, but not all. Some may be negligible (e.g. the temperature effects as long as you stay indoors in a room with normal, constant temperature). A 0.15% error with this level of equipment is about the best you can expect.

wvmarle:
The clock of the Arduino is not perfectly accurate, that's one error.

Your signal is also probably not perfectly accurate, that's another error.

Together they add up indeed - to a whopping 0.15%! Do you really expect even better accuracy than this on anything but carefully calibrated lab equipment?

The issue is that the Uno doesn't use a crystal for its clock, it uses a ceramic resonator, which are very
poor timekeepers. Standard crystals give +/-30ppm or so accuracy or better, not the +/-5000ppm of a
resonator.

5000 ppm = 0.5% making OPs 0.15% look exceptionally good.

Fair chance that the wave generator also uses resonators, so that could be another +/-0.5% of error.

Why do you think your precision gets worse?

(5-4.992)/5 == (100-99.86)/100