Design for two tightly looping ISRs

Hello, apologies for the long post, I tried to be as clear and concise as possible while still providing enough context to my question.

I'm writing a simple program on my Arduino Nano to handle pulse train generation for a stepper motor. Without leaving the scope of the question and getting into specific details, I need 3 key components to function as intended (see example code for a better picture):

  • A quick, lightweight TIMER2 ISR to run at 10kHz
  • A presumably slower TIMER1 ISR to run at 1kHz
  • Enough processor space for real-time receiving and processing of incoming serial data

The issue I have is that I need both ISRs to process as tightly to their schedule as possible and from my understanding of how interrupts are processed, this may be difficult. Assuming my TIMER1 ISR takes longer than 200 microseconds (lets say 500us), after 100us, TIMER2 will flag an interrupt. Every following 100us, TIMER2 will try flag an interrupt (which has already been flagged) until the TIMER1 ISR is completed, at which point the TIMER2 ISR will be processed (once). In this way, won't I lose interrupts from TIMER2 during the processing time of TIMER1 ISR?

Furthermore, even if my TIMER1 ISR takes more than just 100us to process, my TIMER2 ISR will be delayed at best.

I have considered enabling interrupts during the TIMER1 ISR but am unclear how instruction resumes after completing the nested interrupt. If while in my TIMER1 ISR I enter into TIMER2 ISR and then complete, do I resume back into TIMER1 ISR or back into normal instruction (ending TIMER1 ISR prematurely)? If the latter is the case, would that also mean that the Rx ISR would also break my TIMER1 ISR if I happen to get serial data during that time?

Overall, I would like my fast TIMER2 ISR to process as close to 10kHz as possible, and my slower TIMER1 ISR to process as close to 1kHz as possible while still giving priority to TIMER2 ISR. Is this possible? I would appreciate any design suggestions to get at close to this functionality as possible.

Thanks in advance!

General program layout:

void setup() {
  Serial.begin(115200);

  cli();

  // set TIMER1 compare match interrupt to 1kHz
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 249;
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS11) | (1 << CS10);
  TIMSK1 |= (1 << OCIE1A);

  // set TIMER2 compare match interrupt to 10kHz
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2 = 0;
  OCR2A = 199;
  TCCR2A |= (1 << WGM21);
  TCCR2B |= (1 << CS21);
  TIMSK2 |= (1 << OCIE2A);
  
  sei();

}


ISR (TIMER1_COMPA_vect) { // 1kHz
  // slow math here
}


ISR (TIMER2_COMPA_vect) { // 10kHz
  // very quickly set pins here here
}


void loop() {
  // parse incoming serial messages
}

Interrupts on the AVR platform have equal priority.

Your program is only an outline. Please post the entire working sketch, or at least a complete working demo.

ISRs should be fast.

Set a Flag in the ISR then exit.

In loop() when you see the Flag, execute the function that needs to run.

1 Like

That is the pathway to oblivion. Forget all the programming details and just tell us what you are trying to accomplish. There is either a better way, or it can't be done.

If your second interval is phase locked to the first, why not derive the slower tick within the faster timer?

1 Like

I don't believe this is strictly true. I am aware that interrupts do have priority, which is taken into consideration when two different interrupts could be processed at the same time. I am also aware that this does not mean that a higher priority interrupt will be processed during a lower priority one. (just being clear here, not trying to be difficult)

This is intentional as I tried to phrase my question abstractly without getting into what exactly I'm doing (because I don't think that information would aid the question). I think it would suffice to accept that TIMER2 ISR is fast and TIMER1 ISR is slower.

But to provide a bit more detail and in response to:

I had intended all math to be handled in the ISR, requiring no external function. I considered this option but was worried that there is no way to reliably ensure proper scheduling as this would put the execution of the function behind all other interrupts, not just my TIMER2 interrupt. But I think this is my best option so far, thank you for the help!

Please read

It is. AVR interrupts are first come, first served. There is no other priority mechanism.

The second loop is not strictly phase locked to the first, although that certainly wouldn't be a problem. Unless I am misinterpreting your suggestion, deriving a slower tick within my TIMER2 ISR would still pose the problem of a slow execution, delaying subsequent fast TIMER2 processing. That's why I believe the two loops must be kept separate.

You're ignoring my request for project details so I'm out. Good luck.

This is pretty tangential to the question at this point but I'd consider taking a look at Nick Gammon's write up on interrupts (specifically the "What is interrupt priority" portion)

I did not ignore your request, and I'm more than happy to disclose more project details should you request. I am not sure I'm at the liberty of posting my exact code here but can provide other information if helpful. If not I totally get the frustration, I'm sorry, and thank you for the help!

Forget TIMER2 completely…
Just count ticks within TIMER1, and on every tenth tick do the timer2 stuff.

Perhaps there is some confusion since TIMER2 and TIMER1 are not great names for this discussion. To clarify: TIMER2 ISR executes 10x more frequently than TIMER1 ISR.

I assume that you're suggesting I execute the fast loop normally and then on every 10th cycle, I also execute the code for the slow stuff. But that code itself will definitely take longer than 100us to execute, so I'm still stuck with the problem of delaying or missing cycles of the fast code.

It depends on how you implement the slow stuff.
As long as it’s non blocking, the fast interrupt can happen whenever it wants, while the slow code executes in the ‘background’.

But your slow event must not overrun its allowed time slot, or you’ll start digging a hole in the stack.

1 Like

Agreed, I'm beginning to think my best option here is to take the slow stuff out of the ISR and have it run as quickly as possible in real time. It's pretty bulky for an interrupt and I'm trying to optimize it as much as possible without going into the assembly.

My only concern is that when I get a serial message in, this could severely slow down my main loop for a cycle and could prevent my math from being done. I think this solution isn't perfect but is the lesser of the two evils since I really need my 10kHz interrupt to run on schedule. Thanks for the help, i really appreciate it!

Regarding INTs:

  • in general, INTs are not nested (an INT cannot interrupt an INT)
  • as long as you do code execution inside one INT handler - a new INT cannot interrupt the current one (still in progress)
  • you can have "pending" INTs:
    but this means: as soon as the previous INT handler finishes - the next one is "scheduled" immediately (and will be performed as as soon as the previous INT handler "returns" (from INT context)
  • You can have "chained INTs" - describe as above:
    An ARM processor used as MCU can be smart to handle also "chained INTs": instead of
    waiting for a complete finish of the previous, and restore and save again all registers - it can "chain" by": skip this "context restore" and "context save" - not needed in this case (of a new INT is pending already and ready to be processed)

You can do "ugly" stuff, like: "enable to let an INT be interrupted by an INT". Possible: the global EI (Enable INT) would do.
But it is very tricky to handle when an INT interrupts another INT being in process.
I would not do.

There are some golden rules for INT handling:

  • keep the ISR (INT handler) as short as possible: often, they just set a semaphore, in order to release a thread (on RTOS) which will do all, but outside of the INT context
  • never "delay" an INT handler: e.g. HAL_Delay(), "stupid" loops ... would keep the INT handler in "INT context" and no other INT can be done (even pending and scheduled already)
  • have the right priorities:
    in case, one INT is more important as the other and potentially they could come "at the same time" - one wins, before the other. The priority handles the case that two are active at the same time, to decide which one first. But it does not handle that a higher prio INT comes in when you process the lower prio one: as long as you are in an INT handler - they cannot be "intercepted" (interrupted) by any other INT, even it has higher prio.

If you follow the golden rules as: "as short as possible" and move the stuff to do to a thread (triggered by the INT) - you should be fine and you do not block for a long time other INTs possible to come.

"As quickly as possible":
An INT on a MCU is activated "immediately": it will kick in after the current machine instruction completed. Sometimes, if a single instruction is a copy from a memory to another - it will kick in when all is done. Instructions are atomic (cannot be interrupted), but after a finished instructions - the INT can be started. This is the fastest INT support possible on an MCU.

There is one exception:
if you run an RTOS, some of the priorities for INTs (e.g. the INTs with lowest prios, e.g. 15...8) are handled in the "scope of the RTOS": the RTOS will now schedule the INTs.
The RTOS will see an INT, but based on the INT prio - the RTOS will decide which INT has to be scheduled/launched.

You can still "bypass" RTOS by using very small (high prio) values for INT priority. Now the MCU matters.

My other golden rule:
Even you keep the INT handler as short as possible, when you rich a state where an "INT is missing" (not processed right on time), or with several INTs running in background, but one INT comes 'too late' (even you have played with INT prios) - the performance of your system (MCU) is too slow: you cannot fix the issue, just by changing to a faster MCU (esp. increasing the performance for code execution, e.g. using ICache, using ITCM etc. to improve speed on current MCU).

When it comes to using an RTOS: it needs a careful design of the threads running, threads triggered by an INT, thread priorities ... Also: avoid "dead locks".

Using a "fancy MCU", providing ICache, ITCM, DTCM ... you can optimize the speed by using the "proper memory" (e.g. INT handler code sits in ITCM, DTCM for stack and local variables, ...).

When you reach a state where the INT processing seems to be too slow - time to think about "code performance optimization" or even to change to a different MCU platform. But often: the "code design" the cooperation between INT and threads - is the main hook to achieve "real-time performance" (therefore an RTOS but if RTOS is wrongly configured and used - no real-time).

1 Like

This is an extremely bad assumption as a basis for any interrupt process.

Anything that takes longer than 100 microseconds (and preferably much less) should be done in the main loop, and even one of those 100 us ISRs would limit your system throughput to 10 kHz, maximum.

The title of this thread suggests that you have taken on a hopeless task. Consider other approaches to do whatever it is you have in mind.

1 Like

Sorry, I confused (swapped) the Timers
The same suggestion still stands.

Thank you for such a thorough and detailed reply, it is very informative and much appreciated! But painful to hear it may be beyond the capabilities of the ATmega...

I'm seeing a trend here that my "slow" ISR is the cause for concern. I would rather avoid putting this code in the main loop as slow things like serial reads would impact the scheduling of the code, but I see that it needs to be significantly trimmed to operate well in an ISR.


Trimming my slow code is certainly possible to some extent; I think I was looking for a graceful solution with interrupt magic and it seems like such a solution doesn't exist.

I think "slow" in my case will have to do because I really don't need anything else to happen except pulse generation in my "fast" loop and speed ramp calculations in my "slow" loop (and the occasional serial read).

In conclusion: use a faster processor :slight_smile:

Thank you all for the help!

YES!!!!!
a 500 micro-sec "spending" in an INT handler is very bad:
it means: your INT can come just with 0.5 KHz period (very slow).

Again: rule is: keep an ISR (INT handler) as short as possible.
And do not add delay somewhere else, e.g. by blocking a thread with lower prio by "wasting time" in a thread with higher prio:
if you keep looping in a higher prio thread, even a lower prio thread was triggered, e.g. by an INT - you slow down this other thread (and INT handling).

If you assume, when running a thread with a lot of code, "release the CPU" in between, at several points to "yield". "give other threads a chance".
RTOS with multi-threading is very tricky, esp. to decide which prio for which thread (and when to release the CPU in between).

My golden rule: consider when a thread "could give up/release" CPU even it has not finished yet, but no problem to let it be interrupted (and to continue later). Let another thread kick in, e.g. handling a real HW INT (maybe needing much less CPU time).

It is also related to the "responsiveness" of a system:
If you heavy math function (let's assume to do a DFT) blocks an input tasks (e.g. UART receiver) and you do not "have anymore control over the system" - you cannot interrupt of stop the current thread/task running - a "bad real-time" system design (when it is neither possible anymore to react to user interactions).

"Real-time" does not mean "all with fastest speed": it means: the "needed things to do in a reasonable time". Tricky system design (not really related to MCU performance, but to think for every thread/task to do as "minimal steps/instructions" needed - is starting point).