XL Freak Posted May 25 Share Posted May 25 Just after the 800XL was introduced I purchased my first Atari, a 600XL. Upgrading from a Vic-20, I thought 16k of memory would be enough. Boy was I wrong! It wasn't long and the 600XL was upgraded to the 64k 800XL with a 1050 disk drive. I am primarily self taught, and it was typing in the printed programs from Byte and Compute! magazines that taught me the basics. Although I submitted several entries, only one letter of mine was ever published. Those were the days. In memory and honor of those 'Learn it by typing it in' days, I would like to create an 'article' that I might have submitted to Compute! or Byte, here on the forum. An assembly tutorial for integrating machine code into Atari Basic. I'm sure most of you know these techniques already, but in my early days I always envied those Basic's that had bitwise operators, not realizing they were within my grasp. Recently, I have begun coaching a young friend of mine with Assembly Language on my spare 800XL so eventually this will be a good lesson for him. Well, here goes... LESSON 1: Coding the AND bitwise operator for Atari Basic We will be using Assembly Language to create machine language, or machine code, to be run inside Atari Basic. Before we begin, we should touch on exactly how Basic uses the USR function. It is used like this: VALUE=USR(ADDRESS,[ARG1,[ARG2...]]) with optional arguments in square brackets, and may contain zero or many arguments. Basic converts all arguments to 2-byte integer values and pushes them on the microprocessor stack one at a time in reverse order (so they're pulled off and accessed in the correct order), then pushes a single byte count of arguments. This count byte is pushed on the stack even if there are no arguments passed. Then using some assembly magic, it calls the routine specified by ADDRESS. So the machine language routine must always pull off the count byte, even if it doesn't use it, otherwise when returning to Basic, the system will crash. Each argument is pushed on the stack low byte first, so it must be pulled off and processed high byte first by your machine language routine. CAUTION... I have personally seen assembly routines written for Basic's USR routine that simply pull the count byte off and disregard it without checking to see how many arguments were passed, setting the programmer up for a possible unnecessary round of troubleshooting. I always recommend adding a few more lines of code to validate the correct number of arguments have been passed, and properly process the arguments if an incorrect number of them have been passed. If you're not expecting any arguments, a simple BNE ERR is all that's needed after the PLA to get the count byte. And of course the few lines of error code that you can find in the listing below. If you're expecting one or more arguments, then after the PLA should come a BEQ ZERO, then a CMP #ARGS and finally a BNE ERR. You can see this in lines 1020-1050 in the Assembly listing below. The 'ERR' routine pulls all the arguments off the stack so the computer won't crash, and the ZERO routine simply returns 0 back to Basic. Other languages like C for example have bitwise operators. To see if a bit(s) is set, you AND the number with the value of the bit(s) and if the result isn't zero, the bit is set in the value that was tested. Something like this if(value&bit). Bitwise OR and Exclusive Or (XOR or EXOR) work the same, but with different operators. The bitwise operator OR will set a bit, and XOR will flip a bit. To unset a bit, or turn it off (some say RESET the bit), you must AND it with a mask, which is all bits set except the bit in question. Example, you can reset bit 5 of a value like this newvalue = value&(65535-5) or newvalue = value&65530 or newvalue = value&0xFFFA. For reference, C and many other languages commonly use '&' as the bitwise AND operator, '!' or '|' as the bitwise OR, and '^' or '%' as the bitwise XOR operator. But Atari Basic has no bitwise operators. The AND and OR operators only compare and the returned values signify only equality. Newer versions of Basic, such as FastBASIC, TurboBASIC XL and Altirra Basic have these built in, but good old Atari Basic doesn't. But by using the USR function, it is possible to use machine language versions of these in your Atari Basic programs. First, we must write the Assembly code. The Mac/65 Assembler was used for this lesson. Since you cannot use either AND$ or AND as a variable in Atari Basic, XAND must be used instead. 1000 ;SAVE #D:XAND.ASM 1010 FR0 = $D4 ;FP REG 0 1020 PLA ;GET # OF ARGS 1030 BEQ ZERO ;RETURN 0 IF NONE 1040 CMP #2 ;IF NOT 2 ARGS... 1050 BNE ERR ; FIX & RETURN 0 1060 PLA ;GET ARG1 HI 1070 STA FR0+1 ; SAVE IN FP REG0 1080 PLA ;GET ARG1 LO 1090 STA FR0 1100 PLA ;GET ARG2 HI 1110 AND FR0+1 ;BITWISE AND W/ARG 1120 STA FR0+1 ;SAVE IN FR0 HI 1130 PLA ;GET ARG2 LO 1140 AND FR0 ;BITWISE AND W/ARG 1150 STA FR0 ;SAVE IN FR0 LO 1160 RTS ;RETURN VALUE 1170 ERR ; IF ERR, PULL OFF 1180 TAX ; ALL ARGS X=#ARGS 1190 ER1 1200 PLA ;GET UNUSED ARG HI 1210 PLA ;GET UNUSED ARG LO 1220 DEX ;COUNT DOWN BY 1 1230 BNE ER1 ;DO AGAIN IF MORE 1240 ZERO 1250 LDA #0 ;CLEAR FR0 TO 1260 STA FR0 ; RETURN ZERO 1270 STA FR0+1 1280 RTS So what about OR and EOR you ask? Well, all that's needed is to replace the AND in lines 1110 and 1140 with ORA or EOR respectively, then re-assemble. You'll notice that only two of the numbers have changed, so only those changed numbers need to be updated in the Basic listing, with XAND and XAND$ changed to XOR/XOR$ and XEOR/XEOR$ respectively for the other two operators. Another note of interest is since Atari Basic's USR function only deals internally with 2 byte integers, the maximum value that can be sent to or returned from the USR function is 65535, or $FFFF. Since the assembler listing lists opcodes in hexadecimal, that's what I used in my Basic listing. In the listing below, lines 1000-1050 first reads in the size of the routine in bytes, then reads in the hex machine language data one byte at a time, calling the actual hex to decimal conversion routine at lines 3000-3110. 1 REM SAVE "D:XAND.BAS 1000 DIM A$(2) 1010 RESTORE 2000:READ A$:GOSUB 3000 1020 IX=A:DIM XAND$(IX):XAND=ADR(XAND$) 1030 FOR I=1 TO IX:READ A$:GOSUB 3000 1040 XAND$(I,I)=CHR$(A) 1050 NEXT I 1100 REM DEMO 1110 R=INT(RND(0)*65536) 1120 ? "DEMO: ";R;"&15 = "; 1130 ? USR(XAND,R,15) 1900 STOP 2000 REM XAND ML DATA 2010 DATA 25 2020 DATA 68,F0,14,C9,02,D0,11,68,85,D5 2030 DATA 68,85,D4,68,25,D5,85,D5,68,25 2040 DATA D4,85,D4,60,AA,68,68,CA,D0,FB 2050 DATA A9,FF,85,D4,85,D5,60 3000 REM HEX TO DEC CONVERTOR 3010 REM ASSUMES 2 HEX DIGITS IN A$ 3020 HI=ASC(A$(1,1))-48 3030 IF HI<0 OR HI>22 THEN 3200 3040 IF HI>9 AND HI<17 THEN 3200 3050 IF HI>9 THEN HI=HI-7 3060 LO=ASC(A$(2,2))-48 3070 IF LO<0 OR LO>22 THEN 3200 3080 IF LO>9 AND LO<17 THEN 3200 3090 IF LO>9 THEN LO=LO-7 3100 A=LO+HI*16:RETURN 3200 REM FORCE INVALID VALUE ERROR 3210 A=VAL(A$):RETURN As you can see in the example in the listing (lines 1100-1130), you use the new XAND function like this: NEWVALUE=USR(XAND,VALUE,BITS). If you change the code to use the XOR function, use it like this: NEWVALUE=USR(XOR,VALUE,BITS). XEOR is used like this: NEWVALUE=USR(XEOR,VALUE,BITS). And to clear, or reset, one or more bits, do this: NEWVALUE=USR(XAND,VALUE,65535-BITS). If you notice, the above Basic routine takes a few seconds to read and process the data for the XAND function. Add that much more time twice again for the XOR and XEOR routines and it's a noticeable delay in the startup of your Basic program. So how do we fix that? Two ways. First, we could write a machine language hex to decimal function to be used with the USR function instead of calling the convertor routine in lines 3000-3100. We may discuss that in the next lesson. Or... we can alter and run the above Basic code once to create a string variable with the machine language code inside it and remove all the rest of the code. Unfortunately, the special characters comprising ATASCII don't display correctly in this forum, so you can't copy and paste them, but add these two lines to the above code 1025 ? CHR$(125); "10 DIM XAND$(37):XAND$="; CHR$(34); 1055 ? chr$(34); ":XAND=ADR(XAND$) and replace line 1040 with this 1040 XAND$(I,I)=CHR$(A):? CHR$(27); CHR$(A); and the program will generate the string variable for you. This will create a line 10 for you when the program is ran. All you need to do is change the line number to whatever you like, then press RETURN while the cursor is on the line. You no longer need the rest of the program containing the loader and conversion code. None of it. You may delete it line by line, or you may enter NEW, then hit RETURN again on the line it generated for you which will leave only that line. That's all you need. Do the same with the XOR and XEOR code. You should end up with 3 lines of code to define XAND, XOR and XEOR. A word of caution! Basic is made up of several tables. They are the Variable Name Table or VNT, Variable Value Table or VVT, Statement Table or ST and the String Array Table or SAT. In that order. While the other tables are built during the editing of the program and only referenced during runtime, the SAT is created and modified when code is ran. And since it sits behind the ST, any line of code being entered or altered will change the address of the SAT as well as every string or array it contains. Also consider that when a line of code is entered without a line number, it is given the implied line number of 32768 and executed immediately. But since it is given an actual line number and saved to the ST, it also changes the SAT address. So once the program isn't running any longer, the value of the XAND variable, which is the address of the XAND$ string, has changed, so you can no longer use the XAND function the same way, or it will crash the computer, or at best return erroneous values. But you can still use XAND, XOR and XEOR in edit mode by changing the usage from NEWVALUE=USR(XAND,VALUE,BITS) to NEWVALUE=USR(ADR(XAND$),VALUE,BITS) which will start the execution of the machine language at the correct address. Just be sure the program has ran at least once first to set the machine language strings. I hope that someone finds this little tutorial useful, or at least entertaining. Cheers! 2 1 Quote Link to comment Share on other sites More sharing options...
Gury Posted May 26 Share Posted May 26 Hello XL Freak, thank for this very educational and helpful article 👍. It can be useful, for beginners and for all, who want to polish and upgrade knowledge. Hope to see you with new article in the future. Best regards, Gury 1 Quote Link to comment Share on other sites More sharing options...
mono Posted May 26 Share Posted May 26 Takee a look at this solution: http://www.atari.org.pl/forum/viewtopic.php?pid=294856#p294856 1 Quote Link to comment Share on other sites More sharing options...
Rybags Posted May 26 Share Posted May 26 The way I'd do it - just store the routines in strings and call by USR(ADR( For my own program - I'd not bother the parameter checking - if you get it wrong the computer usually just hangs and you press Reset. Potentially you could just have the one program that does all of the operations and have an extra parameter that chooses which one to return of AND, OR, EOR. 1 Quote Link to comment Share on other sites More sharing options...
XL Freak Posted May 27 Author Share Posted May 27 (edited) 20 hours ago, mono said: Takee a look at this solution: http://www.atari.org.pl/forum/viewtopic.php?pid=294856#p294856 That's very interesting. And very clever. However, it's not very useful in a real world app because you can' have a Gr.8 screen open just to do bit mapping. 19 hours ago, Rybags said: The way I'd do it - just store the routines in strings and call by USR(ADR( For my own program - I'd not bother the parameter checking - if you get it wrong the computer usually just hangs and you press Reset. Potentially you could just have the one program that does all of the operations and have an extra parameter that chooses which one to return of AND, OR, EOR. That's a possibility. And probably a better way if you're short on variable names. I imaging the ADR function is probably pretty fast, with only a single lookup and convert INT to FP. But if you're doing it a lot, the space for the extra code and the small bits of extra time may add up to be disadvantageous. Agreed: In my own program I'd probably not do error checking either. But I don't think I'd roll them all into a single routine tho... the USR call is already much larger in an expression than using a real bitwise operator and I wouldn't want to make it any larger. Thanks for the feedback! I apologize guys and gals, I was entering the next lesson, and not being completely used to the new 65% Alice format keyboard I'm learning to use (lots of Fn key combinations), I hit the wrong keys and sent it into outer space 😢😡😫 and was unable to retrieve it. This forum used to save what you had typed in, but I guess that's either been depreciated or I blew it up real good. I will get it posted back in a day or two... Edited May 27 by XL Freak Quote Link to comment Share on other sites More sharing options...
XL Freak Posted May 27 Author Share Posted May 27 Lesson 2 - XOR and XEOR Instead of Hex to Decimal conversion, we need to finish up the bitwise operators. This lesson will be simple. I have created a single Basic program to create the machine language strings for all 3 bitwise operators, XAND, XOR and XEOR. When you run the program, it will create a new file on your disk called XFUN.LST which can be included into another program, using the ENTER command, to use the machine language functions. Usage: Here's how to use the new functions in your own programs: NEWVALUE=USR(XAND,VALUE1,VALUE2) NEWVALUE=USR(XOR,VALUE1,VALUE2) NEWVALUE=USR(XEOR,VALUE1,VALUE2) Enjoy! XFUN.BAS 1 REM SAVE "D:XFUN.BAS 900 ? :? "CREATING FILE XFUN.LST..." 910 OPEN #1,8,0,"D:XFUN.LST" 1000 DIM A$(2) 1010 RESTORE 2000:READ A$:GOSUB 3000:IX=A 1015 ? "WRITING XAND CODE..." 1020 ? #1;"10 DIM XAND$(37):XAND$=";CHR$(34); 1030 FOR I=1 TO IX:READ A$:GOSUB 3000 1040 PUT #1,A 1050 NEXT I 1060 ? #1;CHR$(34);":XAND=ADR(XAND$)" 1090 REM XOR 1110 ? "WRITING XOR CODE..." 1120 ? #1;"20 DIM XOR$(37):XOR$=";CHR$(34); 1130 FOR I=1 TO IX:READ A$:GOSUB 3000 1140 PUT #1,A 1150 NEXT I 1160 ? #1;CHR$(34);":XOR=ADR(XOR$)" 1190 REM XEOR 1210 ? "WRITING XEOR CODE..." 1220 ? #1;"30 DIM XEOR$(37):XEOR$=";CHR$(34); 1230 FOR I=1 TO IX:READ A$:GOSUB 3000 1240 PUT #1,A 1250 NEXT I 1260 ? #1;CHR$(34);":XEOR=ADR(XEOR$)" 1300 ? "CLOSING FILE...":CLOSE #1 1310 ? "ALL DONE... THANK YOU!!!":? 1900 STOP 2000 REM XAND ML DATA 2010 DATA 25 2020 DATA 68,F0,14,C9,02,D0,11,68,85,D5 2030 DATA 68,85,D4,68,25,D5,85,D5,68,25 2040 DATA D4,85,D4,60,AA,68,68,CA,D0,FB 2050 DATA A9,FF,85,D4,85,D5,60 2110 REM XOR 2120 DATA 68,F0,14,C9,02,D0,11,68,85,D5 2130 DATA 68,85,D4,68,05,D5,85,D5,68,05 2140 DATA D4,85,D4,60,AA,68,68,CA,D0,FB 2150 DATA A9,FF,85,D4,85,D5,60 2210 REM XEOR 2220 DATA 68,F0,14,C9,02,D0,11,68,85,D5 2230 DATA 68,85,D4,68,45,D5,85,D5,68,45 2240 DATA D4,85,D4,60,AA,68,68,CA,D0,FB 2250 DATA A9,FF,85,D4,85,D5,60 3000 REM HEX TO DEC CONVERTOR 3010 REM ASSUMES 2 HEX DIGITS IN A$ 3020 HI=ASC(A$(1,1))-48 3030 IF HI<0 OR HI>22 THEN 3105 3040 IF HI>9 AND HI<17 THEN 3105 3050 IF HI>9 THEN HI=HI-7 3060 LO=ASC(A$(2,2))-48 3070 IF LO<0 OR LO>22 THEN 3105 3080 IF LO>9 AND LO<17 THEN 3105 3090 IF LO>9 THEN LO=LO-7 3100 A=LO+HI*16:RETURN 3105 REM FORCE INVALID VALUE ERROR 3110 A=VAL(A$):RETURN Quote Link to comment Share on other sites More sharing options...
XL Freak Posted May 31 Author Share Posted May 31 Lesson 3 - DPOKE and DPEEK Although the work-around is relatively simple and easy, Atari Basic is sorely missing the DPOKE command and the DPEEK function. They are quite easily implemented using machine language and the USR function. Say goodbyte 😉 to the days of using the complex and convoluted work-arounds. These new functions are both easy to use and very fast. DPOKE.ASM 1000 ;SAVE #D:DPOKE.ASM 1010 FR0 = $D4 ;FLOAT REG #0 1020 PLA ;GET # ARGS 1030 BEQ ZERO ;RETURN 0 IF NONE 1040 CMP #2 ;SHOULD BE 2 ARGS 1050 BNE ERR ;FIX & RET IF NOT 1060 PLA ;1ST ARG IS ADDR 1070 STA FR0+1 ;SAVE IT TO FR0 1080 PLA ;EACH ARG IS 2 BYT 1090 STA FR0 1100 LDY #1 ;SETUP FOR HI BYTE 1110 PLA ;2ND ARG IS VALUE 1120 STA (FR0),Y ;'POKE' HI BYTE 1130 DEY ;SETUP FOR LO BYTE 1140 PLA ;GET LO BYTE 1150 STA (FR0),Y ;AND POKE IT TOO 1160 RTS ;BACK TO BASIC 1170 ERR 1180 TAX ;GET # ARGS IN X 1190 ER1 1200 PLA ;GET ALL ARGS 1210 PLA 1220 DEX 1230 BNE ER1 ;UNTIL FINISHED 1240 ZERO 1250 LDA #0 ;CLEAR FR0 1260 STA FR0 1270 STA FR0+1 1280 RTS ;RETURN 0 TO BASIC DPEEK.ASM 1000 ;SAVE #D:DPEEK.ASM 1010 FR0 = $D4 ;FLOAT REG #0 1020 PLA ;GET # ARGS 1030 BEQ ZERO ;RET 0 IF NONE 1040 CMP #1 ;ONLY ONE ARG? 1050 BNE ERR ;FIX&RETURN IF NOT 1060 PLA ;GET ARG HI BYTE 1070 STA FR0+3 ;SAVE IN TEMP REG 1080 PLA ;GET ARG LO BYTE 1090 STA FR0+2 1100 LDY #1 ;SETUP TO PEEK HI 1110 LDA (FR0+2),Y ;GET HI BYTE 1120 STA FR0+1 ;SAVE FOR RETURN 1130 DEY ;SETUP TO PEEK LO 1140 LDA (FR0+2),Y ;GET LO BYTE 1150 STA FR0 1160 RTS ;BACK TO BASIC 1170 ERR 1180 TAX ;GET # ARGS IN X 1190 ER1 1200 PLA ;PULL ALL ARGS OFF 1210 PLA ; STACK 1 AT A TIM 1220 DEX 1230 BNE ER1 1240 ZERO 1250 LDA #0 ;RETURN 0 TO BASIC 1260 STA FR0 1270 STA FR0+1 1280 RTS The basic program adds the machine language string to the end of the XFUN.LST file we created in the last lesson. 1 REM SAVE "D:DPOKE.BAS 1000 DIM A$(2) 1010 RESTORE 2000:READ A$:GOSUB 3000 1020 IX=A:OPEN #1,9,0,"D:XFUN.LST" 1025 ? #1;"40 DIM DPOKE$(";IX;"):DPOKE$=";CHR$(34); 1030 FOR I=1 TO IX:READ A$:GOSUB 3000 1040 ? #1;CHR$(A); 1050 NEXT I 1055 ? #1;CHR$(34);":DPOKE=ADR(DPOKE$)" 1110 RESTORE 2100:READ A$:GOSUB 3000 1120 IX=A 1125 ? #1;"50 DIM DPEEK$(";IX;"):DPEEK$=";CHR$(34); 1130 FOR I=1 TO IX:READ A$:GOSUB 3000 1140 ? #1;CHR$(A); 1150 NEXT I 1155 ? #1;CHR$(34);":DPEEK=ADR(DPEEK$)" 1160 CLOSE #1 1900 STOP 2000 REM DPOKE ML DATA 2010 DATA 24 2020 DATA 68,F0,1A,C9,02,D0,10,68 2030 DATA 85,D5,68,85,D4,A0,01,68 2040 DATA 91,D4,88,68,91,D4,60,AA 2050 DATA 68,68,CA,D0,FB,A9,00,85 2060 DATA D4,85,D5,60 2100 REM DPEEK ML DATA 2110 DATA 26 2120 DATA 68,F0,1C,C9,01,D0,12,68 2130 DATA 85,D7,68,85,D6,A0,01,B1 2140 DATA D6,85,D5,88,B1,D6,85,D4 2150 DATA 60,AA,68,68,CA,D0,FB,A9 2160 DATA 00,85,D4,85,D5,60 3000 REM HEX TO DEC CONVERTOR 3010 REM ASSUMES 2 HEX DIGITS IN A$ 3020 HI=ASC(A$(1,1))-48 3030 IF HI<0 OR HI>22 THEN 3100 3040 IF HI>9 AND HI<17 THEN 3100 3050 IF HI>9 THEN HI=HI-7 3060 LO=ASC(A$(2,2))-48 3070 IF LO<0 OR LO>22 THEN 3100 3080 IF LO>9 AND LO<17 THEN 3100 3090 IF LO>9 THEN LO=LO-7 3100 A=LO+HI*16:RETURN 3105 REM FORCE INVALID VALUE ERROR 3110 A=VAL(A$):RETURN 3900 REM FORCE INVALID VALUE ERROR 3910 A=VAL(A$):RETURN The more I think about it, the more I tend to agree with @Rybags above and think all the parameter/error checking should be omitted, which will reduce the size of the routines by just about half. After all, if you use them in your own programs, you're going to use them correctly. If you don't, the system will just locked, requiring a reset key press, and then right back to coding. I'll re-write all 5 routines thus far in the next lesson and show just how many bytes will be saved. Cheers!! 1 Quote Link to comment Share on other sites More sharing options...
XL Freak Posted June 1 Author Share Posted June 1 (edited) Lesson 4 - Removing Argument/Error Checking for Minimal File Size I surprised myself as to just how much assembly code I was able to remove from these five functions. Wow. I believe I have it down about as small as possible, but I'm also confident some of the more experienced users can probably do better. If there are any suggestions/modifications, I'd love to hear them. Since there were no direct and/or absolute references in any of the functions, except for the Floating Point registers FR0 and FR1, I decided to just put all them together into a single .ASM file. And I thought I'd show you the actual assembly listing instead of just the source code itself. Then you can see the hex bytes that make the DATA statements in the Basic programs in my previous lessons, as well as the structure of an assembled file. Since no assembly origin address is given (because it isn't needed) you'll notice that assembly starts at address 0000 (the left-most column) There is great satisfaction when a machine language routine you've written runs correctly the first time without erroring out. If anyone is interested in my development environment, I use the Mac/65 v3.6 w/ DDT Assembler (pick one from the list that works for you) running on an A8PicoCart, on my 800XL. I don't use the stock Atari Basic that comes on the machine. That is Revision B Basic, and it has the dreaded lockup bug. So I use Atari Basic Rev C also running on the A8PicoCart. Keep in mind, personally, I have had the best luck getting .CAR files to work on the A8PicoCart, verses .XEX, .ROM and .BIN files. All my files are saved to a 3.5" SF551 (Atari ST floppy disk drive modified to work with 8-bit Atari computers) running Atari DOS 2.5, with all files transferred to an S-Drive Mini for transferring to a PC to list here in the forums. They come in a .ATR file (simulated floppy disk image), which I load with the excellent Altirra emulator using its built in File Explorer, copied to a folder on my PC, then converted from ATASCII TO ASCII using the Dratex convertor, then loaded into Notepad++ and copied to a code block in these forums. There's quite a bit of work and running back and forth lol. Fun fact: the Mac/65 assembler is the sequel to the Atari Macro Assembler, which itself is the sequel to the original Atari Assembler/Editor. The author of the Assembler/Editor is Kathleen O'Brien, who just happens to be the wife of Paul Laughton, the main author of Atari Basic. If you find the Basic language as intriguing as I do, I highly recommend Compute! Publication's "The Atari Basic Source Book", which not only has the source code listing for Basic Rev A, but also explains how it all works. I don't care for the two character left margin on the Atari 8-bit computers, or the sound of the keyboard key-clicks, or the slowness of the cursor key activation or moving, so when I load Mac/65, I immediately go into DDT and fix it with the following commands: (note: DDT automatically fixes the left margin by itself, and the commands below fix the rest...) DDT E2D9 D1403FF Q and in Basic, I have saved a simple one line series of POKE statements in LIST format so it can be ENTERed with or without a program in memory. It also turns the background black (which isn't possible with the version of Mac/65 I am using). It is... POKE 82,0:POKE 729,20:POKE 730,2:POKE 731,255:POKE 710,0 and the way to get it to a disk file is like this... OPEN #1,8,0,"D:INI":? #1;"POKE 82,0:POKE 729,20:POKE 730,2:POKE 731,255:POKE 710,0":CLOSE #1 Then when you want to use it, type this in... ENTER "D:INI" and instantly you'll be greeted with a black screen with no left margin, and a very speedy cursor. You can add other things to this as well, if you like... XFUN.ASM 1000 ;SAVE #D:XFUN.ASM 1010 ;---------------- 1020 ; =00D4 1030 FR0 = $D4 ;FP REG #0 =00E0 1040 FR1 = $E0 ;FP REG #1 1050 ; 1060 ;XAND 1070 ; 0000 68 1080 PLA ;GET ARG COUNT 0001 68 1090 PLA ;GET ARG1 HI 0002 85D5 1100 STA FR0+1 ;SAVE IT 0004 68 1110 PLA ;GET ARG1 LO 0005 85D4 1120 STA FR0 ;SAVE IT 0007 68 1130 PLA ;GET ARG2 HI 0008 25D5 1140 AND FR0+1 ;AND ARGS TOGETHER 000A 85D5 1150 STA FR0+1 ;AND SAVE IT 000C 68 1160 PLA ;SAME FOR LO 000D 25D4 1170 AND FR0 000F 85D4 1180 STA FR0 0011 60 1190 RTS ;RETURN TO BASIC 1200 ; 1210 ;XOR 1220 ; 0012 68 1230 PLA ;GET ARG COUNT 0013 68 1240 PLA ;GET ARG1 HI 0014 85D5 1250 STA FR0+1 ;SAVE IT 0016 68 1260 PLA ;GET ARG1 LO 0017 85D4 1270 STA FR0 ;SAVE IT 0019 68 1280 PLA ;GET ARG2 HI 001A 05D5 1290 ORA FR0+1 ;OR ARGS TOGETHER 001C 85D5 1300 STA FR0+1 ;AND SAVE IT 001E 68 1310 PLA ;SAME FOR LO 001F 05D4 1320 ORA FR0 0021 85D4 1330 STA FR0 0023 60 1340 RTS ;RETURN TO BASIC 1350 ; 1360 ;XEOR 1370 ; 0024 68 1380 PLA ;GET ARG COUNT 0025 68 1390 PLA ;GET ARG1 HI 0026 85D5 1400 STA FR0+1 ;SAVE IT 0028 68 1410 PLA ;GET ARG1 LO 0029 85D4 1420 STA FR0 ;SAVE IT 002B 68 1430 PLA ;GET ARG2 HI 002C 45D5 1440 EOR FR0+1 ;EOR ARGS TOGETHER 002E 85D5 1450 STA FR0+1 ;AND SAVE IT 0030 68 1460 PLA ;SAME FOR LO 0031 45D4 1470 EOR FR0 0033 85D4 1480 STA FR0 0035 60 1490 RTS ;RETURN TO BASIC 1500 ; 1510 ;DPOKE 1520 ; 0036 68 1530 PLA ;GET ARG COUNT 0037 68 1540 PLA ;GET DPOKE ADR HI 0038 85D5 1550 STA FR0+1 ;SAVE IN ZERO PG 003A 68 1560 PLA ;GET DPOKE ADR LO 003B 85D4 1570 STA FR0 003D A001 1580 LDY #1 ;USE IND,Y ADR'ING 003F 68 1590 PLA ;HOLD IT FOR A SEC 0040 91D4 1600 STA (FR0),Y ;DPOKE VAL TO ADR 0042 88 1610 DEY 0043 68 1620 PLA 0044 91D4 1630 STA (FR0),Y 0046 60 1640 RTS ;RETURN TO BASIC 1650 ; 1660 ;DPEEK 1670 ; 0047 68 1680 PLA ;GET ARG COUNT 0048 68 1690 PLA ;GET DPEEK ADR HI 0049 85E1 1700 STA FR1+1 ;SAVE IN ZERO PG 004B 68 1710 PLA ;GET DPEEK ADR LO 004C 85E0 1720 STA FR1 004E A001 1730 LDY #1 ;SET FOR ZP IND,Y 0050 B1E0 1740 LDA (FR1),Y ;GET DPEEK VAL HI 0052 85D5 1750 STA FR0+1 ;HOLD IT FOR A SEC 0054 88 1760 DEY ;SET FOR LO BYTE 0055 B1E0 1770 LDA (FR1),Y ;GET DPEEK VAL LO 0057 85D4 1780 STA FR0 ;SAVE FOR BASIC 0059 60 1790 RTS ;RETURN TO BASIC ASSEMBLY ERRORS: 0 27007 BYTES FREE SYMBOLS =00D4 FR0 =00E0 FR1 I hope you have enjoyed this as much as I have. It is a treat for me to be able to share a little bit of the knowledge I've gained throughout the years. Thank you! Is anyone interested in some advanced Basic programming techniques? Or how Atari Basic places its tables in memory, and how it uses them? Suggestions are always welcome. Edited June 1 by XL Freak 2 Quote Link to comment Share on other sites More sharing options...
dmsc Posted June 2 Share Posted June 2 Hi! The DPEEK and DPOKE code can be shortened a little by using a loop: 1500 ; 1510 ;DPOKE 1520 ; 1530 PLA ;GET ARG COUNT 1540 PLA ;GET DPOKE ADR HI 1550 STA FR0+1 ;SAVE IN ZERO PG 1560 PLA ;GET DPOKE ADR LO 1570 STA FR0 1580 LDY #1 ;USE IND,Y ADR'ING 1590 POK PLA ;HOLD IT FOR A SEC 1600 STA (FR0),Y ;DPOKE VAL TO ADR 1610 DEY 1620 BPL POK ;DO THE LOW BYTE 1640 RTS ;RETURN TO BASIC 1650 ; 1660 ;DPEEK 1670 ; 1680 PLA ;GET ARG COUNT 1690 PLA ;GET DPEEK ADR HI 1700 STA FR1+1 ;SAVE IN ZERO PG 1710 PLA ;GET DPEEK ADR LO 1720 STA FR1 1730 LDY #1 ;SET FOR ZP IND,Y 1740 PEK LDA (FR1),Y ;GET DPEEK VAL HI 1750 STA FR0,Y ;SAVE FOR BASIC 1760 DEY ;SET FOR LO BYTE 1770 BPL PEK ;DO LO BYTE 1790 RTS ;RETURN TO BASIC You can make your AND, OR and XOR routines allow any number of arguments with 4 bytes more for each with the following code: 1000 ;SAVE #D:XFUN.ASM 1010 ;---------------- 1020 ; =00D4 1030 FR0 = $D4 ;FP REG #0 =00E0 1040 FR1 = $E0 ;FP REG #1 1050 ; 1060 ;XAND 1070 ; 0000 68 1080 PLA ;GET ARG COUNT 0001 AA 1090 TAX 0002 A9FF 1100 LDA #255 ;INIT RESULT 0004 85D4 1110 STA FR0 ;INTO FR0 0006 85D5 1120 STA FR0+1 ;AND FR0+1 0008 68 1130 LAND PLA ;GET ARG2 HI 0009 25D5 1140 AND FR0+1 ;AND ARGS TOGETHER 000B 85D5 1150 STA FR0+1 ;AND SAVE IT 000D 68 1160 PLA ;SAME FOR LO 000E 25D4 1170 AND FR0 0010 85D4 1180 STA FR0 0012 CA 1190 DEX ;CHECK IF MORE ARGS 0013 D0F3 1200 BNE LAND ;AND REPEAT 0015 60 1210 RTS ;RETURN TO BASIC 1220 ; 1230 ;XOR 1240 ; 0016 68 1250 PLA ;GET ARG COUNT 0017 AA 1260 TAX 0018 A900 1270 LDA #0 ;INIT RESULT 001A 85D4 1280 STA FR0 ;INTO FR0 001C 85D5 1290 STA FR0+1 ;AND FR0+1 001E 68 1300 LOR PLA ;GET ARG2 HI 001F 05D5 1310 ORA FR0+1 ;OR ARGS TOGETHER 0021 85D5 1320 STA FR0+1 ;AND SAVE IT 0023 68 1330 PLA ;SAME FOR LO 0024 05D4 1340 ORA FR0 0026 85D4 1350 STA FR0 0028 CA 1360 DEX ;CHECK IF MORE ARGS 0029 D0F3 1370 BNE LOR ;AND REPEAT 002B 60 1380 RTS ;RETURN TO BASIC 1390 ; 1400 ;XEOR 1410 ; 002C 68 1420 PLA ;GET ARG COUNT 002D AA 1430 TAX 002E A900 1440 LDA #0 ;INIT RESULT 0030 85D4 1450 STA FR0 ;INTO FR0 0032 85D5 1460 STA FR0+1 ;AND FR0+1 0034 68 1470 LEOR PLA ;GET ARG2 HI 0035 45D5 1480 EOR FR0+1 ;EOR ARGS TOGETHER 0037 85D5 1490 STA FR0+1 ;AND SAVE IT 0039 68 1500 PLA ;SAME FOR LO 003A 45D4 1510 EOR FR0 003C 85D4 1520 STA FR0 003E CA 1530 DEX ;CHECK IF MORE ARGS 003F D0F3 1540 BNE LEOR ;AND REPEAT 0041 60 1550 RTS ;RETURN TO BASIC 1560 ; 1570 ;DPOKE 1580 ; 0042 68 1590 PLA ;GET ARG COUNT 0043 68 1600 PLA ;GET DPOKE ADR HI 0044 85D5 1610 STA FR0+1 ;SAVE IN ZERO PG 0046 68 1620 PLA ;GET DPOKE ADR LO 0047 85D4 1630 STA FR0 0049 A001 1640 LDY #1 ;USE IND,Y ADR'ING 004B 68 1650 POK PLA ;HOLD IT FOR A SEC 004C 91D4 1660 STA (FR0),Y ;DPOKE VAL TO ADR 004E 88 1670 DEY 004F 10FA 1680 BPL POK ;DO THE LOW BYTE 0051 60 1690 RTS ;RETURN TO BASIC 1700 ; 1710 ;DPEEK 1720 ; 0052 68 1730 PLA ;GET ARG COUNT 0053 68 1740 PLA ;GET DPEEK ADR HI 0054 85E1 1750 STA FR1+1 ;SAVE IN ZERO PG 0056 68 1760 PLA ;GET DPEEK ADR LO 0057 85E0 1770 STA FR1 0059 A001 1780 LDY #1 ;SET FOR ZP IND,Y 005B B1E0 1790 PEK LDA (FR1),Y ;GET DPEEK VAL HI 005D 99D400 1800 STA FR0,Y ;SAVE FOR BASIC 0060 88 1810 DEY ;SET FOR LO BYTE 0061 10F8 1820 BPL PEK ;DO LO BYTE 0063 60 1830 RTS ;RETURN TO BASIC This allows to call "? USR(XAND, 1234, 5678, 9012)" for example. If you used an absolute address for the code (instead of storing in string), you could use JSR and make the code shorter by reusing code between routines: 1000 ;SAVE #D:XFUN.ASM 1010 ;---------------- 1020 ; =00D4 1030 FR0 = $D4 ;FP REG #0 =00E0 1040 FR1 = $E0 ;FP REG #1 0000 1050 *= $0600 1060 ; 1070 ;XAND 1080 ; 0600 A2FF 1090 LDX #255 0602 A935 1100 LDA #$35 0604 4C1506 1110 JMP MAIN 1120 ; 1130 ;XOR 1140 ; 0607 A200 1150 LDX #0 0609 A915 1160 LDA #$15 060B 4C1506 1170 JMP MAIN 1180 ; 1190 ;XEOR 1200 ; 060E A200 1210 LDX #0 0610 A955 1220 LDA #$55 0612 4C1506 1230 JMP MAIN 1240 ; 1250 ; MAIN SHARED CODE 0615 1260 MAIN 0615 8D2106 1270 STA OP1 ;STORE OPERATION 0618 86D4 1280 STX FR0 ;INITIALIZE FR0 061A 86D5 1290 STX FR0+1 061C 68 1300 PLA ;GET ARG COUNT 061D A8 1310 TAY 061E A201 1320 NXT LDX #1 ;HI/LO 0620 68 1330 LOP PLA ;GET ARG 0621 35D4 1340 OP1 AND FR0,X ;'OP' ARGS TOGETHER 0623 95D4 1350 STA FR0,X ;AND SAVE IT 0625 CA 1360 DEX 0626 10F8 1370 BPL LOP ;NEXT BYTE 0628 88 1380 DEY ;CHECK IF MORE ARGS 0629 D0F3 1390 BNE NXT ;AND REPEAT 062B 60 1400 RTS ;RETURN TO BASIC Note that the last JMP could be eliminated, saving 3 more bytes. Have Fun! 1 Quote Link to comment Share on other sites More sharing options...
XL Freak Posted June 3 Author Share Posted June 3 On 6/1/2024 at 11:24 PM, dmsc said: Hi! The DPEEK and DPOKE code can be shortened a little by using a loop: Thank you for joining in!!! I am honored! I am in the middle of a response to your suggestions For anyone that doesn't know, @dmsc is the mastermind that created FastBASIC. Look it up, it's awesome!!! In the meantime, I just now realized, over 40 years after my first Atari computer, how to easily perform a directory listing in plain old Atari Basic. Here's how. Type in this line and hit the RETURN key: CL.#1:O.#1,8,0,"D:DIR":?#1;"CL.#5:O.#5,6,0,";CHR$(34);"D:*.*";CHR$(34);":F.I=0TO10^10:GET#5,A:?CHR$(A);:N.I":CL.#1 It will create a very small, one sector file on your disk drive called DIR, without any file extension. To use it, simply type: E."D:DIR" and a directory listing will be displayed. Just like the D:INI file, this can be used with or without a program present, and can even be used inside a running Basic program. It is a good idea to issue a CLOSE #5 after using it because, as far as I can see, there is no way to close the file after the directory is finished, as the loop is exited when there is nothing else to read. If you use it inside of a Basic program, the line before it should TRAP to the line after it, which should start with a CLOSE #5, so that the file channel is properly closed. Here is what it looks like. Note that I hit the BREAK key to stop the listing because it is so long it scrolls the code off the screen (Sorry, the code above shows file channel #5, but the picture shows #1. I thought it a good idea to change it in case it is used inside a program that might be using #1 already. You can use any file channel you wish, though.) Also, you will note that the listing is a little slow, retrieving and printing the listing a single character at a time. I could speed it up by using a string variable, but that'd be another variable that may be added to whatever Basic program is in memory at the time, which may have undesirable effects, if it is being used for something else at the time. Is there anything else you'd like to be able to do in Atari Basic? Post it here and someone will hopefully jump in and show you how. 1 Quote Link to comment Share on other sites More sharing options...
XL Freak Posted June 6 Author Share Posted June 6 On 6/1/2024 at 11:24 PM, dmsc said: You can make your AND, OR and XOR routines allow any number of arguments It seems a bit unorthodox, but sure, why not. Now... multiple args in POKE and DPOKE? Absolutely!!! On 6/1/2024 at 11:24 PM, dmsc said: If you used an absolute address for the code (instead of storing in string), you could use JSR and make the code shorter by reusing code between routines You're testing me, right? 😁 That's a great idea, but was easily achieved using branches based on known values. ie. LDA #$35, BNE MAIN, foregoing the JMP and therefore the need for absolute addressing, and it is a byte shorter. The absolute address of OP1 was handled as an offset using FR0/FR0+1 as the zero page base address pointer, since they're already set up with the machine language routine address as part of Basic's USR function call. The tricky part was calculating the offset for XOR and XEOR since their entry addresses are different, but was an easy fix after all. So it's still in a string with no absolute references, and works like a champ. The new XFUNction set includes XAND, XOR, XEOR, DPOKE (see below), DPEEK (see below) and XPOKE (see below). If anyone would like the Basic listing to create the include file, just ask; I'll be happy to whip it up. Here are some notes on the extra functions: DPOKE now takes one or more arguments that are stored in consecutive 16 bit memory locations. So... A=USR(DPOKE,1536,100,200,300,400,500):REM I HAVE BEEN WANTING THIS FOR 40 YEARS LOL is the same as... A=USR(DPOKE,1536,100):A=USR(DPOKE,1538,200):A=USR(DPOKE,1540,300) which is the same as... POKE 1536,100:POKE 1537,0:POKE 1538,200:POKE 1539,0:POKE 1540,44:POKE 1541,1 DPEEK, like DPOKE, works with 2 byte, or 16 bit, values and now remembers its last PEEK address and saves it so it can be called without it next time and it will return the next 16 bit integer. So... A=USR(DPEEK,1536):B=USR(DPEEK,1538):C=USR(DPEEK,1540) can also be used like this now... A=USR(DPEEK,1536):B=USR(DPEEK):C=USR(DPEEK) and will return the same values. Just be sure your code remembers where it is. XPOKE is the 8 byte version of the 16 byte DPOKE function, allowing more than one POKE value per statement call. Like this... A=USR(XPOKE,1536,100,0,200,0,44,1):REM NOW THIS IS USEFUL!!! is the same as... POKE 1536,100:POKE 1537,0:POKE 1538,200:POKE 1539,0:POKE 1540,44:POKE 1541,1:REM THIS KINDA SUCKS Unfortunately, there is no address 'remembering' between DPOKE and XPOKE function calls, like there is with the new DPEEK function, because I couldn't work out an easy way to distinguish whether the first passed argument, which is usually the address, would be a POKE value instead. It wouldn't be that useful anyway... Without further a-due, here is the assembly listing. It is much shorter than I thought it'd be, thanks to help from @dmsc and others. NEWXFUN.ASM 1000 ;SAVE #D:NEWXFUN.ASM 1010 ; =00D0 1020 DPA = $D0 ;DPEEK ADDRESS =00D4 1030 FR0 = $D4 ;FLOAT PT REG 0 =00E0 1040 FR1 = $E0 ;FLOAT PT REG 1 1050 ; 0000 1060 *= $00 1070 ; 1080 ;XAND 1090 ; =0000 1100 XAND = * ;REFERENCE TO HERE 0000 A2FF 1110 LDX #$FF ;255-ALL BITS SET 0002 A021 1120 LDY #OP1-XAND ;OPCODE OFFSET 0004 A935 1130 LDA #$35 ;OPCODE FOR AND 0006 D00E 1140 BNE MAIN 1150 ; 1160 ;XOR 1170 ; =0008 1180 XOR = * 0008 A200 1190 LDX #0 ;NO BITS SET 000A A019 1200 LDY #OP1-XOR ;OPCODE OFFSET 000C A915 1210 LDA #$15 ;OPCODE FOR OR 000E D006 1220 BNE MAIN 1230 ; 1240 ;XEOR 1250 ; =0010 1260 XEOR = * 0010 A200 1270 LDX #0 ;NO BITS SET 0012 A011 1280 LDY #OP1-XEOR ;OPCODE OFFSET 0014 A955 1290 LDA #$55 ;OPCODE FOR EOR 1300 ; 1310 ;MAIN 1320 ; 1330 ;ON ENTRY, FR0/FR0+1 HOLD ADDR OF 1340 ; THIS ML ROUTINE 1350 ; 0016 1360 MAIN 0016 91D4 1370 STA (FR0),Y ;SAV OPCODE TO OP1 0018 86D4 1380 STX FR0 ;INIT RETURN VAL 001A 86D5 1390 STX FR0+1 001C 68 1400 PLA ;GET ARG COUNT 001D A8 1410 TAY ;SAVE TO Y 001E 1420 NEXT 001E A201 1430 LDX #1 ;START WITH HIBYTE 0020 1440 LOOP 0020 68 1450 PLA ;GET NEXT ARG BYTE 0021 1460 OP1 0021 35D4 1470 AND FR0,X ;DO AND/OR/XOR 0023 95D4 1480 STA FR0,X ;SAVE RESULT 0025 CA 1490 DEX ;POINT TO NXT ARG 0026 10F8 1500 BPL LOOP ;GO GET IT 1510 ; 0028 88 1520 DEY ;CK IF MORE ARGS 0029 D0F3 1530 BNE NEXT ;IF SO, GET IT 002B 60 1540 RTS ;RETURN TO BASIC 1550 ; 1560 ;DPOKE 1570 ; 1580 ;ALLOW DPOKE TO POKE MULTIPLE VALS 1590 ; ONE AFTER THE OTHER INTO 1600 ; CONSECUTIVE MEMORY LOCATIONS 1610 ; 002C 1620 DPOKE 002C 68 1630 PLA ;GET ARG COUNT 002D AA 1640 TAX ;LOOP USING X REG 002E 68 1650 PLA ;GET DPOKE ADR HI 002F 85D5 1660 STA FR0+1 ;SAVE IN ZERO PG 0031 68 1670 PLA ;GET DPOKE ADR LO 0032 85D4 1680 STA FR0 0034 1690 DPOK 0034 A001 1700 LDY #1 ;SETUP Y FOR HIBYT 0036 1710 DPO1 0036 68 1720 PLA ;GET NEXT ARG 0037 91D4 1730 STA (FR0),Y ;SAVE ARG BYTE 0039 88 1740 DEY 003A 10FA 1750 BPL DPO1 ;GO GET NEXT ONE 003C A002 1760 LDY #2 ;INC DPOKE POINTER 003E 1770 DPO2 003E E6D4 1780 INC FR0 ;INC DPOKE ADDR 0040 D002 1790 BNE DPO3 ; FOR NEXT DPOKE 0042 E6D5 1800 INC FR0+1 ; HI BYT TOO IF... 0044 1810 DPO3 0044 88 1820 DEY ;SINCE DPOKE, MUST 0045 D0F7 1830 BNE DPO2 ; INC BY 2 BYTES 0047 CA 1840 DEX ;MORE ARGS? 0048 D0EA 1850 BNE DPOK ;GET NEXT IF SO 004A 60 1860 RTS ;BACK TO BASIC 1870 ; 1880 ;DPEEK 1890 ; 1900 ;DPEEK SETS PEEK ADR AND MORE 1910 ; DPEEKS AFTERWARDS WILL USE THAT 1920 ; ADDRESS AFTER ITS BEEN UPDATED 1930 ; *IF* THEY OMIT ADDRESS ARG 1940 ; 004B 1950 DPEEK 004B 68 1960 PLA ;GET ARG COUNT 004C F006 1970 BEQ DPE1 ;SKIP IF NO ADDR 004E 68 1980 PLA 004F 85D1 1990 STA DPA+1 ;SAVE ADR IN SPECL 0051 68 2000 PLA ; DPEEK ADDRESS 0052 85D0 2010 STA DPA 0054 2020 DPE1 0054 A001 2030 LDY #1 0056 2040 DPE2 0056 B1D0 2050 LDA (DPA),Y ;GET DPEEK VAL BYT 0058 99D400 2060 STA FR0,Y ;SAVE FOR BASIC 005B 88 2070 DEY ;UPDATE INDEX 005C 10F8 2080 BPL DPE2 ;GET OTHER BYTE 005E E6D0 2090 INC DPA ;INC DPEEK ADR 0060 D002 2100 BNE DPE3 ;INC HI IF ROLLOVR 0062 E6D1 2110 INC DPA+1 0064 2120 DPE3 0064 E6D0 2130 INC DPA ;INC ADDR AGAIN 0066 D002 2140 BNE DPE4 ; SINCE DPEEK 0068 E6D1 2150 INC DPA+1 006A 2160 DPE4 006A 60 2170 RTS 2180 ; 2190 ;XPOKE 2200 ; 2210 ;POKE MULTIPLE VALUES WITH A 2220 ; SINGLE FUNCTION CALL 2230 ; 006B 2240 XPOKE 006B 68 2250 PLA ;GET ARG COUNT 006C F011 2260 BEQ XPO2 ;DONE IF NO ARGS 006E AA 2270 TAX ;PUT IN COUNTDOWN 006F 68 2280 PLA ;POKE ADR HI BYTE 0070 85D5 2290 STA FR0+1 ;SAVE TO ZPAGE 0072 68 2300 PLA ;SAME FOR LO BYTE 0073 85D4 2310 STA FR0 0075 A000 2320 LDY #0 ;SET (INDIRECT),Y 0077 2330 XPO1 0077 68 2340 PLA ;TRASH POKE VAL HI 0078 68 2350 PLA ;GET POKE VAL LO 0079 91D4 2360 STA (FR0),Y ;POKE THE VALUE 007B C8 2370 INY ;POINT TO NEXT ADR 007C CA 2380 DEX ;MORE VALUES? 007D D0F8 2390 BNE XPO1 ;IF SO GO GET EM 007F 2400 XPO2 007F 60 2410 RTS ;BYE BYE 0080 2420 XEND ASSEMBLY ERRORS: 0 25914 BYTES FREE PAGE 4 SYMBOLS =00D0 DPA 0054 DPE1 0056 DPE2 0064 DPE3 006A DPE4 004B DPEEK 0036 DPO1 003E DPO2 0044 DPO3 0034 DPOK 002C DPOKE =00D4 FR0 =00E0 FR1 0020 LOOP 0016 MAIN 001E NEXT 0021 OP1 =0000 XAND 0080 XEND =0010 XEOR =0008 XOR 0077 XPO1 007F XPO2 006B XPOKE I had coded these routines to NOT have any error or argument checking, as you would be using them in your own code, and I must apologize, I see that I check the first PLA in the XPOKE function to see if there are any arguments and exit if there aren't any. I believe I was in the middle of trying to figure out how to make it work a different way and this slipped in. Thankfully, this oversight only costs 2 extra bytes of machine language code (ie. Basic string space) so it isn't worth fixing and reassembling. I personally don't code on an emulator, but on a real 800XL with a real floppy drive so I must transfer all listings from floppy to sdcard using an SDrive Mini and transport that to my PC where I use the Altirra emulator's Disk Explorer tool/function to extract the listing files from the .ATR emulated disk, then use Dratex.exe to convert the text listings from ATASCII to ASCII to be properly shown here. That's too much work to fix a small mistake Enjoy y'all!!! Quote Link to comment Share on other sites More sharing options...
dmsc Posted June 9 Share Posted June 9 Hi! On 6/5/2024 at 9:55 PM, XL Freak said: It seems a bit unorthodox, but sure, why not. Now... multiple args in POKE and DPOKE? Absolutely!!! You're testing me, right? 😁 That's a great idea, but was easily achieved using branches based on known values. ie. LDA #$35, BNE MAIN, foregoing the JMP and therefore the need for absolute addressing, and it is a byte shorter. The absolute address of OP1 was handled as an offset using FR0/FR0+1 as the zero page base address pointer, since they're already set up with the machine language routine address as part of Basic's USR function call. The tricky part was calculating the offset for XOR and XEOR since their entry addresses are different, but was an easy fix after all. So it's still in a string with no absolute references, and works like a champ. You are right, of course! I started with a JSR to common code, that needed absolute addressing, but after changing to a JMP, you can always do a relative branch and make the code relocatable. Also, using FR0 as the address of the called code is a very good idea. On 6/5/2024 at 9:55 PM, XL Freak said: The new XFUNction set includes XAND, XOR, XEOR, DPOKE (see below), DPEEK (see below) and XPOKE (see below). If anyone would like the Basic listing to create the include file, just ask; I'll be happy to whip it up. Here are some notes on the extra functions: DPOKE now takes one or more arguments that are stored in consecutive 16 bit memory locations. So... A=USR(DPOKE,1536,100,200,300,400,500):REM I HAVE BEEN WANTING THIS FOR 40 YEARS LOL is the same as... A=USR(DPOKE,1536,100):A=USR(DPOKE,1538,200):A=USR(DPOKE,1540,300) which is the same as... POKE 1536,100:POKE 1537,0:POKE 1538,200:POKE 1539,0:POKE 1540,44:POKE 1541,1 Great, having multiple arguments for DPOKE is IMHO very useful, as makes the code for writing multiple registers shorter. On 6/5/2024 at 9:55 PM, XL Freak said: I had coded these routines to NOT have any error or argument checking, as you would be using them in your own code, and I must apologize, I see that I check the first PLA in the XPOKE function to see if there are any arguments and exit if there aren't any. I believe I was in the middle of trying to figure out how to make it work a different way and this slipped in. Thankfully, this oversight only costs 2 extra bytes of machine language code (ie. Basic string space) so it isn't worth fixing and reassembling. I personally don't code on an emulator, but on a real 800XL with a real floppy drive so I must transfer all listings from floppy to sdcard using an SDrive Mini and transport that to my PC where I use the Altirra emulator's Disk Explorer tool/function to extract the listing files from the .ATR emulated disk, then use Dratex.exe to convert the text listings from ATASCII to ASCII to be properly shown here. That's too much work to fix a small mistake Wow, seems cumbersome. When I program in the Atari, I mount a disk image from the computer using sio2pc, so I can access the files directly without transporting the media between both, this makes testing much faster. Have Fun! 1 Quote Link to comment Share on other sites More sharing options...
XL Freak Posted June 10 Author Share Posted June 10 20 hours ago, dmsc said: Wow, seems cumbersome. When I program in the Atari, I mount a disk image from the computer using sio2pc, so I can access the files directly without transporting the media between both, this makes testing much faster I've considered sii2pc but really wanted to shy away from that kind of stuff,but I must admit the sdrive mini is MUCH faster than even my hi-speed sf551, and I've pretty much been using it exclusively. my xl and pc are probably 20 ft apart, so I'm not sure if that'd work for me or not, but I think I'll look into it... thx for suggesting it 😃 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.