|Being able to access Basic commands from machine code is not always as easy as it sounds, especially when dealing with Microdrives and the like. Join David Threlfall as he rummages around in the Spectrum's ROM.||
The complicated manipulation of the
stack pointers and error pointer
(ERR_SP) is not necessary unless you
plan to use Interface 1 commands. If you
don't follow this method then only the
first use of this facility will work and the
command may not return to your routine
at all, even after successful execution. |
The routine 'BASIC' has been written so that it's called with the HL register pair pointing to the tokenised version of the command which you wish to have executed. This allows the routine to be used repeatedly, with different commands if necessary. In all cases, the last byte of the command must be that representing carriage return (13 decimal).
Most Spectrum users will be aware that numbers are stored in a rather odd way in Sinclair Basic. First, there are the ASCII characters making up the number, then the single byte 14 followed by the five byte binary representation of the number. The ASCII form is converted to binary form during syntax checking. If we want to avoid this checking, some way has to be found which doesn't entail us translating any numbers we use into binary. Fortunately there is a way, using the VAL function. Simply write numbers as VAL "number"; for example, if we intend to include the number 500 in the code to be executed, we need only write VAL "500".
FOR EXAMPLE ...There are many uses for this routine, but for now I'll give just two examples. Say you wish to SAVE 3000 bytes of code starting at location 50000 into a cassette file called "fred". The normal Basic command would be:
SAVE "fred" CODE 50000,3000
To execute this from machine code we can insert:
SAVE "fred" CODE VAL "50000",VAL "3000"
At label TEXT in the assembler listing. Note that the word 'SAVE' is inserted with its token value (248), as are all the other keywords like CODE and VAL.
The second example is both more complicated and more useful. It's difficult to use the line editor on the Spectrum for reading a string of characters, but the following code inserted at TEXT will read in a$ and put it at locations 30000 upwards - allowing you to access it from your machine code (the byte '0' marks the end of the string):
INPUT a$: FOR k=VAL "1" TO LEN a$: POKE VAL "29999"+k, CODE a$(k): NEXT k: POKE k+VAL "30000", VAL "0"
The only restriction imposed is that the total length of the input line must be less than 128 characters. This can be increased very easily by making more space and copying more with the LDIR.
Finally, there's no way back to Basic from here because we've erased the input buffer, upset the Basic code pointer and destroyed the floating point stack. These are all repairable, but a RST 8 return to Basic is safe and simple.
There are many functions on the ZX
Spectrum that are not easy to use from
machine code. The simple PLOT, colour
and AT features are straightforward.
Tape commands such as SAVE and
LOAD are a little more difficult, but the
Interface 1 commands for Microdrive,
RS232 and streams are almost impossible without a very detailed
understanding of the ROM routines
The technique to be described here isn't generous enough to allow the spectacular gains in speed you would normally expect from machine code. However, it is very simple to use and there's no advantage in speeding up the rate at which you get to the Microdrive routines when the drives themselves have an access time of several seconds.
The method is simply to build a tokenised version of the command to be executed in the Basic input line area (pointed to by the system variable E_LINE).
The steps involved are as follows: |
1. Clear out all the areas above the input area. This will also empty the workspace and floating point stack.
2. Make space for your command in the input buffer.
3. Build the command into the buffer - this will usually be just a question of copying into that area.
4. Manipulate the error stack pointer (ERR_SP) so that the machine will return to your routine whatever happens.
5. Call the ROM routine which evaluates a single line of Basic.
6. On return, clear work areas and test the error flag. If there was an error go back to Basic.
The assembler listing provided is all that's necessary; it's fairly short and all Basic commands can be operated on in the same way. The code is liberally commented, but a few more notes would undoubtedly be helpful.