Jump to content
IGNORED

Large Capital Letters in BASIC


Casey

Recommended Posts

The jump tables are fixed in all known revisions -- for the older bank switched copy cartridges I wrote a generic loader to load the character set from assembly, and it worked on 99/4, 99/4A, and 99/4A v2.2.

 

Code is attached here. Also includes the subroutines used for setting address, copying bytes, etc.

 

The main trick is in the "GPLVDP" function which parses the jump table and the first instruction of the GPL subroutine to determine the address in GROM of the character set. It does both uppercase and lowercase (small capitals) sets.

 

 

 

* quicky test function - press space to toggle between four modes:
* 1. call LOADCHAR to load uppercase and lowercase (works on 99/4)
* 2. load large capitals to main set
* 3. load small capitals to main set
* 4. load lowercase to main set (will show junk on 99/4)
* these refs and defs are not needed by the subroutines called
  REF VSBW,KSCAN
  DEF MAIN

KEY EQU >8375
  
WAITKEY
  CLR R0
  BLWP @KSCAN
  MOVB @KEY,R0
  CI R0,>FF00   * wait for release
  JNE WAITKEY
WAITKEY2
  BLWP @KSCAN 
  MOVB @KEY,R0
  CI R0,>2000   * wait for space
  JNE WAITKEY2
  B *R11
  
* Call with R0 set to the GROM vector  
COPYSET
  LI R2,>0040  * how many chars
COPYSET2
  MOV R11,R8   * save return address
  LI R1,>4900  * dest in VDP - must OR with >4000 for write
  BL @GPLVDP
  BL @WAITKEY
  B *R8
  
MAIN
* fill screen with 0-255 over and over
  CLR R0
  LI R1,>0000
MLP
  BLWP @VSBW
  INC R0
  AI R1,>0100
  CI R0,768
  JNE MLP
  
FIRSTD  
* first display
  BL @LOADCHAR
  BL @WAITKEY
  
* second - large capitals
  LI R0,>0016  * GPL vector address
  BL @COPYSET
  
* third - small capitals
  LI R0,>0018  * GPL vector address
  BL @COPYSET
  
* fourth - lowercase
  LI R0,>004A  * GPL vector address (not valid on 99/4, will get garbage)
  LI R2,>071F  * need to specify the size for lowercase, it doesn't start with a space
  BL @COPYSET2

  JMP FIRSTD  

*******************************************
* Load upper and lowercase character sets *
*******************************************
* this function is meant to be called as "BL @LOADCHAR"
* it destroys R0-R3,R9-R11

LOADCHAR
 MOV R11,R9   * Save our return spot

* +++ 99/4 support begin +++
* If we are on a 99/4, there are no lowercase characters
* in GROM, so just load uppercase twice.

 CLR R0
 BL @GPLSET
 BL @GETGPL   * read GROM >0000
 CI R0,>AA01  * 99/4 is AA01, all versions of 99/4A seem to be AA02 (even 2.2!)
 JNE IS4A     * note we also assume unknown is 99/4A just to be safe

* make a copy of the capitals for the 99/4 to 'support' lowercase
* this will be partially overwritten by the main set, but it works!

 LI R0,>0018  * GPL vector address
 LI R1,>4A00  * dest in VDP - must OR with >4000 for write
 LI R2,>0040  * how many chars
 BL @GPLVDP   * this function goes somewhere later in your ROM
 JMP MNSET

* +++ 99/4 support end +++

IS4A
* 'lowercase' letters
 LI R0,>004A  * GPL vector address (not available for 99/4)
 LI R1,>4B00  * dest in VDP - must OR with >4000 for write
 LI R2,>071F  * MSB: 7 bytes per char, LSB: how many chars
 BL @GPLVDP   * this function goes somewhere later in your ROM

* main set
MNSET
 LI R0,>0018  * GPL vector address
 LI R1,>4900  * dest in VDP - must OR with >4000 for write
 LI R2,>0040  * how many chars
 BL @GPLVDP   * this function goes somewhere later in your ROM
 B *R9        * RETURN TO CALLER

*****************
* GROM routines *
*****************

* Set GROM address
GPLSET
 MOVB R0,@>9C02
 SWPB R0
 MOVB R0,@>9C02
 SWPB R0
 B *R11

* Get a word from GPL
GETGPL
 MOVB @>9800,R0
 SWPB R0
 MOVB @>9800,R0
 SWPB R0
 B *R11

**************************************************************
* Character set copy function
*
* Copy R2 characters from a GPL copy function vectored at
* R0 to VDP R1. GPL vector must be a B or BR and
* the first actual instruction must be a DEST or a MOVE with an
* immediate operand. This version figures out how many bytes are
* available by counting the zeros in the space character (which
* must be first in the list).
*
* This is a smarter version of the older code used for loader
* carts that can work to load any of the character sets from
* GROM. There are three vectors it is designed to work with:
*
* >0016 - load "large" capital letters (like on the title page)
* >0018 - load "normal" capital letters (like in TI BASIC)
* >004A - load "lowercase" capital letters (like in TI BASIC, not available on 99/4)
*
* Note: I couldn't work out an autodetection, so for lowercase,
* you must set the MSB of R2 to 7. This can be used anytime you
* don't want the function to count.
*
* Any similarly designed vector can be used. Note that we don't
* verify that the vector is correct - you must verify it's suitable.
*
* byte pattern for MOVE instruction
MOVOP DATA >3100
*
GPLVDP
 MOV R11,R10    * save return address
 
* first parse the GPL to find the table address 
 BL @GPLSET     * set GROM address
 BL @GETGPL     * Get branch instruction (not verified!)
 ANDI R0,>1FFF  * mask out instruction part
 MOV R0,R3      * save it (r3 is scratch until we count the bytes)
 BL @GPLSET     * going to read that first instruction
 BL @GETGPL     * get the opcode, we expect BF (DEST) or 31 (MOVE)
 AI R3,3        * skip instruction and destination for DEST
 CB R0,@MOVOP   * is it a MOV?
 JNE NOTMOV
 AI R3,2        * skip two more bytes if it is
NOTMOV
 MOV R3,R0      * prepare to get and set the actual table address
 BL @GPLSET     * set new GROM address
 BL @GETGPL     * get actual address of the table
 BL @GPLSET     * and set that GROM address - GROM is now ready!
 
 SWPB R1        * assume R1 is already prepared for write to save space
 MOVB R1,@>8C02
 SWPB R1
 MOVB R1,@>8C02 * VDP is now ready!

* determine the size of the character set by measuring the space character,
* which must be first. If the MSB of R2 is set (necessary for lowercase),
* then we use that value instead of testing.
 MOV R2,R3
 ANDI R2,>00FF
 ANDI R3,>FF00
 SWPB R3
 JNE GOTSIZ

* now count how many 0 bytes there are - we assume the space is all
* empty and this will tell us 6 or 7 or 8 bytes. 
* you could have a smaller character set this way too. But not bigger,
* if it's more than 8 then we will crash or malfunction below.
 SETO R3        * start counting at -1
CNT0LP
 INC R3         * count up
 MOVB @>9800,R1 * get a byte
 JEQ CNT0LP     * keep going if it's zero
* R3 now contains the right number of bytes

* reset the GROM address, then we're done with R0
GOTSIZ
 BL @GPLSET  
 CLR R0         * convenient zero byte for the padding

LP8
* pad the top of the character in VDP 
 MOV R3,R1      * get the byte count
 SLA R1,2       * multiply by 4 so we can use it in the jump table below
 B @PADTAB(R1) 
PADTAB
 MOVB R0,@>8C00 * Padding zeros
 MOVB R0,@>8C00 * Padding zeros
 MOVB R0,@>8C00 * Padding zeros
 MOVB R0,@>8C00 * Padding zeros
 MOVB R0,@>8C00 * Padding zeros
 MOVB R0,@>8C00 * Padding zeros
 MOVB R0,@>8C00 * Padding zeros
 MOVB R0,@>8C00 * Padding zeros

* copy the actual data bytes
 MOV R3,R0      * prepare to count down
LP9
 MOVB @>9800,@>8C00  * copy a byte (both sides autoincrement)
 DEC R0
 JNE LP9

 DEC R2         * next character
 JNE LP8

 B *R10

 END
 

 

 

(Edit: new almost-universal code. I couldn't come up with a way to autodetect the lowercase set's size, but everything else is now automatically determined through inspection instead of assumed. ;) Sample app included).

Edited by Tursi
  • Like 2
Link to comment
Share on other sites

Well, the GPL jump table starts at >0010 in GROM 0. The “Load Standard Character Set” routine vector is at >0016. All of these vectors are “BR @address” GPL instructions, which will be >4000 + GROM address. If you extract that address and add 5 to it, the next 2 bytes will be the address of the Standard Character Set table. From Heiner Martin’s book:

After reading that and reading my code, I was curious why it's different... my loader only works if the first instruction is a DEST, as the small capitals and lowercase functions both are. I didn't realize that the /large/ capitals would have a different codeset...

 

So I'll go back and patch my code to support the first instruction being a MOVE too, cause now I want it to be complete. ;)

  • Like 1
Link to comment
Share on other sites

The jump tables are fixed in all known revisions -- for the older bank switched copy cartridges I wrote a generic loader to load the character set from assembly, and it worked on 99/4, 99/4A, and 99/4A v2.2.

 

Code is attached here. Also includes the subroutines used for setting address, copying bytes, etc.

 

The main trick is in the "GPLVDP" function which parses the jump table and the first instruction of the GPL subroutine to determine the address in GROM of the character set. It does both uppercase and lowercase (small capitals) sets.

 

 

 

* this function is meant to be called as "BL @GOGO"
* it destroys all registers.

****************************
* Load lower case charsets *
****************************
* Note, if you still need space, you can remove support for the
* 99/4 by deleting the code marked between * +++ 99/4 support +++ begin/end
* blocks

GOGO
 MOV R11,R9   * Save our return spot

* +++ 99/4 support begin +++

* load R3 with 6 for 99/4, or 7 for 99/4A
 CLR R0
 BL @GPLSET
 BL @GETGPL   * read GROM >0000
 LI R3,7
 CI R0,>AA01  * 99/4 is AA01, all versions of 99/4A seem to be AA02 (even 2.2!)
 JNE IS4A     * note we also assume unknown is 99/4A just to be safe
 DEC R3

* make a copy of the capitals for the 99/4 to 'support' lowercase
* this will be partially overwritten by the main set, but it works!

 LI R0,>0018  * GPL vector address
 LI R1,>4A00  * dest in VDP - must OR with >4000 for write
 LI R2,>0040  * how many chars
 BL @GPLVDP   * this function goes somewhere later in your ROM
 JMP MNSET

* +++ 99/4 support end +++
* If you delete the above block, replace with
* LI R3,7
* so that the character size counter is still valid

IS4A
* 'lowercase' letters
 LI R0,>004A  * GPL vector address (not available for 99/4)
 LI R1,>4B00  * dest in VDP - must OR with >4000 for write
 LI R2,>001F  * how many chars
 BL @GPLVDP   * this function goes somewhere later in your ROM

* main set
MNSET
 LI R0,>0018  * GPL vector address
 LI R1,>4900  * dest in VDP - must OR with >4000 for write
 LI R2,>0040  * how many chars
 BL @GPLVDP   * this function goes somewhere later in your ROM
 B *R9        * RETURN TO CALLER

*****************
* GROM routines *
*****************

* Set GROM address
GPLSET
 MOVB R0,@>9C02
 SWPB R0
 MOVB R0,@>9C02
 B *R11

* Get a word from GPL
GETGPL
 MOVB @>9800,R0
 SWPB R0
 MOVB @>9800,R0
 SWPB R0
 B *R11

* Copy R2 characters from a GPL copy function vectored at
* R0 to VDP R1. GPL vector must be a B or BR and
* the first actual instruction must be a DEST with an
* immediate operand. Set R3 to 6 for 99/4 (6 byte characters)
* or 7 for a 99/4A (7 byte characters)
GPLVDP
 MOV R11,R10    * save return address
 BL @GPLSET     * set GROM address
 BL @GETGPL     * Get branch instruction (not verified!)
 ANDI R0,>1FFF  * mask out instruction part
 AI R0,3        * skip instruction and destination
 BL @GPLSET     * set new GROM address
 BL @GETGPL     * get actual address of the table
 BL @GPLSET     * and set that GROM address - GROM is now ready!

 SWPB R1        * assume VDP is already prepared for write to save space
 MOVB R1,@>8C02
 SWPB R1
 MOVB R1,@>8C02 * VDP is now ready!

 CLR R0
LP8
 MOVB R0,@>8C00 * pad the top of the char with a space
 MOV R3,R0      * then copy 7 (or 6) bytes

* +++ 99/4 support begin +++

 CI R3,6        * check for 99/4
 JNE LP9
 MOVB R0,@>8C00 * extra blank line for 99/4

* +++ 99/4 support end +++
* no changes needed if this block removed

LP9
 MOVB @>9800,@>8C00  * copy a byte (both sides autoincrement)
 DEC R0
 JNE LP9

 DEC R2         * next character
 JNE LP8

 B *R10

 

 

Thanks Tursi.

 

I will borrow these ideas for finding the character table ... one day. :)

 

I did make the CHARSET word in high level Forth using the GVMOVE function.

It is not as fast as the BIG chars but not a noticeable delay for most purposes.

HEX
: ]GFONT ( ascii -- grom_adr) BL -  7 * 6B4 + ;  \ GROM array of TI Font data

: CHARSET ( -- )
        [CHAR] ~ 1+ BL                    \ all ASCII chars
        DO  
           I ]GFONT                       \ get GROM address for char I
           I ]PDT                         \ get PDT address for char I
           0 OVER VC!                     \ store 1st zero in VDP
             1+                           \ inc PDT address
             7 GVMOVE                     \ write 7 bytes GROM->VDP
        LOOP ;
        

Edited by TheBF
Link to comment
Share on other sites

  • 2 weeks later...

That's a pretty cool trick. You are the font master.

 

I added these simply routines to my system to access GROM without the Assembly language

: GROM  ( addr -- ) SPLIT 9C02 C! 9C02 C! ;

: GC@+   ( -- char)  9800 C@ ;  \ read & auto-increment address

: GVMOVE  ( grom_addr vdp_addr cnt -- ) \ GROM->VDP move
          ROT GROM
          BOUNDS DO  GC@+  I VC!  LOOP ;

: BIGCAPS  ( -- ) 4B4  20 ]PDT  200 GVMOVE  ;

Which let me write this demo


: TEST  GRAPHICS 
        8 9 AT-XY     ." TEXAS INSTRUMENTS"
       10 11 AT-XY       ." HOME COMPUTER"
        2 16 AT-XY     ." READY-PRESS ANY KEY TO BEGIN"
        4 22 AT-XY (c) ." 1981  TEXAS INSTRUMENTS"

        0 0 AT-XY 0 4 DO I . 1000 MS   -1 +LOOP
        
        0 0 BL 10 HCHAR

        BIGCAPS

        BEGIN KEY? UNTIL
        BYE  ;

post-50750-0-59154100-1503137027.gif

Edited by TheBF
Link to comment
Share on other sites

  • 3 weeks later...

As I am testing out my system I am trying to run other peoples Forth Code.

This BANNER routine uses a data matrix to define big fonts. All I had to do was add my version of the UPPER routine to convert chars

and my BYTES word to compile the data matrix and it worked.

 

 

 

\ BANNER    Wil Baden  2003-02-23  R.I.P.

\  *******************************************************************
\  *                                                                 *
\  *  Wil Baden  2002-08-07                                          *
\  *                                                                 *
\  *  BANNER                                                         *
\  *                                                                 *
\  *     Display short phrase in ####   ###   ####                   *
\  *                             #   #   #   #                       *
\  *                             #   #   #   #                       *
\  *                             ####    #   #                       *
\  *                             #   #   #   #  ##                   *
\  *                             #   #   #   #   #                   *
\  *                             ####   ###   #### letters.          *
\  *                                                                 *
\  *******************************************************************

\ change to the original to use CAMEL99 parlance
: BETWEEN   ( n n n -- ? ) 1+ WITHIN ;
: LOWER?    ( char -- ?)  [CHAR] a [CHAR] z BETWEEN ;

HEX
: UPPER   ( c -- c )    DUP LOWER? IF  05F AND THEN ;

\ compiler addition to compile bytes into memory
: BYTES ( -- )
         BEGIN
            BL WORD COUNT $BUF PLACE
            $BUF C@       \ fetch 1st char (string lenght)
         WHILE            \ while the string len>0
            $BUF ?NUMBER 0= ABORT" BAD#"
            C,            \ compile into next byte of memory
         REPEAT ;

\  BANNER			( str len -- )
\     Display short phrase in BIG letters.
HEX
CREATE Banner-Matrix
BYTES     00 00 00 00 00 00 00 00  20 20 20 20 20 00 20 00
BYTES     50 50 50 00 00 00 00 00  50 50 F8 50 F8 50 50 00
BYTES     20 78 A0 70 28 F0 20 00  C0 C8 10 20 40 98 18 00
BYTES     40 A0 A0 40 A8 90 68 00  30 30 10 20 00 00 00 00
BYTES     20 40 80 80 80 40 20 00  20 10 08 08 08 10 20 00
BYTES     20 A8 70 20 70 A8 20 00  00 20 20 70 20 20 00 00
BYTES     00 00 00 30 30 10 20 00  00 00 00 70 00 00 00 00
BYTES     00 00 00 00 00 30 30 00  00 08 10 20 40 80 00 00

BYTES     70 88 98 A8 C8 88 70 00  20 60 20 20 20 20 70 00
BYTES     70 88 08 30 40 80 F8 00  F8 10 20 30 08 88 70 00
BYTES     10 30 50 90 F8 10 10 00  F8 80 F0 08 08 88 70 00
BYTES     38 40 80 F0 88 88 70 00  F8 08 10 20 40 40 40 00
BYTES     70 88 88 70 88 88 70 00  70 88 88 78 08 10 E0 00
BYTES     00 60 60 00 60 60 00 00  00 60 60 00 60 60 40 00
BYTES     10 20 40 80 40 20 10 00  00 00 F8 00 F8 00 00 00
BYTES     40 20 10 08 10 20 40 00  70 88 10 20 20 00 20 00

BYTES     70 88 A8 B8 B0 80 78 00  20 50 88 88 F8 88 88 00
BYTES     F0 88 88 F0 88 88 F0 00  70 88 80 80 80 88 70 00
BYTES     F0 48 48 48 48 48 F0 00  F8 80 80 F0 80 80 F8 00
BYTES     F8 80 80 F0 80 80 80 00  78 80 80 80 98 88 78 00
BYTES     88 88 88 F8 88 88 88 00  70 20 20 20 20 20 70 00
BYTES     08 08 08 08 08 88 78 00  88 90 A0 C0 A0 90 88 00
BYTES     80 80 80 80 80 80 F8 00  88 D8 A8 A8 88 88 88 00
BYTES     88 88 C8 A8 98 88 88 00  70 88 88 88 88 88 70 00

BYTES     F0 88 88 F0 80 80 80 00  70 88 88 88 A8 90 68 00
BYTES     F0 88 88 F0 A0 90 88 00  70 88 80 70 08 88 70 00
BYTES     F8 20 20 20 20 20 20 00  88 88 88 88 88 88 70 00
BYTES     88 88 88 88 88 50 20 00  88 88 88 A8 A8 D8 88 00
BYTES     88 88 50 20 50 88 88 00  88 88 50 20 20 20 20 00
BYTES     F8 08 10 20 40 80 F8 00  78 40 40 40 40 40 78 00
BYTES     00 80 40 20 10 08 00 00  F0 10 10 10 10 10 F0 00
BYTES     00 00 20 50 88 00 00 00  00 00 00 00 00 00 00 F8

DECIMAL
: BANNER ( str len -- )
    8 0 DO  CR                       ( str len)
        2DUP BOUNDS ?DO              ( . .)
            I C@  UPPER  BL -  0 MAX ( . . char)
            8 *  Banner-Matrix +  J +  C@ ( . . row)
            2 7 DO
                DUP 1 I LSHIFT AND
                    IF  ." #"  ELSE  ."  "  THEN
            -1 +LOOP  DROP           ( . .)
        LOOP                         ( str len)
    LOOP  2DROP ;

HEX
: TEST
      PAGE
      17 7 VWTR
      BEGIN
        S" Camel" BANNER
        S" Forth" BANNER
        S" is"    BANNER
        S" fun!"  BANNER
        S"  " BANNER
      ^C?
      UNTIL ;

\  Thanks to Marcel Hendrix.


 

 

post-50750-0-10738900-1504800793.gif

  • Like 2
Link to comment
Share on other sites

As I looked at this code I realized that the TI-99 already has a bit pattern for all the characters in the VDP RAM.

In CAMEL99 I call the pattern table PDT and you can access the pattern address of any character with ]PDT.

 

So I removed the UPPER case filter and the offset used to start all characters at >20 (space character) and substituted ]PDT.

With this I can get the bit pattern for any ASCII character in the TI-charset.

 

Then I replaced the memory character fetch ( C@ ) with the VDP version ( VC@ ) so that I read the pattern from VDP RAM.

 

After those changes I can print out any TI-99 character in this banner form without using another table of patterns.

 

That saves a lot of space since I only need this code and it works.

: BANNER ( str len -- )
    8 0 DO  CR                       
           2DUP BOUNDS       \ convert str,len to end/start addresses
​           ?DO               \ I is address of each char in string
              I C@ ]PDT J +  VC@    \ read VDP byte PDT[ascii,j]              
​              2 7 DO
                    DUP 1 I LSHIFT AND
                    IF  ." #"  ELSE  ."  "  THEN
              -1 +LOOP  
​              DROP  
           LOOP                       
    LOOP  
​    2DROP ;

post-50750-0-06331100-1504845761.gif

Edited by TheBF
  • Like 3
Link to comment
Share on other sites

As I looked at this code I realized that the TI-99 already has a bit pattern for all the characters in the VDP RAM.

In CAMEL99 I call the pattern table PDT and you can access the pattern address of any character with ]PDT.

 

I just assumed speed was the reason you did not use the PDT. Of course, we all know what “assume” does.

 

...lee

Link to comment
Share on other sites

 

I just assumed speed was the reason you did not use the PDT. Of course, we all know what “assume” does.

 

...lee

 

 

The think you assumed perhaps, that I am as smart as you are. :)

It takes me a little longer to connect the dots or bits as in this case.

 

I was out of this arena for over 20 years doing product marketing and then general management. (be kind)

There are lots of rusty parts in this old head about software and Forth, but I am applying a little oil every day.

 

B

Edited by TheBF
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

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

×   Your previous content has been restored.   Clear editor

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

Loading...
  • Recently Browsing   0 members

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