interruptinterrupt-handlingatmega

Clearing interrupt necessary inside an ISR? (for Atmega644p uC)


When an interrupt service routine is being executed, is it necessary to clear global interrupts (using the cli(); command for example) to prevent another ISR from being executed or queued?

For example, if an external interrupt INT0 is being executed and while it is executing this same external interrupt were to be triggered again. Would that interrupt be queued to be executed after the first interrupt is finished?

would the follow code prevent an interrupt from being queued if it is executed during the current interrupt or would I need to clear an interrupt queue register?

ISR(someISR_vect){
  cli();
  some code...
  sei();
}

Solution

  • No, you do not need to do that manually. AVR hardware already disables interrupts for you and the compiler will not re-enable interrupts when your handler is executing (by default).


    Quoting from the "Nested interrupts" section of this page (it has no anchors, CTRL + F "Nested"):

    The AVR hardware clears the global interrupt flag in SREG before entering an interrupt vector. Thus, normally interrupts will remain disabled inside the handler until the handler exits, where the RETI instruction (that is emitted by the compiler as part of the normal function epilogue for an interrupt handler) will eventually re-enable further interrupts.

    The ISR() macro defines a function with a special name, and generates a call to that function in the interrupt table in AVR firmware. Additionally, the defined function might have attributes set. The two attributes which are of interest to you are interrupt and signal. Both declare the function as an interrupt handler, which causes the return statement to be replaced with a reti instruction (return from interrupt) and also cause the compiler to generate "function entry and exit sequences suitable for use in an interrupt handler".

    The difference is that the interrupt automatically generates sei instruction which cause the interrupts to be enabled during the execution of your handler, basically overriding the hardware-set default. On the other hand, signal does not do that.

    The only thing left to do is to check which one is set by ISR() by default. By consulting avr-libc code on github, specifically the interrupt.h file we can see that the ISR() macro specifies the __signal__ attribute by default (according to this it's the same attribute). At the same time, the macro ISR_BLOCK which may be passed as an arg to ISR() is blank. Meanwhile, the interrupt attribute is specified by the ISR_NOBLOCK macro few lines below. According to this, the default attribute for your interrupt handlers is signal, which does not re-enable interrupts when your handler is executing. You may verify this by looking at a disassembly of your interrupt vector compiled with and without ISR_NOBLOCK set to see whether the sei instruction is generated.


    As for the behaviour of interrupts while the global interrupt flag is disabled, i will refer to the manual of Atmega644p, which at current time i have found here (it appears to be from 2016, but i doubt anything has changed since then in that regard):

    When using the CLI instruction to disable interrupts, the interrupts will be immediately disabled. No interrupt will be executed after the CLI instruction, even if it occurs simultaneously with the CLI instruction. (...) When using the SEI instruction to enable interrupts, the instruction following SEI will be executed before any pending interrupts, as shown in this example.

    AVR does not have a way to queue interrupts while these are disabled. What happens is that once the interrupt happens, an interrupt-specific flag (in contrast to the global interrupt flag) is set. If the interrupts are disabled, the interrupt handler will not execute. This way an interrupt may be in a pending state as referred to in the quote above. An example would be a TOV (timer overflow) flag in the TC0 (timer0 control) register (found in section 16.9.8 in the manual). When the overflow happens in Timer0, the flag will be set to 1. If the interrupts are globally disabled, the interrupt handler will not run, but the flag will remain set. The interrupt will be in the pending state. If another interrupt of the same type (timer0 overflow) happens before the interrupt handler manages to run, it will be lost, as there is only one flag bit per interrupt type. On the other hand another interrupt type, which has a separate flag, might happen during that time. Since it has a separate interrupt bit flag, it will be not impacted by the timer0 overflow interrupt being in the pending state. It will also be put in the pending state (the flag will be set to 1 but no handler will run until the interrupts are enabled again).

    This is true for flag-based interrupts. AFAIK there are a few interrupts in AVR which are not flag-based and which behave slightly differently, but i have no experience with those and do not want to spread misinformation about them. I'd assume it is still impossible to queue more than 1 of each in the pending state, though.