In this update I’m talking about SPIDriver’s firmware. SPIDriver is hardware, of course, but I wrote the firmware first. In fact, most of the effort on the project has been spent on the firmware: iterating on the code itself and writing test harnesses for it.
The firmware code is all on GitHub.
SPIDriver’s hardware is fairly minimal. There’s an FTDI 230X UART, an LCD panel, current and voltage sensors, and the microcontroller. The microcontroller’s job is to drive the UART and SPI ports, run the various analog sensors, and of course drive the graphics display, which is a bitmapped 1.8" LCD 128x160 color panel. The 8-bit controller is working quite hard to update the whole screen 50 times per second, while also transferring data between UART and SPI.
SPIDriver is the first of a number of projects I’m planning on releasing this summer. (I’m talking about the next ones in my weekly newsletter). But they all use the same microcontroller. By using the same one, I can reuse code, tooling, and knowhow between projects.
The microcontroller is a Silicon Labs EFM8. The EFM8s are cheap, speedy, robust, and best of all are supported by MyForth. MyForth is a programming language and development setup written by Forth veterans Charley Shattuck and Bob Nash. It’s both a high-level Forth-like language and very specific to the 8051 CPU, which makes it ideal for SPIDriver because its graphics need plenty of optimized assembly.
The particular controller has 4K flash and 512 bytes of RAM. It runs at 27 MHz from an internal oscillator.
MyForth is a Forth in the Machine Forth family. The difference between it and "standard" Forth is quite fundamental. In a standard Forth the idea is to hide as much of the underlying machine as possible. In a Machine Forth the idea is to expose it as much as possible. Abstraction is your job as the programmer, it’s not done by a compiler or OS.
When you look at Chuck Moore’s ColorForth work, this happens in a few lines. His code very rapidly abstracts the underlying machine just enough for the job at hand.
While I can’t match Chuck Moore for compactness, it’s still surprising to me how much a little bit of MyForth can do.
There are four threads running on the SPIDriver:
The first three all run on interrupts, while the graphic updates run as a background thread using all the spare cycles.
The first two are ‘lightweight’ - they have very little context. But the last two are full Forth threads with their own stacks and register sets. Each is assigned 60 bytes of stack space.
The whole thing fits in a little under 4K, of which 3K is code and 1K is font graphics. Fitting a nice antialiased font in 1K requires some kind of trick. The trick in this case is to only include the actual characters used by the text on the screen. All the on-screen text can be drawn using only the characters "ABCIKMOSVm" - so these are the only ones included in the font:
ABCIKMOSVm is all you need.
The hex digits are a different, smaller font. I was really pleased to find this collection of tiny but readable fonts. It’s a rare thing to find small fonts that are antialiased; almost all tiny fonts are made with black/white pixels only. Particularly important is that the digits are legible: for example 0 and D are quite different. This "mini" hex font encodes only takes up a couple of hundred bytes:
The whole setup took me back to my first job writing games for incredibly crappy British 8-bit home machines, like this one. Our 8-bit CPUs are much quicker than those original ones. The EFM8 can run at 27 MIPS and costs about 45 cents. Those numbers were very different for a 80’s era Z80 or 6502!
Regression tests are cool.
You can write code anytime and anywhere, and if your tests are strong enough, you can be confident that you’re not introducing new bugs. SPIDriver has a regression suite based on a small 8051 software simulator. This 8051 simulator is 200 lines of code, and it simulates just enough of the CPU and the LCD display to produce images.
The test cases cover all the low-level building-block functions, of course. But they also simulate enough of the LCD display to produce output images, and the test suite runs the rendering code on the 8051+LCD simulator.
In about 100 ms the test suite has exhaustively exercised all the major functional paths. This is really nice when optimizing the graphics code; I can focus on speedups without having to worry if something has quietly broken.
Of course, running on the real hardware is important too. Because the SPIDriver keeps a running CRC of all SPI traffic, long ‘soak’ tests can confirm that there are no lurking data corruption problems. Another way of confirming end-to-end integrity is to run with a Gameduino 3 (all the Gameduino 3 examples run on SPIDriver); any data loss is obvious: