uS Timing Measurement

I am trying to measure the time between a rising pulse that is about 1 ms apart with .1 uS precision. In theory the 16mHZ processor of the uno can achieve this precision if I can utilize each clock cycle. I intend to use Timer1 with no prescaler. I need the input capture pin to be dedicated to looking for the rise of the pulse, mark the time, and get back to looking for an additional pulse, and be able to save several pulses (lets say about 10 pulses). Then I would use some post processing to determine time between each pulse and average the time between each pulse. I think I have two problems:

  1. Looking at the construction of timer1 in input capture mode it appears that the ICP will cause the timer to transfer the marked time from TCNT1 to ICR1. I like the idea of hardware storing this information as I would not expect processing delays from the software and therefore be able to maintain 1 clock cycle precision? Getting the software to recognize that a triggering event has occurred to then go look at ICR1 seems difficult. My larger problem is how do I designate a digitalInput as the ICP to Timer1 in the first place?

  2. What is the best way to structure the software? I feel like I need to turn off interrupts and constantly look at changes to ICR1 in a while loop.

  1. I looked up the schematic and found that pin 8 is the ICP1. I believe I could establish an interrupt on this pin being high and it should not effect the hardware triggering the marking of time from TCNT1 to ICR1.

  2. With that in mind, I think I run an ISR that reads and saves ICR1 to an array. Any idea how much time an interrupt takes to trigger as well as writting a 16 bit unsigned interger?

While an interrupt triggers on the next clock cycle, their is an overhead in handling interrupts in C. This sounds like you will have to use machine code to get the accuracy you want.

In theory the 16mHZ processor of the uno can achieve this precision if I can utilize each clock cycle.

I think not.

Even so with a 16MHz Arduino you get 16 clock cycles per uS, so in 0.1uS you get only 1.6 clock cycles, not enough to do much else. You will need to use an Arduino with a much faster clock. Even so you I think you will still have to use machine code.

As long as I have a clean and crisp signal, I think that the rising edge (or falling edge) triggering is independent from software and would have no influence on the processor. The key is capturing that the rise did happen before the signal dropped back to a low state, recording the time that was marked, and be ready for the next pulse. As far as the timer is concerned, I see it marching to the beat of its own drum.

I only need a sample of the signal (10ms) every 10 seconds and I dont plan for the arduino to do a whole bunch on the side. I just want it to watch pin 8 like a hawk.

In input capture mode, you don't need to know how long the interrupt takes to set up and process the input. The value of the timer is 'clocked' into the storage register "instantly". It's obviously not actually instantaneous but it should be accurate and repeatable within 1 clock cycle.

After it's saved the value, then it raises the interrupt to tell your code to do something with the value. You may not even need to have an actual interrupt handler because your main loop can just inspect the values of the appropriate registers and make a copy of the capture register.

The Input Capture Register (and associated Input Capture Interrupt) should let you time pulses (rising edge to falling edge) or cycles (rising edge to rising edge) up to 4.1 milliseconds long with an precision of about 0.0625 microseconds.

bronco9588:
I am trying to measure the time between a rising pulse that is about 1 ms apart with .1 uS precision. In theory the 16mHZ processor of the uno can achieve this precision if I can utilize each clock cycle. I intend to use Timer1 with no prescaler. I need the input capture pin to be dedicated to looking for the rise of the pulse, mark the time, and get back to looking for an additional pulse, and be able to save several pulses (lets say about 10 pulses). Then I would use some post processing to determine time between each pulse and average the time between each pulse. I think I have two problems:

  1. Looking at the construction of timer1 in input capture mode it appears that the ICP will cause the timer to transfer the marked time from TCNT1 to ICR1. I like the idea of hardware storing this information as I would not expect processing delays from the software and therefore be able to maintain 1 clock cycle precision? Getting the software to recognize that a triggering event has occurred to then go look at ICR1 seems difficult. My larger problem is how do I designate a digitalInput as the ICP to Timer1 in the first place?

  2. What is the best way to structure the software? I feel like I need to turn off interrupts and constantly look at changes to ICR1 in a while loop.

In theory you don't have practical concerns including instructions that take more than a cycle or two.

The micros() function rounds up to the nearest 4 microseconds. On an 8 bit cpu, 32 bit values take longer not just to fetch but also to compare.

There have been long threads about timing closer than 4us and even closer than 1us but 0.1us is a bit of a fantasy as far as 16MHz AVR Arduino boards. You would have much better luck with a fast ARM or even with a 48MHz Arduino Due. The Teensy 3.1 and 3.2 will clock 96MHz that has a real chance at precision enough to do what you want, the 3.6 clocks at 180MHz which is fast enough to measure such short intervals and do something about it in real time.

If you only care about 1 measure now and then and not how fast it takes to evaluate, let the short event open a transistor gate and have that charge a small capacitor then analog read the cap. In effect you capture the event and then evaluate in leisure.

It's like measuring the velocity of a bullet by shooting a heavy thing and measuring the slower movement of that which is how ballistic pendulums have worked very accurately for centuries (they measure the height the heavy pendulum swings). Whether converting momentum or charge into what can easily be read, these ways allow measure accuracy to increase greatly.

You can quite well count clock pulses using an interrupt - read the value of the counter in the ISR.

As mentioned before, there IS software overhead. A few cycles to reset the counter and start your readings; 2-3 clock cycles to react to the interrupt, some cycles to read the counter and store it in a variable.

This software overhead should be highly predictable to no more than 1-2 cycles difference - on the ESP8266 I'm doing just this and I've gotten like 100 measurements in a row at the exact same number of clock cycles - while that's an 80 MHz clock so 12.5 nanosecond resolution.

For a bit higher resolution, get a separate ATmega chip and connect it to a 20 MHz crystal (they're rated to run up to 20 MHz at 5V) and you can get 50 ns clock ticks.

You can easily achieve this precision (measure a 1 ms pulse with a 100 ns precision) with a DUE.

Use the SysTick timer which counts down from a preloaded value to zero, reloads to its upper value and counts down again. Each tick equals to a clock cycle ( at 84MHz, this is 11.9 ns). Set the upper value in SysTick->LOAD to 168000 so that you can measure up to 2 ms.

Read the pin state with a blocking code until the pin is high (Read PIOn->PIO_PDSR & Mask), then read SysTick->VAL, Read the pin state with a blocking code until the pin is low (Read PIOn->PIO_PDSR & Mask), then read SysTick->VAL.

Subtract the two measured values of SysTick->VAL, if there is one roll over add 168000 ticks. Do that again into your Loop and average several measures.

OP wants 100 nanosecond measure. A single 16MHz tick is 62.5 ns.

I dunno, if I want to weigh grams then I want my scale accurate to less than 1/2 gram.

If the pulses are 1 millisec apart there is plenty of time between them for computation.

I wonder if the overhead from an interrupt triggering to something getting completed (such as writing a microsec value into a variable) always takes the same number of clock cycles. If so that time could be subtracted from the time that was saved in order to know when the interrupt actually happened.

...R

GoForSmoke:
OP wants 100 nanosecond measure. A single 16MHz tick is 62.5 ns.

I dunno, if I want to weigh grams then I want my scale accurate to less than 1/2 gram.

Maybe that 100 ns is already their 1/2 gram? I don't know and I'm not guessing: OP asks for 100 ns, OP gets 100 ns or better. On a 1 ms time scale it's already the fourth or fifth digit so for most purposes that should be good enough.

Robin2:
I wonder if the overhead from an interrupt triggering to something getting completed (such as writing a microsec value into a variable) always takes the same number of clock cycles. If so that time could be subtracted from the time that was saved in order to know when the interrupt actually happened.

In my experience with the ESP8266: yes, as long as I don't put a yield() in the code that waits for the interrupt to occur as otherwise it sometimes takes a lot longer :slight_smile:
I expect the ATmega to take always the same time as it's the same code path - the spec sheet explains how an interrupt is handled, for ATtiny iirc it takes 2 or 3 clock pulses depending on when exactly the interrupt occurs (as in early or late within a clock cycle), so that'd be 1/2 clock pulse off on average, ATmega I suppose will be very similar if not the same.

With the Input Capture Register the recording of the interrupt time is done in hardware, before the interrupt is signaled. The inputs are roughly one millisecond apart so the interrupt handler has about 16 thousand instruction cycles to activate, read the ICR, subtract the previous value from it, calculate a running average, and store the running average in a volatile global variable for the main program to use.

It occurs to me that a running average should allow the measurement of pulses with resolution higher than the raw clock rate. If the pulses were not an exact multiple of the sample clock the pulse edges will shift relative to the sampling clock the least significant bit of the count will vary between 0 and 1. Averaging those changes should allow a measurement with greater than clock cycle resolution.

Robin2:
If the pulses are 1 millisec apart there is plenty of time between them for computation.

I wonder if the overhead from an interrupt triggering to something getting completed (such as writing a microsec value into a variable) always takes the same number of clock cycles. If so that time could be subtracted from the time that was saved in order to know when the interrupt actually happened.

...R

I have approximately 0.1 millisec or 1600 clock cycles. My sample code will attempt to measure 6 pulses every 1600 clock cycles. I vaguely understand the interrupt on ICIE1 of TIMSK1. I think the general program construction will just wait for ICP to fire, initiate the ICIE1 interrupt to read and save ICR1, and then reset. Should I define 6 uint16_t as t0, t1, t2, etc? Is there a faster way to write to variables such as some type of C and statement?

bronco9588:
I have approximately 0.1 millisec or 1600 clock cycles. My sample code will attempt to measure 6 pulses every 1600 clock cycles.

That is not how I interpreted this, from your Original Post

I am trying to measure the time between a rising pulse that is about 1 ms apart

If you really need precise timings for 6 pulses in 100 microsecs then you need a much faster microprocessor.

...R

I think I got it. Here is the setup: I have arduino #1 that puts out six pulses by writing high and low to a digital pin and waiting a couple microseconds. I have it perform the series of pulses every 10 mS 3x. If I need another set of samples I press the reset button. BlinkSlave4.ino is arduino#1 code.

void setup() {
  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);
  pinMode(2, OUTPUT);

  // Demonstrate successful reboot with 3 blinks
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);              // wait for a second
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);              // wait for a second
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);              // wait for a second
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);              // wait for a second
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);              // wait for a second
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);              // wait for a second

event();
delay(10);
event();
delay(10);
event();
delay(10);
  
}

void event(){
  
uint8_t delayTime = 5;

PORTD |= B00000100;
PORTD &= B11111011;
delayMicroseconds(delayTime);
PORTD |= B00000100;
PORTD &= B11111011;
delayMicroseconds(delayTime);
PORTD |= B00000100;
PORTD &= B11111011;
delayMicroseconds(delayTime);
PORTD |= B00000100;
PORTD &= B11111011;
delayMicroseconds(delayTime);
PORTD |= B00000100;
PORTD &= B11111011;
delayMicroseconds(delayTime);
PORTD |= B00000100;
PORTD &= B11111011;
delayMicroseconds(delayTime);
}

void loop() {
}

It is connected to arduino #2 D8 with a pull down resistor of 100 ohms to cancel out noise. #2 is running FrequencyCounter.ino:

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

// Input: Pin D8 

volatile boolean first;
volatile boolean second;
volatile boolean third;
volatile boolean fourth;
volatile boolean fifth;
//volatile boolean sixth;
volatile boolean triggered;
volatile unsigned long Time1;
volatile unsigned long Time2;
volatile unsigned long Time3;
volatile unsigned long Time4;
volatile unsigned long Time5;
volatile unsigned long Time6;

ISR (TIMER1_CAPT_vect)
{
  // grab counter value before it changes any more
  uint16_t timer1CounterValue;
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers) is this reading 16 or 8 bits?
  
  // wait until we noticed last one
  if (triggered)
    return;

  if (first)
    {
    Time1 = timer1CounterValue;
    first = false;
    second = true;
    return;  
    }

  if (second)
    {
    Time2 = timer1CounterValue;
    second = false;
    third = true;
    return;  
    }

  if (third)
    {
    Time3 = timer1CounterValue;
    third = false;
    fourth = true;
    return;  
    }
  
  if (fourth)
    {
    Time4 = timer1CounterValue;
    fourth = false;
    fifth = true;
    return;  
    } 

  if (fifth)
    {
    Time5 = timer1CounterValue;
    fifth = false;
    return;  
    }
    
  Time6 = timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now. consider revising if using multiple sensors.
}  // end of TIMER1_CAPT_vect
  
void prepareForInterrupts ()
  {
  noInterrupts ();  // protected code
  first = true;
  second = false;
  third = false;
  fourth = false;
  fifth = false;
//  sixth = false;
  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, Consider disabling timer overflow interrupt and maintain input capture interrupt.
    TIMSK1 = bit (ICIE1);   // interrup on timer1 only on input capture 
  // start Timer 1, no prescaler
  TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select rising on D8 and no prescaler
  interrupts ();
  }  // end of prepareForInterrupts
  

void setup () 
  {
  Serial.begin(250000);
  Serial.println("Frequency Counter");
  // set up for interrupts
  prepareForInterrupts ();   
  } // end of setup

void loop () 
  {
  // wait till we have a reading
  if (!triggered)
    return;
 
  // period is elapsed time
unsigned long deltaTime1 = Time2 - Time1;
unsigned long deltaTime2 = Time3 - Time2;
unsigned long deltaTime3 = Time4 - Time3;
unsigned long deltaTime4 = Time5 - Time4;
unsigned long deltaTime5 = Time6 - Time5;
    
  Serial.print ("T1,T2,T3,T4,T5,T6: ");
  Serial.print (Time1);
  Serial.print (", ");
  Serial.print (Time2);
  Serial.print (", ");
  Serial.print (Time3);
  Serial.print (", ");
  Serial.print (Time4);
  Serial.print (", ");
  Serial.print (Time5);
  Serial.print (", ");
  Serial.print (Time6);
  Serial.println (" timestamp. ");   

  Serial.print ("Took: ");
  Serial.print (deltaTime1);
  Serial.print (", ");
  Serial.print (deltaTime2);
  Serial.print (", ");
  Serial.print (deltaTime3);
  Serial.print (", ");
  Serial.print (deltaTime4);
  Serial.print (", ");
  Serial.print (deltaTime5);
  Serial.println (" cycles. ");

    // period is elapsed time
  unsigned long elapsedTime = ( deltaTime1 + deltaTime2 + deltaTime3 + deltaTime4 + deltaTime5 ) / 5;
  // frequency is inverse of period, adjusted for clock period
  float freq = F_CPU / float (elapsedTime);  // each tick is 62.5 ns at 16 MHz
  
  Serial.print ("Took: ");
  Serial.print (elapsedTime);
  Serial.print (" counts. ");

  Serial.print ("Frequency: ");
  Serial.print (freq);
  Serial.println (" Hz. ");

  // so we can read it  
   delay (500);

  prepareForInterrupts ();   
}   // end of loop

I was able to collapse the pulses of arduino #1 such that arduino #2 would consistently read down to 62 cycles. That is pretty good! I was also able to repeat this every to 10ms. From here I see that I have two problems:

  1. If the timer overflows, I need to be able to account for this with an if else statement. This isn't too difficult. I just need to know when the timer overflows. Does a timer count from 0 to 2^16-1 or 1 to 2^16? What is the last discreet number that exists and what does it start on upon overflow?

  2. It would be nice to connect this to a square wave signal generator and observe the resolution by adjusting the frequency and observe changes on the serial monitor. Anybody have any suggestions that gets me to prove the accuracy and precision of this setup? Anybody able to do me a solid and run it on their equipment? I feel like using a different arduino is a self licking ice cream cone at this point.

FrequencyCounter.ino (3.98 KB)

BlinkSlave4.ino (1.96 KB)

  1. Is there a statistical advantage to averaging the clock pulses between pulses 1 and 3, 1 and 4, and 1 and 5? From pascals triangle, it appears that I have 20 discreet timing events with 6 measurements.

bronco9588:
I am trying to measure the time between a rising pulse that is about 1 ms apart with .1 uS precision.

The 'time between a rising pulse' isn't clear in meaning. Maybe a diagram can clear things up.

Do you mean time between rising edge of one pulse and rising edge of a second pulse?

A bit of external fast hardware - eg 74HCT series counters - would make this a lot easier. No s/w overhead.

And the arduino enviroment has a lot of stuff going on in the background - eg the millis() function robs aboiut 7uS every 1.024 mS to get it's counters in sync on a 16MHz AT328 . And ( unless you modify the source) you can't turn it off. It's in every compiled sketch whether you use millis() or not.

And don't forget that the normal frequency reference is a ceramic resonator - +/- 1% or so - so that's as good as you'll get . If you want better a crystal or other precision source is required.

Look at a 74HCT4040. A ripple counter is probably good enough for this - though 12 bits maybe not enough . If so , better 74HCT163's - 4 bits synchronous per chip. Stack 'em up!

It's not clear what signals signify the start and end of your timing period - could you make a drawing, please?

Allan