Now that FAP’s video card is finished, it’s time to move on. Although we’ve come so far, FAP is still missing something crucial in order to be called a proper computer, it still needs at least one input device. That will be the keyboard. More specifically, a PS/2 keyboard.
Using PS/2 keyboard for retro computers is nothing new thanks to its simple protocol and wide availability. It uses a simple synchronous serial interface, one clock line and one data line, and the documentation is all over the internet so I’m not going to bother to explain here again. Actually I didn’t bother to read much about it at all because of a reason you’ll see soon enough.
Before we start working on keyboard though, we need to first figure out some way for keyboard to talk to the CPU. Generally there are two means of communicating with peripherals, memory mapped I/O or port I/O. In memory-mapped I/O peripherals’ registers are mapped to the memory address space, and CPU writes or reads its memory address to control the peripheral. I used this method for FAP’s video card. For the keyboard I decided to use port I/O, since it’s usually used with external peripherals, and I feel it’s a good exercise to try a little bit of everything with Z80, which is why I’m doing this in the first place. The Z80 supports 256 ports, when the user calls in or out instruction it places the lower 8 bit of the register on to the address bus, and activates IORQ and one of the RD or WR lines to read or write a byte from that port.
With the I/O method sorted, next issue is how to let CPU know the keyboard events. One way is polling, in which the CPU constantly asks keyboard if it has something. This approach is simple but extremely wasteful, since CPU will spent most of its time reading from keyboard instead of doing actual work. A much better way is using interrupts. As its name suggests, when there is actually data from keyboard, the CPU will be interrupted from its work, it then can store the keystroke into a buffer and do something with it later. This way the CPU does not waste anytime polling the keyboard, and only acts when there is an actual key press, and that is what I’m going to use today.
The Z80 has a (seen from today)somewhat rudimentary but still rather clever interrupt system. There are two interrupt pins on the package, INT and NMI, the former is for regular interrupt that can be disabled in software, the latter is non-maskable interrupt and always occurs when the line goes active. For the regular interrupt, 3 interrupt modes are provided, mode 0 is a 8080-compatible mode where when interrupted, you have to put some instruction on the data bus for the CPU to execute. It’s basically witchcraft so I’m not going to use it. Mode 1 is much simpler, it just jumps to address 0x38 when interrupted. This makes designing simple systems very easy since you can just put your interrupt handler there, or a jump instruction to jump to somewhere else if you need more space. However, if in mode 1 there are more than one interrupting devices, the CPU will have to figure out who is the one that initialized the interrupt, which gets complicated real fast.
That’s where interrupt mode 2 comes into play. I’m going to list how mode 2 works off the top of my head since I worked with it for so long, it’s going to be a mouthful and I hope I get it right: When interrupt fires, the interrupting device puts a byte onto the data bus, the CPU then combines the 8 bit in the interrupt vector base register and the 8 bit on the data bus to form a 16 bit address, jumps to that, read a 16-bit word at that address, which is the interrupt service routine address, then make another jump to that. For example, if I load interrupt register with 0x12, and put 0x10 on the bus after interrupting the CPU, the CPU will jump to 0x1210. If I put data 0x3000 there beforehand, the CPU will then read a word from 0x1210, gets 0x3000, and jump to 0x3000, which is where the handler is. It’s extremely confusing at first, but once you get the hang of it you’ll realized this vectorized interrupt system is much more powerful than other modes, you can change the location of ISR on the fly, and it supports a huge number of interrupting devices. It might be just a keyboard for now, but I’m also going to add a WiFi module in the future, which takes 2 ports, a timer wouldn’t hurt either. Anyway, the thing is that FAP is going to have a couple more peripherals later on, and interrupt mode 2 is the best way for it.
Now let’s recap the entire interrupt process to see what my keyboard controller needs to do: When user presses a key, the controller pulls the INT line low, wait until M1′ and IORQ’ both goes low (we’ll call it INTACK’, short for interrupt acknowledge), put the interrupt vector on the data bus, wait until INTACK goes high, then stop driving the data bus. Sounds like a lot of work, but I can just use 2 chips for that. A 74HC32 OR gate to generate INTACK’, which is then tied to OE’ of a 74HC245 buffer, it’s bidirectional but in this case I’ll just set it to a single direction. This way, the interrupt vector on the other side of the buffer is gated to the data bus the moment INTACK’ goes active. The CPU will then go to the ISR after 2 jumps.
Things gets slightly more complicated at the ISR though. The CPU needs to read the keyboard port to see what key user just pressed, that needs some decoding logic, the keyboard controller also needs to respond to CPU’s port read just in time, otherwise the CPU will get some garbage data. To achieve this I’m going to use an additional 2 chips, a 74HC688 8-bit equality detector and a 74HC573 transparent latch. The keyboard controller will latch the keyboard data , and when CPU performs a read, the ‘688 compares the port address and put the latched data onto the data bus.
So to sum it all up again, here is how my keyboard controller will work: The controller receives a byte of the key press, put the data on the latch, enable the latch, pull down INT’, wait for INTACK’, put interrupt vector on the data bus, and pull up INT’ and turn off latch when INTACK’ goes away. The cpu will then try to read from port 0, an IORD’ signal is generated when both IORQ’ and RD’ are low, IORD’ is tied to the OE’ of the latch, which enables it when it’s active, releasing the keyboard data onto the data bus for CPU to read.
For reading PS/2 signal itself I used an Arduino Pro Micro that I have laying around, while the AVR chip in Arduino itself might seem slow and out of date compared to the 32-bit microcontroller today, what’s unbeatable is its incredible communities. There is probably already an Arduino library written for every single thing you can think of, and for PS/2 it’s really a doddle. 2 minutes of google search got me the library, and the rest is just hooking up 2 wires. The Arduino reads the keypress and send out a byte of ASCII code to the main controller, which is a STM32F103 dev board, that manages the interrupt activity. Below is the schematics:
As you can see there are already 6 chips right off the bat, and it’s just the start. Looks like the I/O board is going to be the most complicated board in my FAP computer.
Here is the finished board:
I also wrote a short test program for the new I/O board:
Note how my program start from 0x100 now, it’s customary to start Z80 program at that location because there are reset vectors at $0000, $0008, $0010, $0018, $0020, $0028, $0030, $0038, I guess you can put code there if you’re not using RST instruction, but it’s just good practice to leave them alone. My code sets up stack, load interrupt register with 0x0, select interrupt mode 2 and enters an idle loop. When keyboard interrupt comes in the cpu will jump to 0x10, then jump to 0x3000, where the ISR will read a byte from I/O port 0 and print it on screen.
Here is the corresponding STM32 interrupt controller code snippet:
The interrupt controller turns off latch and interrupt on startup, then start listening from the Arduino that decodes PS/2 commands, once it receives a keypress byte it put that byte on lower 8 bits of PORTA, enable the keyboard data latch, then activities Z80’s INT. It then waits for INTACK’ to finish, after which it pulls INT line high again and disables the keyboard data latch.
Does it work? Watch the video:
As it turned out it works surprisingly well, of course that’s not how it looked like the first time round. At first the FAP would print a few characters, and goes back and print from the beginning, it was clock speed depend as well, as it didn’t happen quite as often at slower clock speeds. A bit of debugging later I found that the CPU would randomly jump to 0x66, and since there’s nothing there, it would slide a bunch of NOP’s all the way to 0x100, and start executing the main program from beginning. As it happens 0x66 address is where NMI will jump to if the line is active. It’s the goddamn noises again, and it also looks like the pull up from main stm32 controller is too weak. A few more filtering caps and a dedicated pullup resistor on NMI later, it’s working like a dream.
Next step? WiFi. More specifically, I want to use my FAP as a IRC client to use on Twitch chats, now we have the video card and keyboard, all that’s left is some way to stream IRC data to FAP, which I’ll tackle in the next post. It’s all getting very close now.