Reading Frequency

I am having trouble with reading a pulse correctly. I used pulseIn() which works fine with 50% duty cylce but if I change it the frequency changes. Is there a better way to read the frequency of a pulse?

Thank You

I wanted to clarify when I said when "if I change it" I meant the pulse duty cycle. So if I make it 25% or 75% the pulseIn() reads the frequency differently.

You could call pulseIn() with two parameters, the pin number and the polarity of the pulse. Call it once with HIGH and once with LOW and add the two intervals. That is your period, for a fairly steady wave. The result should be independent of pulse width.

Thank you aarg. This fixed the problem with the changing duration but I have another problem which exists for the pulseIn() and the way you provided.

Reading the pulse on pin 6 I have 508 Hz and on pin 10 I have 1013 Hz.

If I use pulseIn() with 127 value it is pretty close to the same values.

I believe the values should be 490Hz and 976 Hz.

What could be the cause of this?

I should mention im running the atmega328P as a standalone.

unsigned long durH;
unsigned long durL;
//float durH;
//float durL;
int pin = 7;
int average=0;
int i=0;
int sum=0;
int duration=0;

void setup()

{

  pinMode(pin, INPUT);
analogWrite(11,127);
analogWrite(6,127);
Serial.begin(9600);
}



void loop()

{
  if (i < 10){
  durH = pulseIn(pin, HIGH);// read high part of pulse
  durL = pulseIn(pin, LOW );//reads low part of pulse
  duration= (durH+durL)/2; // adds high and low and divides by 2
  sum= sum+duration;
  i++;} /// loops 10 times to get an average later on
  else {
 
  average=sum/10;
  Serial.println(average);
 delay(1000);

}

}

There were some problems in your code, so I fixed it. You forgot to re-initialize some variables outside your averaging loop. I can't fathom why you are dividing the sum of HIGH and LOW readings by 2. If you want the period and frequency, you just add them up. I made the sketch easier to play with different values. For example, I changed the duty cycle to 25%.

unsigned long durH;
unsigned long durL;
const uint8_t pin = 7;
const uint8_t numSamples = 4;
const uint8_t phase = 64;

int average = 0;
int i = 0;
int sum = 0;
int period = 0;

void setup()
{
  pinMode(pin, INPUT);
  analogWrite(11, phase);
  analogWrite(6, phase);
  Serial.begin(9600);
}

void loop()
{
  if (i < numSamples)
  {
    durH = pulseIn(pin, HIGH);// read high part of pulse
    durL = pulseIn(pin, LOW );//reads low part of pulse
    Serial.print("H:");
    Serial.print(durH);
    Serial.print(" L:");
    Serial.print(durL);
    Serial.println();

    period = (durH + durL); // adds high and low
    sum = sum + period;
    i++;
  } /// loops 10 times to get an average later on
  else
  {
    average = sum / numSamples;
    i = 0;
    sum = 0;
    Serial.print("average period:");
    Serial.print(average);
    Serial.print(" average frequency:");
    Serial.println(1000000L/average);
    Serial.println();
    delay(1000);
  }
}

The results seem normal to me, except that the frequency on pin 11 is half what it is on pin 7. I am not sure why that is so. Here is the output:

pin 7 to pin 11:

H:507 L:1522
H:499 L:1514
H:498 L:1514
H:498 L:1515
average period:2016 average frequency:496

pin 7 to pin 6:

H:254 L:764
H:246 L:765
H:246 L:765
H:246 L:765
average period:1012 average frequency:988

Thank you aarg. I initially had sum and i cleared at the end but deleted by mistake when posting it to the forum.

Your code works perfect and thank you for all your help.

What is the reasoning for 1000000L?

i just hooked it up to a waveform generator and my readings are off. The higher i go the more off they are off. It was set for square wave 50% duty cycle

500 Hz ~ 505 Hz
1K Hz ` 1010 Hz
2k Hz ~2020 Hz
3k Hz ~ 3040 Hz
20K Hz ~ 20833 Hz

roybenmo:
i just hooked it up to a waveform generator and my readings are off.

How accurate is your waveform generator? Datasheet?

You take the reading (or setting) from the waveform generator and compare against the reading of your Arduino?

Which type of Arduino board is it? If it is a board in R3 design, such like Arduino UNO R3 or MEGA2560 R3, it is normal that the accuracy is only accurate up to 0.2% ... 0.8%.

So actually 1000 Hz frequency measured may just be 992 Hz or 1008 Hz measured. Many Arduino boards provide 16 MHz clock by a "ceramic resonator" only, which might be inaccurate up to 0.8%.

If you can get an older DUEMILANOVE ("Arduino 2009") board, they are much more accurate using a "crystal oscillator" for 16 MHz clocking instead of a "ceramic resonator" as used with modern board designs.

So older Arduino boards are clocking perhaps 100 times more accurate than modern board design when using the internal 16 MHz clocking for timing.

But I think, also your code for reading frequency is not very good and could be improved for better accuracy, even with boards in "R3" design.

20000 Hz vs 20833 Hz is only 96%, so you have 4% inaccuracy, which is surely wrong, even with boards that have a ceramic 16 MHz resonator and might be off frequency up to 0.8%.

Hi Jurs
im actually using a standalone atmega328P with a 16MHz crystal oscillator. What changes to the code do you suggest?

roybenmo:
Hi Jurs
im actually using a standalone atmega328P with a 16MHz crystal oscillator. What changes to the code do you suggest?

Test this code I made (see file attachment).
Just unpack the ZIP file contents in your sketch file directory.

The test setup is for frequency counting on pin-2.

The test sketch creates a PWM frequency on pin-5, if you want to count that frequency, connect pin-5 and pin-2.

If you have a waveform generator, connect pin-2 to the output of your waveform generator (and Arduino-GND to GND of the waveform).

With 16 MHz "cystal oscillator" clocking of your Arduino circuit the accuracy should be better than 0.01%.
I.e. 1000.0 Hz accurate frequency should be measured in the range 999.9 to 1000.1 Hz, plus inaccuracy of the generated waveform frequency.

Feedback welcome!

jursPulseCounterTest.zip (1.23 KB)

roybenmo:
What is the reasoning for 1000000L?

Sorry, I normally would use a constant like MICROSECONDS_PER_SECOND. I was in a hurry to finish.

Hi jurs
i am trying your code but get an error.

  This report would have more information with
  "Show verbose output during compilation"
  enabled in File > Preferences.
Arduino: 1.0.6 (Windows 7), Board: "Arduino Uno"
In file included from jursPulseCounterTest.ino:1:
/jursPulseCount.h: In member function 'void PulseCounter::begin()':
jursPulseCount.h:60: error: 'NOT_AN_INTERRUPT' was not declared in this scope
jursPulseCount.h:61: error: 'NOT_AN_INTERRUPT' was not declared in this scope
jursPulseCount.h:62: error: 'NOT_AN_INTERRUPT' was not declared in this scope
jursPulseCount.h:63: error: 'NOT_AN_INTERRUPT' was not declared in this scope
/jursPulseCount.h: In member function 'void PulseCounter::end()':
jursPulseCount.h:68: error: 'NOT_AN_INTERRUPT' was not declared in this scope

Sorry, looks like the macro "digitalPinToInterrupt" is not available in older versions of the Arduino IDE.

For compatibility with IDE 1.0.x versions and Arduino UNO, please add three lines of code in the tab "jursPulseCount.h":

#ifndef PulseCounter_h
#define PulseCounter_h

#include <Arduino.h>  // add this line
#define NOT_AN_INTERRUPT -1  // add this line 
#define digitalPinToInterrupt(p)  ((p) == 2 ? 0 : ((p) == 3 ? 1 : NOT_AN_INTERRUPT))  // add this line

Then the code should compile with Arduino 1.0.x versions and it should work with Arduino UNO and other Atmega328 based boards.

Thanks jurs
i updated the software to the most recent and it works perfect.

I would like to add this to another sketch of mine. Will it work together since its using interrupts?

Isn't "digitalPinToInterrupt" part of pins_arduino.h?
That's where it is in my 1.0.6

roybenmo:
i updated the software to the most recent and it works perfect.

Better and more accurate results than with your earlier pulseIn() code?

roybenmo:
I would like to add this to another sketch of mine. Will it work together since its using interrupts?

I've made a class and put the code into a seperate "tab" file, so that the code is easily reusable in different sketches. With Atmega328 based boards you can use pin-2 and pin-3, which can be used with hardware interrupts. Just give it a try!

The code should run in different sketches as well as in the example sketch I provided.

CrossRoads:
Isn't "digitalPinToInterrupt" part of pins_arduino.h?
That's where it is in my 1.0.6

I don't have Arduino 1.0.6. I have 1.0.5-r2 and 1.6.5-r5.