Jump to content


+AtariAge Subscriber
  • Content Count

  • Joined

  • Last visited

Everything posted by TheBF

  1. Maybe you can figure out how to do this one. It seems tricky to do with characters. There is a dot at every intersection, but I can only see 3 at time no matter how hard I look!
  2. Yes. That's fun. And I think there is one that is even weirder using that template... I will try it
  3. A 2nd Shade of Gray for TI-BASIC ​ 100 CALL CLEAR 110 CALL SCREEN(16) 120 PAT$="FFFFFFFFFFFFFFFF" 130 CALL CHAR(96,PAT$) 140 CALL CHAR(104,PAT$) 150 CALL COLOR(9,2,1) 160 CALL COLOR(10,15,1) 170 CALL HCHAR(2,9,65) 180 CALL HCHAR(2,23,66) 190 FOR I=4 TO 24 STEP 2 200 CALL HCHAR(I,1,96,32) 210 NEXT I 220 LET COL=7 230 LET ROW=5 240 GOSUB 290 250 LET COL=20 260 LET ROW=6 270 GOSUB 290 280 GOTO 250 290 FOR X=ROW TO 24 STEP 2 300 CALL HCHAR(X,COL,104,6) 310 NEXT X 320 RETURN
  4. My first FB Forth program: I cheated a little bit. I took a CAMEL99 program and added a small set of translation words. And guess what? It works just fine. For those who are just learning Forth it demonstrates how to use the interrupt timer for a precise delay and how to use the number formatting words. Just paste this code into FB Forth and it puts little clock on the screen. \ A simple Clock in FB Forth (FIG Forth dialect) \ Tiny HARNESS for FBFORTH->CAMEL99 translation : 2DROP DROP DROP ; : 2DUP OVER OVER ; : VARIABLE 0 VARIABLE ; : AT-XY ( n n -- ) GOTOXY ; : OFF ( addr -- ) 0 SWAP ! ; : ^C? ?TERMINAL ; \ CAMEL99 version can now compile HEX 8379 CONSTANT TICKER DECIMAL VARIABLE HRS VARIABLE MINS VARIABLE SECS VARIABLE TICKS 58 CONSTANT ':' \ character we will need \ increment no. in the address : 1+! ( addr -- ) 1 SWAP +! ; \ FB FORTH and CAMEL99 Forth run with interrupts enabled \ so the ticker is changing every 1/60 of a second. \ We can wait until ticker changes and know it's 1/60 of a second : 1/60 ( -- ) TICKER DUP @ \ dup ticker & fetch initial timer value BEGIN OVER @ \ read it again OVER - \ subtract initial value UNTIL \ loop until result is not zero 2DROP ; \ drop the initial value : 1SEC ( -- ) 60 0 DO 1/60 LOOP ; : KEEPTIME 1SEC SECS 1+! SECS @ 60 = IF SECS OFF MINS 1+! THEN MINS @ 60 = IF MINS OFF HRS 1+! THEN ; \ formatted output : ## ( n -- ) 0 <# # # #> TYPE ; : :## ( n -- ) 0 <# # # ':' HOLD #> TYPE ; : .TIME ( -- ) SECS @ MINS @ HRS @ ## :## :## ; : SETCLOCK ( hrs mins secs -- ) SECS ! MINS ! HRS ! ; : CLOCK ( row col -- ) BEGIN 2DUP AT-XY .TIME KEEPTIME ^C? \ waits for function clear in FB Forth UNTIL 2DROP ; 1 59 50 SETCLOCK PAGE 10 10 CLOCK
  5. Thanks Lee. That's a much nicer looking listing!
  6. There are Sprites in the air it seems. I stayed up late last night trying to grok the ISR code for Sprite motion. My homemade sprite auto-motion system is wrong, but I don't understand exactly how. I was taking a velocity(x,y) (+ or - ) from an array and simply adding it to the current sprite position. This gave very ragged motion. I checked XB and the sprites are only moved 1 pixel at a time from what I can see regardless of the velocity used. (Classic99 debugger) So if anybody can explain what's going on with this ROM code I would be all ears. Like what is this auxiliary data in the motion table? This is from TI Intern, but I have added some my own comments to help me stay on track 093A C002 MOV 2,0 093C C0A2 MOV @>0002(2),2 Fetch INT address 093E 0002 0940 0692 BL *2 And execute 0942 C090 MOV *0,2 Next Int routine 0944 10F9 JMP >0938 0946 0460 B @>0AB8 End interrupt from CRU 0948 0AB8 094A 1D02 SBO >0002 Clear VDP interrupt 094C D060 MOVB @>83C2,1 Fetch interrupt flag byte 094E 83C2 0950 0A11 SLA 1,1 No interrupt permitted 0952 1702 JNC >0958 0954 0460 B @>0A84 then jump 0956 0A84 0958 0A11 SLA 1,1 095A 1846 JOC >09E8 No sprite move permitted, then jump 095C D320 MOVB @>837A,12 Number sprites -> R12 095E 837A 0960 1343 JEQ >09E8 IF sprite_cnt=0 then skip all this 0962 098C SRL 12,8 Move sprite count to other byte 0964 0202 LI 2,>8800 VDP RD -> R2 0966 8800 0968 0203 LI 3,>8C00 VDP WD -> R3 096A 8C00 096C 0208 LI 8,>0780 Sprite motion table -> R8 096E 0780 * begin loop for each sprite 0970 D7E0 MOVB @>83F1,*15 Write address motion table 0972 83F1 0974 D7C8 MOVB 8,*15 load Spr motion table to VDP Write address *===================================================== 0976 04C4 CLR 4 0978 D112 MOVB *2,4 read Motion table Y velocity ->R4 097A 04C6 CLR 6 097C D192 MOVB *2,6 read Motion table X X velocity->R6 097E 0844 SRA 4,4 ?? shift Y velocity right 4 0980 D152 MOVB *2,5 read Y Auxiliary sprite data->R5 0982 0845 SRA 5,4 shift Aux data 4 right (16/) 0984 A144 A 4,5 ADD R4->R5 *==================================================== 0986 D1D2 MOVB *2,7 Read Y Auxiliary sprite data-> R7 0988 0846 SRA 6,4 Shift X motion by 4 right 098A 0847 SRA 7,4 Shift X aux data 4 right 098C A1C6 A 6,7 Add R6->R7 098E 0228 AI 8,>FB80 Address sprite descriptor table 0990 FB80 0992 D7E0 MOVB @>83F1,*15 Write address 0994 83F1 0996 D7C8 MOVB 8,*15 ==================================================== 0998 04C4 CLR 4 099A D112 MOVB *2,4 Fetch Y position->R4 099C A105 A 5,4 add Y motion+aux+Ypos->R4 099E 0284 CI 4,>C0FF are we at X,Y limits (192,256) 09A0 C0FF 09A2 1209 JLE >09B6 if not off screen, goto 9B6 09A4 0284 CI 4,>E000 are we at start of screen ( 224,00) 09A6 E000 09A8 1B06 JH >09B6 if HI goto 9b6 09AA C145 MOV 5,5 else test for 0 09AC 1502 JGT >09B2 if R5>0 goto 09B2 09AE 0224 AI 4,>C000 ?? what is this for 09B0 C000 09B2 0224 AI 4,>2000 ?? and why add this 09B4 2000 09B6 04C6 CLR 6 *screen limit tests 09B8 D192 MOVB *2,6 09BA A187 A 7,6 09BC 0268 ORI 8,>4000 setup VDP address for writing 09BE 4000 09C0 D7E0 MOVB @>83F1,*15 09C2 83F1 09C4 D7C8 MOVB 8,*15 09C6 D4C4 MOVB 4,*3 Write X,Y positions to Spr. desc. table 09C8 0228 AI 8,>0482 ? 09CA 0482 09CC D4C6 MOVB 6,*3 09CE 06C5 SWPB 5 09D0 D7E0 MOVB @>83F1,*15 Write address motion table 09D2 83F1 09D4 D7C8 MOVB 8,*15 09D6 0945 SRL 5,4 09D8 D4C5 MOVB 5,*3 Write auxiliary values 09DA 06C7 SWPB 7 09DC 0947 SRL 7,4 09DE D4C7 MOVB 7,*3 09E0 0228 AI 8,>C002 New address motion table 09E2 C002 09E4 060C DEC 12 Last sprite? 09E6 15C4 JGT >0970 No, once again * end sprite loop 09E8 0A11 SLA 1,1 START of sound processing...
  7. No problem. CAMEL99 does as well. In fact the standard Forth context switcher should be able to be installed as an ISR. You would just have to be careful with tasks that used shared resources. Create some locks or something like that.
  8. TheBF


    Since I was looking at the code I re-wrote it so it does not look at column 1 by factoring the hardware reading code as a sub-routine. This further improved the speed from 336uS in the old verison to 273uS but cost 4 bytes of space. The screen capture shows the test results using the 9901 timer word '[email protected]" versus calling KSCAN (ASM word KEY? in Forth) This ctrl C reader is over 4X faster than calling KSCAN Here is the new version in case anybody finds it useful. To use this concept in TI ASM just remember that TOS is just a re-name for R4. \ sub routine to read key matrix. Set R1 to the column to read L: _RKEYS R12 0024 LI, \ set CRU address for column select chip R1 3 LDCR, \ Set 3 bits of column R2 SETO, \ R2 gets the bits. set all bits to 1 R12 0006 LI, \ R12-address to read rows R2 8 STCR, \ store 8 row values of current column (bits)->R2 *** R2 INV, \ outputs are low active so flip bits R2 TOS ADD, \ collect all the bits in TOS (R4) RT, CODE: ^C? ( -- ?) \ fast check if ctrl C key pressed TOS PUSH, \ make room in TOS register TOS CLR, \ TOS will accumulate keyboard bits R1 CLR, \ R1 is column to read. starts at 0 _RKEYS @@ BL, \ read keys in column 0 R1 0200 LI, \ set column to 2 _RKEYS @@ BL, \ read keys in column 0 SCRTO @@ CLR, \ Reset TI system screen timeout counter TOS C000 CMPI, \ look for ctrl 'C'. (2 bits set) @@2 JEQ, \ ^C was pressed TOS CLR, \ no ^C, clear TOS, return to forth NEXT, @@2: TOS SETO, \ set TOS to -1 (Forth true flag) NEXT, \ return to Forth END-CODE
  9. TheBF


    Thanks for the corrections Lee, I will update my source code file. I don't remember where I found the table. But I know I didn't derive it all by myself. Yours appears to align with the Thierry Nouspikel Web site. So I will avail myself of yours in my source as well with credit to you as it makes more sense.
  10. LOL. My first attempt at it created what looked like 3 sprites! :-) It was because they were created so fast there were 3 tight clusters of them locked in motion on the screen. I kept looking at the code thinking WT* ! I made 32 sprites!! Where are they???? And of course since I typed the code for the language and libraries I don't trust anything! So round and round you go looking for what you did wrong inside the system. You probably had that happen with RXB. And if you turn over a stone... there are always bugs. But in this case it was the application code. It's a weird hobby. Sometime I think I need a shrink, but then I think it over and realize I just need a drink.
  11. Nice. It looks much better on the darker background. I was just pleased to get mine going. In time I will take a run at porting the tasker to a few blocks in FB Forth. I am pretty sure the conventional version is straightforward. It's fun to make the old 99 run in circles! :-)
  12. I am having flashbacks reviewing your new Forth 2.0 in the 64 col. Editor. Thanks for all your hard work Lee. It's a beautiful thing.
  13. TheBF


    I too wanted a faster way to catch a keystroke. Mine was to break out of loops without slowing the loop down too much. This is little routine will only detect a control C key press. But it could be configured to different keys. It is 4 times faster than using KSCAN. (approx. 250ms vs over 1mS) Here is the matrix I used to understand how to do it. \ R1 TB 0 TB 1 TB 2 TB 3 TB 4 TB 5 TB 6 TB 7 \ --------------------------------------------------------------------- \ 0000 = space enter fctn shift ctrl \ 0100 . L O 9 2 S W X \ 0200 , K I 8 3 D E C \ 0300 M J U 7 4 F R V \ 0400 N H Y 6 5 G T B \ 0500 / ; P 0 1 A Q Z \ 0600 Fire1 Left1 Right1 Down1 Up1 \ 0700 Fire2 Left2 Right2 Down2 Up2 \ --------------------------------------------------------------------- Written in reverse assembler. (put the instruction on the other side for conventional assembler) Weirdness: TOS is just a renamed R4. PUSH is a macro that moves the stack pointer. (SP DECT) and moves a register ( MOV R4,*SP) The method is I collect bits in R4, while looping through the keyboard rows (Register 1) When R4 = C000 I have a ctrl C pressed. So by changing that value you could detect any key. I took this code from a more general routine and I am sure it can be improved. As I look at It, I see it does not need to look at row 0100 at all for a control C. Duh! So I can speed it up more in the future. CODE: ^C? ( -- ?) \ fast check if ctrl C key pressed TOS PUSH, \ make room in TOS register TOS CLR, \ TOS will accumulate keyboard bits R1 CLR, \ R1 is column to read. starts at 0 @@1: R12 0024 LI, \ set CRU address for column select chip R1 3 LDCR, \ Set 3 bits of column R2 SETO, \ R2 gets the bits. set all bits to 1 R12 0006 LI, \ R12-address to read rows R2 8 STCR, \ store 8 column values (bits)->R2 R2 INV, \ outputs are low active so flip bits R2 TOS ADD, \ collect all the bits in TOS (R4) R1 0100 ADDI, \ advance to next row R1 0300 CMPI, \ are we at row 3? @@1 JNE, \ if not jump back SCRTO @@ CLR, \ Reset TI system screen timeout counter TOS C000 CMPI, \ look for ctrl 'C'. (2 bits set) @@2 JEQ, \ ^C was pressed TOS CLR, \ no ^C, clear TOS, return to forth NEXT, @@2: TOS SETO, \ set TOS to -1 (Forth true flag) NEXT, \ return to Forth END-CODE
  14. A while back Sometimes99er created this pretty demo of "Quasi" particles falling from the screen. (can't seem to find it on the site, but I know I didn't imagine it) Since I have little imagination for the visual arts I enjoy trying to emulate XB functionality made by the creative people here with my hobby Forth system. So I whipped up some code and hit return and mine did not look like Sometimers99er's at all! I went away humbled and didn't think much about it for long time, until this week when I realized that I was creating my sprites too fast. :-) The beautiful way the Quasi particles fall from the top of the screen in BASIC was due to the delay created by the BASIC interpreter as it creates a new sprite. I added some appropriate delays in my "particle sprayer" and voila! A thing of beauty. And we all know that a thing of beauty is a joy forever. :-) Doing this helped expose a few more bugs and speed ups for my Sprite file so a big thanks to Sometimes99er. If I ever stop playing and work on my file interface I might have a complete system. Here is the code with lots of comments:
  15. Hey Lee, the product looks great up on Amazon. Good on you! B
  16. Sounds perfect for implementing a Forth BLOCK system. Once you have that it is possible to create a simple file system relatively easily. Something like TI-99 file system functionality would not be too difficult. (?) (never did it so normal warnings apply) Or you could take some ideas from here: http://www.forthos.org/fs.html
  17. This kind of thing is commonly solved using the U< operator. ("un-signed less than" for those not familiar with Forth) If the array size is 1000 for example, and I ask for index 500 500 1000 U< returns TRUE so it's a good index. BUT... if I ask for index 1001 1001 1000 U< gives me FALSE (1001 is NOT less-than 1000) so I should abort with an error. And if I ask for index -1 or -2 those numbers are actually 65,535 and 65,534 unsigned. -1 1000 U< returns FALSE because it is actually 65535 1000 U<. So U< lets you protect against negative values and "too high" positive values with one comparison. Neat trick of twos complement arithmetic. https://en.wikipedia.org/wiki/Two%27s_complement
  18. Have not tried HYPE, but there are quite a few OOP overlays for Forth. Thanks for the link. I will give it a whirl. And yes. Domain specific languages are how Forth is used very frequently. Nobody knows it's Forth under the hood (bonnet) because it looks like something else. :-) OOP Implementations: Bernd Paisons did one called minioof https://www.complang.tuwien.ac.at/forth/gforth/Docs-html/Mini_002dOOF.html The big one was a language called NEON for the Mac. It survives as MOPS Forth for the MAC. It is pretty cool too.
  19. I think is more of a protection from a negative index getting into the mix. Also the error detection uses '>" which is a signed operation so this implementation could only handle an array on **32K cells max. Which of course in a stock TI-99 is big enough since the largest continuous block of memory is 24K **(because any number bigger than HEX 7FFF (32,767) is treated as negative in signed operations on the 9900) The definition for CELLS on a 16 bit CPU is just: : CELLS ( n -- n ) 2* ; And 2* is typically just duplicate the top of stack value and ADD the 2 together ; : 2* ( n -- n) DUP + ; \ in high level Forth. But normally coded in Assembler Nothing is hidden in Forth, but it can be a little harry sifting through all the Assembly language to find the answer.
  20. Can you give us an example of what you want to parse? A general purpose solution might take way more resources than you really need.
  21. That's a clean approach to arrays in Forth. I have to come to the conclusion that is this kind of code is clearer and faster ultimately than using the Forth CREATE DOES> construct for arrays. Nicely done.
  22. Thanks. It's a little bit long, but I hope it helps folks understand a little more about how Forth works and why it keeps us fascinated.
  23. The Forth language is commonly used for the same type of programs that people might choose to use Assembler. Typically they are embedded programs for electronic devices, measurement instruments, drum machines, satellites and even electric toothbrushes. Forth works close to the hardware but gives you ways to make your own simple language so in short order, you are using your own high level functions to get the job done. This can make it faster to get a program ready and out the door in Forth versus doing it all in Assembler. This tutorial illustrates how Forth manages memory, starting with nothing more than the raw memory addresses, just like you would see in Assembler programming. The difference is that with a few quick definitions, you create a simple memory management system. Using that memory management system you can build named variables, constants buffers and arrays. To begin we have to assume we already have a Forth compiler somewhere that lets us add new routines, or as Forth calls them WORDs to the Forth Dictionary. The dictionary is actually just the memory space that we will be managing in this demonstration. The Forth compiler is nothing more than the ‘:’ and ‘;’ WORDS. To compile a new word that does nothing in Forth you would type: : MY_NEW_WORD ; This would create a word in the Forth dictionary but since there is no other code after the name it does nothing. We also need our compiler to have the Forth word ‘VARIABLE’. With these things in place we can create our simple memory management system. We will create an empty memory location that will hold the next available memory address that we can use. In Forth this is called the dictionary pointer or DP for short we declare it like this: VARIABLE DP \ holds the address of the next free memory location Next we will create a function that returns the address that is held in DP. Forth calls that function ‘HERE’ as in “Where is the next available memory location?” Forth says “HERE”. HERE uses the ‘FETCH’ operator which is the ampersand, ‘@’ in Forth. : HERE ( -- addr) DP @ ; \ fetch the address in DP Another thing that we will need to do is move the “dictionary pointer” by changing the value in DP so Forth creates a function that takes a number from the stack and adds it to DP using the function ‘+!” (Pronounced “plus-store”) Plus-store is very much like ‘+=’ for those familiar with ‘C’. Forth calls this DP altering function ‘ALLOT’ and it is defined like this: : ALLOT ( n --) DP +! ; \ add n to value in variable DP. \ in other words allocate dictionary space (Pretty simple huh) So with these three definitions we have the beginnings of a simple memory manager. We can now say things in our program like: HEX 2000 HERE 20 CMOVE \ move $20 bytes from HEX 2000 to HERE \ Now move DP forward to “allocate” the space we just wrote to: 20 ALLOT By using “20 ALLOT”. HERE is moved past our little 20 byte space so we won’t tromp on it later. So we have allocated 20 bytes of memory for our own use. To make it really practical we should have recorded the address of HERE somewhere because HERE is now pointing to a new address. Getting FancyWe can combine HERE and ALLOT and the STORE word ‘!’ in Forth to make an operator that ‘compiles” a number into memory. Forth uses the comma ‘,’ for this function and the definition is simple now. : , ( n -- ) HERE ! \ store n at HERE, 2 ALLOT ; \ allocate 2 bytes (for a 16 bit computer) To use the comma we simply type: 99 , 100 , 101 , 4096 , And the numbers go into memory like magic! And of course we have a similar word that is used for bytes or characters called ‘C,’. (pronounced “c-comma”) It works the same way a comma. : C, ( c --) HERE C! 1 ALLOT ; Getting CreativeThere is a Forth word called CREATE that lets us add a new word to the dictionary. Words made with ‘CREATE’ simply return the dictionary memory address after the name. So with our new system making a variable called ‘X’ is as simple as: CREATE X 2 ALLOT In fact using the colon compiler we can take it to a higher level still: : VARIABLE CREATE 0 , ; \ create a name and compile 0 in memory Notice how we used the comma to automate the memory allocation of one unit of memory and initialize it to zero. Now our programs can say: VARIABLE X VARIABLE Y VARIABLE Z And if we type: CREATE ABUFFER 50 ALLOT We have created a named memory space that we can use to hold data. Invoking the name ‘ABUFFER’ in our program will give us the beginning address of that buffer. But why stop there? Use the compiler to make it better: : BUFFER: CREATE ALLOT ; Now it’s all done with one word! 50 BUFFER: ABUFFER We could also ‘CREATE’ an array of numbers in memory like this: CREATE MYNUMS 0 , 11 , 22 , 33 , 44 , 55 , 66 , 77 , 88 , 99 , There are fancier ways to access this array but for this tutorial we will keep it simple. To get at these numbers in the array, we simply need to compute the address of the number we want. Since each number has been given 2 bytes of memory or 1 CELL as Forth calls it, the math is easy. Given an index number, we multiply the index by the memory size, in bytes, of the CPU and add the result to the base address. Let’s do it with the colon compiler: : ]MYNUMS ( index –- addr) CELLS MYNUMS + ; Explanation: · CELLS is a forth function that multiplies a number by the memory address size of the CPU. o As in x2 for a 16 bit CPU, x4 for 32 bit CPUs or x8 for a 64 bit computer. o In this case it will multiply the index value on the stack · MYNUMS returns the base address of the array · ‘+’ simply adds the two numbers together giving us the address we need. We can now fetch and see the value of any number in the array like this: 3 ]MYNUMS @ . \ the ‘.’ word prints the number on the top of the stack The screen capture shows all of this entered at Forth console. ConclusionSo this tutorial gives you examples of how Forth builds itself up from almost nothing to higher levels of programming. This approach is used to create the memory management words that you can use but it’s that the Forth compiler uses these same WORDs internally to compile words and numbers into memory and even to ASSEMBLE op-codes into memory in the Forth Assembler. I know you are asking “Where did the compiler come from in the first place?” Well that’s a bit more black-belt level programming but the principals are the very same. You can start in Assembler or C and make a few primitive routines. Then you use those routines to make higher level WORDs. People have even built Forth compilers in Java and LISP. The method however is always the same. You begin with simple pieces and combine them to build something better and eventually you have built the compiler. It’s not for the faint-of-heart but the cool thing is that it is possible to understand every aspect of the system.
  • Create New...