2

Consider this simple approach to make a light work in 3 different modes: on, off and blink:

attachInterrupt(buttonVector, switchMode, FALLING);

void loop() {
  switch(mode) {
    case 0: turnOff(); break;
    case 1: turnOn(); break;
    case 2: blinkOnce(); break;
  }
}

now if switchMode is something like this:

unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = 0;
void switchMode()
{
  interrupt_time = millis();
  // debounce measures
  if (interrupt_time - last_interrupt_time > 200) 
  {
    mode = (mode + 1) % 3;
  }
  last_interrupt_time = interrupt_time;
}

then once the button is pressed, the mode is changed and if we were in the blink mode (mode == 2) we'll wait until the blink is done (let's say that's 2 seconds) and only then the mode is actually switched to the "off" mode. Now, can I stop whatever was in the process of current loop, break it and get the "off" instantly?

PS Of'course there can be some workarounds, like

void switchMode()
{
  interrupt_time = millis();
  // debounce measures
  if (interrupt_time - last_interrupt_time > 200) 
  {
    mode = (mode + 1) % 3;
    if(mode == 0)
      turnOff();
    if(mode == 1)
      turnOn();
  }
  last_interrupt_time = interrupt_time;
}

but is there something more elegant and general? (note that if there's 4 modes: on, off, blink, blink_slowly, than this won't work)

0

No, you can't. An interrupt cannot affect the flow of the existing procedure except by passing data or status to it through a variable.

In multi-threading terms what you are talking about is a semaphore - a shared flag which one thread or context (the interrupt) sets and another thread or context (the main loop) reads and responds to.

If you want to respond to it fast you must write your main loop in such a way that it is possible to respond to it fast.

|improve this answer|||||
2

It is possible. You could use a setjmp()/longjmp() combination, or you could just manipulate the stack in assembly right before returning from the interrupt. This however, makes the program very difficult to reason about, which can easily lead to introducing very hard to find bugs. It is certainly not a good solution for your specific problem.

The straightforward solution to your actual problem is to make your loop non blocking, and this is achieved with the technique explained in the Blink without delay Arduino tutorial. Your case is a little bit more complex, as your LED can be in three modes. I suggest you consider instead that it can actually be in four modes by saying that, while it's blinking, the ON and OFF states are different modes:

static enum { OFF, BLINK_OFF, BLINK_ON, ON } mode;

I would let the interrupt handler take care of the button-controlled mode switching:

void switchMode()
{
    // Debounce.
    static uint32_t last_interrupt_time;
    uint32_t interrupt_time = millis();
    if (interrupt_time - last_interrupt_time < DEBOUNCE_TIME)
        return;
    last_interrupt_time = interrupt_time;

    // Switch modes.
    switch (mode) {
        case OFF:  // OFF -> blink
            digitalWrite(LED_PIN, HIGH);
            mode = BLINK_ON;
            break;
        case BLINK_OFF:  // blink -> ON
        case BLINK_ON:   // ditto
            digitalWrite(LED_PIN, HIGH);
            mode = ON;
            break;
        case ON:  // ON -> OFF
            mode = OFF;
            break;
    }
}

And the loop() only has to deal with the blinking:

void loop()
{
    // This only has to handle the timing of the blinking.
    uint32_t now = millis();
    static uint32_t last_toggle_time;
    switch (mode) {
        case BLINK_OFF:
            if (now - last_toggle_time > TIME_OFF) {
                digitalWrite(LED_PIN, HIGH);
                mode = BLINK_ON;
                last_toggle_time = now;
            }
            break;
        case BLINK_ON:
            if (now - last_toggle_time > TIME_ON) {
                digitalWrite(LED_PIN, LOW);
                mode = BLINK_OFF;
                last_toggle_time = now;
            }
            break;
        default:
            // Nothing to do in the other modes.
            break;
    }
}

Another option is to have the interrupt handler only set a flag to signal the button press. Then loop() would take care off all mode changes. This can be a good option if you want the “state machine” (the control of the mode) contained in a single function.

|improve this answer|||||
1

but is there something more elegant and general?

there are certainly ways to do what you want to do. whether they are elegant / generic is up to debate. however, they are all well known and widely used so i suppose they are at least accepted.

the simplest would be to prohibit interrupt at beginning of the switch / case block. "mode" will always be atomic.

the 2nd would be a double read on "mode".

  do {
    mode_shadow = mode; //save mode
    switch (mode_shadow) {
      case ...
    }
  } while (mode_shadow != mode); //continue to execute the switch block if mode has changed

this is a classic case to ensure atomicity without inhibiting interrupts.

the 3rd possible approach is to make the switch / case block dependent on "mode" at each step of execution. this is often used in high reliability computing where you aren't sure if a piece of data you read is ever valid. here you can ensure that through two ways: 1) affirmative: a statement is only executed if "mode" meets expectation. or 2) exclusive: all statements are not executed as soon as "mode" fails to meet expectation. This will drop you out of any of the branches of the switch / case block. atomicity isn't assured in this case but it is only violated for a short period of time.

the last I would mention would be typical of an OS where a "global" variable is maintained so that each task accesses that global variable to know what's going on. RTOS does that via messages for example. Not as real time as approach #1 / #2 above but it is fairly generic.

|improve this answer|||||
  • As for delays, I have to reimplement them, right? Something like void interruptableDelay(int milliseconds, byte *stopThis) { usigned long t0 = millis(); while(!*stopThis && millis() - t0 < milliseconds) ; } – YakovL Jul 16 '17 at 13:33

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.