I am reading the period of pulses from a 600ppturn shaft encoder, using the FreqPeriod library, by Martin Nawrath.

This works using counter1 and the comparator on pin6 and pin7 on the Arduino Uno.

 Counter and Capture unit

The period duration for 1-2500Hz is being used in a midi musical instrument to give a speed to volume and a note change at a fixed speed. This has worked well on the Uno but now I want to use a Mega (which does not have both comparator pins).

So I have tried using the Nick Gammon interrupt sketch. This has produced very erratic results returning both positive and many negative numbers (shown by using "volatile long period;" in the monitor.

Can you tell me what is happening and would it be better to use a counter instead of interrupts?

Hope I have given enough information to be getting on with, many thanks.

// Camera shutter speed timer
// Author: Nick Gammon
// Date: 15th January 2012

volatile boolean started;
volatile unsigned long startTime;
volatile unsigned long endTime;
volatile unsigned long period;
volatile unsigned long lfrq;

// interrupt service routine
void isr () {
    if (started)
        endTime = micros ();
        startTime = micros ();
    started = !started;
} // end of Pulse Width

void setup () {
    digitalWrite (2, HIGH); // pull-up
    Serial.begin (9600);
    Serial.println (" Pulse Width test = ");
    attachInterrupt (digitalPinToInterrupt (2), isr, CHANGE); //CHANGE RISING
} // end of setup

void loop () {
    if (endTime) {
        period = (endTime - startTime) * 32 ; // pulse width?
        Serial.print (" \n  ");
        Serial.print ( " period =  ") ;
        Serial.print (period) ;
        lfrq = 16000000.0 / period;
        Serial.print(" Hz = "); // one rev per sec = 26666 micros period
        Serial.print( lfrq );
        endTime = 0;
} // end of loop
  • If you simply want frequency and period, in the Interrupt Service Routine, simply count the times the ISR is called. In the loop() at regular intervals, say 10 mS, look at the count and multiply up to get the counts per second. If you use 10mS intervals, multiply by 100. That is your frequency in Hz. The inverse, i.e. divide 1 by your frequency in Hz, and that is your period. Then reset the count back to 0. – 6v6gt Mar 1 '17 at 18:45
  • 1
    Why are you using the analog comparator for what is a digital input? An encoder provides pulses, not a range of analog values. I have a page about rotary encoders if that helps. – Nick Gammon Mar 1 '17 at 20:09
  • I am reading the period of pulses from a 600ppturn shaft encoder, using FreqPeriod Library , by Martin Nawrath because I was very new and ignorant ( but now slowly learning) to programing. Martins sketch answered my need to produce a continuous series of pulse period measurements at low Hz to compute a rising volume with turning speed and to trigger a midi note change a speed set by a pot`. This sketch is the only one I found that worked out of the box . – Sam the man Mar 2 '17 at 14:33
  • 1
    Hold The Pages! I have got the above Nick Gammon sketch working to producing a chain of readings. – Sam the man Mar 2 '17 at 18:38
  • 1
    I think the problem started when I updated my IDE from 1.6.0 to 1.8.0. I have had problems compiling . The only way I found to make some sketches compile and upload was to include a library such as <SD.h> . I since realized that "digitalWrite" may not be supported , replacing with "pinMode" and removing <SD.h> ,the sketch now works as I am sure intended. I will now incorporate this into my existing musical instrument sketch and let you know how I get on. – Sam the man Mar 2 '17 at 19:05

Can you tell me what is happening [...]?

Not really, but here is a wild guess: your input signal is bouncy, the ISR fires too often, your loop cannot keep up with the rate, and you end up accessing endTime in the ISR and loop simultaneously.

If you have a scope, look at your encoder signal. Is it clean?

would it be better to use a counter instead of interrupts?

Using the input capture feature of a timer will give you the best possible accuracy, as you will have single-cycle resolution. In contrast, micros() has only 4 µs resolution (64 times worse) and the ISR may be delayed by up to ≈ 16 µs by the TIMER0_OVF ISR.

You can use the FreqPeriod library you linked to as an inspiration for writing your own timer-based code, but it will require some changes:

  • As pointed out by Nick Gammon in a comment, you don't need the analog comparator, nor the conditioning RC network, as the encoder's outputs are already digital. Or you may use a low-pass filter if the encoder is bouncy.

  • You will use the input capture pins of either timer 4 (digital pin 49) or timer 5 (digital pin 48). The other input capture pins are not available on the Mega's pin headers.

  • You will replace ISR(ANALOG_COMP_vect) by either ISR(TIMER4_CAPT) or ISR(TIMER5_CAPT) (the relevant “capture event” ISR). No need to adjust a comparator threshold in the ISR.

  • In the begin() method, you will leave the analog comparator alone, and you will enable the capture event interrupt.

Obviously you will have to study the datasheet before start coding.

  • Hold The Pages! I have got the above Nick Gammon sketch working to producing a chain of readings. Please excuse my lack of comprehension, using this sketch will I get a reading for all pulses? Although I do not seem to get two readings using pulse edge"CHANGE" as apposed to one reading with pulse edge "RISING" why is this, am I wrong in my thinking ? Two readings per pulse period I think should be better than one? I still can not comprehend what would be better for my needs counter or interrupts. Does the Martin Nawrath sketch use interupts or counter ,or both ? – Sam the man Mar 2 '17 at 19:52

While you are doing all those things with those volatile variables in the loop function, the interrupt can still be firing. If it fires in the middle of a read of startTime or endTime then you get might get part of the variable holding old value and part holding new value and when you put it together it makes garbage.

You need to do a cli() at the beginning of loop to turn off interrupts and quickly make local copies of all those variables and then sei() to turn interrupts back on. Now you can safely work with the copies you made.


Could this be so?

because the code is faulty: there is no assurance of atomicity between startTime and endTime. ie. the interrupts could have arrived, multiple times, while the if loop is executing.

two ways to fix it:

  1. calculate period in the isr; or

  2. clear endTime once the clock starts in the isr.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.