Time measurement using Arduino.

Welcome all. I would like to get some advice on how to measure time accurately. Picture attached shows two square impulses please note one in green is now corrected previously it was at -5V and rising to 5V and I believe it wouldn’t be healthy for my inputs. So, My Arduino generates yellow pulse exciting system and when certain threshold is achieved green pulse appears. My interest is the time between both rising edges. I and new to Arduino hence not sure how to approach it as after I generated single input pulse (yellow) my Arduino has no other tasks apart from precise time measurement. What would be best method to count time between both raising edges?

What kind of accuracy (in uSec) you are looking for?

Thank you for your reply. About 1 us would be accurate enough anything above it is even better.

If we are talking about Atmega328 based arduino (Uno, Nano) the best resolution you can achieve without external HW is around 10uS.

Thank you for your reply could I ask how was it calculated? From what I gathered “digitalread” takes 50 clock cycles to complete therefore time needed would be equal to (1/16Mhz) * 50 so about 3.13 us. Also, I read about hardware timers that run as parallel process could I use time instead as stopper?

Yes you can run a timer at 16MHz which gives you resolution around 60 nSec. But you need to start and stop it. So you need an ISR which will start and stop that timer by two external events . Even very short C code for that will require about 10uSec or so. If you write highly optimized assembly code you probably may squeeze it to required 1uSec.

From what I gathered "digitalread" takes 50 clock cycles

Don't use digitalRead(), use direct port access to read the pin, in one clock cycle.

Timer1 has an input capture function that allows you to time external input events to within about 2 clock cycles (one each for start and stop). See this tutorial, especially Reply #12, which shows you how to implement a 32 bit, 62 ns timer.

You do not need an ISR to start and stop the timer, but the input capture event interrupt is very useful to read out the timestamp.

jremington:
You do not need an ISR to start and stop the timer, but the input capture event interrupt is very useful to read out the timestamp.

Could you briefly explain me how to measure time between two pulses coming from 2 different lines without using an interrupt (and with no additional HW)?

Thank you all for reply. This morning I was thinking about this problem and would like to as opinion. As Jr Remington stated “Don't use digitalRead(),” I understand limitation and time consumed by it. However, my system (amplifier, filter, detector) causes bout 50 us delay which I am going to compensate in my calculations. Therefore if Don't use digitalRead() is fairly repetable in terms how much time it gets I could add this to system delay. Now big question is Don't use digitalRead() a function that takes approximately constant time each time?

Could you briefly explain me how to measure time between two pulses coming from 2 different lines without using an interrupt (and with no additional HW)?

Use the input capture pin to trigger the capture, as described in the processor data sheet.

If two different input lines are to be monitored by the input capture pin, some method of logically ORing the signals is required. Two diodes and a resistor may suffice.

digitalRead() a function that takes approximately constant time each time?

Yes, and you could compensate for the delay. However, if you are polling a pin, there must be a loop involved, which will inject random delays that you cannot compensate.

The input capture function of Timer1 is intended for exactly your application, so use it.

alesam:
If we are talking about Atmega328 based arduino (Uno, Nano) the best resolution you can achieve without external HW is around 10uS.

No, you can get to 50 ns resolution on a 16 MHz Arduino. You will have to use interrupts, though.

Thank you all for contribution. I am currently working through tutorial that Jremington suggested.
Here I have some issues firstly was trying to use “digitalwirefast.h” however is missing was trying to find it in online libraries. No success.
I also tested one of programs posted to measure time using interruption however I get strange results. Code I implemented is attached below and two pictures attached represent print out and triggering waveform itself.

// Robot race timer
// Author: Nick Gammon
// Date: 1st February 2012

unsigned long lastTriggerTime;
volatile unsigned long triggerTime;
volatile boolean triggered;

void isr () 
{
  // wait until we noticed last one
  if (triggered)
    return;

  triggerTime = micros ();
  triggered = true;
}  // end of isr

void setup() 
{
  digitalWrite (2, HIGH);  // pull-up
  attachInterrupt(digitalPinToInterrupt (2), isr, RISING);   
  Serial.begin (115200);
  Serial.println ("Started timing ...");  
}  // end of setup

void loop() 
{
  if (!triggered)
    return;
  
  unsigned long elapsed = triggerTime - lastTriggerTime;
  
  if (elapsed < 1000000L)
    {
    triggered = false;
    return;  // ignore if less than a second
    }
    
  lastTriggerTime = triggerTime;
  triggered = false;  // re-arm for next time
  
  Serial.print ("Took: ");
  Serial.print (elapsed);
  Serial.print (" microseconds. ");
  
  unsigned long minutes, seconds, ms;
  
  minutes = elapsed / (1000000L * 60);
  elapsed -= minutes * (1000000L * 60);
  
  seconds = elapsed / 1000000L;
  elapsed -= seconds * 1000000L;
  
  ms = elapsed / 1000;
  elapsed -= ms * 1000;
  
  Serial.print (minutes);
  Serial.print ("m ");
  Serial.print (seconds);
  Serial.print ("s ");
  Serial.print (ms);
  Serial.println ("ms.");
}  // end of loop

You appear to be getting the expected results.

  if (elapsed < 1000000L)
    {
    triggered = false;
    return;  // ignore if less than a second
    }

Jremington I am bit confused here I would expect 1000ms between raising edges as per waveform.

Results are perfectly within expectation.
You may have a 1 kHz input, you only accept "elapsed" being 1,000,000 µs or higher.

1 kHz = 1000 Hz = 1 ms per cycle

I would expect 1000ms between raising edges as per waveform.

And that is what you are getting. What is causing the confusion?

Sorry it was typing error value I expected would be 1000 micro seconds = 1 milli second. This is period of interrupt signal provided as per oscilloscope screen shoot.
However, values printed via serial communication vary.

So, you still don't understand that the program rejects all intervals less than 1 second?

Jremington it took some time for penny to drop. Following you advice and checking tutorials I customised one of uploaded solution to measure time. It is performing well. Since I need to measure it 4 times for 4 signals, I am using analogue multiplexer. This also means I need to make the measurement 4 times. My initial though was to create function and call it 4 times using switch function. Here I run into some trouble therefore I went for switch solution coping code 4 times. So without case function it works fine with case function I get redeclaration of ‘long unsigned int elapsedTime’. Any suggestion what could be the cause?

// Frequency timer using input capture unit
// Author: Nick Gammon
// Date: 31 August 2013

// Input: Pin D8 

volatile boolean first;
volatile boolean triggered;
volatile unsigned long overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;

// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect) 
{
  overflowCount++;
}  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
  {
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;
  
  // if just missed an overflow
  if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 0x7FFF)
    overflowCopy++;
  
  // wait until we noticed last one
  if (triggered)
    return;

  if (first)
    {
    startTime = (overflowCopy << 16) + timer1CounterValue;
    first = false;
    return;  
    }
    
  finishTime = (overflowCopy << 16) + timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now
  }  // end of TIMER1_CAPT_vect
  
void prepareForInterrupts ()
  {
  noInterrupts ();  // protected code
  first = true;
  triggered = false;  // re-arm for next time
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  
  TIFR1 = bit (ICF1) | bit (TOV1);  // clear flags so we don't get a bogus interrupt
  TCNT1 = 0;          // Counter to zero
  overflowCount = 0;  // Therefore no overflows yet
  
  // Timer 1 - counts clock pulses
  TIMSK1 = bit (TOIE1) | bit (ICIE1);   // interrupt on Timer 1 overflow and input capture
  // start Timer 1, no prescaler
  TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select (rising on D8)
  interrupts ();
  }  // end of prepareForInterrupts
  
#include <LiquidCrystal.h>
int i, n=1;   // number of pulses.
int ctrl=1;
float timeEW, timeWE, timeNS, timeSN;
const int rs = 12, en = 11, d4 = 10, d5 = 7, d6 = 6, d7 = 5;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

void setup () 
  {
  // set up lcd screen
  lcd.begin(16, 2);
  lcd.setCursor(0, 0); // top left
  lcd.print("time Counter");
  // set up serial communication
  Serial.begin(115200);       
  Serial.println("Frequency Counter");
  // set up for interrupts
  prepareForInterrupts ();   
  } // end of setup

void loop () 
  {
  switch(ctrl){
  case 1: //  EW
  // wait till we have a reading
  if (!triggered)
    return;
  // period is elapsed time
  unsigned long elapsedTime = finishTime - startTime;
  // frequency is inverse of period, adjusted for clock period
  float freq = F_CPU / float (elapsedTime);  // each tick is 62.5 ns at 16 MHz
  // time is number of ticks multiplied by 62.5 ns
  float time = elapsedTime * 62.5;
  time = timeEW;
  Serial.print ("Took: ");
  Serial.print (elapsedTime);
  Serial.print (" counts. ");
  Serial.print ("Frequency: ");
  Serial.print (freq);
  Serial.println (" Hz. ");
  lcd.setCursor(0, 1); // bottom left
  lcd.print("                ");  // clear screen
  lcd.setCursor(0, 1); // bottom left
  lcd.print(time);
  lcd.print(" ns");
  // so we can read it  
  delay (1000);
  prepareForInterrupts ();
  case 2: //  WE
  // wait till we have a reading
  if (!triggered)
    return;
  // period is elapsed time
  unsigned long elapsedTime = finishTime - startTime;
  // frequency is inverse of period, adjusted for clock period
  float freq = F_CPU / float (elapsedTime);  // each tick is 62.5 ns at 16 MHz
  // time is number of ticks multiplied by 62.5 ns
  float time = elapsedTime * 62.5;
  time = timeWE;
  Serial.print ("Took: ");
  Serial.print (elapsedTime);
  Serial.print (" counts. ");
  Serial.print ("Frequency: ");
  Serial.print (freq);
  Serial.println (" Hz. ");
  lcd.setCursor(0, 1); // bottom left
  lcd.print("                ");  // clear screen
  lcd.setCursor(0, 1); // bottom left
  lcd.print(time);
  lcd.print(" ns");
  // so we can read it  
  delay (1000);
  prepareForInterrupts ();
  case 3: //  NS
  // wait till we have a reading
  if (!triggered)
    return;
  // period is elapsed time
  unsigned long elapsedTime = finishTime - startTime;
  // frequency is inverse of period, adjusted for clock period
  float freq = F_CPU / float (elapsedTime);  // each tick is 62.5 ns at 16 MHz
  // time is number of ticks multiplied by 62.5 ns
  float time = elapsedTime * 62.5;
  time = timeNS;
  Serial.print ("Took: ");
  Serial.print (elapsedTime);
  Serial.print (" counts. ");
  Serial.print ("Frequency: ");
  Serial.print (freq);
  Serial.println (" Hz. ");
  lcd.setCursor(0, 1); // bottom left
  lcd.print("                ");  // clear screen
  lcd.setCursor(0, 1); // bottom left
  lcd.print(time);
  lcd.print(" ns");
  // so we can read it  
  delay (1000);
  prepareForInterrupts ();
  case 4: //  SW
  // wait till we have a reading
  if (!triggered)
    return;
  // period is elapsed time
  unsigned long elapsedTime = finishTime - startTime;
  // frequency is inverse of period, adjusted for clock period
  float freq = F_CPU / float (elapsedTime);  // each tick is 62.5 ns at 16 MHz
  // time is number of ticks multiplied by 62.5 ns
  float time = elapsedTime * 62.5;
  time = timeSW;
  Serial.print ("Took: ");
  Serial.print (elapsedTime);
  Serial.print (" counts. ");
  Serial.print ("Frequency: ");
  Serial.print (freq);
  Serial.println (" Hz. ");
  lcd.setCursor(0, 1); // bottom left
  lcd.print("                ");  // clear screen
  lcd.setCursor(0, 1); // bottom left
  lcd.print(time);
  lcd.print(" ns");
  // so we can read it  
  delay (1000);
  prepareForInterrupts ();
  }
}   // end of loop

There's a second declaration of the variable with that name. Exactly what the compiler says. Remove the duplicate.