How to pass arguements from subroutines back to main routine

Go To Last Post
6 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi - I'd like to have a subroutine return a series of ASCII letters from a subroutine to the primary routine. I had been thinking I might be able to use the stack, though as I understand it it's normally used to pass arguments to subroutines, not from. Anyways, I tried just writing some super simple code to see if it would still work:

.device	AT90S8515
.include "8515def.inc"

.ORG 0x0000
	RJMP reset

reset:
	;Initialize stack
	LDI R16,low(RAMEND)
	OUT SPL, R16
	LDI R16,high(RAMEND)
	OUT SPH, R16
	;Sets portb to outputs
	LDI R16, 0xFF
	OUT DDRB, R16
	
	RCALL stacktest
	POP R17
	OUT PORTB, R17
	donithing:
	RJMP donothing

	stacktest:
	LDI R16, 0xAA
	PUSH R16
	RET

And it didn't work - it just kept on jumping to reset. Is there a way to make this work, or do I have to look for another route? I was thinking I could instead just make the stack at say low(RAMEND) + 20 or something - and then use those 20 bytes to pass arguements back and forth, but as far as I know reading from and writing to the ram is more complicated (and processor intensive) then using the stack. Thanks!

Last Edited: Mon. Jan 10, 2005 - 06:05 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Remember when calling subroutines, the return address is pushed onto the stack at the point of the call, when the processor hits the ret instruction it then pulls this off the stack and starts executing code from this address.

So when in your subroutine you pushed a register onto the stack and then executed a return, it will have taken the byte you pushed onto the stack as part of the address to jump to...which more likley than not is not what you want, hense the reset.

If you want to pass parameters on the stack you need to make room for them before calling the subroutine, and then point one of the index registers at the stack, increment past the saved return address and then use sts to save them directly to the stack.

Alternatly you can pop the return address into a pair of tempory registers,
push on your return values, and the push the return address back.

The following code shows these two methods, try stepping through it in the simulator and watch the registers...it should become clear how they are working.

.include "m128def.inc"

	rjmp	Reset

Reset:
	ldi		R16,low(RAMEND)
	out		spl,R16
	ldi		R16,high(RAMEND)
	out		sph,R16

	clr		R16		; R16=0 at this point
	push	R16		; save dummy var on stack
	rcall	Modify
					
	pop		R16		; R16=FF


	clr		R16		; R16 = 0 again
	rcall	MakeRoom
	pop		R16		; R16 = FF

Forever:
	rjmp	Forever


;
; Return a parameter by modifying a dummy variable on the stack
;
Modify:
	in		XH,sph		; Get stack pointer
	in		XL,spl

	adiw	XH:XL,3		; Increment past saved return address

	ser		R16			; set return value	
	st		X,R16
	clr		R16

	ret

;
; Return a parameter by making room on the stack.
;

MakeRoom:
	pop		R17		; Pull return address int temp regs
	pop		R18

	ser		R16		; set return value
	push	R16
	clr		R16

	push 	R18		; restore return address
	push	R17
	ret

A couple of gotyas :-

1) remember if you push registers within your subroutine, you have to take note of how many bytes you pushed.

2) The long jumps of the Mega 128/256 MAY push more than 2 bytes of return address onto the stack.

Hope this helps.

Phill.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK well I was trying to understand how and why your code works - and it made me realize that I really just don't understand how the stack works.

So in just a generic program, you first set the stack pointer to the very last byte in the RAM, right? This byte is empty to begin with. Now when you first call a routine, The stack pointer is decremented by 2 (assuming 2 byte return addresses), and the last two bytes in the stack are now the flash address of the next command after the call command that was just run, with the less significant byte the very last byte in the stack, and the more significant byte the second to last byte in the stack. Then when you return from the subroutine, the AVR looks at the two bytes that are at the stack pointer + 1 and +2 and runs the command at that flash address, then increments the stack pointer by 2 and then clears the last byte in the ram.

Thus - the stack pointer always points to the byte directly before the first byte of the return address. Thus when there is nothing in the stack it refers to the last byte in the ram, as there is no next byte for there to be a return address stored in.

Is that all correct? So this all assumed 2 byte return addresses. Some larger AVRs use 3 byte return addresses, while others use 2 byte return addresses, right? I'm guessing somewhere in the datasheet for a specific AVR it will say how large these return addresses are, right? Where should I look for that information? I don't see why I'm having so much trouble wrapping my mind around this - but I just can't seem to make sense of all of this! Thanks so much for all your help!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

nleahcim wrote:
OK well I was trying to understand how and why your code works - and it made me realize that I really just don't understand how the stack works.

So in just a generic program, you first set the stack pointer to the very last byte in the RAM, right?

Yes, it is a common feature of all the stacks I have ever encountered (AVR, 6809, 680x0, 80x86 etc), that they grow downward, this oftern trips the beginner up, mainly because things higher up the stack are at a lower address !

The stack pointer on the AVR, seems to point to the first free byte below
the stack.

Quote:

This byte is empty to begin with. Now when you first call a routine, The stack pointer is decremented by 2 (assuming 2 byte return addresses), and the last two bytes in the stack are now the flash address of the next command after the call command that was just run, with the less significant byte the very last byte in the stack, and the more significant byte the second to last byte in the stack. Then when you return from the subroutine, the AVR looks at the two bytes that are at the stack pointer + 1 and +2 and runs the command at that flash address, then increments the stack pointer by 2 and then clears the last byte in the ram.

Yes this is basically what happens, when you call the subroutine, the current program counter is pushed onto the stack (decrementing it by 2),
and the address of the subroutine is then loaed into the program counter.

When you return from the subroutine, the top 2 bytes are poped off the stack and loaded into the program counter, and execution continues from there.

This is why you have to be carefull if you push things onto the stack in the routine on top of the return address, that you pull them off before doing the return, because if you don't the wrong bytes gety used for the return address (this is what happened in your code).

Also you have to pull things in the reverse order they where pushed (if you want them to go back into the same registers :-


Test:
    push  R16
    push  R17
    push  R18

; Do something usefull in here

    pop R18
    pop R17
    pop R18
    ret

Quote:

Thus - the stack pointer always points to the byte directly before the first byte of the return address. Thus when there is nothing in the stack it refers to the last byte in the ram, as there is no next byte for there to be a return address stored in.

Or more accurately it points to the first free byte below the stack.

Quote:

Is that all correct? So this all assumed 2 byte return addresses. Some larger AVRs use 3 byte return addresses, while others use 2 byte return addresses, right? I'm guessing somewhere in the datasheet for a specific AVR it will say how large these return addresses are, right? Where should I look for that information? I don't see why I'm having so much trouble wrapping my mind around this - but I just can't seem to make sense of all of this!

Yes that is basically it, from what I can see in the "AVR instruction set.pdf" if the ere is less than 64K of flash between the caller and the subroutine, then 2 bytes are pushed, if there is >64k then 3 bytes are pushed. If you are going to manipulate the stack you of course need to be aware of this. Though the only current device where it will be significant is the Mega128, and only with >64k of code.

Generally however for a device that is register rich like the AVR, it is easier to pass parameters and receive results in register(s), for example I wrote a set of LCD routines that expect a pointer to a string to display in ZH:ZL for example, X and Y co-ordinates in R16, and R17, and return an error code in R16.

[quote[Thanks so much for all your help!

No probs, hope I have made thins a little clearer :)

Phill.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Hi - I'd like to have a subroutine return a series of ASCII letters from a subroutine to the primary routine. I had been thinking I might be able to use the stack, though as I understand it it's normally used to pass arguments to subroutines, not from.

Well using the stack is one way, but not the only way. There are alternatives. The stack is just memory, remember. There's nothing special about it except that it usually has its own pointer register, which makes it useful for portable languages like C. But just like you can push the results to the stack and find them with the stack pointer, so you can store the results anywhere else in memory and return the address in a general purpose pointer register like X, Y or Z. If you know in advance where the results will be, you don't even need to return that.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

prime wrote:
nleahcim wrote:
OK well I was trying to understand how and why your code works - and it made me realize that I really just don't understand how the stack works.

So in just a generic program, you first set the stack pointer to the very last byte in the RAM, right?

Yes, it is a common feature of all the stacks I have ever encountered (AVR, 6809, 680x0, 80x86 etc), that they grow downward, this oftern trips the beginner up, mainly because things higher up the stack are at a lower address !

The stack pointer on the AVR, seems to point to the first free byte below
the stack.

Quote:

This byte is empty to begin with. Now when you first call a routine, The stack pointer is decremented by 2 (assuming 2 byte return addresses), and the last two bytes in the stack are now the flash address of the next command after the call command that was just run, with the less significant byte the very last byte in the stack, and the more significant byte the second to last byte in the stack. Then when you return from the subroutine, the AVR looks at the two bytes that are at the stack pointer + 1 and +2 and runs the command at that flash address, then increments the stack pointer by 2 and then clears the last byte in the ram.

Yes this is basically what happens, when you call the subroutine, the current program counter is pushed onto the stack (decrementing it by 2),
and the address of the subroutine is then loaed into the program counter.

When you return from the subroutine, the top 2 bytes are poped off the stack and loaded into the program counter, and execution continues from there.

This is why you have to be carefull if you push things onto the stack in the routine on top of the return address, that you pull them off before doing the return, because if you don't the wrong bytes gety used for the return address (this is what happened in your code).

Also you have to pull things in the reverse order they where pushed (if you want them to go back into the same registers :-


Test:
    push  R16
    push  R17
    push  R18

; Do something usefull in here

    pop R18
    pop R17
    pop R18
    ret

Quote:

Thus - the stack pointer always points to the byte directly before the first byte of the return address. Thus when there is nothing in the stack it refers to the last byte in the ram, as there is no next byte for there to be a return address stored in.

Or more accurately it points to the first free byte below the stack.

Quote:

Is that all correct? So this all assumed 2 byte return addresses. Some larger AVRs use 3 byte return addresses, while others use 2 byte return addresses, right? I'm guessing somewhere in the datasheet for a specific AVR it will say how large these return addresses are, right? Where should I look for that information? I don't see why I'm having so much trouble wrapping my mind around this - but I just can't seem to make sense of all of this!

Yes that is basically it, from what I can see in the "AVR instruction set.pdf" if the ere is less than 64K of flash between the caller and the subroutine, then 2 bytes are pushed, if there is >64k then 3 bytes are pushed. If you are going to manipulate the stack you of course need to be aware of this. Though the only current device where it will be significant is the Mega128, and only with >64k of code.

Generally however for a device that is register rich like the AVR, it is easier to pass parameters and receive results in register(s), for example I wrote a set of LCD routines that expect a pointer to a string to display in ZH:ZL for example, X and Y co-ordinates in R16, and R17, and return an error code in R16.

[quote[Thanks so much for all your help!

No probs, hope I have made thins a little clearer :)

Phill.


OK - thank you very much Phil - you've been extremely helpful. The code that you posted in your first post now makes perfect sense to me.