Nanocomp is a single-board computer based on the Motorala M6802 running at a clock speed of 812kHz, described in an article by Bob Coates in the January, 1981 issue of Wireless World. The 6802 contains 128 bytes of RAM, and the board has an additional 1k of RAM and a 2k ROM containing a monitor program. There's also a 6821 Peripheral Interface Adapter for I/O, through which the on-board hex keypad and seven-segment displays are driven. At the time, my father was moving on from amateur radio to computing, and he sent off by mail order for a PCB and a set of chips and, after building the board and putting it in a home-made case, spent many pleasant evenings hand-translating programs into 6802 machine code and loading them with the hex keypad. Later, he built the interface that let you save your programs on an audio cassette.
Recently, he passed the board on to me, still in full working order, together with a charming programming reference manual for the M6800 that, even at one instruction per page, comes in at well under 100 pages. I replaced the clunky power supply with a USB cable, but wondered what to do about the cassette interface, until it occurred to me that if the audio signal could be produced by a 812kHz 6802, then surely the same signal could be produced by a modern microcontroller such as an AVR. So I made up a small board with an eight-pin AVR running at 8MHz that can be plugged into the expansion port and powered by the Nanocomp. Rather than saving the contents of the RAM after laboriously keying in hand-translated hex, you can now write in assembly language on a Linux machine, then use the AVR board to download the code into the Nanocomp.
The steps are these: first, the 6800 code is assembled. The resulting binary becomes a table of data that can be linked with a small C program that will run in the AVR, and the combination can be loaded into the AVR chip using an ISP programmer.
Then the AVR board is plugged into the Nanocomp, and you go through the motions of preparing the Nanocomp monitor to load a program from cassette. When the moment comes to press Play on the cassette recorder, instead you press a button on the AVR and it produces an audio signal from the table of binary data, loading it into the Nanocomp.
Assemblers for the 6800 are few and far between, so it seemed easiest to hack one together. I used TCL to avoid the problem of parsing the input file, instead making each line of assembly language into a call to a TCL procedure that adds an instruction to the program. Embedding in TCL makes the full power of the language available for programming the equivalent of high-level macros. The assembler's output is C source for the table of machine code bytes, rather like the way bitmaps used to work in X Windows. I had a dump of the ROM contents, so I disassembled it by hand, using my assembler to check that the commented source code I wrote did exactly agree with the ROM. In the process, I also naturally tested that the assembler produced correct code for a representative sample of instructions. The disassembly allowed me to work out the details of the audio signal used by the cassette interface, with confirmation from an hour spent scrolling through a long single-shot capture with the scope. I remember, from the golden year when I was allowed to help some people at Tektronix make an oscilloscope, that good record length is not something that should be subject to compromise. Sadly, that's the reason my el-cheapo oscilloscope is an Agilent-aka-Keysight (or really a rebadged Rigol) and not a Tek scope.
It's good to find a proper use for ancient hardware. Since the Nanocomp has a reasonably stable crystal-controlled oscillator and six digits of seven-segment display, it seemed a good idea to program it as a digital clock. Using the CPU clock for timing means writing a main loop that takes an invariable number of clock cycles. Conditional branches are forbidden unless the code uses the same number of cycles whether the branch is taken or not; inner loops are impossible unless they take a fixed number of iterations.
In each iteration of the main loop, the clock program increments a 32-bit counter with a calibrated constant, determined by measuring the loop frequency with an oscilloscope. The addition is done in a constant time by using a sequence of add-with-carry instructions, and the calibration constant is chosen so that the counter overflows once per second, with any remainder carried over to the next second. The carry condition from incrementing the 32-bit counter is used to increment another counter, this time constisting of six BCD digits, with overflow implemented appropriately for a 24-hour clock. The display is refreshed once per iteration by converting the BCD digits into seven-segment code using a built in table. Incrementing the time without using any conditional branches is a nice exercise in bit manipulation, and the temptation to use one of the 6800's well-known 'undocumented' instructions is hard to resist. An extra pulse on an otherwise unused I/O pin makes it easy to attach an oscilloscope and measure the loop frequency for calibration.
To start the clock:
- Press RESET then L.
- Activate the Nanoboot board by pressing the button; wait for F to appear in the display.
- Press RESET then G and enter the address 1000.
- Enter a 24-hour time in the format HHMM and press I.
(Read-Only Memory). A form of storage whose contents are non-volatile (are not lost when the power is off) but cannot be changed under program control. Modern ROM is usually EEPROM – Electrically Erasable Programmable Read Only Memory, and can be changed electrically, and even under control of a program running on the microcontroller, but using special peripheral registers and not the normal store instructions. Flash memory is a modern, super-compact implementation of EEPROM, but for our purposes it does exactly the same job. We will modify the contents of the micro:bit's flash memory by downloading programs, but we will probably not be writing programs that change the contents of the flash memory.
A single integrated circuit that contains a microprocessor together with some memory (usually both RAM for dynamic state and ROM for storing a persistent program) and peripheral interfaces.
A symbolic representation of the machine code for a program.