Are AVR interrupts re-entrant?

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

Ok, I read the interrupt tutorials/trap thread and couldn't spot where it does say that AVR interrupts are or are not re-entrant definitely. It does say to have critical section or disable then re-enable the interrupt. So, sounds like it's re-entrant.

Just wanted to make that if I do some stuff in my ISR that takes longer than the elapsed timer/counter to expire again, will the ISR be called again. Or not until the current executing ISR exits. If so, I don't have to waste some bytes in writing code to make sure the current ISR finishes executing before it's executed again.

Thx

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

Global Interrupts are disabled on interrupt until the RETI instruction is executed, which enables them.

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

unebonnevie wrote:
So, sounds like it's re-entrant.
On the non-xmega AVR devices, interrupts are disabled globally when the ISR begins executing. If you want to re-enable interrupts, you can do so although it is probably good advice not to do so unless you know exactly what you're doing.

The xmega AVR devices have three interrupt classes (four, if you count the non-maskable interrupt) that have different priority levels. The ISR for a higher priority interrupt can be executed in the midst of the ISR for a lower priority interrupt unless you explicitly disable interrupts globally in the ISR. Said differently, interrupts are not globally disabled upon entry to an ISR in the xmega but all interrupts of the same and lower priority class are effectively disabled.

Also, on the xmega the RETI instruction does not reenable interrupts globally but only reenables interrupts for the same and lower priority level. Consequently, if you disable interrupts globally in the ISR, you must explicitly re-enable interrupts before exiting.

Don Kinzer
ZBasic Microcontrollers
http://www.zbasic.net

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

Thanks, all!

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

The other thing to keep in mind is that when you enter an ISR on a "regular" (non-X) AVR, the flag that was set and generated the interrupt is cleared. If another interrupt of that same type occurs while you are in the handler, that flag is set again. Then, when you leave the ISR and the global interrupts are re-enabled, you will come back to the ISR after what, one instruction? (old brain, limited memory, too lazy to look it up)

Note that if you get two or more interrupts of the same type while global interrupts are disabled, you'll only know that at least one happened, not how many there actually were.

Chuck Baird

"I wish I were dumber so I could be more certain about my opinions. It looks fun." -- Scott Adams

http://www.cbaird.org

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

Quote:
couldn't spot where it does say that AVR interrupts are or are not re-entrant definitely
Just a note on terminology.

An interrupt is neither reentrant nor non-reentrant. Reentrancy applies to a routine, in this case an interrupt service routine (ISR).

Reentrant ISRs (or other routines) may be invoked concurrently without potential failure in the semantic sense, e.g. global variables are not used in a way which could lead to one context clobbering the behavior of another context.

Interrupts can be triggered, disabled or enabled, prioritized, and serviced. If an interrupt is both triggered and enabled, the ISR will be invoked, i.e. the interrupt is serviced, (provided no higher priority ISRs are also triggered/enabled at the same time). As stated above interrupts are disabled upon entry to an ISR on the AVR. Servicing another interrupt of the same type (or other type) will not occur on the AVR unless specific actions are taken to enable interrupts.

C: i = "told you so";

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

So the rule of thumb is: if the interrupt handler doesnt write to any multibyte global variables, everything is cool. If the handler does write to a 2 or 4 byte global (like tics), the main program should disable ints, read tics into tmptics, reenable ints, then continue calculations using tmptics.

Imagecraft compiler user

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

I think reentrancy is more complicated than just ensuring atomicity of operations. For example, this routine is comprised of atomic operations but is not reentrant.

uint8 i;

void interrupt ISR()
{
     enable_interrupts();
     for(i=0;i<100;i++)
         do_something_with_i(i);
}

C: i = "told you so";

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

Bob, remember atomicty not only applies to multibyte variables - if you read/modify/write even a byte variable then you need to ensure atomic operation. This can have implications with many of the control registers for the AVR peripherals that exist in the ram area (sbi/cbi don't apply) or where you modify a number of bits such that cbi/sbi aren't used.


void interrupt ISR() 
{ 
uint8 i; 

     enable_interrupts(); 
     for(i=0;i<100;i++) 
         do_something_with_i(i); 
}

The above example would be re-entrant as the variable i is a local and gets allocated for each call of the function.

The thing to watch out for when doing nested or re-entrant interrupts (terminology noted) is that you never allow the interrupts to happen so fast that the isr never completes ( say you have an encoder and the motor spins it too fast) - the result is two fold - either the stack overflows and the processor crashes or the processor gets crippled with just servicing interrupts. If you have the watchdog enabled he will just reset proceedings until next time. This doesn't make for a very robust system.

Especially in small memory systems (like the AVR) you probably want to avoid anything re-entrant and not to use nested interrupts.

For those of us that have spent days pouring over in-circuit emulator traces back tracking to find where the system crashed know the consequences. Trying to find this kind of bug without in-circuit debug is next to impossible. With processors "one in a million" happens a few times a second - bugs due to atomicity, re-entrancy stack overflow etc may happen only one in a week or more so you're looking for something more like "one in a 100 billion" which is pretty small odds.

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

Hardware interrupts, by their very nature are not re-entrant. This is because the state of the hardware is not locally generated on each execution. So if there is a chance your ISR is running longer than the period of the interrupt, you need to re-think and re-write your ISR. In general ISR's should be as short as possible, servicing only the critical hardware requirements. All other processing should be performed in the main thread of execution. This should resolve any re-entrancy issues if proper buffering is employed.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

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

Clarifying my comments:

1) An ISR can be called reentrant if it is POSSIBLE to be invoked concurrently.

2) A routine can be called reentrant if it is SAFE to invoke concurrently.

C: i = "told you so";

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

unebonnevie said:

"...The ISR for a higher priority interrupt can be executed in the midst of the ISR for a lower priority interrupt unless you explicitly disable interrupts globally in the ISR. Said differently, interrupts are not globally disabled upon entry to an ISR in the xmega but all interrupts of the same and lower priority class are effectively disabled..."

 

I've been looking for a while for information like this; thank you!

 

If anyone still notices this thread, I'd appreciate a little expansion: I'm working on xmega code with many tiny ISRs and one main, regular, fairly short, time-critical ISR which, every 60th time it runs, currently enables global interrupts so it can continue to do a LOT of other stuff while yielding to ALL other interrupts, including itself another 10-40 times (re-entrantly). This "ISR tail" computes and buffers all the data which is then used by each "short main ISR". All 4 DMA channels are already running flat-chat. The ISR tail will always complete before the next time it runs, and status flags positively control the limited amount of data passing between processes. The main ISR tail code must run in preference to the background code, which uses all remaining processor time, but has a much more forgiving timing requirement.

 

From unebonnevie's explanation, I believe that (in contrast to my understanding up to now) I can explicitly CLI and SEI around the short time-critical part of the main ISR (if needed) to prevent any interruptions except NMI, then ISRs for other higher level interrupts can run during the long main ISR tail.

 

But my main ISR still won't be able to run re-entrantly as it's obviously at the same level as itself, and will remain blocked. Is there a way to allow an ISR to be interrupted by another (or the same) ISR, at the same level? Or to convert xmega interrupts to plain old mega-style? Or is it simply not possible for any xmega interrupt to be re-entrant? Thanks in advance.

 

TonyR

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

Is there a way to allow an ISR to be interrupted by another (or the same) ISR, at the same level?

No, that's the whole idea behind the PMIC. To allow interrupts of a higher priority to interrupt those of a lower priority.  While an ISR could be written to allow for re-entrancy, there is no way for the hardware to invoke an ISR while that ISR is executing. 

 

Or to convert xmega interrupts to plain old mega-style?

If all interrupts are set for the same level, won't they behave like "old mega-style?"  While the "I" bit won't be cleared, the PMIC will prevent any other interrupt from being acknowledged until the current ISR returns.  The only exception would be if they were set for LOW level and "Round Robin" scheduling was enabled; this may be advantageous in some situations, but it is definitely not "old mega-style."

 

I have no idea what this means:

...I'm working on xmega code with many tiny ISRs and one main, regular, fairly short, time-critical ISR which, every 60th time it runs, currently enables global interrupts so it can continue to do a LOT of other stuff while yielding to ALL other interrupts, including itself another 10-40 times (re-entrantly). This "ISR tail" computes and buffers all the data which is then used by each "short main ISR". All 4 DMA channels are already running flat-chat. The ISR tail will always complete before the next time it runs, and status flags positively control the limited amount of data passing between processes. The main ISR tail code must run in preference to the background code, which uses all remaining processor time, but has a much more forgiving timing requirement.

Greg Muth

Portland, OR, US

Xplained/Pro/Mini Boards mostly

 

Make Xmega Great Again!

 

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

tonyroe wrote:

From unebonnevie's explanation, I believe that (in contrast to my understanding up to now) I can explicitly CLI and SEI around the short time-critical part of the main ISR (if needed) to prevent any interruptions except NMI, then ISRs for other higher level interrupts can run during the long main ISR tail.

 

But my main ISR still won't be able to run re-entrantly as it's obviously at the same level as itself, and will remain blocked. Is there a way to allow an ISR to be interrupted by another (or the same) ISR, at the same level? Or to convert xmega interrupts to plain old mega-style? Or is it simply not possible for any xmega interrupt to be re-entrant? Thanks in advance.

A common trick to emulate more levels in interrupts, is to call a RETI earlier than the usual exit (ie after your critical stuff, but before the longer tail)

This resets the Interrupt logic and allows other interrupts of the same level (or lower) to occur.

Note the same interrupt might recur, which needs special attention - you could disable that and re-enable later, just before the tail RET.

 

You would need (extreme) care with operating with re-entrant of the same interrupt, as that could overflow the stack - ie if timing is such it can happen twice in a row, what stops that 100x in a row ?

 

If you have a long, rare tail, another way to manage that, is to set a flag in the interrupt, and poll that outside the interrupt system.

This keeps INT code short, but does mean getting to that queued tail task can take a while...

 

 

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

What you want to do is actually very reasonable, but in general it would take an RTOS.  On the 60th ISR execution it would set a semaphore or other RTOS flag which would let a high-ish priority task do the tail-end processing in preference to the background task.

 

You can often fake this behavior by using an either a software interrupt (if the chip supports one) or an unused hardware interrupt that is lower in priority than the one that wants tail-end processing, and trigger that interrupt hardware flag in the 60th ISR execution.  Now when the ISR finishes, and no higher interrupts are pending, that low-priority interrupt will fire off and act as the higher-priority RTOS task, running in preference to the background code.  In that ISR, immediately enable global interrupts so all higher interrupts will be recognized.  What you are doing is using an unused hardware interrupt to emulate a software interrupt.

 

Last Edited: Tue. Apr 12, 2016 - 02:26 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Greg

 

Even with all ints at the same level, I doubt it will behave like a mega in the the way that I need (reentrancy).

 

Sorry for the waffle that you didn't understand - it was just some background.

 

But I think I've solved the problem now - I just need to set up the stack etc so I can execute a "fake" RETI just to re-enable all interrupts (including same level) but then continue to execute the ISR code. It's not nice, but it looks like it'll get the job done that I need (and I really have no option). 

TonyR

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

I settled on your "premature RETI" concept - it does everything I need. I started with a flag, but the background code can't guarantee to check the flag often enough. Thanks all. 

TonyR

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

In assembler, I think that you can

  .global isrname
isrname:
  ...
  push
  push
  reti
  ...
  ret

If you must use C,

you might be able to get away with three lines of inline assembly.

That would still leave a spurious reti.

If that would be a problem,

use awk, sed or Python to edit the .s file so that the second reti becomes a ret.
 

"SCSI is NOT magic. There are *fundamental technical
reasons* why it is necessary to sacrifice a young
goat to your SCSI chain now and then." -- John Woods

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

The 32MHz xmega barely makes it in optimized assembler, so can't use C. I like the idea of changing the original reti at the very end to a ret (nice). I was a bit confused about how a return address goes onto the stack (it's a xmega256A3), but I believe I need something like this...

    push    low(label)
    push    high(label)
    in         r0,EIND
    push    r0
    reti      ; enable ALL interrupts

label:      ; carry on, with all interrupts now re-enabled...

:

:

    ret      ; final exit with a ret (was reti)

 

TonyR

Last Edited: Wed. Apr 13, 2016 - 01:12 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

tonyroe wrote:
The 32MHz xmega barely makes it in optimized assembler, so can't use C. I like the idea of changing the original reti at the very end to a ret (nice). I was a bit confused about how a return address goes onto the stack (it's a xmega256A3), but I believe I need something like this...

    push    low(label)
    push    high(label)
    in         r0,EIND
    push    r0
    reti      ; enable ALL interrupts

label:      ; carry on, with all interrupts now re-enabled...

:

:

    ret      ; final exit with a ret (was reti)

I gather that pushing a constant is allowed on xmegas.

Even so, gnu addresses are all byte addresses, even for flash.

Instead of label, I think you need to push pm(label) or gs(label).

So far as I can tell, they are not documented anywhere.

I do not remember how I learned of them.

There is a reason one should not just divide by two.

I do not remember what it is.

Use pm or gs instead.

 

from 2011

 

Edit: Note that it is still listed as new and unassigned.

"SCSI is NOT magic. There are *fundamental technical
reasons* why it is necessary to sacrifice a young
goat to your SCSI chain now and then." -- John Woods

Last Edited: Wed. Apr 13, 2016 - 09:10 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:

I gather that pushing a constant is allowed on xmegas.

Even so, gnu addresses are all byte addresses, even for flash.

Instead of label, I think you need to push pm(label) or gs(label).

So far as I can tell, they are not documented anywhere.

I do not remember how I learned of them.

There is a reason one should not just divide by two.

I do not remember what it is.

Use pm or gs instead.

from 2011

Mmm no, can't push constants on xmega - I should think a bit more before posting anything. This is my first dabble in code of any kind in nearly 20 years, so quite rusty. But I've set myself a target, so full speed ahead, with apologies for saying stupid things. So far I'm just trying to outline all the code in the project to see if it will possibly run in an Atxmega256a3. There's so much yet to do, all inter-dependent, that testing is going to be a challenge (that I will need to meet quite soon).

 

So no GNU toolchain right now (only the assembler), and I haven't uncovered anything on pm(label) or gs(label). I am very familiar with foggy memory, but do you recall what they might yield or how to use them?

 

TonyR

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

tonyroe wrote:
and I haven't uncovered anything on pm(label) or gs(label)

pm(), or rather variants of it are explained in the GNU Binutils As manual:

 

https://sourceware.org/binutils/...

 

However, the lack of detail for pm() and the total absence of gs() has been noted:

 

https://lists.gnu.org/archive/ht...

 

Bjorn's answer to Georg-Johan's post explains some of it:

 

https://lists.gnu.org/archive/ht...

 

They are a function of the avr-as assembler - not Atmel's Asm2 assembler.

 

EDIT: Doh! Just realised that Michael's link to the 2011 Bugzilla entry is the same thing I just linked to on the binutils developer's mailing list!

Last Edited: Wed. Apr 13, 2016 - 08:49 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
tonyroe wrote:
and I haven't uncovered anything on pm(label) or gs(label)

pm(), or rather variants of it are explained in the GNU Binutils As manual:

 

https://sourceware.org/binutils/...

Alas, it's wrong.

It implies no division by two.

pm_lo8(label) should give one bits 1..8, omitting bit 0.

 

http://www.nongnu.org/avr-libc/u...

is somewhat better, but not all that good.

Anyone know how to go about getting it made better?

"SCSI is NOT magic. There are *fundamental technical
reasons* why it is necessary to sacrifice a young
goat to your SCSI chain now and then." -- John Woods