Capturing event accurately using micros timer

Hello,

I want to capture an event that has a period of 4 seconds. It's a pulse train, I want to measure if it has drifted from 4 seconds period. I am planning to use micros to capture time based on rising edges. My question:

What crystal oscillator frequency (or what arduino board?) divides nicely for system clock? For example, I have seen some reference of 16Mhz crystal does not give a system clock of 16Mhz, rather some number that comes close but not exactly 16Mhz. This makes a difference since micros is derived by timers using the crystal as reference. 1us might not be exactly 1us...if not true, kindly explain. Thanks!

There is two types of oscillators. Some use a crystal, others a resonator.
The crystals are reasonably accurate the resonators can be off and are sensitive to surrounding temperature (the crystal as well, but to a lesser extent.
They are not sufficient accurate to be used as a clock. They will be off by seconds to even minutes per day.
Realize that a second is 1/(24*60*60) of a day, so in fact the crystals are quite accurate.
If you let it count for one day, you can derive a correction factor.
Using that you can easily get to reasonable accuracy. Realize that micros takes steps of 4. So you cannot accurately (within 1%) measure time steps smaller than 400 us.

1 Like

how accurate do you need to be?

nothing is perfect. seems a typical crystal is accurate to 100ppm

even the temperature controlled crystal (TCXO) on the femto base stations i worked on was only accurate to ~1ppm and was constantly sync'd to some external time reference

1 Like

The "micros()" function counts 4-microsecond intervals. If you want more precise timing, use the timer hardware. Particularly the Input Capture feature of Timer1 on an UNO/Nano/Mega. That measures events with the raw system clock (16 counts per microsecond).

1 Like

Doing more research about this. If I use the UNO board it has a resonator installed on the board. Meaning the PPM is going to be terrible over time. What i am doing is over a long period of time say 3 days. The timing I am capturing will be periodic example 10sec intervals. This will be sent out the USB and into an excel spreadsheet to log the value. So what I am looking for is a 1 PPM possibly or 0.0001% accuracy. Can I get this if I de-solder the resonator and solder in a 16Mhz replacement? It should not affect the Arduino config files I don't think.

No, you need a temperature compensation to get that accurracy.
An RTC is your best option.

one source that is perfectly accurate is 60Hz wall frequency. you could tap into the transformer of your power supply and use a low voltage signal thru a Schmit trigger

I settled on a Leonardo since it has a 16Mhz quartz crystal. I have searched for input capture example code. I have not had any luck with code example for capturing input rising edge to a 32bit variable adding roll over of the 16bit to zero. My timing captured will be 4sec long or so. It could vary. Any help would be appreciated.

Nick Gammon shows you how to handle input capture and the 16 bit rollover in his excellent Timers tutorial: https://gammon.com.au/timers

It is straightforward to use the high precision GPS 1PPS output as the capture event, to measure the actual frequency of the 16 MHz crystal to +/- 1 Hz. That calibration can then be used to time other events.

1 Like

Here is an example.

// This example sketch uses the Input Capture Register (Timer1)
// and MedianFilterLibrary to measureRPM given a pulse stream.
// Becsuse of the Median Filter, this should work with tooth-gap
// sensors like crank position sensors.  The missing tooth will
// be thrown out with the rest of the noise.
//
// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for
// analogWrite().

const boolean MeasureRisingEdges = true;
const float PulsesPerRevolution = 1.0 / 2.0; // One pulse every two revolutions (Spark Plug)

#include <MedianFilterLib.h>
MedianFilter<uint32_t> InterpulseFilter(5); // 5 samples

void setup()
{
  Serial.begin(115200);
  delay(200);
  while (!Serial);

  // For testing, uncomment one of these lines and connect
  // Pin 3 or Pin 5 to Pin 8
  // analogWrite(3, 64);  // 490.20 Hz  58823.53 RPM
  // analogWrite(5, 64);  // 976.56 Hz  117181.50 RPM

  noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0;

  TCCR1B |= _BV(CS10); // start Timer 1, prescale = 1
  TCCR1B |= (MeasureRisingEdges << ICES1); // Input Capture Edge Select (1=Rising, 0=Falling)

  TIFR1 |= _BV(ICF1); // clear Input Capture Flag so we don't get a bogus interrupt
  TIFR1 |= _BV(TOV1); // clear Timer Overflow so we don't get a bogus interrupt

  TIMSK1 |= _BV(ICIE1); // Enable Timer 1 Input Capture Interrupt
  TIMSK1 |= _BV(TOIE1); // Enable Timer 1 Timer Overflow Interrupt
  interrupts ();
}

volatile uint16_t Overflows = 0;

ISR(TIMER1_OVF_vect)
{
  Overflows++;  // Just count them
}

// Input Capture has triggered.
ISR(TIMER1_CAPT_vect)
{
  static uint32_t previousEdgeTime = 0;
  uint32_t thisEdgeTime;
  uint16_t overflows = Overflows;
  uint32_t interpulseTime = 0;

  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (ICR1 < 1024))
    overflows++;

  // Interrupted on Rising Edge
  thisEdgeTime = overflows; // Upper 16 bits
  thisEdgeTime = (thisEdgeTime << 16) | ICR1;

  //uint32_t pulseDuration = thisRisingEdgeTime - previousRisingEdgeTime;
  interpulseTime = thisEdgeTime - previousEdgeTime;
  previousEdgeTime = thisEdgeTime;

  // This is a good place to add 'interpulseTime' to the median filter.
  // Make sure the median filter is only ued with interrupts disabled
  InterpulseFilter.AddValue(interpulseTime);
}

float GetRPM()
{
  uint32_t medianInterpulseTime;

  noInterrupts();
  // Make sure the median filter is only ued with interrupts disabled
  medianInterpulseTime = InterpulseFilter.GetFiltered();
  interrupts();

  // Calculate RPM from inter-pulse time

  // First calculate how many pulses are happening per second,
  // Times are measured in clock ticks (16 MHz for UNO and 5V Nano)
  float pulsesPerSecond = (float)F_CPU / medianInterpulseTime;

  // Multiply by 60 for pulses per minute and
  // divide by PulsesPerRevolution to get RPM
  float RPM = pulsesPerSecond * 60.0 / PulsesPerRevolution;

  return RPM;
}

void loop()
{
  static float previousRPM = 0.0;
  const float MinChangeOfRPM = 100.0;

  float RPM = GetRPM();

  // Display RPM only if it has changed enough
  if (RPM > previousRPM + MinChangeOfRPM ||
      RPM < previousRPM - MinChangeOfRPM)
  {
    Serial.println(RPM);
    previousRPM = RPM;
  }
}
1 Like

I'll try this code out thanks. What gps module do you recommend 1PPS output? Prefer no mcu connection, just power and use PPS signal out.

I am getting the following error.

Arduino: 1.8.19 (Linux), Board: "Arduino Leonardo"

In file included from /home/nick/snap/arduino/current/Arduino/Counting_Interrupts/Counting_Interrupts.ino:14:0:
/home/nick/snap/arduino/current/Arduino/libraries/MedianFilterLib/src/MedianFilterLib.h:14:10: fatal error: arduino.h: No such file or directory
#include "arduino.h"
^~~~~~~~~~~
compilation terminated.
exit status 1
Error compiling for board Arduino Leonardo.

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

Line 14 is: #include "Arduino.h", not #include "arduino.h" so you seem to have a bad copy of the library somehow. Install it again from Library Manger.

Strange. On my Linux machine with same IDE Rev and same libraries as windows gives me an error. My Windows machine compiles just fine. I will poke around a bit, has to be something simple. Mean time I can play with the code that compiles. Thanks again!

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