Inaccuracy in measuring square wave frequency - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: August 08, 2017, 07:14:29 pm
content: Hi all. I was experimenting with some AVR code today to measure the frequency of a square wave input signal. But I'm getting some inaccuracy at 1kHz+ frequencies, and I'm not sure why.I'm trying to improve upon some existing code I had that uses the method of using the input square wave to externally clock one of the MCU's timers, and then gating it with a second 1Hz timer, where I simply take the first timer's counter value as the frequency (well, also some extra code to catch overflows too). But, that doesn't work very well at single-digit frequencies, so on to a different method...Decided to try using the input capture feature of a 16-bit timer and implement a reciprocal method of frequency counting. My code thus far is below:Code:volatile uint16_t frequency = 0;int main(void) {DDRB &= ~(_BV(DDB0));OCR1A = (F_CPU / 1024) - 1;TCCR1B |= _BV(ICNC1) | _BV(ICES1) | _BV(WGM12) | _BV(CS12) | _BV(CS10);TIMSK1 |= _BV(ICIE1) | _BV(OCIE1A);sei();while(1) {printf_P(PSTR("Frequency: %u Hz\n"), frequency);_delay_ms(250);}}ISR(TIMER1_CAPT_vect) {TCNT1 = 0;if(ICR1 > 0) {frequency = (F_CPU / 1024) / ICR1;} else {frequency = 0;}}ISR(TIMER1_COMPA_vect) {frequency = 0;}It seems to work very well for the situation I was trying address - namely, single-digit frequencies. However, when trying it with a 1kHz signal, the frequency value my code reports bounces back-and-forth between 976Hz and 1041Hz at random.I have verified with an oscilloscope that the input signal is actually 1kHz, so it's just my code's measurement that is inaccurate.Am I doing something wrong in my code? Is my methodology flawed?It's nottooimportant that it works accurately at 1kHz+ frequencies, as I only intend to measure a signal that will be in the range 0-200Hz, but it would be nice to make it better.

author: alm
author_rank: Super Contributor
timestamp: August 08, 2017, 08:12:16 pm
content: If F_CPU is 8 MHz, then F_CPU / 1024 is approximately 8 kHz, or 8 counts per period. So the uncertainty is +/- 1 count, or +/- 125 µS, or +/- 125 Hz. For better resolution, you would either have to increase the speed of the timer, or switch to conventional counting. An alternative might be averaging the period over multiple cycles.

author: Kleinstein
author_rank: Super Contributor
timestamp: August 08, 2017, 08:16:50 pm
content: The idea of using the input capture function is good. However the way it is implemented is not that good. There is some delay in resetting the timer to zero - the delay can vary, depending on which ISR is active at the time. Also the timer is running at a low speed and this the resolution is limited - this could become a problem at relatively high frequencies. So a first step would be running the timer faster.The better way to do the measurement would be to use the ICP function the measure the time for a start and a stop event. The periods length is than calculated from the difference in time for subsequent events. For a higher resolution, one could measure the time for a larger number of periods. Up to some 10 kHz (maybe up to about CPU clock / 20 if the code is fast) one could count the periods in software. Ideally timer1 would run at full speed.With the higher resolution one might run into the limit of the 16 Bit counter at lower frequencies. If needed one could extend the resolution of the timer in software by counting overflows. However this can get a little tricky - it is not as easy as is seems at first look. If interested a solution would be here (however with description in German).http://rn-wissen.de/wiki/index.php?title=Timer/Counter_(Avr)#Input_Capture

author: HwAoRrDk
author_rank: Super Contributor
timestamp: August 08, 2017, 08:57:25 pm
content: Quote from: alm on August 08, 2017, 08:12:16 pmIt's running on an Arduino board, with an ATmega328P @ 16MHz, so F_CPU / 1024 is actually 15.625 kHz. But if I'm following your line of thought correctly, the uncertainty is +/- 64 Hz. I note this happens to be very close to the difference between 1041 and 976, so I guess this is the cause of the inaccuracy!But, if I am understanding correctly that we're saying the MCU only has 64 timer clock cycles in which to capture the edge of the signal, why is this not enough? Seems like plenty to me.So, I need to increase the timer clock speed. Hmm, but leaving the code as-is, I can't go below clk/256, as then my OCR value would be too large for 16-bits. I shall try clk/256 and see what results I get.Incidentally, would also disabling the noise reduction feature help? As I understand it from the datasheet, enabling this means it will wait until it has a consistent input level for 4 clock cycles before capturing the counter value. I probably don't need to use it, as I will eventually be running the input signal through a low-pass RC filter first.Quote from: Kleinstein on August 08, 2017, 08:16:50 pmI had initially thought about taking the delta between the current and previous counter values, but as I intend to ultimately run this on an ATtiny with limited memory, I decided on going forward with my current solution that involves the least number of variables in memory, hoping that it'd be okay. Guess not!

author: alm
author_rank: Super Contributor
timestamp: August 08, 2017, 10:22:00 pm
content: Quote from: HwAoRrDk on August 08, 2017, 08:57:25 pmIt is quantization noise: a result of 64 represents any period between 64 * timer period and 65 * timer period. If the real period is 64.99 * timer period, you expect to see the value vary between 64 and 65 cycles with even a tiny amount of jitter. Of course any additional timing jitter will add to this. If you look at the specs of a lab frequency counter, you will see that the accuracy is quoted as +/- 1 LSD. Your counter is reading 64 +/- 1 LSD. To reduce the effect of this 1 LSD, make the number bigger.Quote from: HwAoRrDk on August 08, 2017, 08:57:25 pmIncreasing the speed was one (the most straight-forward) suggestion I gave. Averaging (determine the ICR value over multiple periods, then divide by the number of periods) was an alternative. You would have to make sure you arrange your divisions and multiplications so the integer division does not kill your accuracy (e.g. F_CPU / 1024 / PERIOD_COUNT / ICR1). I might also move the division outside the ISR to reduce latency, but obviously Kleinstein's suggestion reduces jitter even more. Just like with increasing the timer frequency, overflow may be an issue at low frequencies.Quote from: HwAoRrDk on August 08, 2017, 08:57:25 pmI doubt it. If the signal is not noisy, it will add a constant delay and should not add to jitter. If the signal is noisy, then you need the noise canceler.

author: Ian.M
author_rank: Super Contributor
timestamp: August 08, 2017, 11:45:49 pm
content: One of the few nice things about the 8 bit PICs compared to the ATmega chips is their CCP module's input capture mode. It can decimate the input signal to trigger on each 4th or 16th rising edge insted of only on each rising edge. This makes it easy to time the input clock over 4 or 16 cycles for improved accuracy at input frequencies that are slightly too high for good resolution on simple period measurement. You may want to consider adding an external software switchable prescaler . . .You need to do the maths to find the optimum transition frequency between input capture and gated timer then do input capture (with a timeout) to acquire low frequencies then if the period is too short for accuracy do a gated timer count, otherwise do the division to get the frequency from the period.

author: hamster_nz
author_rank: Super Contributor
timestamp: August 09, 2017, 02:42:54 am
content: If I was to do this, I would make an ISR just incremented a 16-bit counter.To deduce the frequency I would:* Grab the counter value (being careful if it is a 16 bit counter to avoid it being corrupted by an interrupt while grabbing the value)- grab high word,- grab low word,- grab high word again,- check that high word hasn't changed, if it has, then repeat* Wait for the gate time* Grab the counter again, once again being careful* Do the math to find the frequency out of the ISRfreq = (end_count - start_count)/(gate time)* display it

author: danadak
author_rank: Super Contributor
timestamp: August 09, 2017, 11:45:40 am
content: Ap notes on freq measurement -https://www.dropbox.com/sh/prpc74bdga7yijz/AAAeC-Dmmn5sYbWwRrl0-ZUoa?dl=0Regards, Dana.

author: Kleinstein
author_rank: Super Contributor
timestamp: August 09, 2017, 03:38:00 pm
content: The noise cancellation function is not causing any trouble with a low frequency - it just gives you the result 4 clock cycles (e.g. 250 ns) later. If the signal is reasonable clean, an RC filter is not helping it is more like possibly adding extra jitter from amplitude noise.With the low frequencies used here, using the classic counter method is not such a good solution. It will kind of work, but slow or with limited resolution.The main point would be increasing the timer clock, to reduce the quantization noise. With a very slow clock, like used so far, the way of resetting the timer is not that bad (especially if there is no other IRQ active). It is only when the delay gets larger than the pre-scaler ratio that the delay till the timer is reset would be visible. So not a problem with 1024 or 256 cycles of quantization, but it could be with a prescaler of 64 and definitely would be with a smaller divider.Even with the small Tiny's there is usually sufficient RAM to hold the old timer value - it is just 2 bytes.

author: hamster_nz
author_rank: Super Contributor
timestamp: August 15, 2017, 11:17:15 am
content: Sorry to resurrect a dead thread, but I played with an Arduino and this code... Tested fine to about 55kHz, above which it chokes.Code:static volatile unsigned int count = 0;static byte a0_val_last;static unsigned long next_millis;static unsigned last_count = 0;///////////////////////////////// Set up pin change interrupt///////////////////////////////void pciSetup(byte pin){*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin)); // enable pinPCIFR |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interruptPCICR |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group}////////////////////////////////////////////// What to do during the interrupt for A0-A7// In this case, count rising edges////////////////////////////////////////////ISR(PCINT1_vect) {byte a0_val = digitalRead(A0);if (a0_val_last == 0 && a0_val == 1)count++;a0_val_last = a0_val;}void setup(){Serial.begin(9600);pinMode(A0, INPUT); // Pin A0 is input to which a signal connecteddigitalWrite(A0, HIGH); // Configure internal pull-up resistornext_millis = millis()+1000; // Work out when to next report frequency - in a secondpciSetup(A0); // Enable pin change interrupt for pin A0}void loop() {if((long)(millis() - next_millis) >= 0 ) { // If it is time to reportunsigned this_count;// Grab the counter */cli();this_count = count;sei();Serial.println(count-last_count);last_count = this_count;next_millis+=1000; // report again after 1000 ms}}

author: niekvs
author_rank: Contributor
timestamp: August 15, 2017, 11:59:29 am
content: Quote from: HwAoRrDk on August 08, 2017, 08:57:25 pmI'm not sure you're understanding the root issue, which is that you're working with integers here, not floats. If your result varies between either 1041 Hz and 976 Hz, that obviously means your calculation is as follows:frequency = (16000000 / 1024) / 16 =976(rounded, as it's an int)or:frequency = (16000000 / 1024) / 15 =1041(rounded, as it's an int)There are your two observed values, caused by ICR1 being either 15 or 16 at the moment of the ISR. This shouldn't really have come as a surprise to you if you had just filled in the blanks, or had debugged the code. Again, this is an integer, not a float value. Now, you can go back to the datasheet, and see if you can improve the resolution.