[img] Calling BASIC from machine code
 MDFS::Info.Comp.Spectrum.ProgTips.Basic.linerun/htm Search  

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



Best viewed with Any Browser Valid HTML 4.0! Authored by J.G.Harston
Last update: 10-Jul-2005