3

I'm struggling with something. My CPU sits in a switch case state machine like this:

switch(expression) {

   case constant-expression_1  :
      statement(s);
      break; /* optional */

   case constant-expression_2  :
      statement(s);
      break; /* optional */

   /* you can have any number of case statements */
   default : /* Optional */
   statement(s);
}

I have an interrupt that occurs on a button press. In the interrupt, I change the switch expression to the case I want to go next, like this:

void ISR(void){
   expression = constant-expression_2;
}

After the interrupt has been handled, I want the CPU to return directly to the case being switched to, and to not continue where it left off.

Does anyone have any good ideas on how to implement this, if it's possible? I'd prefer to not have to wait for the code to finish what was happening in the previous case before going to the one being switched to.

Thanks in advance!

  • 1
    A rather drastic, solutions would be be to reset the chip in the ISR. – Gerben Apr 14 '16 at 15:26
1

It is certainly possible, although I do not think there is any clean way of doing it...

Here is an idea that may work: right before the switch statement, you save the current context with the setjmp() function. Then, in the ISR, you restore that context with longjmp(). This is kind of like a goto statement that works across functions:

#include <setjmp.h>

jmp_buf env;

ISR(FOO_vect) {
    expression = ...;
    longjmp(env, 1);  // go to start of loop
}

void loop() {
    setjmp(env);  // landing point from ISR exit
    switch (expression) {
        ...
    }
}

Notice that the normal exit sequence of an ISR (the reti instruction) re-enables the interrupts. Since we are skipping this exit sequence, we have to enable them explicitly upon return from setjmp() interrupts are re-enabled not by the reti instruction but by longjmp() restoring the CPU's status register.

Warning: This is a (very dirty) hack that I have not tested. If I were to use this trick, I would first:

  1. Carefully examine the generated assembly to convince myself it does the right thing.
  2. Test, test, test...

In case of problems, debugging this may be harder than just testing the expression inside every slow function you have.

Addendum: Although I am saying that there is no clean way of doing what you intend, there certainly is a clean way of achieving the same end result: just make sure your switch statements return fast. My preferred way of doing this would be to make sure no function ever waits. For example:

  • A routine reading sensors would not wait for them to respond. Instead, it would return immediately if no reading is available. If a reading is available, it would retrieve it, send a query to the next sensor in the list, then return.
  • A communication routine would not wait for complete messages. Instead, it would return immediately if no byte is available. If a byte is available, it would buffer it and return. Unless it's the last byte of a complete message, in which case it would process the message.

Obviously this could require a heavy refactoring of your code, probably using finite state machines (see for example the tutorials here and here). It may be easier to just test whether the expression has changed inside every busy-wait loop you have, as recommended by Gerben's and Nick's answers.

  • I think longjmp() may return to the mainline but with the ISR's context; I'd expect some surprising behavior. But if you call setjmp() prior to the switch statement (or higher, if appropriate), and the ISR sets a (volatile!) flag to indicate it has been called, then each case could do if( flag ) longjmp(); as frequently as it is appropriate to do without leaving some operation partially completed. ... – JRobert Apr 14 '16 at 17:39
  • ... You'll probably want to abstract away as much of the case code as possible into small functions where the actual work and the flag-testing will be done. This way, both setjmp() and longjmp() will have (had) the same context. – JRobert Apr 14 '16 at 17:39
  • @JRobert: “longjmp() may return to the mainline but with the ISR's context” I don't understand what you mean by “ISR context”. setjmp() and longjmp() save and restore quite a bit of context, including all the call-saved registers and the stack pointer. – Edgar Bonet Apr 14 '16 at 18:33
  • Longjmp restores the context as it was at the setjmp call. It may be; I just don't know. It sounds like you had similar concerns in your caveat to inspect the assembly code and to test carefully. It's quite possible it will work as expected/desired. On the other hand, allowing the ISR to return first gives me a lot more confidence in the outcome. – JRobert Apr 14 '16 at 23:53
  • @JRobert: I fully agree with you. I posted this because I think it's a fun hack worth sharing. I already stated in the answer I do not consider this a clean programming technique. I just expanded on that consideration, just in case it was not already extra clear. – Edgar Bonet Apr 15 '16 at 10:13
1

Most of the time this isn't a problem, because code normally runs pretty fast. The problem, most of the time, is the use of delays. What I did was use my own delay function that checks if the expression has changed, and if so, immediately returns.

Below is my code. I had to modify it a bit, so it might not work out of the box, but will give you the idea as to how.

volatile byte expression;
volatile boolean expressionChanged = false;

switch(expression) {

   case constant-expression_1  :
      statement(s);
      myDelay(1000);
      statement(s);
      break; /* optional */

   case constant-expression_2  :
      statement(s);
      myDelay(500);
      statement(s);
      break; /* optional */

   /* you can have any number of case statements */
   default : /* Optional */
      statement(s);
}

void myDelay(int ms)
{
    uint32_t start = micros();
    while (ms > 0 && !expressionChanged)
    {
        //yield();not implemented in arduino
        if ( (micros() - start) >= 1000)
        {
            ms--;
            start += 1000;
        }
    }   
}

void ISR(void){
   expression = constant-expression_2;
   expressionChanged = true;
}
  • Thanks for the answer! My problem is, one of my cases includes a communication transceiver method, which can take a lot of time to get through, and another case does sensor acquisition from about 30 sensors. I would prefer to not have to check in every "slow" piece of code if the case has changed, but if this is unavoidable, it will have to do. Any ideas? – makepeace Apr 14 '16 at 14:51
  • In case of the 30 sensors, you could rewrite the code, so it only check a single sensor at a time, and then in the next cycle checks the next sensor. Your communication transceiver description is to vague for me to make any suggestions. Other that sprinkeling if(expressionChanged)break; around. This is probably unavoidable, since breaking at certain locations in the code, could cause some bad side-effects. – Gerben Apr 14 '16 at 15:23
0

First, put your major processing into functions. That looks better anyway. eg.

switch(expression) {

   case constant-expression_1  :
      function1 ();
      break; /* optional */

   case constant-expression_2  :
      function2 ();
      break; /* optional */

   /* you can have any number of case statements */
   default : /* Optional */
      function_default ();
}

Now inside your functions do a return if you notice the expression has changed. This will get you out of any number of nested loops. For your 30 sensors, for example, you can now have a loop.

void function1 ()
  {
  for (int i = 0; i < 30; i++)
    {
    if (expression_changed)
       return;
    // take sensor reading
    }
  }  // end of function1

Also, as Gerben suggested, replace any delay calls with a function that does the delay, however which ends early if the expression changes. It can return a boolean to let you know this happened. eg.

boolean myDelay(int ms)
  {
    uint32_t start = micros();
    while (ms > 0)
    {
        if (expression_changed)
           return true;   // early return
        if ( (micros() - start) >= 1000)
        {
            ms--;
            start += 1000;
        }
    }   
  return false;   // normal return
  }

Now any function that needs delays can use that, like this:

void function2 ()
  {
  do_something ();
  if (myDelay (1000))  
     return;   // bail out
  do_something_else ();
  }  // end of function1

My problem is, one of my cases includes a communication transceiver method, which can take a lot of time to get through

There must be logical spots where you can just test the expression and do a return (preferably cleaning up anything that needs it) when required.

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.