There are a couple of approaches to interfacing what is essentially a switch matrix with a microprocessor. The two main ones are:
- Directly polling the switch matrix through a parallel interface on the main processor. This is the method used by most 8 bit micros including the C64, where the keyboard is attached to a VIA dedicated to this purpose, whilst the ZX Spectrum used its ULA.
- If the keyboard has its own dedicated microcontroller then the main microprocessor does not have to poll the matrix, leaving this chore to the microcontroller. Instead it can be interrupted when the controller detects a keypress. This is how all modern computers deal with keyboards.
Instead I have opted to add a microcontroller to a C64 keyboard, and then attach this to my computer. The C64 had quite a nice keyboard, being a proper typewriter keyboard. The switch matrix consists of 8 columns and 8 rows, for 64 keys. A further decision of how to link the controller to the rest of the computer arises. Once again there are choices for how to do this; at least four come to mind:
- Use a asynchronous serial interface, such as a UART channel. This was and remains a viable solution used on many non-PC systems.
- Use a synchronous serial connection such as a SPI connection.
- Use a synchronous serial connection through dedicated shift register.
- Use a parallel bus between the keyboard controller and the main computer.
SPI was a very attractive idea save for the fact that the 65SPI on the IO board can only act as a SPI master and not as a slave. This means it has to control when bytes are received over the bus, which is not what's needed to receive keypresses, which obviously happen at unpredictable times.
So initially I was very keen on the third approach. The 6522 VIA contains such a shift register and in theory this would have been a great solution, minimising the number of wires between the keyboard controller and the rest of the computer (only four would have been needed including power) whilst not requiring any more parts. I just couldn't get it to work reliably. In a test rig, without an actual keyboard attached, bytes would usually transfer ok but would sometimes get corrupted somehow. The VIAs shift register does have some known issues but these did not account for all the problems I was seeing.
So in the end I settled for using an entire VIA port to link a 8 bit port on the AVR with the rest of the computer. This also meant a method would be needed so the AVR could signal the VIA when a new byte needed to be sent down the bus.
A further decision needed to be made, that of what should be encoded in the bytes sent from the keyboard. This really boils down to where the conversion to a useful ASCII stream should take place. It can either be done in the keyboard itself or it can be done in the MPU. For maximum flexibility I've opted to do it in the MPU. This means the keyboard routing running in the MPU can either operate in ASCII mode or it can operate in a "raw" scancode mode, which is the data generated by the AVR. The advantage with a raw mode is it allows a program to see all keypresses not just ones which translate to an ASCII value, for example a pinball game night use the shift keys for activating the flippers.
For the C64 keyboard a scancode can be derived from the row and column of the pressed key. There are eight rows and eight columns, giving 2 lots of 3 bits. Since the data stream is a sequence of key events, only when the matrix changes state is a scancode generated. To represent keys going up, the high bit can be set. Otherwise the key is being pressed.
Here is the circuit for the keyboard controller:
This is a slightly updated schematic compared to what I have made up myself. I seem to have lost the schematic I originally drew up. Anyway, as you can see, it is trivial. I originally hoped to use a ATMega8 (PDF) in DIP28. Unfortunately the keyboard requires two 8 bit ports and the interface to the VIA requires another. An addition two pins are needed to handshake the data to the VIA. This makes 26 pins! So I needed an alternative AVR with more IO ports. Luckily the ATMega8 was also made in 44 pin PLCC which has 4 8 bit ports plus a few more IO pins. This is the ATMega8515 (PDF). The circuit contains two "fluffy" features; a buzzer and a serial header. The buzzer was originally intended to signal a buffer overflow (where the 6809 hasn't ack'd the keypresses fast enough), and a serial header intended to be used for debugging. I have yet to actually implement either function in software however. I decided to make this circuit up on a PCB myself:
The next thing to do was write the AVR firmware. This turned out to be a little harder then expected due to the matrix scanning routine being something I'd not written before.
Scanning a key matrix involves sending a signal down one row of the matrix grid and then reading what switches are pressed in that row by simultaneously reading the columns. In an eight by eight grid, eight rows have to be scanned. If this is done rapidly enough the illusion of continual scanning is achieved.
In my first attempt at coding this up, I rotated a logic high across the rows. Rows that weren't being scanned were set to logic zero. Whilst this appeared to work I had introduced a nasty issue: when a key was pushed, row lines that weren't being scanned were effectively sinking current from the row line that was being scanned. This was because the high outputs and the low outputs were being connected together by the key switch. Not good at all.
The solution to this problem is to instead rotate the data direction instead of the outputted value. Thus rows that aren't being scanned are treated as inputs, and are high impedance. The row that is being scanned is an output at logic zero. A further details is to use pull up resistors on the column port. Thus pressing a key results in a zero being sensed at the column pin, otherwise a logic one is obtained at the port.
Actual scanning is done in a timer interrupt handler. This adds a key event into a 32 (fairly arbitrary choice) byte circular buffer, when it detects a change in the keyboard matrix. A pointer maintains the position in the buffer where the next scancode will be stored. Determining if the matrix has changed is simple enough and is a matter of comparing the current state with the previous one.
Inside the main program, the write buffer pointer is compared to the read pointer and if the write pointer is ahead then a new key event needs to be sent to the VIA. This scancode byte will then be put on the VIA port and the handshake line asserted, which should cause the 6809 to wake up and process the byte - more on that later. The AVR, meanwhile, enters a loop waiting for the byte to be acknowledged by polling on the "received" handshake line. Once the byte has been received the read pointer is advanced, and the AVR main loop reaches the top where it waits for the next keypress.
The VIA meanwhile is configured to generate an interrupt when it receives a byte. This interrupt handler's first job is to pull off the byte from from the VIA port. This does two things to the VIA: it clears the interrupt handler and it tells the VIA to acknowledge the byte received, which simultaneously will cause the AVR code to go back to the top of the main loop and wait for the next keypress. The remaing task for the interrupt handler is to optionally translate the scancode to an ASCII value. This is indicated by a flag value at a memory location and works using a simple lookup table of scancide value to ASCII. A complication in this task exists because of handling shift keys, but this is reasonably simple to deal with by tracking the state of the two shift keys and using an alternative lookup table if either is known to be depressed. Mirroring the AVR behaviour this keypress data (weather its a scancode or ASCII) is placed in a 32 byte circular buffer. Outside of the interrupt, game code (for example) is free to poll on this buffer, pulling off keypresses as they are received. This completes the somewhat involved handling of a key going down to user code running on the MPU.
The updated monitor code (including Snake), as well as the keyboard controller code, are available on github, as always.
The end result of this is my computer can now be used completely stand alone, in the spirit of the classic 80s micros.
Originally I hoped to write a version of Pac Man, but this is a complex game to start with so I dialled back my ambitions and decided to write another of my favourite old time games, Snake.
This game uses a different video mode to the mode used by the monitor, but it is still a tiled mode. This time the screen is 32 by 24 tiles, with each tile being 8 by 8 pixels. This finally lets me use the Sinclair Spectrum font. This screen mode also supports a limited form of colour: groups of 8 tile types can have a different foreground and background colour.
Here is a screenshot of the game:
To save me from writing all the video routines from scratch, the Snake game utilises the lower level video routines in the EEPROM. The snake game makes heavy use of the read and write VRAM calls for drawing the snake on the screen and detecting a collision between the head of the snake and itself, the wall or some food.
I think the snake game is fairly playable. The snake not only gets longer as it eats the food, the game also increases in speed ever so slightly to make it more challenging. It's not quite finished however: it would be nice to have better sounds, variable sized "food parcels" and a displayed score based on the length of the snake.
I'm now at a bit of a cross roads with my computer project. I feel I have several possible paths forward:
- Get updated IO and keyboard PCBs made up. I have already made the design changes to the IO board that are necessary to fix the previously described problems with the DIN RGB port and issues with the AY sound IC. Also, the keyboard board; while it works it would be nice to have it made up professionally, and make it so it can be directly attached to the IO board instead of the currently solution of messy wires.
- The 6809 software is becoming a bit of a mess of helper routines, monitor code, and is getting difficult to extend. It would be nice to rewrite large parts of it into something resembling an Operating System with proper APIs and layers of abstraction.
- Yet another possibility exists. I've learnt a lot another programmable logic since I laid out the computer main board. I'm fairly sure I could implement some new custom circuits for the computer. This of course means throwing away the SBC I designed back at the beginning of the year, since it would require a new design for the core computer.
- Finally I could go off and look at a more advanced MPU and experiment with that. I have several MC68000 parts, of various types, that I've collected over the last few months.
And whilst it would be nice to redo the IO board and "tidy up" the current computer boards, I wouldn't really learn anything new from doing so. And learning new things is the key thing here for me with this project.
So I have decided to focus my efforts, over the next few months, on working on an updated core computer. There are a couple of areas I want to look at:
DMA - a simple Direct Memory Access Controller should be possible in XC9572 COLD. I've always been fascinated with DMA, ever since I first found out how the Amiga used it to deliver its amazing, for the time, sound and graphics.
MMU - a Memory Management Unit is perhaps overkill for an 8 bit micro, but it would be interesting to investigate what options exist for memory mapping. Whilst bank switching can be used to gain access to memories bigger the address bus normally allows, an MMU could be useful for isolating multitasking tasks from each other, mapping a task so it has access to the whole 64KByte space as RAM and so on.
For now I will focus on DMA ideas, since this is the more clear cut of the two.
Another thing I'm keen to do over the Christmas break is to make a video of the computer as it currently stands, to show of the hardware, monitor, and snake game. Unfortunately I lack even the most basic of equipment for this task...