GOTO from inside of ISR? (Atmega32a & 328pu)

Go To Last Post
40 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm currently trying to design a state machine (with C) that has two infinite while(1) loops corresponding to two different states: blinking LED and constantly lit LED.

The only way to switch between the states should be an interrupt (either external or pin-change). However, whenever I try to use the labels from the main() inside the ISR I always get a compiler error "Undeclared label".

 

Is it possible to do a jmp/longjmp or GOTO from inside the interrupt service routines? Are "global" labels possible to declare in C?

P.S: I am aware that goto use is undesirable. However, this is just an experiment.

 

Pseudocode of the solution:

#all the defines and includes

 

volatile bool machinestate = 0 //If this var is zero, then LED is blinking, if not zero, then LED is constantly lit

 

main()

{

                

label1:      while(1)                              /*Just a plain old blinking LED routine*/

                        {

                              <LED on>     

                              _delay_ms(500)

                              <LED off>

                             _delay_ms(500)

                        }

              

              

label2:            while(1)                         /*LED constantly ON routine*/

                               {

                                   <LED on>                            

                               }

 

ISR (some_vect)

{

        machineState = !machinestate;             //Flip the boolean

        if (machinestate == 0) {goto label1;}   //Compiler produces an error here

        else {goto label2;}                               

}

Last Edited: Mon. Dec 9, 2019 - 07:22 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

That is NOT how you want to do this.

Set an event variable in the ISR, and check it in your while loop.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is what I've done initially.

 

BUT, what IF the interrupt occurs during the execution of _delay_ms(500)? Since delay_ms doesn't have the internal checks for this variable, the program has no choice but to complete the delay routine, producing mSeconds of lag before jumping to the other state. My idea is to have an interrupt that will switch states instantly and deliberately, regardless of what stage the program is at.

 

I've measured the ISR execution time with the OSCope, and it comes to about 30uS (with the variable and IF statements). Much less than having to wait for the delay to finish.

Last Edited: Mon. Dec 9, 2019 - 08:05 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

use a shorter delay and count them. That way you can sample the interrupt flag faster. 10ms is a good choice.

 

Of course, you don't have switches connected to interrupts do you? That's bad juju.

 

Last Edited: Mon. Dec 9, 2019 - 08:24 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

use a shorter delay and count them. That way you can sample the interrupt flag faster. 10ms is a good choice.

 

10mS is still an eternity for an interrupt that can be done in 30 microseconds.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Don't use delay at all.  _delay_ms is just a carefully counted while loop.  It's not like a multi-processing system where calling a "delay" function is "friendly" to other users or processors.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Alright. I wasn't planning on using the delay_ms ever since I've learned the HW timers. However, instead of delay_ms there might be some other time-consuming library function. Hence I'd still REALLY like to know if it is possible to goto or jmp from the ISR immediately and unconditionally.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

totesnochill wrote:
Hence I'd still REALLY like to know if it is possible to goto or jmp from the ISR immediately and unconditionally.
Your design is atrocious. Do not persevere with this ill guided course but sit down and design this on paper properly. When you have a clear design the implementation will be easy and it will not (should not!) have any need to use goto.

 

(if however you are unable to see the correct way to design a computer program your ultimate fall back may be setjmp and longjmp:  https://en.wikipedia.org/wiki/Setjmp.h )

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Okay. Any suggestions on how can I trigger the immediate and unconditional state change in the middle of delay_ms() without goto through the interrupt? No need to post code, just a couple of leads and concepts would do.

 

I cannot go with Kartman's suggestion of using shorter delays+counting, because there might be some other library function in the place of delay_ms() that takes a set (and unchangeable) amount of time to complete.

 

Last Edited: Mon. Dec 9, 2019 - 09:16 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

timers and polling loop in main......

no need for _delay_ms as you know HW timers.... use it to your advantage.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was using interrupts with the sole purpose of getting rid of polling of any kind, so...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is a sketchy outline:

volatile uint8_t machine_state;

int main(void) {
    uint8_t led_state = 0;
    uint16_t led_count = 0;
    
    while (1) {
        if (machine_state) {
            LED_ON();
        }
        else {
            led_count++;
            _delay_ms(1);
            if (led_count >= 500) {
                led_count = 0;
                led_state = !led_state;
                if (led_state) {
                    LED_ON();
                }
                else {
                    LED_OFF();
                }
            }
        }
    }    
}

ISR(some_vect) {
    machine_state = !machine_state;
}

Basically it breaks the _delay_ms(500) into 500 individual steps of 1ms so the execution is never held in one iteration of the while(1) loop for more than 1ms before it might recognise a change in machine_state. When the led counter reaches 500 (that is 500 * 1ms) then it resets the 500 count and toggles the LED state.

 

I just made this up as I went a long. A better approach is to sketch the design (not the actual code!) out on paper (pencil plus back of envelope will do) first and then when you are happy with the design only then sit down to actually write some C code. The key elements are that you are going to want to remember the machine state (flashing or solid on) and also the state of the LED itself (is it on or off during the flashing phase).

 

BTW this is not a great design anyway. Suppose an "on for 500ms" period had just started then you trigger the event to change machine state. It will actually be a while before you know if your trigger activity has had any effect. Usually in human interfaces you want to give the end user instant feed back that their control event has been recognised.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was using interrupts with the sole purpose of getting rid of polling of any kind, so...

That isn't always a good idea.  If your program is doing other things, you REALLY may not want it to instantly go off and do something else.

 

But to answer your actual question... You'd have to manually enable interrupts, unwind the stack, and adjust any below-C "context" that might have been in effect at the time of the interrupt.  Interrupts occur at machine-level-instruction boundaries, which don't necessarily match up with C statements.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That isn't always a good idea.  If your program is doing other things, you REALLY may not want it to instantly go off and do something else.

Unfortunately, it is exactly the requirement here. Think of it as an "emergency response" routine: as soon as an emergency is detected (e.g critical component failure), an interrupt is sent, and the machine drops whatever it was doing to address the said emergency ASAP.

But to answer your actual question... You'd have to manually enable interrupts, unwind the stack, and adjust any below-C "context" that might have been in effect at the time of the interrupt.  Interrupts occur at machine-level-instruction boundaries, which don't necessarily match up with C statements.

I thought that was no longer the case with modern compilers: source

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks, but I've already mentioned (in post #9, reply to your prev. response), that:

 

I cannot go with Kartman's suggestion of using shorter delays+counting, because there might be some other library function in the place of delay_ms() that takes a set (and unchangeable) amount of time to complete.

In other words, _delay_ms() is a placeholder. Replace it with:

 

_very_important_maths();   //Takes fixed 500ms to complete, set in stone and non-negotiable

and your solution is no longer valid. I'm designing the code with this case in mind. Also, I've made an entire flowchart in DIA for this program before attempting the code. Might not be perfect, but it is there.

Last Edited: Mon. Dec 9, 2019 - 10:51 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Not so much the actual goto but your intended (mis)use of it. As Cliff explained, you’d need to do a few shenanigans to get it to work more than once.

Seems you don’t like delays, so use a hw timer. I wrote a tutorial on multitasking in the tutorial section that might be of interest.

Hopefully you don’t want to use the interrupt for a safety function. That is bad juju.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Could you please give some hints on why it is bad to use interrupts for that case? They are fast response and run in-parallel with the rest of the program, why not?

Last Edited: Mon. Dec 9, 2019 - 11:03 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
uint_16t gDelay_counter
uint_16t gControl

main()
{
    
    switch (gControl)
    case LED_FLASH_ON
        <led = on>
        gDelayCounter++
        if (gDelayCounter > 500)
            gDelayCounter = 0
            gControl = LED_FLASH_OFF

    case LED_FLASH_OFF    
        <led = off>
        gDelayCounter++
        if (gDelayCounter > 500)
            gDelayCounter = 0
            gControl = LED_FLASH_ON
            
    case LED_CONSTANT
        <led = on>
        
}

ISR (some_vect)

{

        machineState = !machinestate;             //Flip the boolean

        if (machinestate == 0) {gControl = LED_FLASH_ON;}   

        else {gControl = LED_CONSTANT}                               

}

 

 

Isn't this just a case of how you deal with tasks using a co-operative task switcher? How you need to break them down so that they don't block any other execution paths?

#1 This forum helps those that help themselves

#2 All grounds are not created equal

#3 How have you proved that your chip is running at xxMHz?

#4 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

first of all interrupts will happen random during your other program execution. You never ever know at what instruction cycle the CPU is when an interrupt occurs. It does not run in parallel, but is running at random times during normal program execution.

 

why not set a timer that gives an interrupt every 500ms. if a certain variable is set you then toggle the LED pin, if the variable is set to another value you only turn it on(and on again after 500ms...... etc etc....)

so no need for delay and you can run your main program with minimum interference of the interrupts that happen.... that should always be the case.. interrupts taking as low a time as possible to execute as they distort the "normal" program flow.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What makes you so sure the processor will respond to an interrupt let alone handle a critical function correctly? Google ‘therac 25’.

As well, isrs do not run in parallel with the main code - when the isr is running, the main code is not. And vice-versa.
If you cannot break up your long calculation, then maybe you need a pre-emptive rtos. The average AVR might be a bit resource constrained, but nevertheless it is a common solution.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah, I'm aware of the therac fiasco. Though in that case the fault nature lied in software. How do I know my MCU would respond? Well, aside from total PCB meltdown, peripheral HW faults like the sensor dying out can be detected pretty easily.

Regarding the "parallel" - a bad choice of words on my side. What I meant is that interrupts can occur without having to poll for the triggering condition.

 

And I DO wand to be able to break my long calculation and switch the program counter to another part of the code.

E.g:

ISR is entered at line 10

ISR does something

ISR ends, but the code resumes execution from line 99 instead of 11, without using state variables.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If this interrupt is "catch bad event, we're going to die" then why does it need to get back to the main() loop at all? Why not simply:

ISR(some_vect) {
    LED_ON();
    while(1);
}

Why does it even need to "goto" somewhere else to do this "everything failed" behaviour?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh, that is for the machine that is capable of resolving some problems on its own, having a degree of autonomy.

 

E.g:

 

Line 100: Some fault happens and is detected

Everything machine was doing is dropped, Interrupt is entered

 

ISR

{

       <Automatic diagnose routine>    

       Fault is deemed resolvable without human intervention

       <Fault is logged>

       <Some actions>

       Fault is resolved

       ISR ends here, BUT the program doesn't resume from where it was. Rather it resumes from the "Homing" stage (Line 5) immediately, "soft reboot" of sorts to ensure the smooth operation cycle.

}

 

          main(){

Line 5:       <Homing routine>

               ...

                           ...

                           ...

Line 999:      } //main() end

 

 

Last Edited: Mon. Dec 9, 2019 - 11:56 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Now you are getting the design thing! :-)

 

Continue as you are and sketch out the design of this as you show in #23 using "broad brush strokes". Don't try getting bogged down in a specific C implementation until you have the design pattern clear in your head.

 

If as you say it's "handle bad event and reboot" then why not simply:

ISR(some_vect) {
    handle_bad_stuff();
    reboot();
}

"reboot()" can be as simple as:

typedef void (*fptr_t)(void);

fptr_t reboot = (fptr_t)0x0;

...

reboot();

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How about a transient causing the interrupts to be disabled, stack corrupted etc? This assumes the software itself is perfect, so don’t ever bank on the cpu doing what you expect everytime.

With the Therac the fault lie in the design - assumptions were made that everything would work perfectly, everytime. Critical defects in the code sealed the deal.

The problem with the idea of simply changing the program counter in the isr is that the interrupt will hit at a random address in your code, thus the state of the stack is indeterminate. You then change the return address the isr put on the stack on entry to where you want to go then unwind the stack and rti to your new location. The stack state is indeterminate and your code is running. Who fixes ip the stack? Eventually the stack might overflow and your code dies violently. As i mentioned before, preemptive rtos addresses the stack problem by having multiple stacks. You don’t simply jump to a new location, you change the execution context and run another task.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And even if you jump to the reset vector as suggested in #24, the machine will not be in a real power-on reset state. Your initialization routines may be assuming peripherals are in power-on reset state, but if they aren't, unpredictable results may ensue.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Jump was supposed to land the machine at the "Homing" state - during which all peripherals will be rebooted/servo positions reset/displays cleared, etc. No unpredictables, unless more faults occur during homing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

totesnochill wrote: Jump was supposed to land the machine at the "Homing" state

 

I didn't see it explicitly mentioned but I believe the glaring flaw in using goto in an ISR stems from the Routine part.  To get to the ISR the processor puts a return address to the next instruction on the stack then jumps.  When the ISR ends that address is pulled off the stack and execution resumes where it left off.  If you jump to another address/function inside the ISR, and thereby cut out a proper exit, that return address remains on the stack.  Next time that IRQ occurs, same thing.  At the least, the stack will eventually grow and run into something you don't want destroyed.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

dougp2 wrote:
If you jump to another address/function inside the ISR, and thereby cut out a proper exit, that return address remains on the stack.  Next time that IRQ occurs, same thing.  At the least, the stack will eventually grow and run into something you don't want destroyed.

 

That's not the only problem. The ISR code could directly access the stack and replace the return address by wherever you want to jump to, but that could still cause stack overflow, because at the time the ISR is called, which is random, the stack can have other content, not just return addresses (saved registers for example). The code that would free the stack is waiting to be executed in the normal code stream, that will not be executed, because execution jumped somewhere else.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Not that I recommend it,

but I think longjmp will do what OP says he wants.

ISR's are effectively called and longjmp undoes

calls by restoring SP, PC and other registers.

There is no reason it should fail for ISRs.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I thought that was no longer the case with modern compilers: source

  1. That's one opinion.
  2. "hardware interrupts" aren't part of the standard C specifications, so behavior of jumping from an ISR to normal code would at best be "undefined."  The issues I mentioned were more-or-less unique to ISRs.
  3. As it turns out, C labels have "function" scope, so you can only jump to labels that are in the same same function as the goto.  This explains your error message.
  4. (no one came up with that immediately, because no one uses goto any more.)
  5. (that no one uses goto should be a strong hint that they aren't necessary.)

 

If you want to shut down something when the interrupt occurs, go ahead and shut it down INSIDE the ISR.  Then you can either:

  1. Never return from the ISR, because things are shutdown and you're waiting for human intervention.
  2. return from your ISR and notice that "things have been shutdown" at your leisure (presumably, before you turn them back on!)
  3.  
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I ought to stay out of software discussions...

 

That said, if you use Westfw's suggestion above, then run your emergent shut down within the ISR, and then trigger the Watchdog reset to actually reset the micro back to a known state.

 

How quickly after the emergency condition is signaled does the micro have to stop the normal processing?

You mentioned switching tasks without using a State variable, but that seems to be the "clean" way to do this.

 

If the ISR sets an Emergency Condition flag, then each of the two loops, (LED On and LED Flashing, per your original example), could simply test for the flag once per loop.

If the loop execution time is too long, then insert the test multiple times within the loop, to minimize the time delay before recognizing the flag and cleanly handling the condition.

If the loop, even with multiple tests inserted into it, is too slow when running on a 16 MHz AVR, then switch to a 32 MHz AVR Xmega, and cut the time interval before detection in 1/2.

Or overclock the Xmega to 48 MHz, (caveats apply), and cut the time...

Or switch to a dinky ARM chip..., and cut the time...

 

It seems to me that if the system is suppose to recover elegantly, (and not just a master Watchdog reset of everything, but some sort of smart system clean up), then having a clean approach would also be a high priority.

 

JC 

 

Edit: Typo

Last Edited: Tue. Dec 10, 2019 - 01:51 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How about this? It is a pretty simple pattern that I used in a bootloader. 

 

Line 100: Some fault happens and is detected

Everything machine was doing is dropped, Interrupt is entered

 

ISR

{

       <Automatic diagnose routine>    

       Fault is deemed resolvable without human intervention

       <Fault is logged>

       <Some actions>

       Fault is resolved

       Set special ISR flag RAM

       Invoke reset

}

 

          main(){

 

              if (NOT (special ISR flag in RAM))

              {

                 Do hardware initialization

              }

Line 5:       <Homing routine>

               ...

                           ...

                           ...

Line 999:      } //main() end

PS note how goto's always can be replaced by other better constructs. That's the reason why they are in general avoided...

/Jakob Selbing

Last Edited: Tue. Dec 10, 2019 - 08:42 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I thought that was no longer the case with modern compilers: source

 

That source is, I think, missing the point -- and at least partially wrong, it claims that a goto is equivalent to a function call, but it's not. Because a function call has a return, which automatically returns to where it was called from.

 

The problem here is that interrupts, too, have to return, or things don't work anymore. Also, I don't think it's ever sane to say "if this happens, I want to unconditionally interrupt any current execution", because for arbitrary code, you have no idea at all what the state of the machine is. It could be in the middle of doing some elaborate register-shuffle dance. The stack is full of data and return addresses, intermingled, and if you jump to another location, you no longer have any idea what state that's in.

 

 

 

 

What you might be able to do is set up multiple stacks, one per task/state/whatever, and then when you hit the interrupt, fixup the rest of the context so that when you leave it, you end up using the other stack. That's basically how operating systems did task-switching multitasking, and what's important to understand here is, they did not do it by just having a goto. They did all the actual work to make sure that register states were saved and restored cleanly on each end, so each end would resume where it left off. And in this case, it sounds like what you want is more like stashing a state, and restoring that state rather than where the processor was.

 

And honestly, I think that's more than you've got the space or resources for on this hardware.

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

the_real_seebs wrote:
That source is, I think, missing the point -- and at least partially wrong, it claims that a goto is equivalent to a function call, but it's not. Because a function call has a return, which automatically returns to where it was called from.

I think the article is a little silly.

 

Sure, you can use goto's in C code. It's not the end of the world.

 

But... WHY would you want to use it, ever?

 

I follow the principle of NOT using goto simply because IMHO the code becomes much more readable and less error prone. I haven't yet seen a situation where a goto is better that the alternatives.

/Jakob Selbing

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I actually learned "Structured Programming" by being made to use Pascal in 1981 (not modern/later Pascal like Borland or Delphi etc but the original Niklaus Wirth type). It's a very good way to wean frustrated Asm/BASIC programmers out of the habit of using anything like JMP/Goto:

 

https://en.wikipedia.org/wiki/Pascal_(programming_language)#Control_structures

 

At the time I remember thinking "Why Pascal? What relevance does this have to the real world?". These days I appreciate it for enforcing me to think about clean structure in any language. (it's insistence on strong typing is also appreciated)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

An assembly language goto was selected for the Meltdown and/or Spectre patches to the x86 Linux kernel (reverse trampoline, Linux trace, LLVM 9)

[llvm-dev] [RFC] Implementing asm-goto support in Clang/LLVM

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


clawson wrote:
I actually learned "Structured Programming" by being made to use Pascal in 1981 (not modern/later Pascal like Borland or Delphi etc but the original Niklaus Wirth type).

Likewise!

 

Subsequently used it in an MoD project on the grounds that Ada wasn't available for the target at the time, and Pascal was the next best thing 

 

I still have my copy of the User Manual & Report next to my K&R

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've seen goto used in C as a way to improve the clarity of error-handling code, but it's rare. But it is a thing that sometimes makes sense. But only within functions, usually.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


Good Lord I can't believe that I still remember this 38 years later but our standard text for Pascal was:

 

https://www.amazon.co.uk/Programming-PASCAL-Peter-Grogono/dp/0201027755

 

 

I think I've probably still got that in the loft somewhere.