Jump to content
IGNORED

Assembly on the 99/4A


matthew180

Recommended Posts

Yes. I can see it in TurboForth. TF uses the console's KSCAN routine, and if you put it in a short loop the delay is noticable.

 

Try this in TF:

 

 

: TEST1 ( -- ) PAGE 500 0 DO I . KEY? DROP LOOP ;
: TEST2 ( -- ) PAGE 500 0 DO I . LOOP ;

:mad:

 

But you get the keys. How cool would it be to have something fast but then unreliable ?

Link to comment
Share on other sites

But you get the keys. How cool would it be to have something fast but then unreliable ?

 

I take your point. I did once think about a routine whereby you tell the routine which keys you want to scan, and it checks the appropriate CRU lines; faster than a whole keyboard scan.

 

I wonder about the de-bounce thing as well (the main cause of the delay, IIRC). It might be required for say, the BASIC interpreter, or for INPUT etc, but for games (up, down, left, right, fire) no debounce is usually used at all. I seem to recall a few years ago on the Yahoo list somebody posted a super fast KSCAN routine. Was it yourself? Can't remember now!

Link to comment
Share on other sites

To debounce in software you have to read the input *at least twice* (but more samples is better) and wait some amount of time between reads, then compare the two values. It comes down to waiting between samples, and there are good ways to wait, and not so good ways to wait. The ROM routine in the 99/4A takes the easy way, i.e. not so good way by using a busy loop to waste some time between samples:

 

read

busy loop (loop: DEC Rx, JNE loop)

read again to confirm

 

The good way, although a little more involved is to sample and store the input multiple times over a period of time, which allows you to do other things like run game logic, update the screen, etc. between samples. Anywhere between 20ms and 100ms seems to be typical based on some Google searches. Here is a guy who went all out on debouncing:

 

http://www.ganssle.com/debouncing.htm

 

Basically, waiting between samples does not mean you have to "wait" in a loop like the console does. Something like this frees up the system and still achieves user input 30 times a second (~33.2ms between samples):

 

toggle=0
init sample array (~40 bytes)
init key array (~40 bytes) // stores key up or key down states

main loop:
  if toggle == 0 then
    read inputs and store to sample array
  program logic, uses *key* array (not the sample array) for input values
  wait for vsync
  toggle = not toggle
  if toggle == 0 then
    read inputs and compare to previous values:
      if current input == last input, set key array value to input (key up or down)
end main
This could be used for the entire keyboard using the arrays, or just limit the inputs to the specific keys you care about and use individual variables. The sample rate could be increased to 60 times per second (16.6ms between samples) simply by removing the "toggle" variable. Finally, doing the two samples on either side of the program logic would probably suffice if the logic was running long, i.e. over a frame or two.
Link to comment
Share on other sites

I have a fast asm kscan function in the TI Farmer combination I did (TI MOTIF), so you can check that thread and play with the function included there - it does no debounce.

 

For gaming input, really, I don't see the harm in ignoring the key bounce. You will never get the WRONG input, it will just be on/off/on a couple of times at the transition. It only really matters when you care about discreet presses (ie: if your input is EDGE triggered instead of LEVEL triggered, or if PULSES matter, then you need debounce.)

 

The delay mechanism instead means there is a latency between the user input and the character movement, ignoring keybounce as a problem means it takes about that long before the character moves smoothly. There's little perceptible difference.

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

Hi. I have a couple of questions regarding KSCAN:

 

  • Is it necessary to zero address >8374 to select the entire keyboard at the start of a program? I have not done so in the past with no issues, but I'm trying to acquire good programming habits.
  • is the LIMI 2/LIMI 0 set necessary with each KSCAN call? Aren't interrupts by default enabled on the TI?

 

Thanks!

Link to comment
Share on other sites

You should load >8374 with the key unit that you want.. at least once in the system runtime a value other than 0 needs to be loaded (5 is the usual value). I haven't dug into what '0' means, except that it's documented as "last"... I suspect it means between 3 or 5. If you override the system start up and never set either, KSCAN doesn't work with 0, but in a normal startup it is fine.

 

Interrupts should be disabled since KSCAN changes the CRU and GROM addresses and uses a shared workspace - if it were interrupted it could malfunction. That said, there's no specific LIMI call needed for every KSCAN.

Link to comment
Share on other sites

Interrupts are normally turned off on start of an assembly program, but there is no standard that says they must be (and you have little idea what launched your program). You're safest to do it yourself. The normal state of the machine is interrupts disabled, though, usually they are only enabled briefly, including in all GPL software (such as Editor/Assembler).

 

The KSCAN code is not set up as you might expect if you are starting as a cartridge. The console uses mode 3, not mode 5, for the main menu. If you are looking for a rule, always scan with mode 5 at the beginning of your program, or it may malfunction in some environments. You can leave >8374 alone after the first time you set it unless you run something that changes scratchpad.

Link to comment
Share on other sites

Got it.

I have to say though that learning assembly language on the TI, by itself, is only the tip of the iceberg. There seem to be so many vagaries, exceptions, undocumented features etc... not explicitly covered in books which I have no hope of ever mastering given that my real life is far removed from the programming world, and so I have to bow in admiration to your collective mastery of such arcana. I get great comfort from the fact that no matter how stuck I am on a project, the solution is only a matter of posting on this forum (or the Yahoo ones) and waiting a few hours, or sometimes mere minutes!

So thank you :)

  • Like 1
Link to comment
Share on other sites

Does Assembly have some problem with KSCAN mode 0?

Why is mode 5 prefered over 0?

 

GPL always uses mode 0 and other then XB I have not seen any GPL use mode 5 so again why mode 5?

(Well except games that use mode 1 and 2 for left or right of Keyboard.)

Link to comment
Share on other sites

You should load >8374 with the key unit that you want.. at least once in the system runtime a value other than 0 needs to be loaded (5 is the usual value). I haven't dug into what '0' means, except that it's documented as "last"... I suspect it means between 3 or 5. If you override the system start up and never set either, KSCAN doesn't work with 0, but in a normal startup it is fine.

 

For whatever reason it sticks in my brain that mode 0 (last) simply meant to use whatever scan operation was selected previously until such time as a new scan mode was selected. Whether that works in practice one would need to test and verify.

Link to comment
Share on other sites

I think that's just incorrect. AFAIR 0 selects the *previous* keymode (which seems dumb to me - why would even need such a feature - if a programmer can't arrange his code such it can't remember *by itself* then he should knitting, not coding!).

 

I always use 5 which is the whole keyboard, including upper/lower case, all FCTN codes, all CTRL codes... The whole shootery :grin:

Link to comment
Share on other sites

Exactly.. there is no mode 0. The EA manual makes a lot of assumptions -- including that you are launching after the Editor/Assembler cartridge has run. :)

 

5 is the 99/4A keyboard mode. 3 is the 99/4 compatible keyboard mode (uppercase only, no control or function keys). 4 is Pascal, IIRC, but it's just a different keymap, and 1 and 2 are the left and right split keyboard/joystick scans. 0 is "whatever was set" - I believe it's there for compatibility between the 4A and the 4. If the startup system (E/A cart) sets the mode, and you always use 0, you don't have to worry about it.

 

I'll dig through KSCAN later if nobody else does and verify 0's purpose. :)

Link to comment
Share on other sites

Every cart including XB, RXB, other XB carts and EA cart all use Keyscan mode 0 but do check 5. Default is not mode 5 but 0.

 

I can not find any GPL code in any Cart that defaults to Keyscan Mode 5. Where are you guys coming up with this info on mode 5 is the defualt?

 

XB checks other modes also and there are some uses for Keyscan mode 5 but something people may not know is that 0 works better then 5 as some keys are not seen by Keyscan mode 5.

 

An example is in RXB when Keyscan Mode 5 is set multiple keys pressed on a real TI no longer respond while in Keyscan Mode 0 they did.

 

I posted a video on this using RXB command CALL IO in one that does not work properly in Classic99 but tested on a real TI99/4A works as should.

(up to 4 keys pressed at once detected in this CALL IO program)

 

If you change >8374 from 0 to 5 you have some keys that no longer respond. I do not know why?

 

http://www.youtube.com/watch?v=sHTHDngNMcE&feature=c4-overview&list=UUULwPKqrRFCtNv5_xMuOqQw

Edited by RXB
Link to comment
Share on other sites

The keyboard modes are an invention for KSCAN and are as @Tursi delineated. This is also what is very clearly laid out in the documentation of the CALL KEY subprogram in the TI-99/4A User's Reference Manual. What is not so clear from the documentation is what actually happens in KSCAN. From Thierry Nouspikel's website (http://nouspikel.group.shef.ac.uk/ti99/keyboard.htm#quick%20scan), the "default" keyboard argument – 3 is stored at 83C6h. If you store 1 (left side of keyboard) or 2 (right side of keyboard) at 8374h, nothing happens at 83C6h and 8374h keeps 1 or 2 until it is changed. If, however, you store 3 (TI-99/4 keyboard mode), 4 (Pascal keyboard mode) or 5 (Standard [basic] keyboard mode) at 8374h, 83C6h will change to 0, 1 or 2 for whichever code of those three modes you stored at 8374h AND KSCAN changes 8374h to 0. What this means is that you can always restore KSCAN to using the last-used 3, 4 or 5 mode by storing 0 at 8374h when you're done with left/right modes because KSCAN will get it from 83C6h. You can check this for yourself by storing, say, 3 at 8374h (this can easily be done in fbForth, TI Forth, TurboForth or Classic99's debugger) and watching 8374h and 83C6h to verify what I've said. Furthermore, typing uppercase and lowercase letters now all appear in uppercase because you've put the keyboard in TI-99/4 mode. You will now need to store 5 in 8374h to get back to Standard mode; but, after that, it's only necessary to store 0 at 8374h after using 1 or 2.

 

...lee

  • Like 1
Link to comment
Share on other sites

I saw this today on the Yahoo listserv, and I thought it was fascinating. Essentially the author, James Abbatiello, is able to coerce TI BASIC to run a small assembly language program. Take a look:

 

 

10 REM Escape the BASIC sandbox
20 REM by James Abbatiello <abbeyj@...>
30 REM Displays an animated Hello, World!
40 REM message using machine language.
50 REM Works even without Extended BASIC
60 REM and without the 32K memory expansion.
100 CALL CLEAR
110 PRINT "PRESS"
120 PRINT "1 FOR TI-99/4A"
130 PRINT "2 FOR TI-99/4A (alt)"
140 PRINT "3 FOR TI-99/4A V2.2"
150 CALL KEY(5,C,S)
160 IF (C < 49) + (C > 51) THEN 150
170 PRINT "INITIALIZING ..."
180 V(0)=885
190 V(1)=882
200 V(2)=846
210 L=V(C-49)
220 A$=CHR$(INT(L/256))&CHR$(L-256*INT(L/256))
230 FOR I = 1 TO 126
240 READ C
250 A$=A$&CHR$©
260 NEXT I
270 OPEN #1:A$
800 DATA 128,168,165,172,172,175,140,128
810 DATA 183,175,178,172,164,129,0,0
820 DATA 0,0,0,0,0,0,0,0
830 DATA 0,0,0,0,0,0,0,0
840 DATA 0,0,0,0,0,0,0,74
850 DATA 0,0,0,0,0,0,0,0
860 DATA 0,0,0,0,131,130,2,0
870 DATA 2,244,2,11,64,0,6,0
880 DATA 19,250,208,111,252,0,10,17
890 DATA 23,252,215,224,131,247,2,2
900 DATA 131,76,215,203,5,139,208,114
910 DATA 19,242,219,193,255,254,16,251
920 DATA 0,0,0,0,0,0,0,0
930 DATA 0,0,0,0,0,0,0,0
940 DATA 0,0,0,0,0,0,0,0
950 DATA 0,0,0,0,0,0

The first thing we need is the address of some specific code in GROM but this unfortunately varies in the different GROM versions and I have no way to detect which value is appropriate. I resorted to asking the user. Armed with this, we use it as the first two bytes of a long string. Then we take this string and try to OPEN it.

For the following explanation you might want to follow along in your own copy of TI Intern.

When we try to open this weird filename we'll end up at G>401E (Basic OPEN). From there we'll call G>4BA1 to build a PAB. There's a bug here but not one that I exploit. The code takes the length of the input string and adds >0E to it to compute the length of the entire PAB and then it tries to allocate this much space. Only one byte is used for this length so passing in a long name can cause this calculation to overflow, leading us to allocate less space than needed. We'll end up writing past the end of the allocated space and overwriting something (probably part of the line number table). If we ever want to return to BASIC this would be a problem. The string I use is not long enough to trigger this bug.

Eventually we'll try to actually open this file by calling G>03D9 (DSRLNK). This is expecting a string like "DSK1" or "DSK1.FOO" and it wants to find the first period (if any) and then treat the part to the left as the device name. It will copy this device name into the FAC (at >834A) but only if it fits. First it looks for a period and stores its index (or the length of the entire string if there is no period) in >8355. It then uses "CGE @>8355,>08" to check the length and make sure it is 7 bytes or less. However this doesn't work properly for long strings because it is a signed comparison. So if you have a string of length >80 or longer it will still pass this check. Then the code uses "MOVE @>8354 TO @>834A FROM VDP*>8356" to try to copy the device name into the FAC and conveniently copies the entire string, blindly overwriting whatever was there. This is nice for us because it copies our string unmodified right into the scratchpad.

Let's take a closer look at that string. Here's a hex dump along with the addresses that it will end up being copied to:

Addr Offset
834A 0000 03 ?? 80 A8 A5 AC AC AF
8352 0008 8C 80 B7 AF B2 AC A4 81
835A 0010 00 00 00 00 00 00 00 00
8362 0018 00 00 00 00 00 00 00 00
836A 0020 00 00 00 00 00 00 00 00
8372 0028 00 4A 00 00 00 00 00 00
837A 0030 00 00 00 00 00 00 83 82
8382 0038 02 00 02 F4 02 0B 40 00
838A 0040 06 00 13 FA D0 6F FC 00
8392 0048 0A 11 17 FC D7 E0 83 F7
839A 0050 02 02 83 4C D7 CB 05 8B
83A2 0058 D0 72 13 F2 DB C1 FF FE
83AA 0060 10 FB 00 00 00 00 00 00
83B2 0068 00 00 00 00 00 00 00 00
83BA 0070 00 00 00 00 00 00 00 00
83C2 0078 00 00 00 00 00 00 00 00


0000-0001 is the address in GROM. It can be >0375 (Classic99 and my actual hardware), >0372 (as in TI Intern), or >034E (V2.2).

0002-000F is the string " HELLO, WORLD!" with a leading space and the >60 BASIC screen bias.

0029 is >4A which will overwrite the GPL substack pointer at >8373

0036-0037 contains the address of the entry point of our program (>8382).

0038-0061 contains the actual program:
ENTRY LI R0,756 768 - len(" HELLO, WORLD!")
LI R11,>4000 VDP write address
WSTR DEC R0 at end of screen?
JEQ ENTRY if so, start over at top
WAITVBL MOVB @->400(R15),R1 wait for vblank
SLA R1,1
JNC WAITVBL
MOVB @>83F7,*R15 low byte of VDP write address
LI R2,>834C address of " HELLO, WORLD!"
MOVB R11,*R15 high byte of VDP write address
INC R11
WCHAR MOVB *R2+,R1 load byte of string
JEQ WSTR done when we hit NUL terminator
MOVB R1,@-2(R15) write byte to screen
JMP WCHAR


The rest of the bytes are just filler/padding. We end up overwriting quite a lot of stuff in the scratchpad including R0 through R4 in the interrupt workspace.

After copying the string to the scratchpad the GPL will continue running until it hits RTN or RTNC. The interrupt handler might execute between any of these instructions and update the timer at >8379 or the VDP status byte at >837B so we have to be careful not to rely on having anything in those locations. This bit me at first and I it took a while to figure out why things would work properly one time and not the next.

At this point the RTN will pop a GROM address off the GPL subroutine stack. We've set that pointer to >4A so the address will be fetched from >834A. Hopefully we've managed to arrange for that to point at these GPL instructions:
DST @>8300,@>8380 Fetch address in >8300 (for XML)
XML >F0 and execute
This copies our start address from >8380 to >8300 and then calls XML >F0 which jumps to an address stored in >8300. And we're off and running!
I don't suppose anybody wants to try their hand at making a more interesting payload for this?

--
James Abbatiello
  • Like 9
Link to comment
Share on other sites

  • 2 weeks later...

Hook the isr. Increment a byte on each interrupt. On the 60th count increment your seconds byte and do a cascading check on minutes and hours.

 

Accuracy would only be okay while interrupts are enabled. Disk access etc would stop the clock.

 

Maybe there's a better way with the 9901?

Link to comment
Share on other sites

There are only two "real time" parts in the 99/4A - the VDP and the 9901. You can count VDP interrupts (as long as you can guarantee you will never miss one) to get 1/60th second resolution, or you can use the 9901 timer mode for more control over the duration. You will still need to respond to timer expiries (and without a bit of hackery, you can't get an interrupt - Theirry's page shows Jeff Brown's trick for it), and count them yourself.

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