;In-circuit programmer for the Atmel 89C2051 microcontroller
;Paul Stoffregen, 1995 (paul@ece.orst.edu)

;A 87C51 based 89C2051 programmer.  Download code via serial
;port.	Command switches programmed 89C2051 into circuit
;via 4066 analog switches (80 ohm on resistance).  Reset is
;controlled by programmer, and crystal is local in programmer.

;uart txd pin of 89C2051 is logically or'd with the txd of the
;programmer while the chip is switch in-curcuit, so debugging
;information can be sent to the same console as the programming.
;rxd pin of 89C2051 not connected to programmer, however.

;this is a BETA test copy, version 0.004.  Use at your own risk!
;please report bugs to paul@ece.orst.edu.  Updates available at:
;http://www.ece.orst.edu/~paul/8051-goodies/goodies-index.html#atmel

;This software is in the public domain.	 I, Paul Stoffregen, give
;no warranty, expressed or implied for this software and/or
;documentation provided, including, without limitation, warranty
;of merchantability and fitness for a particular purpose. 

;history:
;
; beta 0.004: Many user interface improvements, .equ statements
;   for port pins, buffer address translation added...
;   no more hardware memory mapping tricks required now.
; beta 0.003: Everything works, but user interface sucks and
;   it may not work in other boards without some hardware tricks
; beta 0.002: Programming works, but it thinks it's failing to
;   write.  Readback and reading ID bytes works, but erase
;   does nothing... good thing the chips come erased when new!
; beta 0.001: Hardware too difficult to manipulate... redo
;   hardware using an 82C55 instead.  (This code was deleted)
;
;to do list:
; - make all reads from external memory with MOVC
; - do something useful with reading the ID bytes
; - maybe add support for AT89C1051??
; - add in-circuit option without txd echo enabled.
; - more testing, other boards... need some beta testers.
; - getting very close to 4k size, need to trim down a bit

;******************************************************************  
;Hardware specific configuration options
;******************************************************************

    ;timer reload calculation
    ; baud_const = 256 - (crystal / (12 * 16 * baud))
.equ	baud_const, 253		;19200 baud w/ 11.0592 MHz

.equ	loc_82c55, 0xE000	;memory mapped location of the 82C55

;control signals connected to port pins
.equ	aic_en, 0x90		;0=in circuit, 1=disconnect
.equ	pgmv_pu, 0x91		;0=pull to 12 volts, 1=float
.equ	pgmv_pd, 0x92		;0=pull to 0 volts, 1=float
.equ	txd_en, 0xB4		;0=disable txd echo, 1=enable it
.equ	xtal1_in, 0xB3		;drives xtal1 pin on 89c2051
.equ	osc_en, 0xB5		;0=disable osc, 1=allow to run

;to the user, a buffer appears to be locatated from 0000,
;but this constant allows the buffer memory to exist in any
;location, since the 87C51's code will exist starting at
;0000.	Do not specify a buffer that will wrap beyond FFFF.
.equ	buffer, 0x2000		;physical location of the buffer
.equ	buf_size, 0x0800	;buffer size, may not be safe to change?

;location to assemble this code (don't overlap with the buffer!)
.equ	begin, 0x0000

;******************************************************************
;it should not be necessary to make changes below this line,
;but you may if you want.  Please contact me if you do, thanks.
;****************************************************************** 

;memory locations for the 8255 chip
.equ	port_a, loc_82c55
.equ	port_b, loc_82c55+1
.equ	port_c, loc_82c55+2
.equ	port_pgm, loc_82c55+3

.equ	stack, 0x30

	.org	begin
	ljmp	poweron		;reset vector
	.org	begin+3		;ext int0 vector

	.org	begin+11	;timer0 vector
	.org	begin+19	;ext int1 vector

	.org	begin+27	;timer1 vector
	.org	begin+35	;uart vector
	.org	begin+43	;timer2 vector (8052)
	.org	begin+48	;finally we can begin


;r6-r7 are used to hold the user's buffer pointer, in terms of the
;physical address, not the 0000-1FFF one the user sees.

main:
	mov	r6, #buffer&255
	mov	r7, #buffer>>8
	lcall	newline
	lcall	delay
	clr	tr0
	lcall	erase_buf
	ajmp	mainloop

;the main program loop:

mainloop:
	mov	dptr, #mprompt	;print a prompt
	lcall	pstr
	clr	c
	mov	a, r6		;print buffer offset, not physical
	subb	a, #buffer&255
	push	acc
	mov	a, r7
	subb	a, #buffer>>8
	lcall	phex
	pop	acc
	lcall	phex
	mov	a, #'>'
	lcall	cout
	lcall	cin
	lcall	upper
	mov	dptr, #mainloop	; so routines will return here
	push	dpl
	push	dph
main1:	cjne	a, #'D', main2
	ljmp	dnld
main2:	cjne	a, #'?', main3
	mov	dptr, #help
	ljmp	pstr
main3:	cjne	a, #'P', main4
	ljmp	prog
main4:	cjne	a, #'I', main5
	ljmp	in
main5:	cjne	a, #'R', main6
	ljmp	read
main6:	cjne	a, #'H', main7
	ljmp	hexdump
main7:	cjne	a, #'E', main8
	ljmp	erase
main8:	cjne	a, #'S', main9
	ljmp	readid
main9:	cjne	a, #'T', main10
	ljmp	testpvolt
main10: cjne	a, #'N', main11
	ljmp	newloc
main11: cjne    a, #'U', main12
	ljmp	upload
main12: cjne    a, #'C', main13
        ljmp	comp
main13:
	ret
      


help:	.db	13,10
	.db	"Atmel 89C2051 In-circuit programmer",13,10
	.db	"BETA VERSION 0.004 (8 Oct 95)",13,10
	.db	"Paul Stoffregen (paul@ece.orst.edu)",13,10,13,10
	.db	"  D - download Intel HEX file into buffer",13,10  
	.db	"  P - program the chip from the buffer",13,10
	.db	"  E - erase chip",13,10
	.db	"  I - in-circuit operation",13,10
	.db	"  C - compare chip with buffer",13,10
	.db	"  R - read back into ram",13,10
	.db	"  U - upload Intel HEX",13,10
	.db	"  S - read ID bytes",13,10
	.db	"  H - hexdump ram",13,10
	.db	"  N - new memory location",13,10
	.db	"  ? - This help",13,10
	.db	0

mprompt:.db	13,10,"AICP 0.004(beta) buf:",0


out:	;switch the 89C2051 out of the circuit
	;this should reset the internal address counter too
	setb	aic_en		;disconnect from circuit
	clr	osc_en		;stop the oscillator
	clr	xtal1_in	;force xtal1 to low
	clr	txd_en		;block txd echo
	lcall	volt0		;make sure reset is low
	nop
	nop
	lcall	volt5		;force reset to high
	clr	txd_en		;block its access to our serial line
	mov	dptr, #port_a
	mov	a, #00001111b	;port a = 00001111
	movx	@dptr, a
	mov	dptr, #port_pgm
	mov	a, #10001011b	;port a=out, b=in, c=in
	movx	@dptr, a
	mov	a, #00001111b
	mov	dptr, #port_a
	movx	@dptr, a	;port a = 00001111
	ret


read:	;read the 89C2051 back into buffer

	lcall	out
	mov	dptr, #port_pgm
	mov	a, #10001011b	;port a=out, b=in, c=in
	movx	@dptr, a
	mov	dptr, #port_a
	mov	a, #11100111b	;read mode
	movx	@dptr, a
	mov	dptr, #buffer
read1:
	push	dpl
	push	dph
	mov	dptr, #port_b
	movx	a, @dptr	;read in data from 89C2051
	pop	dph
	pop	dpl
	movx	@dptr, a	;write to ram
	inc	dptr
	setb	xtal1_in	;advance addr counter
	nop
	nop
	nop
	nop
	clr	xtal1_in
	mov	a, dph
	cjne	a, #(buffer+0x0800)>>8, read1
	mov	a, dpl
	cjne	a, #(buffer+0x0800)&255, read1
	ret


comp:
	mov	dptr, #m_comp1
	lcall	pstr
	acall	compare
	jnz	comp2
	mov	dptr, #m_comp2
	lcall	pstr
	ret
comp2:	mov	dptr, #m_comp3
	lcall	pstr
	ret

m_comp1:.db	13,10,"Compare Chip with buffer: ", 0
m_comp3:.db	"Error, they do not "
m_comp2:.db	"match",13,10,0



compare:	;compare buffer with chip
		;return acc=0 if ok, acc=255 if not equal
        lcall   out
        mov     dptr, #port_pgm
        mov     a, #10001011b   ;port a=out, b=in, c=in
        movx    @dptr, a
        mov     dptr, #port_a
        mov     a, #11100111b   ;read mode
        movx    @dptr, a
        mov     dptr, #buffer
compare1:
        push    dpl
        push    dph
        mov     dptr, #port_b
        movx    a, @dptr        ;read in data from 89C2051
        pop     dph
        pop     dpl
	mov	b, a		;put chip's value in b
	movx	a, @dptr	;get memory's value into a
	cjne	a, b, comp_nope
        inc     dptr
        setb    xtal1_in        ;advance addr counter
        nop
        nop
        nop
        nop
        clr     xtal1_in
        mov     a, dph
        cjne    a, #(buffer+0x0800)>>8, compare1
        mov     a, dpl
        cjne    a, #(buffer+0x0800)&255, compare1
	lcall	out
	clr	a
        ret
comp_nope:
	lcall	out
	mov	a, #255
	ret


iserased: ;returns acc=0 if erased, acc=255 if not erased
        lcall   out
        mov     dptr, #port_pgm
        mov     a, #10001011b   ;port a=out, b=in, c=in
        movx    @dptr, a
        mov     dptr, #port_a
        mov     a, #11100111b   ;read mode
        movx    @dptr, a
        mov     dptr, #buffer
iser1:
        push    dpl
        push    dph
        mov     dptr, #port_b
        movx    a, @dptr        ;read in data from 89C2051
        pop     dph
        pop     dpl
	cjne	a, #255, iser_no
        inc     dptr
        setb    xtal1_in        ;advance addr counter
        nop
        nop
        nop
        nop
        clr     xtal1_in
        mov     a, dph
        cjne    a, #(buffer+0x0800)>>8, iser1
        mov     a, dpl
        cjne    a, #(buffer+0x0800)&255, iser1
	lcall	out
	clr	a
        ret
iser_no:
	lcall	out
	mov	a, #255
	ret



readid: ;read the product id bytes

	mov	dptr, #id_msg
	lcall	pstr
	lcall	out
	mov	dptr, #port_pgm
	mov	a, #10001011b	;port a=out, b=in, c=in
	movx	@dptr, a
	mov	dptr, #port_a
	mov	a, #01000111b	;read id mode
	movx	@dptr, a
	mov	dptr, #port_b
	movx	a, @dptr	;read in data from 89C2051
	lcall	phex
	mov	a, #' '
	lcall	cout
	setb	xtal1_in	;advance addr counter
	nop
	nop
	nop
	nop
	clr	xtal1_in
	mov	dptr, #port_b
	movx	a, @dptr	;read in data from 89C2051
	lcall	phex
	mov	a, #' '
	lcall	cout
	setb	xtal1_in	;advance addr counter
	nop
	nop
	nop
	nop
	clr	xtal1_in
	mov	dptr, #port_b
	movx	a, @dptr	;read in data from 89C2051
	lcall	phex
	lcall	newline
	ret

id_msg: .db	13,10,"ID Bytes: ",0



in:	;put the 89C2051 into the circuit and start it
	mov	dptr, #m_in1
	lcall	pstr
	lcall	volt5		;make sure reset is at 5 volts
	setb	osc_en		;start the oscillator
	setb	xtal1_in	;and allow it to drive the chip
	setb	txd_en		;allow the 89C2051's TxD pin to
				;drive our serial output too
	lcall	dly_50us	;wait a bit
	mov	dptr, #port_pgm
	mov	a, #10011011b	;port a=in, b=in, c=in
	movx	@dptr, a
	clr	aic_en		;connect it to the external circuit
	lcall	dly_50us	;wait a little while
	lcall	volt0		;begin operation!
	;at this point, the Atmel 89C2051 should be in the
	;circuit and running.  Now we wait for a keypress, and
	;then we'll yank it out of the circuit again.
in_wait:
	lcall	cin
	cjne	a, #27, in_wait
	lcall	out
	mov	dptr, #m_in2
	lcall	pstr
	ret

m_in1:	.db	13,10,13,10,13,10
	.db	"In-Circuit, press <ESC> to remove",13,10,0
m_in2:	.db	13,10,"Removed from circuit",13,10,0



prog:	;program the 89C2051 using the data in the buffer

	lcall	iserased
	jz	prog0c		;skip erase if not necessary
	lcall	erase		;do erase first
prog0c:

	lcall	out

	mov	dptr, #p_msg1
	lcall	pstr

	mov	dptr, #buffer

prog1:
	movx	a, @dptr	;get the value to program now
	mov	b, a		;and keep it around in b
	cjne	a, #0xFF, prog1b
	ljmp	prog_skip	;skip programming if 0xFF
prog1b:
	push	dph
	push	dpl
	mov	a, dpl
;	 jnz	 prog2
	mov	a, #13
	lcall	cout
	mov	dptr, #p_msg2
	lcall	pstr
	pop	dpl
	pop	dph
	push	dph
	push	dpl
	mov	a, dph
	lcall	phex
	mov	a, dpl
	lcall	phex

prog2:
	mov	dptr, #port_pgm
	mov	a, #10001001b	;port a=out, b=out, c=in
	movx	@dptr, a
	mov	dptr, #port_b
	mov	a, b
	movx	@dptr, a	;drive port 1 with data byte
	mov	dptr, #port_a
	mov	a, #11110111b
	movx	@dptr, a
	lcall	volt12		;raise reset to 12 volts
	mov	dptr, #port_a
	mov	a, #11110011b
	movx	@dptr, a	;begin prog pulse
	lcall	dly_50us
	mov	a, #11110111b
	movx	@dptr, a	;end prog pulse
	;the text description seems to suggest keeping reset at
	;12 volts until it's done writing, but the detailed timing
	;diagram says it's ok to lower reset back to 5 volts
	;only 10us after the end of our prog pulse...
	lcall	dly_50us
	lcall	dly_50us
	lcall	dly_50us
	lcall	dly_50us
progwait:
	mov	dptr, #port_c
	movx	a, @dptr	;read port #3
	anl	a, #00000001b	;the lsb is our busy line
;should check here to make sure it's not stuck...  typical
;program time is 1.2ms, worst case is 2ms
	jz	progwait
	lcall	volt5		;make sure we're not still at 12 volts
	;get ready to read
	mov	dptr, #port_pgm
	mov	a, #10001011b	;port a=out, b=in, c=in
	movx	@dptr, a
	mov	dptr, #port_a
	mov	a, #11100111b	;read mode
	movx	@dptr, a
	nop
	nop
	nop
	mov	dptr, #port_b
	movx	a, @dptr	;read data back from chip
	push	acc		;push it into stack for now
	mov	dptr, #port_a
	mov	a, #11110111b	;disable read mode
	movx	@dptr, a
	nop
	nop
	nop
	nop
	;and now all we need to do is increment the addr counter
	setb	xtal1_in
	nop
	nop
	nop
	clr	xtal1_in
	;now let's look at the data we verified
	pop	acc
	cjne	a, b, prog_err
prog_next:
	pop	dpl
	pop	dph
pskip2:
	inc	dptr
	mov	a, dpl
	cjne	a, #(buffer+0x0800)&255, prog_j
	mov	a, dph
	cjne	a, #(buffer+0x0800)>>8, prog_j
	lcall	newline
	mov	dptr, #p_msg3
	lcall	pstr
	lcall	compare
	jnz	pgm_bad
	mov	dptr, #p_good
	lcall	pstr
	ret
pgm_bad:mov	dptr, #p_bad
	lcall	pstr
	;not much we can do at this point though.
	ret


prog_skip:
	;don't forget to increment the addr counter when
	;we skip a location because the buffer is 0xFF
	setb	xtal1_in
	nop
	nop
	nop
	clr	xtal1_in
	sjmp	pskip2

prog_j: ;jump up to do the next location
	ljmp	prog1


prog_err:
	mov	r3, a		;keep bogus value in r3
	mov	r4, b		;keep correct value in r4
	mov	dptr, #pgm1
	lcall	pstr
	pop	dpl
	pop	dph
	push	dph
	push	dpl
	mov	a, dph
	lcall	phex
	mov	a, dpl
	lcall	phex
	mov	dptr, #pgm2
	lcall	pstr
	mov	a, r4
	lcall	phex
	mov	dptr, #pgm3
	lcall	pstr
	mov	a, r3
	lcall	phex
	lcall	newline
	ljmp	prog_next

p_msg1: .db     13,10,"Programming Flash ROM:",13,10,0
p_msg2: .db     "Loc=",0
p_msg3: .db     "Finished Programming",13,10
        .db     "Verifying...",0
p_good: .db     "OK",13,10,0
p_bad:  .db     "Error, device does not match buffer!",13,10,0


pgm1:	.db	"  Program verify error at ",0
pgm2:	.db	" correct: ",0
pgm3:	.db	" actual: ",0



erase:	;erase the 89C2051
	lcall	out
	mov	dptr, #er_msg1
	lcall	pstr
	mov	dptr, #port_a
	mov	a, #01001111b	;erase code
	movx	@dptr, a
	nop
	nop
	lcall	volt12		;go to 12 volts
	mov	dptr, #port_a
	mov	a, #01001011b	;erase pulse
	movx	@dptr, a
er_dly: mov	r3, #40
er_dly2:lcall	dly_50us
	lcall	dly_50us
	lcall	dly_50us
	lcall	dly_50us
	lcall	dly_50us
	lcall	dly_50us
	lcall	dly_50us
	lcall	dly_50us
	lcall	dly_50us
	lcall	dly_50us
	djnz	r3, er_dly2
	mov	dptr, #port_a
	mov	a, #01001111b	;end erase pulse
	movx	@dptr, a
	nop
	nop
	lcall	volt5		;back to 5 volts
	mov	dptr, #port_a
	mov	a, #11111111b
	movx	@dptr, a
	nop
	nop
	mov	dptr, #done
	lcall	pstr
	ret

er_msg1:.db	13,10,"Erasing Flash ROM...",0
done:	.db	"done",13,10,0

testpvolt:
	mov	dptr, #testwarn
	lcall	pstr
	lcall	cin
	lcall	upper
	clr	c
	subb	a, #'Y'
	jz	testpv2
	lcall	newline
	ret
testpv2:lcall	newline
	mov	dptr, #twarn2
	lcall	pstr
	lcall	cin
	lcall	upper
	clr	c
	subb	a, #'Y'
	jz	testpv3
	lcall	newline
	ret
testpv3:
	lcall	newline
	lcall	volt12
	mov	dptr, #progmsg1
	lcall	pstr
	mov	a, #'1'
	lcall	cout
	mov	a, #'2'
	lcall	cout
	mov	dptr, #progmsg2
	lcall	pstr
	lcall	cin

	lcall	volt0
	mov	dptr, #progmsg1
	lcall	pstr
	mov	a, #'0'
	lcall	cout
	mov	dptr, #progmsg2
	lcall	pstr
	lcall	cin

	lcall	volt5
	mov	dptr, #progmsg1
	lcall	pstr
	mov	a, #'5'
	lcall	cout
	mov	dptr, #progmsg2
	lcall	pstr
	lcall	cin

	ret


testwarn:.db	13,10,"Program Voltage Test.  You must remove the",13,10
	.db	"89C2051 chip or it could be damaged.",13,10,13,10
	.db	"Are you sure (N/y): ",0
twarn2:	.db	"Remove the chip, probe pin 1's voltage, "
	.db	"press Y to begin ",0
progmsg1:.db	"Reset now at ",0
progmsg2:.db	" volts, press a key",13,10,0



volt0:	;drive reset to zero volts
	setb	pgmv_pu
	nop
	clr	pgmv_pd
	lcall	dly_50us
	lcall	dly_50us
	ret

volt5:	;drive reset to five volts
	setb	pgmv_pu
	setb	pgmv_pd
	lcall	dly_50us
	lcall	dly_50us
	ret


volt12: ;drive reset to twelve volts
	setb	pgmv_pd
	nop
	clr	pgmv_pu
	lcall	dly_50us
	lcall	dly_50us
	ret


erase_buf:			;fill buffer with 0xFF
	mov	dptr, #buffer
ebuf2:	mov	a, #255
	movx	@dptr, a
	inc	dptr
	mov	a, dph
	cjne	a, #(buffer+buf_size)>>8, ebuf2
	mov	a, dpl
	cjne	a, #(buffer+buf_size)&255, ebuf2
	ret


	;approx 0.1 second delay (11.0592 MHz crystal)
delay:	mov	r3, #200
delay2: nop
	mov	r2, #228
	djnz	r2, *
	djnz	r3, delay2
	ret

dly_50us:
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	ret


cin:	jnb	ri, cin
	clr	ri
	mov	a, sbuf
	ret

cout:	jnb	ti, cout
	mov	sbuf, a
	clr	ti
	ret

newline:push	acc
	mov	a, #13
	lcall	cout
	mov	a, #10
	lcall	cout
	pop	acc
	ret

phex:
phex8:
	push	acc
	swap	a
	anl	a, #15
	add	a, #246
	jnc	phex_b
	add	a, #7
phex_b: add	a, #58
	lcall	cout
	pop	acc
phex1:	push	acc
	anl	a, #15
	add	a, #246
	jnc	phex_c
	add	a, #7
phex_c: add	a, #58
	lcall	cout
	pop	acc
	ret

PSTR:		       ;print string
	PUSH	ACC
PSTR1:	CLR	A
	MOVC	A,@A+DPTR
	JZ	PSTR2
	mov	c, acc.7
	anl	a, #01111111b
	lcall	cout
	Jc	pstr2
	inc	dptr
	SJMP	PSTR1					       
PSTR2:	POP	ACC
	RET    


poweron:
	setb	aic_en		;disconnect from the circuit
	setb	pgmv_pu		;drive reset to 5 volts
	setb	pgmv_pd
	MOV	SP, #stack
	clr	psw.3
	clr	psw.4
	lcall	out		;take the chip out of the circuit
	mov	th1, #baud_const
	orl	PCON,#10000000b ;set double baud rate
	MOV	TMOD,#00100001b ;T0=16 bit, T1=8 bit auto reload
				;both are timers, software control
	MOV	SCON,#01010010b ;Set Serial for mode 1 &
				;Enable reception, ti=1, ri=0
	ORL	TCON,#01010101b ;Start both timers, both int are
				;falling edge trigger
	mov	dptr, #help
	lcall	pstr
	ljmp	main


translate:	;take value in dptr (0000-1FFF) and translate
		;it to a physical address in the buffer
	mov	a, #buffer&255
	add	a, dpl
	mov	dpl, a
	mov	a, #buffer>>8
	addc	a, dph
	mov	dph, a
	ret


  ;location of parameter table used by download command
  ;sixteen bytes of internal memory are required
	.equ	dnld_parm, 0x20

;16 byte parameter table: (eight 16 bit values)
;  *   0 = lines received
;  *   1 = bytes received
;  *   2 = bytes written
;  *   3 = bytes unable to write
;  *   4 = incorrect checksums
;  *   5 = unexpected begin of line
;  *   6 = unexpected hex digits (while waiting for bol)
;  *   7 = unexpected non-hex digits (in middle of a line)

dnld:
	mov	dptr, #dnlds1		 
	lcall	pstr		;"begin sending file <ESC> to abort"
	mov	r0, #dnld_parm
	mov	r2, #16
dnld0:	mov	@r0, #0		;initialize all parameters to 0
	inc	r0
	djnz	r2, dnld0

	  ;look for begining of line marker ':'
dnld1:	lcall	cin
	cjne	a, #27, dnld2	;Test for escape
	sjmp	dnld_esc
dnld2:	cjne	a, #':', dnld2b
	mov	r1, #0
	lcall	dnld_inc
	sjmp	dnld3
dnld2b:	  ;check to see if it's a hex digit, error if it is
	lcall	asc2hex
	jc	dnld1
	mov	r1, #6
	lcall	dnld_inc
	sjmp	dnld1
	  ;begin taking in the line of data
dnld3:	mov	a, #'.'
	lcall	cout
	mov	r4, #0		;r4 will count up checksum
	lcall	dnld_ghex
	mov	r0, a		;R0 = # of data bytes
	mov	r4, a
	lcall	dnld_ghex
	mov	dph, a		;High byte of load address
	add	a, r4
	mov	r4, a
	lcall	dnld_ghex
	mov	dpl, a		;Low byte of load address
	add	a, r4
	mov	r4, a
	lcall	translate	;--> translate to physical addesss <--
	lcall	dnld_ghex	;Record type
	mov	r2, a
	add	a, r4
	mov	r4, a
	mov	a, r2
	cjne	a, #1, dnld4	;End record?
	sjmp	dnld_end
dnld4:
dnld5:	lcall	dnld_ghex	;Get data byte
	mov	r2, a
	mov	r1, #1
	lcall	dnld_inc	;count total data bytes received
	mov	a, r2
	add	a, r4
	mov	r4, a
	mov	a, r2
	lcall	smart_wr	;c=1 if an error writing
	clr	a
	addc	a, #2
	mov	r1, a
;     2 = bytes written
;     3 = bytes unable to write
	lcall	dnld_inc
	inc	dptr
	djnz	r0, dnld5

	lcall	dnld_ghex	;get checksum
	add	a, r4
	jz	dnld1		;should always add to zero
	mov	r1, #4
	lcall	dnld_inc
	sjmp	dnld1

dnld_end:   ;handles the proper end of download marker
	mov	dptr, #dnlds3
	lcall	pstr		;"download went ok..."
	sjmp	dnld_sum

dnld_esc:   ;handle esc received in the download stream
	mov	dptr, #dnlds2	 
	lcall	pstr		;"download aborted."
	sjmp	dnld_sum


dnld_inc:     ;increment parameter specified by R1
	      ;note, values in Acc and R1 are destroyed
	mov	a, r1
	anl	a, #00000111b	;just in case
	rl	a
	add	a, #dnld_parm
	mov	r1, a		;now r1 points to lsb
	inc	@r1
	mov	a, @r1
	jnz	dnldin2
	inc	r1
	inc	@r1
dnldin2:ret

dnld_gp:     ;get parameter, and inc to next one (@r1)
	     ;carry clear if parameter is zero.
	     ;16 bit value returned in dptr
	setb	c
	mov	dpl, @r1
	inc	r1
	mov	dph, @r1
	inc	r1
	mov	a, dpl
	jnz	dnldgp2
	mov	a, dph
	jnz	dnldgp2
	clr	c
dnldgp2:ret



;a spacial version of ghex just for the download.  Does not
;look for carriage return or backspace.	 Handles ESC key by
;poping the return address (I know, nasty, but it saves many
;bytes of code in this 4k ROM) and then jumps to the esc
;key handling.	This ghex doesn't echo characters, and if it
;sees ':', it pops the return and jumps to an error handler
;for ':' in the middle of a line.  Non-hex digits also jump
;to error handlers, depending on which digit.
	  
dnld_ghex:
dnldgh1:lcall	cin
	lcall	upper
	cjne	a, #27, dnldgh3
dnldgh2:pop	acc
	pop	acc
	sjmp	dnld_esc
dnldgh3:cjne	a, #':', dnldgh5
dnldgh4:mov	r1, #5		;handle unexpected beginning of line
	lcall	dnld_inc
	pop	acc
	pop	acc
	ajmp	dnld3		;and now we're on a new line!
dnldgh5:lcall	asc2hex
	jnc	dnldgh6
	mov	r1, #7
	lcall	dnld_inc
	sjmp	dnldgh1
dnldgh6:mov	r2, a		;keep first digit in r2
dnldgh7:lcall	cin
	lcall	upper
	cjne	a, #27, dnldgh8
	sjmp	dnldgh2
dnldgh8:cjne	a, #':', dnldgh9
	sjmp	dnldgh4
dnldgh9:lcall	asc2hex
	jnc	dnldghA
	mov	r1, #7
	lcall	dnld_inc
	sjmp	dnldgh7
dnldghA:xch	a, r2
	swap	a
	orl	a, r2
	ret

;dnlds4 =  "Summary:"
;dnlds5 =  " lines received"
;dnlds6a = " bytes received"
;dnlds6b = " bytes written"

dnld_sum:    ;print out download summary
	mov	dptr, #dnlds4
	lcall	pstr
	mov	r1, #dnld_parm
	lcall	dnld_gp
	lcall	space
	lcall	pint16u
	mov	dptr, #dnlds5
	lcall	pstr
	lcall	dnld_gp
	lcall	space
	lcall	pint16u
	mov	dptr, #dnlds6a
	lcall	pstr
	lcall	dnld_gp
	lcall	space
	lcall	pint16u
	mov	dptr, #dnlds6b
	lcall	pstr
dnld_err:    ;now print out error summary
	mov	r2, #5
dnlder2:lcall	dnld_gp
	jc	dnlder3		;any errors?
	djnz	r2, dnlder2
	 ;no errors, so we print the nice message
	mov	dptr, #dnlds13
	lcall	pstr
	ret
dnlder3:  ;there were errors, so now we print 'em
	mov	dptr, #dnlds7
	lcall	pstr
	  ;but let's not be nasty... only print if necessary
	mov	r1, #(dnld_parm+6)
	lcall	dnld_gp
	jnc	dnlder4
	lcall	space
	lcall	pint16u
	mov	dptr, #dnlds8
	lcall	pstr
dnlder4:lcall	dnld_gp
	jnc	dnlder5
	lcall	space
	lcall	pint16u
	mov	dptr, #dnlds9
	lcall	pstr
dnlder5:lcall	dnld_gp
	jnc	dnlder6
	lcall	space
	lcall	pint16u
	mov	dptr, #dnlds10
	lcall	pstr
dnlder6:lcall	dnld_gp
	jnc	dnlder7
	lcall	space
	lcall	pint16u
	mov	dptr, #dnlds11
	lcall	pstr
dnlder7:lcall	dnld_gp
	jnc	dnlder8
	lcall	pint16u
	mov	dptr, #dnlds12
	lcall	pstr
dnlder8:lcall	newline
	ret

;dnlds7:  = "Errors:"
;dnlds8:  = " bytes unable to write"
;dnlds9:  = " incorrect checksums"
;dnlds10: = " unexpected begin of line"
;dnlds11: = " unexpected hex digits"
;dnlds12: = " unexpected non-hex digits"
;dnlds13: = "No errors detected"


;a smart-write routine which does a destructive test to
;try to figure out if the memory is SRAM or Flash ROM
;and then does the correct one.	 Carry bit will indicate
;if the value was successfully written, C=1 if not written.

smart_wr:
	movx	@dptr, a	;ok, not so smart just yet
	clr	c
	ret





esc:  ;checks to see if <ESC> is waiting on serial port
      ;C=clear if no <ESC>, C=set if <ESC> pressed
      ;buffer is flushed
	push	acc
	clr	c
	jnb	ri,esc2
	mov	a,sbuf
	cjne	a,#27,esc1
	setb	c
esc1:	clr	ri
esc2:	pop	acc
	ret


pint8u: ;prints the unsigned 8 bit value in Acc in base 10
	push	b
	push	acc
	sjmp	pint8b

pint8:	;prints the signed 8 bit value in Acc in base 10
	push	b
	push	acc
	jnb	acc.7, pint8b
	mov	a, #'-'
	lcall	cout
	pop	acc
	push	acc
	cpl	a
	add	a, #1
pint8b: mov	b, #100
	div	ab
	jz	pint8c
	add	a, #'0'
	lcall	cout
pint8c: mov	a, b
	mov	b, #10
	div	ab
	jz	pint8d
	add	a, #'0'
	lcall	cout
pint8d: mov	a, b
	add	a, #'0'
	lcall	cout
	pop	acc
	pop	b
	ret


	;print 16 bit unsigned integer in DPTR, using base 10.
pint16u:	;warning, destroys r2, r3, r4, r5, psw.5
	push	acc
	mov	a, r0
	push	acc
	clr	psw.5
	mov	r2, dpl
	mov	r3, dph

pint16a:mov	r4, #16		;ten-thousands digit
	mov	r5, #39
	lcall	pint16x
	jz	pint16b
	add	a, #'0'
	lcall	cout
	setb	psw.5

pint16b:mov	r4, #232	;thousands digit
	mov	r5, #3
	lcall	pint16x
	jnz	pint16c
	jnb	psw.5, pint16d
pint16c:add	a, #'0'
	lcall	cout
	setb	psw.5

pint16d:mov	r4, #100	;hundreds digit
	mov	r5, #0
	lcall	pint16x
	jnz	pint16e
	jnb	psw.5, pint16f
pint16e:add	a, #'0'
	lcall	cout
	setb	psw.5

pint16f:mov	a, r2		;tens digit
	mov	r3, b
	mov	b, #10
	div	ab
	jnz	pint16g
	jnb	psw.5, pint16h
pint16g:add	a, #'0'
	lcall	cout

pint16h:mov	a, b		;and finally the ones digit
	mov	b, r3
	add	a, #'0'
	lcall	cout

	pop	acc
	mov	r0, a
	pop	acc
	ret

;ok, it's a cpu hog and a nasty way to divide, but this code
;requires only 21 bytes!  Divides r2-r3 by r4-r5 and leaves
;quotient in r2-r3 and returns remainder in acc.  If Intel
;had made a proper divide, then this would be much easier.

pint16x:mov	r0, #0
pint16y:inc	r0
	clr	c
	mov	a, r2
	subb	a, r4
	mov	r2, a
	mov	a, r3
	subb	a, r5
	mov	r3, a
	jnc	pint16y
	dec	r0
	mov	a, r2
	add	a, r4
	mov	r2, a
	mov	a, r3
	addc	a, r5
	mov	r3, a
	mov	a, r0
	ret


	;carry set if esc pressed
	;psw.5 set if return pressed w/ no input
ghex16:
	mov	r2, #0		;start out with 0
	mov	r3, #0
	mov	r4, #4		;number of digits left
	clr	psw.5

ghex16c:
	lcall	cin
	lcall	upper
	cjne	a, #27, ghex16d
	setb	c		;handle esc key
	clr	a
	mov	dph, a
	mov	dpl, a
	ret
ghex16d:cjne	a, #8, ghex16f
	sjmp	ghex16k
ghex16f:cjne	a, #127, ghex16g  ;handle backspace
ghex16k:cjne	r4, #4, ghex16e	  ;have they entered anything yet?
	sjmp	ghex16c
ghex16e:lcall	cout
	lcall	ghex16y
	inc	r4
	sjmp	ghex16c
ghex16g:cjne	a, #13, ghex16i	  ;return key
	mov	dph, r3
	mov	dpl, r2
	cjne	r4, #4, ghex16h
	clr	a
	mov	dph, a
	mov	dpl, a
	setb	psw.5
ghex16h:clr	c
	ret
ghex16i:mov	r5, a		  ;keep copy of original keystroke
	lcall	asc2hex
	jc	ghex16c
	xch	a, r5
	lcall	cout
	mov	a, r5
	push	acc
	lcall	ghex16x
	pop	acc
	add	a, r2
	mov	r2, a
	clr	a
	addc	a, r3
	mov	r3, a
	djnz	r4, ghex16c
	clr	c
	mov	dpl, r2
	mov	dph, r3
	ret

ghex16x:  ;multiply r3-r2 by 16 (shift left by 4)
	mov	a, r3
	swap	a
	anl	a, #11110000b
	mov	r3, a
	mov	a, r2
	swap	a
	anl	a, #00001111b
	orl	a, r3
	mov	r3, a
	mov	a, r2
	swap	a
	anl	a, #11110000b
	mov	r2, a
	ret

ghex16y:  ;divide r3-r2 by 16 (shift right by 4)
	mov	a, r2
	swap	a
	anl	a, #00001111b
	mov	r2, a
	mov	a, r3
	swap	a
	anl	a, #11110000b
	orl	a, r2
	mov	r2, a
	mov	a, r3
	swap	a
	anl	a, #00001111b
	mov	r3, a
	ret

asc2hex:	     ;carry set if invalid input
	clr	c
	push	b
	subb	a,#'0'
	mov	b,a
	subb	a,#10
	jc	a2h1
	mov	a,b
	subb	a,#7
	mov	b,a
a2h1:	mov	a,b
	clr	c
	anl	a,#11110000b	 ;just in case
	jz	a2h2
	setb	c
a2h2:	mov	a,b
	pop	b
	ret




upper:	;converts the ascii code in Acc to uppercase, if it is lowercase
	push	acc 
	clr	c
	subb	a, #97
	jc	upper2		;is it a lowercase character
	subb	a, #26
	jnc	upper2
	pop	acc
	add	a, #224		;convert to uppercase
	ret
upper2: pop	acc		;don't change anything
	ret



phex16:
	push	acc
	mov	a, dph
	lcall	phex
	mov	a, dpl
	lcall	phex
	pop	acc
	ret


pbin:	mov	r0, #8
pbin2:	rlc	a
	mov	f0, c
	push	acc
	mov	a, #'0'
	addc	a, #0
	lcall	cout
	pop	acc
	mov	c, f0
	djnz	r0, pbin2
	rlc	a
	ret


lenstr: mov	r0, #0		;returns length of a string in r0
	push	acc
lenstr1:clr	a
	movc	a,@a+dptr
	jz	lenstr2
	mov	c,acc.7
	inc	r0
	jc	lenstr2
	inc	dptr
	sjmp	lenstr1						 
lenstr2:pop	acc
	ret    


dnlds1: .db	13,10,13,10,"Begin ascii transmission of "
	.db	"Intel HEX format file, "
	.db	"or <ESC> to abort",13,10,13,10,0
dnlds2: .db	13,10,"Download aborted",13,10,13,10,0
dnlds3: .db	13,10,"Download completed",13,10,13,10,0
dnlds4: .db	"Summary:",13,10,0
dnlds5: .db	" lines received",13,10,0
dnlds6a:.db	" bytes received",13,10,0
dnlds6b:.db	" bytes written",13,10,0
dnlds7: .db	"Errors:",13,10,0
dnlds8: .db	" bytes unable to write",13,10,0
dnlds9: .db	" incorrect checksums",13,10,0
dnlds10:.db	" unexpected begin of line",13,10,0
dnlds11:.db	" unexpected hex digits",13,10,0
dnlds12:.db	" unexpected non-hex digits",13,10,0
dnlds13:.db	"No errors detected",13,10,13,10,0



space:	mov	a, #' '
	lcall	cout
	ret



hexdump:
dump:	
	mov	r2, #16		;number of lines to print
	lcall	newline
	lcall	newline
dump1:	clr	c
	mov	a, r6		;print buffer offset, not physical
	subb	a, #buffer&255
	push 	acc
	mov	a, r7
	subb	a, #buffer>>8
	lcall	phex
	pop	acc
	lcall	phex
	mov	a,#':'
	lcall	cout
	lcall	space
	mov	r3, #16		;r3 counts # of bytes to print
	mov	dpl, r6
	mov	dph, r7
dump2:	clr	a
	movc	a, @a+dptr
	inc	dptr
	lcall	phex		;print each byte in hex
	lcall	space
	djnz	r3, dump2
	lcall	space		;print a couple extra spaces
	lcall	space
	mov	r3, #16
	mov	dpl, r6
	mov	dph, r7
dump3:	clr	a
	movc	a, @a+dptr
	inc	dptr
	anl	a, #01111111b	;avoid unprintable characters
	clr	c
	subb	a, #20h
	jnc	dump4		;avoid control characters
	mov	a, #(' ' - 20h)
dump4:	add	a, #20h
	lcall	cout
	djnz	r3, dump3
	lcall	newline
	mov	r6, dpl
	mov	r7, dph
	djnz	r2, dump1	;loop back up to print next line
dump5:	lcall	newline
	ret


newloc:

	lcall	newline
	mov	dptr, #prompt6
	lcall	pstr
	lcall	ghex16
	jc	nloc2
	jb	psw.5, nloc2
	mov	a, dpl
	add	a, #buffer&255	;adjust to physical buffer location
	mov	r6, a
	mov	a, dph
	addc	a, #buffer>>8
	mov	r7, a
	lcall	newline
	ret
nloc2:	mov	dptr, #abort
	lcall	pstr
	lcall	newline
	ret

prompt6:.db	"New memory location: ",0
abort:	.db	"  Command Aborted!",13,10+128


upload:
	lcall	get_mem
	;assume we've got the beginning address in r3/r2
	;and the final address in r5/r4 (r4=lsb)...

	;print out what we'll be doing
	mov	dptr, #uplds3
	lcall	pstr
	mov	a, r2
	clr	c
	subb	a, #buffer&255
	push	acc
	mov	a, r3
	subb	a, #buffer>>8
	lcall	phex
	pop	acc
	lcall	phex
	mov	dptr, #uplds4
	lcall	pstr
	mov	a, r4
	clr	c
	subb	a, #buffer&255
	push	acc
	mov	a, r5
	subb	a, #buffer>>8
	lcall	phex
	pop	acc
	lcall	phex
	lcall	newline

	;need to adjust end location by 1...
	mov	dph, r5
	mov	dpl, r4
	inc	dptr
	mov	r4, dpl
	mov	r5, dph

	mov	dptr, #prompt7
	lcall	pstr
	lcall	cin
	cjne	a, #27, upld2e
	ajmp	abort_it
upld2e: lcall	newline
	mov	dpl, r2
	mov	dph, r3

upld3:	mov	a, r4		;how many more bytes to output??
	clr	c
	subb	a, dpl
	mov	r2, a
	mov	a, r5
	subb	a, dph
	jnz	upld4		;if >256 left, then do next 16
	mov	a, r2
	jz	upld7		;if we're all done
	anl	a, #11110000b
	jnz	upld4		;if >= 16 left, then do next 16
	sjmp	upld5		;otherwise just finish it off
upld4:	mov	r2, #16
upld5:	mov	a, #':'		;begin the line
	lcall	cout
	mov	a, r2
	lcall	phex		;output # of data bytes
	push	dpl
	push	dph
	clr	c
	mov	a, dpl		;adjust output value from physical
	subb	a, #buffer&255
	mov	dpl, a
	mov	a, dph
	subb	a, #buffer>>8
	mov	dph, a
	lcall	phex16		;output memory location
	mov	a, dph
	add	a, dpl
	add	a, r2
	mov	r3, a		;r3 will become checksum
	pop	dph		;restore physical buffer address
	pop	dpl
	clr	a
	lcall	phex		;output 00 code for data
upld6:	clr	a
	movc	a, @a+dptr
	lcall	phex		;output each byte
	add	a, r3
	mov	r3, a
	inc	dptr
	djnz	r2, upld6	;do however many bytes we need
	mov	a, r3
	cpl	a
	inc	a
	lcall	phex		;and finally the checksum
	lcall	newline
	lcall	esc
	jnc	upld3		;keep working if no esc pressed
	sjmp	abort_it
upld7:	mov	a, #':'
	lcall	cout
	clr	a
	lcall	phex
	lcall	phex
	lcall	phex
	inc	a
	lcall	phex
	mov	a, #255
	lcall	phex
upld8:	lcall	newline
	lcall	newline
	ret

uplds3: .db	13,10,13,10,"Sending Intel HEX file from ",0
uplds4: .db	" to ",0

pop_it: pop	acc
	pop	acc
abort_it:
	lcall	newline
	mov	dptr, #abort
	lcall	pstr
	lcall	newline
	ret

prompt7:.db	"Press any key: ",0


get_mem:	;this thing gets the begin and end locations for
		;a few commands.  If an esc or enter w/ no input,
		;it pops it's own return and returns to the menu
		;(nasty programming, but we need tight code for 4k rom)
	lcall	newline
	lcall	newline
	mov	dptr, #beg_str
	lcall	pstr
	lcall	ghex16
	jc	pop_it
	jb	psw.5, pop_it
	push	dph
	push	dpl
	lcall	newline
	mov	dptr, #end_str
	lcall	pstr
	lcall	ghex16
        mov     a, dpl
        add     a, #buffer&255  ;adjust to physical buffer location
        mov     r4, a  
        mov     a, dph
        addc    a, #buffer>>8
        mov     r5, a  
	pop	acc
	add	a, #buffer&255  ;adjust to physical buffer location
	mov	r2, a
	pop	acc
	addc    a, #buffer>>8
	mov	r3, a
	jc	pop_it
	jb	psw.5, pop_it
	lcall	newline
	ret

beg_str:.db	"First Location: ",0
end_str:.db	"Last location:  ",0

