Jump to content

TheBF

+AtariAge Subscriber
  • Posts

    4,470
  • Joined

  • Last visited

Posts posted by TheBF

  1. @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  
    ;
    
    
    

     

     

    • Like 3
  2. 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. 

     

    • Like 1
  3. 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.

     

     

     

    • Like 3
  4. 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
    ;
    

     

    • Like 2
  5. 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?

  6. 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. 

  7. 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?

  8. 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
    

     

     

    • Like 2
    • Thanks 2
  9. 6 hours ago, Tursi said:

     

    Attached is an assembly version of your code, excluding the ACCEPT statement. I commented as much as I could.

     

      Reveal hidden contents

     

     

    * ###################
    * #                 #
    * #  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.

    • Like 1
  10. 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.
     

    TurboForth.jpg

    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.

     

    • Like 2
    • Haha 1
  11. 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. 

    • Like 2
  12. 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
    

     

     

    • Like 2
  13. 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. :)

     

    • Thanks 1
  14. 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.

    • Like 1
  15. 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
    

     

    • Like 1
  16. 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. 

     

    • Like 2
  17. 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.

    • Haha 4
×
×
  • Create New...