Entertainment Computer System
From Intellivision Wiki
This is just a stub page for now.
The following table is used to matrix-scan the keyboard:
$00FE | $00FF bits bits | 0 1 2 3 4 5 6 7 ------+----------------------------------------------------------------- 0 | left, period, scolon, p, esc, 0, enter, [n/a] 1 | comma, m, k, i, 9, 8, o, l 2 | n, b, h, y, 7, 6, u, j 3 | v, c, f, r, 5, 4, t, g 4 | x, z, s, w, 3, 2, e, d 5 | space, down, up, q, 1, right, ctl, a 6 | [n/a], [n/a], [n/a], [n/a], [n/a], [n/a], [n/a], shift
The ECS has two different keyboards you can connect to it: The alphanumeric keyboard and the synthesizer keyboard. This topic intends to describe the process of reading both, as well as to provide useful code for reading each. If you just want to use the code, you can skip most of this post and just download the code. You don't need the low level detail to use the code. I'm providing the low-level detail so that you can understand the code if you want to, or write your own routines if you want to.
The ECS provides a second sound chip to the Intellivision (AY-3-8917), which is similar to the AY-3-8914 in the Master Component, only mapped at $00Fx rather than $01Fx. Like the Master Component's sound chip, the ECS's provides two 8-bit I/O ports.
(Side note: David "Papa Intellivision" Chandler's recently released documentation indicates that the AY-3-8914 was modified at some point to make its I/O ports input-only, unlike every other variant of the device with I/O ports. That is not relevant to this discussion; the AY-3-8917's I/O ports are bidirectional. It is an interesting historical footnote, though.)
Introduction to Matrix Scanning
Both keyboards connect to the AY-3-8917's 8-bit I/O ports. Both keyboards employ a basic technique called matrix scanning. The basic idea is simple. Making a matrix-scanned keyboard takes four steps:
Construct a grid of "row lines" and "column lines". Connect your keyboard switches so that each one connects one "row line" to one "column line" when closed. Connect an output port to the "rows", so that you can selectively drive one row at a time. Connect an input port to the "columns", so that you can see what switches are closed on any given row when that row is driven.
That gives you a basic matrix-scanned keyboard. Here's a simple example of a 3x3 keyboard with the keys A through I. (Note: This is just meant to be an example; it is not the actual ECS keyboard matrix.)
When you press 'A' in this example keyboard, it connects Row 0 to Col 0. When you press 'I', it connects Row 2 to Col 2.
To read such a keyboard, you need to scan each of the rows, one at a time, reading the columns after each. To start, you drive Row 0, and then read the columns. If one of the switches is closed in that row, then the signal on the Row 0 line will show up on the appropriate column. For example, if you press "B", then you'll see a signal on column 1. Next, you switch to driving Row 1 (and stop driving Row 0), and read the columns. And so forth.
Now, the observant will notice I left out a couple details in the diagram and description above. Now to add those back in. In the Intellivision, the keyboards use an "active low" scheme to scan the keyboard. That is, each row and column have pull-up resistors on them that cause them to read as 1 by default. So, to drive a row, you need to set that row's bit to 0. To detect a driven column, you need to look for a 0 in that column's bit.
Problems (and Solutions) with Matrix Scanning
Refer back to the example matrix above. What happens when you press multiple keys at the same time?
Let's say you press A and E at the same time. When you scan Row 0, you'll see Col 0 light up, and when you scan Row 1, you'll see Col 1 light up, as in the following diagrams.
Posted Image Posted Image
So far, so good. Now what happens when you also press B in addition to A and E? Let's scan Row 0:
We light up Row 0, and both Col 0 and Col 1 light up as we expect. But, the fuschia line above shows another line that gets lit up... Row 1! So far, that hasn't caused a problem. But if your Spidey Sense is tingling, telling you that things are about to go horribly wrong, you'd do well to listen to it. Let's scan Row 1 and watch it go pear shaped:
Since we've only pressed E in Row 1, we only want to see Col 1 light up. But, because the switches at B and A are also closed, we also have a complete circuit to Col 0, so both Col 0 and Col 1 light up. It looks to us as if both D and E are pressed at the same time. Oops!
This is sometimes called "ghosting," and can happen whenever you press keys on three corners of a rectangle in the keyboard matrix. It turns out that there's an easy fix for this: Diodes!
Now, partly because I'm lazy, partly because I'm still learning Inkscape and partly because I want to keep the diagram general, I'm just going to represent the diodes as rectangles below. The direction they face depends on whether you use an active-low scheme, like the Intellivision, or an active-high scheme. And, it depends on whether you scan by row (as we have so far) or scan by column. (More on this in a moment.)
With diodes, the keyboard matrix looks like this:
Now if we press A, B and E, everything works like you expect, because the diodes block the ghost paths:
Posted Image Posted Image
Even though B lights up when we light up Row 1, the diode prevents that from inadvertently lighting up Row 0 and flowing (via the switch at A) to Col 0.
What's in the Intellivision's ECS Keyboards?
Both ECS keyboards are basic matrix scanned keyboards. The alphanumeric keyboard is a simple 48-key matrix with no diodes. There are 49 actual keys, but the "Shift" keys are wired to the same row/column in the matrix. If you press the wrong keys at the same time, you will see ghosting. The following diagram gives the matrix for this keyboard:
You'll notice Row 6 only has the shift key, and Row 7 is empty. Also, there's no key associated with Row 0/Col 7.
The synthesizer keyboard, on the other hand, is a diode-filled 49-key matrix. This allows you to press whatever keys you like, without restrictions, and without ghost paths. The piano keys are mapped to matrix points in a very straight forward manner, so I won't bother with a matrix diagram. The lowest note on the keyboard corresponds to Col 0, Row 0, and the highest note is in Col 0, Row 6.
Scanning the Alphanumeric Keyboard
As stated before, the alphanumeric keyboard uses a 48-position matrix, and has no diodes. Its matrix also has a peculiarity: "Shift" is in a row all by itself. We'll make use of that in a minute. First, let's look at how to program the hardware.
Programming the AY-3-8917 PSG I/O Ports
The keyboard connects to the two ports on the ECS's AY-3-8917 PSG. While we usually associate this chip with sound, the ECS also uses it for its keyboards. The PSG exposes its two I/O ports at locations $FE and $FF. The port at $FE connects to the rows in the diagram above, and the port at $FF connects to the columns.
Both ports are bidirectional. Bits 6 and 7 of the register at location $F8 control the I/O ports at $FE and $FF, respectively. To configure a port for input, set the corresponding bit to 0. To configure a port for output, set the bit to 1. Each 8-bit I/O port is either "entirely input" or "entirely output." There's no way to set some bits for input or some bits for output. The other bits at location $F8 control the tone and noise channel enables for the three sound channels. You will want to leave those lower bits undisturbed.
Both ports also have 8 pull-up resistors connected to them internally to the PSG. This causes input ports to return the value $FF by default if nothing is connected or if nothing happens to pull the line low. Also, to protect each port, the ECS includes 2K? current-limiting resistors in series with both ports--a total of 16 altogether! Here's a picture of that PSG with its current-limiting resistors, for the curious:
Son of Ghosting: Peculiar Challenges Due To I/O Port Limitations
Recall the ghosting effect I described previously? It turns out, on the ECS, there's a related masking problem that works similarly, that arises due to the fact that the PSG's I/O ports are always "entirely input" or "entirely output".
In the previous discussion of matrix scanning, I indicated that you need to drive one row at a time. The implication is that the other rows remain undriven, and are free to float to whatever state they like. Pull-up resistors then float the undriven lines to 1. In an idyllic world, that's how you'd do things. Unfortunately, though, the ECS doesn't work that way.
Because of the internal pullups on the inputs, the ECS uses an active-low scheme to scan the keyboard. To read row 0, for example, you output $FE on port $FE, and read the result on port $FF. This drives row 0 low, and so if any keys in row 0 are pressed (left arrow, space, etc.), you'll see those bits go to 0. Here's the fun part, though: While row 0 gets driven to 0, rows 1 through 7 get driven to 1! Oops!
What fun does that cause?
Suppose you press the 'left arrow' key, which will set column 0, row 0. Next, also press the comma (column 0, row 1). What happens? Because Row 1 gets driven to 1 you now have both a 1 and a 0 driven onto column 0. It turns out that the 1 wins over the 0 and both key presses disappear, at least as far as the Intellivision is concerned. I've written a quick and dirty test program that will let you witness this. (BTW, jzIntv does mimic this behavior, if you want to experiment there.)
The test program scans the keyboard two ways: by row and by column. I'll discuss the latter in a moment. It displays two 8x8 tables full of 1s. In the left 8x8 table, each row of the table corresponds to one row in the keyboard matrix and each column represents one column of the keyboard matrix. The right-hand table transposes this, so each row corresponds to a column in the matrix, and vice versa. Below, you can see the output when no key is pressed, followed by pressing just the left arrow, followed by pressing left arrow plus comma:
Posted Image Posted Image Posted Image
As you can see in the left table, both keys "disappear" when they're both pressed. But, in the right table, they don't. That brings us to the next topic: Transposed scanning and a curious ECS bug.
Transposed Scanning, and the ECS's Shift Key Problem
Transposed scanning merely refers to switching the role of rows and columns in the keyboard matrix, where you drive individual columns, and read the input data via the rows.
As you see above, when you press two keys in the same column, they disappear from the row-oriented scan. But, they still appear in the column-oriented scan. Transposed scanning isn't magical though. If you press two keys in the same row, they'll disappear in the transposed scan but appear in the normal scan. Since both scanning modes have the same problem, and since you generally only press one key at a time anyway, why bother with a transposed scan?
The shift key, that's why. Refer back to the scanning matrix above, and you'll see that the L, J, G, D and A keys all share the same column as the shift key. But, the shift key has an entire row to itself. If you scan the ECS keyboard in the normal order (the same order the built in EXEC scans it, incidentally), you can not upper-case those letters! As it happens, the ECS BASIC was an all-caps environment, so you wouldn't notice it on the stock software. But, the bug is there.
Transposing the scan makes it possible to upper-case every key on the keyboard.
What About The CTL key?
The CTL key is the other modifier key on the board, and so far as I recall, nothing really made use of it. But, if we're to write our own keyboard scanning code, we may as well try to incorporate it too. CTL shares a row with "Space", "Down Arrow", "Up Arrow", Q, 1, "Right Arrow", and A. CTL shares a column with RTN, O, U, T, and E. In the transposed scan, CTL will get masked out with its row-mates. While we probably won't miss such dubious combinations as "CTL-Space" and "CTL-Down Arrow", we probably do still want CTL-Q and CTL-A. And, we certainly don't want to through CTL-O, CTL-U, CTL-T and CTL-E under the bus to get there.
What should we do then?
Easy: Scan for CTL twice, both with normal and transposed scans. (ie. light up row 5 and look at column 6, then light up column 6 and look at row 5) Take the AND of both scans and tada! Now you reliably know whether CTL is pressed.
"Not so fast!" you might say. "What about the other keys in the same row/column as CTL? You found CTL, but you've still lost its row-mates/column-mates!"
Ok, you got me. It's not that easy. To do CTL properly, you actually need to do a normal scan and decode all of Row 5 in addition to the transposed scan so you can see all of Col 6. I'll be honest: I haven't bothered yet. I did give up on CTL-Q and CTL-A for now. But, I know how to get them if I decide I want them back, and now you do too.
Scanning the Synthesizer Keyboard
The synthesizer keyboard has 49 piano keys, covering a 4 octave range, from C to shining C. The keyboard matrix for the synthesizer keyboard is very simple, putting the lowest note in Row 0, Col 0, the next note in Row 0, Col 1, and so on all the way up the scale. Thus, you can compute note number directly as Row*8 + Col, which is rather convenient.
The synthesizer keyboard has a full complement of diodes. These solve all ghosting and masking issues, so that you can press any combination of keys with no restrictions, and the Intellivision can resolve them all. Unlike the alphanumeric keyboard, which can barely handle anything more than a single key at a time without a headache, the synthesizer keyboard is very capable and easy to work with. So what's the catch?
Well, because it's so versatile, you probably want more versatile software to read it. For the alphanumeric keyboard, returning key-down events and a generic key-up event is probably sufficient for most purposes. For the synthesizer, though, you want to keep track of every key that's pressed and released, and when.
Architecting the Software
To get the most out of the synthesizer keyboard, a good software driver needs to be able to do the following:
Keep track of what keys are pressed Inform the program when a new key gets pressed Inform the program when a pressed key gets released
In the worst case, someone could press or release all 49 keys on the synthesizer at once. This seems improbable, sure, but we'd like the software not to break even if it did happen. So, in any given call, we could generate up to 49 change events. We can't generate more than that, if we only look at each key once during a given scan.
What's the best way to handle this? One approach is to use an event queue. To absorb a huge, if improbable burst of events, you'd need a large queue -- large enough for 49 events in this case. You could go with a smaller queue and drop extra events, but what's the right size?
Another approach is to use callbacks. In this scheme, you register a function that gets called whenever a key changes state, and the driver calls it with each key change event. This way, you don't have to store all those events anywhere. That's the approach my driver takes.
Next, how do you detect what keys changed? The most straightforward approach (and the approach I've taken) is to store a bitmap of "pressed keys". That way, you can compare the new state against the previous state with a simple XOR, and know exactly what changed.