Using isr for frequency measurement - dumb question?

Hi, I am using an arduino uno..
I want to measure a sinusoidal frequency around 800Hz. I decided i wanted to use a physical interrupt to measure T . Apart from anything else i want to get a better understanding of interrupts which look very useful for interacting with the real world. This is to be part of a larger project.

My simple sketch works OK, BUT:

I am using a sq wave generator, but, my generator has some jitter and i thought to try EMA to get a better average.
When i use the EMA (or even just averaging) code at lines 43 or 44 the serialmonitor gives me 'inf'. Its probably just a simple logic error but I cant find it having looked a long time - maybe I'm missing something. It works ok without that. I'm feeling rather dumb, :face_with_diagonal_mouth:`` but help would be appreciated?

A second query, interrupts() and nointerrupts() at lines 39 and 60 seem to have no effect, should i use them or not?
Thanks to anyone willing to help.

/*
  ##### Pin 2 is the interrupt pin
  interrupt set to 'rising' on pin 2
*/
//*************************
float alpha = 0.8; //use for EMA function
int counter = 0; //counting the number of times interrupted
int waiting = 0; //counting the number of cycles waited between interrupts
float now = 0; 
float then = 0;
float duration = 0;  //will be (now - then)
float freq = 1;
float prevfreq = 1;
const byte interruptPin = 2; //pin2 will be the   interrupt
bool  flag = true;  //used in ISR
//**************************

void checktISR() {

  flag = false;
}

//======================
void setup() {
  Serial.begin(115200);
  pinMode(2, INPUT_PULLUP);
  //pinMode(2, INPUT);
  Serial.println();
  Serial.println();
  Serial.println("***********test of interrupts use************");
  attachInterrupt(digitalPinToInterrupt(2), checktISR, RISING); //rising level on pin 2 triggers the checktisr
  delay(3000);//time to read sketch name -and clear serial monitor output

}
//==========
void loop()
{
  if (flag == false) {           //an interrupt has occurred
    //noInterrupts();
    waiting = 0;
    duration = ((now - then) / 1000000);
    freq = (1/duration);
    //freq = ((freq * alpha) + (prevfreq * (1 - alpha))); //implement simple EMA to smooth measurement DONT WORK??
    //freq = (freq + prevfreq)/2;  //simple averaging DONT WORK?? gives 'inf' in serialmonitor.
    /*
      Serial.println(now);
      Serial.println(then);
      Serial.println(now - then);
      Serial.println ("");
      Serial.print ("              interrupt operated    ");
      counter = counter + 1;
      Serial.print (counter);
      Serial.print(" *********  duration = ");   
      Serial.println(duration);  */
    Serial.println(freq);
    //Serial.println ("");
    flag = true;                   //reset the flag
    then = now;
    prevfreq = freq;
    //interrupts();
    //delay(10);
  }
  /*
    Serial.print ("  waiting for interrupt ");
    Serial.println(waiting);
    waiting = waiting + 1;
    delay(50);


    Serial.print ("   Now  =  ");
    Serial.print(now);
    Serial.print ("   Then  =  ");
    Serial.print(then);
  */
  now = micros();
}

Hello mikep32

Create a new arrangement of the sketch.

Count the interrupts events per time unit, e.g. per second, and then no further consideration of the data is necessary.

Have a nice day and enjoy coding in C++.

1 Like

Always use unsigned long for times recorded with millis() or micros().

Best to use interrupt to count what you want to measure and let the loop() code measure the time. For AC, you need to rectify it with a diode before applying the voltage to a resistive divider and then apply the lower pulses to an Arduino pin with an interrupt.

ran this on an ESP32

// ESP32 determine pulse period in microseconds and frequency in Hz

#define RXPIN 16

volatile long timer = 0;
long average = 0.0;

// interrupt handler - change in level if LOW to HIGH note time in microseconds
void IRAM_ATTR handleInterrupt2() {
  static int lastPin = -1;
  int pin = digitalRead(RXPIN);
  lastPin = pin;
  if (pin == HIGH) timer = micros();
}

void setup() {
  Serial.begin(115200);
  pinMode(RXPIN, INPUT_PULLDOWN);
  attachInterrupt(digitalPinToInterrupt(RXPIN), handleInterrupt2, CHANGE);
}

// calculate period width in microseconds
void loop() {
  static long lastTimer = 0, counter = 0;
  static long printer = millis();
  // if had inperrupt on pin (timer>0.0) get pulse width time
  if (timer > 0.0) {
    // add pulse width time in microseconds to average and increment counter
    if (lastTimer) average += (timer - lastTimer);
    counter++;
    lastTimer = timer;  // note current time
    timer = 0;          // clear ready for nect reading
  }
  // after 10 seconds print average pulse width time
  if (millis() - printer > 10000) {
    float period = (float)average / counter;
    Serial.printf("Period %.2fuSec Frequency %.2fHz\n", period, 1000000.0f / period);  // print average pulse width
    counter = average = 0;                                                 // reset initialse values
    printer = millis();
  }
}

square wave results tests from .1 Hz to 10KHz

Period 10000131.00uSec Frequency 0.10Hz
Period 200000.98uSec Frequency 5.00Hz
Period 166667.45uSec Frequency 6.00Hz
Period 100000.45uSec Frequency 10.00Hz
Period 10000.04uSec Frequency 100.00Hz
Period 1000.00uSec Frequency 1000.00Hz
Period 100.00uSec Frequency 9999.96Hz

does not work outside this range

thank you Paul, but the frequency i wish to monitor is around 800 Hz and i wish to measure it quickly, a second or even 0.1s would be too long. Measuring the duration of T will be my best outcome.

Thank you Horace. That looks good and the frequency range well covers my area of interest. I will try that when i can - i fear i have destroyed the interrupt pins on my arduino with a careless overvoltage.

I am still interested though to have answers to my two questions, for my education !

my input signal will be full wave rectified, with appropriate V level limiting. However, counting cycles will be too slow hence the desire to count duration of 1 cycle.
i remain interested in answers to my 2 questions, for my personal education.

That signal repeats at twice the frequency of the unrectified signal. For 800 Hz input the period is 1/1600 Hz = 625 microseconds, and interrupt overhead can be a significant fraction of that period.

Input capture with a hardware timer can be used to very accurately time such signals. For an AVR Arduino, this tutorial is excellent: Gammon Forum : Electronics : Microprocessors : Timers and counters

Scroll down to Input Capture.

What is too long a time? You said you have jitter so getting the time each cycle will not work either.
How about 0.0125 seconds that allows you to get 10 measurements.
Capture the time in the interrupt, not the loop.

Then what part of the full wave pulsing signal will you count? That voltage can NEVER go to zero because no diode is perfect.

I apologise, I mis-spoke. The source signal is 400 hZ, and the easiest access is via a full wave rectified so the dominant ripple frequency is 800 hZ, though I could access the unrectified AC signal should I wish to do so. The voltage is around 20v pk. Obviously not applied directly to an Arduino pin! The signal will require some processing, possibly low pass filter, I'm also thinking maybe a Schmidt trigger and consecutive rising (or falling) interrupts. Could also use an emitter follower with appropriate base biasing etc.. There are many options.

However gentlemen (assuming that's what you all are), you're getting ahead of me with discussion of the external circuitry. At this point I'm simply examining some Arduino functions, and the rather simple 2 questions I asked are just for my education.
BTW, I do appreciate the examples proffered, so thanks for that. I think further on I will probably adopt one of those. One of them using 'timer' looks quite like what I'm considering.

I am now away from home and can not access the forum as I can't remember my password, sorry if this is not quite right.

Jremington thanks for that link.
I've scanned the numerous options put up by the presenter in the link. At this point in my understanding much of it it is new to me, but the sketch with header below looks good to me. Thanks.

Frequency timer
// Author: Nick Gammon
// Date: 10th February 2012TT

My son has pointed out my error in my sketch which is causing my code to fail when trying EMA. I have defined 'now' and then as '0', so that at the very FIRST interrupt 'period' calculates as 0 so 'freq' is 'infinity' and all subsequent averaging is just adding numbers to infinity. Simple when you see it!

Actually the error is much more fundamental. You are using and interrupt on a rising voltage. A sinusoidal voltage will RISE for 1/4 of the complete cycle. Look at the data sheet for the processor to determine the voltage rise amount vs. time.
You will see that there is very little voltage rise needed for a very short time period to generate an interrupt. The way you are programming, an interrupt will be generated when the sinus voltage begins to change. Your interrupt code will be invoked and will quickly exit and re-enable interrupts.
Guess what? The sinus voltage is still rising! And will generate a new interrupt. How many time will that occur during the 1/4 cycle of your input?

I looked through the data sheet, and while I found defined voltage levels for high and low, and timing, nothing for what voltage changes define rising/falling voltage levels. Mind you, after page 200 my mind went numb and I skipped ahead, LOL possibly missing something... It's an interesting question though.

However, since none of the potentially useful input from forum members was actually relevant to my VERY simple query, and it was never my intent to pursue the direction the replies were taking, I have in trying to avoid further entanglement perhaps misled people.

For what it's worth, the input to the Arduino will likely come from a differentiator fed by a Schmidt trigger, . I have no need at this stage to decide on my final approach.

My bench setup is using just a square wave generator which due to its quality does have some jitter , and the use of the EMA was a bit of an academic exercise to see how it would work, never having used one before but being rather taken by it's simplicity.

Anyway, thanks to all who took an interest.

Accuracy was mentioned earlier, and for 800Hz it makes sense to do an interval measurement (and optionally low pass filter it). Input capture is great but a little more complex, interrupts have latency but consider that the latency is almost constant, so if the time is recorded by an ISR, the main program can easily calculate the frequency.

The issue of accuracy is embodied in the fast operation of the timers, and the relatively short latency delays of the interrupts. In fact, tons of tachometer/speedometer code works this way. The good ones, anyway. However most of these applications are fed signals with a repetition rate below about 4kHz.

It's when the frequency becomes very high, that the best numerator/denominator reverse, and it becomes more sensible to count pulses in a time interval. In such case of high frequency, the timing granularity begins to degrade the accuracy, there is no cure. At that point, or even in any desired case, an extended timing interval (window) can increase the accuracy by counting pulses instead. Without consideration and treatment, the timing window will truncate the fractional value (phase) of the pulse both at beginning and end, introducing error.

Finally, a post filter can produce more useful measurements by filtering out noise.

Thanks aarg!

While this is perhaps a 'new' question i think it may relate to my earlier one, so I've carried on here.
My little project has moved along and is MAINLY doing what i want, albeit it needs further refinement to actually work in the real world. In fact I have further iterations of this project to make it more real-world capable, but this problem exists in all later iterations.
However my stepper moves as desired for 'period T' errors below and up to 50% above setpoint.

The snag I've hit is this;
using an interrupt i measure micros at interrupt 1 then again after N interrupts, giving me N*T micros which i compare after division to desired T, the period of my input waveform .
After the desired number of cycles has been reached i disable interrupts to use the info to drive the stepper motor to move to correct the error. After movement is complete i then reset the interrupt counter back to 0 and enable interrupts .
This works fine until the T exceeds about 2500 micros where measurement of even a single T becomes erratic and typically about half of the real number. The problem exists whether I'm measuring a single cycle or multiple cycles.

I'm puzzled by this and wonder if anyone can shed light on what is happening?

( I have pasted all the code including my comments, but if preferrable i can repost with all uneccessary lines omitted )

~~~
/*
  NO ATTEMPT fo manage startup/shutdown -
 switch speed reg on after start, and off before stop
  12V freq = approx 1000hz,
  measuring time for 1 cycles is TMeas
  TTarget is then 1mS,  or (1000 microsecs * n), n is number of cycles included
  N used to count cycles (interupts)
  Speed engine up if TMeas > TTarget
   Slow engine if TMeas is < TTarget
  Deadband to reduce jitter
  Use TMeasNow and TMeasPrev
    to apply exponential moving average for TMeas to reduce jitter
    K is the 'alpha' ie TMeas = TMeasNow(K)+ TMeasPrev(1-K)


 stepPin = pin2
 direcnPin = pin5
 Interrupt pin = pin3
 EcoPin = pin7 not in use yet but available to
 set speed 10% lower (say)by addnl switch
*/

unsigned long TTarget = 1000;
int long TMeas;
unsigned long TMeasNow;
unsigned long TMeasPrev;
unsigned long StartTime;
unsigned long EndTime;
int n = 5;              //number of interrupts to monitor, n-1 cycles
float K = 1;            // 1 = disabled for testing
int StepSize = 15;      // 15 seems to work best on uno
int Step;
int wait = 40; //wait time between steps for stepper to complete move
const byte stepPin = 2;     //make a step; must be pin2 if cnc shield is used
//const byte mso1 = 8;     //set combinatn of mo1,2,3 for fractional steps
//const byte mso2 = 9;     //ditto  all hardwired to HIGH at present
//const byte mso3 = 10;   //ditto IF CHANGE THESE, WAIT MUST BE CHANGE TOO
const byte dirPin = 5;    //set this to 1 (high) or 0 (low) for cw or ccw
const byte PulsePin = 3;
const byte EcoPin = 7;
const byte EnablePin = 8;
int N = 0;
int Deadband = 0; // disabled for testing

//************

void setup() {
 Serial.begin(57600);
 pinMode(stepPin, OUTPUT);
 pinMode(dirPin, OUTPUT);
 pinMode(PulsePin, INPUT);
 pinMode(EcoPin, INPUT);
 pinMode(EnablePin, OUTPUT);
 digitalWrite (EnablePin, HIGH);
 EndTime = micros() - 100;        //just for a non-zero start
 /*pinMode (mso1 , OUTPUT);    //all high sets 1/16th steps these are hard wired
   pinMode (mso2 , OUTPUT);
   pinMode (mso3 , OUTPUT);
   digitalWrite(mso1,HIGH);//IF CHANGING STEP SIZE ALSO CHANGE "WAIT"
   digitalWrite(mso2,HIGH);// AND THROTTLESTART NO. OF .STEPS
   digitalWrite(mso3,HIGH);*/
 attachInterrupt(digitalPinToInterrupt(3), checktISR, RISING); //rise on pin 3 triggers interrupt
 Serial.println(" ******  EngineSpeed15-09-23  ******");
 delay(1000);//time to read sketch name -remove for final
 digitalWrite (EnablePin, LOW);

}

//********

void checktISR() {
 N = N + 1;
 if (N == 1) {
   StartTime = micros();
 }
 if (N == n) {
   EndTime = micros();
   TMeasNow = EndTime - StartTime;
 }

}

//***********

void MotorCntrl() {
 if ( (TMeas / (n - 1) > TTarget) && ((TMeas / (n - 1) - TTarget) < Deadband) ) {
   // Serial.print ("high deadband error   ");
   //  Serial.println ((TMeas-TTarget));
   digitalWrite (dirPin, HIGH);
   //digitalWrite (EnablePin, HIGH);
   StepSize = 1;
 }
 else if ( (TMeas / (n - 1) < TTarget) && ((TTarget - TMeas / (n - 1)) < Deadband) ) {
   // Serial.print ("lo deadband error   ");
   // Serial.println ((TTarget - TMeas));
   digitalWrite (dirPin, LOW);
   //digitalWrite (EnablePin, HIGH);
   StepSize = 1;
 }
 else if (TMeas / (n - 1) > (TTarget + Deadband)) {  //deadband implementation
   StepSize = 10;                                    //modified in later sketch to make StepSize proportional to error
   digitalWrite (dirPin, HIGH);
   //digitalWrite (EnablePin, LOW);
 }
 else if (TMeas / (n - 1) < (TTarget - Deadband)) {  //deadband implementation
   StepSize = 10;
   digitalWrite (dirPin, LOW);
   //digitalWrite (EnablePin, LOW);
 }

 stepper();
}

//***********

void stepper() {

 for (int Step = 0; Step < StepSize; Step++) {                //StepSize determines the angle to move vs step size
   digitalWrite(stepPin, HIGH);
   delayMicroseconds(wait); //allow stepper to make step
   digitalWrite(stepPin, LOW);
   delayMicroseconds(wait);
 }

}

//**********

void filter() {

 TMeas = TMeasNow * K + (TMeasPrev * (1 - K));
 TMeasPrev = TMeas;
}

//********

void loop() {
 if (N >= n) {
   noInterrupts();
   Serial.println(TMeas / ( n - 1));
   Serial.println(N);
   filter();
   MotorCntrl();
   N = 0;
   interrupts();
 }
}

I apologise for this error; the above ought to say
"using an interrupt i measure micros at interrupt 1 then again after N interrupts, giving me N*T micros which i compare after division (giving the period of my input waveform) to TTarget .