Programming Techniques - Basic on the 6502 Second Processor
-----------------------------------------------------------
J.G.Harston, 25-Nov-1997
Covers: HiBasic, 6502Tube, Basic filename just loaded, utilising memory,
menu prompting.
A 6502 second processor gives you an extra 64k or memory, but not all
programs take advantage of this. When Basic is entered, it is copied over
to the second processor and runs from there. If you have HiBasic, it is
assembled to run at &B800 giving you memory from &800 to &B800 giving 44k
available. However, this means loosing a rom socket, as you need HiBasic
to run on the second processor and Basic (assembled to run at &8000) when
running with the second processor turned off. Most people end up using
having Basic at &8000 as this runs on both sides. Unfortunately, this
means that the spare memory at &C000 to &F800 in the second processor is
wasted.
The second processor introductory guide suggests putting LOMEM=&C000 and
storing variables above Basic. This works perfectly well, but it implies
that your program is always going to be massively larger than the
variables it uses. What would be more useful it to put the Basic program
above Basic at &C000 and put the variables in &800-&8000. Initially, it
would seem that you would do this with:
PAGE=&C000:LOMEM=&800
Unfortunately, it is not quite that simple. Basic gets confused if PAGE
is higher than HIMEM, you keep getting No room errors whenever you try to
do something a simple as listing the program. Also, you can't just stick
a PAGE move at the start of a program. The program has to be moves to the
new PAGE position. The easiest way of doing this is to load it again.
This then raises the problem of knowing what the program was saved as.
Most programs that reload themselves like this hard-wire the name into the
program giving something like this:
PAGE=&xxxx:CHAIN "MyName"
However, this doesn't work if you have called it with something like
CHAIN "$.Users.Jim.Progs1.MyName"
Fortunately, there's a way of finding this out. When Basic does a CHAIN,
it passes a string to OSFILE to load the program. This string is stored
in Basic's string buffer which is at &0600 in 6502-Basic, and if you don't
do any string operations that filename will still be there. Caution -
string operations include printing numbers! So, to reload the current
program, we can do something like:
CHAIN $&600
as long as we haven't done anything to disturb the string buffer.
So, bearing this in mind, we can construct a program to hold its variables
below its code.
To start with, we need to see if we are running 6502-Basic on a second
process, and change PAGE, HIMEM and LOMEM to suit and reload if necessary.
HIMEM cannot be changed inside a PROCedure or FuNction as you loose the
return address, so the best thing to do is to return a value to set HIMEM
to:
HIMEM=FNhimem0
If the program needs to end with the program listable, or you want to load
another program in the same space, HIMEM needs to be set above PAGE
otherwise No room errors will occur:
DEFPROCend:HIMEM=FNhimem1:END
and
HIMEM=FNhimem1:CHAIN "Program"
And finally, you may want to exit or chain a program into the 'normal'
memory arrangement:
HIMEM=FNhimem2:CHAIN "Program"
As LOMEM will not be at the end of the program, TOP should not be used to
find out how much memory is available - this is sloppy programming anyway.
The following should be used in all cases anyway:
maxmem%=HIMEM-LOMEM
Now that the structure of the program has been described, here are the
three support functions:
FNhimem0 - called to initialise the memory arrangement and reload the
program if necessary. This function will normally actually be called
twice on the second processor, first with PAGE set to &800 when the
program is reloaded to &C000, and then with PAGE set to &C000 when LOMEM
and HIMEM are re-arranged.
DEFFNhimem0:A%=130:IF((USR&FFF4)AND&FFFF00)=&FFFF00:=HIMEM
REM If not running in i/o, just return current HIMEM
IF?&FFF7<>&6C OR HIMEM=&B800:=HIMEM
REM If not running on a 6502, or we are running HiBasic,
REM return current HIMEM
IFPAGE=&C000:LOMEM=&800:=&8000
REM If we've just been loaded above Basic, set LOMEM to
REM bottom of memory and return to set HIMEM to bottom of
REM Basic code
PAGE=&C000:HIMEM=&F800:CHAIN$&600
REM At this point we must be running in 'normal' memory, so
REM move PAGE up, put HIMEM above it and reCHAIN the name
REM stored in the string buffer
FNhimem1 - called to ensure HIMEM is above the program in memory. This is
needed if you want to END and list the program, or load another program
over the current program without overwriting any data in the variable
space.
DEFFNhimem1:IFHIMEM<PAGE:=&F800 ELSE =HIMEM
REM If the program is above the variables, return HIMEM
REM above the program, otherwise return the current HIMEM
FNhimem2 - called whenever you want the memory arrangement returned to
'normal', ie to chain another program at the normal memory position. This
program will be loaded over whatever data is in the variable area.
DEFFNhimem2:IFHIMEM<PAGE:PAGE=&800:=HIMEM ELSE =HIMEM
REM If the program is above the variables, reset PAGE back
REM to the bottom of memory, otherwise return the current
REM HIMEM
I have tested these functions on the following Basics:
Basic 1, Basic 2, Basic 4 (Master), Basic 40 (Compact), BasicZ80,
Basic 5 (Archimedes)
and on the following hardware:
BBC B i/o, BBC B+6502Tube, BBC B+Z80Tube, Master Compact,
Master 128 i/o, Master 128+6502Tube, !Z80Tube, !65Host, !65Tube,
Archimedes
The accompanying program HiDemo (listed below) shows these functions in
operation. They are also used in real applications in the JGH-PD library
in FileIndexer and FormList (on JGH-012) amongst others.
REM > HiDemo
REM Demonstrates running above Basic on 6502Tube
:
HIMEM=FNhimem0
ONERRORIFFNerr:PROCend
PRINT"Program is at &";~PAGE
PRINT"Data space is at &";~LOMEM;" to &";~HIMEM
PRINT"Available memory &";~HIMEM-LOMEM;" (";HIMEM-LOMEM;" bytes)"
REPEATUNTILFNmenu:PROCend:END
:
DEFPROCend:HIMEM=FNhimem1:END
:
DEFFNerr:REPORT:PRINT:=INKEY-1
:
DEFFNhimem0:A%=130:IF((USR&FFF4)AND&FFFF00)=&FFFF00:=HIMEM
IF?&FFF7<>&6C OR HIMEM=&B800:=HIMEM
IFPAGE=&C000:LOMEM=&800:=&8000
PAGE=&C000:HIMEM=&F800:CHAIN$&600
DEFFNhimem1:IFHIMEM<PAGE:=&F800 ELSE =HIMEM
DEFFNhimem2:IFHIMEM<PAGE:PAGE=&800:=HIMEM ELSE =HIMEM
:
DEFFNmenu
PRINT"M: Return to 8BS Menu"
PRINT"H: Chain a program in high memory"
PRINT"C: Chain a program in low memory"
PRINT"Q: Quit"
:
REPEAT
A$=GET$:IFA$="*":REPEATINPUTLINE"*"A$:OSCLIA$:UNTILA$="":PRINT":";:
... A$="*"
A$=CHR$(ASCA$AND(&DF OR(A$<"£"))):UNTILINSTR("MHCQ",A$)
:
IFA$="M":HIMEM=FNhimem2:CHAIN"$.!Boot"
IFA$="C":PRINT"Loads program over data space":INPUT"Filename: "F$:
... HIMEM=FNhimem2:CHAINF$
IFA$="H":PRINT"Loads program over this program":INPUT"Filename: "F$:
... HIMEM=FNhimem1:CHAINF$
:
=A$="Q"
This also demonstrates a good way of prompting for and getting a key-press
from a menu while also allowing multiple '*' commands. I get very annoyed
with programs that only allow me to issue one '*' command and then force
me back to the menu before I can do another one. That means I can' do
something like '*.' to see what's there, then '*Info xxxx' on something.