Jump to content
IGNORED

keypads and batari basic


toiletunes

Recommended Posts

One of my ideas was to use the paddle variables for the keyboard. However, since they are shared with missile0, this would require the kernel_option no_blank_lines to be set. I think I like the idea of dimming the variable better.

The nice thing about making the user "dim" a variable is that it lets you pick which variable you want to use-- one of batari Basic's 26 "user variables," or one of batari Basic's "system variables," or even a byte of Superchip RAM! :)

 

The way I would have used vblank was to spread the reading of the controller out over multiple vblanks. Set the output register in one and read it in the next. The drawscreen in between would give you plenty of microseconds. This would require two variables, one for a counter and one for the results. The results variable would just stay 0 until the keypad read was done.

That would take a minimum of 4 frames to read a keypad. I would prefer to read a keypad (or both keypads) in 1 frame. And I've already worked out (in my head, but not yet on paper) how to read the keypad(s) during the vblank and still let the users do other things during the vblank if they want to (although most batari Basic programmers do all of their game tasks during the overscan).

 

Another possible issue is that this code forces both controllers to be a keypad, whereas some people would like to use a keypad plus a joystick.

No, it doesn't-- or rather, it won't when I'm done with it! :D I'm definitely interested in using both a keypad and a joystick, and I'd like to be able to use either controller in either controller port. So I'm going to design my "keypads.asm" include file to compile in different ways depending on the options you want to use. But these won't be kernel options-- at least, not yet, although if batari incorporates my "keypads.asm" include file in a future version of batari Basic, he might want to add some kernel options for it. Instead, what I plan to do is let the user "dim" three possible variables-- "keypads" if both keypads will be used, or "left_keypad" if only one keypad will be used in the left controller port, or "right_keypad" for one keypad in the right controller port. The "keypads.asm" file will compile in different ways depending on whether it should read both keypads, or only one keypad, and which one (left or right) it should read. This will also affect how the "get_keypad" function compiles.

 

The vblank routine will also compile differently depending on whether the users have specified their own "mini-vblank" routines, which will be called something like "vblank_1" through "vblank_4." Of course, each of these "mini-vblank" routines will be limited in how many cycles they can run for, but that will still be better than not letting the users perform anything at all during the vblank. ;)

 

Once I've coded everything and tested it out, I'll document it thoroughly before I post it, with some examples showing how to use all of the options-- including the ability to specify which value should be returned for each key when the "get_keypad" function is called.

 

Michael

Link to comment
Share on other sites

That all sounds good. How are you going to handle the 400 usec wait? One of the timers. perhaps?

Yes, I'm going to use the timer. That will definitely be more ROM-efficient than using over 230 NOPs not once, not twice, but four times during the keypad-reading routine. :)

 

Michael

Link to comment
Share on other sites

One of? Isn't there only 1 timer that can be set to count down at one of four rates?

 

I suspect you could do something like this:

 

 lda $0284; read the timer
 sec
 sbc #$08 (477 cycles / 64 for TIM64T)
 sta tempram
 jsr vblank_1
wait1
 lda $0284
 cmp tempram
 bcs wait1
; read keyboard row 1here
 lda $0284; read the timer
 sec
 sbc #$08 (477 cycles / 64 for TIM64T)
 sta tempram
 jsr vblank_2
wait2
 lda $0284
 cmp tempram
 bcs wait2
; read keyboard row 2 here
...

Link to comment
Share on other sites

I suspect you could do something like this:

 

 lda $0284; read the timer
 sec
 sbc #$08 (477 cycles / 64 for TIM64T)
 sta tempram
 jsr vblank_1
wait1
 lda $0284
 cmp tempram
 bcs wait1
; read keyboard row 1here
 lda $0284; read the timer
 sec
 sbc #$08 (477 cycles / 64 for TIM64T)
 sta tempram
 jsr vblank_2
wait2
 lda $0284
 cmp tempram
 bcs wait2
; read keyboard row 2 here
...

That's an interesting idea, and I may use it; but I was planning to set TIM8T to 60 for each wait, and then set it again after the routine to finish up the vblank wait.

 

I've been wondering about the precision of the 400 usec specification, because I know that a lot of documents give rounded-off numbers (e.g., for hertz values), and 400 looks suspiciously rounded-off to me. In my original routine I was using "sleep 476," which worked okay on the Stella emulator, but in my include file I'm using "sleep 478"-- which will effectively become "sleep 480" when I replace it by setting TIM8T to 60. If 400 usec was actually rounded up from something like 384 usec, or 396 usec, etc., then it might be nice to "tighten up" the timing by using the smallest possible wait that will be sufficiently long enough.

 

On the other hand, since I'm going to allow up to four (or maybe five) user-defined mini-vblanks to be performed during the routine's wait periods (with the possible fifth mini-vblank coming after the routine has finished), then it might be better to *increase* the waits so that the four mini-vblanks can be as long as possible, and time the whole thing to fill up as much of the vblank as possible (but still allowing time for anything that batari Basic does during vblank).

 

Michael

Link to comment
Share on other sites

On the other hand, since I'm going to allow up to four (or maybe five) user-defined mini-vblanks to be performed during the routine's wait periods (with the possible fifth mini-vblank coming after the routine has finished), then it might be better to *increase* the waits so that the four mini-vblanks can be as long as possible, and time the whole thing to fill up as much of the vblank as possible (but still allowing time for anything that batari Basic does during vblank).

 

I think bB allows you to specify a routine for overscan as well as one for vBlank. I'd suggest that on one frame you read row 0 of both controllers during overscan and set the outputs for row 1, then read row 1 during vblank and set the outputs for row 2. Next frame, read row 2 during overscan and set up row 3, then read row 3 during vblank and set up row 0.

 

That would read both controllers 30x/second, which should suffice for most purposes.

Link to comment
Share on other sites

That would read both controllers 30x/second, which should suffice for most purposes.

 

BTW, in assembly code I'd probably handle the reading as something like this (assumes you've previously written "0" to SWCHA) and at some point wrote $11 to SWACNT. Same key poll is used for overscan or vblank. The label "keys" must point to a 3 byte area of RAM.

 

key_poll:
 ldx #2
 lda #128
lp:
 cmp INPT0,x
 rol keys,x
 cmp INPT3,x
 rol keys,x
 dex
 bpl lp
 asl SWACNT
 bcc key_not_done
 lda #$11
 sta SWACNT
 rts

 

The routine will return with carry set 1/4 of the time. When it does, the 24 keypad buttons will each be in a different bit in one of the "keys" bytes. Note that they will not be in a terribly nice sequence; they could be sequenced more nicely at the expense of more code.

Link to comment
Share on other sites

I think bB allows you to specify a routine for overscan as well as one for vBlank.

Actually, anything you code before or after the "drawscreen" command gets performed during the overscan by default, so the entire bB program is essentially a set of overscan routines. :) The ability to perform code during vblank, on the other hand, is a relatively new addition to batari Basic (it was added in version 0.99a, or maybe 0.99b).

 

I'd suggest that on one frame you read row 0 of both controllers during overscan and set the outputs for row 1, then read row 1 during vblank and set the outputs for row 2. Next frame, read row 2 during overscan and set up row 3, then read row 3 during vblank and set up row 0.

 

That would read both controllers 30x/second, which should suffice for most purposes.

I'm dead-set on reading both keypads (or one keypad, if only one is being used) during a single frame, because I think the typical batari Basic programmer will expect that to be the way it works. A lot of batari Basic programmers would probably get frustrated if they had to design their game loops around alternating frames. :)

 

Michael

Link to comment
Share on other sites

BTW, in assembly code I'd probably handle the reading as something like this (assumes you've previously written "0" to SWCHA) and at some point wrote $11 to SWACNT. Same key poll is used for overscan or vblank. The label "keys" must point to a 3 byte area of RAM.

 

key_poll:
 ldx #2
 lda #128
lp:
 cmp INPT0,x
 rol keys,x
 cmp INPT3,x
 rol keys,x
 dex
 bpl lp
 asl SWACNT
 bcc key_not_done
 lda #$11
 sta SWACNT
 rts

My first attempt at reading the keypads actually used "ROL INPTx" followed by "ROL keypad," but it had other problems due to my misunderstanding about (1) how to set the bits of SWCHA (I used 1 where I should have used 0, and vice versa), and (2) how to read the right keypad (I thought it used INPT0, INPT1, and INPT4, like the left controller, because the "Stella Programmer's Guide" didn't say anything about INPT2, INPT3, and INPT5).

 

When I rewrote the routine to correct those two misunderstandings, I opted for using LDA# and LDY# to indicate that a particular key had been pressed, because (1) it was faster, and (2) it solved the issue of what to do if two keys were pressed at the same time on the same controller (although I suppose it might be interesting to allow for multiple key presses, as long as the program is designed to handle the different combinations of key presses).

 

Michael

Link to comment
Share on other sites

When I rewrote the routine to correct those two misunderstandings, I opted for using LDA# and LDY# to indicate that a particular key had been pressed, because (1) it was faster, and (2) it solved the issue of what to do if two keys were pressed at the same time on the same controller (although I suppose it might be interesting to allow for multiple key presses, as long as the program is designed to handle the different combinations of key presses).

 

There are certainly advantages and disadvantages to producing a bitmap of pressed keys. Any combination of keys pressed, up to two on each keypad, may be uniquely read using the approach I illustrated. If the game can make use of this, it could be a nice capability to have. But if the game doesn't require it, simply setting a "key number" may be more useful.

 

If you call the routine I indicated 4x/frame instead of 2x, you'll get one keyscan per frame. Perhaps it could be invoked immediately before and after VSYNC, and before and after the sprite-positioning lines.

Link to comment
Share on other sites

There are certainly advantages and disadvantages to producing a bitmap of pressed keys. Any combination of keys pressed, up to two on each keypad, may be uniquely read using the approach I illustrated. If the game can make use of this, it could be a nice capability to have. But if the game doesn't require it, simply setting a "key number" may be more useful.

I think it might be possible to read and store all keys pressed during one frame (or vblank)-- if not for both keypads, then at least for one. But that would require 2 bytes (12 bits) for 1 keypad, or 3 bytes (24 bits) for 2 keypads. Unless only one key per row can be detected by the 2600, in which case it would take only 1 byte (8 bits) per keypad-- each row would be stored as 2 bits, with 00 meaning that no key is pressed in that row, and then 01, 10, or 11 meaning that the 1st, 2nd, or 3rd key in that row was pressed. Allowing combinatons of key presses to be detected would allow such things as using one or more keys as "shift," "control," or "alt," thereby multiplying the number of meanings that could be attached to each key. :)

 

If there isn't enough time to read both keypads that way during one vblank, then I'd rather read one entire keypad per frame, rather than reading two rows of both keypads in one frame, and the other two rows of both keypads in another frame. But I think there should be time to read both in one frame, especially if only one key per row can be detected at a time. I'll have to experiment and see...

 

I also want to experiment with different timings. As I mentioned in another post, that "400 usec" looks awfully rounded-off to me. If it turns out that the minimum wait is actually less than 400 usec, then I'd like to use the minimum wait so the routine runs in as little time as possible.

 

Right now I want to get the one-keypress-per-keypad routine completed as an include file, and then I'll add the multiple-keypresses option to it, so that it compiles differently depending on which option is specified. The routine I'm doing now already compiles differently depending on whether both keypads are being used, or just one (either left or right). The way it works is that if "dim keypads = variable" is used, then both are read, otherwise if "dim left_keypad = variable" is used, then only the left keypad is read, otherwise "dim right_keypad = variable" is assumed and only the right keypad is read. So if you "dim" all three (keypads, left_keypad, and right_keypad), then both will be read, since "keypads" was used. I could modify the "ifconst" statements so that if both "left_keypad" and "right_keypad" are specified, but "keypads" is not specified, then each keypad will be stored in its own byte instead of being combined into a single byte, thereby allowing up to 4 keypresses per keypad (one per row).

 

Michael

Link to comment
Share on other sites

Okay, I'm a little confused. I modified my keypad-reading routine to use different "sleep" amounts, and the keypad seemed to work fine. (To clarify, I'm running the program on my Krokodile Cartridge on my heavy-sixer, using the Star Raiders Video Touch Pad in the left controller port.) Once I got down to about "sleep 195," the central keys on the second and third rows started acting iffy-- but they would work okay again if I moved the cord connecting the keypad to the back of my heavy-sixer. So now I'm not sure if my controller is flaky, or if the unpredictable behavior is caused by the timing, or what.

 

Could someone test this program on their real Atari, with any of the different versions of the keypad controllers, and see if the keypads are read correctly and consistently? This program uses "sleep 196."

 

Michael

keypad_test.bas.bin

Link to comment
Share on other sites

Okay, I'm a little confused. I modified my keypad-reading routine to use different "sleep" amounts, and the keypad seemed to work fine. (To clarify, I'm running the program on my Krokodile Cartridge on my heavy-sixer, using the Star Raiders Video Touch Pad in the left controller port.) Once I got down to about "sleep 195," the central keys on the second and third rows started acting iffy-- but they would work okay again if I moved the cord connecting the keypad to the back of my heavy-sixer. So now I'm not sure if my controller is flaky, or if the unpredictable behavior is caused by the timing, or what.

 

Could someone test this program on their real Atari, with any of the different versions of the keypad controllers, and see if the keypads are read correctly and consistently? This program uses "sleep 196."

 

Michael

I'd be apprehensive to assume that 196 would be adequate on all systems if 195 was flaky. Components like resistors and capacitors are not manufactured to a precise specification - they have tolerances. Without knowing what those tolerances are, we should assume 20%. Also, some 2600's have 1.8k series resistors in the circuit that will change the cap charge/discharge times by a significant amount on a keyboard controller.

 

Also, moving the cable as you describe might be changing the capacitance in the circuit slightly.

 

It would be prudent to use an RC charge time calculation that assures a voltage within TTL spec given the worst-case scenario of part tolerance.

 

I did a calculation to estimate the number of cycles needed. I made some assumptions:

 

The cap is completely discharged initially (if it's not, the equation below gets more complicated)

Vcc=5v

R=(4.7k+1.8k)*1.2=7.8k

C=0.061uF*1.2=0.0732uF

Typical Vih=2.0v for TTL, and I'll assume the TIA will also detect 2.0v as high.

 

Vih/Vcc=1-e^(-t/(R*C)), solving for t, yields 292 us for the minimum charge time. At 1.19 Mhz, this is about 348 cycles.

 

If my assumptions are wrong or someone knows the actual tolerance of the parts, feel free to correct the above.

Edited by batari
Link to comment
Share on other sites

Okay, I'm a little confused. I modified my keypad-reading routine to use different "sleep" amounts, and the keypad seemed to work fine. (To clarify, I'm running the program on my Krokodile Cartridge on my heavy-sixer, using the Star Raiders Video Touch Pad in the left controller port.) Once I got down to about "sleep 195," the central keys on the second and third rows started acting iffy-- but they would work okay again if I moved the cord connecting the keypad to the back of my heavy-sixer. So now I'm not sure if my controller is flaky, or if the unpredictable behavior is caused by the timing, or what.

 

Could someone test this program on their real Atari, with any of the different versions of the keypad controllers, and see if the keypads are read correctly and consistently? This program uses "sleep 196."

 

Michael

I'd be apprehensive to assume that 196 would be adequate on all systems if 195 was flaky. Components like resistors and capacitors are not manufactured to a precise specification - they have tolerances. Without knowing what those tolerances are, we should assume 20%. Also, some 2600's have 1.8k series resistors in the circuit that will change the cap charge/discharge times by a significant amount on a keyboard controller.

 

Also, moving the cable as you describe might be changing the capacitance in the circuit slightly.

 

It would be prudent to use an RC charge time calculation that assures a voltage within TTL spec given the worst-case scenario of part tolerance.

 

I did a calculation to estimate the number of cycles needed. I made some assumptions:

 

The cap is completely discharged initially (if it's not, the equation below gets more complicated)

Vcc=5v

R=(4.7k+1.8k)*1.2=7.8k

C=0.061uF*1.2=0.0732uF

Typical Vih=2.0v for TTL, and I'll assume the TIA will also detect 2.0v as high.

 

Vih/Vcc=1-e^(-t/(R*C)), solving for t, yields 292 us for the minimum charge time. At 1.19 Mhz, this is about 348 cycles.

 

If my assumptions are wrong or someone knows the actual tolerance of the parts, feel free to correct the above.

348 works for me, but if anyone can provide a more accurate figure based on the actual specs, that would be excellent.

 

Also, if you must read a row at a time, and it's all based on discharging capacitors, does that mean you would be limited to detecting one keypress per row-- that multiple keypresses per row could not be (properly) detected? I didn't get as far as trying to detect and display *all* keys being pressed on a real keypad, because first I wanted to focus on testing the "sleep" length.

 

Michael

Link to comment
Share on other sites

348 works for me, but if anyone can provide a more accurate figure based on the actual specs, that would be excellent.

I'm sure there's something on the Stella list. I just haven't bothered to look. If not, it would be "super" if some "cat" would chime in.

Also, if you must read a row at a time, and it's all based on discharging capacitors, does that mean you would be limited to detecting one keypress per row-- that multiple keypresses per row could not be (properly) detected? I didn't get as far as trying to detect and display *all* keys being pressed on a real keypad, because first I wanted to focus on testing the "sleep" length.

 

Michael

I think it would be possible to detect all keys on a keypad. I recall a post once that said this was impossible, but I can't see why it wouldn't work.

Link to comment
Share on other sites

Could someone test this program on their real Atari, with any of the different versions of the keypad controllers, and see if the keypads are read correctly and consistently? This program uses "sleep 196."

 

Well, I've got a 2600 Jr. Rev. A, a pair of CX-50 "keyboard" controllers, and a Krok. Your sleep 196 test semi-works on my gear. The rightmost column of each pad reads just fine. The first and second columns don't read when a single button is pressed. Holding down two buttons in a column will get one of them read. Maybe.

 

Got a sleep 348 version you want tested?

Link to comment
Share on other sites

  • 10 months later...

Sorry to bring back a topic from the depths, but is there any word on whether keypads will ever be available to use with batari BASIC? Because I only have one keypad, and I want to write a game that only requires one (instead of two, like some other games like Basic Programming and Brain Games)

Link to comment
Share on other sites

  • 9 months later...
I also would like to know if this will ever be incorporated into BatariBASIC as the mentioned "Keypad_Input.asm" file as i am planning a game that will use the keypads. Also how do i only check for certain keys and not all of them?

 

2600Hero

Yeah, this is yet another one of those projects that I started and then got sidetracked from. :| I need to finish this so it can be used by whoever wants to try it out. I'll have to review everything I was doing/planning, but I think it could incorporate some kind of boolean functions similar to what you have with the joysticks-- although I'm not sure what to call them, since names like "keypad0key0" sounds awkward.

 

Michael

Link to comment
Share on other sites

  • 6 months later...
  • 2 years later...

Okay, I finally got my keypad controller routine to work. I'm sure it could be done a lot better, and I intend to optimize it before putting it into a routine that could be included in a batari Basic program-- e.g., by adding "include keypads.asm" and "dim keypads = a" (or some other user variable) to the program. One way it can be optimized is by replacing the "sleep" statements with less ROM-hungry methods of wasting 476 cycles.

 

Right now, the routine reads both keypad controllers during the vblank period-- so that rules out any possibility of using the vblank period for other things. And even if someone wanted to customize the routine so they can squeeze other tasks into the four lengthy "sleep" periods, the routine uses the accumulator and the Y register for storing the left and right key presses, so any other tasks that are squeezed into the "sleep" periods would be able to use only the X register-- unless the routine were rewritten to use X and Y, such that only the accumulator could be used for other tasks. Anyway, the key presses that are stored in A and Y get combined at the end of the routine, and are stored in the variable "keypads" (which must be set to one of the user variables using a "dim" statement). When the routine ends, the low nybble of "keypads" will contain the key pressed on the left keypad, and the high nybble will contain the key pressed on the right keypad, as follows:

 

$00 = nothing pressed

$01_$02_$03__________$10_$20_$30
$04_$05_$06__________$40_$50_$60
$07_$08_$09__________$70_$80_$90
$0A_$0B_$0C__________$A0_$B0_$C0

If two keys are pressed on the same controller, the higher-valued key will take priority-- e.g., if 1 and 7 are pressed at the same time, then 7 will override 1. However, a key pressed on one keypad will *not* override a key pressed on the other keypad-- e.g., if 1 is pressed on the left keypad, and 7 is pressed on the right keypad, then both key presses will be returned.

 

When I optimize the routine and put it into an includes file, I'll also add a function that can be called to more easily identify the key that was pressed on a given keypad, so the includes file will contain both the vblank routine and the function.

 

The "keypad_test.bas" program displays two 3-by-4 grids of squares that represent the keys on the left and right keypads. As you press a key on either keypad controller, the appropriate square will light up to show which key you're pressing.

 

Don't forget that when you run the program in an emulator, you need to tell the emulator that the program uses the keypad controllers in the left and right ports. In the Stella emulator, the keys on the left and right keypads are controlled (or emulated) by the following keys on the keyboard:

 

1_2_3__________8_9_0
Q_W_E__________I_O_P
A_S_D__________K_L_;
Z_X_C__________,_._/

I want to extend sincere gratitude to the following people:

 

-- tolietunes, for asking the question that prompted me to finally try this;

-- CurtisP, for suggesting that the keypads be read during vblank;

-- supercat, for pointing out that both keypads can be read at the same time;

-- SpiceWare, for providing a working example of actual code; and

-- batari, for suggesting the idea of a function (which I haven't done yet).

 

And to answer atari2600land's question-- yes, I think the kids' controllers are identical to the keypad controllers as far as functionality and programming are concerned.

 

If anyone can see ways of improving the vblank routine, *please* post them, so I can modify the routine before posting it as a "keypads.asm" includes file.

 

Michael

 

post-7456-1177653287_thumb.png

 

post-7456-1177653319_thumb.png

I used some of this code in my R.O.B. control program today, it worked fine. Thanks for sharing.

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