The way Commodore handled keyboard input on the VIC-20 was pretty cunning - they hooked the keyboard switch-matrix up to ports A and B of VIA #2, and wrote some code in the KERNAL to perform a moderately-complex but clever series of tests against those ports to determine whether a key had been pressed, and if so which one. That code also handles the 'action' keys like Shift, Control, the Commodore key, and the Run/Stop key, and alters the resultant keyboard response accordingly before (if appropriate) dealing with auto-repeat and finally placing a relevant character into the keyboard buffer. I'm now starting to write something similar - but you definitely need a copy of the 6522 datasheet in one hand and the VIC-20 schematic in the other when diving-in, because the process requires a fairly detailed understanding of how the VIA data ports work and how they're connected to the keyboard hardware.
For the purposes of VIC++ I'm not interested in handling the Commodore key, since when this OS moves to my custom hardware design in future I'll be using a standard ANSI keyboard which doesn't feature it. I do want to handle Shift and Control though, and also Run/Stop (although this will eventually manifest as Escape). The VIC-20 Restore key is actually handled completely separately from the rest of the keyboard scanning logic because it's tied to VIA #1 and triggers generation of the NMI signal - this is the ultimate 'override' interrupt for the CPU, higher in priority than IRQ, and therefore it's important for the OS to recognise it (however it's generated) and do something sensible when it occurs - but I'll leave that until after the rest of the keyboard handler is working.
So how does VIA #2 represent a keypress signal from the switch matrix, and how do we interpret that representation in order to turn it into something recognisable - so that if I activate the keyswitch under the 'A' key (by pressing 'A') I get an 'a' on the screen? Or an 'A', if I'm holding Shift down at the same time? Well, first of all let's take a look at the hardware connections.
What we see here is the circuit layout between the keyboard connector (shown at the bottom) and VIA #2 (the big rectangle marked 'MPS 6522'). This is a small slice of the full VIC-20 schematic taken from 'The VIC Revealed', a book I've mentioned before which should be in every owners' library. The crucial thing to note is that the keyboard 'column' lines connect to VIA #2 Port B, and the 'row' lines connect to Port A. So this yields a simple 8x8 logical matrix in which each physical key on the keyboard corresponds to a row/column pair appearing on VIA #2 Ports A and B, and thus means that we can detect up to 64 unique keypress events just by reading the patterns appearing in these two VIA registers. Sort of.
There are a couple of problems with this idea - the first being that the VIC-20 keyboard has 66 keys, so two keys couldn't ever be detected! This is exactly the case but, as I already mentioned, Restore is handled by VIA #1 - you can see the dedicated 'RESTORE' line disappearing off to the right where VIA #1 is hiding out-of-shot - and Shift Lock is actually just a mechanical PTMPTB (push-to-make, push-to-break) switch that, when active, makes it look like Shift is being held down continuously. But for any given keypress aside from these two keys, VIA #2 Port A should reflect which keyboard row the key was on, and Port B would tell us the column - simple, eh?
Well, not quite - because the second problem is that that's not actually how the electronics of the keyboard hardware works. Pressing a key doesn't magically generate a pair of bit-patterns representing that specific 'crossing-point' in the matrix (to do so would require a microcontroller inside the keyboard hardware itself, which is expensive, complicated, and as we'll see, unnecessary). A big clue to what is actually going-on is the fact that in the Commodore KERNAL initialisation code, VIA #2 Port A (the keyboard row register) is configured as an input port, as we'd expect - but Port B (the keyboard column register) is configured as an output port. What the...? How can we output anything to a keyboard, and how would doing so assist in determining which key was pressed?
Here's how it works: when a key is pressed, it bridges a contact between the column and row lines, causing power to flow from the column line, through the key contact, and out along the corresponding row line for that key. But we don't want power flowing through all the column lines at the same time, because then when a key was pressed anywhere along a row, we wouldn't know which actual column had been bridged - a key in any column would form the circuit. So what happens instead is that we cycle power along each column line in turn, and look to see if a row input flagged a keypress - if it did, we know which row it was, and of course we know which column currently has power. There's a really nice page here with some animations to demonstrate how a keyboard matrix works at the electrical level.
So that's why VIA #2 Port A is configured as an input (it's watching for a row signal to be flagged from the keyboard) and Port B is an output (because we cycle bits through it to send power down a column line sequentially). As it turns out, the signals are inverted on the ports, so we actually send power to a column by setting a Port B bit to zero, and the corresponding row bit(s) go to zero when active - which is why, when we set Port B to $00 (power to all columns) we can tell straight-away if no key is being pressed by looking at Port A and seeing if it has $FF (all bits on, no key pressed) in it. Anything other than $FF means at least one row has been activated (because it has a zero in its' bit) and then, knowing the row, we can cycle a zero through Port B's bits to discover which specific column is the one where a key has been pressed - as we send a zero to each individual column in turn, the row input will disappear until the right column has power.
We need some code to test whether a key has been pressed anywhere, which is pretty simple, and then if a keypress was detected we need some more, slightly more complex, logic to cycle Port B and find out which column is producing the response. Here's the initial key-test code:
As you can see, we set Port B to $00 which sends power to all the column lines; if we then look at Port A and get $FF then there are no bits being pulled low by a key being pressed, so we can just quit at this point. On the other hand, if Port A gives us anything other than $FF then we know that at least one key is being pressed somewhere, so we continue by identifying which column is the one that a key is bridging to a row line. And that's the bit of code I'm writing now, so you'll just have to check back in a few days to see what I've come-up with. ;)
What we see here is the circuit layout between the keyboard connector (shown at the bottom) and VIA #2 (the big rectangle marked 'MPS 6522'). This is a small slice of the full VIC-20 schematic taken from 'The VIC Revealed', a book I've mentioned before which should be in every owners' library. The crucial thing to note is that the keyboard 'column' lines connect to VIA #2 Port B, and the 'row' lines connect to Port A. So this yields a simple 8x8 logical matrix in which each physical key on the keyboard corresponds to a row/column pair appearing on VIA #2 Ports A and B, and thus means that we can detect up to 64 unique keypress events just by reading the patterns appearing in these two VIA registers. Sort of.
There are a couple of problems with this idea - the first being that the VIC-20 keyboard has 66 keys, so two keys couldn't ever be detected! This is exactly the case but, as I already mentioned, Restore is handled by VIA #1 - you can see the dedicated 'RESTORE' line disappearing off to the right where VIA #1 is hiding out-of-shot - and Shift Lock is actually just a mechanical PTMPTB (push-to-make, push-to-break) switch that, when active, makes it look like Shift is being held down continuously. But for any given keypress aside from these two keys, VIA #2 Port A should reflect which keyboard row the key was on, and Port B would tell us the column - simple, eh?
Well, not quite - because the second problem is that that's not actually how the electronics of the keyboard hardware works. Pressing a key doesn't magically generate a pair of bit-patterns representing that specific 'crossing-point' in the matrix (to do so would require a microcontroller inside the keyboard hardware itself, which is expensive, complicated, and as we'll see, unnecessary). A big clue to what is actually going-on is the fact that in the Commodore KERNAL initialisation code, VIA #2 Port A (the keyboard row register) is configured as an input port, as we'd expect - but Port B (the keyboard column register) is configured as an output port. What the...? How can we output anything to a keyboard, and how would doing so assist in determining which key was pressed?
Here's how it works: when a key is pressed, it bridges a contact between the column and row lines, causing power to flow from the column line, through the key contact, and out along the corresponding row line for that key. But we don't want power flowing through all the column lines at the same time, because then when a key was pressed anywhere along a row, we wouldn't know which actual column had been bridged - a key in any column would form the circuit. So what happens instead is that we cycle power along each column line in turn, and look to see if a row input flagged a keypress - if it did, we know which row it was, and of course we know which column currently has power. There's a really nice page here with some animations to demonstrate how a keyboard matrix works at the electrical level.
So that's why VIA #2 Port A is configured as an input (it's watching for a row signal to be flagged from the keyboard) and Port B is an output (because we cycle bits through it to send power down a column line sequentially). As it turns out, the signals are inverted on the ports, so we actually send power to a column by setting a Port B bit to zero, and the corresponding row bit(s) go to zero when active - which is why, when we set Port B to $00 (power to all columns) we can tell straight-away if no key is being pressed by looking at Port A and seeing if it has $FF (all bits on, no key pressed) in it. Anything other than $FF means at least one row has been activated (because it has a zero in its' bit) and then, knowing the row, we can cycle a zero through Port B's bits to discover which specific column is the one where a key has been pressed - as we send a zero to each individual column in turn, the row input will disappear until the right column has power.
We need some code to test whether a key has been pressed anywhere, which is pretty simple, and then if a keypress was detected we need some more, slightly more complex, logic to cycle Port B and find out which column is producing the response. Here's the initial key-test code:
keyscan SUBROUTINE
LDX #$00 ; [2]
STX _SCANCODE ; [3] ZP clear keyboard scan code
STX _V2PORTBO ; [4] clear VIA #2 port B register (keyboard column)
LDX _V2PORTAO ; [4] get VIA #2 port A register (keyboard row)
CPX #$FF ; [2] test for all bits set
BEQ .done ; [3/2] no key pressed if all bits set
As you can see, we set Port B to $00 which sends power to all the column lines; if we then look at Port A and get $FF then there are no bits being pulled low by a key being pressed, so we can just quit at this point. On the other hand, if Port A gives us anything other than $FF then we know that at least one key is being pressed somewhere, so we continue by identifying which column is the one that a key is bridging to a row line. And that's the bit of code I'm writing now, so you'll just have to check back in a few days to see what I've come-up with. ;)

No comments:
Post a Comment