Frequency measurement on digital pin not stable

Hi there,

My first arduino project is this one, I have an arduino uno and I hooked up a ne555 pulse generator to
a digital pin on the arduino, the frequency can be adjusted from 33Hz to over 2000Hz and on my
voltmeter it displays for example 2038Hz, quite steady, sometimes 2037, sometimes 2039.
With my program (with 2038Hz on my voltmeter), about every second a value is uploaded to my
serial monitor in the arduino program. I see here 2000Hz a lot, but also other values like 2083 and
2173. I don't understand why the difference is so big on my serial monitor, while on my voltmeter
it's only a couple of Hz.
I hope someone can explain this to me, I tried to stop Timer0 and tested it with my own delay subroutine
already, but I don't know what code is inside the avr, put there by the arduino. Unfortunately my stk500
starterkit broke down, so I cannot test this with assembly code without arduino bootcode loaded into an
avr.
Thank you in advance for helping me!

Derk

My sourcecode:
// The sourcecode begins after this line!

unsigned int freq_in = 2;
unsigned int countticks1 = 1;  
unsigned int countticks0 = 1;   
unsigned int newvalue=0;        
unsigned int oldvalue=0;        
unsigned int number_of_ticks=0; 

void setup()
{
Serial.begin(9600);
pinMode(freq_in, INPUT);   
    cli();
    TCCR1A = 0;     
    TCCR1B = 0;
    OCR1A = 320;               
    TCCR1B |= (1 << WGM12);
    TCCR1B |= (1 << CS10);   
    TIMSK1 |= (1 << OCIE1A);
    sei();                     
}

void loop()
{
unsigned int freq_to_display=0;               

if ((countticks0+countticks1)!=0)
{ 
  freq_to_display=50000/(countticks0+countticks1);
}

Serial.print("Frequency: "); Serial.print(freq_to_display); Serial.println(" Hz");
delay(1000);
}

ISR(TIMER1_COMPA_vect)
{
cli();     
newvalue = digitalRead(freq_in);
if (oldvalue==newvalue)
 {
  number_of_ticks++;
 }
  else
 {
   if (newvalue==1)
   {
     countticks1=number_of_ticks;
   }
   else
   {
     countticks0=number_of_ticks;
   }
   number_of_ticks=1;
 } 
oldvalue=newvalue;                    
sei();
}

Please use code tags when posting code, they are under the # button above the smileys (you can modify existing posts too!)

Your serial port takes quite some time to communicate

Try a higher baud rate ==> Serial.begin(115200);

Furthermore make the variables that are used in the ISR volatile.

volatile unsigned int countticks1 = 1;
volatile unsigned int countticks0 = 1;
volatile unsigned int newvalue=0;
volatile unsigned int oldvalue=0;
volatile unsigned int number_of_ticks=0;

Try to keep your ISR as short as possible to prevent missing interrupts [but I do not understand the logic of that ISR code]

There is a rounding issue here
freq_to_display=50000/(countticks0+countticks1); // integer math truncates, it never does rounding.

hope this helps to improve your project!

Thank you for your reply!
Your suggestions do not solve the problem.

Derk

If you want to measure frequency why not use attachInterrupt() to count the pulses on line 2

Something like this (not tested)

volatile int count = 0;
unsigned long lastTime = 0;

void setup()
{
  Serial.begin(115200);
  attachInterrupt(0, ISR, RISING);
}

void loop()
{
   if (millis() - lastTime >=1000)  // show once per second the frequency
  {
    cli();
    int freq = count;
    count = 0;
    sei();
    lastTime = millis();
    Serial.print(freq);
    Serial.println("Hz");
  }
}

void ISR()
{
  count++;
}

Here is some code that will capture cycle time on pin 8 and provide an unsigned long int of the number of uS since last capture. Since this is the period of the waveform, frequency is a simple calculation. My code snaps the time in an ISR and puts the data into a circular queue to pass it back to main level. You should be able to extract what you want from this. It works on an Uno as is. Feed the signal in on pin 8. Values are written to the serial port.

#include "Arduino.h"

volatile unsigned t1captured = 0;
volatile unsigned t1capval = 0;
volatile unsigned t1ovfcnt = 0;
volatile unsigned long t1time;
volatile unsigned long t1last = 0;

#define BUFFER_SIZE 32

volatile unsigned long int buffer[BUFFER_SIZE];
volatile int head = 0;
volatile int tail = 0;

void setup() {

  Serial.begin(9600);  

  TCCR1A = 0x0;    // put timer1 in normal mode
  TCCR1B = 0x2;    // change prescaler to divide clock by 8

  // clear any pending capture or overflow interrupts
  TIFR1 = (1<<ICF1) | (1<<TOV1);
  // Enable input capture and overflow interrupts
  TIMSK1 |= (1<<ICIE1) | (1<<TOIE1);
  
  pinMode(8, INPUT);
}

void loop() {

  if(head != tail) {
    head = (head + 1) % BUFFER_SIZE;
    Serial.println(buffer[head]);
  }
  
}

ISR(TIMER1_OVF_vect) {
  
   t1ovfcnt++;              // keep track of overflows

}


ISR(TIMER1_CAPT_vect) {
  
  unsigned long t1temp;

  // combine overflow count with capture value to create 32 bit count
  //  calculate how long it has been since the last capture
  //   stick the result in the global variable t1time in 1uS precision

  t1capval = ICR1;
  t1temp = ((unsigned long)t1ovfcnt << 16) | t1capval;
  t1time = (t1temp - t1last) >> 1;
  t1last = t1temp;
  
  tail = (tail + 1) % BUFFER_SIZE;
  buffer[tail] = t1time;

}

Have you tried the pulseIn command>
http://arduino.cc/en/Reference/PulseIn
Measure the high time, measure the low time, add together for the period, do the math for frequency.

Thanks to all of you!
I will test it later on and report back the results of the accuracy and stability.
Derk

At the moment I'm testing.

My voltmeter says 1085Hz, my own sourcecode says 1111Hz and sometimes 1000Hz.
The pulseIn() function returns 724Hz all the time, maybe it's me, so I will provide you with
the code being used:

unsigned long times = 0;
unsigned long freq = 0;

void setup()
{
Serial.begin(9600);
pinMode(2,INPUT);
}

void loop()
{
  pulseIn(2,LOW);
  times=micros();
  pulseIn(2,HIGH);  
  pulseIn(2,LOW);
  freq=micros()-times;
  freq=(1000000/freq)*2;
  Serial.println(freq);
  delay(1000);
}

Ofcourse the pulseIn() function is not handy if you want to do other things while waiting for this
function to return.

The code posted by Afremont returns a frequency of 918 or 919 Hz, it is stable, but not anywhere
near 1085Hz (which is almost also what a calibrated meter measures). (See new post)
For some reason I cannot explain, this works on pin 8 but not on pin 2.

The code posted by RobTillaart works very well, frequencies between 1089 and 1091 Hz are displayed,
The ISR() routine may not be used, I changed it to ISAR()

Thanks a lot for your reactions, I think I will use Rob Tillaart's code (edit:Or Afremont's code), they are
stable and accurate!

Derk

My program is not giving you the frequency, it's giving you the period in microseconds. 919uS = 1088Hz. You have to calculate the inverse.

1/.000919S = 1088.1Hz 1/.000918S = 1089.3Hz

It only works on Pin 8 because that's the only input capture pin (ICP1).

EDIT: Corrected numbers

I made some changes in my previous post, Afremont's code also works great!!!
Thank you!