Skip to content

Reprogramming an 1805-Based Calculator in C

April 15, 2017

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.

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\n seq\n dec 2\n out 7\n req\n")
unsigned char boydscan();
void boydinc(){
	asm(" include \"\"\n");
void disp1(unsigned char d){//display a byte as two hex digits
	asm(" glo 12\n ani 0x0f\n" //prep bottom digit
		" dec 2\n str 2\n out 7\n"
		" glo 12\n shr\n shr\n shr\n shr\n" //prep top digit
		" dec 2\n str 2\n out 7\n"


void dispmemloc(unsigned char * loc){
	register unsigned int lint;
	lint=(unsigned int)loc;
	disp1((unsigned int)loc&0xff);
void dispval(unsigned char v){
	register unsigned int i;
	for (i=6;i!=0;i--) out(7,0);

unsigned int getsp(){//return stack pointer value
	asm(" cpy2 r15,sp\n"  	//copy stack pointer to return reg
		" cretn\n");		//return it to the caller;
	return 0;				//not executed
unsigned char * execute(unsigned char * loc){
	unsigned char op,val;
	unsigned char * mp;
		op=*loc; val=*(loc+1);
		switch (op){
			case 0: //display memory at mem[val];
				mp=(unsigned char *)(4096+val);
				dispval(*mp); delay(1000);
			case 1: //increment location val
				mp=(unsigned char *)(4096+val);
			case 2: //goto val
				loc=(unsigned char *)(val+4096-2); //ugh
			case 3: //delay val*4 ms
			case 4: //display stack pointer;
				dispval(0x41); delay(250);
				dispmemloc(loc); delay(5000);
	return loc;

void main()
	unsigned char * loc=0;
	unsigned char memtype='o'; //displaying o=eeprom,a=ram
	unsigned char k,k2;
			case 16: //+
				loc +=1;
			case 17: //-
				loc -=1;
			case 18:	//rem
				if (memtype=='o'){
					loc=(unsigned char *)4096;
					loc=(unsigned char *)0;
			case 19: //ms
				dispmemloc(loc); //makes a blink
				k=boydscan(); dispval(k); delay(250);
				k2=boydscan(); dispval(k2); delay(250);
			case 20: //X for execute

#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

.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
		ldi	20+1
		br	.key_save
.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:

From → Uncategorized

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: