I am using an ATMEGA328p to measure the frequency of a 50% duty cycle square wave in the range 2khz to 15khz. The square wave is being generated by a good quality Function Generator and I can see on the scope that the output is stable.
For the purpose of raising this question I have simplified my code to
#define SERIAL_OUTPUT
#define led 12
volatile boolean FREQint = false;
volatile unsigned int FREQcount = 0;
volatile unsigned int FREQovfcount = 0;
volatile unsigned int temp_TCNT2 = 0;
volatile unsigned int temp_FREQovfcount = 0;
int FREQprescaler = 1;
int FREQdivider[6] = {
0, 1, 8, 64, 256, 1024
};
#define FREQ_input_pin 8 // PCINT0
//////////////////////////////////////////////////
ISR(TIMER2_OVF_vect) {
FREQovfcount++;
//digitalWrite(led, !digitalRead(led));
}
//////////////////////////////////////
ISR(PCINT0_vect)
{
if ((PINB & 0x01) == 0) { // falling edge
//digitalWrite(led, !digitalRead(led));
// take quick snapshot
temp_TCNT2 = TCNT2;
TCNT2 = 0; // reset timer
temp_FREQovfcount = FREQovfcount;
FREQovfcount = 0; // clear overflow counter
// if just missed an overflow
if (TIFR2 & bit (TOV2)) {
TIFR2 = bit (TOV2); //clear pending interrupts
if (temp_TCNT2 <= 40) {
temp_FREQovfcount++;
//digitalWrite(led, !digitalRead(led));
}
}
FREQcount = (temp_TCNT2 + temp_FREQovfcount * 256);
//digitalWrite(led, !digitalRead(led));
FREQint = true;
}
}
//////////////////////////////////////////////////
void setup() {
#ifdef SERIAL_OUTPUT
Serial.begin(115200);
while (!Serial) {
;
}
#endif
pinMode(led, OUTPUT);
cli();//stop interrupts while setting up the timers
TIMSK2 = 0; // turn off Timer
TCCR2A = 0x00; // normal mode
TCNT2 = 0; // clear timer
TCCR2B = FREQprescaler;
pinMode(FREQ_input_pin, INPUT);
// set interrupt on change
PCICR |= 0b00000001; // turn on port B
PCMSK0 |= 0b00000001; // PCINT8 pin D8
TIMSK2 = bit(TOIE2);// enabled global and timer overflow interrupt;
sei();//allow interrupts
}
//////////////////////////////////////////////////////////////////////////////
void loop()
{
#ifdef SERIAL_OUTPUT
if (FREQint) {
byte oldSREG = SREG; // remember if interrupts are on or off
cli(); // turn interrupts off
unsigned int tempcount = FREQcount;
SREG = oldSREG; // turn interrupts back on
Serial.print(" Freq ");
Serial.print(16000000.0 / FREQdivider[FREQprescaler] / tempcount);
Serial.print(" Hz");
Serial.print(" Count ");
Serial.print(tempcount);
//Serial.print(" TCNT2 ");
//Serial.print(temp_TCNT2);
//Serial.print(" OVF ");
//Serial.print(temp_FREQovfcount);
Serial.println();
FREQint = false;
}
#endif
}
This code is similar to a number of examples on the net eg www.gammon.com.au/timers
My issue is that the FREQcount value varies much more than I expected. The extent of the variation is also sensitive to the frequency of the square wave being measured.
For example
Freq 9750.15 Hz Count 1641
Freq 9621.17 Hz Count 1663
Freq 9632.75 Hz Count 1661
Freq 9632.75 Hz Count 1661
Freq 9227.22 Hz Count 1734
Freq 10302.64 Hz Count 1553
Freq 9638.55 Hz Count 1660
Freq 9732.36 Hz Count 1644
Freq 9638.55 Hz Count 1660
Freq 9632.75 Hz Count 1661
Freq 9638.55 Hz Count 1660
Freq 9638.55 Hz Count 1660
Freq 11228.07 Hz Count 1425
Freq 9744.21 Hz Count 1642
Freq 9632.75 Hz Count 1661
Freq 9638.55 Hz Count 1660
Freq 9632.75 Hz Count 1661
Freq 9227.22 Hz Count 1734
Freq 9950.25 Hz Count 1608
Freq 9638.55 Hz Count 1660
Freq 9632.75 Hz Count 1661
Freq 9638.55 Hz Count 1660
The actual frequency should be 9.543khz.
I can certainly understand why there will be some variation
BUT why are there count values of 1425 and 1734 which are well outside the average?
I could understand the count may be bigger if there were other interrupts but why the lower value?
Can anyone explain why this might be happening please?
Best Answer
You've clearly given this some thought, but some of the low-level details are tripping you up. I'm going to focus on one aspect of your problem, and maybe this will lead you into thinking in the right way to solve the rest of it.
After doing a little research, I learned that
In your pin change ISR, you capture the state of the hardware and software counters:
One problem is that you're getting readings of around 1660, when calculation shows that a frequency of 9.543 kHz should give you readings of about 1676. You're consistently losing 16 clocks somewhere.
The first statement above reads the TCNT2 hardware register and stores it in an
int
variable. One problem is that TCNT2 is an 8-bit unsigned variable, while in the Arduino environment anunsigned int
is a 16-bit variable. Furthermore, you've declared the variable asvolatile
, which means that the compiler must generate code to extend the value to 16 bits and then write it to memory as 2 bytes. Then you clear the register.This code between the reading and the clearing of the register could easily account for the missing 16 clocks.
It would be better to write this ISR in assembly so that you know exactly what code is running in such a time-critical area. Or at least, examine the compiler output and tweak the source code to minimize the number of instructions it generates here. For example, just changing
to
and making it local to the ISR (it isn't used anywhere else) will probably get you close.
Better still, don't clear the timer at all — let it free-run and take the difference between the current and previous values. This way, you eliminate any possibility of "dead time", but this does make the job of resolving overflow events somewhat tricky.