- 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.