Tursi Posted August 11, 2017 Share Posted August 11, 2017 (edited) 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 August 11, 2017 by Tursi 2 Quote Link to comment Share on other sites More sharing options...
Tursi Posted August 11, 2017 Share Posted August 11, 2017 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. 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted August 11, 2017 Share Posted August 11, 2017 Thanks Lee How did I ever work with the 99 before I met you? B Quote Link to comment Share on other sites More sharing options...
+TheBF Posted August 11, 2017 Share Posted August 11, 2017 (edited) 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 August 11, 2017 by TheBF Quote Link to comment Share on other sites More sharing options...
+TheBF Posted August 11, 2017 Share Posted August 11, 2017 Timings: : BIGCAPS ( -- ) 4B4 900 200 GVMOVE ; \ 18.4 mS CHARSET 143 mS ( approx. 72 mS for upper case only) So not too shabby. Less than 4 times slower than ALC. B Quote Link to comment Share on other sites More sharing options...
sometimes99er Posted August 19, 2017 Share Posted August 19, 2017 Here's the standard capital character made just a little bigger. 100 call clear::call screen(14)::print "ABCDEFGHIJKLMNOPQRSTUVWXYZ"::for c=65 to 90 110 call charpat(c,c$)::c$=seg$(c$,3,4)&seg$(c$,5,12)::call char(c,c$)::next c 120 input c$::goto 120 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted August 19, 2017 Share Posted August 19, 2017 (edited) 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 ; Edited August 19, 2017 by TheBF Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 7, 2017 Share Posted September 7, 2017 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. 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 8, 2017 Share Posted September 8, 2017 (edited) 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 ; Edited September 8, 2017 by TheBF 3 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted September 8, 2017 Share Posted September 8, 2017 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 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted September 8, 2017 Share Posted September 8, 2017 (edited) 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 September 8, 2017 by TheBF Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.