Frequency counter low hz

Hi, I'm looking for a way to code a frequency counter with high accuracthisy for low Hertz (0.1 to 2Hz). All the code and libraries i found didn't answer thoses two objectives or justdidn't work.

So my impulse that i want to mesure is 5V. around 0.5hz.

I would like to get down to 10uS accuracy (for the period).

Do someone did stuble on an example that could help me start this.

Can we get this accurate with the arduino uno? at those freq.

regards

Sylvain

If you're looking for 10 uSec accuracy over a period of up to 10 seconds, you're asking for an (in)accuracy of 0.0001. I don't think that's realistic even with a crystal controlled Arduino. See this for a look at the timing accuracy of the Arduino. http://jorisvr.nl/arduino_frequency.html

Maxim says their highly accurate real time clocks can achieve ±20ppm at room temperature. This performance will be degraded by more extreme temperatures. So at best, 0.002% variation.

How are you going to sense the impulse? Does your measurement process support this accuracy with repeatability?

I think these are the challenges, not the math.

I think that if you can live with the accuracy of the Arduino and your sensor, the approach would be to view the problem as one of time measurement and not frequency measurement. Start a timer when the intial impulse is received, stop it when the subsequent impulse is received. The reciprocal of the elasped time in seconds is the frequency.

If you use a rising interrrupt to start capturing micros, and a falling interrupt to stop capturing micros, that will be about as accurute as you can get. There will be 4-6-8-10uS of inaccuracy.

I have this code to create a frequency. The frequency as read from a meter is 20.0008 KHz. Looking with a scope, you can see a lot of jitter.

unsigned long previousMicros;
unsigned long halfPeriod = 25; // 25 x 2 = 50uS, 20KHz
void setup(){
pinMode (2, OUTPUT);
}
void loop(){
while(1){  // skip the loop recycling; loop takes around 10-12uS to complete
  if (micros() >=previousMicros){
   previousMicros = previousMicros + halfPeriod;
   PIND = 0b00000100;  //toggle D2
   }
  }
}

If you use a rising interrrupt to start capturing micros, and a falling interrupt to stop capturing micros, that will be about as accurute as you can get. There will be 4-6-8-10 uS of inaccuracy.

That look like accurate enough. What do you mean by 4-6-8-10 inaccuracy? What command should i use to trigger on a rising interrupt? and get those low value .

If i use a external crystal to clock won't it be more accurate? I didn't find any clear example of a external clock integration with fuses values that needs to be change,

regards

I am using a Duemilanove with 16 MHz crystal and 22pF caps, bootloaded as an Uno.
The actual code:

const byte triggerOut = 2;

unsigned long nextMicros;
//unsigned long duration = 200UL; // flip every 200uS = 2.5KHz pulse
unsigned long duration = 25UL; // flip every 50uS = 20KHz pulse

void setup(){
  pinMode (triggerOut, OUTPUT);
  digitalWrite(triggerOut, LOW);
  //Serial.begin(115200);
}
void loop(){
  while (1){
    if ((micros() - nextMicros) >= duration){
      nextMicros = nextMicros + duration;
      PIND = 0b00000100; // toggle output by writing PINx register
    }
  }
}

The results read out on my $1600 dollar scope:
19.9998 kHz up t o 20.0009 kHz
I would say its generally 20.0004 +/- .0006 after watching a few minutes.

A 1 KHz signal measures 1.00001 to 1.00004 kHz

Screen captures looks like the attached.

DS0018.BMP (1.37 MB)

DS0017.BMP (1.37 MB)

I'm not sure what the specified frequency accuracy of crystal based Arduinos is but I've seen several people report that they've measured 40 ppm. This is an error of .8 Hz at 20Khz. I have seen Agilent frequency counters that spec this performance. They cost about $1,000.

To get better performance, people commonly use RTCs. Maxim says their RTC's are good to about 20 ppm.

For your lowest desired frequency, 0.10 Hz, an accuracy of 10 uSec is ((10^-5)sec/10sec)= 10^-6 or 1 ppm. So what you desire is an accuracy 20 times better than the spec for the RTC.

For a frequency of 20 KHz, 10 uSec error = (10^-5)sec/ 0.00005 sec = 0.02 or 200,000 ppm. Since the period of 20 Khz is only 50 uSec, a 10 uSec error is huge.

These estimates don't address the impact of temperature variations or things like the response time of the sensors and the processing time of the Arduino, among other things. These additional error sources will have factors that you can adjust for like temperature compensation and measurement of the processing time but they will also have factors that are random.

Calibration of your test equipment is going to be critical. Otherwise, you may get results with a high degree of precision but not your desired accuracy. If you have lab grade equipment it would be interesting to see your results. I think it's going to be very challenging.

So I tried another test:

#include <avr/interrupt.h>  // added to support detecting clock edge via interrupt
// Hardware interrupt 0

byte clockEdgeUp;
byte clockEdgeDown;
unsigned long riseTime1;
unsigned long riseTime2;

/*
void clockFalling() // 
// ISR (INT0_vect) //
{
  clockEdgeDown = 1;
}
*/

void clockRising() // 
// ISR (INT0_vect) //
{
  clockEdgeUp = 1;
}

void setup(){
  pinMode (2, INPUT);
  digitalWrite (2, HIGH);
  pinMode (9, OUTPUT);
  analogWrite (9, 127);
  attachInterrupt(0, clockRising, RISING);
  Serial.begin(115200);
  //Serial.print("setup done");
}
void loop(){

  while(1){
    // look for rising edge
    while (clockEdgeUp == 0){
    Serial.print("0");
    }
    Serial.println("1");
    clockEdgeUp = 0;
    riseTime1 = micros();

    // look for next risign edge
    while (clockEdgeUp == 0){
    Serial.print("2");
    }   
    Serial.println("3");
    clockEdgeUp = 0;
    riseTime2 = micros();
    Serial.println(riseTime2 - riseTime1);
    
  }
}

Results:

0000000000000001
2222222222222222222223
2040
0000000000000001
2222222222222222222223
2040
0000000000000001
2222222222222222222223
2040
0000000000000001
2222222222222222222223
2040
0000000000000001
2222222222222222222223
2040
0000000000000001
2222222222222222222223
2040

1/2040 uS = 490.196Hz. Am seeing 490.208 on the scope.
What I don’t understand is why this code won’t run without the serial prints.

Even odder - if I change this line
Serial.println(“1”);
to
Serial.print(“1”);
the reported output changes even tho the actual output is still 490.208Hz:

0000000000000001222222222222222222222223
2208
0000000000000001222222222222222222222223
2208
0000000000000001222222222222222222222223
2208
0000000000000001222222222222222222222223
2216
0000000000000001222222222222222222222223
2216
0000000000000001222222222222222222222223
2208
0000000000000001222222222222222222222223
2208

1/.002208 = 452.899 Hz

DS0019.BMP (1.37 MB)

If I take all the .println’s out, I see some 2036s, and 2044s as well:

#include <avr/interrupt.h>  // added to support detecting clock edge via interrupt
// Hardware interrupt 0

byte clockEdgeUp;
byte clockEdgeDown;
unsigned long riseTime1;
unsigned long riseTime2;

/*
void clockFalling() // 
// ISR (INT0_vect) //
{
  clockEdgeDown = 1;
}
*/

void clockRising() // 
// ISR (INT0_vect) //
{
  clockEdgeUp = 1;
}

void setup(){
  pinMode (2, INPUT);
  digitalWrite (2, HIGH);
  pinMode (9, OUTPUT);
  analogWrite (9, 127);
  attachInterrupt(0, clockRising, RISING);
  Serial.begin(115200);
  //Serial.print("setup done");
}
void loop(){

  while(1){
    // look for rising edge
    while (clockEdgeUp == 0){
    Serial.println("0");
    }
    Serial.println("1");
    clockEdgeUp = 0;
    riseTime1 = micros();

    // look for next risign edge
    while (clockEdgeUp == 0){
    Serial.println("2");
    }   
    Serial.println("3");
    clockEdgeUp = 0;
    riseTime2 = micros();
    Serial.println(riseTime2 - riseTime1);
    
  }
}
0
0
0
0
0
1
2
2
2
2
2
2
2
3
2036
:
:
0
0
0
0
0
1
2
2
2
2
2
2
2
3
2040
:
:
0
0
0
0
0
1
2
2
2
2
2
2
2
3
2044

most of the time its 2040.

So going back to the original question, I would say your best results would likely vary +/-4uS because that is the resolution that micros( ) can offer.

Crossroads:

Your code worked fine for me even when I removed all the serial communication. When I used the serial monitor, I did use 9600 instead of 115200 because I sometimes have inconsistent serial links at 115200.

Using the time markers, aligned by eye, I got a very consistent 490.196Hz, as you calculated. The square wave did not show any jitter for me. I think there is some granularity in my ability to position the markers and so I don't believe the result to its implied precision. Also, I bought this scope at auction and I don't know when it was last calibrated to rigorous standards.

I don't think the question here is the resolution of the results but in their accuracy over a time period that, by microprocessor standards, is very long.

Maybe the serial prints are an IDE 1.0 thing. That particular computer has many rev's of IDE on it g going back -0018, seems to default to 1.0 opening when I wanted 1.0.5.

Crossroads, i got the same results as you when a change the print vs println.

Couldn’t use it to read my frequency. Maybe I don’t trigger for some reason.

Here my code :

/* Arduino 101: timer and interrupts
1: Timer1 compare match interrupt example
more infos: http://www.letmakerobots.com/node/28278
created by RobotFreak
*/

#define ledPin 12

void setup()
{
pinMode(ledPin, OUTPUT);

// initialize timer1
noInterrupts(); // disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;

OCR1A = 28000; // compare match register 16MHz/256/2Hz
TCCR1B |= (1 << WGM12); // CTC mode
TCCR1B |= (1 << CS10); // 1024 prescaler
TCCR1B |= (1 << CS12); // 1024 prescaler
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
interrupts(); // enable all interrupts
}

ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine
{
if (OCR1A==28000) // si on vient de terminer un cycle long
OCR1A=125; // faire le prochain cycle plus court
else
OCR1A=28000; // sinon faire le prochain cycle long

digitalWrite(ledPin, digitalRead(ledPin) ^ 1); // toggle LED pin

}

void loop()
{
// your program here…
}