slows response to interrupt (increases average response time)
increases variability to response time
check →
do other work →
IE here will incur larger response time →
than here →
while(! UCSR0A &(1<<UDRE0))){
DO OTHER WORK
DO OTHER WORK
DO OTHER WORK
};
UDR0 = data;}
Interrupt Servicing Flow
Normal execution flow:
Interrupt Check Flow:
After executing each instruction, check for any pending interrupts
If there is an interrupt, save PC and load address of ISR into PC
After handling the interrupt, old PC is restored and execution of program resumes (return might be handled by the interrupt routine itself)
Interrupt Vector Table
Event #
Handler Address
0
funcPtr[0]
1
funcPtr[1]
2
funcPtr[2]
3
funcPtr[3]
4
funcPtr[4]
5
funcPtr[5]
...
...
Mapping of interrupt events/requests to functions is handled with an interrupt vector table of
EITHER
A table of function pointers
A vector of jmp instructions each with a function address
Interrupt system calls correct function from a table based on event that occurred
Just like a function call, system state should be pushed onto the stack as needed to return and continue execution
Can be handled by prologue code at the begining of the interrupt function
When a routine is called, it is said the interrupt has been “handled” or “serviced”
The call interrupts the execution of the main sequence of code; execution may return to the interrupted sequence when the interrupt routine is finished.
Implementations of Interrupt Vector Table
Each platform (hardware+software+dev.tools) has some method of defining the interrupt vector table.
Vector tables may be implemented as a list of function addresses or instructions
which is system dependant
Vector holds jump to ISR
.org WHERE_VECTORS_GO
VEC0: jmp ISR0 ;jump to ISR
VEC1: jmp ISR1 ;jump to ISR
VEC2: jmp ISR2 ;jump to ISR
Vector holds address of ISR
.org WHERE_VECTORS_GO
VEC0: ISR0 ;address of ISR
VEC1: ISR1 ;address of ISR
VEC2: ISR2 ;address of ISR
ISR
ISR0: ISR for Interrupt 0
...
...
IRET ; return for ISRs
AVR Interrupt Vector Table
On AVR, interrupt vector table is implemented at the top of program memory with jmp instructions that are listed at beginning of program memory, position determines the interrupt number
Earlier (low-numbered) interrupts have higher priority
Must provide system reset vector at .org 0x0000 with jmp to main
.org 0x0000
jmp Main
.org tells the assembler where in program memory the code following it should be placed
Provide vector table entry of interest
.org URXCOaddr
jmp myISRHandler
URXCOaddr is a hardware-dependant location for the UART receive interrupt
Note line provide in m169pdef.inc: equ URXC0addr = 0x001a; USART0, Rx Complete
Provide Handler
myISRHandler:
; ISR code to execute here
reti;
Interrupts must be enabled globaly in main or elsewhere
main:
sei; Enable Global Interrupts
Enable specific interrupts in control registers in main or elsewhere
Set enable bit RXCIE in UCSRB:
in r16, UCSRB
ori r16, (1<<RXCIE)
out UCSRB, r16
Coding Interrupts in AVR C
Include interrupt macros and device vector definitions
#include<avr/interrupt.h>#include<avr/io.h>
Define ISR using macro and appropriate vector name: by default the compiler determines all registers that will be modified and saves them
(prologue code) and restores them for you (epilog)
ISR(UART0_RX_vect){// ISR code to execute here}
Somewhere in main or function code
sei();//Enable global interrupts
Enable specific interrupts of interest in corresponding control registers. Ex:
UCSRB |=(1<<RXCIE);//enable interrupt
Note line provide in iom169.h:
#defineUSART0_RX_vect_VECTOR(13)
Keep ISR short
ISRs affect the normal execution of program and can block handling of other interrupts and timing critical tasks
Common strategy is to keep ISRs as short as possible
Facilitates overall consistent timing in the system, avoids frequent ISRs occupying the processor, ensures that other pending interrupt requests can be serviced quickly enough
Each ISR should do only what it must at the time of the event, favor setting software signals (flags) for response by other software in due time over doing more work in the ISR itself
some work, like recording a timestamp from a running clock or offloading a data byte from an input queue warrent immediate work
If a long ISR is needed, consider in it allowing nested interrupts to avoid missing other interrupt events or responding to them slowly
Example Diagram (in-class):
Refer to RTOS notes, figures below note ** Keep ISRs Short that show effect of short vs long ISR on Task 1 and Task 2 timing
Use of volatile keyword for shared variable
Use of volatile keyword to prevents erroneous optimization by the compiler for any shared variables.
In the following code, the change of a variable outside the scope of main, in this case by an ISR, is not understood by the compiler and so the compiler would assume that the variable is always 0
global
volatileuint8_t flag;
main:
flag =0;while(flag==0){
do_other_work;}
response0;
flag =0;while(flag==0){
do_other_work;}
response1;
in ISR:
flag =1;
Interaction of Interrupts with read-modify-write of variables shared with ISR
global
volatileint c=100;
ISR code :
c=c+10;
Main:
c = c+1;
c=c+1; is really a 3-step process:
Load c from memory to register
increment register
store register to memory
Consider the follow sequence:
This problem is generally an issue in mult-threaded code, with multiple, concurrent access to a shared variable
Will learn to minimize such unfortunate interactions
ISRs and multi-word code
Global declaration
volatileuint16_t c=0x01FF;
ISR code:
if(c==0x100)
PORTB =1;
Main:
c= c+1;
Consider c = c + 1; as a 5 step process:
Load low byte of c from memory to register
Load high byte of c from memory to register
perform increment
Store low byte register to memory
Store high byte register to memory
Consider the following sequence
Atomic Operation for Resource Access
Once you start using interrupts you must be sensitive to code that it is not OK to interrupt
During execution of such code, some interfering ISRs should be blocked
Alternatively, resources like shared variables or hardware registers may be protected form multiple interfering access by using additional code to flag access to shared resource
Simplest way to guarantee ATOMIC access is to temporarily disable interrupts
AVR gcc provides a macro “ATOMIC_BLOCK” to disable interrupts
Typically, methods exist to globally disable or enable interrupts (AVR provides sei,cli in asm and C).
Furthermore, individual Interrupts can be enabled/disabled according to the status of certain flag bits which may be modified.
It is common to set the enable bits for all the individual interrupts that should be initially enabled and then set the global interrupt enable.
Other interrupts can be enabled and disabled as needed.
You may temporally disable individual interrupts as needed
You may temporally disable all interrupts using global enable/disable commands (sei,cli on AVR)
Many systems disable interrupts upon invoking an ISR (or at least temporarily disable interrupts after an ISR is called to allow the coder to disable them for longer if desired) to prevent other interrupt service routines.
Multiple Pending Interrupts in AVR (lower wins)
If multiple interrupt requests are pending, the order in which they are handled is system dependent
Some predefine priorities based on event number
Others allow software defined priorities
AVR uses lowest-addressed vector
Execution flow returns to main allowing at least one ASSEMBLY instruction to run before handling next IRQ
Interrupting ISRs
What about new interrupts during ISR execution?
If interrupt service routines are (by default) interrupted by higher priority interrupts or at all is system-dependent. You must make yourself aware of how a given system handles interrupts
megaAVR interrupts are disabled by default when ISR is called until reti is encountered
(not the case with AVR XMEGA devices)
on megaAVR, call sei within the ISR to reenable interupts
(on AVR XMEGA call cli within the ISR CLI to disable interrupts)
One the AVR, if a new interrupt request is pending during an ISR the one assembly instruction of foreground code is allowed to execute before the next ISR is called
Interrupt Mask Registers
Interrupt Mask Registers
Potential to enable or disable groups of interrupts through a masking process
Interrupts can be disabled when
Not needed or used
Critical section of code is running
Can’t be interrupted because of trimming or order of operations
Clearing the Interrupt Flag (IRQ)
When ISR is called, the corresponding interrupt flag must be cleared or the ISR would be called again
Depending on the system, the flag may be cleared
Automatically by hardware
Require that software for an ISR must handle clearing the flag
AVR clears the flag automatically using hardware as soon as the ISR is called
Means that if multiple interrupts are mapped to the same ISR there is no way to tell in the ISR itself which
event triggered it
Interrupt Sequence (AVR)
With interrupts enabled and foreground code running, an Interrupt Event occurs
An interrupt request is flagged by the hardware
Current Instruction Completed (machine instruction, which is NOT same as a line of C code)
Address of next instruction is stored on the stack
Address of ISR is loaded into PC and Global Interrupt Enable Bit is Cleared and Specific Interrupt Flag is Cleared automatically indicating it has been handled
Processor Executes ISR
If desired, interrupts should be be reenabled with sei() command to allow the ISR itself to be interrupted (if writing C, avr-gcc provide macro for this)
If any state registers should be saved because they will be changed, they must be explicitly saved (if using C avr-gcc provides macros that do this)
reti is encountered (C macros take care of including this)
PC loaded from stack and Global Interrupt Enable Bit is Set
Foreground code execution continues
Interrupt Response Time (AVR)
For timing critical system design familiarize your self with details like the response time.
The exact response time to an ISR might be the most important time constraint in your system since
Details Quoted from datasheet:
The interrupt execution response for all the enabled AVR interrupts is four clock cycles minimum. After four clock cycles the program vector address for the actual interrupt handling routine is executed.
During this four clock cycle period, the Program Counter is pushed onto the Stack.
The vector is normally a jump to the interrupt routine, and this jump takes three clock cycles. If an interrupt occurs during execution of a multi-cycle instruction, this instruction is completed before the interrupt is served.
If an interrupt occurs when the MCU is in sleep mode, the interrupt execution response time is increased by four clock cycles. This increase comes in addition to the start-up time from the selected sleep mode.
A return from an interrupt handling routine takes four clock cycles.
During these four clock cycles, the Program Counter (two bytes) is popped back from the Stack, the Stack Pointer is incremented by two, and the I-bit in SREG is set.
Questions for familiarizing yourself with a platform for ISR
What to look for when learning a new platform.
How are ISRs and the interrupt vector table defined?
Are there priorities of interrupts and how are they defined?
Nested Interrupts: Do you want them and are they enabled by default? Do you need to enabled or disable interrupts be to allow nested interrupts?
In AVR, interrupts are disabled when an interrupt routine is called, so you need to explicitly call sei() in ISR if desired
Which interrupts should be enabled?
Should only certain interrupts should be enabled? May be a mechanism to allow int. ISRs based on priority. Otherwise, may be able to manually enabled selected interrupts
How is the state restored and what side effects can be caused when interrupts are called?
Generally, ISRs should save and restore status registers and any registers it uses for work. Can use stack or RAM for this.
Where, if anywhere, should interrupts be disabled?
Main code, with atomic or critical sections of code should disable interrupts. May be related to timing or need to access a set of resources without any interruptions to avoid invalid states or just to achieve proper sequences of operations to peripheral hardware.
How are interrupts flagged as being serviced?
Should interrupts do something to indicate IRQ has been handled?
Usually handled automatically internally by processor, but if request is coming from external device it may need a signal that the request has been handled.
Do interrupt service routines need to set some flag to indicate further action to be taken by main code or peripheral?
Slide Fixes:
2022-08: ## ISRs and multi-word code: figure arrow note corrected : hnote right of ISR: load ~~low~~ ++high++(c) to r1