There are moments in a big project like this that really stand out. Those moments where it all seems to come together, and…. magic happens. This is one of those moments. After some rough times on Veronica as of late, it was great to have a win. This is going to be a long piece, so grab a nice local microbrew and get comfortable.
As you’ve already surmised from the title, it was time for Veronica to get some input, in the form of a keyboard. My goal throughout has been for Veronica to get along in the real modern world. That’s why I put all that effort into VGA, even though composite video would have been easier. Composite displays are just getting too hard to find. As I’ve said before, Veronica is basically being surrounded in a “1980s bubble” created by fancy interfaces to convert modern tech down to her level. The keyboard interface is another great example of that. In this spirit of getting along, I wanted to support modern USB keyboards. Most homebrew projects go with a PS/2 keyboard, or a serial terminal, or something custom, because those are all easier. The 1980s home computers had it “easy” in a sense, because they all had custom keyboards built in, so the designers could make them produce whatever signals were most convenient. We have no such luxury here.
I also decided I wanted to use the 6522 Versatile Interface Adapter (VIA). This is a general purpose I/O chip that was part of the original family of 65xx chips. It’s designed to work with the 6502, and it’s “period correct”, if you like. I picked up a nice set of W65C22S chips from Western Design Center, who still makes this modernized version. So, the challenge boils down to getting a USB keyboard from 2013 to talk to an I/O chip from the 1970s. Never a dull moment on Veronica.
I started by researching USB hosting, and what it takes to implement that. It turns out, quite a bit. USB is a very complex protocol, and while implementing a peripheral is fairly straightforward (nice single-chip solutions exist), implementing a host is considerably more involved. I found some expensive multi-chip solutions, but it wasn’t going to be easy. Then I stumbled on to a great alternative, as long as you only want a keyboard (as opposed to general USB connectivity).
It turns out that, thanks to globalization, virtually every USB keyboard out there uses the same USB keyboard controller chip. That chip has legacy goo in it for those old USB-to-PS/2 adapters that every PC in the 1990s came with. During that transition period from PS/2 to USB, this backward compatibility was essential, and it turns out the chip makers never bothered to remove it. I’m told some hardcore gamers still use these, because apparently PS/2 keyboards have some superior characteristics with regards to handling multiple keys being down at once, compared to USB.
Anyways, what this means is you can basically implement the much simpler PS/2 keyboard protocol through a female Type A USB connector, and it will just work. Of course, you end up with a “Keyboards Only” USB port, and chaos might ensue if you tried to plug anything else into it. For us homebrewers, though, that’s fine. The main thing is, it achieves my goal of being able to pull a modern USB keyboard off the shelf, plug it in, and have it work. There’s a trivial trick you need to do to force the keyboard in PS/2 compatibility mode. I’ll explain that in a minute.
So, with that USB problem “solved”, it was time to figure out the PS/2 protocol. There are some great resources out there on this. My favorite is the OSDev Wiki. The executive summary is that each keypress sends an 8-bit scan code to the host when it goes down. When the key comes back up, it sends a special prefix byte (usually $F0), followed by the scan code again. There are also some more exotic packets for special keys, but we can safely ignore them. The nice thing about the PS/2 protocol is that it degrades gracefully. Many special keys, for example, are two byte packets prefixed by $E0. If you simply ignore that prefix byte, the scan code will be interpreted as the second byte of the packet, which will map harmlessly to some other key. That won’t mess up any internal state in your driver (if you code it carefully), it just means the Page Up key might return a ‘9’.
One minor catch with all this- PS/2 devices are serial devices. That means they send 11-bit RS-232 style packets that need to be stripped of all their extraneous bits. We also need to be aware of what happens when the keyboard is first connected. The USB chip in the keyboard sends out a bunch of negotiation bytes. The handy part is, it if doesn’t get the proper replies to those bytes (as per the USB protocol), it falls back to PS/2 compatibility mode. So, that “trick” I mentioned earlier is simply “inaction”. That’s good, because inaction is something I’m really great at. If doing nothing was a professional sport, I’d be internationally ranked.
The point of all that is, we have a few housekeeping tasks we need to perform on the data coming in from the USB connector. When I see a job like this, I think microcontroller. I’m a big fan of the ATTiny13, and I tend to throw handfuls of them at the breadboard until the problem goes away.
To test this approach, I set up a breadboard with the USB connector, an ATTiny to read the data from it, and a shift register to push the bytes out to HexOut. That would allow me to see exactly what the keyboard spits out, and how I can work with that data. Connecting to USB is easy- it’s just power, ground, D-, and D+. In PS/2 compatibility mode, the keyboard uses D- for data, and D+ for clock. New Egg sells a really nice USB socket that ends in a 0.1″ header. It’s perfect for breadboarding USB experiments.
The keyboard, being an input device, might start clocking data out at any time. That means we need to respond with an interrupt handler on the ATtiny, so we don’t miss anything. Here’s the code that I came up with to do that:
That code sets up an interrupt handler that listens for clocks from the USB connector, then buffers up 11 bits, strips off the start and stop bits, and holds the remaining 8 data bits. The code then uses a couple of other pins on the ATtiny to manage a 75HC595 shift register, which is used to convert this serial data in an 8-bit byte usable by the 6522 VIA. The ATtiny is acting as the glue and signal converter between the USB and the 6522.
So, now that I can get data to the VIA, the next piece of the puzzle is interfacing the VIA to the 6502. This could not be easier, since they are designed to work together. If you’ve worked with microcontrollers, it will seem very familiar. The VIA provides “Ports”, timers, and other facilities, which are all memory mapped into the 6502’s address space wherever you like. The interface uses Data Direction Registers and such, just like a microcontroller. I just need to connect up my shift register to provide the keyboard data, add a little address decoding, and throw the whole thing on Veronica’s main bus. Here’s the circuit I came up with to do that.
Note that I could not find an Eagle symbol for the 6522, so I made one. Here it is, free to use, if you like. The schematic above is a work-in-progress, hence a few missing details like decoupling caps and power/ground connections. The VIA has a lot more room for other I/O on it, so I’ll be adding more stuff to this before I finish it up and etch a PCB.
The address decoding is handled by a single 74HC688 8-bit comparator. All addresses in the $E0 page are reserved for I/O in Veronica’s memory map, so this is a nice one-chip way to do decoding. The low address bits are routed to the 6522’s internal address bus, which is how you map its registers into the 6502’s address space. A very elegant design by those wily WDC engineers.
With that design figured out, it was a simple matter of prototyping it on the breadboard and working the kinks out.
When the ATtiny has received a byte from the keyboard, it presents it to the 6522 (using the ‘595), then signals the data is ready using the 6522’s CA1 line. The 6522 datasheet explains very well how to do all this. When the 6522 receives the keyboard byte, it signals the 6502 with an interrupt. So, the next thing we need is an interrupt handler in Veronica’s ROM.
The secret to a good interrupt handler is speed- get in, do your business, and get out. In this case, all I do is grab the byte and buffer it for processing elsewhere.
To debug this, I wrote a routine to print out the hex values that were landing in the buffer (put there by the above interrupt handler). The final result is shown below. Protip- change the quality setting to 720 on these videos. Otherwise you won’t be able to read the screen.
This is a good point to mention what a joy it is to have full character video output on Veronica. It’s a huge milestone when you can debug things using “print” statements instead of having to bust out the logic analyzer, or do a lot of trial and error. There were moments while debugging this keyboard driver that I started to feel like a software engineer again.
In the above video, I’m pushing my favorite key, ‘Q’. You can see it display the three-byte sequence I described earlier- the scan code ($15 for ‘Q’), followed by the “key going up” marker ($F0), followed by the scan code again. Success!
At this point, we can figure out what kind of keyboard we have. It turns out there are three different kinds of PS/2 keyboards, with three slightly different sets of scan codes. Likely, it seems that 95% of the worlds’ keyboards are the same- Type 2. Here’s the scan code list for a Type 2 PS/2 keyboard. I have no idea how all this applies to non-North-Amercan keyboards, mind you, so your mileage may vary.
Once I can read the scan code sequences correctly, the next step is to track the state of all the keys. The keyboard tells you about events (things going up or down). It’s up to the software to keep track of what state everything is in. For that, I wrote some code to track all the keys in a big bit array. It sets a bit for each key when a scan code comes in, to indicate it is down. When an $F0 code comes in, it sets a flag to note that the next scan code will be an “up” event. When the next code comes in, it clears the appropriate bit in the array.
That code runs any time the system is waiting for user input. The interrupt handler is running all the time, pulling bytes off the 6522 VIA and jamming them into a holding area. If that processKeyboard routine isn’t called regularly, keys will be missed (which doesn’t actually matter if the software isn’t expecting input at the time). You might think all that processing could be done in the interrupt handler, but it’s too slow (I know because I tried). The interrupt handler has to be fast enough that it can respond to repeated interrupts coming from the VIA at the maximum data rate of the keyboard (which is pretty brisk). If the interrupt handler is too slow, it starts dropping the ends off the multi-byte commands. That causes the key state array to get of sync with the keyboard, because “key up” events were missed. This makes a real mess, as you might imagine, and made for some debugging challenges.
Speaking of debugging, I wrote a display routine to render the keyboard state array for that purpose. Here I am pushing that ‘Q’ again.
You can see the bit for ‘Q’ go high when the first scan code comes in. Then, the $F0 code comes in as the key is released. This sets a special flag bit at the end of the array. Then, when the next ‘Q’ scan code comes in, the ‘Q’ bit is cleared again. Huzzah!
The final piece of this puzzle is a bunch of grunt work. You see, keyboard scan codes aren’t particularly useful. What software really wants is ASCII characters, and some state bits for shift keys and such. To get to that point, we need to track states for modifier keys, manage the caps lock key, and use some lookup tables to convert scan codes to ASCII codes. Here’s a sample of one of the tables I wrote to do that:
Then there’s a bunch of fiddly state and flag management code that combines those tables with the keyboard state array we set up earlier, to generate actual characters:
Then putting alllll that stuff together, we can write an actual input routine, that polls the keyboard buffer and displays the keystrokes:
Now, the money shot:
My text generator still needs some work- especially the lowercase. But hey, it’s moderately legible.
As I said at the top of the piece, there are a few magic moments in a big project like this. Moments where all the dozens of layers of electronics and protocols and interfaces and software all come together and make something that looks and feels like…. a computer. These are the moments that keep me going through the really hard parts.