Path:
Home =>
AVR overview =>
Interrupt programming => Interrupts and Resources
(Diese Seite in Deutsch:
)
Interrupts and Resources
Interrupts are effective because they only require action when those are really
necessary. In most cases it is not necessary to predict when exactly this will
happen. The mechanism even allows that several events overlap.
But: they need some specific resources to work well:
- They need a functioning stack.
- As they can occur at any time, and if the service routine alters flags in
the status register, they have to save and, at the end,
to restore the status register.
- All other used resources, such as registers, SRAM content, internal
hardware etc., all need to be exclusive for the interrupt: whenever you change
something, you'll have to make sure that these changes are fine and do not
interact with other program uses of such resources.
Interrupts can occur at any time. They change the controller's program counter
and so execute different code. In order to return back to the original place, to
before the event occurred, they need to store that address somewhere. In a few
older AVR types (AT90S1200, ATtiny12) they had an internal storage place for those
16-bit addresses. All newer devices use the stack for this purpose.
The stack is located in the SRAM, more specific: in its last bytes. A stackpointer
is used to point to that location, and this stackpointer is automatically
manipulated. Whenever an interrupt occurs, the two address bytes are written to
the stack, and the stackpointer advances two steps backwards. At the end of the
interrupt service routine, when the instruction RETI is executed, these
two address bytes are read back to the program counter. Reading automatically
advances the stackpointer, so its original value is reached after RETI.
So, any interrupt-driven program has to init the stackpointer, once following
its RESET. The following assembler code is to be used for that:
ldi R16,Low(RAMEND) ; Point to the end of SRAM, LSB address
out SPL,R16 ; Init the LSB of the stackpointer
.ifdef SPH ; Does the device have an MSB stackpointer?
ldi R16,High(RAMEND) ; Point to the end of SRAM, MSB address
out SPH,R16 ; Init the MSB of the stackpointer
.endif
The first two instructions have to be executed in any case. SPL is a port
register and holds the stackpointer's lower eight bits. The function LOW
returns the lower 8 bits of the constant RAMEND, which is defined in
the def.inc-file of the device (to be included with the .include
directive. That is the reason why you need the def.inc: RAMEND depends from
the device's SRAM extend.
If your device has more than 32 + (port register extent) + (SRAM-size) >
256 bytes, it has an additional SPH port register that holds the upper 8 bits
of the stackpointer address. The two following instructions only execute in
cases that port register SPH exists as constant (as defined in the def.inc
file). If that symbol name does not exist, those two instructions are not
exceuted.
To demonstrate the mechanism, the following pictures show an interrupt
execution in the simulator avr_sim.
Simulated is an overflow of TC0 in an ATtiny13.
In the first two instructions the stack pointer of the device is initiated.
It points to the last location in the SRAM of the device.
With the next four instructions the timer is started in normal mode and
with a prescaler value of one. The timer's overflow int has been enabled.
When the timer reaches 256 (and restarts with zero) he requests an interrupt
by setting its respective flag bit. If no higher-priority interrupt request
is activated, the overflow interrupt will be executed as next step.
Indeed, the overflow interrupt is now executed.
The stackpointer has been decreased from 0x9F down to 0x9D, two bytes
return address have been written to the SRAM's last two positions, and
the program counter is on the timer-overflow jump address executing the
instruction there.
After the RETI instruction has been executed the stackpointer
returned to its previous value, the execution address has been restored
and the timer waits for the next overflow.
Once the stack has been initiated for handling interrupts, you can use it
for other purposes as well. The instrcution (R)CALL uses it for calls
to subroutines: just like in an interrupt the calling address is pushed to
the stack and the calling address is written to the program counter. Whenever
your subroutine is completed, you can return to the calling address with the
instruction RET. And, like RETI, the return address is read back
from the stack to the program counter.
Another use of the stack: you can PUSH the content of a register on
the stack, and later restore this (or any other) register with POP.
This even works with interrupts: whenever those occur they use the next two
stack locations for the address to jump back on RETI, no matter where
the stackpointer points to when the interrupt starts. As the interrupt return
address is always on the bottom of the stack (the stackpointer advances
backwards), after RETI the program finds its pushed stack content at
the right place, even if in between an interrupt has been executed.
The stack size can require a few bytes at the end of the SRAM. As only one
interrupt is executed at a time, while others wait for their execution, this
size is very small. Even ten interrupts in a row need only two bytes of stack
space. If you use many additional PUSHs and POPs you might need
a bit more stack space. If you program in C, your stack might be unnecessarily
filled with several useless PUSHs by the compiler, but not so in
assembler.
Forgetting to init the stack with interrupts enabled yields some funny results.
As the stackpointer points to zero by default, and as there is no SRAM space
where something can be stored, the interrupt will not be able to store and
remember the return address. It therefore executes, but only once. And never
returns back, to where it was before the interrupt occurred. All subsequent
interrupts also execute correctly, but also never return. Nice example for
a simulator (like avr_sim): this
should recognize the missing stackpointer.
One of the resources you have to take care of in case of an interrupt is the
status register. This is implemented in AVRs only once, so interrupts and
normal program execution both use this resource.
As interrupts are assynchroneous to normal program execution and can execute
at any time, even if normal execution is executing something else than the
sleep instruction, you'll have to save the status register at the beginning
of any interrupt service routine and restore its content at the end. This
applies only if your service routine changes flags. So a typical ISR looks
like that:
Isr:
in R15,SREG ; Save status register content in a register
; ... execute interrupt service
out SREG,R15 ; Restore status register content
reti ; Return from interrupt
In one case this is a difficult choice: if you want to change the T flag in
SREG. The T flag in the status register can be used as a multipurpose flag.
It can be set with SET and cleared with CLT or can be loaded
with a single bit in a register with BST or a single bit in a
register can be overwritten with the T flag by BLD. If you have
saved SREG and if you want to restore it afterwards, change this bit in the
register you have saved SREG in. Otherwise your change in SREG will be
overwritten at the end of the ISR. Or: if nothing else in that routine
changes flags: just do not save and restore SREG. The T flag is the
reason why AVRs do not automatically save and restore the SREG during
interrupts.
There are different methods to save and restore SREG.
Nr. | Save in | Code | Time required Clock cycles |
Pro's | Con's |
1 | Register | in R15,SREG [...] out SREG,R15 | 2 |
Fast | Register needed |
2 | Stack | push R0 in R0,SREG [...] out SREG,R0
pop R0 | 6 | No register required |
Slow |
3 | SRAM | sts $0060,R0 in R0,SREG [...] out SREG,R0
lds R0,$0060 | 6 |
Therefore, method selection and priority is clear: if you have enough registers
available, select (1), if not (2) or (3).
As interrupts can occur at any time, be sure to avoid interferences with normal
program execution. A register or an SRAM location that is used by an ISR cannot
be used by any other program part. Be sure that you do not mix appliances here
without being aware what the consequences could be. If you need one register
or a register pair in an interrupt, handle this as exclusive and note that in
the register defintion section as "Used in interrupt X". If you need
it only temporarily, name it something with an "i", such as rimp
for "Register Interrupt Multi Purpose" or as riTemp. As no
interrupt can occur during an ISR execution the same register can be used
temporarily in different ISRs.
If you need to change 16-bit values that are used in an interrupt from outside
the ISR: disable interrupts temporarily with CLI and enable those if
you have written both registers or SRAM locations. Otherwise you risk than
an interrupt in between works with a partial or incomplete and false value.
An example: An interrupt counts down a value in the register pair R25:R24
with SBIW R24,1, e. g. for the duration of a tone. If you change either
R25 or R24 from outside, make sure that no interrupt occurs in between those
two. You would end up with a completely false duration if that would happen.
A special case can occur if you have a fast and a slow interrupt generation
mixed or if your flag handling routine is very long. The two or more
handling routines then have to ensure that all flag settings are treated
and that no flag is missed. That means you have to carefully select the
what-comes-first and, if necessary, to insert further flag handling routine
calls inside your lengthy routine. Just consider your timings and check
whether something can go wrong or not.
Some basic recommendations
- Do not forget stack initiation.
- Is the status register saved and restored in each branching ISR?
- Define registers either exclusively for interrupts or make sure that no
interference can occur.
- If used in- and outside of ISRs: make a clear distinction who is writing
and who is only reading content, who's the master and who the slave. Take
care when changing 16 bit values. Do'nt forget re-enabling interrupts if
you switched them off temporarily.
- The faster your interrupts follow the more care you'll have to take to
avoid overruns.
To the top of that page
©2009-2019 by http://www.avr-asm-tutorial.net