Working example showing Slots with Return stack and Parm stack
TimC
Posts: 77
Hi All,
Been looking through some old code to try to understand how to use Slots. To make them easier and provide more local variables to work with.
Most of the work was started years ago by Tracy Allen and others. I think I improved on a bit but it does take time to get used to.
For example in normal basic you would write a line: Gosub xPrintEEDATA(char) which runs a routine with one parameter and returns.
In my example it looks like this:
GoToX=xPrintEEDATA : ReturnPointer=ReturnPointer-1 : PUT ReturnPointer,xDispMsg1
ParmPointer=ParmPointer-1 : PUT ParmPointer,char : RUN GoToSlot : DispMsg1:
You could read the two lines as: ' 1:where are we going, 2:How are we getting back, 3:What are we passing 4:Our RETURN label
Without a macro language or a pre-processor it's never going to look neat and tidy.
The Quick flow of the program is this:
Slot 0 EnterMessage
passes to
Slot 1 GetMsgNum
returns to
Slot 0 ShowMessage
passes to
Slot 1 DispMsg
passes to
Slot 2 Print
returns to
Slot 1 DispMsg
returns to
Slot 0 Final
There is more than that but if you run the program is self documents where it is and where it's going and how it is going to return.
Uncomment the '#DEFINE DebugMode lines to see more detail or debug.
The Console output should look like this:
Starting MultiSlot program. It's got slots and stacks oh my!
---> Hi there! Type in your new message now (CR finishes) <---
Just Testing.
What message # to Display (0-5)?
0
Our Message # is:0
Just Testing.
Return Stack is empty. ReturnPointer is Zer0. No Problems
**** FINI ****
Hope someone finds this usefull
Regards
Tim C.
****** LOOK FOR NEWER VERSION BELOW, EASIER TO USE, FIXED SOME BUGS *******
Here is the Main Program
Here is the First Slot Program:
Second and Last Slot
Been looking through some old code to try to understand how to use Slots. To make them easier and provide more local variables to work with.
Most of the work was started years ago by Tracy Allen and others. I think I improved on a bit but it does take time to get used to.
For example in normal basic you would write a line: Gosub xPrintEEDATA(char) which runs a routine with one parameter and returns.
In my example it looks like this:
GoToX=xPrintEEDATA : ReturnPointer=ReturnPointer-1 : PUT ReturnPointer,xDispMsg1
ParmPointer=ParmPointer-1 : PUT ParmPointer,char : RUN GoToSlot : DispMsg1:
You could read the two lines as: ' 1:where are we going, 2:How are we getting back, 3:What are we passing 4:Our RETURN label
Without a macro language or a pre-processor it's never going to look neat and tidy.
The Quick flow of the program is this:
Slot 0 EnterMessage
passes to
Slot 1 GetMsgNum
returns to
Slot 0 ShowMessage
passes to
Slot 1 DispMsg
passes to
Slot 2 Print
returns to
Slot 1 DispMsg
returns to
Slot 0 Final
There is more than that but if you run the program is self documents where it is and where it's going and how it is going to return.
Uncomment the '#DEFINE DebugMode lines to see more detail or debug.
The Console output should look like this:
Starting MultiSlot program. It's got slots and stacks oh my!
---> Hi there! Type in your new message now (CR finishes) <---
Just Testing.
What message # to Display (0-5)?
0
Our Message # is:0
Just Testing.
Return Stack is empty. ReturnPointer is Zer0. No Problems
**** FINI ****
Hope someone finds this usefull
Regards
Tim C.
****** LOOK FOR NEWER VERSION BELOW, EASIER TO USE, FIXED SOME BUGS *******
Here is the Main Program
' ========================================================================= ' ' File...... MsgMaster.bsp ' Purpose... Program for slot demo showing stacks. ' ' Author.... TimC. (based on code from Chris Anderson, Tracy Allen and Parallax) ' E-mail.... ' Started... 30 Mar 2020 ' Updated... ' Tim Comments... Working Great!!! ' ' {$STAMP BS2p, MsgSlot1, MsgSlot2} ' {$PBASIC 2.5} ' ' ========================================================================= ' --- scratch pad assignments ReturnPointerTop CON 62 ' will be initial value of stack pointer ParmPointerTop CON 52 ' will be initial value of Parm pointer ' --- variable assignments DataPointer VAR Word ' Used as an address pointer for DATA statements ReturnPointer VAR Byte ' stack pointer--do not use for anything else! ParmPointer VAR Byte ' Parm pointer--do not use for anything else! GoToX VAR Byte ' for bank switching GoToSlot VAR GoToX.nib1 ' target bank for RUN GoToLabel VAR GoToX.nib0 ' target routine from BRANCH within bank cat VAR GoToX ' this byte can be reused within bank char VAR Byte ' for demo, preserved across banks ' an index for the message to retrieve temp VAR Byte temp2 VAR Byte ' --- crossbank labels targets ---- xInit CON $00 ' bank 0 targets xEnterMessage CON $01 xShowMessage CON $02 xFinal CON $03 xDispMsg CON $10 ' bank 1 targets xDispMsg1 CON $11 xGetMsgNum CON $12 xPrintEEDATA CON $21 ' bank 2 targets '#DEFINE DebugMode 'Follow along on the console ' ---------------------------------------------------- ' --- top of program ' sxGoTo is $00 at reset ---> branch to top BRANCH GoToLabel,[Init,EnterMessage,showmessage,Final] Init: ReturnPointer=ReturnPointerTop ' initialize the return stack ParmPointer=ParmPointerTop ' initialize the Parm stack PAUSE 700 DEBUG 0,"Starting MultiSlot program. It's got slots and stacks oh my!",CR EnterMessage: ' get a new message from the serial port #IF DebugMode #THEN DEBUG "inside slot 0 get message 0,1",CR #ENDIF DEBUG "---> Hi there! Type in your new message now (CR finishes) <---",CR STORE 2 ' All my data is in Slot 2 DataPointer = 0 ' reset data pointer char = " " ' else first character would be a 0 and erase screen DO WRITE DataPointer,char ' Write to the EEPROM DEBUGIN STR char\1 ' Read in one character from the console DataPointer=DataPointer+1 ' Inc to next EEPROM Location LOOP UNTIL (char = CR) ' enter key finishes input WRITE DataPointer,0 ' write end of line char char=0 'points to the message we just typed in 'This is our first slot style GoTo. Read the line as: Where we Going and how are we getting back GoToX=xGetMsgNum : ReturnPointer=ReturnPointer-1 : PUT ReturnPointer,xShowMessage #IF DebugMode #THEN DEBUG "GoTo is Now=", HEX2 GoToX, " ReturnPointer =", SDEC ReturnPointer - ReturnPointerTop, ".",CR #ENDIF RUN GoToSlot ' GoTo get the new message ShowMessage: #IF DebugMode #THEN DEBUG "inside slot 0 show message number 0,2",CR #ENDIF DEBUG "Our Message # is:",DEC char, CR 'This is all we do in ShowMessage, Just show the Message # 'Slot style GoToSUB with a parm GoToX=xDispMsg : ReturnPointer=ReturnPointer-1 : PUT ReturnPointer,xFinal ' stack the return target in this bank ParmPointer=ParmPointer-1 : PUT ParmPointer,char ' put the parameter char on the stack too RUN GoToSlot ' GoTo show the message #X Final: IF char=6 THEN EnterMessage #IF DebugMode #THEN DEBUG "Inside slot 0 Label text 0,3",CR #ENDIF '----- GET ReturnPointer,char ReturnPointer = ReturnPointer - ReturnPointerTop IF ReturnPointer = 0 AND char=0 THEN DEBUG "Return Stack is empty. ReturnPointer is Zer0. No Problems",CR ELSE DEBUG "Return Stack = ", DEC ReturnPointer, " Return Address = ", HEX char, ". There is a problem." ENDIF DEBUG "**** FINI ****", CR PAUSE 700 END
Here is the First Slot Program:
' ========================================================================= ' ' File...... MsgSlot1.bsp ' Purpose... Program for slot demo showing stacks. ' ' Author.... TimC. (based on code from Chris Anderson, Tracy Allen and Parallax) ' E-mail.... ' Started... 30 Mar 2020 ' Updated... ' Tim Comments... Working Great!!! ' ' {$STAMP BS2p} ' {$PBASIC 2.5} ' ' ========================================================================= ' DATA section, messages in eeprom. ReturnPointerTop CON 62 ' will be initial value of stack pointer ParmPointerTop CON 52 ' will be initial value of Parm pointer ' variable assignments DataPointer VAR Word ' Used as an address pointer for DATA statements (if more than 256 bytes of DATA) ReturnPointer VAR Byte ' stack pointer--do not use for anything else! ParmPointer VAR Byte ' Parm pointer--do not use for anything else! GoToX VAR Byte ' for bank switching GoToSlot VAR GoToX.nib1 ' target bank for RUN GoToLabel VAR GoToX.nib0 ' target routine from BRANCH within bank cat VAR GoToX ' this byte can be reused within bank char VAR Byte ' for demo, preserved across banks ' an index for the message to retrieve temp VAR Byte temp2 VAR Byte ' --- crossbank labels targets ---- xInit CON $00 ' bank 0 targets xEnterMessage CON $01 xShowMessage CON $02 xFinal CON $03 xDispMsg CON $10 ' bank 1 targets xDispMsg1 CON $11 xGetMsgNum CON $12 xPrintEEDATA CON $21 ' bank 2 targets '#DEFINE DebugMode 'Follow along on the console ' ---------------------------------------------------- ' --- top of program BRANCH GoToLabel,[dispmsg,dispmsg1,GetMsgNum] ' --- display the stored message ' Calls a second routine to actually print the message DispMsg: #IF DebugMode #THEN DEBUG "inside Display message slot 1,0",CR #ENDIF GET ParmPointer,char : ParmPointer=ParmPointer+1 ' retrieve (pop) the parm we need #IF DebugMode #THEN DEBUG "** Display Message #", DEC1 char, ". **",CR #ENDIF ' as close to a gosub parm subroutine: Gosub xPrintEEDATA(char) as we can make it all compressed in 2 lines ' What's in these 2 lines: Don't forget the branch edit above. ' 1:where are we going, 2:How are we getting back, 3:What are we passing 4:Our RETURN label GoToX=xPrintEEDATA : ReturnPointer=ReturnPointer-1 : PUT ReturnPointer,xDispMsg1 ParmPointer=ParmPointer-1 : PUT ParmPointer,char : RUN GoToSlot : DispMsg1: GET ReturnPointer,GoToX : ReturnPointer=ReturnPointer+1 ' retrieve (pop) the return vector #IF DebugMode #THEN DEBUG "GoTo is Now=", HEX2 GoToX, " ReturnPointer=", SDEC ReturnPointer-ReturnPointerTop, ".",CR #ENDIF RUN GoToSlot ' do it, return to bank 0 ' --- get a user message and store it in eeprom in this bank GetMsgNum: #IF DebugMode #THEN DEBUG "inside get message slot 1,1",CR #ENDIF DEBUG "What message # to Display (0-5)?",CR DEBUGIN DEC1 char : DEBUG CR GET ReturnPointer,GoToX : ReturnPointer=ReturnPointer+1 ' get (pop) the return vector #IF DebugMode #THEN DEBUG "GoTo is Now=", HEX2 GoToX, " ReturnPointer=", SDEC ReturnPointer-ReturnPointerTop, ".",CR #ENDIF RUN GoToSlot ' GoTo back to bank 0 target
Second and Last Slot
' ========================================================================= ' ' File...... MsgSlot2.bsp ' Purpose... Program for slot demo showing stacks. ' ' Author.... TimC. (based on code from Chris Anderson, Tracy Allen and Parallax) ' E-mail.... ' Started... 30 Mar 2020 ' Updated... ' Tim Comments... Working Great!!! ' ' {$STAMP BS2p} ' {$PBASIC 2.5} ' ' ========================================================================= ' data, messages in eeprom msg0 DATA @0,(75) ' reserve space for user message msg1 DATA "The quick brown fox jumpted over the lazy dog.",0 msg2 DATA "This is a switch because we are reading sentences right out of EPROM!",0 msg3 DATA "Not even Houdini could escape If he were alive.",0 msg4 DATA "Back to the future is a great movie one the whole family will enjoy you must see it soon.",0 msg5 DATA "Now is the Time for all good men to come to the aid of the Party!",0 ReturnPointerTop CON 62 ' will be initial value of stack pointer ParmPointerTop CON 52 ' will be initial value of Parm pointer ' variable assignments DataPointer VAR Word ' Used as an address pointer for DATA statements ReturnPointer VAR Byte ' stack pointer--do not use for anything else! ParmPointer VAR Byte ' Parm pointer--do not use for anything else! GoToX VAR Byte ' for bank switching GoToSlot VAR GoToX.nib1 ' target bank for RUN GoToLabel VAR GoToX.nib0 ' target routine from BRANCH within bank cat VAR GoToX ' this byte can be reused within bank char VAR Byte ' for demo, preserved across banks ' an index for the message to retrieve temp VAR Byte temp2 VAR Byte ' --- crossbank labels targets ---- xInit CON $00 ' bank 0 targets xEnterMessage CON $01 xShowMessage CON $02 xFinal CON $03 xDispMsg CON $10 ' bank 1 targets xDispMsg1 CON $11 xGetMsgNum CON $12 xPrintEEDATA CON $21 ' bank 2 targets '#DEFINE DebugMode 'Follow along on the console ' ---------------------------------------------------- ' --- top of program BRANCH GoToLabel,[Init,printEEDATA] Init: DEBUG "Stoping at Slot 2 Init.",CR STOP printEEDATA: GET ParmPointer,char : ParmPointer=ParmPointer+1 ' retrieve (pop) the Var we need #IF DebugMode #THEN DEBUG "We are in printEEData subroutine printing message# ", DEC1 char, ".",CR #ENDIF STORE 2 ' now look at data in the next slot LOOKUP char,[msg0,msg1,msg2,msg3,msg4,msg5],DataPointer ' map to message start address char = " " ' else first character would be a 0 and erase screen DO DEBUG char READ DataPointer,char DataPointer=DataPointer+1 LOOP WHILE char DEBUG CR GET ReturnPointer,GoToX : ReturnPointer=ReturnPointer+1 ' get (pop) the return vector #IF DebugMode #THEN DEBUG CR,"GoTo is Now=", HEX2 GoToX, " ReturnPointer=", SDEC ReturnPointer - ReturnPointerTop, ".",CR #ENDIF RUN GoToSlot ' GoTo back to target
Comments
I never got around to a full stack implementation, thinking it too cumbersome and unnecessary for my purposes. My stack was only one level deep, so its fixed return pointer location in scratchpad memory I named "back", not "stack". An early version of my implementation is still up on my website at http://emesystems.com/OLDSITE/BS2SX.htm#Crossbank.
It is interesting to see how you have made a deeper stack. 10 deep, right? Why top at location 62?
One comment. Since the Stamp assigns space in RAM in order of words, bytes, nibs, bits, I found it important to assign my cross-bank variable to the first word in all the linked programs. Same goes for shared flags and other template variables. Doing so allows new code to be added in any slot independently without worrying about bumping the position of the critical template variables.
The variable name lola is a tribute to the main character in the 1999 crime action drama, Run Lola Run.
62 was just random! and when you want to make changes with slots you have to do it in so many places. Just adding a slot label to jump to and answering a distracting phone call can create debugging problems later. 52 is the start of the data stack, thinking who would ever need a return stack that deep on a stamp.
I agree with you on the assignment, but I have another plan, another slot version and perhaps you were the origin of this as well. When switching Slots save all the variables and restore when you land in the new slot. On a BS2p and up you can save all 26 word vars x4 with 24 scratchpad bytes to spare. So the stacks can not be so big. My goal is to combine the two Slot systems without making it an impossible monster to work with. Maybe the top 2-3 word vars would not have to be saved since they are the same anyway
Here is the Part of the non-stack version showing the Save and restore vars at the bottom
https://www.parallax.com/downloads/nuts-volts-stamp-applications-87-multi-bank-programming
Toward the end of the article Jon suggests an approach where the main program would PUT all of the RAM variables into the scratchpad, saving them, like on a stack, for later retrieval with GETs. His example earlier in that article didn't do that; used a more ad-hoc approach instead.
I found this example from Chris Anderson. I forget what sort of applications he had.
https://forums.parallax.com/discussion/comment/504138
My own apps needed something KISS, fast and systematic. I used my template for over 1000 programs, mostly variations on a Stamp2pe platform that had data storage with the option of eeprom, flash, or FTDI vDrive, also included a small amount of battery backed ram in the RTC chip to survive Stamp reset. There were options for lots of sensors and some effectors, communications by cable or wireless. Usually 6 to 8 slots in use, and almost all of the scratchpad memory was commandeered for data and computation buffers.
About the number 62, I couldn't place why it looked so familiar. That is the top scratchpad memory address in the .bsx and .bse. The later Stamps, p, pe and px had more, top address 126.
I have the merged code working.
So now I get 20 free bytes of local storage per slot, 10 Bytes of stack space and 10 bytes of Scratch space left over.
Going to any slot routine is easier:
GoToX = x13SimpleMessage : PUT ReturnPointer,x05RecordData : GOTO SlotGo ' The simpleist example. No parms passed
And with a parm:
char = 55 'just something to pass
PUT ParmPointer,char : ParmPointer=ParmPointer-1 ' put the parameter char on the stack then dec
GoToX = x11ProcessData : PUT ReturnPointer,x02ReadTemperature : GOTO SlotGo ' Where to go, Where to return, GO!
A slot subroutine starts with it's label and now only needs a goto
_13SimpleMessage:
DEBUG "In Slot ", DEC pgmSlot, " Label ", DEC GoToLabel, CR
GOTO SlotReturn
The idea that Jon William's had of saving RAM variables into the scratchpad is working as well:
SaveVars:
'Decrease the save pointer when we are in a higher bank
'First save B5, the one we will destroy
PUT BankVarsStart, B0(5)
FOR B5 = 6 TO 25
PUT (BankVarsStart + B5 - 5), B0(B5)
NEXT
'Finally restore B5
GET BankVarsStart, B0(5)
RETURN
I still have a lot more work to do before it can be looked at so maybe a few more days.
Regards
Tim
Here is the new version I promised.
Cleaned up some errors with edge cases saving and restoring local variables. The 2 stacks are better managed. I have left in place lots of debug statements just in case things go wrong. 2 bytes of global ram (Global X, GlobalY and GlobalbitQ) are available for your use as well. Added one more trick that might be useful:
It's a subroutine that acts like any other subroutine when the isSubroutine bit set. When running from another slot it will return control back to the caller in any other slot. No Hard coding slot numbers. isSubroutine is cleared automatically when jumping to another slot.
Have fun
Tim
The 3 files Below are: Master 0, Slot 1, Slot 2.