Measuring time in microseconds between pin events

MartinJR

Member
I am trying to accurately measure the time between two rising edge events on a pin, using an interrupt, so that every rising edge of a pulse train triggers the ISR. Inside the ISR I need to be able to read from a timer to capture the time since the last rising edge. I am familiar with using a timer to trigger an ISR, but can't wrap my head around how to capture a precise microsecond time from somewhere from within an ISR. Is it possible to read a GPT or QUAD timer directly to create a timestamp, for example? Thanks.
 
Another way could be to find the number of F_CPU_ACTUAL cycles (600M/sec) between calls to _isr().

Code:
void _isr () {
static uint32_t cycLast;
uint32_t cycDiff = ARM_DWT_CYCCNT - cycLast;
cycLast = ARM_DWT_CYCCNT;
// ...
}

cycLast is a local - to see it in loop() it would need to be volatile and global scope.

It would show the number of CPU cycles between calls.

If _isr() is called slower than each 7.158 seconds it will roll over.
If called too fast the volatile in loop() would update that single copy very fast.
Reading ARM_DWT_CYCCNT is done in about 3 cycles.
 
Another way could be to find the number of F_CPU_ACTUAL cycles (600M/sec) between calls to _isr().

Code:
void _isr () {
static uint32_t cycLast;
uint32_t cycDiff = ARM_DWT_CYCCNT - cycLast;
cycLast = ARM_DWT_CYCCNT;
// ...
}

cycLast is a local - to see it in loop() it would need to be volatile and global scope.

It would show the number of CPU cycles between calls.

If _isr() is called slower than each 7.158 seconds it will roll over.
If called too fast the volatile in loop() would update that single copy very fast.
Reading ARM_DWT_CYCCNT is done in about 3 cycles.

I like the simplicity of this solution and it might just do what I need with a little fine-tuning.. Thanks!
 
I like the simplicity of this solution and it might just do what I need with a little fine-tuning.. Thanks!

Good luck. Yes, it is that simple.

Not sure how or where the number is to be used - but it will be captured with good precision with the cycle counter.

Hopefully the notes are helpful to fit the use case. The code reading the ARM_DWT_CYCCNT twice will skip a few clocks - but could be read once to a stack variable and that would go away.

A single value could be saved in the _isr() until the loop reads it, or an array could be filled as a circular buffer if a few running samples are needed. Or perhaps keep a running average in one of two variables until the loop reads one and toggle to the other, ..., repeat.
 
FreqCount and FreqMeasure don't do what I need

FreqMeasure does exactly the thing you asked. The example program averages 30 readings together and converts raw data to frequency as floating point. But if you delete that averaging and conversation stuff, the raw data is indeed the number of timer clock cycles between rising edges.

Measurement is done with timer input capture, so it's not sensitive to interrupt latency (unless longer than total time between rising edges) from other libraries or USB communication. So you get less resolution than the ARM cycle counter, because the peripheral clock is only 150 MHz when the CPU clock is 600 MHz, but the direct timer input capture eliminate the possible errors due to interrupt response latency, so it's a good trade-off.
 
FreqMeasure does exactly the thing you asked. The example program averages 30 readings together and converts raw data to frequency as floating point. But if you delete that averaging and conversation stuff, the raw data is indeed the number of timer clock cycles between rising edges.

Measurement is done with timer input capture, so it's not sensitive to interrupt latency (unless longer than total time between rising edges) from other libraries or USB communication. So you get less resolution than the ARM cycle counter, because the peripheral clock is only 150 MHz when the CPU clock is 600 MHz, but the direct timer input capture eliminate the possible errors due to interrupt response latency, so it's a good trade-off.

The frequency range I am working with is, according to the literature, too high for FreqMeasure, maybe 20 kHz, otherwise I would use it. FreqCount doesn't yield fractional values of cycles per second, which I need. That's why I am pursuing direct measurement of pulse length plus an averaging approach. I'm dealing with a situation that falls unfortunately between the capablilities of the two libraries!
 
Good luck. Yes, it is that simple.

Not sure how or where the number is to be used - but it will be captured with good precision with the cycle counter.

Hopefully the notes are helpful to fit the use case. The code reading the ARM_DWT_CYCCNT twice will skip a few clocks - but could be read once to a stack variable and that would go away.

A single value could be saved in the _isr() until the loop reads it, or an array could be filled as a circular buffer if a few running samples are needed. Or perhaps keep a running average in one of two variables until the loop reads one and toggle to the other, ..., repeat.

I think I will increment volatile sum-of-the-difference and count variables and then calculate the average on demand from the main loop. I have enough pulses to disregard cases when ARM_DWT_CYCCNT overrflows. Thanks.
 
The frequency range I am working with is, according to the literature, too high for FreqMeasure, maybe 20 kHz, otherwise I would use it. FreqCount doesn't yield fractional values of cycles per second, which I need. That's why I am pursuing direct measurement of pulse length plus an averaging approach. I'm dealing with a situation that falls unfortunately between the capablilities of the two libraries!

20 kHz is easily within the range of FreqMeasure. If you need the best accuracy, use FreqMeasure. If the variability of the timer interrupt is okay, then ... okay.
 
Last edited:
The guidance on the website was written in the days of 16 MHz Teensy 2.0. With the timer clocking at 16 MHz, measuring a 10 kHz signal would give you 1600 timer counts. Or in other words, the error due to resolution would be about 0.06% (eg, if occasionally you measure 1599 or 1601 when the true result should have been 1600).

Maybe you prefer 4X higher resolution, but with the clock at 150 MHz you'll be looking so little error due to resolution that adding the uncertainty of interrupt latency really does not seem like a good trade-off.

With all frequency measurement the absolute accuracy also depends on the accuracy of the timer (or CPU) clock. I believe the crystal we use on Teensy 4.0 and 4.1 is rated to 30 ppm initial accuracy and like all crystals can vary somewhat with temperature and aging. Maybe this doesn't matter in some applications where you look for only relative change (eg, calibrate against an initial reading) but if you care about absolute accuracy, remember more resolution beyond the accuracy of the reference clock really doesn't gain you much.
 
The guidance on the website was written in the days of 16 MHz Teensy 2.0. With the timer clocking at 16 MHz, measuring a 10 kHz signal would give you 1600 timer counts. Or in other words, the error due to resolution would be about 0.06% (eg, if occasionally you measure 1599 or 1601 when the true result should have been 1600).

Maybe you prefer 4X higher resolution, but with the clock at 150 MHz you'll be looking so little error due to resolution that adding the uncertainty of interrupt latency really does not seem like a good trade-off.

With all frequency measurement the absolute accuracy also depends on the accuracy of the timer (or CPU) clock. I believe the crystal we use on Teensy 4.0 and 4.1 is rated to 30 ppm initial accuracy and like all crystals can vary somewhat with temperature and aging. Maybe this doesn't matter in some applications where you look for only relative change (eg, calibrate against an initial reading) but if you care about absolute accuracy, remember more resolution beyond the accuracy of the reference clock really doesn't gain you much.

OK... I hadn't realised that the available info. was quite so old! What frequency resolution will the library acheive with the 150 MHz clock? Point taken about crystal precision - I can generate calibration data. I've just rolled my own pulse length measurement so I'm keen to better understand what is acheivable before changing things again!
 
Back
Top