Part 5 of 11 -- Simple Assembly for Atari BASIC - Implement DPOKE
6502 Assembly Atari BASIC Mac/65 Atari 8-bit
Part 1 - Introduction
Part 2 - Learn 82.7% of Assembly Language in About Three Pages
Part 3 - The World Inside a USR() Routine
Part 4 - Implement DPEEK()
Part 5 - Implement DPOKE
Part 6 - Various Bit Manipulations
Part 7 - Convert Integer to Hex String
Part 8 - Convert Integer to Bit String
Part 9 - Memory Copy
Part 10 - Binary File I/O Part 1 (XIO is Broken)
Part 11 - Binary File I/O Part 2 (XIO is Broken)
Writing a two-byte, 16-bit value (DPOKE) goes hand-in-hand with reading (DPEEK). An 8-bit computer environment has many opportunities for managing 16-bit address and the Atari environment is no exception. Address of display lists, Address of screen memory. Numerous buffers and pointers. Address pointers abound.
OSS BASIC XL provides the DPOKE command to perform a 16-bit, two-byte write to a specified memory location. Like DPEEK, this can be duplicated in regular Atari BASIC, although much slower. So, in BASIC XL the action:
Dpoke Address, Value
is frequently seen in Atari BASIC programs expressed as:
POKE ADDRESS, VALUE - INT( VALUE / 256 ) * 256
POKE ADDRESS + 1, INT( VALUE / 256 )
That’s a bit more complicated than DPEEK. Here the value must be broken down into the high byte (integer result of the value divided by 256) and the low byte (the remainder after division), and then the two values assigned to the two consecutive bytes in memory in low byte, high byte order. This can also be worked into a reusable subroutine in Atari BASIC:
10 DPOKE=28500 20 ADDRESS=560:VALUE=39968:GOSUB DPOKE 30 END . . . 28497 REM IMPLEMENT DPOKE 28498 REM INPUT = ADDRESS 28499 REM INPUT = VALUE 28500 HB=INT(VALUE/256) 28501 POKE ADDRESS,VALUE-HB*256:POKE ADDRESS+1,HB 28502 RETURN
This is still fairly simple code. A small difference here is the high byte is calculated and retained to avoid multiple occurrences of the same floating point division. But, again, expressed in Atari BASIC this is fairly slow using one floating point division and one multiplication. As before, applying assembly language rocketry yields:
DPOKE in Mac/65 Assembler Code
0100 ; DPOKE 0105 ; 0110 ; Write a 16-bit word into 0115 ; the specified address. 0120 ; 0125 ; USR 2 arguments: 0130 ; Addr == address to write. 0135 ; Val == Value to write. 0140 ; 0145 ; USR return value is 0. 0150 ; 0155 ; Use the FR0 FP register. 0160 ; The return value for BASIC 0165 ; goes in FR0. 0170 ; No FP is used so all of FR0 0175 ; (and more FP registers) can 0180 ; be considered available. 0185 ; 0190 ZRET = $D4 ; FR0 $D4/$D5 Return value 0195 ZARGS = $D5 ; $D6-1 for arg Pulldown loop 0200 ZVAL = $D6 ; FR0 $D6/$D7 Value 0205 ZADDR = $D8 ; FR0 $D8/$D9 Address 0210 ; 0215 .OPT OBJ 0220 ; 0225 ; Arbitrary. This is relocatable. 0230 ; 0235 *= $9600 0240 ; 0245 INIT 0250 PLA ; argument count 0255 TAY 0260 BEQ EXIT ; shortcut for no args 0265 ASL A ; now number of bytes 0270 TAY 0275 CMP #$04 ; Value, Address 0280 BEQ PULLDOWN 0285 ; 0290 ; Bad args. Clean up for exit. 0295 ; 0300 DISPOSE ; any number of args 0305 PLA 0310 DEY 0315 BNE DISPOSE 0320 BEQ EXIT 0325 ; 0330 ; This code works the same 0335 ; for 1, 4, 8 ... arguments. 0340 ; 0345 PULLDOWN 0350 PLA 0355 STA ZARGS,Y 0360 DEY 0365 BNE PULLDOWN 0370 ; 0375 ; Y is already zero here. 0380 ; 0385 LDA ZVAL 0390 STA (ZADDR),Y 0395 INY ; now Y is 1 0400 LDA ZVAL+1 0405 STA (ZADDR),Y 0410 ; 0415 ; On failure Y = 0 0420 ; On Success Y = 1 0425 ; 0430 EXIT ; 0435 STY ZRET 0440 STY ZRET+1 0445 RTS ; bye. 0450 ; 0455 .END
The error detection and handling have a few differences from DPEEK. The DPOKE routine does not inherently require a response value where DPEEK does, so the DPOKE code does not have separate, early-exit code and uses only one place to set the response value merely to indicate general success or failure.
This code uses an additional Page Zero value to accept the value to write to the address:
0190 ZRET = $D4 ; FR0 $D4/$D5 Return value 0195 ZARGS = $D5 ; $D6-1 for arg Pulldown loop 0200 ZVAL = $D6 ; FR0 $D6/$D7 Value 0205 ZADDR = $D8 ; FR0 $D8/$D9 Address
The real work part of the code is obviously different since writing bytes into memory (DPOKE) is not the same as reading bytes from memory (DPEEK):
0375 ; Y is already zero here. 0380 ; 0385 LDA ZVAL 0390 STA (ZADDR),Y 0395 INY ; now Y is 1 0400 LDA ZVAL+1 0405 STA (ZADDR),Y
The DPOKE version above conforms to my standard plan (more or less) for stack management and error handling. Most of the remaining utilities will stick with this plan.
However, for the sake of (averting) argument there are admittedly more optimal ways to accomplish this depending on the programmer's definition of optimal. For instance, the value to write to the address need not be temporarily stored in Page Zero. After the code pulls the target address from the stack and establishes it in Page Zero it could pull the value and directly store it to the target address, like this:
0375 ; Y must be 1 here. 0380 ; 0385 PLA ; high byte 0390 STA (ZADDR),Y 0395 DEY ; now Y is 0 0400 PLA ; low byte 0405 STA (ZADDR),Y
That does look clever as it subtracts two bytes from the code, and removes ZVAL from Page Zero. However, to make this work the argument handling must be different to pull two, not four bytes worth of arguments, and the Y value must be reset to 1 before arriving at this code, and the error return value needs to be reworked. In other words, the code savings in one place may be consumed by code needed in another place.
The standardized approach may not be the most optimal, but it is conceptually consistent, flexible, and doesn't need to be re-factored from scratch for each different routine. Also, regardless of the degree of inefficiency in this less-than-optimal method the execution time is negligible compared to slower Atari BASIC. More time is spent by Atari BASIC converting the floating point values into 16-bit integers to pass as arguments to the machine language routine than the amount of time for execution of the the machine language routine.
Testing DPEEK and DPOKE
The Atari BASIC program below, TESTDPK.BAS, demonstrates how to load the machine language routines into strings and it tests the two routines:
0 REM H1:TESTDPK.BAS 100 REM TEST DPEEK AND DPOKE USR() TOOLS 110 GRAPHICS 0:POKE 710,0:POKE 82,0 120 GOSUB 10000 130 REM 140 REM TEST DPEEK 150 REM 155 ? "*** DPEEK ***" 160 DL=PEEK(560)+256*PEEK(561) 170 ? "BASIC Dpeek Display List = ";DL 180 DL=0 190 DL=USR(DPEEK,560) 200 ? "USR() Dpeek Display List = ";DL 210 REM 220 REM TEST DPOKE 230 REM 240 CL=10+256*68:REM 711=10,712=68 250 X=USR(DPOKE,711,CL) 260 REM 270 REM VERIFY WITH DPEEK 280 REM 290 ? :? "*** DPOKE ***" 300 ? "10 + 256 * 68 = ";10+256*68 310 CL=PEEK(711)+256*PEEK(712) 320 ? "BASIC After Dpoke = ";CL 330 CL=0 340 CL=USR(DPEEK,711) 350 ? "USR() After Dpoke = ";CL 360 END 970 REM 980 REM DPEEK AND DPOKE 990 REM 10000 DIM DPE$(36),DPO$(38) 10001 DPEEK=ADR(DPE$):DPOKE=ADR(DPO$) 10010 RESTORE 27000 10011 FOR I=0 TO 35 10100 READ D:POKE DPEEK+I,D 10101 NEXT I 10110 FOR I=0 TO 36 10111 READ D:POKE DPOKE+I,D 11000 NEXT I 11001 RETURN 28000 REM dpeek.obj 27000 DATA 104,168,240,10,10,168,201,2 27010 DATA 240,9,104,136,208,252,132,212 27020 DATA 132,213,96,104,153,213,0,136 27030 DATA 208,249,177,214,133,212,200,177 27040 DATA 214,133,213,96 29000 REM dpoke.obj 29000 DATA 104,168,240,28,10,168,201,4 29001 DATA 240,6,104,136,208,252,240,16 29002 DATA 104,153,213,0,136,208,249,165 29003 DATA 214,145,216,200,165,215,145,216 29004 DATA 132,212,132,213,96
First the program performs DPEEK the slow way in BASIC by calculating the 16-bit address of the display list (at locations 560 and 561) with a multiply in BASIC (line 160). Then the program performs the same action using the DPEEK USR() routine (line 190). The value determined by each method is displayed for comparison:
Next the program tests the DPOKE USR() routine by assigning a 16-bit value to locations 711 and 712. First it calculates the 16-bit value and reports it. Then it performs DPOKE via the USR() routine (Line 250). Then it retrieves the contents of the two memory locations by the slower BASIC method for DPEEK and by the DPEEK USR() routine. Finally, it reports the value determined by each method to verify the value was written to memory correctly:
Below are the source files and examples of how to load the machine language routine into BASIC included in the disk image and archive:
DPOKE File List:
DPOKE.M65 Saved Mac/65 source
DPOKE.L65 Mac/65 source listing
DPOKE.T65 Mac/65 source listed to H6: (linux)
DPOKE.ASM Mac/65 assembly listing
DPOKE.TSM Mac/65 assembly listing to H6: (linux)
DPOKE.OBJ Mac/65 assembled machine language program (with load segments)
DPOKE.BIN Assembled machine language program without load segments
DPOKE.LIS LISTed DATA statements for DPOKE.BIN routine.
DPOKE.TLS LISTed DATA statements for DPOKE.BIN routine to H6: (linux)
MAKEDPOK.BAS BASIC program to create the DPOKE.BIN file. This also contains the DPOKE routine in DATA statements.
MAKEDPOK.LIS LISTed version of MKDPOKE.BAS
MAKEDPOK.TLS LISTed version of MKDPOKE.BAS to H6: (linux)
TESTDPK.BAS BASIC program that tests the DPEEK and DPOKE USR() routines.
TESTDPK.LIS LISTed version of TESTDPK.BAS.
TESTDPK.TLS LISTed version of TESTDPK.BAS to H6: (linux)
ZIP archive of files:
Tar archive of files (remove the .zip after download)
For we are his workmanship, created in Christ Jesus for good works, which God prepared beforehand, that we should walk in them.