How do I disable pin change interrupts on the fly?

I have 3 buttons hooked up to an Arduino UNO. I need all three of them to individually set the same flag, I don't care if they share a common ISR, but I only want to listen to one button at a time. Currently, I have two of them hooked up to Port D (pins 7 and 5), and one hooked up to Port B (pin 12). Now my code is certainly wrong, I just don't understand what I'm doing wrong.

I have created handlers to disable/enable specific pins on the fly, such as when I only want to listen to the button on pin 12, I call:

void disable_pins_5_7() {
  PCICR |= 0b00000000; // Disable Port D, pins D1-D7
  PCMSK2 |= 0b00000000; // Disable pin 5 and 7 as an interrupt
}

and then I enable the button on pin 12 by calling:

void enable_D12() {
  PCICR |= 0b00000001
  PCMSK0 |= 0b00010000;
}

I also have two ISRs (and I'm not sure if this is allowed so maybe this is my problem?) that look like this:

ISR (PCINT0_vect) {
  delay(100);
  if (((millis() - lastPauseTime) >= 200) && (digitalRead(fillBtn) == 0)) {
    flag = true;
  }
  lastPauseTime = millis();
}

ISR (PCINT2_vect) {
  if (((millis() - lastPauseTime) >= 200) && (digitalRead(pin_7) == 0)) {
    flag = true;
  }
  if (((millis() - lastPauseTime) >= 200) && (digitalRead(pin_5) == 0)) {
    flag = true;
  }
  lastPauseTime = millis();
}

My issue is that for some reason, if I press for example the button on pin D12, and then press the button on pin D7, it will listen to the D7 button press despite having just called the function that (I think?) should be disabling the interrupts on pin D7, and furthermore in that case it should be disabling the interrupts on that port altogether, because I set port D back to 0 using PCICR |= 0b00000000;.

Any help would be extremely appreciated.

You should post your entire sketch.

Why do you need to use interrupts for buttons?

The delay() function (along with many others that depend on interrupts) does nothing useful in an ISR. In any case, slowing down interrupt response is a really bad idea.

There is no good reason to use interrupts to read buttons.

Pin change interrupts are best used to wake the processor from sleep.

What a crap :frowning:
To disable such an interrupt add
if (disabled0) return;
and set disabled0, disabled1 etc. in the main code.

I have a program that is configured like an FSM. I have an idle mode where all the code does is poll the buttons for presses, and 3 additional modes mode1, mode2, mode3. If for example the mode2 button is pushed during the idle mode, I swap to the mode2 state.

In any of the 3 additional modes, (mode1, mode2, mode3) I need to be able to pause, wherever I am in the mode2 loop by pressing the mode2 button, and then press the same button to resume. For the overall timing of the loop is appropriate enough to check a flag on each new loop to see if the pause button has been pressed, but the loop is not fast enough to detect a momentary button press consistently. So, I use an interrupt to set that flag, and then I enter a pause function. If the same button is pressed again, I have a condition that sees that button press and kicks me back into the mode2 loop which then continues to loop. Here is how my code is set up:

#include <avr/interrupt.h>

// Button Pin Assignments
int mode1Button = 12; //Pin D12
int mode2Button = 7; //Pin D7
int mode3Button = 5; //Pin D5

//Interrupt Flag & Last Pause Time
volatile bool paused = false;
unsigned long lastPauseTime = 0;

//FSM Mode Enumeration and Initial Mode
enum {idle, mode1, mode2, mode3};
int programState = idle;

void disable_D12() {
  PCMSK0 |= 0b00000000; // Disable pin 12 as an interrupt
}
void enable_D12() {
  delay(200); //This delay gets rid of an intial bounce when the pin is enabled.
  PCICR |= 0b00000001; // Enable port B, pins D8-D12
  PCMSK0 |= 0b00010000; // Enable pin 12 as an interrupt
}
void disable_D7() {
  PCMSK2 |= 0b00000000; // Disable pin 7 as an interrupt
}
void enable_D7() {
  delay(200); //This delay gets rid of an intial bounce when the pin is enabled.
  PCICR |= 0b00000100; // Enable Port D, pins D1-D7
  PCMSK2 |= 0b10000000; // Enable pin 7 as an interrupt
}
void disable_D5() {
  PCMSK2 |= 0b00000000; // Disable pin 5 as an interrupt
}
void enable_D5() {
  delay(200); //This delay gets rid of an intial bounce when the pin is enabled.
  PCICR |= 0b00000100; // Enable Port D, pins D1-D7
  PCMSK2 |= 0b00100000; // Enable pin 5 as an interrupt
}

void setup() {
  pinMode(mode1Button, INPUT_PULLUP);
  pinMode(mode2Button, INPUT_PULLUP);
  pinMode(mode3Button, INPUT_PULLUP);
  paused = false;
}

void loop() {
  switch(programState) {
    //////////////////////// IDLE MODE //////////////////////
    case idle:
      {
        while(true) {
          if (digitalRead(mode1Button == LOW)) {
            programState = mode1;
            paused = false;
            break;
          }
          if (digitalRead(mode2Button == LOW)) {
            programState = mode2;
            paused = false;
            break;
          }
          if (digitalRead(mode3Button == LOW)) {
            programState = mode3;
            paused = false;
            break;
          }
        }
        break;
      }
    //////////////////////// MODE 1 //////////////////////
    case mode1:
      {
        //ATTEMPTING TO DISABLE ALL INTERRUPTS EXCEPT FROM D12
        disable_D7();
        disable_D5();
        enable_D12();
        //Variables for Loop Timing
        unsigned long startTime = millis(); //When the mode starts
        unsigned long endTime = startTime; //The time when each loop of the while loop ends
        unsigned long timeBeforePause; //To record the current time before entering pause FXN
        unsigned long timeAfterPause; //To record the current time after exiting the pause FXN
        unsigned long pauseDuration; //To figure out how long it was paused for
        while ((endTime-startTime) <= 10000) {
          if (paused) { //Check if the pause flag is set true
            timeBeforePause = millis(); 
            pauseFunction(mode1Button); // ENTERS PAUSE FUNCTION HERE, PASS IN THE MODE1 BUTTON
            timeAfterPause = millis();
            pauseDuration = pauseDuration + (timeAfterPause - timeBeforePause);
            paused = false; // SET THE FLAG BACK TO FALSE
          }
          endTime = millis() - pauseDuration;
        }
        programState = idle;
        break;
      }
    //////////////////////// MODE 2 //////////////////////
    case mode2:
      {
        //ATTEMPTING TO DISABLE ALL INTERRUPTS EXCEPT FROM D7
        disable_D12();
        disable_D5();
        enable_D7();
        //Variables for Loop Timing
        unsigned long startTime = millis(); //When the mode starts
        unsigned long endTime = startTime; //The time when each loop of the while loop ends
        unsigned long timeBeforePause; //To record the current time before entering pause FXN
        unsigned long timeAfterPause; //To record the current time after exiting the pause FXN
        unsigned long pauseDuration; //To figure out how long it was paused for
        while ((endTime-startTime) <= 10000) {
          if (paused) { //Check if the pause flag is set true
            timeBeforePause = millis(); 
            pauseFunction(mode1Button); // ENTERS PAUSE FUNCTION HERE, PASS IN THE MODE1 BUTTON
            timeAfterPause = millis();
            pauseDuration = pauseDuration + (timeAfterPause - timeBeforePause);
            paused = false; // SET THE FLAG BACK TO FALSE
          }
          endTime = millis() - pauseDuration;
        }
        programState = idle;
        break;
      }
    //////////////////////// MODE 3 //////////////////////
    case mode3:
      {
        //ATTEMPTING TO DISABLE ALL INTERRUPTS EXCEPT FROM D5
        disable_D12();
        disable_D7();
        enable_D5();
        //Variables for Loop Timing
        unsigned long startTime = millis(); //When the mode starts
        unsigned long endTime = startTime; //The time when each loop of the while loop ends
        unsigned long timeBeforePause; //To record the current time before entering pause FXN
        unsigned long timeAfterPause; //To record the current time after exiting the pause FXN
        unsigned long pauseDuration; //To figure out how long it was paused for
        while ((endTime-startTime) <= 10000) {
          if (paused) { //Check if the pause flag is set true
            timeBeforePause = millis(); 
            pauseFunction(mode1Button); // ENTERS PAUSE FUNCTION HERE, PASS IN THE MODE1 BUTTON
            timeAfterPause = millis();
            pauseDuration = pauseDuration + (timeAfterPause - timeBeforePause);
            paused = false; // SET THE FLAG BACK TO FALSE
          }
          endTime = millis() - pauseDuration;
        }
        programState = idle;
        break;
      }
  }
}

void pauseFunction(int relevantButton) {
  while(true) {
    if (digitalRead(relevantButton) == LOW) {
      paused = false; //This may be redundant I'm just doing it out of an abundance of caution
      return;
    }
  }
}

ISR (PCINT0_vect) {
  // This is to handle a Mode1 button press
  if (((millis() - lastPauseTime) >= 200) && (digitalRead(mode1Button) == 0)) {
    paused = true;
  }
  lastPauseTime = millis();
}

ISR (PCINT2_vect) {
  // This is to handle a Mode2 button press
  if (((millis() - lastPauseTime) >= 200) && (digitalRead(mode2Button) == 0)) {
    paused = true;
  }
  // This is to handle a Mode3 button press
  if (((millis() - lastPauseTime) >= 200) && (digitalRead(mode3Button) == 0)) {
    paused = true;
  }
  lastPauseTime = millis();
}

My design constraints are the following:

  • 3 buttons
  • Each button is its own play/pause/resume
  • The buttons must momentary
  • The buttons cannot be held down to trigger a pause/resume, it must still just be a normal press

I do not see how to do this without interrupts. I'm not a pro by any shot, just a student, and I can't find enough information on enabling/disabling interrupts. That's why I'm appealing to people who probably know more than I do. Also I threw together this example code on a bus so if there's a syntax mistake or something of the like please excuse it.

Really easily. digitalRead(button) takes a couple of microseconds, so it introduces no significant overhead in the loop function.

Start by studying the StateChangeDetection example in the Arduino IDE. Eventually you will end up with a State Machine.

All blocking code like this has to go, though, and in any case is very poor programming practice.

void pauseFunction(int relevantButton) {
  while(true) {
    if (digitalRead(relevantButton) == LOW) {
      paused = false; //This may be redundant I'm just doing it out of an abundance of caution
      return;
    }
  }
}

I understand how to do a digitalRead. I literally do it later in the code. But how does that help me if that line isn’t run right when I press the button? That’s the issue I was encountering when I tried to use digitalReads instead of interrupts. I need to be able to press pause at at point during a mode and then have it pause on the next loop through. If I just use a digitalRead then I can’t set a flag or enter any sort of pause state unless that line is being executed at the exact moment I press the button, unless I poll the button after every single line in the mode. That sounds like bad practice to me. Maybe not.

Also I have to use blocking code unless again you have a better suggestion. Each loop needs to run for a total of X seconds, in this example I’ve chosen 10. When paused, it must then resume and execute the remainder of the time. For example, if I pause after 3 seconds it must resume and execute the next 7, and then terminate.

The loop function should run multiples of 10,000 times per second. You can't possibly press and release the button fast enough to be missed, using digitalRead(). And if that isn't fast enough, a direct port read takes less than 100 nanoseconds.

Programming embedded devices as you are now starting to do takes a completely different mindset than all other types of programming.

Also I have to use blocking code unless again you have a better suggestion.

Again, completely different mindset. Just avoid all unnecessary actions. Never block. The State Machine approach takes care of that automatically.

I have a program that is configured like an FSM.

Well, sort of, except that some of the states are blocking. Not a productive approach.

If you continue asking these sorts of questions on this forum, this is the sort of advice you will be getting.

No. It does not. Use something like PCMSK2 &= 0b11011111 ;

What is the goal of the program?  What are you trying to do in the real world?   I'm getting an X-Y vibe.

A FSM programming concept shouldn't use with blocking code.
You need the interrupt exactly because your code is blocking. First of all, you need to get rid of the blocking code - and then the need for interrupts will disappear by itself

Do you have any resources or suggestions on how to do so? I don’t know how else to time an event (e.g. have it only last 10 seconds) without using millis() and a while loop. I would happily get rid of the blocking code if it’s that bad — but I just wouldn’t know where else to start and I’ve tried googling things like FMSs without while loops and FSMs no blocking and can’t find much. It’s also worth noting this code does not necessarily need to be a perfect FSM, when I said an FSM I just really meant that I had states that can be entered which then feed back to an initial state (which is FSM-like).

It is really very simple. Instead of programming a state to block, the state checks whether the wait time has elapsed, and if not, immediately exits. If yes, perform the action and update the state variable. Exactly like the Blink Without Delay example.

Tutorials:

I'd say that this is a good exercise to learn about pin change interrupts. However, as others have said, in this particular case, there are better methods of solving this particular problem. A finite state machine model is also appropriate but, apart from the blocking code issue, the starting point should be a state diagram of which this is an example. It shows (or should show) the states, the transitions to the successor state, the trigger for the transition and any action to be taken on the transition.

Makes enough sense, I’ll have to rethink my timing logic. I will give a shot, thanks.