Interrupt Problems

I'm using a self built and encoded Optical Shaft Encoder, and I recently switched my detection method from constant checking to interrupts using the attachInterrupt() function.

Problem: There are certain times during my project when I wish to stop the interrupts from taking place, therefore I use the detachInterrupt() function. [u]However[/u], it would seem that the interrupts are still somehow taking place even when they are detached. This is the weird thing though, they don't operate properly as they should....Let's take a look at some code:

Interrupt Declarations:

attachInterrupt(0, intA, CHANGE);
attachInterrupt(1, intB, CHANGE);

Interrupt Example:

//upon interrupt, sets the direction of movement, then runs
//runBoth() which dtermines what to do bases on that direction,
//aka, incrementing or decrementing the degree count.
void intA() {
  currentStateA = digitalRead(outerLoopA);
  if (lastStateA == 1 & currentStateA == 0) {
    directionA = "10";
  } else {
    directionA = "01";
  }
  lastStateA = currentStateA;
  changeA = 1;
  runBoth();
}

Button that does things (also an easy way to test this error)

//checks to see if the maintenance button has been pushed, if so, puts the
//device in a loop, disabling all functionality, until the
//button is hit once again.
void checkMaint() {
  int tempDegree = degree;
  if (digitalRead(maintenanceButton)) {
    while (digitalRead(maintenanceButton)) {delay(2);}
    detachInterrupt(0);
    detachInterrupt(1);
    Serial.println("Now entering Maintenance Mode.");
    Serial.println("Press the Maintenance Button again to Exit");
    while (!digitalRead(maintenanceButton)) {     //NOTE: This Loop is Normally Empty, The following lines are for testing only
      checkOpto();
      delay(2);
      if (degree != tempDegree) {
        Serial.println(degree);
        tempDegree = degree;
      }
    }
    while (digitalRead(maintenanceButton)) {delay(2);}
    attachInterrupt(0, intA, CHANGE);
    attachInterrupt(1, intB, CHANGE);  
    Serial.println("Now leaving Maintenance Mode.");
    Serial.println("Recalibration is strongly recommended!");
  }
}

OK, now that you have been fed some code, hopefully you understand what the program is supposed to do. When an interrupt happens, the program recognizes that, sets the direction, and then proceeds to increase or decrease the degree count (remember this is an optical shaft encoder using a repeating Gray Code (4-bits long)).

For the purposes of testing, I am simply using some pushbuttons to simulate the Shaft Encoder as otherwise testing would be quite laborious. A typical sequence in one direction would be the following: BUTTON1: PUSHED BUTTON2: PUSHED BUTTON1: UNPUSHED BUTTON2: UNPUSHED

That simulates the sequence 00?01?11?10?00, tasty Gray Code.

Back to the Problem: When I flip the Maintenance Switch, both interrupts should be disconnected, so even if the Opto-Couplers change state, nothing should happen...[u]However[/u], if either button is pushed, the degree count will only decrement, but it only decrements once. This means, if I push BUTTON1 during Maintenance Mode, it doesn't matter how many times I push it, the degree count will go down by only one. BUTTON2 is also affected. If I don't push any buttons, nothing happens (which is great), and if I push both buttons, the degree count is decremented by a total of 2 (how lovely, it can add 1+1=2).

The REALLY fishy thing is that the degree does not change until the interrupts are reattached. As you can see in the following code (this is my loop that normally has nothing in it):

while (!digitalRead(maintenanceButton)) {
      checkOpto();
      delay(2);
      if (degree != tempDegree) {
        Serial.println(degree);
        tempDegree = degree;
      }
    }

I am trying to check here to see what is going on while my interrupts are disabled and NOTHING should be going on. This loop still does nothing, as I stated above, the degree only changes once the interrupts are reattached.

Summary: Interrupts are disabled, but still have an affect on the program if buttons are pushed (and only if they are pushed), and in a really weird way too (abnormal affecting of the program, not what it should even normally be doing if they are attached).

Any help?

**And before you ask, "What do you think is wrong cypherrage?" I have no freaking clue. As far as I am concerned, the interrupts are completely disabled and therefore, pushing/unpushing/any combination of the two, on either or both buttons, should have no affect on the program whatsoever. And, to confuse me further, the problem doesn't even act logically so that I can figure it out (as it only decrements, which means it doesn't seem to be following my algorithm at all, but some random bug or something).

****If you want to see my "runBoth()" function I can put it on here, but I can assure you it works under normal circumstances (using pinMode(xyz, INPUT) and constantly checking that Pin), and it is really simple, aka compare the current state with the last state, then increment or decrement based on the pattern given. It is large and bulky, but if someone actually wants to see it (as people usually want to see all the superfluous code anyway), I can post it.

directionA = "10";

A really good reason why we ask you to post all your code.

Your wish is my command, this is a new “beta” version of my program, so it isn’t near completion, as I was just trying to test the new interrupts with various functions:

//Single Arduino Implementation (TBA)
//

//                          Pins
//        Communication                 Analog In                  Side Strips
// SCL 21:                         A15:                      5V:               5V: 
// SDA 20:                         A14:                      22:               23: 
// RX1 19:                         A13:                      24:               25: 
// TX1 18:                         A12:                      26:               27: 
// RX2 17:                         A11:                      28:               29: 
// TX2 16:                         A10:                      30:               31: 
// RX3 15:                          A9:                      32:               33: 
// TX3 14:                          A8:                      34:               35:                  
//   RX?0:                          A7:                      36:               37: 
//   TX?1:                          A6:                      38:               39: 
//                                  A5:                      40:               41: 
//             PWM                  A4:                      42:               43: 
//      2: Optocoupler_A            A3:                      44:               45: 
//      3: Optocoupler_B            A2:                      46:               47: 
//      4:                          A1:                      48:               49: 
//      5:                          A0:                      50:               51: 
//      6:                                                   52:               53: 
//      7:                              Power               GND:              GND: 
//      8:                         Vin: 
//      9:                         GND: 
//     10:                         GND: 
//     11:                          5V: 
//     12:                        3.3V: 
//     13:                       RESET: 
//    GND:                       IOREF: 
//   AREF:                       Extra: 
//  Extra: 
//  Extra: 
//

//    Libraries
#include <EEPROM.h>
#include <LiquidCrystal.h>
#include <RTClib.h>
#include <SD.h>
#include <Wire.h>

//     Buttons  
#define testButton (48)
#define maintenanceButton (49)
#define calibrationButton (50)
#define ledButton (51)
#define enterButton (52)

//    Input Pins
#define outerLoopA (2)
#define innerLoopB (3)

//   Output Pins
#define motorPower (22)
#define motorPWM (23)
#define motorNPWM (24)
#define lightsPowerA (25)
#define lightsPowerB (26)
#define bellsPower (27)
#define brakePower (28)
#define ledOutput (30)
#define chipSelect (29)


//     EEPROM
#define calibrationBias (5)
#define totalCalibration (6)
#define degreeMemory (7)

//   Instantiation
File Errors;
File Status;
int calibrationDegreeCount;
int minimumDegree = 0;
int maximumDegree = EEPROM.read(calibrationBias);
volatile int degree = EEPROM.read(degreeMemory);
volatile int lastStateA;
volatile int lastStateB;
volatile int currentStateA;
volatile int currentStateB;
volatile int changeA;
volatile int changeB;
volatile int changeDegree = 0;
volatile int changeRotation = 0;
String directionA;
String directionB;
String rotation;
String prevRotation;


void setup () {
  //Inputs
  pinMode(testButton, INPUT);
  pinMode(maintenanceButton, INPUT);
  pinMode(calibrationButton, INPUT);
  pinMode(ledButton, INPUT);
  pinMode(enterButton, INPUT);
  //Outputs
  pinMode(motorPower, OUTPUT);
  pinMode(motorPWM, OUTPUT);
  pinMode(motorNPWM, OUTPUT);
  pinMode(lightsPowerA, OUTPUT);
  pinMode(lightsPowerB, OUTPUT);
  pinMode(bellsPower, OUTPUT);
  pinMode(brakePower, OUTPUT);
  pinMode(ledOutput, OUTPUT);
  pinMode(chipSelect, OUTPUT);
  //Initializations
  Serial.begin(9600);
  attachInterrupt(0, intA, CHANGE);
  attachInterrupt(1, intB, CHANGE);
  lastStateA = digitalRead(outerLoopA);
  lastStateB = digitalRead(innerLoopB);
  Serial.println("Initialization Complete.");
}

void loop() {
  checkOpto();
  checkButtons();
}

void checkButtons() {
  checkCalib();
  checkMaint();
}

void checkOpto() {
  if (changeDegree) {
    Serial.println(degree);
    changeDegree = 0;
  }
  if (changeRotation) {
    Serial.println("The direction is now " + rotation);
    changeRotation = 0;
  } 
}

//upon interrupt, sets the direction of movement, then runs
//runBoth() which dtermines what to do bases on that direction,
//aka, incrementing or decrementing the degree count.
void intA() {
  currentStateA = digitalRead(outerLoopA);
  if (lastStateA == 1 & currentStateA == 0) {
    directionA = "10";
  } else {
    directionA = "01";
  }
  lastStateA = currentStateA;
  changeA = 1;
  runBoth();
}

//upon interrupt, sets the direction of movement, then runs
//runBoth() which dtermines what to do bases on that direction,
//aka, incrementing or decrementing the degree count.
void intB() {
  currentStateB = digitalRead(innerLoopB);
  if (lastStateB == 1 & currentStateB == 0) {
    directionB = "10";
  } else {
    directionB = "01";
  }
  lastStateB = currentStateB;
  changeB = 1;
  runBoth();  
}

void runBoth() {
  if (changeA) {
    if (directionA == "01") {
      if (currentStateB) {
        rotation = "CW";
      } else {
        rotation = "CCW";
      }
    } else {
      if (currentStateB) {
        rotation = "CCW";
      } else {
        rotation = "CW";
      }
    }
    changeA = 0;
  } else if (changeB) {
    if (directionB == "01") {
      if (currentStateA) {
        rotation = "CCW";
      } else {
        rotation = "CW";
      }
    } else {
      if (currentStateA) {
        rotation = "CW";
      } else {
        rotation = "CCW";
      }
    }
    changeB = 0;
  }
  if (rotation == "CW") {
    degree++;
  } else if (rotation == "CCW") {
    degree--;
  }
  changeDegree = 1;
  if(rotation != prevRotation) {
    changeRotation = 1;
  }
  prevRotation = rotation;
}

//Checks to see if the Calibration button has been pushed, if so, initiates
//the calibration function.
void checkCalib() {
  if (digitalRead(calibrationButton)) {
    while (digitalRead(calibrationButton)) {delay(2);}
    if (EEPROM.read(totalCalibration)) {
      Serial.println("Calibration has already been performed.");
      Serial.println("Press the Calibration button again to continue with Calibration.");
      Serial.println("If this was a mistake, press Enter now to exit.");
      while (!digitalRead(calibrationButton) && !digitalRead(enterButton)) {delay(2);}
      if (digitalRead(calibrationButton)) {
        while (digitalRead(calibrationButton)) {delay(2);}
        calibrate();
      } else if (digitalRead(enterButton)) {
        while (digitalRead(enterButton)) {delay(2);}
        Serial.println("Exiting...");
      }
    } else {
      calibrate();
    }
  }
}

void calibrate() {
  Serial.println("Beginning Calibration...");
  EEPROM.write(totalCalibration, 0);
  detachInterrupt(0);
  detachInterrupt(1);
  Serial.println("Please move the gate arm to Horizontal Position, then press Enter.");
  while (!digitalRead(enterButton)) {delay(2);}
  while (digitalRead(enterButton)) {delay(2);}
  attachInterrupt(0, intA, CHANGE);
  attachInterrupt(1, intB, CHANGE);
  degree = 0;
  Serial.println("Please move the gate arm to the maximum Vertical Position, then press Enter.");
  while (!digitalRead(enterButton)) {
    checkOpto();
    delay(2);
  }
  while (digitalRead(enterButton)) {delay(2);}
  EEPROM.write(calibrationBias, degree);
  EEPROM.write(totalCalibration, 1);
  EEPROM.write(degreeMemory, degree);
  //changeDegree = 0;
  //changeRotation = 0;
  Serial.println("Calibration complete.");
  Serial.print("The calibration bias is now: ");
  Serial.println(degree);
  Serial.println("Resuming Regular Services...");
}

//checks to see if the maintenance button has been pushed, if so, puts the
//device in a loop, disabling all functionality, until the
//button is hit once again.
void checkMaint() {
  int tempDegree = degree;
  if (digitalRead(maintenanceButton)) {
    while (digitalRead(maintenanceButton)) {delay(2);}
    detachInterrupt(0);
    detachInterrupt(1);
    Serial.println("Now entering Maintenance Mode.");
    Serial.println("Press the Maintenance Button again to Exit");
    while (!digitalRead(maintenanceButton)) {
      checkOpto();
      delay(2);
      if (degree != tempDegree) {
        Serial.println(degree);
        tempDegree = degree;
      }
    }
    while (digitalRead(maintenanceButton)) {delay(2);}
    attachInterrupt(0, intA, CHANGE);
    attachInterrupt(1, intB, CHANGE);  
    Serial.println("Now leaving Maintenance Mode.");
    Serial.println("Recalibration is strongly recommended!");
  }
}

//initiates a soft internal reset by jumping the code back up to the first line.
void softReset() {
  EEPROM.write(degreeMemory, degree);
  delay(2000);
  asm volatile ("  jmp 0");
}

Just in case you were wondering, which I doubt you were, but just in case, this problem also occurs in the calibrate() function where I also detach and reattach my interrupts.

Also, I'm glad I hadn't added anything else to the program, I just barely beat the character limit (tried to post the above sentence in a Modify, and it failed)

What's with all the Strings? (and strings)

Simple numeric constants would save a lot of memory (in the case of Strings) and the F() macro would save a lot of RAM in the case of all those constant strings.

AWOL: What's with all the Strings? (and strings)

Simple numeric constants would save a lot of memory (in the case of Strings) and the F() macro would save a lot of RAM in the case of all those constant strings.

I only see 4 Strings, unless you were referring to something else, like all the Serial.print() statements, which honestly won't be there in the final iteration of the program, those are only there as placeholders while I am testing everything on my computer

*edit, if you were referring to the big fat runBoth() where I have lots of Strings and stuff, well, call me a rookie programmer. Mostly I used CW and CCW for purposes of printing, whereas obviously a simple 0 or a 1 would suffice. Once I finish this program and I no longer need to print anything over Serial, I can change all those strings to simple integers.

It's not going to be in the final iteration, so it won't possible be the cause of your issues now? Not sure I follow the logic on that one...

I only see 4 Strings

That’s four too many.

Arrch: It's not going to be in the final iteration, so it won't possible be the cause of your issues now? Not sure I follow the logic on that one...

What are you referring to, print statements and strings? There are no problems with my program outside of the way the attachInterrupt() and detachInterrupt() functions operate. I already know that print statements do not work during Interrupts and that's why I don't have any.

AWOL:

I only see 4 Strings

That's four too many.

Thanks, I will take them out when I have more time to focus on optimizations, but I do not think they are the cause of the problem. If they are, any ideas why?

cypherrage: What are you referring to, print statements and strings? There are no problems with my program outside of the way the attachInterrupt() and detachInterrupt() functions operate. I already know that print statements do not work during Interrupts and that's why I don't have any.

Too many strings, and well, any Strings, can cause unexpected memory issues. The operative word here is unexpected.

Arrch:

cypherrage: What are you referring to, print statements and strings? There are no problems with my program outside of the way the attachInterrupt() and detachInterrupt() functions operate. I already know that print statements do not work during Interrupts and that's why I don't have any.

Too many strings, and well, any Strings, can cause unexpected memory issues. The operative word here is unexpected.

1) I have never experienced any problems remotely close to this ever before 2) This is the only current problem in the code, everything else works fine. 3) The problem only arose when I started using the Interrupts protocol (obviously, because it directly stems from those interrupts). 4) 2 People have now told me that I have "too many strings". If you think that statement is an educated guess to the cause of the problem, then say so. Pointing something out and saying it might be a problem doesn't really help me unless I go change everything and test it (which I have no plans on doing right now unless someone specifically states that the Strings are most likely the problem).

cypherrage: 1) I have never experienced any problems remotely close to this ever before

Operative word being unexpected.

cypherrage: 2) This is the only current problem in the code, everything else works fine.

Operative word being unexpected.

cypherrage: 3) The problem only arose when I started using the Interrupts protocol (obviously, because it directly stems from those interrupts).

Operative word being unexpected.

4) 2 People have now told me that I have "too many strings". If you think that statement is an educated guess to the cause of the problem, then say so. Pointing something out and saying it might be a problem doesn't really help me unless I go change everything and test it (which I have no plans on doing right now unless someone specifically states that the Strings are most likely the problem).

Too many strings is always an educated guess. There aren't many people who are going to have the exact hardware you have and are willing to set everything up like you have it in order to check the available memory. For the record, I don't think it's memory related, but that doesn't mean it's not worth the 5 minutes it takes to surround some of the strings with the F() macro and give it another go, just to be sure.

I was actually going to say something about that, right now it is simply the current model of Arduino Mega with 5 buttons hooked up, the 2 simulated opto-coupler buttons, enterButton, calibrateButton, and maintenanceButton, with the serial line to the computer. I didn't expect anyone to try my code for themselves, but just in case you were that vigilant to help me, the current setup takes probably 60 seconds to wire up if you have a Mega and 5 buttons/switches.

When I'm back at work tomorrow I will change the strings out.

In the OP under Back to the Problem:

When does this decrement happen? Can you tell if it is happening while the interrupt is disabled as soon as the button is pushed or could the actual decrement of the variable happen right at the end when the interrupts are turned back on?

If you think that statement is an educated guess to the cause of the problem, then say so

It's an uneducated guess - it never even went to kindergarten - but it is based on decades of debugging experience. When faced with a problem (particularly when it isn't one's own problem), start by a spot of housekeeping - clear out the stuff that patently should not be there. Using a String (or even a string) to store a condition that could (and should) be stored in a couple of bits is perverse.

Quite often, you'll find that the process of tidying-up will reveal all sorts of useful things. Just like real-life.

Summary: Interrupts are disabled, but still have an affect on the program if buttons are pushed (and only if they are pushed), and in a really weird way too (abnormal affecting of the program, not what it should even normally be doing if they are attached).

Interrupts are remembered unless you clear the interrupt flag for that interrupt.

http://www.gammon.com.au/interrupts

I wouldn't be writing to a String class inside an interrupt personally. Too much likelihood of corruption.

Please note that in versions of the IDE up to and including 1.0.3, the String library has bugs as discussed here and here.

In particular, the dynamic memory allocation used by the String class may fail and cause random crashes.

I recommend reworking your code to manage without String. Use C-style strings instead (strcpy, strcat, strcmp, etc.), as described here for example.

Alternatively, install the fix described here: Fixing String Crashes

Preferably upgrade your IDE to version 1.0.4 or above at: http://arduino.cc/en/Main/Software

Delta_G: In the OP under Back to the Problem:

When does this decrement happen? Can you tell if it is happening while the interrupt is disabled as soon as the button is pushed or could the actual decrement of the variable happen right at the end when the interrupts are turned back on?

If you notice under the checkMaint() function, there is this loop, which is normally supposed to do nothing (wait for the button to be pressed), but instead I added a few "debugging" print statements:

while (!digitalRead(maintenanceButton)) {
      checkOpto();
      delay(2);
      if (degree != tempDegree) {
        Serial.println(degree);
        tempDegree = degree;
      }
    }

If the integer degree changes at all during this time, I would know about it, it isn't until I reattach the interrupt that the value actually changes (I can't do any line for line variable monitoring due to IDE restrictions, but this is my best possible conclusion). So, we are stuck in this loop, and then I push BUTTON1 (temporarily represents the first Opto-coupler). In a normal situation, if I push this button, it will represent an increment by 1, and then if I release the button (without touching BUTTON2), it should decrement by 1. Instead, nothing happens, and then when I reattach the interrupts the variable degree changes. Unless there is a problem with that little "test" if statement I put in there, degree is indeed staying a constant during that loop.

To AWOL and Nick, is there anything else important I should note about interrupts? I read your page Nick and the only things I noted were, don't use print statements, don't read serial data, and don't use delay().

To AWOL and Nick, is there anything else important I should note about interrupts?

Get what it is you have to do done as quickly and cheaply as possible, and get out.

AWOL:

To AWOL and Nick, is there anything else important I should note about interrupts?

Get what it is you have to do done as quickly and cheaply as possible, and get out.

Is there any difference between F() and PROGMEM? Should I be using PROGMEM for everything since I have a ton of space on the Mega?

Is there any difference between F() and PROGMEM?

From a memory usage point-of-view, no, not really. The F() macro just makes it easier to print strings from program memory. I'd always recommend using it if possible - you never know when you might need those few extra byes of RAM.