Trying to use Timer for accurate microsecond pulse measurement

Dear all,

I'm currently working on a frequency measurement device, wich eventualy measure the frequency of a generator 3 times with a fixed period in between, to determine what the final frequency will be. After that, stuff wil happen to keep the frequency constant (sort of feedback loop). Problem now is, I need a verry high resolution in the pulselength measurement, otherwise the math will be bad. So I tried with micros(), turns out.. resolution of micros() function is 4 us. Bit to bad..

Now I've done a little reading, digging in the datasheet of the atmega 2560 and stuff, cause I want to use Timer5 to count the clockpulses between HIGH state and HIGH state of the measurement pin, to determine the periode 'time' (I know that in order of time, this is not a guaranty of frequency accuracy of 100%, but i'm conserned about the resolution in the first place).

Problem is, that when i set the OCR5A output compare register to a small value (to get beter resolution), results go nuts and arent what they sepose to be (20Hz PWM from the arduino itself in the test).

Can anyone please tell me what I'm doing wrong?

Here is my code:

#include <PWM.h>

#define frequencyOutput  5
#define frequencyInput  3 // this is pin 20

const int uSecInterval = 6;
int32_t frequencyOfPin = 5; 
double Freq;
double Frequency_1;
double duration;
int x;
int i;
int finalTime;
int beginTime;
unsigned long TimeDuration;
int startF = 50;
unsigned long Time;
unsigned long EndTime;
volatile unsigned long TimerS;

void setup() {

  InitTimersSafe();
  SetPinFrequencySafe(frequencyOutput,frequencyOfPin);
pinMode(frequencyOutput, OUTPUT);
pinMode(9,OUTPUT);
Serial.begin(9600);
x= startF;
pinMode(22,INPUT);

  noInterrupts();                     //disable global interupt
  
  TimerS = 0;                         //clear increment in ISR
  TCCR5A = 0;                         //clear timer nA register
  TCCR5B = 0;                         //clear timer nB register
  TCNT5=0;                            //Set counter to 0
  OCR5A = uSecInterval*16-1;          //Output Compare Restister nA (with prescaler one, one pulse = 
                                                        1/16th microsecond, so topvalue should be this value, to fit in 
                                                        the amount of microsecond between TimerS_n and TimerS_n+1
  TCCR5B |= (1<<CS50) | (1<<WGM52);   //Prescaler = 1, CLC
  TIMSK5 |= (1<<OCIE5A);              //Enable OCR5A interrupt
  
  interrupts();                       //enable global interupt
  
  for(i=0;i<5;i++){
    Time = TimerS;
    delayMicroseconds(50);
    EndTime = TimerS;
    Serial.println((EndTime-Time)*uSecInterval);  //<<this is to check if the timing is a bit right 
  }
}

ISR(TIMER5_COMPA_vect){
TimerS++;
}

void loop() {

delay(100); 
SetPinFrequencySafe(frequencyOutput,x);
beginTime = micros();
analogWrite(frequencyOutput,254);
Frequency_1 = FrequencyMeasure_1(); // this is the call for frequency measurement!

      
}

double FrequencyMeasure_1(){
restart:
TimerS=0;
switch(digitalRead(22)){
  case LOW:
      while(digitalRead(22)==LOW){}
      Time = TimerS;
      while(digitalRead(22)==HIGH){}
      while(digitalRead(22)==LOW){}
      EndTime = TimerS;

    break;
  case HIGH:
      while(digitalRead(22)==HIGH){}
      Time = TimerS;
      while(digitalRead(22)==LOW){}
      while(digitalRead(22)==HIGH){}
      EndTime = TimerS;
    break;
}

/*if (EndTime<Time){
  goto restart;
}*/
Freq= 1000000/((float)EndTime-(float)Time)/uSecInterval;
//Serial.println(Freq);
  return Freq;
}

Tried this with macros() and that worked ok, but not within the resolution I need/want.

Results with uSecInterval = 6 are between 3333 to 3335 pulses from the clock (19998 to 20010 us on 50Hz)
Results with uSecInterval = 2 are between 4964 to 5000 pulses from the clock (9928 to 10000 us on 50Hz, which is totaly wierd..)
Results with uSecInterval = 1 are between 4965 to 5000 pulses from the clock (4965 to 5000 us on 50Hz, which is even wierder..)

Any help will be great!

What frequency ranges are you trying to measure?

if you want an accurate measurement of a Duty cycle , your best bet is "Timer input Capture" . it basically captures timer count value when an input edge is detected .

here is a good tutorial : neat link

(PS: just look for the equivalent registers and pin for the Atmega2560)

Note that most Arduinos are clocked with ceramic resonators.
That makes them useless for accurate frequency measurements.

And the ones that are clocked with real crystals are likely not factory tuned.
Leo..

DKWatson:
What frequency ranges are you trying to measure?

From 40Hz to 60Hz (target is 50Hz as an off-grid generator)

amine2:
if you want an accurate measurement of a Duty cycle , your best bet is "Timer input Capture" . it basically captures timer count value when an input edge is detected .

here is a good tutorial : neat link

(PS: just look for the equivalent registers and pin for the Atmega2560)

Thanks! I will look into it. But does'nt this do the same trick?

Wawa:
Note that most Arduinos are clocked with ceramic resonators.
That makes them useless for accurate frequency measurements.

And the ones that are clocked with real crystals are likely not factory tuned.
Leo..

I know that the measurement on the arduino is'nt realy reliable in terms of time (I thought this was something like 1 hour of per year). But this will be only for a test setup, after that I can try the same trick with say a more accurate cristal or something.

I think you must check your measurements against a verified meter. Not (only) for the accuracy of resonator or crystal but because for 50Hz (20ms period) there are plenty of "things" that will run in backround and "neutrralise" the ticking you measure.

demkat1:
I think you must check your measurements against a verified meter. Not (only) for the accuracy of resonator or crystal but because for 50Hz (20ms period) there are plenty of "things" that will run in backround and "neutrralise" the ticking you measure.

What do you mean with neutrralise? When i run this script with the following loop:

double FrequencyMeasure_1(){
restart:
TimerS=0;
switch(digitalRead(22)){
  case LOW:
      while(digitalRead(22)==LOW){}
      Time = mircos();
      while(digitalRead(22)==HIGH){}
      while(digitalRead(22)==LOW){}
      EndTime = mircos();

    break;
  case HIGH:
      while(digitalRead(22)==HIGH){}
      Time = mircos();
      while(digitalRead(22)==LOW){}
      while(digitalRead(22)==HIGH){}
      EndTime = mircos();
    break;
}

/*if (EndTime<Time){
  goto restart;
}*/
Freq= 1000000/((float)EndTime-(float)Time)/uSecInterval;
//Serial.println(Freq);
  return Freq;
}

Only thing changed, is using simple mircos() function, this worked fine (exept for the resolution). This methode is pritty simple, it waits to get to the other state on the measurementpin, gets the time, then waits to get to the same rissing or falling edge again and take that time and so on.. Again, this worked fine. So my guess is that there is something wrong with MY timer settings somehow.

FvDam:
What do you mean with neutrralise? When i run this script with the following loop:

Only thing changed, is using simple mircos() function, this worked fine (exept for the resolution). This methode is pritty simple, it waits to get to the other state on the measurementpin, gets the time, then waits to get to the same rissing or falling edge again and take that time and so on.. Again, this worked fine. So my guess is that there is something wrong with MY timer settings somehow.

Yeaap! thats exactly what im saying. You "count" ticks for a period of 20ms. During this counting, other functions run (in order to update micros(), millis() etc). So, during your counting some other codes "use" those ticks, which at the end dont come to your counting.
Example : for a millis() update, about 5 usec are spent every about 1 ms.
For you, that is a "lower" measurement of about : 20ms period * 5usec =100usec.

When a timer is active, every 'tick' increments a counter, regardless of any other activity except a sleep state other than IDLE. There are no interrupts that stop this. There are no 'lost ticks'. When you read the value of TCNTn you will get a number that relates to exactly the number of ticks that have occurred since the timer was started. The only counter is used in the timer0 overflow ISR that increments to keep track of 256 ticks. You want precise timing, you actually need to learn how to program the controller. Stop looking for canned solutions in some hobbyist's library and then get put off when you find it isn't perfect. The timers on the 328P are accurate once you identify what frequency the oscillator is running at. If you read the specs, you'll find that the tolerance is not zero, so don't wade in thinking every tick is 62.5ns. And yes, the oscillator changes with temperature, which is why there is a way to measure the processor temp and compensate. All of this you would know if you read the datasheet.

is there an active timer used in OP's sketch?

demkat1:
is there an active timer used in OP's sketch?

I'm sorry, is that a serious question?

it seems you strongly believe Im wrong. Pls help to understand "where".
Isn't the millis() update "costing" a 3,5-5 usec to timing?

I mis - interprete something here ?:

Every function call takes time of course. The implication you were making was that this time would be taken away from the count which is not true. What is true is that when the value of the counter is recorded it is already several ticks past the time you ask it to record and by the time the value is returned to you, more clock ticks have past. The onus is on you, the developer, to understand that this happens and to compensate. I measure instruction cycles and code segment execution times to the exact tick because I know the precise number of clock pulses it takes to perform the task. It's really easy to use this or that library to do this or that function and then say it's not very accurate because it's Arduino. It's not very accurate because those who wrote the library and those who use it never take the time to understand what they're doing and actually do it right - too much work.

thank you for clarification.
so, if now i get it correctly, what is expected here with the OPs code as it is, is :

  1. some on start counting "delay" composing of a) some ticks because of code's statements and b) some usec (0-5?) because of "when" the system if free of backround code to digitalRead().

  2. Same for end.

So counting error (at first glance and no tick compensation) is ~0 to 10usec, no matter what the signal period is.

Correct? (well, more or less correct?)

In principle yes, you are correct except 10us is a bit harsh, it's more like 2 or 3. There are many things that go on with bare metal programming that you don't read in books and will never understand through the use of abstraction layers. Timing is a big one and to get it right, you need to get in bed with the assembler. Even when you compile your code with optimization you run the risk of altering your timing routines. As an example, there are two subroutine calls, CALL and RCALL. CALL can redirect to anywhere within the program memory and takes 4 clock cycles. RCALL can only move the program counter +/- 2K to enter a subroutine, but takes only 3 clock cycles. When developing according to 'mission critical' guidelines, we can only use libraries that come with the compiler and those written in-house. We're also not allowed to optimize until all code is performing to spec and can be used as a baseline for comparison, and only then is optimization is deemed necessary.

What can I say

Thank you again
Have a nice day