MORE ON
MACHINE LANGUAGE ROUTINES
by Guy Cousineau


In SMARTBASIC, any PROTECTED RAM area may be used to write machine language routines. But why machine language? Because there are some operations which simply cannot be performed by standard BASIC commands or functions. Additionally, some processor intensive tasks will execute very slowly in BASIC; machine language is one way to speed them up.

In this chapter, we will look at 3 ways of accessing machine language routines from SMARTBASIC. Although source code examples will be supplied, the purpose of this article is NOT to explain Z-80 operation codes and their use -- it is intended for those who know something about ML and want to incorporate it into BASIC programs.

CALL is the simplest function. It takes care of housekeeping operations such as saving program pointers prior to executing your machine language routine. It is the programmer's responsibility, however to preserve the stack or to set up a local stack. In most cases, BASIC's enormous stack should be sufficient.

You may have seen CALL instructions used in sound generation, disk copy programs, and sprite animation. These are three examples of functions which cannot be performed any other way. Here is a sound generation routine:


LDA,0;put value in A

OUT(255),A;send it

RET;return to BASIC


In machine language, this would look like: 62 0 211 255

201. Simple enough now we must POKE it in memory:


10 LOMEM: 27417: REM make room for it

20 for x=0 to 4:REM read 5 values

30 READ y

40 POKE 27407+x,y: REM put in memory

50 NEXT x

60 DATA 62,0,211,255,201


Now if I "CALL 27407", a 0 will be sent to the sound port. If I want to send any other value, I can POKE it into memory at 27408 (where the 0 is) and CALL 27407 again. Refer to other articles on sound generation for the specific information required to create notes or sound effects.

READ/WRITE block routines have been around for some time but all the ones I have seen lack one essential element: they neglect to check for errors. When the EOS performs a read/write operation, the error code is returned in register A. Since we have no direct access to registers, we must devise another approach; here's my routine which I install at 27420:


XORA;clear accumulator

LD(27419),A ;reset error

LDA,0;device

LDBC,0;high block number

LDDE,0;low block number

LDHL,0;RAM address

CALL64755;read block

RETZ;no error

LD(27419),A ;set error

RET;exit


This routine starts by clearing the error then load A with the device, DE with the block number (BC must always be 0) and HL with the address where the information will be read or written. 64755 is the address of the READ block routine, 64758 is the write block routine. If there is no error, the program just returns and the error code in 27419 is zero. When there is an error, the error code will appear in 27419. Following are the addresses for the parameters:


27425device number 4,5,8,24

27430-31block number lo-hi

27433-34RAM address

27436243 for read 246 for write


Following is the entire data stream to POKE in the machine language routine starting at 27420:


175 50 27 107 62 0 1 0 0 17 0 0 33 0 0 205 243 252

200 50 27 107 201


The last byte (201) should be at 27442.


To use this routine, request or determine the block number, memory address, and device number, select read/write, and CALL 27420. Following is part of a subroutine to do that:


99REM R/W error check subroutine

100...poke block, memory, device, etc

...

150CALL 27420

160e=PEEK(27419)

170IF e=0 then return: REM all ok

180PRINT "Error ";e

190STOP


When using this routine, the program will abort when the EOS reports an error. If you want to get more sophisticated, following are some of the error codes:


1,2,3device unavailable

5no file

6file exists

11file too big

12directory full

13tape/disk full

15rename error

16delete error

17range error

21bad device status

24bad directory on medium


Now for something more complex: SPRITE animation! Sprites are managed in 2 tables which the programmer must maintain in RAM:

The first has 32 segments of 8 bytes (256 total) which represent the sprite shape. The 8 bytes represent a horizontal bitmap (on off) of each of the 8 lines of a sprite shape. Thus if sprite number 0 is a circle, you could have the following definition:


00011000 24

00111100 60

01100110102

11000011195

11000011195

01100110102

00111100 60

00011000 24


The next table has 32 elements of 4 bytes which represent the first byte of each set of 4 is the Y coordinate, the second is the X coordinate, the third is the sprite number, and the fourth is the sprite colour. The sprite number may be any value from 0 to 31; the first sprite does not need to be 0. Furthermore, you can use the same sprite definition to place the same shape in different colours on different parts of the screen. You are limited, however to 32 sprite definitions (shapes) and 32 sprite attributes (location and colour).

Sprites can be displayed in 4 fashions:


SIZEVALUE

8x8E0H (224)

8x8 doubleE1H

16x16E2H

16x16 doubleE3H


Following is a machine language routine which updates all 32 sprite positions at once:


LDB,1;register

LDC,0E0H;use 8x8 sprites

CALL64800;update vdp

LDA,1;shape definitions

LDIY,32;do all sprites

LDDE,0;offset in table

LDHL,28256 ;my def's are here

CALL64812;update vram

LDA,0;position table

LDIY,32;do all sprites

LDDE,0;offset in table

LDHL,28512 ;my table is here

CALL64812;update vram

RET


This routine can be POKED anywhere in memory; I use 28000

with the following data:


6 1 14 224 205 32 253 62 1 253 33 32 0 17 0 0

33 96 110 205 44 253 62 1 253 33 32 0 17 0 0

33 96 111 205 44 253 201


The last byte should be at 28037.


Three user parameters must be supplied: the size and the

location of the 2 user tables:


28003 sprite size E0 E1 E2 E3

28017 shape definition (lo-hi)

28032 position table (lo-hi)


The VRAM write routine can be indexed in order to update only one sprite at a time but this would require several modifications to the routine on every CALL. Once you get this installed, you will find that it is fast enough that you don't need to bother for most applications; you would probably spend more time calculating offsets.

In the examples above, I talked about references to memory addresses as lo-hi. This means that you must POKE 2 values which represent a number from 0-65535. Following is the easiest method to make this calculation:


100 x=28017: REM POKE it here

110 y=28256: REM POKE this value

120 GOSUB 900

...

899 REM routine to POKE y into x

900 POKE x+1,y/256: REM high byte

910 POKE x,y-256*PEEK(x+1): REM low byte

920 RETURN


Note in line 900 that y/256 does not necessarily result in an integer value; the POKE command, however, converts all values to integer prior to placing them in memory. Thus extracting the POKed value by using PEEK in line 910 correctly calculates the integer value of the high byte.

If you intend to use complex machine language routines, you may occasionally want to print something to the screen. Here you can make use of 2 routines which already exist in SMARTBASIC:

PRINT CHARACTER IN A resides at 11994. Thus if you want to print a question mark, you simply do:


LDA,'?'

CALL11994


The advantage of the print routine is that it will perform word wraps and screen scrolls when required.


If you want to print a message, you can use the length_encoded routine at 12110:


LDHL,MESSAGE

CALL12110

.....

MESSAGE:

DB14;length of message

DB13;a carriage return

DB'Guy Cousineau'


If you want to get USER input into your routines, you can use the EOS read keyboard routine located at 64620; it returns a character in register A. Note that this routine waits until a character is pressed:


CALL64620;get character

CP3;is it ^C

RETZ;yes, abort

CP'y'; is it YES

JPz,YES;process yes answer

.....


Thus you could use a USR function to get a routine started and it could prompt the programmer/player for the required parameters.

If you plan on creating complex machine language routines, you may wonder how you will ever accurately determine the POKE values. If you have CP/M or TDOS, you can save a lot of work by using a Z-80 assembler in CP/M. Then you can use the resulting PRN or HEX file to determine the POKE values and critical addresses in your routines.

The following program may be even more useful. Start by writing your routine in CP/M and assemble to a HEX file. Then use CPM.COM (CP/M) or FC.COM (TDOS) to convert the HEX file to EOS format. The next step is to run this program which will POKE your routine in memory for you:


100 INPUT "file to assemble "; f$

110 INPUT "drive number "; d

200 ONERR GOTO 500

210 ? CHR$(4);"open "; f$; ",d"; d

220 ? CHR$(4);"read "; f$

229 REM extract the load address

230 INPUT w$: h$=MID$(w$, 4, 2):GOSUB 300

235 w=v*256: h$=MID$(w$, 6, 2):GOSUB 300: q=v+w

240 w$=" "+w$: p=q:? "first byte at", p

250 FOR x=11 TO LEN(w$)-3 STEP 2

260 h$=MID$(w$, x, 2):GOSUB 300: REM get a value

265 POKE p, v: p=p+1:NEXT

270 INPUT w$:IF MID$(w$, 3, 6)<>"000000" GOTO 250

280 GOTO 500 :REM end of file

300 a=ASC(LEFT$(h$, 1))-48: REM high nibble

305 b=ASC(RIGHT$(h$, 1))-48: REM low nibble

310 v=(a-7*(a>9))*16+b-7*(b>9):RETURN

500 CLRERR:?:? CHR$(4);"close "; f$

510 p=p-1:? "last byte at ", p

540 ?:? " BSAVE ,A"; q; ",L"; p-q+1

550 ? CHR$(160); :? CHR$(160);


The last thing the program does before exiting is to calculate the length of your routine and supply you with a BSAVE instruction line. Just scroll past BSAVE, enter a file name, scroll to the end of the line and press RETURN. Now your ML routine is saved as a file which can be quickly reloaded via a BLOAD command.

When creating your routines, be sure to use the assembler ORG directive to set the destination address of your routine above 27407

Once you start experimenting with machine language routines, you may quickly discover the benefits. Sort routines, for example, will run up to 100 times faster in machine language. Should you have any questions about machine language programming, you may contact me.

This concludes my analysis of SMARTBASIC. Should any major topics have been omitted, please let me know. Also, please advise if you would like additional information on any of the topics covered in this series. For a complete set of articles (CP/M or MS-DOS text) or this document (WordPerfect 5.1), please send two single sided formatted disks or one double sided disk, or one DDP for the CP/M information, or one MS-DOS disk to the undersigned.

Back to Top