interrupts

I am using the “attachInterrupt” to read an encoder line on pin 8 of a Due board.

Once I read the encoder, I try and send the data off to a computer application.

The problem I have is that if the leading edge of encoder signal rises during my communication routine, some of the variables I am sending might change. Yet, I don’t want to miss a leading edge by adding a disable interrupt and re-enabling it after data transmission.

Currently I use several serial.print statements to get my data out. Is there a way to stream line my output into one packet and avoid having my data change or miss an encoder edge.

Attached is my code.

sketch_apr17a.ino (5.3 KB)

Make a copy of the value in another variable and use it when communicating. The one that is being updated by the encoder can still be updated while the copy you made remains where it was and you can complete your communication.

The way this is typically done is to disable interrupts, make a “protected” copy of the variables which are change within the ISR, and re-enable the interrupts. This should take only a few processor clock cycles and you not likely to miss any data. You then work with the unchanging copied variables. I have added the copies and patched them into your code. All the variables which change within the ISR should be declared volatile as well.

volatile unsigned long ltime, stime, dtime; // ltime => last or previous time, stime => current time, dtime => difference time
volatile long line = 0; // Line is initialized at zero. This will be the starting position.
long copy_line;
unsigned long copy_dtime;


int pin = 8;
volatile int state = LOW;
int ppr = 720;


void setup() {
  // Setup will be used to set up our pins and create and interrupt routine.
  // This code will only be run once during the program operation. It is not in the loop structure.

  pinMode(pin, INPUT);                     // pin 8 is an input pin (encoder counts)
  pinMode(pin + 1, INPUT);                 // pin 9 is an input pin (clockwise/counter clockwise)
  pinMode(pin + 2, INPUT);                 // pin 10 is an input pin (not used).
  Serial.begin(115200);                    // Set the baud rate on the serial port to 115.2Kb
  attachInterrupt(pin, meas, RISING);      // Pin 8 will generate an interrupt on the rising edge of the attached signal and run
  // a program calles "meas" when that happens.
}


//-----------------------------------------  Meas  -----------------------------------------------------------//
//  This subroutine will measure the time between encoder signals. It also determinses position by incrementing and decrementing
//  the integer "line" by reading pin 9 and seeing if it is a positive signal or a negative signal.

void meas()
{
  stime = micros();            // stime = current time in microseconds.

  if (digitalRead(pin + 1))    // If pin 9 is true (positive) when the rising edge of pin 8 is detected,
    line++;                  // then we increment the integer "line" (clockwise rotation).
  else                        // Otherwise you will decrement the integer "line" because the encoder is rotating counter clockwise.
    line--;


  dtime = stime - ltime;     // "difference in time = the current time in microseconds - the "last or previous" time in microseconds.
  ltime = stime;             // The last or previous time is now equal to the current time in microseconds.

}

//------------------------------------------------------------------------------------------------------------//



//-------------------------------------------------------------------------------------------------------------------------------------//
//-----------------------------------------------  Main Loop  -------------------------------------------------------------------------//
//-------------------------------------------------------------------------------------------------------------------------------------//

void loop()
{
  noInterrupts();
  copy_line = line;
  copy_dtime = dtime;
  dtime = 0;
  interrupts();

  int c;

  if (copy_dtime && copy_line)  // If the difference time is positive and line is also a positive value, do the following.
  {
    // We need to disable interrupts here because dtime and line could change between the data packets as they are sent.
    //detachInterrupt(pin);              // disable the interrupt on pin 8. In other words, don't respond to the encoder signals.


    Serial.print(copy_line);             // send the variable line to the application.
    Serial.print(" ");              // send a space character to the application.
    Serial.print(copy_dtime / 1000.0);   // send the difference time in microseconds diveided by 1000
    Serial.print(" ");              //send another space character.
    Serial.println((60.0 * 1000.0 * 1000.0) / (ppr * copy_dtime)); // send 60 million/(720 x the difference time)

    //We should re-enable the interrupts here once communication has ended and all the data has been sent.

    //attachInterrupt(pin, meas, RISING);   // initiate the external interrupt to pin 8 to run the subroutine meas on the
    // rising edge of the signal on pin 8.

    //dtime=0;   // set dtime to 0 microseconds.
  }


  if (Serial.available() > 0) // if data is available on the serial bus, do the following:
  {
    c = Serial.read();      // read the data in the buffer.

    switch (c)
    {
      case 'r':          // if the data is an 'r' character initiate variables line and dtime to zero
        line = 0;
        dtime = 0;
        attachInterrupt(pin, meas, RISING);   // initiate the external interrupt to pin 8 to run the subroutine meas on the
        // rising edge of the signal on pin 8.
        //           attachInterrupt(pin+1, meas, RISING);
        Serial.println("----------------------------------------------------------");  // Transmit this back to the application.
        break;                                                                         // Get out of the switch routine.


      case 's':        // if though the data read is an 's' character, initiate the variables line and dtime to zero.
        line = 0;
        dtime = 0;
        detachInterrupt(pin);              // disable the interrupt on pin 8. In other words, don't respond to the encoder signals.
        //           detachInterrupt(pin+1);
        break;                             // Get out of the switch routine.

    }               // if the data received is neither 's' or 'r', we do nothing and return back up to the top of the "loop".
  }
}

EDIT-- DeltaG posted while I was putting this together, but you might find the example useful.

attachInterrupt()/detachInterrupt() should NOT be used to turn interrupts on and off, other than when starting up or shutting down. It is about the LEAST efficient way to disable interrupts, and you WILL lose interrupts.

If you need to read any non-byte variable that gets updated by an interrupt handler, simply use noInterrupts()/interrupts() around the data access, to ensure you get an atomic read of the data.

volatile int x = 0;

void handler(void)
{
if (digitalRead(pin))
    x++;
else
    x--;
}


void loop()
{
    ...
    noInterrupts();
    int y = x;
    Interrupts();

    ...do whatever you want with y...
}

Regards, Ray L.

That depends on if you want to turn off ALL interrupts or just the one you're interested in. For example if you want to use millis or delay or the Serial line during the time you need your interrupt turned off then you have to go with the detachInterrupt.

Delta_G:
That depends on if you want to turn off ALL interrupts or just the one you’re interested in. For example if you want to use millis or delay or the Serial line during the time you need your interrupt turned off then you have to go with the detachInterrupt.

There is absolutely nothing to lose by turning off all interrupts for only long enough to make a copy of a volatile variable. And, as I said, using attach/detachInterrupt you stand a good chance of missing interrupts entirely. under what circumstance do you believe turning off interrupts for a few microseconds would do any harm whatsoever? Except for truly extraordinary circumstances, if your application is THAT sensitive to interrupt latency, it is probably never going to be very robust.

Regards,
Ray L.

But they're not always turning them off for only the copying of one variable. Your comment said the only time you should turn off one interrupt by itself was startup or shutdown. I can think of other scenarios. It obviously wouldn't be this one. But there are certainly times when you want to turn off only the pin interrupt and keep your timers and other things working. It depends on how much work you need to do while they're off.