| Calling BASIC from machine code
|
Spectrum BASIC allows you the usual methods to call machine code
from BASIC - USR address
calls the code and returns
the value in the Z80's BC register. However, it can often be useful to be able
to call a piece of BASIC code from machine code.
LINE_RUN at &1B8A executes a line of BASIC in the editing area
pointed to by E_LINE, such as a line entered directly. If you
point E_LINE to your own BASIC you can call LINE_RUN to execute it.
Slightly different code has to be used for 48K Basic
commands and Interface 1 commands as Interface 1 commands always
return via ERR_SP rather than via a RET. See also
fsfile.asm for a Z80 library that
builds on this to provide generic microdrive LOAD and SAVE routines.
; Execute a Interface 1 command at E_LINE
; ---------------------------------------
; NB! Interface 1 commands *ALWAYS* return via (ERR_SP), even when OK
; NB! Must ALWAYS be CALLed, cannot be JPed to as stack fiddled with
;
.MDCommand
SET 7,(IY+1) ; Set 'Not syntax checking'
SET 2,(IY+124) ; Ensure ERR_SP used for errors
LD (IY+10),1 ; Set ZXBasic statement 1
LD HL,(ERR_SP):PUSH HL ; Save ERR_SP
LD HL,&FFFE:ADD HL,SP ; Point to where return address will be
LD (ERR_SP),HL ; Set up new ERR_SP
CALL &1B8A ; Execute command at E_LINE, will always return to here
BIT 7,(IY+0):JR NZ,MDNoError ; No error occured
RES 1,(IY+124) ; Ensure autoload trap avoided
LD HL,&0046:LD (&5CED),HL ; Point to part of error handler
RST &08:DEFB &32 ; Reclaim channels and turn off drives
XOR A ; Set Z to indicate error occured
.MDNoError
POP HL:LD (ERR_SP),HL ; Restore ERR_SP
RET
; On exit,
; NZ=No error, Z=Error occured
; ERR_NR=&FF - ok
; ERR_NR=&14 - BREAK
; ERR_NR=&0B - Interface 1 error
; Could be Invalid name/Missing name/Drive write protected/Microdrive full/Microdrive not present
; File not found/Wrong file type/Writing to a 'read' file
With this code you can then execute an Interface 1 command from machine
code. The following loads a piece of code by executing a LOAD
*"m"
command.
; Load code from microdrive
; -------------------------
RST &08:DEFB &31 ; Wake up Interface 1, ensure E_LINE in correct place
LD DE,(E_LINE):LD HL,MDLoad
LD BC,31:LDIR ; Copy LOAD*"m";VAL"1";"filename"CODE0<0e><0><0><80><0><0><cr><80>
EX DE,HL:CALL &16B9 ; Point WORKSP to end of line
CALL MDCommand ; Execute LOAD command
...
.MDLoad
DEFB &EF:DEFM "*":DEFB &22:DEFM "m":DEFB &22:DEFM ";" ; LOAD*"m";
DEFB &B0:DEFB &22:DEFM "1":DEFB &22:DEFM ";":DEFB &22 ; VAL"1";"
DEFM "filename" ; filename
DEFB &22:DEFB &AF:DEFM "0":DEFB &0E:DEFB &00:DEFB &00 ; "CODE0<0e><00><00>
DEFW &8000:DEFB &00:DEFB &0D:DEFB &80 ; <00><80><00><cr><80>
Note that the file is loaded to &8000 by specifying the address
in the embedded number. The literal number is ignored. This procedure
can be used to load an arbitary file by copying the filename to E_LINE
seperately, and to load to an arbitary address by setting the embedded
number after copying, and from an arbitary drive by overwriting the
drive number.
; Load code from microdrive
; -------------------------
; A=drive number
; HL=>filename
; BC=length of filename
; DE=load address
.MDLoadFile
PUSH DE:PUSH HL:PUSH BC:PUSH AF ; Wake-up trashes all registers
RST &08:DEFB &31 ; Wake up Interface 1, ensure E_LINE in correct place
LD DE,(E_LINE):LD HL,MDLoad1
PUSH DE:POP IX ; IX=>start of command
LD BC,12:LDIR ; Copy LOAD*"m";VAL"1";"
POP AF:OR 48:LD (IX+8),A ; Overwrite drive number
POP BC:POP HL:LDIR ; Copy filename
PUSH DE:POP IX ; IX=>end of filename
LD HL,MDLoad2:LD BC,11:LDIR ; Copy "CODE0<0e><00><00><00><00><00><0d><80>
POP DE:LD (IX+6),E:LD (IX+7),D ; Insert load address
EX DE,HL:CALL &16B9 ; Point WORKSP to end of line
CALL MDCommand ; Execute LOAD command - MUST be called
RET ; Exit with Z=error, NZ=no error
;
.MDLoad1
DEFB &EF:DEFM "*":DEFB &22:DEFM "m":DEFB &22:DEFM ";" ; LOAD*"m";
DEFB &B0:DEFB &22:DEFM "1":DEFB &22:DEFM ";":DEFB &22 ; VAL"1";"
.MDLoad2
DEFB &22:DEFB &AF:DEFM "0":DEFB &0E:DEFB &00:DEFB &00 ; "CODE0<0e><00><00>
DEFB &00:DEFB &80:DEFB &00:DEFB &0D:DEFB &80 ; <00><80><00><cr><80>
; Save code to microdrive
; -----------------------
; A=drive number
; IX=>filename
; HL=start address
; DE=length of data
; BC=length of filename
.MDSaveFile
PUSH DE:PUSH HL:PUSH IX:PUSH BC ; Wake-up trashes all main registers
PUSH AF
RST &08:DEFB &31 ; Wake up Interface 1, ensure E_LINE in correct place
LD DE,(E_LINE):LD HL,MDSave1
PUSH DE:POP IX ; IX=>start of BASIC command
LD BC,12:LDIR ; Copy SAVE*"m";VAL"1";"
POP AF:OR 48:LD (IX+8),A ; Overwrite drive number
POP BC:POP HL:LDIR ; Copy filename
PUSH DE:POP IX ; IX=>end of filename
LD HL,MDSave2:LD BC,19:LDIR ; Copy "CODE0<0e><8000>,<8000><0d><80>
POP DE:LD (IX+6),E:LD (IX+7),D ; Insert start address
POP DE:LD (IX+14),E:LD (IX+15),D ; Insert length
EX DE,HL:CALL &16B9 ; Point WORKSP to end of line
CALL MDCommand ; Execute LOAD command - MUST be called
RET ; Exit with Z=error, NZ=no error
;
.MDSave1
DEFB &F8:DEFM "*":DEFB &22:DEFM "m":DEFB &22:DEFM ";" ; SAVE*"m";
DEFB &B0:DEFB &22:DEFM "1":DEFB &22:DEFM ";":DEFB &22 ; VAL"1";"
.MDSave2
DEFB &22:DEFB &AF:DEFM "0":DEFB &0E:DEFB &00:DEFB &00 ; "CODE0<0e><00><00>
DEFB &00:DEFB &80:DEFB &00:DEFM ",":DEFM "0":DEFB &0E ; <00><80><00>,0<0e>
DEFB &00:DEFB &00:DEFB &00:DEFB &80:DEFB &00:DEFB &0D ; <00><00><00><80><00>
DEFB &80 ; <80>
; MDCAT
; -----
; On entry, A =drive '1'-'8'
; IX=>Parsed header
MDCAT PUSH AF ; Save drive number
LD B,&CF ; B='CAT'
LD HL,MDHDR3 ; HL=>"0"...
AND A ; NC=no filename
CALL MDSETUP ; Set up Int#1 command
LD HL,-5
ADD HL,DE ; Point to drive number
POP AF
LD (HL),A ; Store drive number
INC HL ; HL=>end of line
JP MDCOMMAND
; MDLOAD
; ------
; On entry, A =drive '1'-'8'
; IX=>Parsed header
MDLOAD LD B,&EF ; A= 'LOAD'
CALL MDINIT ; Set up Int#1 command
LD HL,-10
ADD HL,DE ; HL=>load address
LD A,(HL)
INC HL
OR (HL) ; EQ=use file's address
INC HL
JR NZ,MDCOMMAND
LD DE,-7 ; HL=>terminate address
ADD HL,DE
JP MDCOMMAND
; MDSAVE
; ------
; On entry, A =drive '1'-'8', Cy=C
; IX=>Parsed header
MDSAVE LD B,&F8 ; A= 'SAVE'
CALL MDINIT ; Set up Int#1 command
EX DE,HL ; HL=>end of command
; fall through to...
;
; Execute a Interface 1 command at ELINE
; --------------------------------------
; ELINE=>command, HL+1=><80> to be put in
MDCOMMAND
INC HL
CALL &16B3 ; Set end of line and WORKSP
LD HL,(ERRSP) ; Save error handler
PUSH HL
CALL MDWAKE ; Set ERRSP, wake up Interface 1
SET 2,(IY+124) ; Ensure ERRSP used for errors
SET 7,(IY+1) ; Set 'Not syntax checking'
LD (IY+10),1 ; Set ZXBasic statement 1
LD (IY+0),&FF ; No error
CALL MDCALL ; Execute command
LD A,(23610) ; Get ERRNR
INC A ; EQ=ok, NE=error
EX AF,AF' ; Can't PUSH through restore
CALL MDRESTORE ; Clear workspace
EX AF,AF'
MDCMD2 POP HL
LD (ERRSP),HL ; Restore ERRSP
CP 1 ; C=ok, NC=error
EI
RET
MDWAKE LD (ERRSP),SP ; Set up error handler
RST &08 ; Wake up Interface 1
DEFB &31
RET
MDCALL JP &1B8A ; Call LINERUN to execute code
MDRESTORE
RES 1,(IY+124) ; Prevent autoload trap
LD DE,MDRETRN ; Disciple/PlusD return
LD HL,&0046 ; Interface 1 part of error handler
LD (&5CED),HL ; Reclaim channels, turn off drives,
RST &08 ; reset ERR_NR
DEFB &32 ; Call via vector
MDRETRN RET ; Interface 1 returns via ERRSP
; Initialise microdrive call
; --------------------------
; Enter here for filename
; B=command, A=drive, HL=>command
MDINIT SCF ; CS=filename needed
LD HL,MDHDR1 ; HL=> *"m";"...
MDSETUP EX DE,HL
LD HL,(23641) ; HL=>workspace
LD (HL),B ; Insert command
EX DE,HL
INC DE
LD BC,7 ; Copy *"m";VAL"
LDIR ; or VAL"0";""
RET NC ; No filename
LD (DE),A ; Drive character
INC DE
INC HL
LD BC,3 ; Copy ";"
LDIR
PUSH IX
LD B,10
MDSETLP INC IX
LD A,(IX)
CP "!"
JR C,MDSETAD
LD (DE),A
INC DE
DJNZ MDSETLP
MDSETAD POP IX
AND A ; Carry=0
MDSET2 LD BC,6 ; Copy "CODE0<0e><00><00>
LDIR
CALL MDADDR
CALL MDADDR
CCF
JR C,MDSET2 ; Do twice
RET
MDADDR LD A,(IX+13)
JR NC,MDADD2
LD A,(IX+9)
MDADD2 LD (DE),A
INC IX
INC DE
RET
; Microdrive command data
; -----------------------
MDHDR1 DEFB &2A,&22,&6D,&22,&3B ; *"m";
MDHDR3 DEFB &B0,&22,&30,&22,&3B,&22 ; VAL"1";"
DEFB &22,&AF,&30,&0E,&00,&00 ; "CODE0<0e><00><00>
DEFB &00,&2C,&30,&0E,&00,&00 ; <00>,0<0e><00><00>
FSDRV DEFB "T" ; Current drive
Authored by J.G.Harston
Last update: 10-Jul-2005