Arduino timing issue

hi community,
I am testing the precision of timing for both Uno and Mega board. I found the timing is unpredictable. after it goes beyond 25 samples, the timing became inaccurate. I am wondering if there is a hardware issue or something else. Please find the attached testing code and its output. The code basically print the time every 1ms.
Thank you in advance.


bool state=0;
int sampling=1; //1ms
unsigned long startTime;
int n=1;
void setup() {
    // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);

}


void loop() {

read_serial();
if (state==1 && n<=100) {
Serial.println(millis()-startTime);
delay(sampling);
n=n+1;  
}
}


void read_serial(){  
if (Serial.available()>0) {
char var=Serial.read();

if (var=='e') {//stop serial print
state=0;
}
else if (var=='s') {//start serial print
state=1;
n=1;
Serial.print("started");
startTime=millis();
}
}
}

Output (time in 'ms' relative to the startTime):
0

1

2

3

5

6

7

8

9

10

11

12

13

14

15

16

18

20

21

22

23

24

25

28

32

36

40

44

48

52

56

60

65

69

73

77

You are filling the serial transmit buffer, after which print() becomes a blocking function, waiting for space in the buffer before returning. At 9600 baud, it takes approximately 1mS to send each character. Change the Serial baud rate to 115200 and you will see different results.

Also be aware that occasionally the millis() count will increment by 2 instead of 1. This is caused by the counter interrupt used by millis() being 1.024mS instead of exactly 1mS, requiring a periodic adjustment.

3 Likes

Hello mouse123

The Arduino hardware isn´t a timekeeper.

Arduino millis() uses a shortcut to make 250ms fit 8 bits.
Arduino millis math when the lower 8 bits is involved is +/-1ms or to cut to the conclusion of one big thread ---

If close timing matters, use micros()! They good for over 71 minute intervals.

Take from millis that the high 3 bytes do count 250ms intervals.. a decimal value and even fraction of one second at the same time, very useful!

Instead of subtracting start ms from end ms to see if 1 second has passed, I can watch bit 10 that changes every second and detect the change from last read.

1 Like

Two things to consider, It is not on a crystal so you will get timing errors from that. Also background interring(s) are occurring which will also change timing.

1 Like

You Probably did not think of the Serial.print() function create Buffer int TX so it may block your code

schalte Timestamp ein und probiere das:

void setup() {
  Serial.begin(115200);
}
void loop() {
  static unsigned long startTime = 0;
  static uint8_t n = 0;
  if (millis() - startTime >= 1000) {
    Serial.println(n++);
    startTime += 1000;
  }
}

1 Like

You're kind-of on the right track there, but your explanation is flawed.
The creation of a Tx buffer does not block, but if you try to write to the buffer when it is full, then that will block

Thank you all!
The baud rate indeed affects. The discussion is very helpful!

A while back, when I was first learning Arduino and working with an UNO, I also tested the timing precision. I found that I could accurately measure up to 8 MHz (50% duty) signals if they originate from the same MCU (UNO) but could only measure signals up to 3.2 MHz (as noted in AVR205) when originating from an external source.

//
//  FILE:   PulseMeasure.ino (Ver 03)
//  AUTHOR: dlloyd
//  DATE:   2016-aug-4
//
//  LINK:   http://forum.arduino.cc/index.php?topic=413133.0
//
//  Measures/Calculates Frequency, Period, Width and Duty.
//  For self testing, connect pin 3 (PWM output) to pin 8 (capture input)
//
//  Input Frequency:    62,500 Hz   to   8,000,000 Hz
//  Input Period:        0.125 us   to      16.000 us
//  Input Width:          62.5 ns   to      15.937 us
//  Input Duty:          0.391 %    to      99.609 %
//

byte printHeader = 1;
word hzControl, widthControl;
word data[100], periodCount, widthCount;

float period, width, duty, frequency;

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  pwmSweep(0);  // 0 = normal print, 1 = diagnostic print
  while (1);
}

void pwmSweep(byte printMode) {
  unsigned long startTime = millis();
  word testCount = 0;
  for (hzControl = 1; hzControl <= 255; hzControl++) {
    for (widthControl = 0; widthControl <= (hzControl - 1); widthControl++) {
      pwmBegin(hzControl, widthControl);
      delayMicroseconds(20);
      pulseRead();
      pulseCalculate();
      pulsePrint(printMode);
      testCount++;
    }
  }
  Serial.print("\nCompleted ");
  Serial.print(testCount);
  Serial.print(" tests, calculations and printing in ");
  Serial.print(float(millis() - startTime) / 1000, 3);
  Serial.println(" seconds.");
  Serial.println();
}

void pwmBegin(byte ocra, byte ocrb) {
  TCCR2A = 0;                               // TC2 Control Register A
  TCCR2B = 0;                               // TC2 Control Register B
  TIMSK2 = 0;                               // TC2 Interrupt Mask Register
  TIFR2 = 0;                                // TC2 Interrupt Flag Register
  TCCR2A |= (1 << COM2B1) | (1 << WGM21) | (1 << WGM20);  // OC2B cleared/set on match when up/down counting, fast PWM
  TCCR2B |= (1 << WGM22) | (1 << CS20);     // no clock prescaler for maximum PWM frequency
  OCR2A = ocra;                             // TOP overflow value (Hz)
  OCR2B = ocrb;                             // PWM Width (duty)
  pinMode(3, OUTPUT);
}

void pulseRead() {
  TCCR1A = 0;                               // normal operation mode
  TCCR1B = 0;                               // stop timer clock (no clock source)
  TCNT1  = 0;                               // clear counter
  TIFR1 = bit (ICF1) | bit (TOV1);          // clear flags
  TIMSK1 = bit (ICIE1);                     // interrupt on input capture
  TCCR1B =  bit (CS10) | bit (ICES1);       // start clock with no prescaler, rising edge on pin D8

  noInterrupts();
  for (int i = 1; i <= 99; i += 2) {        // read captured values
    data[i - 1] = ICR1;                     // get two successive readings
    data[i] = ICR1;                         // get two successive readings
    if (i == 49) TCCR1B &=  ~bit (ICES1);   // capture on falling edge (pin D8)
    __builtin_avr_delay_cycles (1);         // to prevent reading in synch with input
  }
  TIMSK1 = 0;
  interrupts();
}

void pulseCalculate() {

  byte highHz = 1;
  byte zeroCount = 0;

  word dif, prevDif = 0;
  for (int i = 1; i <= 99; i++) {
    dif = data[i] - data[i - 1];
    if (i > 1) data[i - 1] = prevDif;
    if (i < 50 && dif == 0) zeroCount++;  // check first half of array for no change in readings
    prevDif = dif;
  }
  if (zeroCount > 15) highHz = 0;

  if (highHz) {  // high pulse rate at input
    sort(data, 50);  // for high input rates, sort first half of the diff array

    word minDiff = 0xFFFF;
    word secondNonZeroSortedDiff = data[zeroCount + 2];
    word diffDiff = 0, previousDiffDiff = 0;

    for (int i = 3; i <= 46; i++) {

      // Get difference of differences of captured readings.
      diffDiff = data[i] - data[i - 1];

      // Normal condition
      if (diffDiff > 0) {
        periodCount = diffDiff;

        // If the change in results is split between 2 readings, use both
        if (previousDiffDiff > 0) periodCount = previousDiffDiff + diffDiff;

        // If no duplicate readings (very high Hz)
        if (zeroCount == 0) {
          if (diffDiff < minDiff) minDiff = diffDiff; // use lowest non-zero difference
          if (previousDiffDiff > 0) minDiff = previousDiffDiff + diffDiff; // if split reading, use the sum
          periodCount = minDiff;
        }
      }
      // when multiple readings can be taken within within 1 period, just use the second non-zero value
      if (zeroCount > 5)  periodCount = secondNonZeroSortedDiff;
      previousDiffDiff = diffDiff;
    }

  } else {  // low pulse rate at input
    byte diffCount = 0;
    for (int i = 1; i <= 49; i++) {  // all rising to rising edge readings
      if (data[i] > 0) diffCount++;
      if (diffCount == 2) {  // second non-zero change in reading
        periodCount = data[i];
        diffCount++;
      }
    }
  }

  period = periodCount * 0.0625;
  frequency = 1000 / period;

  byte testDone = 0;
  for (int i = 50; i <= 99; i++) {  // all falling to falling edge readings
    if ((data[i] > 0) && !testDone) {

      word widthRead = data[i];

      for (int i = 0; i < 15; i++) { // subtract out all periods to get width
        if (widthRead >= periodCount) widthRead -= periodCount;
      }
      if (widthRead) {
        widthCount = widthRead;
        testDone = 1;
      }
    }
  }
  width = widthCount * 0.0625;
  duty = (width / period) * 100;
}

void pulsePrint(byte printMode) {
  if (printHeader) {
    Serial.print("\n\nOCR\tCount\tWidth us\tPeriod us\tDuty %\t\tFreq kHz\n");
    printHeader = 0;
  }
  Serial.print(hzControl);
  Serial.print(",");
  Serial.print(widthControl);
  Serial.print("\t");
  Serial.print(periodCount);
  Serial.print(",");
  Serial.print(widthCount);
  Serial.print("\t");
  Serial.print(width, 3);
  Serial.print("\t\t");
  Serial.print(period, 3);
  Serial.print("\t\t");
  Serial.print(duty, 3);
  Serial.print("\t\t");
  Serial.print(frequency, 3);
  Serial.println();
  if (printMode) {
    for (int i = 1; i <= 99; i++) {
      Serial.print(i);
      Serial.print("\t");
      Serial.println(data[i]);
    }
    Serial.println();
  }
}

void sort(word * ar, word n)
{
  word i, j;
  word gap;
  word swapped = 1;
  word temp;

  gap = n;
  while (gap > 1 || swapped == 1)
  {
    if (gap > 1)
    {
      gap = gap * 10 / 13;
      if (gap == 9 || gap == 10) gap = 11;
    }
    swapped = 0;
    for (i = 0, j = gap; j < n; i++, j++)
    {
      if (ar[i] > ar[j])
      {
        temp = ar[i];
        ar[i] = ar[j];
        ar[j] = temp;
        swapped = 1;
      }
    }
  }
}

ISR (TIMER1_CAPT_vect)
{
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.