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