- Subroutines make use of the stack to keep track of the program counter value upon returning from a subroutine.
- Whenever a program uses subroutine(s), we must first initialize the stack pointer.
initstack: ; r16 SP
ldi r16, HIGH(RAMEND-0x20) ; 0x08 ----
out SPH, r16 ; 0x08 08--
ldi r16, LOW(RAMEND-0x20) ; 0x3f 08--
out SPL, r16 ; 0x3f 083f
- We jump to a subroutine using either RCALL or CALL (relative or not).
- We return from a subroutine using RET.
Example Program
The following program shows an example of calling a delay subroutine. Can you walk through the code and keep track of the registers R16-R18, the program counter, the stack and the stack pointer?
.cseg
.org 0x0
rjmp initStack
.org 0x2a
initStack:
ldi r16, HIGH(RAMEND-0x20)
out SPH, r16
ldi r16, LOW(RAMEND-0x20)
out SPL, r16
start:
ldi r16, 0
rcall delay
dec r16
rjmp start
delay:
push r16
push r17
push r18
ldi r16, 20
loop1:
ldi r17, 255
loop2:
ldi r18, 255
loop3:
dec r18
brne loop3
dec r17
brne loop2
dec r16
brne loop1
pop r18
pop r17
pop r16
ret
- Your subroutine should begin by pushing all of the register values that your subroutine uses onto the stack.
- Your subroutine should end with popping all of the saved register values off of the stack and into the appropriate registers.
- If you don't do this, your subroutine may corrupt the value of a register that the code calling the subroutine relied on.
General Stack Usage Rules
- The stack is used by:
- PUSH and POP
- CALL, RCALL and RET
- Interrupts (more on these later this quarter)
- If using any of the above, you must initialize the stack pointer.
- For every PUSH there must be a POP.
- A RET is needed at the end of every subroutine.
- Never jump or branch (RJMP, BRNE, etc) into a subroutine.
- Never jump or branch out of a subroutine.