-
Posts
4,470 -
Joined
-
Last visited
Content Type
Profiles
Forums
Blogs
Gallery
Events
Store
Posts posted by TheBF
-
-
@Retrospect is kicking the tires on the latest and greatest RXB 2024. Lots of speed ups in the Rich's new system.
The bouncing ball was re-written using CALL IO which gives you background sound lists.
I could not let that go unanswered so here is how you would do it Camel99 Forth.
The library file ISRSOUND gives you tools to make sound lists in VDP RAM and ISRPLAY will play them.
Spoiler\ ################### \ # # \ # BOUNCING BALL # \ # # \ # BY RETROSPECT # \ # # \ ################### \ Translated to Camel99 Forth by @theBF \ V2 Adds ISR driven sound player INCLUDE DSK1.GRAFIX \ Graphics 1 mode with TI BASIC look-alike commands INCLUDE DSK1.INPUT \ gives us an input statement INCLUDE DSK1.ISRSOUND \ make a sound list in VDP RAM HEX VCREATE EXPLODE VBYTE 7,9F,BF,DF,E7,F0,C0,07,5 VBYTE 1,F1,6 VBYTE 1,F2,7 VBYTE 1,F3,8 VBYTE 1,F4,9 VBYTE 1,F5,10 VBYTE 1,F6,11 VBYTE 1,F7,12 VBYTE 1,F8,13 VBYTE 1,F9,14 VBYTE 1,FA,15 VBYTE 1,FB,16 VBYTE 1,FC,17 VBYTE 1,FD,18 VBYTE 1,FE,30 VBYTE 1,FF,0 /VEND DECIMAL VARIABLE X \ x position VARIABLE Y \ y position VARIABLE DX \ x vector VARIABLE DY \ y vector VARIABLE T \ delay between movements : DEFINE_THE_BALL S" 3C7EFFFFFFFF7E3C" 42 CALLCHAR 42 SET# 5 1 COLOR ; : VARS_TO_START 1 DUP X ! Y ! 1 DUP DX ! DY ! ; : ASK_DELAY CLEAR 0 0 AT-XY ." DELAY VAL?" 0 1 AT-XY T #INPUT ; : ADD_DIRECTION ( dx dy --) Y @ + Y ! X @ + X ! ; : MOVE_BALL X @ Y @ AT-XY SPACE \ erase current position DX @ DY @ ADD_DIRECTION \ compute next location X @ Y @ AT-XY 42 EMIT ; : REVERSE ( var -- ) DUP @ NEGATE SWAP ! ; : BETWEEN ( n lo hi -- ?) 1+ WITHIN ; : HORZLIMIT? ( n -- ?) 1 30 BETWEEN 0= ; : VERTLIMIT? ( n -- ?) 1 21 BETWEEN 0= ; : ?BOUNCE ( x y -- ) VERTLIMIT? IF EXPLODE ISRPLAY DY REVERSE THEN HORZLIMIT? IF EXPLODE ISRPLAY DX REVERSE THEN ; : RUN DEFINE_THE_BALL VARS_TO_START ASK_DELAY CLEAR BEGIN MOVE_BALL X @ Y @ ?BOUNCE T @ TICKS ?TERMINAL UNTIL ;
- 3
-
I was reading some discussions on GROM and SAMS in another thread and realized that I had not extended my DUMP utility to dump GROM.
Here is some code to read and write GROM using only Forth. It's transportable to other systems I think.
>< is SWPB in FbForth.
SPLIT is code word that splits and integer into 2 bytes. Easy to make one in FbForth but it is a name conflict with SPLIT screen mode.
HEX : SPLIT ( AABB -- BB AA ) DUP FF AND SWAP SWPB FF AND ;
I don't have a GRAM device so I have not tested the write words but I think they are correct.
HEX \ GROM access port addresses 9800 CONSTANT Gread \ read data (GROM base) 9C00 CONSTANT Gwrite \ write data 9C02 CONSTANT Gaddr \ write address : GROM ( addr -- ) DUP Gaddr ! Gaddr C! ; \ set GROM address \ read GROM : GC@+ ( -- c) Gread C@ ; \ read char, auto-incr. address : GC@ ( addr -- c) GROM GC@+ ; \ fetch grom character : G@ ( addr -- n) GROM GC@+ >< GC@+ OR ; \ fetch grom integer \ write GROM : GC!+ ( c --) Gwrite C! ; : GC! ( c addr --) GROM GC!+ ; : G! ( n addr --) GROM SPLIT GC!+ GC!+ ;
In Camel99 Forth the DUMP utility uses deferred words MEM@ and MEMC@ to read memory.
This means we can change them to access any type of memory in the system. (RAM, VDP, SAMS and now GROM)
: GROMDUMP ( addr len -- ) ['] G@ IS MEM@ ['] GC@ IS MEMC@ (DUMP) ;
According to nouspikel the BASIC keywords are at address GROM >285C.
Let's see if it works.
- 1
-
1 hour ago, sometimes99er said:
Never thought that would be a problem for Forth ... 🤨
But ain't that kind of your solution anyway !? 🥸To be honest I didn't think it would be hard either, until I tried to make it.
In Forth circles it is considered bad form to reach down below the 3rd item on the data stack.
If you want to use a stack machine it becomes a game to organize things so data arrives at the CPU in the correct order or close to it.
It's the classic issue of registers vs stacks.
If you need to get at 5 arguments in random order then registers (or locals) are simpler.
But stacks have the advantage of not requiring loading into registers and saving registers later, so sub-routine and interrupt handling is faster.
No free lunches in this world.
In this case I resorted to one variable for the character and then I used the word PICK to reach into the stack to compute the height and length of the box.
This could be argued to be bad form, but on the 9900 the number of instructions to do OVER ( copy the 2nd item to the top of the stack) is the same as
getting any item in the stack onto the top.
At the end of the day modern Standard Forth has local variables like other languages which you can use when you have complex math.
But locals take up memory to implement so on small machines I survive without them.
It just requires a bit more thought sometimes.
- 3
-
I have been going through my demo programs to make sure they still work with the latest version of Camel99 Forth.
I found this BASIC program that I think was written by @sometimes99er but I am not sure about the source.
1 ! TILTED BOXES ILLUSION 100 call clear 102 call screen(6) 103 call char(33,"30C0030C30C0030C0C03C0300C03C03") 110 call box(4,9,29,11,33) 111 call box(10,13,23,18,34) 112 goto 112 1120 sub box(x1,y1,x2,y2,c) 1130 for y=y1 to y2 1140 call hchar(y,x1,c,x2-x1+1) 1150 next y 1160 subend
I had originally avoided doing the general purpose BOX function because it's harder to manage 5 arguments on the stack.
The BASIC program using a SUB program makes it very simple.
I decided to try to make the BOX function but I had to resort to one variable.
This would be very simple if we use local variables or 4 more global variables, but here is my solution.
I resorted to simplifying the x,y coordinates to a VDP address, a height and length.
This allowed the use of VFILL which is like HCHAR but uses a VDP address rather than x,y coordinates.
This was harder than I thought it would be.
NEEDS DUMP FROM DSK1.TOOLS NEEDS GRAPHICS FROM DSK1.GRAFIX DECIMAL S" 30C0030C30C0030C0C03C0300C03C030" 126 CALLCHAR \ convert x,y coordinates to VDP address, height and length : HEIGHT ( x1 y1 x2 y2 -- x1 y1 x2 y2 n) DUP 3 PICK - 1+ ; : LENGTH ( x1 y1 x2 y2 -- x1 y1 x2 y2 n) OVER 4 PICK - ; : VADDR ( x1 y1 x2 y2 -- Vaddr) 2DROP >VPOS ; \ resorted to 1 temp variable :-( VARIABLE CHR : BOX ( x1 y1 x2 y2 char -- ) CHR ! HEIGHT >R LENGTH >R VADDR R> R> ( -- Vaddr len hgt) 0 DO 2DUP CHR @ VFILL SWAP C/L @ + SWAP LOOP 2DROP ; : RUN PAGE ." * Tilted Boxes Illusion *" 6 SCREEN BEGIN \ x y x y char 6 3 16 7 127 BOX 4 10 20 15 126 BOX 2000 MS 6 3 16 7 31 BOX 4 10 20 15 31 BOX 2000 MS ?TERMINAL UNTIL ;
- 2
-
20 minutes ago, Lee Stewart said:
I wouldn’t call that a “device name conflict”. The DSR of RS232 cards handles PIO/1 ,RS232/1, RS232/2 in the first card reached and PIO/2, RS232/3, RS232/4 in the second card reached or not at all—the DSR is the same in both. True device name conflicts involve two or more cards having the same device names expecting to services all matching devices without regard to any other DSR. The card with the lowest CRU will service the device. Cards with higher CRUs will never get the opportunity.
...lee
So when you strap the 2nd RS232 card as a 2nd card, it changes the names in ROM somehow or maps in ROM space with the extra names?
-
43 minutes ago, Lee Stewart said:
Yes. The console and E/A sources call it 'version' and, as far as I can tell, it is only used by RS232 cards. For PIO/2, RS232/3, RS232/4 devices, the device name is found and rejected because the version/instance is 1. When it comes back to DSRLNK, the version/instance is incremented and another card is sought. The second RS232 card accepts the device because the version/instance is now 2.
...lee
OK. So that is how they handle device name conflicts with two RS232 cards installed?
I had never thought about that problem. Kind of nasty.
-
Over my pay grade. Is 'call instance' a DSR parameter?
-
3 hours ago, Vorticon said:
Well, 41 years later and the situation does not seem to have changed much! 😁 Back then, as now, the hardware requirements to run UCSD Pascal were steep considering that most folks had bought their TI's heavily discounted towards the end of its production, while expansion hardware like the PEB, floppy drives, 32K RAM, Disk controller and RS232, not to mention a printer, remained very expensive to acquire. Your particular situation definitely was uncommon to say the least...
Speaking of RS232, were you able to make any progress on that?
-
How do we enable/disable case insensitivity?
I see the CASENS flag in the code but it's not a variable name.
-
Over in the Assembly Language thread @Retrospect asked for a translation of his bouncing ball program into Assembler.
Well... we could not leave it up to the ALC coders to have all the fun around here now could we?
Here is a Forth version with all the BASIC lines removed. It is not completely "idiomatic" Forth. There are more variables than would be typically used but I tried to demonstrate the typical way we use Forth by factoring things into small pieces.
The delay here uses the word TICKS which delays based on "ticks" of the 9901 timer. My MS word limits the smallest delay to 32mS which is too big to see speed.
I was a bit cheeky and used @Retrospect 's comment text to name the Forth words, to show how well named code requires less comments.
Spoiler\ ################### \ # # \ # BOUNCING BALL # \ # # \ # BY RETROSPECT # \ # # \ ################### \ Translated to Camel99 Forth by @theBF \ We need to compile some TI-99 words into the standard Forth \ INCLUDE DSK1.TOOLS \ for debugging INCLUDE DSK1.GRAFIX \ Graphics 1 mode with TI BASIC look-alike commands INCLUDE DSK1.SOUND \ sound chip contolr INCLUDE DSK1.INPUT \ gives us an input statement \ Variables are declared instead of commented. Changed names for clarity VARIABLE X \ x position VARIABLE Y \ y position VARIABLE DX \ x vector VARIABLE DY \ y vector VARIABLE T \ delay between movements VARIABLE SOUND \ sound flag VARIABLE VOL \ volume \ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ \ Forth does not translate from BASIC in straight line like Assembler. \ In Forth it's better to create the parts we need as named sub-routines \ then put them all together at the end as a final program : DEFINE_THE_BALL S" 3C7EFFFFFFFF7E3C" 42 CALLCHAR 42 SET# 5 1 COLOR ; : VARS_TO_START 1 DUP X ! Y ! 1 DUP DX ! DY ! SOUND ON ; : ASK_DELAY CLEAR 0 0 AT-XY ." DELAY VAL?" 0 1 AT-XY T #INPUT ; : DISPLAY_BALL ( x y -- ) X @ Y @ AT-XY 42 EMIT ; : ADD_DIRECTION ( dx dy --) Y @ + Y ! X @ + X ! ; \ reverse a direction variable with one command : REVERSE ( var -- ) DUP @ NEGATE SWAP ! ; \ no need to duplicate these statements. Give them a name. : RESET_SOUND SOUND ON 0 VOL ! ; \ eliminate some if statements with a system function : BETWEEN ( n lo hi -- ?) 1+ WITHIN ; \ return true if we are out of range : HORZLIMIT? ( n -- ?) 1 30 BETWEEN 0= ; : VERTLIMIT? ( n -- ?) 1 21 BETWEEN 0= ; : ?BOUNCE ( x y -- ) VERTLIMIT? IF DY REVERSE RESET_SOUND THEN HORZLIMIT? IF DX REVERSE RESET_SOUND THEN ; \ logic built in: if SOUND is on do the sound : PING ( -- ) SOUND @ TRUE = IF GEN1 900 HZ VOL @ DB GEN2 901 HZ VOL @ DB 4 VOL +! VOL @ 30 > IF SOUND OFF SILENT THEN THEN ; \ TICKS delays in video frames (16ms) : KILL_SOME_TIME T @ TICKS ; : ERASE_BALL ( x y --) AT-XY SPACE ; : RUN DEFINE_THE_BALL VARS_TO_START ASK_DELAY CLEAR BEGIN X @ Y @ ERASE_BALL \ erase current position DX @ DY @ ADD_DIRECTION \ compute next location DISPLAY_BALL X @ Y @ ?BOUNCE \ test new x y for walls PING KILL_SOME_TIME \ self explanatory name ?TERMINAL \ check for break key UNTIL SILENT ;
Here is the program with the BASIC lines left in for comparison.
Spoiler\ ! ################### \ ! # # \ ! # BOUNCING BALL # \ ! # # \ ! # BY RETROSPECT # \ ! # # \ ! ################### \ Translated to Camel99 Forth by @theBF \ * We need to compile some TI-99 words into the standard Forth INCLUDE DSK1.TOOLS \ for debugging INCLUDE DSK1.GRAFIX \ Graphics 1 mode with TI BASIC look-alike commands INCLUDE DSK1.SOUND \ sound chip contolr INCLUDE DSK1.INPUT \ gives us an input statement \ ! ------------------------------------------------------------------ \ ! VARIABLES USED; \ ! \ Variables are declared instead of comments. changed names for clarity VARIABLE X \ x position VARIABLE Y \ y position VARIABLE DX \ x vector VARIABLE DY \ y vector VARIABLE T \ delay between movements VARIABLE SOUND \ sound flag VARIABLE VOL \ volume \ ! ------------------------------------------------------------------- \ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ \ Forth does not translate from BASIC in straight line like Assembler. \ In Forth it's better to create the parts we need as named sub-routines \ then put them all together at the end as a final program \ 100 ! BOUNCING BALL PROGRAM \ 110 ! \ 120 ! \ 130 ! \ 140 ! CLEAR THE SCREEN \ 150 CALL CLEAR \ 160 ! \ 170 ! \ 180 ! DEFINE THE BALL & COLORS \ 190 CALL CHAR(42,"3C7EFFFFFFFF7E3C") \ 200 CALL COLOR(2,5,1,3,5,1,4,5,1) \ GRAFIX library provides some words like TI BASIC : DEFINE_THE_BALL S" 3C7EFFFFFFFF7E3C" 42 CALLCHAR \ set no.s are different than BASIC. \ SET# word means I don't need to remember that stuff 42 SET# 5 1 COLOR ; \ 210 ! \ 220 ! \ 230 ! \ 240 ! VARIABLES TO START \ 250 X,Y=2 \ 260 DX,DY=1 \ 270 SOUND=0 : VARS_TO_START 1 DUP X ! Y ! 1 DUP DX ! DY ! SOUND ON ; \ 280 ! \ 290 ! \ 300 ! \ 310 ! ASK USER DELAY VAL \ 320 DISPLAY AT(1,1):"DELAY VAL?" \ 330 ACCEPT AT(3,1):T \ 340 CALL CLEAR : ASK_DELAY CLEAR 0 0 AT-XY ." DELAY VAL?" 0 1 AT-XY T #INPUT ; \ 350 ! \ 360 ! \ 370 ! \ 380 ! DISPLAY THE BALL \ 390 CALL HCHAR(X,Y,42) : DISPLAY_BALL ( x y -- ) X @ Y @ AT-XY 42 EMIT ; \ 400 ! \ 410 ! \ 420 ! \ 430 ! KEEP THE OLD COORDS \ 440 TEMPX=X \ 450 TEMPY=Y ( Don't need TEMPX TEMPY. keep copies on the DATA stack ) \ 460 ! \ 470 ! \ 480 ! \ 490 ! ADD DX & DY \ 500 X=X+DX \ 510 Y=Y+DY : ADD_DIRECTION ( dx dy --) Y @ + Y ! X @ + X ! ; \ Here is where we can use "words" to simplify all this logic \ 520 ! \ 530 ! \ 540 ! \ 550 ! IF AT BOUNDARIES, ADD DX AND DY \ 560 IF X>23 THEN DX=-DX :: SOUND=1 :: VOL=0 \ 570 IF X<2 THEN DX=-DX :: SOUND=1 :: VOL=0 \ 580 IF Y>30 THEN DY=-DY :: SOUND=1 :: VOL=0 \ 590 IF Y<2 THEN DY=-DY :: SOUND=1 :: VOL=0 \ reverse a direction variable with one command : REVERSE ( var -- ) DUP @ NEGATE SWAP ! ; \ no need to duplicate these statements. Give them a name. : RESET_SOUND SOUND ON 0 VOL ! ; \ eliminate some if statements with a system function : BETWEEN ( n lo hi -- ?) 1+ WITHIN ; \ return true if we are out of range : HORZLIMIT? ( n -- ?) 1 30 BETWEEN 0= ; : VERTLIMIT? ( n -- ?) 1 21 BETWEEN 0= ; : ?BOUNCE ( x y -- ) VERTLIMIT? IF DY REVERSE RESET_SOUND THEN HORZLIMIT? IF DX REVERSE RESET_SOUND THEN ; \ 600 ! \ 610 ! \ 620 ! \ 630 ! IS SOUND"ON"? \ 640 IF SOUND=0 THEN 710 \ 650 VOL=VOL+4 :: IF VOL<30 THEN 670 \ 660 SOUND=0 :: CALL SOUND(-1,110,30):: GOTO 710 \ 670 CALL SOUND(-150,900,VOL,901,VOL) : PING ( -- ) SOUND @ TRUE = \ logic built in: if SOUND is on do the sound IF \ Camel99 has sound words for generator, frequency & volume \ we use Standard Forth's MS for milliseconds of sound delay GEN1 900 HZ VOL @ DB GEN2 901 HZ VOL @ DB 4 VOL +! VOL @ 30 > IF SOUND OFF SILENT THEN THEN ; \ 680 ! \ 690 ! \ 700 ! \ 710 ! KILL SOME TIME \ 720 FOR DELAY=1 TO T \ 730 NEXT DELAY \ TICKS delays in video frames (16ms) : KILL_SOME_TIME T @ TICKS ; \ 740 ! \ 750 ! \ 760 ! \ 770 ! DISPLAY NEW BALL \ 780 CALL HCHAR(TEMPX,TEMPY,32,1) \ 781 CALL HCHAR(X,Y,42,1) : ERASE_BALL ( x y --) AT-XY SPACE ; \ 790 ! \ 800 ! \ 810 ! \ Make the final program loop : RUN DEFINE_THE_BALL VARS_TO_START ASK_DELAY CLEAR BEGIN X @ Y @ ERASE_BALL \ erase current position DX @ DY @ ADD_DIRECTION \ compute next location DISPLAY_BALL X @ Y @ ?BOUNCE \ test new x y for walls PING KILL_SOME_TIME \ self explanatory name ?TERMINAL \ check for break key UNTIL SILENT ; \ 820 ! REPEAT THE LOOP \ 830 GOTO 440
- 2
- 2
-
6 hours ago, Tursi said:
Attached is an assembly version of your code, excluding the ACCEPT statement. I commented as much as I could.
* ################### * # # * # BOUNCING BALL # * # # * # BY RETROSPECT # * # # * ################### * ------------------------------------------------------------------ * REGISTERS USED: * * r0 used for subroutines and temporary data * r1 used for subroutines and temporary data * r2 used for subroutines and temporary data * r3 used for subroutines and temporary data * r4 -- free -- * R5 BALL'S Y POSITION * R6 BALL'S X POSITION * R7 NUMBER TO ADD TO BALL'S Y POS * R8 NUMBER TO ADD TO BALL'S X POS * R9 USER DEFINED DELAY VALUE * R10 "PING" NOISE ON OR OFF? * r11 branch and link return address (hardware defined this!) * r12 -- free -- (but if we needed the keyboard or DSRs, this is the CRU register) * R13 STORES THE VOLUME FOR THE "PING", DECREASES BY 4 EACH UPDATE * r14 -- free -- * r15 -- free -- * TEMPR0 THE BALL'S OLD Y POSITION * TEMPR1 THE BALL'S OLD X POSITION * ------------------------------------------------------------------- * load with Editor/Assembler, loads at 32k * this defines 'START' as a name you can use with LOAD AND RUN DEF START * This pulls in some named addresses from E/A for us, you can also use EQUs REF VDPWA,VDPWD,SOUND * this will store the data in scratchpad RAM. Remember this is very limited * (only 256 bytes) and if you want to use VDP interrupts or any of the OS * subroutines you have to preserve a lot of it. In this program we won't. * You could also store these in the 32k RAM (low memory is good). If you ever * want to move your code to ROM, I recommend you explicitly choose somewhere * rather than just merging it with your main program. * I am going to put the registers at >8300, that takes 32 bytes, so >8320 is next free aorg >8320 TEMPR5 bss 2 * bss "block starting with symbol" just reserves this many bytes of memory TEMPR6 bss 2 * since we are storing 16-bit values, we want 2 bytes * next information goes at >A000, you can use other addresses or let it automatically select aorg >a000 * Let's define some VDP access utilities. I'm not using the * names here, but you could 'REF' the names in the E/A manual * write the VDP address in R0 (as a 'write' address) vadr ori r0,>4000 * make this a write address swpb r0 * we need to write the LSB first movb r0,@VDPWA * this writes the LSB to the address register swpb r0 * get the MSB back movb r0,@VDPWA * write the MSB to the address register (with the >40 bit set) andi r0,>3fff * this turns the >4000 bit off again, restoring the original b *r11 * or "rt", this returns to caller * copy a block of data from CPU memory to VDP memory (similar to VMBW) * R0 = VDP address * R1 = CPU address * R2 = count of bytes * R3 = temporary storage vmbw mov r11,r3 * we have to save the return address from R11, the 9900 doesn't use a stack bl @vadr * use the routine above to tell the VDP what address we want to write to vmlp movb *r1+,@VDPWD * move a byte from CPU memory to the VDP data address. R1 is automatically incremented dec r2 * count down the number of bytes jne vmlp * keep looping until it's zero (jump if not equal (to zero)) b *r3 * we saved the return address from r11 in r3, so use that to return * CALL CLEAR - since we use it more than once, I am making it a function * uses r0,r1,r2,r3 clear * Under Editor/Assembler, the screen image table is at >0000 mov r11,r3 * save the return from r11 to another register, as there's no stack clr r0 * going to be address 0 bl @vadr * set the VDP address li r1,>2000 * going to write spaces li r2,768 * we need 768 of them lp150 movb r1,@VDPWD * write a single space to the VDP, the address automatically increments dec r2 * count down jne lp150 * loop around until the dec returns 0 (jump if not 'equal' (to zero)) b *r3 * we saved the return address in r3, so use that to return * char data for the ball ballch data >3c7e,>ffff,>ffff,>7e3c * color data - since we have an odd number of bytes, it's important to use * the 'EVEN' directive to get the assembler address back to an even value * Note that color codes are 1 less than in BASIC!! coldat byte >40,>40,>40 EVEN * Start the program here START *100 * BOUNCING BALL PROGRAM *110 * *120 * *130 * *140 * CLEAR THE SCREEN *150 CALL CLEAR bl @clear *160 * *170 * *180 * DEFINE THE BALL & COLORS *190 CALL CHAR(42,"3C7EFFFFFFFF7E3C") * Under Editor/Assembler, the pattern descriptor table is at >0800. * Each character takes 8 bytes, so character 42 is at 42*8+>0800 = >0950 * we need to store the bytes somewhere, so they are stored up above START li r0,>0950 * the VDP address for character 42 li r1,ballch * the CPU address for the pattern data li r2,8 * and there are 8 bytes bl @vmbw * do the write *200 CALL COLOR(2,5,1,3,5,1,4,5,1) * Under Editor/Assembler, the color table is at >0380. Each color set is * one byte consisting of foreground and background color. Since this * sets sets 2-4, we can just use another vmbw with the data stored above. * however, remember that BASIC color set 2 is NOT actually the first color * set, we need to count all the way back to character 0. So character 42, * which is color set 2 in BASIC, is 42/8=5 in assembly. So, we start this * write at >0385 li r0,>0385 * starting at >0385 li r1,coldat * pulling from coldat li r2,3 * write 3 bytes bl @vmbw * and go do it *210 * *220 * *230 * *240 * VARIABLES TO START *250 R5,R6=2 *260 R7,R8=1 *270 R10=0 * It gets a little tricky here. It's tradition (and only tradition) to * reserve the lower registers for your function calls, thus the above used * R0-R3. For that reason, I've renamed the variables in this program to * use higher registers, starting at R5 to avoid confusion. You can also * just store data directly in memory using labels, if you run out of registers, * but we'll keep this one simple. li r5,2 li r6,2 li r7,1 li r8,1 clr r10 *280 * *290 * *300 * *310 * ASK USER DELAY VAL *320 DISPLAY AT(1,1):"DELAY VAL?" *330 ACCEPT AT(3,1):R9 * this is actually a fairly complex request. There's no pre-made functions for input. * the display is easy - you can use another vmbw. But the input, reading the keys, * and converting each character to an ultimate number is more than I'm willing to * do tonight. So with apologies, I am hard-coding the delay here. ;) li r9,>0800 *340 CALL CLEAR bl @clear *350 * *360 * *370 * *380 * DISPLAY THE BALL *390 CALL HCHAR(R5,R6,42) * this is the screen image table again, but now we need to do some math. * since the base is 0, we don't need to add an offset, but we need to * multiply the rows by 32, then add the column. Interestingly, we can * multiply by 32 more quickly and easily by shifting left by 5 (every * shift is essentially multiplying by 2 thanks to binary math) mov r5,r0 * get the row into r0 sla r0,5 * 2,4,8,16,32 -- 5 times a r6,r0 * add the column in - now we have the VDP address bl @vadr * now we just have to write the asterisk li r1,>2A00 * character 42 in the MSB movb r1,@VDPWD * write it to the VDP write data address *400 * *410 * *420 * *430 * KEEP THE OLD COORDS *440 TEMPR5=R5 *450 TEMPR6=R6 * to handle non-register variables, we need to just dedicate some memory locations * to them. I have done so up at the top, see the comments there. * This is also where we loop around to at the bottom of the loop l440 mov r5,@TEMPR5 * copy r5 to the memory value mov r6,@TEMPR6 * and r6 too. note the '@' indicator to reference memory *460 * *470 * *480 * *490 * ADD R7 & R8 *500 R5=R5+R7 *510 R6=R6+R8 a r7,r5 * add r7 to r5 and store in r5 a r8,r6 * add r8 to r6 and store in r6 *520 * *530 * *540 * *550 * IF AT BOUNDARIES, ADD R7 AND R8 *560 IF R5>23 THEN R7=-R7 :: R10=1 :: R13=0 * here we can start using the compare instructions, as opposed to the previous * assumed comparison against zero. ci r5,23 * compare against 23 jle l570 * jump ahead to the label l570 if LESS or EQUAL (note: this is an unsigned test!) neg r7 * negert r7 li r10,1 * set r10 to 1 clr R13 * set R13 to 0 * and for any place we need to jump to, we need a label l570 *570 IF R5<2 THEN R7=-R7 :: R10=1 :: R13=0 ci r5,2 * compare against 2 jhe l580 * jump if high or equal (again, note this is an unsigned test! It can't detect negative numbers) neg r7 li r10,1 clr r13 l580 *580 IF R6>30 THEN R8=-R8 :: R10=1 :: R13=0 ci r6,30 jle l590 * note how we are doing the opposite test to skip the code we don't want to execute neg r8 li r10,1 clr r13 l590 *590 IF R6<2 THEN R8=-R8 :: R10=1 :: R13=0 ci r6,2 jhe l600 neg r8 li r10,1 clr r13 l600 *600 * *610 * *620 * *630 * IS R10"ON"? *640 IF R10=0 THEN 710 * To test against zero, it can be faster to use MOV than CI as it's a shorter instruction mov r10,r10 * test against zero jeq l710 * if equal, then jump ahead *650 R13=R13+4 :: IF R13<30 THEN 670 ai r13,4 * just add 4 to r13 ci r13,30 * compare against 30 jl l670 * jump if less than (unsigned test) *660 R10=0 :: CALL SOUND(-1,110,30):: GOTO 710 * Sound is a tricky thing - more to say on the next one * here, all we want to do is set the volume to muted, we * don't actually care about the pitch and the duration is not important * however, we have to manually mute both channels that we use. TI BASIC * automatically mutes any channel you don't specify, but assembly won't! * (Duration has to be handled manually) clr r10 * set r10 to 0 li r0,>9FBF * the volume register on the sound chip is at >9x, and volumes are 1/2 BASIC's value. F is 15. (And >Bx is channel 2) movb r0,@SOUND * write the command to the sound chip swpb r0 * get the other command movb r0,@SOUND * and send it to the sound chip too jmp l710 * unconditional jump l670 *670 CALL SOUND(-150,900,R13,901,R13) * This is a bigger deal. Because we do a nice ping and mute the sound explicitly, we can * ignore the duration. Otherwise, you need to count it out yourself and then mute manually. * The frequency is a tricky thing. While BASIC uses the frequency in hertz, the sound chip * actually uses a 'sound code', or a countdown timer, which ranges from >0000 to >03FF. Higher * numbers result in a lower tone (and >0000 wraps around and is the lowest, but might not work * on some of the newer consoles as the newer variant of the sound chip changed that.) * Because the pitches are fixed in this program, we COULD set them once at the start and * never touch them again, only the volumes, but we'll go ahead and load them here for example. * The formula to get a frequency code from Hz is 110860.8/Hz, which in this case works out to * 123 for both channels (123.17 and 123.04). So you'll see that the sound doesn't interfere * or make that synth noise you might have expected for that reason - the step isn't big enough. * 908 should work though - that would give a code of 122. For now I'll match the BASIC code. * To write a frequency directly to the sound chip takes two writes, and it's in a somewhat obtuse * order. The first write contains the command nibble (ie: >8x for the first channel), and the * /least/ significant nibble of the tone. The second write relies on the first to know where to * go, and contains the two most significant nibbles (which, you'll note, is NOT a byte but * straddles them both - see the range I listed above). This is a little confusing, yes, but it's * just fact of life and never changes or gets more complicated, so eventually you just accept it. * So, we have two frequencies, both are currently 123 (though you can change one to 122 if you want). * converted to hex, that gives us >007B. The least significant nibble is >B, and the next two are >07. * I'll load them separately here, so you can easily change the second one. * So, the first byte we write has the command nibble, which for voice 1 tone is ">8x", plus that least * significant nibble, so it will be >8B. Then we write the other two nibbles: >07. * For voice 2, it's the same thing, but the tone command nibble is >Ax, so it will be >AB, >07. li r0,>8B07 * the two bytes to write for tone 1 movb r0,@SOUND * write the command and least significant nibble swpb r0 * get the other byte movb r0,@SOUND * write the rest of it li r0,>AB07 * the two bytes for tone 2 - to change it to 122 as described above, change to >AA07 movb r0,@SOUND * write the command and least significant nibble swpb r0 * get the other byte movb r0,@SOUND * write the rest of it * now we still have to write the volumes. Normally in an assembly program you would manage the volumes * in the range the hardware knows, which is 0-15, but since we are converting from BASIC we need to * deal with 0-30. We can divide by 2 with a simple shift just like multiplying by 2. * The command nibble for voice 1 volume is >9x, and voice 2 is >Bx mov r13,r0 * get the volume so we can work on it srl r0,1 * shift by 1. For right shift you select 'logical' for unsigned numbers, and 'arithmetic' for signed ai r0,>0090 * currently the value is in the least significant byte, so add in the command nibble swpb r0 * and get it up where we need it movb r0,@SOUND * voice 1 volume loaded andi r0,>0F00 * since we are loading the same volume, just clear all the bits except the volume bits ai r0,>B000 * note we are in the MSB now because of the earlier swpb, add command for voice 2 volume movb r0,@SOUND * and write it *680 * *690 * *700 * l710 *710 * KILL SOME TIME *720 FOR DELAY=1 TO R9 *730 NEXT DELAY * this is not so different in assembly. The main difference is it can be very, very fast. So usually, * unless I need the fastest speeds, I put some dummy instructions inside the loop to slow it down a bit. * I use shifts as they take 2 extra cycles for every count you shift by, up to 16, making it easy to * adjust the speed of the delay loop by changing how much it shifts mov r9,r0 * get the delay value l720 srl r1,8 * do a dummy shift of r1 to waste time, we aren't using it right now dec r0 * count down the delay jne l720 * back up until it reaches zero *740 * *750 * *760 * *770 * DISPLAY NEW BALL *780 CALL HCHAR(TEMPR5,TEMPR6,32,1,R5,R6,42,1) * I don't think I knew you could do this in one call... ;) * anyway, this should be clear by now - calculate the address, write the value mov @TEMPR5,r0 * get the row into r0 sla r0,5 * 2,4,8,16,32 -- 5 times a @TEMPR6,r0 * add the column in - now we have the VDP address bl @vadr * now we just have to write the space li r1,>2000 * character 32 in the MSB movb r1,@VDPWD * write it to the VDP write data address * now calculate the new address mov r5,r0 * get the row into r0 sla r0,5 * 2,4,8,16,32 -- 5 times a r6,r0 * add the column in - now we have the VDP address bl @vadr * and write the asterisk li r1,>2A00 * character 42 in the MSB movb r1,@VDPWD * write it to the VDP write data address *790 * *800 * *810 * *820 * REPEAT THE LOOP *830 GOTO 440 * If you have to go more than 127 instructions, you can't use jumps anymore, you have to branch. * Branches can go anywhere in memory, but are slightly slower. b @l440 * Assemblers want you to include END - this is not the end of execution, it means end of the file! END
For testing, I assembled this using WinAsm994a, cause it's fast and easy. If you set the "object file" path to a Classic99 disk folder, you can directly LOAD AND RUN the executable after it builds. Just specify the filename to LOAD AND RUN, hit enter, and in this case use START as the program name. It should build in Editor/Assembler if you can get it in there but I didn't test it.Note that because you don't use CALL SCREEN, the background from Editor/Assembler will be green instead of cyan. Also note that it does not check the QUIT key, so you'll have to hard reset to end it.
To get the interrupt, just put LIMI 2, LIMI 0 (on separate lines) in your main loop, maybe right before the final GOTO. To do a call screen, in this application you can cheat and use the vadr function I provided - just use LI R0,>870x, BL @vadr (on separate lines, where 'x' is the screen color minus 1, cause all assembly color codes are -1 from BASIC). Changing the screen color changes the internal address, so you'll need another call anyway to change it, so it's no big deal that my function will also set the address for write.
Going to attach a zip here with the source, the object file, AND the source for TI MOTIF. The reason for that is it was a large conversion from XB and contains most of the common utilities and examples already converted to an assembly language program, and might be useful for deeper learning/stealing functions.
BOUNCING.zip 33.29 kB · 8 downloads
There are not enough superlatives to describe your generosity.
Bravo.
- 1
-
The very first line is wrong.
8300 is not a screen address so I suspect it goes wrong pretty fast.
And all VDP memory must be accessed through port addresses in memory and need some code.
The delay will work!
- 2
- 1
-
TIPI is good but I don't know the price.
- 1
-
16 minutes ago, Pixel_Outlaw said:
Hello All,
So I've just purchased a Ti/99 and being a pretty hardcore programmer I wanted to get started.
I found out that I didn't have (it seems) enough memory to run much with a stock TI so I purchased a 32k memory upgrade from The Brewing Acadamy.
I also purchased a FinalGROM cart to store the TurboForth cart on.
Unfortunately I'm seeing the error below. I do know that on emulator it boots with the TurboForth logo showing.
Also I'd be interested in purchasing a real Forth cart (I saw a few implementations) but I'm not sure if they require additional hardware.
I can only write to tape currently for storage, I'm a bit tapped out for cash after the RAM and FinalGROM upgrades.I'm a hardcore Lisp fan but Forth sure seems interesting from what I've poked in.
As far as I know all the Forth systems for TI-99 expect a disk drive.
That was always the classical Forth system back in the early days.
Forth was the CPU, RAM, I/O devices (terminal) and virtual memory disk system.
So you can explore a lot of stuff in the kernel, but you can't save anything as far as I know.
LISP and Forth are similar but Forth is waaaay lower level out of the box.
However your LISP training of factoring your programs into small functions holds. Actually even more so.
Forth favours small named routines to simplify stack parameter gyrations. It gets away with that because calling overhead is quite low.
You will probably use some colourful language in the course of exploring Forth.
You might find that you begin "backing talkwards".
Welcome to the party.
- 2
- 1
-
25 minutes ago, Tursi said:
Be aware that this might not be true for one of the common RS232 cards - I can't remember which. But someone gave me the notes years ago that at least one of the three main ones needs the CRU bit 0 enabled for the other bits to be available.
I know that the UART at >40 works without bit zero. I think I tried testing the 2nd one at >80 years back.
I don't recall getting very far and stopping so that could be have been the problem.
Also there didn't seem to be separate RTS/CTS handshake lines for the 2nd port if I recall correctly, when I look at the schematic.
- 2
-
I always hesitate to submit my weird code but maybe it will help for this case.
I wrote this RS232 card driver to support a Forth built to run over RS232 so I know this code works.
It just requires deciphering to translate it to conventional Assembler. 😕
It currently has hardware handshaking locked in. I found that polling the RS232 was pretty slow in Forth and CTS/RTS control made it possible to reliably send text files to the TI-99 without resorting to an interrupt driven serial receive.
Something I did from a program structure perspective was to create four variables to hold the setup.
VARIABLE CARD \ CRU address of rs232 card. Default to >1300 VARIABLE UART \ /tty1 = >40 /tty2 = ?80 VARIABLE BPS \ 0034 BPS T! \ 9600 baud VARIABLE PROTO \ 9300 PROTO T! = 8 bits, no parity, 1 stops
The port speed and protocol are locked in this code I had to fit the whole system into 8K bytes so no room for fancy stuff.
9600 bps, 8 bits, no parity, 1 stop bit.
Change it for you needs.
Here is a table of baud rate values
001A , \ 19200 0034 , \ 9600 0068 , \ 4800 00D0 , \ 2400 01A0 , \ 1200 0340 , \ 600 04D0 , \ 300 0638 , \ 110
RS232 port selection is done using a CARD+UART concept to select which 9902 to use.
So the CARD as the base address goes into R12, then you add the UART address to R12 >40 OR >80.
I put the LED on/off in a sub-routine.
This Forth code is for my cross-compiler so it has some very non-standard incantations that make that process work but they can be ignored.
I don't turn on the LED when receiving, again because I didn't want to miss characters while polling in a Forth loop.
(I didn't want to write the string input code in Assembler because it would be bigger)
Hope it helps.
Spoiler\ 9902SHAK.HSF rs232/1 DRIVER with H/W handshake 9Feb2019 bjf \ For: xfc99X.exe cross-compiler \ CODE words are used to save kernel space by not needing the CRU library \ These routines push the value in R12 onto the return stack \ then restore it when returning to Forth. \ This supports accessing other I/O devices while using the serial port. \ Jul 30/2019 added CTS control in CKEY? for hardware handshaking \ - CKEY \ for reference... [CC] HEX [TC] \ 1300 CONSTANT RS232/1 \ RS232/1 card address \ 1500 CONSTANT RS232/2 \ RS232/2 card address \ 40 CONSTANT TTY1 \ 40 = uart#1 \ 80 CONSTANT TTY2 \ 80 = uart#2 \ 9902 control bits [CC] DECIMAL 13 EQU LDIR \ "load interval register" \ 16 EQU RTSON \ request to send \ 18 EQU RIENB \ rcv interrupt enable \ 21 EQU RBRL \ receive buffer register loaded \ 22 EQU TXRE \ transmit register empty bit \ 27 EQU -DSR \ NOT data set ready \ 28 EQU -CTS \ NOT clear to send 31 EQU RESET \ 9902 reset bit [CC] HEX TARGET-COMPILING \ these variables are the data needed for 1 comm UART connection VARIABLE CARD \ CRU address of rs232 card. Default to >1300 VARIABLE UART \ /tty1 = >40 /tty2 = ?80 VARIABLE BPS \ 0034 BPS T! \ 9600 baud VARIABLE PROTO \ 9300 PROTO T! = 8 bits, no parity, 1 stops \ 9900 sub-routines. *NOT* Forth words. l: CARD-ON CARD @@ R12 MOV, \ select the card 7 SBO, \ turn LED on RT, l: CARD-OFF CARD @@ R12 MOV, \ select the card 7 SBZ, \ turn LED off RT, \ *variables CARD, UART, BPS and PROTO must be set correctly before using OPEN-TTY* CODE OPEN-TTY ( -- ) \ Usage: /TTY1 OPEN-TTY R12 RPUSH, CARD-ON @@ BL, \ load the card address UART @@ R12 ADD, \ add 9902 port address RESET SBO, \ reset UART PROTO @@ 8 LDCR, \ set protocol LDIR SBZ, \ disable 9902 timer BPS @@ 0C LDCR, \ set baud rate for XMIT & RCV CARD-OFF @@ BL, R12 RPOP, \ restore R12 NEXT, ENDCODE [CC] DECIMAL [TC] \ this word turns on the LED when sending CODE CEMIT ( c -- ) \ 'com-emit" R12 RPUSH, CARD-ON @@ BL, UART @@ R12 ADD, \ add UART offset \ *** DSR handshake *** BEGIN, 27 TB, EQ UNTIL, \ wait for -DSR=0 \ *** handshake hardware *** 16 SBO, \ set RTS \ BEGIN, 28 TB, EQ UNTIL, \ wait for CTS line \ ****************************** BEGIN, 22 TB, EQ UNTIL, \ wait XBRE empty TOS SWPB, \ put byte on the other side TOS 8 LDCR, \ send 8 bits 16 SBZ, \ reset RTS \ housekeeping on USER VARIABLES... R1 STWP, \ get current user area address 48 (R1) INC, \ inc OUT user variable **DECIMAL OFFSET VALUES** 52 (R1) INC, \ inc VCOL user variable **DECIMAL OFFSET VALUES** CARD-OFF @@ BL, R12 RPOP, TOS POP, NEXT, ENDCODE [CC] DECIMAL [TC] CODE KEY? ( -- n ) \ "com-key" 0 LIMI, R12 RPUSH, \ save R12 on return stack *Needed?* CARD @@ R12 MOV, \ set base address of CARD TOS PUSH, \ give us a new TOS register (R4) TOS CLR, \ erase it \ *** handshake hardware ON *** 5 SBZ, \ CARD CTS line LOW. You are clear to send UART @@ R12 ADD, \ add UART, >1300+40 = CRU address 21 TB, \ test if char ready EQ IF, TOS 8 STCR, \ read the char 18 SBZ, \ reset 9902 rcv buffer TOS SWPB, \ shift char to other byte ENDIF, \ *** handshake hardware off *** CARD @@ R12 MOV, \ select card 5 SBO, \ CTS line HIGH. I am busy! \ ****************************** R12 RPOP, \ restore old R12 *Needed?* 2 LIMI, NEXT, ENDCODE \ hi level interface : EMIT ( c -- ) PAUSE CEMIT ; [CC] HEX TARGET-COMPILING : CR ( -- ?) \ comm port carriage return 0D EMIT 0A EMIT VCOL OFF VROW @ 1+ 17 MIN VROW ! ; \ don't count past last line : TYPE ( addr cnt --) PAUSE BOUNDS ?DO I C@ CEMIT LOOP ; T' TYPE RESOLVES 'TYPE : SPACE ( -- ) BL EMIT ; : SPACES ( n -- ) 0 MAX 0 ?DO SPACE LOOP ; \ : BS ( --) 08 EMIT SPACE 08 EMIT ; [CC] HEX [TC] \ simplified 9902 setup commands \ Usage: \ RS232 /TTY1 BA=9600 8,N,1 OPEN-TTY : RS232 ( -- ) 1300 CARD ! ; \ primary card \ : RS232/2 ( -- ) 1500 CARD ! ; : /TTY1 ( -- ) 040 UART ! ; : /TTY2 ( -- ) 080 UART ! ; \ : BA=1200 01A0 BPS ! ; : BA=2400 00D0 BPS ! ; : BA=9600 ( -- ) 0034 BPS ! ; : BA=19200 ( -- ) 001A BPS ! ; \ : BA=38400 ( -- ) 000D BPS ! ; : 8,N,1 ( -- ) 9300 PROTO ! ; CROSS-COMPILING
- 2
-
9 minutes ago, Lee Stewart said:
It was never clear to me why the TI programmers of TI Forth defined the word DSRLNK to presume its use for only Level 3 file handling. I am seriously considering changing the fbForth word DSRLNK (inherited from TI Forth) to require an argument of 8 for Level 3 file handling and 10 for Level 1 and 2 subprograms, just as its ALC counterpart. This will obviously require any TI Forth code ported to fbForth to put 8 on the stack before DSRLNK is executed. Thoughts?
...lee
I think it's a great idea. I have been meaning to do something like that for a long time.
I just hate touching the DSRLNK because my life goes on hold for awhile when I do.
- 1
-
6 hours ago, apersson850 said:
Another way, which works even with code in ROM, is to store the opcode for SBO 0 or SBZ 0 in a register.
When you know which bit to set, load that offset into the low byte of the register. Then eXecute the register.
I think I need to steal that idea. Thanks.
- 1
-
Correction:
In the case of SBO etc. You change the byte in the instruction.
For reference I make these instructions manually with HEX code,
You can see where I only change one number for go from SBO 0 to SBO 1
CODE 0SBO ( -- ) 1D00 , NEXT, ENDCODE CODE 0SBZ ( -- ) 1E00 , NEXT, ENDCODE CODE 1SBO ( -- ) 1D01 , NEXT, ENDCODE CODE 1SBZ ( -- ) 1E01 , NEXT, ENDCODE
- 1
-
22 minutes ago, Vorticon said:
How would one do that?
Stuart is referring to something you can do in ASM or Forth or C (perhaps).
This is where you make a function with the SBO instruction in it and your language lets you get the address of that function
and lets you change the value in memory right after the instruction. (which you might get to with an offset)
Self modifying code in other words.
Not taken to be good form in modern times, but done by ASM coders to overcome shortcomings in the instruction set like you have found.
- 2
-
Have not use Pascal for 35 years but I think if you declare a variable in the Program section it is a global variable.
-
Here's a dump of my TI-Forth disk that I did in 2019.
There's some ugly stuff in here but the editor is how I left it in 1987 when I got my first PC clone.
- 2
- 1
-
1 hour ago, Vorticon said:
So it is a kosher way of doing things after all...
It's usually best to consult a Rabbi for these decisions, but I think Lee will do in a pinch
I totally forgot about that oddity in CRU addresses.
- 4
-
33 minutes ago, GDMike said:
Yes,words take years off my life I found.
You're still a forth guru even in willsy code.
As long we use the word "guru" loosely.
- 1
TF, camel, FB Forth fun
in TI-99/4A Development
Posted
That's fancy using the sprite. The motion is much smoother with pixel level resolution.