;---------------------------------------------------------------
;
; UHEXLOAD.ASM
;
;   Intel hex file loader for use with M. Eberhard's UBMON
;   for the Altair 8800. The loader is started by typing
;   "L" in UBMON (or jumping to 176000). After the last data
;   record, or after a hex file error is detected, the loader
;   flushes all received characters until 1/2 second passes
;   without receiving any characters. Control then returns
;   to UBMON. This ensures that additional bytes received
;   from hex file reception are not interpreted as commands
;   by UBMON.
;
;   This code is located at FC00h which is the same address
;   as the hard disk boot loader PROM. Obviously, only one
;   or the other can be used.
;
;  Written by Mike Douglas 1/09/2016
;
;---------------------------------------------------------------
	org	0fc00h

; Equates

UBMON	equ	0fd00h		;UBMON (turnkey monitor)

SIOCSR	equ	010h		;2SIO control register
SIODR	equ	011h		;2SIO data register
SIOTDRE	equ	002h		;transmit ready bit
SIORST	equ	003h		;reset command to 6850
SIO8N1	equ	015h		;8 bits, no parity, 1 stop

CR	equ	13
LF	equ	10

;------------------------------------------------------------
; Display message to send the file, then loop receiving 
;    and storing the hex file.
;------------------------------------------------------------
init	lxi	h,mSend		;send file message
	call	dispMsg
	mvi	c,0		;clear echo character flag

; rcvLine - receive a hex file line

rcvLine	lxi	h,mCrLf		;send a CR/LF
	call	dispMsg

wtMark	call	getChar		;read next character
	sui	':'		;record marker?
	jnz	wtMark		;no, keep looking

; Have start of new record. Get the byte count in b and the load
;   address in hl. The load address is echoed to the screen so the
;   user can see the file load progress.

	mov	d,a		;init checksum in D to zero

	call	inByte		;input two hex digits (byte count)
	mov	a,e		;test for zero byte count
	ora	a
	jz	done		;count of 0 means end

	mov	b,e		;B = byte count on line

	inr	c		;set echo flag for address bytes
	call	inByte		;get MSB of address
	mov	h,e		;H = address MSB
	call	inByte		;get LSB of address
	mov	l,e		;L = address LSB
	dcr	c		;clear echo flag

	call	inByte		;ignore/discard record type

; Receive the data bytes of the record and move to memory

data	call	inByte		;read a data byte (2 hex digits)
	mov	m,e		;store in memory
	inx	h
	dcr	b
	jnz	data

	call	inByte		;read and verify checksum
	jz	rcvLine		;checksum good, receive another line

; error - hex conversion or checksum error

error	lxi	h,mError	;display error message
	call	dispMsg
	jmp	flush		;flush rest of file and exit

; done - hex file loaded without error

done	lxi	h,mDone		;display "complete" message
	call	dispMsg	
				;fall through to flush rest of file

; flush - discard rest of transmission so the characters don't go to
;   the monitor ROM. End of transmission is assumed when no more
;   characters are received for 1/2 second.

flush	lxi	d,20833		;time out counter 48 cycle loop = 0.5s

flshLp	in	SIOCSR		;(10) wait for a character
	rrc			;(4)
	jnc	noChar		;(10) no character
	in	SIODR		;flush the character
	jmp	flush		;start a new timeout

noChar	dcx	d		;(5) decrement timeout
	mov	a,d		;(5)
	ora	e		;(4)
	jnz	flshLp		;(10) loop until 1/2s without data

	jmp	UBMON		;exit to monitor

;-----------------------------------------------------------
; inByte - read two ascii hex digits and return binary
;    value in e. If a conversion error occurs, exit
;    directly to error label above.
;
; returns  e = received byte
;	   z flag set if current checksum is zero
; clobbers d, e
;-----------------------------------------------------------
inByte	call	getChar		;get a character
	call	asc2Bin
	jc	error

	add	a		;put in msn, zero lsn
	add	a
	add	a
	add	a
	mov	e,a		;save byte with MSN in E

; 2nd byte (LSN)

	call	getChar		;get a character
	call	asc2Bin
	jc	error

	add	e		;combine msn and lsn
	mov	e,a		;save in E
	add	d		;add character to checksum
	mov	d,a
	ret

;-------------------------------------------------------------
; asc2Bin - ASCII hex digit to binary conversion. Digit
;    passed in a, returned in a. Carry returned clear
;    for valid conversion, carry set for error.
;
; input    a = ascii hex digit
; returns  a = digit value in binary
;	   carry set for data conversion error
; clobbers none 
;-------------------------------------------------------------
asc2Bin	sui	30h		;'0' to 0
	rc			;error - below zero

	cpi	10		;0-9 ?
	jc	ascExit		;yes

	sui	11h		;A-F to 0-5
	rc			;error - below A

	adi	10		;convert back to A-F
	cpi	16		;too big? (carry clear if so)

ascExit	cmc			;complement carry
	ret

;-------------------------------------------------------------
; getChar - read a character from the 6850 (2SIO), and
;    return it in a. The character is also echoed
;    back out the serial port if the echo flag (c)
;    is set (non-zero)
;
; input    c = zero for no echo, non-zero for echo
; returns  a = character read from serial port
; clobbers none 
;-------------------------------------------------------------
getChar	in	SIOCSR		;wait for a character
	rrc	
	jnc	getChar

	mov	a,c		;echo flag (c) set?
	ora	a
	jz	noEcho		;no, don't echo

	in	SIODR		;read the character
	out	SIODR		;echo it
	ret

noEcho	in	SIODR		;read the character
	ret

;------------------------------------------------------------
; dispMsg - display the null-terminated message passed in hl
;
; inputs   hl->null terminated string to display
; clobbers hl (points to null terminator on exit)
;------------------------------------------------------------
dispMsg	in	SIOCSR		;wait for OK to transmit
	ani	SIOTDRE
	jz	dispMsg

	mov	a,m		;get the next message byte
	ora	a		;null terminates
	rz

	out	SIODR		;send the character
	inx	h		;move to next byte
	jmp	dispMsg

;------------------------------------------------------------
;  Messages and Data
;-----------------------------------------------------------
mSend	db	cr,lf,'Send Hex File Now',0

mError	db	cr,lf,lf,'Hex File Error',cr,lf,0

mDone	db	cr,lf,'Load Complete'
mCrLf	db	cr,lf,0

	end
