AVR, measuring frequency of incoming signal without interrupts

I'm asking this in regards to atmega328p or other AVR based (ATTiny84 or 85...) arduino programmable devices...

Is there a way to measure the period, or otherwise measure the frequency over multiple waveforms, of an incoming square/rectangular wave without using interrupts?

I'm aware of the ICP pin (D8 on an Uno pinout, PB0 in AVR speak), which can be used for an input capture. As far as I can tell though, it can only log the value which Timer1 holds (Timer1 being set to continuously count up then overflow and restart) at the time of an arriving rising (if you set the ICES1 bit for rising) edge. This means if you want to measure the time between subsequent rising edges you must copy to a variable of your own, either via polling ICF1 of the TIFR1 register or via an interrupt, and you have to get this copying done before the next incoming rising edge. Then you can subtract the time stored in that variable from the time collected in ICR1 next time around. The time dependent part here being that you MUST get the initial copying out of ICR1, and the clearing of the ICF1 flag, done before the next rising edge arrives.

Is there any way to get the ICP input capture mechanism to log the times of two subsequent rising (actually falling would be just as good, as long as both edges are the same type) edges "in hardware" so you can then simply look at the subtracted difference when you want to use the timing figure further on in your code?

I'm looking at a situation where I have some sections in use in the code which last for up to 0.5ms where interrupts get disabled, I also have some interrupts, not related to the incoming waveform I'm wanting to time, which can last up to 0.5ms. The waveform I'm wanting to time is in the range of 10KHz to 30KHz, and I want to time it to an accuracy around +/-0.5% (for testing I am not concerned about the true accuracy of the arduino's 16MHz resonator, for a finalised design I'm doing a custom PCB and I'll be using a crystal oscillator with an accuracy better than this). If the incoming signal is at 20KHz then the timing method gives about 700 as the count between rising edges, and I want an accuracy to within 3 or so counts. The code below is very suitable for this, I'm not sure whether code based on powering a timer from arriving edges on the PD4 or PD5 (T0 in T1 in) pins could match up to this resolution without having to happen over a great many (several hundred) waveforms indeed.

To be clear, I don't need to catch any and every incoming wave and time it, I just need, perhaps several hundred times a second, to get a timing (or frequency) for one (or a few) of the incoming waveforms.

The attached code parts work perfectly well were it not for the presence of interrupts, and do have a bit of a "defence" in them against getting incorrect readings if a long interrupt does happen... but as far as I can tell if long interrupts were to happen in a high intensity burst then it is possible that all attempts by the main code loop to measure the incoming signal timing could be corrupted for a long period, depdnding how long the events causing the long interrupts went on for.


#include <util/atomic.h>

uint16_t StartTime=0;
uint16_t EndTime=0;

uint16_t TemporaryTimeDelta=0;
uint16_t TimeDelta=0;

volatile uint8_t NoLongInterruptsHaveOccured=1; //gets set to zero by any long interrupts (code for a long interrupt not shwon here)
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);    
  Serial.println("Logs the times taken\n\n");   
  Serial.println("Count, Nanoseconds, Freq");
  pinMode(8,INPUT);
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
    TCCR1A=0;
    TCCR1B=bit(ICNC1) | bit(ICES1) | bit(CS10);//noise cancelling on, activate on rising edge, prescaler of 1 to start clock running
    
  }
}


void loop() {

  //from here to there... if any interrupts occur they may mess up our reading, but we only update TimeDelta with a non-messed-up value, a value taken when we find no interrupts have happened
  NoLongInterruptsHaveOccured=1;
  while (!(TIFR1 & (1 << ICF1))){
    // Wait for rising edge
  }
  StartTime = ICR1;
  TIFR1 |= bit(ICF1); // Clear the flag
  while (!(TIFR1 & (1 << ICF1))){
    // Wait for next rising edge
  }
  EndTime = ICR1;
  //from there to here...
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
     if(NoLongInterruptsHaveOccured){
      TemporaryTimeDelta=EndTime-StartTime;
    }
  }

  TIFR1 |= bit(ICF1); // Clear the flag
  
  
  if(TemporaryTimeDelta>200 && TemporaryTimeDelta<2000){//change these if the signal being measured is no longer the 10 to 30KHz (approx) which is in use for this
    TimeDelta=TemporaryTimeDelta;
  }
  Serial.print(TimeDelta);
  float nanosec = 1e9*(1.0 / F_CPU * 1) *TimeDelta; //the 1 multipled by F_CPU is due to prescaler of 1
  float Freq=1e9/nanosec;
  Serial.print(",");
  Serial.print(nanosec,1);
  Serial.print(",");
  Serial.println(Freq,2);
  delayMicroseconds(2000);
}

Is there an alternative method of measuring I should be considering, perhaps involving using incoming wavform edges to clock the Timer1 counter rather than the ICP pin... which can work without needing interrupts?

Or is there indeed any way to get the subtraction of ICP timings done "in hardware", the atmega328p and attiny84 datasheets don't mention one, but maybe there's a possiblity I haven't appreciated. I don't strictly need to have delay() able to work, so could always take over all three counters of an atmega328p (or all two counters of a tiny84/tiny85), but still can't see how use of further timers can put me in a position where I can subtract the times of different arriving edges without needing an interrupt.

P.S. I'll agree than 0.5ms is a very long time for an interrupt, but there is good reson for them, so I'm looking for ways to do this time/frequency measurement operation without needing interrupts, rather than ways to make the long interrupt shorter so that it won't create a need for a timing method which eschews use of interrupts.

Thank You

Right.

IMO other interrupts have no effect if the starting ICR is read and restarted in an ISR or (faster) an atomic block. Then the flag prevents delayed capture of the following ending edge.

Timer1 can be set to operate in capture mode. When the input on D8 goes high, the current value of the timer count is immediately saved to the capture register, followed by executing the related interrupt at ISR(TIMER1_CAPT_vect). But the interrupt is only used to transfer the captured value into a circular buffer, or however you want to deal with it, so you have the full time before the next rising edge to do that. But again, the capture takes place immediately, before the ISR is executed. So it is very accurate regardless of any other delays in the system.

You may also want to use the overflow interrupt if you need to convert Timer1 to a 24 or 32-bit timer.

have a look at the ESP32 Pulse Counter (PCNT) - have used it counting pulses up to 10MHz

~30 usec

  • couldn't you capture a timestamp
  • then monitor a pin for a change in state (~15 usec), increment a count and exit after cnt > #
  • capture a timestamp and compute/print the avg period (t1 - t0) / (cnt/2)

presumably easier to count change in state rather than check for rising/falling edge

You would have to somehow keep track of how many times the input rising edge has occurred, along with the number of times Timer1 has overflowed, while you were away doing other stuff with interrupts disabled. It may be possible to feed the output of Timer1 into the input of Timer0, which would then give you a 24-bit counter, and by adjusting the prescaler for Timer1, you could make sure Timer0 did not overflow in 0.5ms. But then you still have the issue of how many rising edges have occurred. It may be possible to set Timer2 to run on an external clock, so maybe that could be used to count the rising edges.

you can just count edge, not bother with rising/falling detection, capturing a timestamp before/after and dividing the time delta by cnt/2