Break out of a case after interrupt function?

Discussion in 'Programmer's Corner' started by Mailliw102, Nov 6, 2012.

  1. Mailliw102

    Thread Starter New Member

    Nov 5, 2012

    I'm working on a school project in C, using a PIC16F877A. Here is the general idea of the code (This is pretty basic stuff, I'm not an advanced C programmer):

    Code ( (Unknown Language)):
    1. int step=0;         //This is global
    3. void main (void){
    4.      while(1){
    5.        switch(step){
    6.          case 0:do stuff
    7.             while(button1 not pressed);     //Wait for button1 push
    8.                 do more stuff
    9.                 step = 1;
    10.                 break;
    12.          case 1:do stuff
    13.             while(button1 not pressed);     //Wait for button1 push
    14.                 do more stuff
    15.                 step = 2;
    16.                 break;
    18.          case 2:do stuff
    19.             while(button1 not pressed);     //Wait for button1 push
    20.                 do more stuff
    21.                 step = 3;
    22.                 break;
    24.          case 3:do stuff
    25.             while(button1 not pressed);     //Wait for button1 push
    26.                 do more stuff
    27.                 step = 0;
    28.                 break;
    30.          default:step = 0;
    31.                 break;
    32.        }
    33.      }
    34. }
    Basically, this program performs different actions depending on the current step. Every step there has to be a button1 push to continue to the next step. This part of the code is 100% working as intended.

    I want to add another button (button2), that whenever pushed, changes the "step" value to 3. In other words, if you press button2, I want it to skip right to case 3 in the main function. I have no clue as to how I can achieve this. I thought of using an interrupt on change on portB with button2 that would put 3 in the "step" variable (which is a global variable) but the problem is that after the interrupt function is completed, the program would go back to where it was before the interrupt i.e. in the middle of a case. I don't want it to finish the case it was in, I'd like it to jump straight to case 3. How would I go about doing that? I also thought of putting my whole while(1) in a function, then calling this function from within the interrupt function. While this would technically make the program work as I want it to, it kinda feels weird and I'm really not sure if this is a good idea. I mean, the interrupt function would never properly complete itself. Is this dangerous for my PIC ?

    Is there a "cleaner" way to do this?

    Thanks in advance!

    Edit : I'm using MPlab with the CCS compiler in case that matters.
    Last edited: Nov 6, 2012
  2. tshuck

    Well-Known Member

    Oct 18, 2012
    Add dangerous as it is, you could modify the stack so that the program would think it wad returning where it was when, in fact, it would be going to case 3. Other than that, I can't think of anything aside from testing a variable that is set when interrupted...
  3. tshuck

    Well-Known Member

    Oct 18, 2012
    actually, I just thought of another possibility, you could pop the stack of the return address of the interupt and do a go to the location of case 3. You would probably need to write this in assembly, and you'd need to be sure how many items were added to the stack from the context switch...
    Last edited: Nov 6, 2012
  4. tshuck

    Well-Known Member

    Oct 18, 2012
    To respond to your solution, no, your pic has a finite stack size it would overflow...I believe the 16f877A has a 8 level deep stack, but I'm not sure of this... also, you should not be able to interrupt within an interrupt routine, you need a retfie(return from interrupt...this cleans up the stack) first.
  5. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    Assuming the "while(button1 not pressed);" is pseudo code, I would write it like this:

    Code ( (Unknown Language)):
    1. int step=0;         //This is global
    3. void main (void){
    4.      while(1){
    5.        while(button1 and button2 not pressed);     //Wait for either button pushed
    6.        if (button2 pressed) step = 3;              // this is your step correction for button 2
    8.        switch(step){
    9.          case 0:do stuff
    10.                 do more stuff
    11.                 step = 1;
    12.                 break;
    14.          case 1:do stuff
    15.                 do more stuff
    16.                 step = 2;
    17.                 break;
    19.          case 2:do stuff
    20.                 do more stuff
    21.                 step = 3;
    22.                 break;
    24.          case 3:do stuff
    25.                 do more stuff
    26.                 step = 0;
    27.                 break;
    29.          default:step = 0;
    30.                 break;
    31.        }
    32.        // note this essential addition !!!!
    33.        while(button1 or button2 pressed);     //Wait for button release here
    34.      }
    35. }
    Do check the release I added.
  6. Mailliw102

    Thread Starter New Member

    Nov 5, 2012
    Thank you for your answers. I have never played with the stack and have no experience with it, so I did a bit of research and found this :

    PICs have a hardware call stack, which is used to save return addresses. The hardware stack is not software accessible on earlier devices, but this changed with the 18 series devices. Hardware support for a general purpose parameter stack was lacking in early series, but this greatly improved in the 18 series, making the 18 series architecture more friendly to high level language compilers.

    As I'm using a PIC16 (earlier than 18) I don't know if this would even be possible.

    As of your suggestion, ErnieM, after trying to apply your logic to my project, I find that I could very well make this work and it is so much simpler than messing around with interrupts and with the stack :)

    Thanks again to both of you!
  7. John P

    AAC Fanatic!

    Oct 14, 2008
    I'm sure there's a way to make ErnieM's suggestion work, but he has introduced a major change. In the original version, the order of events was "do stuff, Wait for button1 push, do more stuff". Ernie wants it to go "Wait for either button pushed (assuming it's not b2), do stuff, do more stuff". If this is workable, then fine.

    I don't think there's any way a PIC processor can be interrupted during a task, and then based on something that happens during that interrupt, go somewhere else rather than returning to where it was. The best you can do IMHO is have a flag examined at various places in the code, which can be set in the interrupt and if found to be set, can cause a branch in program execution.
  8. MrChips


    Oct 2, 2009
    Just to repeat what others are saying,

    there is no safe way to break out of a task on an interrupt.
    Modifying the stack is a definite no-no.

    The proper solution is to set a flag from the interrupt handler.
    Test this flag within your main program in order to control program flow as Ernie and others have said. Remember to clear this flag once acknowledged.
  9. kubeek


    Sep 20, 2005
    I would do it like MrChips says:

    Code ( (Unknown Language)):
    1. int step=0;         //This is global
    3. void main (void){
    4.      while(1){
    5.        switch(step){
    6.          case 0:do stuff
    7.             while(button1 not pressed || button2 not pressed);     //Wait for button push
    8.             //these should be just flags and should be set by the interrupt routine
    9.             //remember to unset the flags in the interrupt as well, or in some other predictable way
    11.             if(button2 not pressed == pressed)
    12.             {
    13.                     step=3;
    14.                     break;
    15.             }
    16.             do more stuff
    17.             step = 1;
    18.             break;
    20.         case 1:
    21.         etc...
    23.          default:step = 0;
    24.                 break;
    25.        }
    26.      }
    27. }
  10. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    A good point, I did make that change. As far as how workable it is, it is basically how I test my buttons when driving around a menu. Here's a snipplet of some code (simplified to remove my app's details) I'm currently using:

    Code ( (Unknown Language)):
    2. void Routine(void)
    3. {
    4.   // routine initial set-up here
    5.   while (Keys);       // wait for keys to be released from previous task
    6.   while (1)           // loop to watch for key pressings
    7.   {
    8.     while (!Keys);   // wait for keys to be pressed
    9.     switch (Keys)
    10.     {
    11.       case BUTTON_UP:
    12.         // do the BUTTON_UP task
    13.         break;
    15.       case BUTTON_LEFT:
    16.         // do the BUTTON_LEFT task
    17.         break;
    19.       case BUTTON_DOWN:
    20.         // do the BUTTON_DOWN task
    21.         break;
    23.       case BUTTON_RIGHT:
    24.         // do the BUTTON_RIGHT task
    25.         break;
    27.       case BUTTON_ENTER:
    28.         // ********************************
    29.         // THIS task completes this routine
    30.         //  then returns control to the calling routine
    31.         // ********************************
    32.         // do the BUTTON_ENTER task
    33.         return;        // EXIT this routine
    34.     }
    35.     while (Keys);   // wait for key to be released
    36.   }
    37. }  
    In this code Keys is a global variable who's value is set inside an ISR to co-inside with the current debounced state of 5 external buttons who's names and functions can be inferred from the case statements.

    When a button is pressed the function is activated and a case statement executes. Then the trailing while(Keys); blocks the code till the button is released. There is also an auto-repeat built into the ISR which comes in handy when incrementing or decrementing some parameter.

    Exactly the correct point. You loop and test a global variable set in the ISR that the looped code monitors. Here I am using the Keys variable in just such a fashion.
  11. Mailliw102

    Thread Starter New Member

    Nov 5, 2012
    After a bit of thinking I figured that the "do stuff" and the "do more stuff" parts are actually happening really quick in my project. Therefore button2 would almost certainly be pressed during the "while(button1 not pressed)" part in any case of the switch. This brought me to this :

    Code ( (Unknown Language)):
    1. int step=0;         //This is global
    3. void main (void){
    4.   while(1){
    5.     switch(step){
    6.       case 0:do stuff
    7.          while((button1 not pressed)&&(button2 not pressed));     //Wait for any button push
    8.              if(button 2 is pressed)
    9.                step = 3;
    10.              else{
    11.                do more stuff
    12.                step = 1;
    13.              }
    14.              break;
    16.       case 1:do stuff
    17.          while((button1 not pressed)&&(button2 not pressed));     //Wait for any button push
    18.              if(button 2 is pressed)
    19.                step = 3;
    20.              else{
    21.                do more stuff
    22.                step = 2;
    23.              }
    24.              break;
    26.       case 2:do stuff
    27.          while((button1 not pressed)&&(button2 not pressed));     //Wait for any button push
    28.              if(button 2 is pressed)
    29.                step = 3;
    30.              else{
    31.                do more stuff
    32.                step = 3;
    33.              }
    34.              break;
    36.       case 3:do stuff        
    37.              while(button1 not pressed);     //Wait for button1 push
    38.              do more stuff
    39.              step = 0;
    40.              break;
    42.       default:step = 0;
    43.              break;
    44.     }
    45.   }
    46. }
    This way, I wouldn't even need to use interrupts. I would of course add some more "whiles" to wait for buttons to be released to make sure the cases won't all happen in one button push.
    Last edited: Nov 7, 2012
  12. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    You are beginning to loose me. (OK, I think I really dropped out a while ago.) This is due to using code to design the code: all you have is code and no design!

    A state diagram would be useful to describe the steps needed to perform the task without writing code, be it pseudo code or real C.
  13. THE_RB

    AAC Fanatic!

    Feb 11, 2008
    I don't use interrupts for button presses, and try to save ints for very time critical stuff like timers.

    Buttons are very very slow in comparison and are best handled manually by your code as some part of a well designed main operation loop, which normally does the decision making stuff (ie "if this button is pressed, do X, or go to mode Y").

    I agree with ErnieM you should start fromt he beginning with a hand written flow chart etc and decide exactly WHAT the program will do and HOW and WHEN it is done. The adding the code part later should be much easier.