Skip to content

Goodbye Mr Chips



I got another end-of-life parts warning from digikey today.  This time for the 6 pin ATTINY10 I use to generate the millisecond interrupt for the olduino/z.  You can see it in the image almost hidden by the single male header I needed to program it. I forget but I think it only took power, ground and one pin to program.  OK, it’s only 32 bytes of ram and 1K of flash but come on, isn’t that the cutest thing ever?  I’ll buy a few more before the year is up.

I’ve had a few other chips go obsolete on me: the TP3465V SPI chip that I used in the olduino/Z and the STK16C88 non volatile RAM that I use in both olduinos. In each case I’ve sourced a couple of spares from eBay because there’s really no follow-on replacement for either. The ATTINY10 6 pin package is obsolete in favour of an SOIC 8 pin which is not that much bigger but doesn’t seem nearly as cute.

Debugging – How the Heck Did That Ever Work?

Because I’m lazy, I do everything in C and, if it works, I leave it alone til it doesn’t. In this case, I had written the fake loader in C. So I was going through all the init code, setting up stacks and registers then eventually loading or branching to an already loaded program that repeated the initialization code. Critically, the CPU was running with R3 as the program counter when it hit the second copy of the init code. The init code expects to be running on R0 so when it started loading R3, hilarity ensued. That was tough. Thankfully it failed nicely in the emulator so it turned up after some head scratching.

On the plus side, this forced me to redo the loader in assembly which i should have done from the beginning. It only takes 21 bytes but i’m going to settle on location 0x100 for the target program to leave me room for a better loader, maybe a register save etc.

When I get to the serial/xmodem loader that won’t be enough room so maybe I’ll make it 512 – that just saves me changing the target code location.

;;1806loader simulates 1802 load mode in run for 1805/6
target: equ 256
	b4 run		;bypass bootloader if IN pressed
	ldi	(target)&255
	plo	14
	ldi	(target)>>8; was/256
	phi	14
	sex 14		;in X register
noEF4:	bn4 noEF4	;loop til IN pressed
	inp 6		;load memory
	nop		;let the AVR breathe
	out 7		; echo
yEF4:	b4 yEF4		;wait til switch released
	br noEF4	;back for more
run:	lbr target	;finally - off we go

Debugging – Just Shoot Me

I came back to the 1806 olduino after a break and encountered a bug that has me scratching my head.  I’m using something I call the fakeloader to emulate 1802 load mode on the 1806.   fakeloader.c gets compiled to low memory and looks to the AVR external loader like an 1802’s load mode. Fakeloader loads the code from the external loader to location 0x800 and runs it from there.   The AVR external loader does actually know what’s going on, If it sees code compiled for location 0 it puts the 1802 in load mode as usual; if it sees code compiled for a different address it puts the processor in RUN and counts on the fakeloader.

I had a little test program called hellofromtheotherside.c that i would run that could report where it was loaded and what kind of processor it was compiled for.  The trouble was it didn’t work! I could run almost anything compiling it for location 0x800 and loading it via the fakeloader but not hellofromtheotherside.  After much dinking around my simplest fail/no-fail case involves code that is never executed! The test case is below, In the function dump(which is never executed!) there’s a for loop.  I can make it fail or run depending on whether there are a couple of print statements inside the for loop or not.  Although the code is never executed, the version with the print statements inside the loop compiles to more code and ALTHOUGH THAT CODE IS NEVER EXECUTED it seems to load to low memory being overwritten as well as simply not printing. The version below is the one that fails. If I were to close out the for loop on the 12th(**A) line and comment out the } on the 15th line(**B) it would work.

Obviously, everything after dump() including main() and the library routines are in slightly different locations but I don’t know what’s leading to the failure. cr*p.

void fun(int i){}
int bar(int s,int b,int c){
	int local=42;
	return local+c;
#include <nstdlib.h>
#include <cpu1802spd4port7.h>
#define putc(x) out(7,x)
void dump(unsigned char* data, unsigned int len){
	unsigned int i=0;
	printf("dumping %d bytes at %x",len,data);
		if (0==(i%8)) printf("\n%x ",data);
		printf("%cx ",*data++);
void main()
	char* dummy=(void*) &fun;
	printf("Hello World!9!\n");
#include <nstdlib.c>

The failing case, with the printfs outside the loop body generates LESS code than the working case(because the compiler puts i in a register). I stuck enough db’s in the bottom of dump to move main to the same place and it suddenly works! Moving them down to above the nstdlib include also works. Moving them down to below the nstdlib include also works. So something in the epilog is getting b*ggered.

Further diddling by taking out filler I can get it to work with 2 bytes of filler or fail with 1. In the guts of the epilog it works with lcc1802init: at 15EC and fails when it’s at 15E8. In any case, by the time it gets to the call routine it will be the same because there’s an align 256 that brings it to 0x1700 in either case. I give up for now.

Also, I’ve been assuming this won’t fail in the emulator but don’t really know that. worth a try tomorrow.

UV EPROM Defeats Ghetto Programmer

I was able to get the breadboard programmer to read(I think) the CY27C256 EPROMS but my attempts at programming failed. The essence of the programming is to apply a series of 200 uS low pulses on the chip select (pin 20) while holding VPP(pin 1) at 12 volts. I first built that into the ghetto programmer code but when it didn’t work I just made a standalone sketch that would do 30 pulses and changed all the other signal levels with jumpers.  I set it up with A14,13 & 12 at +V and the others at 0 so i would be programming location 7000; i pulled D0-D7 high, held /OE(pin 22) at +V; applied 12V from a wall wart to VPP, and pulsed pin 22. After 30 pulses I disconnected the jumpers and tried to read the same location but all the bits remain stubbornly 0.

I tried each of two chips with the same result so it’s more likely some error on my part than a faulty part.

The chips have a signature built in which you read out by holding all address lines low except A9 which you connect to 12V(!).  I was able to do that on the breadboard and read out the signature successfully so it’s even less likely that the chips are bad.  I have a friend in Toronto who can maybe program them for me so i’ll try that.  I was surprised not to have found an arduino shield or schematic for programming them but i see why now – it’s a lot of trouble.

The pics below show the eprom on the breadboard set up to read the signature and then to program it.  In each case the 12V VPP is being supplied from a wall wart through the alligator clips at the bottom.  There’s an arduino off to the right supplying 5V and, in the programming case, pulsing the /CE pin low.

Ghetto Programmer Meets UV EPROM

The Cypress CY27C256T is a UV erasable EPROM.  Mostly those are the wide .6″ DIP’s  but this is available in a .3″ wide package which means it will fit under the non-volatile RAM on the membership card. That spot is meant for a RAM but the pinouts are similar enough that I should be able to make it work for at least the bottom part of the address space.  To program it I’m adapting my ghetto eeprom programmer which I last used for the Boyd Calculator reprogramming.  I don’t want to bollix it permanently so I’m trying to be careful with the changes.  The first try is a read test. For  this all I had to do was connect pin 1(VPP) to +V along with pins 27(MA14) and 26(MA13), pin 23(MA11) goes to A11. Probably if I were smarter I’d make an adapter socket to match up with the footprint for the AT28C64 that the programmer was originally made for.  For now, I’ll just have to be careful.

17-05-22 ghetto 27c256I fired it up and it does read as 0’s which is good. I was also able to measure the 5V current at 27MA(including the 74595) and, by switching the /CE to +V, put the chip in low power mode and see the current drop to 7MA. The 74HC595’s data sheet says the supply current is max .08MA so I’ll ignore that!

To actually program it I have to set VPP(pin 1) to 12V with /OE and /CE held high then pulse /CE low many times for 200us at a time until the programming takes.  Then do it twice as many times again.  Fortunately the VPP is static at 12V so if i can figure out how to source it I don’t have to pulse it.  The ghetto programmer doesn’t normally control /CE but there’s no /WE required so that signal is freed up to use(the arduino’s D2).

Below is stuff I’m gathering for the programming push:

17-05-22 WS27C256 fit to programmer

17-05-22 WS27C256 fit to U2

As regards putting the EPROM chip into the Membership card, I have to think about pins 1,20, and 27. As currently routed, pin 1(VPP) will fluctuate with A14 which theoretically is ok. pin 20(/CE) needs to be wired to some variant of A15 to divide the address space with the RAM and pin 27(A14) would fluctuate with /WE which again theoretically doesn’t matter for testing since I won’t write in the ROM. So, hell, I may just be able to leave it all alone except for whatever jumpers lee provides for addressing.  Aaand it looks like the default jumpers on the board address the ROM high and the RAM low.  Perfect for my first programming attempt.  I’ll write something in the high part of the chip and then see if i can read it with the 1802.

Programming Instructions for the CY27C256


membership card schematic

UPDATE: Poking around my pile of castoffs I have a 12V wall wart that actually delivers just a bit over 12V.  I may try to get a regulator tomorrow or i may just try it out.

Below is the EPROM in place under the RAM.  I actually have to solder in some pin sockets and a capacitor before i run it but the fit looks fine.

27-05-22 ROM under RAM

Another Stab at Xmodem

17-05-05 xmodem test rig
Xmodem is a very simple and easy to understand protocol but i have spent a LOT of time diddling with it. I’m trying to combine it with some bit bang serial routines for the 1802 as a return to a serial bootloader.  I’m really just using xmodem for pacing and i’m not checking any error conditions. Testing it involved my usual “fantastic collection of stamps” approach with a python sender on windows, a couple of serial ports, and the logic analyzer.

After hours of flaky results it finally started working today when I overwrote low memory and had to switch back from the 1806 to the 1802.  Now it seems bulletproof! I’ll retry with the 1806 tomorrow with some confidence that i can make it work.

The reason for the custom python sender is so i could build up from working with very small blocks and visible control characters. I’m munging all the code together here so i can track where i was when it started working.

#include <olduino.h>
#include <nstdlib.h>
#include <cpu1802spd4port7.h>
int XR(unsigned char *);
void dump(unsigned char* data, unsigned int len){
	unsigned int i=0;
	printf("dumping %d bytes at %x",len,data);
		if (0==(i%8)) printf("\n%x ",data);
		printf("%cx ",*data++);
void main(){
	int ret;
	asm(" seq\n"); //make sure Q is high to start
	ret=XR((unsigned char *)0x7000);
	printf("XR returns %x\n",ret);
	dump((unsigned char *)0x7000,ret-0x7000-1);
void includeser(){
	asm(" include xrwjr2.asm");
#include <olduino.c>
#include <nstdlib.c>
**************xrwjr2,asm include file follows ***************
NAK:	EQU 0x15;'N'
SOH:	EQU 0x01;'S'
EOT:	EQU 0x04;'T'
ACK:	EQU 0x06;'K'
Rrcv:	EQU 8
Rsnd:	EQU 9
blksize:	EQU 128
trc:	MACRO
	dec 2
	str 2
	out 7
; XMODEM receiver based on xr.asm by Michael H Riley and serial routines by Josh Bensadon
; See bottom of file for full acknowledgements and links.
; On entry R12 points to memory where received data will go
; On exit R15 has the last byte written
	align 64
_XR:	   ldaD	   Rsnd,serout
	   ldaD    Rrcv,serin
	ldi '<' 	trc  	   ldi     NAK                 ; need to send NAK to start            sep     Rsnd filelp:    ;receive address is in R12, length goes in R11 ;begining of block read. returns to filelp or exits to filedn   				            sep     Rrcv               ; wait for next incoming character            smi     EOT              ; check for EOT            lbz     filedn           ; jump if so 	   sep     Rrcv               ; read block number            sep     Rrcv               ; read inverted block number            ldi     blksize             ; 128 bytes to receive            plo     r11 readlp:    sep     Rrcv               ; read data byte            str     r12                  ; store into output buffer            inc     r12                  ; point to next position            dec     r11                  ; decrement block count            glo     r11                  ; see if done            bnz     readlp              ; loop back if not ;end of block read            sep     Rrcv               ; read checksum byte            ldi     ACK                  ; send an ACK            sep     Rsnd            lbr     filelp              ; loop back for more filedn:                ldi     ACK                  ; acknowledge end of transmission            sep     Rsnd 	   glo	   R12			;copy last address to return register 	   plo     R15 	   ghi     R12 	   phi     R15 	ldi '>'
           cretn	           	; and return to caller

; *******************************************************************
; *** This software is copyright 2005 by Michael H Riley          ***
; *** You have permission to use, modify, copy, and distribute    ***
; *** this software so long as this copyright notice is retained. ***
; *** This software may not be used in commercial applications    ***
; *** without express written permission from the author.         ***
; *******************************************************************
;bit-bang Serial routines adapted from Josh Bensadon's VELFbios-v3.1.asm
;Transmit Byte via Q connected to RS232 driver
;call via SCRT
;Byte to send in D
;Destroys r14
bitdelay: MACRO baudrate,cpuspeed,baseline,xreg
	rept ((cpuspeed/(baudrate*8)-baseline))/3
	rept (((cpuspeed/(baudrate*8)-baseline)#3))>=1
	sex xreg
	align 32
serout:			;entry from assembly with char in D
	phi R14		;save char in R14.1
	ldi 9		;9 bits to transmit (1 start + 8 data)
	plo r14
	ghi R14
	shl		;set start bit
	rshr		;DF=0

	bdf $+5		;10.5   jump to seq to send a 1 bit
	req		;11.5   send a 0 bit
	br $+5		;1      jump +5 to next shift
	seq		;11.5   send a 1 bit
	br $+2		;1      jump +2 to next shift (NOP for timing)
	rshr		;2      shift next bit to DF flag
	phi r14		;3      save D in r14.1
	DEC r14		;4      dec bit count
	glo r14		;5      get bit count
	bz .txcret	;6      if 0 then all 9 bits (start and data) sent
	ghi r14		;7      restore D
	bitdelay __BAUDRATE,LCC1802CPUSPEED,20,2
	br .txcloop	;9.5    loop back to send next bit
.txcret: ghi r14		;7
	bitdelay __BAUDRATE,LCC1802CPUSPEED,16,2
	seq		;11.5 stop bit
	bitdelay __BAUDRATE,LCC1802CPUSPEED,4,2
	sep R3		;return
	br serout	;reset for next time
;Receive Byte via EF2 connected to RS232 receiver
;Receives 8 bits
;call via sep
;Returns with Byte received in D
;Destroys r14.0
	align 32
 	ldi 8		;start bit +7 bits from loop, last bit on returning
	plo r14
	ldi 0
.rxcw:			;wait for start bit
	bn3 .rxcw	;each instr takes 9us, we need 104us = 11.5
			;delay 1/2 bit time to center samples
	NOP		;     Don't test for correct start bit
	NOP		;     it will work. if there's too much
	NOP		;     noise on the line, shorten the cable!
	bitdelay __BAUDRATE,LCC1802CPUSPEED,20,2
	b3 $+6		;11.5 sample rx input bit
	ori 80h		;1
	br $+4		;2
	phi r14		;1
	phi r14		;2
	shr		;3
	phi r14		;4
	DEC r14		;5
	glo r14		;6
	bz .rxcret	;7
	ghi r14		;8
	br  .rxcloop	;9
.rxcret: ghi r14	;8
	ghi r14		;9
	bitdelay __BAUDRATE,LCC1802CPUSPEED,20,2
	b3 $+4		;11.5 sample last rx input bit
	ori 80h		; for a 1 bit
	sep R3		;return
	br  serin	;for next time

	ldad Rsnd,serout
	glo R12
	sep Rsnd

	ldad Rrcv,serin
	sep Rrcv
	plo R15
	ldi 0
	phi R15

************************SSX3.PY custom sender follows *****************
from __future__ import print_function
import sys
import logging
import serial
    from cStringIO import StringIO
    from StringIO import StringIO
from time import sleep
import os
if len(sys.argv)>1:
print ("File Size is",fileSize)

def xmodem_send(serial, file):
	#	t, anim ='|/-\\'
	while 1:
	    if != NAK:
		t = t + 1
		print ('.')
		if t == 3 : return False

	p = 1
	s =
	while s:
	    s = s + '\xFF'*(blocksize - len(s))
	    chk = 0
	    for c in s:
		#print (c,ord(c),chk,chk%256)
	    while 1:
		serial.write('\x01')#('S') #SOH)
		serial.write(chr(255 - p))
		print ('checksum is ',format(chk%256, '02X'),end=' ')
		answer =
		if len(answer)!=0:
			print('answer is ',format(ord(answer[0]), '02X'))
			print ("Timeout I guess")
		if  answer == '\x15': continue
		if  answer == '\x06': break
		return False
	    s =
	    p = (p + 1)%256
	    print ('.')

	return True

#Main program starts here - define the serial port, set RTS off, then open it
#open the file to be loaded
stream = open(filename,'rb')

port = serial.Serial(parity=serial.PARITY_NONE,
#transfer the file
result=xmodem_send(port, stream)


if result:
    print ("\ntransfer successful")
    print ("\ntransfer unsuccessful")
    #x=raw_input("press enter to continue...");

I always want the last word so that wordpress doesn’t eat my code!

Reprogramming an 1805-Based Calculator in C

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: