Keyboard layoutINPUT, GET, KEY$Special keysC and AssemblerThe ROM IRQ handlerThe Keyboard MatrixIRQ HandlerReading one rowHardware limitsWhen people decide to make a game on the Oric, on of the first issue they stumble upon is how to deal with keyboard input.
There are many different ways, some are simple but only allow to check for one key at a time, while some other allow to test for multiple key presses.
Keyboard layoutBefore entering on the actual code, I'd like to bring your attention to a small detail that may have many consequences:
The keyboard layout of the various machines.
Most people these days are using emulators, and as such are used to the standard setup with a separate set of arrow keys on the right of the main block of alphanumerical keys, and the separate sections for function keys and special keys (print screen, scroll, break, etc...).
Then when testing, most people will only use an Oric Atmos, because it is the most common machine these days, but what about the Oric 11?
Yes, we tend to forget that, but the layout between the Oric 1 and Oric Atmos keyboard is different enough to have made some games unplayable, like for example the super famous Zorgon's Revenge, where pretty much everybody miserably fail on the mission that involves the space ship!
Basically on the Orix 1 the "Z" and "Left Arrow" key are exactly on top of each other and perfectly aligned, so the developer thought it would be nice to use them to move UP and DOWN your space ship with the left hand, while using "Space" with the right hand to shoot.
When you look at the Oric 1 layout it makes sense.
When you look at the Oric Atmos layout you see that some gymnastic may be involved.
And when you try to play the game on a PC... Ouch!
The bottom line is:
When you design a control scheme for your game (or tool), please make sure it is usable on people using a different type of keyboard, or even one using AZERTY or QWERTZY instead of QWERTY, that may be the difference between a very enjoyable or a very frustrating program.
Let's go back to the main topic: Accessing the keyboard in your programs.
INPUT, GET, KEY$Let's start by the basics, and let see what the Oric ROM provides.
If you look in the Oric user manual, you will see there are three main commands you can use, here is the actual content of the documentation for each of the three commands:
- BASIC Token: 146
- Format: INPUT v, v$, ...
- INPUT "prompt", v, v$, ...
This allows the computer to receive information from the outside world.
The command stops the execution of a program and the Oric will not continue until the user has INPUT a word/letter or number. There are a number of ways in which the command can be formatted. For example:
10 INPUT N$will stop the program after placing a question mark prompt on the screen.
The user must now key-in the appropriate INPUT and press the RETURN key.
However, it is unlikely that anyone other than the programmer will understand exactly what form of data is required by the program, so obviously some sort of explanatory message is required. This can be achieved in one of two ways:
10 PRINT@13,10;"WHAT IS YOUR NAME"or else:
20 INPUT N$
10 INPUT "WHAT IS YOUR NAME";N$
The only advantage of the first construction is that it enables you to position the message anywhere on the screen (provided you own an Oric V1.1), whereas the second example PRINTS the prompt AT the current cursor position.
Related keywords: GET, KEY$
INPUT is quite a practical command, and is highly recommended for prototyping, but unfortunately the error handling is minimalistic, and if the function does not like what you entered it will simply output a new line starting with ? eventually leading to the entire screen to scroll, breaking whatever carefully crafted presentation you've made.
Another issue is that the INPUT commands stops your program until it has received a valid input, so you could not do a real time adventure game where things happens while you are playing or even simple background animations.
- BASIC Token: 190
Format: GET v$
'Press any key to continue' (or a variation on it), is one of the most common instructions facing users of interactive programs. Well, if the program is written in Oric BASIC it's more than likely that GET (followed by a string variable) is holding up the program until you are ready to go on. It has some similarities with INPUT. GET stops the program and will not allow the Oric to continue until a key is pressed. In the statement:
100 GET A$the character of the first key pressed will now be stored in A$. However, unlike INPUT, GET does not require RETURN, but automatically continues to the next line of the program as soon as a key is pressed. Thus GET A$ will only allow the value of a single character to be stored in A$. GET followed by a numeric variable name can be used to GET a single digit from the keyboard, but will give an error if a non-numeric key is pressed.
As this example program demonstrates, GET is useful in menu-driven programs which only require single key entry:
1 REM *** GET ***GET differs from its sister command, KEY$, in that whilst the former actually stops the program until a key is pressed, the latter scans for input but passes on to the next line whether or not a key is pressed.
5 HIRES:C=RND(1)*6+1:INK C:PAPER 0
10 FOR A=10 TO 50 STEP 10
20 CURSET 50+(A*2),96,3
30 CIRCLE 10+A,2
50 PRINT "PRESS ANY KEY TO RUN AGAIN"
60 GET A$
70 GOTO 5
Related keywords: INPUT, KEY$
Please note that in some situation2 the initialization status of the RAM may cause the Oric ROM to incorrectly think some keys were pressed on boot-up, so a program that would simply do an unchecked GET to start would actually detect a key press despite the user having done nothing3.
- BASIC Token: 241
KEY$ is one of the means by which the Oric receives information from the outside world. Like GET it seeks a keyboard response, but will allow the program to continue whether or not a key is pressed.
Since KEY$ contains the value of whatever key is being pressed, it is valuable in arcade-type games (e.g. allowing the cursor keys to be used to control 'movement' on the screen). As the example below demonstrates, it is also useful when a particular response is required from the user of the program:
1 REM *** KEY$ ***
10 PRINT "USE 'Z' TO MOVE LEFT, 'M' FOR RIGHT & 'S' TO STOP"
40 IF V$="Z" THEN X=X-3:PLOT X+3,10," "
50 IF V$="M" THEN X=X+3:PLOT X-3,10," "
60 IF X<2 THEN X=2
70 IF X>35 THEN X=35
80 PLOT X,10,"<*>"
90 UNTIL V$="S"
100 PRINT"EXAMPLE TERMINATED"
Related keywords: GET, INPUT
So, relatively simple: KEY$ returns whatever key is pressed or an empty string ("") if there is nothing.
Special keysOne important thing to realize, is that none of the three commands allow you to check for the state of the modifier keys4 (CTRL, SHIFT, FUNCT5), which seriously limits your possibilities.
Additionally, the fact you get a lower case or upper case character (or a number instead of a symbol) is defined by the current CAPS status, so if the user decided to press CTRL-T to switch to lower case, the previous program would fail because it would receive "z", "m" and "s" instead of "Z", "M" and "S"!
There is unfortunately no BASIC command that can be used to check the status of all these keys, but you can read some system variables in page 2 to provide you with this additional missing information :)
- $208 (520) Contains the scan code of the currently pressed key
- $2DF (735) Contains the ASCII code + 128 of the last pressed key
- $209 (521) Indicates the status of the SHIFT, CONTROL and FUNCTION keys
- $38 (56) No key pressed
- $A2 (162) CONTROL
- $A0 (160) Not available on the real Oric but mapped to RIGHT CTRL in Euphoric
- $A4 (164) LEFT SHIFT
- $A5 (165) FUNCTION (not available on the Oric 1) / LEFT ALT on PC
- $A6 (166) Not available on the real Oric but mapped to RIGHT ALT in Oricutron and Euphoric
- $A7 (167) RIGHT SHIFT
10 PRINT PEEK(521)Be aware that if you press multiple keys at the same time it is not clear which one will "win", so here is an explanation from Fabrice about what the ROM routines actually do:
20 GOTO 10
- First, in order to handle "Auto-repeat", the key memorized in $0208 is first checked: If it is still pressed, then the keyboard doesn't check anything else and auto-repeats the key.
- The rest of the keyboard will be scanned only when this key is released. In practice that has the opposite behavior most fast typist would expect since usually the next key get pressed while the current one has not yet been released!
The Oric will only detect this second key when you release the first one. You won't notice any problem until you type again a little faster and now have three keys pressed at the same time : of course, you haven't typed them at the same time, just one after the other but when you first key is released, there are two other keys pressed on the keyboard...
The way the state of these new keys is detected is now a matter of luck: depending on the location of these keys in the Oric matrix (cyclic sequential scan), you might be lucky if the second key is scanned before the third key, or unlucky if the third key is scanned before the second key (because in this case you will release the second key before the third key, and since the rom routine is "focused" on this third key, it will not detect the second key when the third key is released).
You can check this behavior by having your fingers lag on the three keys SDF (you will get SF instead, because F is scanned before D when you release S), and observe the difference when lagging on the three keys JKL (you will get JKL, because K will be scanned when J is released).
C and AssemblerBoth GET and KEY$ are available in C, but they've been renamed for consistency with the rest of the code:
- int get(void);
- char key(void);
#define getdeadkeys() peek(0x209)The actual implementation of key and get is actually very similar, both access the documentated XGETKY vector at address $23B, which by default calls the GTORKB ROM function which returns in the accumulator the code of any pressed key:
#define NOKEY 0x38
#define ALTGR 0xa0 /* Euphoric only */
#define CTRL 0xa2
#define LSHIFT 0xa4
#define FUNC 0xa5 /* Atmos only (and Euphoric, of course) */
#define RSHIFT 0xa7
_keyThere is no direct equivalent of INPUT in the C library, but a part of stdio libs is available, which gives us access to some more advanced functions suchs as scanf:
jsr $023B ; get key without waiting. If not available
bpl key001 ; return 0
jsr $023B ; blatantly ripped off Fabrice's getchar
bpl _get ; loop until char available
jmp grexit2 ; rip off Vaggelis' code as well, and exit.
These functions can potentially be causing havoc in your code, but it still nice to have them!
/* Get a line from the keyboard
Like 'puts', the routine is much smaller than 'scanf/sscanf'
Note: the Del key is active, allowing limited editing.
All control chars except Return are rejected
Caution: nothing prevents the user to exceed the buffer space,
so this implementation has been limited to a 256-bytes entry:
this way, a 256-bytes buffer will never be exceeded */
void gets(char buf);
/* Scan formatted data from the keyboard
Note: this is implemented as a 'gets' followed by a 'sscanf'.
An internal 256-bytes buffer is used for gets, which will only read one line.
This means 'scanf' will only parse one entry line.
Also, extra data won't be retained for future scans */
int scanf(const char *format,...);
That being said, none of what I presented so far can allow you to do a game where you can move in diagonals by pressing two arrows at the same time, for this we need to explore the Matrix.
The ROM IRQ handlerBefore looking at how we can improve on what the Oric BASIC Rom is doing, it is worth looking at what it does, and why it is so slow.
Yes, it is slow: For some reason, the designers of the Oric decided to run the main system IRQ at 100hz while 50hz would have been perfectly sufficient, and as a result almost 20% of the total CPU time is used6!
If you want to tweak the behavior of the system keyboard handler, you can play with a few system variables:
- $24E (KBDLY) delay for keyboard auto repeat, defaults to 32
- $24F (KBRPT) repeat rate for keyboard repeat, defaults to 4
The Keyboard MatrixTechnically, your keyboard is made of multiple parts:
- A circuit board with open contacts
- Key caps designed to close the contacts
- Multiple tracks forming a grid (the "Matrix")
- A 4051 multiplexer chip
- One of the AY-8912 I/O ports (to select the column)
- The VIA 6522 (to select the row and also control to the AY-8912)
Here is how the various keys are laid out on the Matrix:
|Column: AY-8912 Register $0E|
The organisation looks a bit random, but it is worth noting that the Row #4 is quite interesting for people who want to make games, because all the arrows and space are on the same row, which can be a useful optimization... because reading the entire keyboard takes quite some time.
Also, if you are wondering about the strange values for the column registers, here is what they look in binary, that should make the values easier to understand:
- $FE = %11111110
- $FD = %11111101
- $FB = %11111011
- $F7 = %11110111
- $EF = %11101111
- $DF = %11011111
- $BF = %10111111
- $7F = %01111111
So, in order to access the status of key, we need to juggle with both with the sound chip's I/O port A to select a specific column, as well as the VIA's port A (to select the row through the 4051 multiplexer) and B (to access the sound chip).
Be aware that the hardware is such that three simultaneous pressed keys are enough to prevent a correct detection of every key:
Detecting a key is done by forcing Ground on a column, and looking if the selected row in the multiplexer is at this ground level. The voltage level of the selected row will be 0V when you press the key which lies at the intersection of the row and the column, but not only in this case.
Try the following:
- Press "i", you will see repeating "i" letters)
- Keep "i" pressed, and press UYH together as well. You will still see repeating "i" letters
- Keep UYH pressed and release "i". You will still see repeating i letters, despite the fact "i" has been released
IRQ HandlerOne thing to consider, when you plan to read the keyboard by yourself, is that you should probably disable the system routines completely, else you will get conflicts.
And as usual, when using a custom IRQ handler, different code will be required depending if you are running with the ROM enabled or disabled.
If the ROM is enabled, you will need to use the system IRQ vector at address $245-$246, while if you are running from the overlay memory, you should probably use directly the 6502 IRQ vector in $fffe-$ffff.
Just to make things a little bit more challenging, it's just not possible to bash the registers as fast as possible, because it takes some time to switch to a new row or column, and on some Oric the CB2 status need to be reset.
Reading one rowThe following routine is a variant of what I used on my "Quantum Fx" entry for the CEO new year competition which basically only handled the four arrows and the space bar, but I added support for the left Shift as well as < and > keys:
#define KEY_SPACE 1A simple game Space Invaders game could just look like that:
#define KEY_LESS_THAN 2
#define KEY_GREATER_THAN 4
#define KEY_UP_ARROW 8
#define KEY_LEFT_SHIFT 16
#define KEY_LEFT_ARROW 32
#define KEY_DOWN_ARROW 64
#define KEY_RIGHT_ARROW 128
InitIRQ();And then the associated 6502 code that does all the magic:
if (gKey & KEY_LEFT_ARROW))
// Move Left
if (gKey & KEY_RIGHT_ARROW))
// Move Right
if (gKey & KEY_SPACE))
As you can see, there's quite a lot of code to perform to read just ONE key, the reason being that all the accesses to the sound chip have to go through the VIA.
#define via_portb $0300
#define via_porta $030f
#define via_pcr $030c
values_code .byt $df,$7f,$f7,$bf,$fe,$ef,$fd,$fb
; Select the bottom row of the keyboard
; Write Column Register Number to PortA
; Tell AY this is Register Number
; Clear CB2, as keeping it high hangs on some orics.
; Pitty, as all this code could be run only once, otherwise
; Write to Column Register
The source code is available on the SVN depot.
And here is the pre-build binary in TAP format
Hardware limitsI made a small video showing the small program in action, both on my real Oric Atmos and on emulator.
As you can see, up to three simulatenous keys things are reasonably ok, but after that you get ghosting - some keys that are not pressed are detected, and vice versa.
This specific problem is not limited to the Oric, it also happens on PC, which is why you get an entire category of "Gaming Keyboard" that feature something called "N-Key Rollover"7
In a next article, I will explain how to handle all the keys, but in the mean-time: Have fun!
PS: Special thanks to Fabrice for the additional information on the way the keyboard matrix and ROM routine behaves on the Oric as well as Chema for the proof-reading!
1. And don't forget the Pravetz 8D!↩
2. Due to the different types of DRAMs on Orics, some Orics will start with bit 7 set in variable $02DF, and since the Microdisc eprom doesn't reset this variable, a keyboard key might be present in variable $02DF at bootup...↩
3. Fortunately you can fix that using our next command, KEY$ to check on program start if a key appears to be pressed with KEY$ and if yes call GET to remote the phantom key from the input buffer.↩
4. Sometimes called 'dead keys'↩
5. The FUNCT key was added on the Oric Atmos, it is not available on the Oric 1 models↩
6. You can easily check that by reading the second VIA timer with and without IRQ enabled↩