My latest foray into the world of bodging together an agglomeration of electronics into something resembling a computer has reached that point where I take pictures of it and put them on the internet. I started work on it on October, and finished most of it by Christmas. This week I did Reversi, and today (yesterday now), this unnecessarily long post about the whole thing.
And yes, I've implemented Tetris on it.
For want of a better name, it's called Albert. It's based on a variant of the Z80 microprocessor, which was used by the Sinclair Spectrum and other home computers of its time, including my first computer, the Memotech MTX512. Building the Albert is a step up from my last attempt at a home-made computer, the hand-held Pickle, so called because it used PIC microprocessors.
A PIC, being a microcontroller, has all its code and memory as well as a number of I/O devices on the chip. However, a microprocessor such as the Z80 needs to be provided with memory and I/O devices that connect to it via a data bus, address bus and control lines. So the block-diagram of my Z80 machine looks like this:
I've shown all the basic connections between modules at a general level, rather than getting bogged down in all the individual components and wires. All the capacitors, resistors, and anything to do with power or ground, aren't shown - just assume every device has a 5V input. Particularly, I haven't shown the Z80's control lines that tell the I/O devices and memory chips whether it wants to read or write, and whether this read or write is supposed to go to an I/O device or to the memory. This makes the block diagram above considerably neater than the circuit inside the box actually is.
When the Z80 starts up, it fetches instructions from memory and executes them sequentially, starting with the instruction at memory location 0. The EEPROM is mapped to this region of memory (0000-0FFF hex) using address decoding logic that basically puts the most significant address bus lines through a series of NOR gates.
I've programmed the EEPROM with the bare minimum code to set up the serial port, load a program into memory from the serial port if necessary (very useful in development, but not really so any more, now it can boot from the SD card) and jump into the program it's just loaded. I've set it up so it loads the first sector from the SD card (luckily the display/SD card module provides commands to manipulate the SD card at the sector level as well as the file level). This sector, known as the Master Boot Record, contains code to find the first FAT16 partition, load the code contained in the first sector of that and run it, and that in turn contains code to load into memory at 1000h (which is where the RAM starts) a file on the SD card called SYSTEM.BIN, which is the program that accepts commands from the user and runs them. Especially in the first two of those three stages, this is pretty much the same as how an ordinary computer boots up. This is a very basic shell, whose job is only to ask the user what .BIN file they want to run, load it into memory at location 8000h, and jump into it.
How did I get the code onto the EEPROM in the first place? By building a separate EEPROM programmer that connects to a real computer's serial port, so I can plug the EEPROM chip into it and send the code to it. That should really be the subject of a different article, otherwise this could go on forever.
SYSTEM.BIN also contains, at fixed memory locations, code to jump to certain low-level routines that applications might find useful, for example, code to draw a window on the screen, or accept a keypress from the user, or draw a shape on the display. This is the system call table, and applications that run at 8000h interact with the hardware using these system calls.
The screen above is after I've run "ls" which is a program that lists the contents of the SD card's root directory, "bat" which is another program that uses the system call to get the battery level from the PIC, and the built-in command "time" which (and here's roughly the same path in a bit more detail) causes the shell program to call the system call that tells the UART to send a message to the PIC to ask it to ask the RTC what the time is, so the RTC can tell the PIC what the time is so it can send it back to the UART so it can interrupt the CPU and give it this information so the system call can return and you can get the date and time on the screen. It's a wonder anything works properly when you consider all the hoops a piece of information has to jump through to get to the user.
Perhaps the most important, and certainly the most expensive, device on the computer is the display. It's a 4D Systems uOLED-32028-P1T display, which is not only a 2.8" quarter-VGA display but also an SD card reader. The processor talks to it, via a UART as shown on the diagram, by a simple serial protocol that is well-documented. This means you can send commands to it like "draw a circle here" or "draw this text there" or "send me the file on the SD card called foo.txt".
The display module also has on it a tiny 8 ohm speaker, which you use by putting a WAV file on the SD card and then sending the module the appropriate command to play that WAV file. You might have spotted "TETRIS.WAV" on the directory listing in one of the images above. Yes, when playing Tetris you can press S to make it play the original Tetris music at a volume so pathetically low you have to press your ear against the little holes next to the display to hear it properly. This tends to affect one's success at the game, so the music is mostly left off.
The computer also has a PIC which controls most of the other devices. The CPU talks to the PIC via the UART. The PIC is connected to a number of things:
The PS/2 port. The PIC is programmed to accept keypresses and send them to the CPU. The UART interrupts the CPU whenever it gets data, so the computer gets told of any keypresses. The computer accepts input from any off-the-shelf PS/2 keyboard. The real-time clock, which is a battery-backed module I bought from Cool Components. The PIC talks to this using I2C, which was the protocol the Pickle's PICs used to talk to each other. The green and red LEDs. The green LED is normally left on and the red one off, but they can be modified if the CPU sends the PIC the appropriate commands. The PIC samples the battery voltage as well (actually, half the battery voltage, for reasons marginally less interesting than it would have to be to make me explain it here), and when it drops below 7.1V it flashes the red light. When it drops below 7.0V, the voltage regulator which supplies power to everything in the system isn't specified to work properly (it's supposed to have an input voltage at least 2V higher than the output voltage, which is 5V), so it drops the non-maskable interrupt pin of the CPU. This causes the CPU to immediately branch to a particular address in the EEPROM, where I've put code to tell the display to switch off and the CPU to halt.
All applications interact with the hardware through a (mostly) well-defined interface. They're all written in Z80 assembler and assembled using the assembler that comes in the GNU z80-binutils package.
I've implemented Pong before - it was for a first-year project at university. The computer was a breadboard-wired thing with most of the components provided, and the output device was an oscilloscope.
This Z80 version has three modes: 1-player, 2-player and 0-player. I ran it in 0-player mode, where the AI plays against itself, and it managed to run for long enough without crashing that I could take the picture below. I thought I'd solved the suddenly-locking-up-and-ignoring-all-user-input bug, but it appears to have come back. All I can tell is that it's sat waiting for the display to acknowledge a command. Anyway, here's Pong running properly for a brief instant, but obviously not too brief as the camera managed to capture four frames in the time its shutter was open.
The Albert version of Reversi is basically more of the same. It's even got a scrolling input/output text window for you to enter your move and read messages. The text window code is all in SYSTEM.BIN and text windows are manipulated using system calls. For the pictures above and below, I've left the computer playing against itself. It looks two moves ahead and uses the ordinary algorithm: What's the best move to a depth of 2? Well, try each possible move, work out how many counters you get, and subtract the score of the best move the opponent can make in reply. How do you work that out? Work out the best reply to a depth of 1, which is trying each possible move, working out how many counters you get, and subtracting the score of the best possible move the other player can reply with. And how do you work that out? Work out the best reply to a depth of 0, by trying each possible move, and find which gives the most counters. Don't recurse any further down, as depth is now zero.
In complex mid-game positions it can take the computer up to about ten seconds to decide on its move, so I decided increasing the lookahead depth to 3 would slow things down too far. In the pictures, the number in brackets after the computer's move indicates the "score" it gave to that move, taking into account the likely reply move and the likely reply move to that. The numbers of large magnitude towards the end are because the AI values a winning and losing position as +50 and -50 points respectively.
Everyone knows what this game is like. Everyone who has ever played the game knows that all Tetris implementations, no matter how fair the programmer intended to be, have a mysterious and difficult-to-pin-down bug that causes it never to give you the long thin piece until after it's no longer useful. Indeed there are implementations which actively promote this as a feature.
My implementation (to my knowledge) has no such feature, and nor does the one I did for the Pickle, but it didn't stop me getting a less-than-average score when playing it to take pictures today.
Note the red light has come on. That's the "your six AA batteries are kicking out less than 7.1 volts across them, you probably want to do something about this" warning.
All computers have an analogue clock somewhere on them. In Z80 assembly, it's the programmer's way of smugly showing that they know how to build a lookup table that vaguely approximates the sine function, so the hands point to the right places, even though this is quite a lot easier than it first sounds. Look, the minute markers are only slightly wonky!
Here's a modified version of the clock application, the now-traditional random clock-based drink-recipe generator "Clocktails".
Even when switched off, your face appears in the display if you look at it from the appropriate angle.
There's still scope for improvement, though; the Pickle has a GPS module in it, but this doesn't. It does, however, have the ability to read the GPS output from the Pickle's serial port. I just haven't written the program to do that.
I also need to look into getting an appropriate C compiler so I can write applications easier. Z80 assembly is reasonably logical and flexible, but time-consuming to write. That's not helped by my habit of giving labels incredibly long names to explain exactly what they do and why they're there.
So, what next? Yes, other than "a life"? Don't know. GPS, probably.