Accuracy Issue with AVR timers

Go To Last Post
22 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello all, 

I am trying to design a frequency meter with ATMega162. I tried multiple ways to do the task but the frequency measured is much less than the actual frequency of the signal appearing on the oscilloscope. Right now i am pasting my code using Input Capture pin for sampling the signal and tested with 500Hz PWM signal. oscilloscope shows 497 Hz but my code returns 485 to 490 Hz only. I tried the same with polling and External interrupt (INT0) but all the way i get the same accuracy issue. Any idea how can i get the accurate results. adding a fixed offset doesn't seem to work as i want to make the measurement generic.

 

Pasting my AVR gcc code here, with many thanks.

 

#include<avr/io.h>
#include<avr/delay.h>
#include<avr/interrupt.h>
#include"lcd.h"
void _display(int);

int time=0;
int freq=0;

ISR(TIMER3_CAPT_vect)
{
	PORTC^=0x01;
	time=ICR3;
	TCNT3=0;
	ICR3= 0;
}
void main ()
{
DDRD=0x00;
//------LCD Port
DDRE=0xFF;
DDRA=0xFF;

_LCD_Int();

TCCR3A=0x00;
TCNT3=0x00;
TCCR3B  = (1 << ICNC3) | (1 << CS30);
ETIMSK|= (1 << TICIE3);

TCNT3=0x00;
ICR3=0;
sei();
_delay_ms(1000);

	while(1)
	{
		freq=1000000/time;
		_LCDClear();
		_display(freq);
		_delay_ms(500);

	}
}

 

This topic has a solution.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Whereas you don't want too much baggage,   a few comments would be useful.    You will follow how it works.

 

#include<avr/io.h>
#include<avr/delay.h>
#include<avr/interrupt.h>
#include"lcd.h"
void _display(int);

int time = 0;                 // should be volatile
int freq = 0;               

ISR(TIMER3_CAPT_vect)
{
    PORTC ^= 0x01;            // toggle PC0 pin.   it does not seem to be an output
    time = ICR3;              // remember the most recent capture
    TCNT3 = 0;                // this will b*gger up the current time
    ICR3 = 0;                 // even more pointless
}

void main()
{
    DDRD = 0x00;
    //------LCD Port
    DDRE = 0xFF;
    DDRA = 0xFF;

    _LCD_Int();

    TCCR3A = 0x00;            // these are the default values at reset
    TCNT3 = 0x00;
    TCCR3B = (1<<ICNC3)|(1<<CS30); // +ve edge, div1
    ETIMSK |= (1<<TICIE3);    // enable Capture IRQ

    TCNT3 = 0x00;             // reset timer for some reason
    ICR3 = 0;                 // ditto
    sei();                    // interrupts start now
    _delay_ms(1000);          // let first few captures occur.

    while (1) {
        freq = 1000000 / time; // time might get interrupted
        _LCDClear();
        _display(freq);
        _delay_ms(500);

    }
}

You might just as well start the timer running,   and never stop it.

Simply remember the previous ICP3 value.    And subtract it from the current value using uint16_t maths.

Since 'time = ICP3 - prev',   you need to make an atomic copy before you display.  e.g.

    while (1) {
        cli();
        copy = time;      //always get entire 16-bits
        sei();
        freq = 1000000 / copy;
        _LCDClear();
        _display(freq);
        _delay_ms(500);

    }

Or you can use the <atomic.h> macros.

 

Good luck.

 

David.

Last Edited: Sun. Oct 26, 2014 - 08:37 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks David,

Got your point and tried with an atomic copy but no difference on the results. Still unable to get accurate results.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>
#include "lcd.h"

void _display(int);

volatile uint16_t time = 0; // should be volatile
uint16_t freq = 0;               

ISR(TIMER3_CAPT_vect)
{
    static uint16_t old;
    PORTC ^= 0x01;            // toggle PC0 pin.   it does not seem to be an output
    time = ICR3 - old;        // calculate the most recent interval
    old = ICR3;               // remember the most recent capture
}

void main()
{
    uint16_t copy;            // temporary copy
    DDRD = 0x00;
    //------LCD Port
    DDRE = 0xFF;
    DDRA = 0xFF;

    _LCD_Int();

    TCCR3B = (1<<ICNC3)|(1<<CS30); // +ve edge, div1
    ETIMSK |= (1<<TICIE3);    // enable Capture IRQ

    sei();                    // interrupts start now
    _delay_ms(1000);          // let first few captures occur.

    while (1) {
        cli();
        copy = time;           // atomic
        sei();
        freq = 1000000 / copy; // copy does not get interrupted
        _LCDClear();
        _display(freq);
        _delay_ms(500);
    }
}

This "code editor" is horrible!   

 

Untested.    But it looks pretty straightforward.   Note that the unsigned maths mean that you get the correct time even if old or ISR have wrapped around zero.

 

David.

 

Last Edited: Sun. Oct 26, 2014 - 11:17 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sidk wrote:

Still unable to get accurate results.

 

What's your MPUs clock source?

'This forum helps those who help themselves.'

 

pragmatic  adjective dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Still unable to get accurate results.

First, tell us the AVR's clock speed, and  the source of the AVR's clock.  If you say "internal oscillator" then the uncalibrated value can be many percent off.  You listed "much less" appears to be 2% or 3%?

 

Basically, there are two "best" ways to do frequency counting with an AVR8.  Either one, properly implemented, can give results accurate to near an AVR clock cycle or two.  There have been many many threads on both methods; have you tried to search them out?  I'd wager a Google search for "atmel avr frequency meter" will uncover others.

 

A)  Use Input Capture with the signal to the ICP1 pin.  For frequencies in an appropriate range, say 1kHz to 1MHz, accuracy is to an AVR clock cycle or two.  That would be with the timer running at /1 i.e. no prescaler.  Using the timer prescaler brings the useful low-end range down to about  1Hz, and 1000 AVR clock cycle resolution/accuracy.  About 100us at 10MHz AVR clock speed, or 0.01%.

 

B)  Use timer-as-counter Tn pin.  Use the AVR's timer to count signal edges.  Then, a second timer gets that count periodically.  Common periods are 1/10 or 1/16 second.  From that, the frequency is calculated.  Use a rolling buffer with samples from the last second or so for display.  Carefully executed, there will be in the long run no lost edges and near-perfect accuracy.  This tends to work better for higher frequencies.  Max might be several MHz with fast AVR8 clock.

 

Any other methods (such as your mentioned external interrupt) may be useful, but will not be as good as A) or B).  And to you, 2% is "much".

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Unless I have lost the plot,   both the original and my untested offering use TIMER3_CAPT.     And I presume the input signal is going to the ICP3 pin (PD3).

It happens to be that INT1 lives on PD3 too.    But if you don't enable INT1,    you will get ICP3.    (I guess)

 

I don't own a mega162.    It looks a lot more attractive than the mega8515.    JTAG too!

 

It would be helpful if we had some actual figures from sidk.     Mind you,   he may not have tried the last code yet.    He could be on the other side of the world.

 

But as Lee has suggested.    You either count multiple pulses over a fixed period,   or you capture edges and calculate from a single period.

Either way,   you can get the initial result very quickly.    Then you can calculate a running average to get a better resolution.

 

Your accuracy is entirely dependent on the quality of your HF crystal.    e.g. 20-30ppm.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Its running at 1MHz internal oscillator.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Its running at 1MHz internal oscillator.

LOL LOL LOL

 

So, what about the rest of my analysis?   Why do you think that 2% or 3% is "much less", when you are using a clock source that can be -- how far from nominal according to the datasheet?

At 3V and 25C, this calibration gives a frequency within ±10% of the nominal frequency.

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ooopss, 2-3% is much better then.... but yes 2-3% is much, i need more accurate measurements. Going to introduce crystal now. Thanks theusch for pointing out, will update results here.

Thanks every one.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To summarize what others have told you, you have two sources of error.  The huge source of error is that you are using the internal oscillator.  As mentioned, that can be as much as 10% off.  Use a 35 cent crystal!  The small source of error is clearing TCNT in the ISR.  You always want to let your timer free-run, and get your time period by subtracting the previous capture value from the current capture value.  As long as the period is less than one full timer period (i.e. less than 65536 timer ticks for a 16 bit timer), that math will always be correct, even if the timer overflows during the period in question.

Last Edited: Sun. Oct 26, 2014 - 04:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks David i tried your code too but it increased the difference. i will go for increased frequency. will update here.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sidk wrote:

Thanks David i tried your code too but it increased the difference. i will go for increased frequency. will update here.

David's code is using the correct approach in using input capture to measure a period.  Of course it can't do anything about your use of the inaccurate internal clock, you'll have to change that.

 

The reason the code "increased the difference" is that it correctly measured the period, while your method of clearing TCNT in the ISR will result in an incorrect, smaller period value.  Thus the incorrect method will result in a higher reading (reciprocal of a smaller number is a bigger number).  Using the correct method, all of the error you see will be the result of your inaccurate AVR clock.  Fix that and you should be good.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello everyone,

Thanks a lot, finally i got the accurate results. Adding the crystal worked, i followed instructions to let the counter run free without clearing. thanks again. but a little confusion, if i do not clear the counter what happens when it overflows? the previous and current count values technique is perfect before the counter overflows. what happens when overflow occurs in the middle?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It does not matter when the overflow occurs.     You will still get the correct period.     (Assuming your period is <= 65535 ticks)

 

If you are measuring very long periods e.g. with multiple overflows,    you have to use a little intelligence.

 

For example,    if your ICR1 is saying ~ 65510 and TCNT1 is saying ~ 20,    you have probably overflowed since the capture.

It is like real life,   when your watch says 00.01 it probably means you have just passed midnight!

You can guess whether you have been on the p*ss for 4 hours, 28 hours, 52 hours, ...

 

OTOH,   your brain may have stopped working due to alcohol poisoning.

 

David.

Last Edited: Fri. Oct 31, 2014 - 12:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

what happens when overflow occurs in the middle?

Why not try it yourself. Suppose a 16 bit counter is at 65300 and then there are 540 ticks. That is  0xFF14 + 0x21C. If you add them you get 0x10130 but because it's 16 bit the upper 0x10000 is lost and it just holds 0x0130 (aka 304). So the sum to find out the elapsed time will be 0x0130 - 0xFF14 to get the difference between "now" (304) and "then" (65300). Now if you did that subtraction in 32 bits the result would be 0xFFFF021C but this is still 16 bits remember so the "borrow"from the 17th bit (the 0xFFFF0000 in the upper bits) does not occur and the value is simply 0x021C which just happens to be 540. So the fact that you are subtracting a "big number" from a "small number" after the overflow does not actually matter.

Last Edited: Fri. Oct 31, 2014 - 01:17 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah got that, Thanks all.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

what happens when overflow occurs in the middle?

Why not try it yourself. Suppose a 16 bit counter is at 65300 and then there are 540 ticks. That is  0xFF14 + 0x21C. If you add them you get 0x10130 but because it's 16 bit the upper 0x10000 is lost and it just holds 0x0130 (aka 304). So the sum to find out the elapsed time will be 0x0130 - 0xFF14 to get the difference between "now" (304) and "then" (65300). Now if you did that subtraction in 32 bits the result would be 0xFFFF021C but this is still 16 bits remember so the "borrow"from the 17th bit (the 0xFFFF0000 in the upper bits) does not occur and the value is simply 0x021C which just happens to be 540. So the fact that you are subtracting a "big number" from a "small number" after the overflow does not actually matter.

The day I "got" that was one of the ah-ha! days of my career.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

adding the crystal of 16MHz giving perfect timing but now causing problem in UART:( i got the perfect timing but now UART on the same controller is not working. I have updated the UBRR value to 0xCF (baud=4800) but it shows garbage on terminal. the code was working Ok with internal oscillator. Do i need to change something else too?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Got UART working, fuse bit CKDIV8 was '1', causing the system clock of 2MHz instead of 8MHz.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sidk wrote:

Got UART working, fuse bit CKDIV8 was '1', causing the system clock of 2MHz instead of 8MHz.

Is that 1MHz/8MHz or 2MHz/16MHz?  That was the obvious first thing to check, but then how could your timer values have been accurate, as you reported?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sorry its 2MHz/16MHz; yeah i was missing this point in UART calculations. While in timer calculations i didn't miss it and put system clock frequency 2MHZ.