



A few weeks ago one of the fellows on the Cosmac Elf mailing list spotted some surplus calculators based on the CDP1805 processor chip. This is a follow-on to the 1802 with a few extra instructions and a 64 byte onboard ram. Much discussion ensued and many of us bought the calculators for novelty value or to hack. I, of course, set out to re-program mine in C with the idea of implementing a “little language” that would output some sort of ascii art or patterns on the printer.

The baseline calculator dates from the eighties and was made as a contractors tool for doing math with feet, inches, and sixteenths.

Boyd Calculator Bottom of Case Boyd Calculator with case split top of display board bottom of display board display chip re-wired foor hex display Keyboard remoted with jumpers Hello World!

It’s very well made: all through-hole parts with no fewer than three processors. The 1805 does the actual calculations and there’s a seiko and an 80c49 that are dedicated to the printer. Opening it up we see a main PCB that has the 1805 logic and a 4K 2532 eprom. On top of that is a daughter board with the printer and display. The display uses a 7218B display driver that can be jumpered to display decimal digits plus a few characters or to display hex 0-F. Everything is nicely done with good quality connectors and screws for assembly.

Josh Bensadon on the mailing list went at the 1805 code with a will and produced a beautifully commented disassembly showing how the display, keyboard, and printer worked. The code is not concise but it’s pretty easy to follow (thanks Josh!).

I’ve replaced the 4K EPROM with a 2K flash EEPROM which is faster and easier for me to program. I re-jumpered the display chip to display hex instead of decimal. This gives up the ability to display blanks but it seemed worth it for hacking. To program it, I’m working in C to produce a hex file. That gets converted to a C header file for an arduino program that writes the EEPROM which I then move over into the calculator. It’s a bit outlandish but it works.

The code is standard LCC1802 but I have jiggered the epilog code to leave out the math routines. That keeps things pretty easily within the 2K EEPROM space. At first, when i was just displaying memory, working in C seemed painful but when i started implementing my “little language” it felt much better. So far the “language” is all two byte instructions:

00 xx is display the memory at location 10xx (that’s where the 64 byte ram is located)

01 xx is increment the memory location

02 xx is goto

03 nn is delay nn*4 ms

04 xx is display the bottom byte of the stack pointer(xx is ignored)

The monitor just implements the basic functions:

display memory, moving backward and forward with + and – keys

switch between the eeprom memory at 0 and the 64 byte onboard RAM at 0x1000 with the REM key

Change memory by pressing MS and two hex keys

Begin interpreting the program at the location counter by pressing the X key (times)

The 04xx instruction displays 20 meaning that the stack has used up 32 of 64 bytes leaving 32 for the “little language” program. I haven’t tried but i would think I could improve that. For example main() gets entered by subroutine call and saves 4 registers but it’s never going to return or restore them. Execute() similarly saves 4 registers so if i had to move that inline in main()I’d have 16 bytes free right there.

Looking at the compiled program It uses the EEPROM up to 0x5B6 leaving another 500-600 bytes for “little language” features. After all – I have a total of 2,112 bytes of memory. Surely that ought to be enough for anybody!

Here’s the working version of the monitor/interpreter and one assembly include where I adapted some of josh’s disassembly of the original code. The keyboard is conceptually a matrix of 7X4 keys. A row of keys is activated by doing an OUT to port 1,2,3,4,5 or 6 or by setting Q and pressing a single key will assert one of EF1 to 4.

#include "olduino.h" #define initleds() asm(" req

seq

dec 2

out 7

req

") unsigned char boydscan(); void boydinc(){ asm(" include \"boydscan.inc\"

"); } void disp1(unsigned char d){//display a byte as two hex digits asm(" glo 12

ani 0x0f

" //prep bottom digit " dec 2

str 2

out 7

" " glo 12

shr

shr

shr

shr

" //prep top digit " dec 2

str 2

out 7

" ); } void dispmemloc(unsigned char * loc){ register unsigned int lint; initleds(); disp1(*(loc+1)); disp1(*loc); lint=(unsigned int)loc; disp1((unsigned int)loc&0xff); disp1(lint>>8); } void dispval(unsigned char v){ register unsigned int i; initleds(); disp1(v); for (i=6;i!=0;i--) out(7,0); } unsigned int getsp(){//return stack pointer value asm(" cpy2 r15,sp

" //copy stack pointer to return reg " cretn

"); //return it to the caller; return 0; //not executed } unsigned char * execute(unsigned char * loc){ unsigned char op,val; unsigned char * mp; while(1){ op=*loc; val=*(loc+1); switch (op){ case 0: //display memory at mem[val]; mp=(unsigned char *)(4096+val); dispval(*mp); delay(1000); break; case 1: //increment location val mp=(unsigned char *)(4096+val); *mp+=1; break; case 2: //goto val loc=(unsigned char *)(val+4096-2); //ugh break; case 3: //delay val*4 ms delay(val*4); break; case 4: //display stack pointer; dispval(getsp()); delay(250); break; default: dispval(0x41); delay(250); dispmemloc(loc); delay(5000); break; } loc+=2; } return loc; } void main() { unsigned char * loc=0; unsigned char memtype='o'; //displaying o=eeprom,a=ram unsigned char k,k2; dispval(0x42); delay(1000); while(1){ dispmemloc(loc); k=boydscan(); switch(k){ case 16: //+ loc +=1; break; case 17: //- loc -=1; break; case 18: //rem if (memtype=='o'){ loc=(unsigned char *)4096; memtype='a'; }else{ loc=(unsigned char *)0; memtype='o'; } break; case 19: //ms dispmemloc(loc); //makes a blink k=boydscan(); dispval(k); delay(250); k2=boydscan(); dispval(k2); delay(250); *loc=(k<<4)+k2; break; case 20: //X for execute dispval(0x45); delay(250); loc=execute(loc); break; default: dispval(k); delay(250); } } } #include "olduino.c" //for the delay routine

_boydscan: ;SCAN THE KEYBOARD sex r14 ;set up "don't care" X register rldi r15,0 ; r15 is return value .scan: OUT 1 ;109: 61 B1 .KEY_12 ;10A: 34 50 B2 .KEY_8 ;10C: 35 60 B3 .KEY_4 ;10E: 36 70 B4 .KEY_0 ;110: 37 80 OUT 2 ;112: 62 B1 .KEY_13 ;113: 34 54 B2 .KEY_9 ;115: 35 64 B3 .KEY_5 ;117: 36 74 B4 .KEY_1 ;119: 37 84 OUT 3 ;11B: 63 B1 .KEY_14 ;11C: 34 58 B2 .KEY_10 ;11E: 35 68 B3 .KEY_6 ;120: 36 78 B4 .KEY_2 ;122: 37 88 OUT 4 ;124: 64 B1 .KEY_15 ;125: 34 5C B2 .KEY_11 ;127: 35 6C B3 .KEY_7 ;129: 36 7C B4 .KEY_3 ;12B: 37 8C OUT 5 ;12D: 65 B1 .KEY_DIV_WHOLE ;12E: 34 99 B2 .KEY_MUL ;130: 35 96 B3 .KEY_SUB ;132: 36 93 B4 .KEY_ADD ;134: 37 90 OUT 6 ;136: 66 B1 .KEY_REM ;137: 34 A5 B2 .KEY_MEM_STORE ;139: 35 A2 B3 .KEY_MEM_RECALL ;13B: 36 9F B4 .KEY_EQU ;13D: 37 9C SEQ ;13F: 7B B1 .KEY_DIV_FIS ;140: 34 B1 B2 .KEY_CLEAR ;142: 35 AE B3 .KEY_CLR_ENTRY ;144: 36 AB B4 .KEY_INV_SIGN ;146: 37 A8 REQ ;148: 7A ;here we have no keys pressed, if r15.0 has a value, return it -1 glo r15 bz .scan dec r15 sex r2 ;restore the X register before returning cretn .KEY_12 LDI 13 ;150: F8 C BR .KEY_SAVE ;152: 30 B4 .KEY_13 LDI 14 ;154: F8 D BR .KEY_SAVE ;156: 30 B4 .KEY_14 LDI 15 ;158: F8 E BR .KEY_SAVE ;15A: 30 B4 .KEY_15 LDI 16 ;15C: F8 F BR .KEY_SAVE ;15E: 30 B4 .KEY_8 LDI 9 ;160: F8 8 BR .KEY_SAVE ;162: 30 B4 .KEY_9 LDI 10 ;164: F8 9 BR .KEY_SAVE ;166: 30 B4 .KEY_10 LDI 11 ;168: F8 A BR .KEY_SAVE ;16A: 30 B4 .KEY_11 LDI 12 ;16C: F8 B BR .KEY_SAVE ;16E: 30 B4 .KEY_4 LDI 5 ;170: F8 4 BR .KEY_SAVE ;172: 30 B4 .KEY_5 LDI 6 ;174: F8 5 BR .KEY_SAVE ;176: 30 B4 .KEY_6 LDI 7 ;178: F8 6 BR .KEY_SAVE ;17A: 30 B4 .KEY_7 LDI 8 ;17C: F8 7 BR .KEY_SAVE ;17E: 30 B4 .KEY_0 LDI 1 ;180: F8 0 BR .KEY_SAVE ;182: 30 B4 .KEY_1 LDI 2 ;184: F8 1 BR .KEY_SAVE ;186: 30 B4 .KEY_2 LDI 3 ;188: F8 2 BR .KEY_SAVE ;18A: 30 B4 .KEY_3 LDI 4 ;18C: F8 3 BR .KEY_SAVE ;18E: 30 B4 .KEY_ADD ldi 16+1 br .key_save .KEY_SUB: ldi 17+1 br .key_save .KEY_MUL: ldi 20+1 br .key_save .KEY_MEM_STORE: ldi 19+1 br .key_save .KEY_REM: ldi 18+1 br .key_save .KEY_CLEAR: req ldi 20+1 br .key_save .KEY_INV_SIGN: req .KEY_CLR_ENTRY: req .KEY_DIV_FIS: req .KEY_EQU: .KEY_DIV_WHOLE: .KEY_MEM_RECALL: .KEY_SAVE: plo 15 br .scan

Below is a video of the hoops I’ve been jumping through to load code into the calculator



And here’s the calculator baseline function:

