Jump to content
IGNORED

Remembering the magazines... Coding bitwise AND for Basic


Recommended Posts

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!

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

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

  • Thanks 1
Link to comment
Share on other sites

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.

  • Like 1
Link to comment
Share on other sites

Posted (edited)
20 hours ago, mono said:

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 by XL Freak
Link to comment
Share on other sites

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 

 

Link to comment
Share on other sites

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!!

  • Like 1
Link to comment
Share on other sites

Posted (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 by XL Freak
  • Like 2
Link to comment
Share on other sites

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!

 

  • Like 1
Link to comment
Share on other sites

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.

0.thumb.jpg.661d1e1735dc17c1f8831a973971980a.jpg

 

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.

  • Like 1
Link to comment
Share on other sites

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 :D

 

Enjoy y'all!!!

Link to comment
Share on other sites

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 :D

 

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!

  • Thanks 1
Link to comment
Share on other sites

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 😃

Link to comment
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...