See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7 and more (coming “soon”).
- 2020-12-13 – Fixed a typo in the last BASIC example. Thanks, Johann K.
- 2020-12-14 – Fixed a typo in a PEEK command. My bad.
Over in the Facebook CoCo group, Jim McClellan posted a tidbit about reading the CoCo keyboard in BASIC:
I self answered a question I had and figured maybe this might be helpful info for other BASIC peeps. The location for whether a key is beng pressed is 65282. It’s either 255 (yes) or 0 (no) By peeking location 135 (135 retains the ASCII value of the key pressed even after releasing the key) and ANDing 65282, you can get a smooth, repeating key.Jim McClellan
10 KBD=PEEK(135) AND PEEK(65282)
20 PRINT KBD
30 GOTO 10
Using standard BASIC commands, we have INKEY$ available to detect a keypress:
10 A$=INKEY$:IF A$="" THEN 10 20 PRINT "YOU PRESSED ";A$
This works great, and I have mentioned it in earlier articles on this site including one showing a way to use it without the variable by using it as a parameter of INSTR inside an ON GOTO.
But I digress.
INKEY$ only gets you the first press. It will not detect if the key is being held down. This limits its usefulness to one-key selections (without needing to press ENTER) and perhaps turn-by-turn games where you have to press a directional arrow over and over to move from position to position.
Most games want to move as long as you are holding down the arrow key, and Jim was sharing a way to do that easily by using PEEK.
What the PEEK is going on?
I wanted to understand what these PEEKs are doing, so I consulted the Color BASIC Unravelled book:
0146 0087 IKEYIM RMB 1 *TV INKEY$ RAM IMAGE
Memory location 135 (&H87 hex) contains the last key handled by INKEY$. Except, that must not be technically correct because it works without calling INKEY$. Looking further into the disassembly, I see that it is actually where the BREAK CHECK stores the key it found:
It checks for BREAK and also the PAUSE key (SHIFT+@ on the CoCo keyboard, which now I see returns as &H13 from the “GET A KEYSTROKE ENTRY” routine). Thus, while INKEY$ uses this location…
…it is not involved with setting that location. (Note to self: Explore what KEYIN does.)
Doing a PEEK(135) will return whatever the last pressed key was, as detected by the looping BREAK check code. By itself, you could use this for a game where the player never stops moving, or only stops when you press a “stop moving” key.
GAME: Never Stop Moving using PEEK
0 REM nsm.bas 10 CLS0 20 PRINT STRING$(32,153); 30 FOR A=0 TO 12 40 PRINT CHR$(153);STRING$(30,32);CHR$(153); 50 SOUND 200-A*2,1:NEXT 60 PRINT STRING$(32,153); 70 L=1024+8*32+16:SC=0 80 POKE L,255:SOUND1,1 90 K=PEEK(135) 100 IF K=94 THEN M=-32 110 IF K=10 THEN M=32 120 IF K=8 THEN M=-1 130 IF K=9 THEN M=1 140 POKE L,96:IF PEEK(L+M)=96 THEN L=L+M ELSE 200 150 PRINT @480,SC;:IF K<>0 THEN SC=SC+1 160 GOTO 80 200 SOUND 1,5 210 PRINT @32*7+11,"GAME OVER!"; 999 GOTO 999
In this pointless game (though I’ve seen worse for smartphones), you begin moving a red block around the screen using the arrow keys. Once you start moving, it will never stop unless you crash into a wall, ending the game. How high of a score can you make?
GAME: Never Stop Moving using INKEY$
It would have been easier (and more portable) to do this without PEEK and use INKEY$. All we would have needed was a check for no key being pressed, and then continue using the last direction used. Something like this would work:
0 REM nsm2.bas 10 CLS0 20 PRINT STRING$(32,153); 30 FOR A=0 TO 12 40 PRINT CHR$(153);STRING$(30,32);CHR$(153); 50 SOUND 200-A*2,1:NEXT 60 PRINT STRING$(32,153); 70 L=1024+8*32+16:SC=0 80 POKE L,255:SOUND1,1 90 K$=INKEY$:IF K$<>"" THEN K=ASC(K$) 100 IF K=94 THEN M=-32 110 IF K=10 THEN M=32 120 IF K=8 THEN M=-1 130 IF K=9 THEN M=1 140 POKE L,96:IF PEEK(L+M)=96 THEN L=L+M ELSE 200 150 PRINT @480,SC;:IF K<>0 THEN SC=SC+1 160 GOTO 80 200 SOUND 1,5 210 PRINT @32*7+11,"GAME OVER!"; 999 GOTO 999
By avoiding CoCo-specific PEEKs, this version may run on an MC-10 or Dragon.
But, using PEEK may be faster since it has less BASIC to churn through than an INKEY$, IF and ASC() conversion. (Note to self: Benchmark PEEK versus INKEY$, assuming I haven’t already done that in an earlier article.)
But what if you wanted the player to move ONLY when the key is being pressed down? INKEY$ cannot do that, and neither can PEEK(135). That’s where the second memory location come in to play:
Decoding the (keyboard) Matrix
The Unraveled book shows that memory location 65282 (&HFF02) is part of a PIA that is hooked to the keyboard column matrix. BASIC uses this to determine which key is being held down:
Two bytes earlier is a similar value for the keyboard rows:
These bits are set or clear based on what key in the keyboard matrix is being held down.
Color Computer Keyboard Array Pin 1 --- @ --- A --- B --- C --- D --- E --- F --- G | | | | | | | | Pin 2 --- H --- I --- J --- K --- L --- M --- N --- O | | | | | | | | Pin 3 nc | | | | | | | | | | | | | | | | Pin 4 --- P --- Q --- R --- S --- T --- U --- V --- W | | | | | | | | Pin 5 --- X --- Y --- Z -- UP -- DWN - LFT - RGT - SPACE | | | | | | | | Pin 6 --- 0 -- 1! -- 2" -- 3# -- 4$ -- 5% -- 6& -- 7' | | | | | | | | Pin 7 -- 8( -- 9) -- :* -- ;+ -- ,< -- -= -- .> -- /? | | | | | | | | Pin 8 -- ENT - CLR - BRK - ALT - CTL - F1 -- F2 - SHIFT | | | | | | | | Pin 9 ----- | | | | | | | | | | | | | | Pin 10 ---------- | | | | | | | | | | | | Pin 11 ---------------- | | | | | | | | | | Pin 12 ---------------------- | | | | | | | | Pin 13 ---------------------------- | | | | | | Pin 14 ---------------------------------- | | | | Pin 15 ---------------------------------------- | | Pin 16 ----------------------------------------------
But, from my testing, just PEEKing these I/O values does not work, at least not on Color BASIC 1.1. (Keyboard scanning changed a bit in later version of the Color BASIC ROMs.) I am unfamiliar with using the PIA so there may be some other things that have to be done to set it up before you can read it. Still, just PEEKing doesn’t do it for me.
10 PRINT PEEK(65282):GOTO 10
I get 255s over and over, though sometimes it looks like it might be blipping to a different value. To catch the changes, I tried this:
10 P=PEEK(65282) 20 IF P<>LP THEN PRINT P:LP=P 30 GOTO 10
Jim says it is 0 if nothing is pressed, or 255 if any key is being held down. From looking at the matrix, even if this did work, it seems like it would only be 0 if nothing was held down in any column, and 255 if a key was held down in each column. Can this be correct?
So I ask the audience: Why does this work for Jim, but not in the Xroar emulator I am using?
Comments are apprecaited!
To be continued…
I think it would only work on Color Basic 1.2 or 1.3. The reason for that is Color Basic 1.2 introduced an optimization where it only does the full matrix scan if no keys (or joystick buttons!) are pressed. It does this by writing a 0 to FF02 to “strobe” all columns. That means there will be at least one 0 bit in FF00 representing a key pressed if anything is down at all. If it detects nothing, it just exits, leaving the column strobe set to 0.
Prior to Color Basic 1.2, the matrix scan was always run. In that case, if no keys are down, it will have strobed all columns in sequence (by writing a zero bit to successive bits of FF02). This zero bit is shifted over by a ROL instruction so it eventually shifts off into carry and the final result in FF02 is FF. Eventually, most versions will eventually strobe no columns by writing FF to FF02 which is used to detect joystick buttons. The debounce check also puts a nonzero, non FF value in FF02 corresponding with the column where a key was detected. Depending on the Color Basic version, the final results of KEYIN and FF02 are (assuming I read the code correctly):
1.0: FF if no key jor joystick button, non-FF if key down (or debounce fails)
1.1: FF if no key or joystick button, non-FF if key down (or debounce fails)
1.2: 0 if no key or joystick button, FF if key or joystick button, not 0 or FF if debounce fails
1.3: same as 1.2; no modifications to KEYIN
Coco3: always FF (the check for a key optimization is removed) except when debounce fails
Basically, this trick only works on Color Basic 1.2 and 1.3. And even then, it isn’t actually reliable since you won’t get FF if debounce fails. Instead, you’ll potentially get a value with a single zero bit which will corrupt your key code. The odds of the timing being just right for that to happen are really small. However, it is possible.
So my considered opinion based on the variances between ROM versions is that this trick must not be used.
As a side note: aside from the system initialization code, KEYIN has the most significant changes between Color Basic versions.
Perfect! Thanks. I don’t know if my line got edited out before posting, but I had a comment about 1.2 having changes in keyboard reading. Since I didn’t really know what they were, other than to speed it up, I may have deleted that.
I have a routine from Jim Gerrie, I think, which I’ll have to share in part 2, allong with your explanation, if that’s okay.
Is it possible to turn off interrupts, and if so will the computer still work (i.e. not breaking stuff like dram refresh and whatnot)?
If so, you could poke and peek the I/O registers directly.
Btw the hardware is identical to the Commodore 8-bit computers except that the keyboard matrix is different and Commodore did use their own I/O chips.
C= used a matrix too? Same limitations with detecting certain keys at the same time?
I think most computers used a matrix starting from when keyboards were really an integrated part of the computer up to when microcontrollers became cheap enough to have some kind of serial interface (like for example a PC).
Apple II is an oddity as it were produced well past mid 80’s but as it had roods in the Apple 1 it used a keyboard with a parallell interace producing ascii codes, which seems to have been common before integral keyboards became a thing in microcomputers.
The software were more or less the same on all those computers, i.e. a periodic interrupt doing the matrix scanning and the result were stored somewhere.
IIRC on the VIC 20 and C64 you can PEEK(197) to find which key is pressed, if any. IIRC that location goes back to 0 when no key is pressed. Some other address can be used to tell which if any qualifiers (Commodore and Shift) are pressed.
However I think most hobbyists used the joystick ports when they were writing simple games in Basic.
I wrote only one VIC program with a joystick, because I didn’t own one (I borrowed it from my Uncle’s Atari). I will be writing a new series soon, and hope to create code for CoCo and VIC. I have several parts started, with the CoCo stuff figured out (which is what this series here is leading to). But I’ll need to figure out the same on a VIC. I suppose VIC uses WASD for keyboard directions, most commonly? Can you detect diagonals? Like UP+LEFT (W+A)?
There seems to be a typo: (near the end of article)
should be probably re-written to
Pingback: Benchmarking the CoCo keyboard – part 2 | Sub-Etha Software