implementing a "soft reset" ISR / delay inside isr

Hello,
i'm programming an "reset"/"abort" ISR in my program(essentially a kind of "emergency stop" button, so i can't simply set a value in a variable and return to the main loop.
Main loop has lots of delays and if/for loops that need to be instantly aborted should the button be pressed.
Now, i'm having a couple issues with this:

  1. i need to implement a small delay inside the ISR, since Delay() doesn't works i thought about using a simple for with inline NOPs, but i'd like to at least be able to somewhat calculate how much the delay is, the delay line i arbitrarely built is: for(int z = 0; z < 500000; z++) asm("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); so that's AT LEAST 50000 iterations of 3 NOPs, but i have no idea how much overhead the for/increment generates, if i take the NOPs alone, that's 500K x 62.5nS x 4 =125mS :disappointed_relieved: i need at least 3 to 4s, so i'd need to do a z <16000000 right?

now the main problem:
2) the "reset" button(not the arduino reset mind you) needs to stop all running stuff, as you can see in the code i cycle a relay(that's part of a need for the rest of the circuit) and set the "contando" varibale to false(a very long counting timer event in main loop), my concern is that main loop has a for() loop with large delays inside that need to get aborted, so if i run this right now, the ISR executes, does it's thing and then returns the stack to the previous position!, which could be inside a Delay() or the for() loop and then everything would execute the same!.
How can i force the ISR to return to a specific PC address(POPing the PC from a previus PUSH i should do in the beginning of the main loop, something like that comes to mind) instead of just returning to last PC+1?.
I don't want to have reset the AVR itself for this(using a pin jumped to the reset input and issuing a digitalwrite there....) as it's anything but elegant, or am i going to have to write a new special ISR in an assembler macro just for this?

code follows:

void reset_event(){
  digitalWrite (rele, HIGH); //activar rele para resetear el UC3906, delay no funciona en el ISR
  for(int z = 0; z < 500000; z++) __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); 
  digitalWrite (rele, LOW); //desactivar rele para operación normal
  contando = false; //abortar cuenta
}

Connect a pull-up resistor to the rele pin. Use the processor's hardware reset. In setup, pull the rele pin low. Problem solved.

Coding,
i can't put a pullup in the relay pin, as thee are some high power stuff that could arc or be damaged if that relay isn't activated at the right time, and activating it with a pullup(in the time the avr taks to boot) might work but it's not elegant and can become a fire hazard, and it doesn't delays as i need it to (unless i pull the reset up after the delay expires in the ISR, then it would at least delay)

Also, for another project i'm gonna need to implement a rather more complex ISR which CAN'T just reset the arduino as it isn't the purpose(a similar emergency stop button) so sooner or later i need to deal with this(might as well do it now)

How about a state machine ?

switch(state) {
  case RUNNING:
    make_my_day();
  break;
  case RESET:
    do_what_needs_to_be_done_to_not_fry_the_rest_of_the_project();
  break;
  default:
  break;
}

And replace the 'dumb' delays with checking for the right time using the system ticker millis(). And people on the avrfreaks.net forum would torture you to death for putting such long delays inside an ISR. Probably by slowly plucking out every single hair on your body.

This of course relies on the fact that the switch(state) is cycled through very often and the delays are not blocking. Just recently there's been a post about state machines for arduino, even on the playground. Looks quite sophisticated.

madworm:
in the main loop i used delays(15min delays, with 15min cooldowns between, called 4 times) for that portion because it was far easier than millis()+time and then an entire if ((long)( millis() - end_time ) >= 0) checking(albeit only one as i can load the end_time for each iteration, inside a for).
So there lies the issue, i could very well use that switch case argument you posted, but the "make_my_day()" function i assume you declare elsewhere or put the main code there directly has those delays(and some other checking as well) and so it would continue to execute and after that check the case reset, which leaves me with the same problem as now.
In fact, main loop has a 2 week timed event(already implemented with millis) so if i'm not wrong, it would take 2 weeks to go out of the case RUNNING.
I'm not sure i could change the main loop to use a switch case system as i have several subloops evaluating stuff

hahaha, i was about to post in avrfreaks about how to modify the return stack on the ISR so that i can make it return to whenever i want(well i'm still gonna post it as i want to know if it's possible), not the last PC before execution, the delay in the ISR is a byproduct of the apparnet inability to do that, so i must do it all inside the ISR(and reset the ugly way). Besides with one high priority ISR, it doesn't matters much if the ISR takes an hour of delay as it's a "reset" ISR....

How about using the watchdog timer.

Set up an ISR on a normal timer that kicks the dog if the button is not pressed. If it is pressed then do nothing.

About 16mS (can be set up to 8secs IIRC) later the WD will fire and either reset the processor (but you can test to see if the WD was the cause) or enter another ISR in which you can abort the program.


Rob

Why not set a Kill flag in the ISR? Then setup each delay as multiples of smaller delays (so a 100mS delay would 100 times thru a 1ms delay), after each mS check if the Kill flag is set or not. Then you could serially kil off each delay 1ms after another.

I would agree with CR if there aren't that many delay()s to break up. Just take a delay(long_time) and

short_time = long_time /100;
for (i = 0; i < 100; i++) {
delay (short_time);
if (button_pressed()) shit_bed();

OR

if (kill_flag) shit_bed();

}

But if there a LOT of places this needs to be done I'd go with the interrupt approach.


Rob

i've been looking closely at the execution of my program to see which are the critical parts that have long delay and luckily there's only one part with 4x2x15min delays(implementes with an accumulator).
I like your suggestion CrossRoads/Graynomad, this way i can combine both using the ISR to set the "kill flag" instead of polling for "button_pressed", even if it will complicate code a little to break the delays it would still fit in less than 5K.
i'm gonna post the code after it's done here to see what you think

edit: if i iterate short delays, how much time overhead am i incurring?, i know that delay(1000) won't be the same as a for loop with 10x delay(100) -because the for and variable increment take cycles-, is there any way to calculate or approximate this?, in a short delay of 1s this will be negligible, but i'm using 15 min delays which is a 900000 delay(i chose 1000 as divider) and the errors can pile up(or not)

Instead of a time (minutes) delay, why not use millis() instead?
declare these all unslgned long types:
start_time = millis();
end_time = start_time;
duration = 900000;
elapsed_time = end_time - start_time;

// long loop code
start_time = millis();
while ( elapsed_time <= long_duration)
{
if (kill_flag ==1){stop event}
// long event code
end_time = millis();
elapsed_time = end_time - start_time;
}

or run this as an if-then loop if you want this to occur within a bigger set of functions.

For example, I have a countdown timer that does this:
if (timer is running){
check if 250mS gone by
{if yes, update colon on/off, turn on update_display flag}
check if 1S gone by
{if yes, count down by 1 S, roll over the time digits from 10:00 to 9:59, etc turn on update_display_flag)}
check if serial input received
{if yes, set flag to stop timer, set the indicator_lights update flag to turn on/off some lights}
check if flag set for indicator_lights update
{if yes, write out the data, clear the flag}
check if wireless command received
{if command is start/stop time, do that
if timer is running & command is anything else (updating score, etc), ignore it
if timer is not running, do the other commands: update score, load up new time to count down from, etc.}
check if display_update flag is set
{if yes, write out the updated time digits, clear the flag}
check if score_update flag is set
{if yes, write out the updated score digits, clear the flag}

and repeat.

So, most of what is going on is the code going thru the if statements & not doing them, and every quarter second something happens, or occasionally a wireless command is received, or some serial input is received.

I have another loop on a different arduino (that sends serial data to the above) that does a similar loop but on 100uS (0.1mS);
this one checks whether the fencers have scored touches and send a message to the first one, and turns on the touch lights.
Still the same, most of what goes on is going thru the if statements & not doing them.

You could do the same - create on outer loop and set a 10mS flag, 20mS flag, 100mS flag, then have the events check the appropriate flag and then act when they are supposed to.
Or, if you have a bunch of isolated events running concurrently that need to delay themselves while not impacting others:

if (my delay flag is not set)
{if kill_flag is not set, run my function}
else {if kill_flag is not set, see if my delay time is elapsed, if so clear the delay flag so function can occur next pass thru}

Seems to me the root of your troubles is the use of delay(). Instead of trying to work around the problem, why not just fix the root problem, ie use millis() instead of delay(). In my book, that is a far cleaner solution than trying to resort to a stack manipulating ISR() to abort a delay().

jraskell, i find millis calculation and comparison cumbersome, but that's just my way of thinking...

CR, i put together the code but i'm experiencing a huge millis calculation problem(opened another thread for that with the entire code here: http://arduino.cc/forum/index.php/topic,51523.0.html )
so for now, reset routing takes a secondary place until i fix the millis problem

Eliminateur:
i find millis calculation and comparison cumbersome, but that's just my way of thinking...

That's a strong sign, that you really need to work more on it until you really understand how to work with it. It's a learning process to get familiar with the available tools and understand where to use them and where they create more problems than they solve.

Korman

of course there are a ton of things i need to get more familiarized with and room for improvements as well but i don't want to get too sidetracked and falling into "feature creep" at this point of this little project.
Maybe now that i got it working completely i can change the synthetic delay iterations with while+millis statements just for academic purposes like CR posted in his example(could be useful for future designs).

anyway, i finally got this working, i had a couple bugs in the reset code as well that once resetted it kept resetting in a loop(i forgot to deassert the reset_flag at the end hehehe).
and anothe rin pin addignment that i forgot to remap so i was using the SLO input as the reset pin, doh!!! :roll_eyes:

ok, so i've integrated the arduino in the rest of the circuit and i'm experiencing spurious resets, i've observed 2 so far, one in "idle"(not counting) and the other whilst the tests where running...

the reset circuit is local on a protoboard that i'm using as interface and connected to the arduino board GND and the reset pin is fixed to gnd AND with a wire and also has a pulldown to GND so i have no idea what could be causing this, maybe relay switching noise?(but it's not happening on relay switch either)....

should i change the interrupt to CHANGE instead of RISING?.
AFAIK when Arduino enters the ISR it disables all interrupts, so even if i use CHANGE(which would trigger on off->on and then on->off) the 2nd one would be ignored, right?

what other things could be triggering the spurious reset?
How can i make the ISR call more robust(like a debounce inside the ISR or a level check?), i.e. requiring a X time of button pressed?