After several frustrating days and a lot of captures from my logic analyzer I am happy to report that I finally have the AVR integrated with the system, along with the basics of PS/2 keyboard support!
I did have to make one small sacrifice, which is to move my as-yet-unimplemented game pad support from the AVR to the VIA. This was done to free up enough pins on the AVR to let me go back to an external 16 MHz resonator, as running on the internal 8 MHz clock was proving to be a bit too slow to keep up. Game pads don’t support interrupts anyway so this is not going to be an issue at all.
How it works
To begin let me explain the actual hardware connection between the VIA and the AVR (an ATmega 328p):
The connection is a bidirectional parallel bus between port A of VIA #1 and port D of the AVR. Data transfer handshaking is handled by the VIA’s built-in handshaking support., which uses the CA1 and CA2 lines to act as Data Ready and Data Acknowledge signals. When sending, the VIA generates Data Ready pulses on CA2; the AVR acknowledges the bytes by sending Data Acknowledge pulses on CA1. Receive mode works the same way except the meaning of the CA1 and CA2 lines is flipped; note that this means CA1 is always the input and CA2 the output.
Since the VIA and AVR are sharing a bidirectional data bus it is important to make sure only one device is trying to drive the bus at any given moment. This is controlled by the VIA using the DDIR line; it is high when the VIA is driving the bus and low otherwise.
Data Transactions
Every transaction starts with a data packet from the VIA, followed by a response packet from the AVR. The packet format is very simple and consists of a packet type, payload length, and the payload:
+0 | Packet Type |
+1 | Payload size, low byte first |
+3 – +514 | Payload (optional) |
The payload, when present, can be up to 512 bytes in length; this number was chosen to allow a single SD card block to fit in a packet. The payload length can even be zero, in which case the entire packet is just three bytes.
A transaction starts when the VIA drives DDIR high; this signals that it’s about to start transmitting a packet. The VIA then transmits the packet in its entirety before pulling DDIR low again and going into receive mode. It then waits for the AVR to transmit the reply packet, after which both sides fall back to their idle state.
The AVR never sends data unless it is replying to a request from the VIA. It can, however, generate an interrupt by driving the CB1 input to the VIA low. A GET_STATUS request can then be sent to the AVR to determine the source of the interrupt.
The AVR Implementation
On the AVR side all of this is implemented as a state machine, driven almost entirely by pin change interrupts:
The only part that is not interrupt-driven is the transition from the RECEIVED state to the SENDING state. The actual packet processing is done in the AVR’s main loop, and the act of queuing the reply is what triggers that state transition.
There is one small difference between the diagram above and the actual implementation, and that is the handling of DDIR going high. In the code as implemented a DDIR low-high transition will ALWAYS transition to the RECEIVING state, regardless of the current state. I did this so that it is possible to recover easily from a botched transaction without needing to reset the AVR.
PS/2 Keyboard Support
With the VIA and AVR finally talking reliably I moved on to implementing the PS/2 keyboard driver. This was much easier and took me maybe half a day.
Like the MPU interface, the PS/2 driver is interrupt-driven, this time by the PS/2 clock signal, which is always generated by the device. I won’t take the time to diagram it here since there are quite a few examples on the net of how to do this, and my implementation is nothing special.
As it stands now I am able to send and receive bytes with a PS/2 keyboard; this means I can receive key up/down events as well as control the LEDs, configure the typematic key repeat, and other fun things. I need to implement two more pieces to make this a complete solution however:
- Implement interrupts when the AVR has buffered data from the keyboard
- Decoding of key scan codes into actual ASCII that the rest of the computer expects. I am leaning towards doing this on the ‘816 side.
These two tasks are my focus for at least the beginning of the week. I’ll post another status as soon as I’m able to type to the computer without using the serial port!