;++
;
; PIC16F84 chip programmer.
;
; By John Wilson <wilson@dbit.com>.
;
; Copyright (C) 1999 by D Bit.  All rights reserved.
;
; 02/28/99	JMBW	Created.
; 03/04/99	JMBW	Basic functions work for PIC16F84.
; 03/06/99	JMBW	/INHX8M, /INHX8S, /INHX32 switches work.
;
;--
	.radix	8
;
	locals
;
fbufl=	1024d			;.HEX file buffer length
;
bel=	7
lf=	12
cr=	15
;
rs232_base=word ptr 0400h	;base I/O addr of each COM port
timer_low=word ptr 046Ch	;low word of BIOS time
;
; INS8250 bits that we care about:
break=	100	;break (in LCR), 1 => +12, 0 => -12
rts=	2	;request to send (in MCR), 1 => -12, 0 => +12
dtr=	1	;data terminal ready (in MCR), 1 => +12, 0 => -12
cts=	20	;clear to send (in MSR), 1 => -12, 0 => +12
;
callr	macro	dest		;;call and return
	jmp	&dest
	endm
;
icall	macro	dest		;;indirect call
	call	word ptr ds:&dest
	endm
;
ascic	macro	lab,str		;;counted ASCII string with label and <CRLF>
	local	x
&lab	db	x,&str,cr,lf
x=	$-&lab-1
	endm
;
; Macro to define an entry in a keyword table (for GETW/TBLUK):
;	db	length to match
;	db	total length
;	db	string
;	dw	addr
;
kw	macro	text,addr
kh=	0
ki=	0
	irpc	kc,&text
ki=	ki+1
ifidn <&kc>,<->
kh=	ki
endif
	endm ;; irpc
ife kh
	.err	No hyphen in string:  &text
	exitm
endif ;; ife kh
	db	kh-1,ki-1	;;len to match, total len
	irpc	kc,&text	;;keyword text
ifdif <&kc>,<->
	db	'&kc'
endif
	endm ;; irpc
	dw	&addr
	endm
;
; It's a .COM file so everything is in one segment (so don't enlarge FBUFL too
; much or it won't fit).
;
; Throughout program:
; DF	clear (string instructions move forwards)
; DS	same as CS
; ES	scratch, generally used to point at BUFSEG (rarely touched otherwise)
;
code	segment
	assume	cs:code
	org	100h
;
start:	cld			;DF=0
	mov	dx,offset banner ;point at banner
	mov	ah,09h		;func=print
	int	21h
	cmp	sp,offset pdl	;space for stack?
	jb	nomem		;no
	mov	sp,offset pdl	;yes, back it up
	mov	bx,sp		;copy
	mov	cl,4		;shift count
	shr	bx,cl		;find # paragraphs to keep
	mov	ah,4Ah		;func=setblock
	int	21h
	jnc	jcl1		;success
nomem:	mov	si,offset allerr ;memory alloc error
fatal:	lodsb			;read length byte
	cbw			;AH=0
	mov	dx,si		;point at string
	mov	cx,ax		;copy length
	mov	bx,0002h	;handle=STDERR
	mov	ah,40h		;func=write
	int	21h
	mov	ax,4C01h	;func=punt
	int	21h
jcl1:	; parse JCL from command line
	call	set84		;assume part type is PIC16F84
	mov	si,80h		;point at it
	lodsb			;get length byte
	xor	ah,ah		;AH=0
	mov	cx,ax		;copy
@@1:	call	skip		;skip white space
	jc	@@4		;done
	cmp	al,'/'		;switch?
	jne	@@3		;no, must be filename
	inc	si		;skip
	dec	cx
	call	getw		;get switch name
	jc	@@2		;lone "/", error
	mov	ax,offset jcltab ;point at keyword table
	call	tbluk		;look it up
	jc	@@2		;invalid switch, error
	call	ax		;handle it
	jmp	short @@1	;continue parsing
@@2:	mov	si,offset badjcl ;syntax error
	jmp	fatal
@@3:	; filename
	call	getw		;parse it
	jc	@@2		;huh?  shouldn't happen
	xchg	dx,ds:fnctr	;set length, get old length
	test	dx,dx		;shouldn't be any
	jnz	@@2		;we already had one filename, error
	mov	ds:fnptr,bx	;save ptr
	jmp	short @@1	;continue parsing
@@4:	; look up COM port's base I/O addr
	mov	bx,ds:com	;get COM port index
	xor	ax,ax		;load 0
	mov	es,ax		;point at BIOS data
	mov	ax,es:rs232_base[bx] ;look up base I/O addr of COM port
	test	ax,ax		;if any
	jnz	@@5		;yes
	mov	si,offset nxport ;nonexistent port
	jmp	fatal
@@5:	; set addr of each port we care about
	mov	ds:port,ax	;(e.g. 3F8) save base port just for fun
	add	ax,3		;(e.g. 3FB) line ctrl reg
	mov	ds:lcr,ax
	inc	ax		;(e.g. 3FC) modem ctrl reg
	mov	ds:mcr,ax
	inc	ax		;(e.g. 3FE) modem status reg
	inc	ax
	mov	ds:msr,ax
	; allocate memory
	mov	bx,ds:bufmax	;get max offset into buf
	mov	ax,ds:bufmax+2
	add	bx,1+0Fh	;find size, round to paragraph boundary
	adc	ax,0
	mov	cl,4		;shift count
	shr	bx,cl		;convert to paragraph count
	shl	al,cl
	or	bh,al		;combine (must fit)
	mov	ah,48h		;func=GETBLOCK
	int	21h
	jnc	gotmem
	 jmp	nomem		;failed
gotmem:	mov	ds:bufseg,ax	;save seg addr
	icall	clear		;clear buffer
	mov	al,ds:inhx8m	;sum up hex file type flags
	add	al,ds:inhx8s
	add	al,ds:inhx32
	test	al,not 1	;should add up to 0 or 1
	jz	@@1		;OK
	mov	si,offset swtcnf ;switch conflict
	jmp	fatal
@@1:	; actually do whatever we're here to do
	mov	al,ds:blkflg	;see if any are set
	or	al,ds:eraflg
	or	al,ds:rdflg
	or	al,ds:vfyflg
	or	al,ds:wrflg
	jnz	@@2		;got something
	 inc	byte ptr ds:wrflg ;default cmd is /WRITE
@@2:	test	byte ptr ds:eraflg,-1 ;/ERASE?
	jz	@@3
	 icall	erase		;erase part
@@3:	test	byte ptr ds:blkflg,-1 ;/BLANK?
	jz	@@4
	 icall	verify		;verify part against cleared buffer
@@4:	test	byte ptr ds:wrflg,-1 ;/WRITE?
	jz	@@5
	call	load		;load file
	icall	write		;write to part
@@5:	test	byte ptr ds:vfyflg,-1 ;/VERIFY?
	jz	@@6
	call	load		;load file
	icall	verify		;verify part against file contents
@@6:	test	byte ptr ds:rdflg,-1 ;/READ?
	jz	@@7
	icall	read		;read part into file
	call	dump		;dump to file
@@7:	int	20h
;
; Routines to handle command line switches (generally just set a flag for later
; use):
;
blkcmd:	; /BLANK
	mov	byte ptr ds:blkflg,1 ;set flag
	ret
;
	irp	x,<1,2,3,4>
com&x:	; /COMn, set COM port where COM84 burner is attached
	mov	word ptr ds:com,(&x-1)*2
	ret
	endm ; IRP
;
eracmd:	; /ERASE
	mov	byte ptr ds:eraflg,1 ;set flag
	ret
;
help:	; /?, print help message
	mov	dx,offset jclhlp ;command line help
	mov	ah,09h		;func=print
	int	21h
	int	20h		;happy exit, don't return
;
h8mcmd:	; /INHX8M
	mov	byte ptr ds:inhx8m,1 ;merged .HEX file
	ret
;
h8scmd:	; /INHX8S
	mov	byte ptr ds:inhx8s,1 ;split .HXL/.HXH files
	ret
;
h32cmd:	; /INHX32
	mov	byte ptr ds:inhx32,1 ;single .HEX file with 32-bit addrs
	ret
;
nercmd:	; /NOERASE
	mov	byte ptr ds:nerflg,1 ;set flag
	ret
;
rdcmd:	; /READ
	mov	byte ptr ds:rdflg,1 ;set flag
	ret
;
vfycmd:	; /VERIFY
	mov	byte ptr ds:vfyflg,1 ;set flag
	ret
;
wrcmd:	; /WRITE
	mov	byte ptr ds:wrflg,1 ;set flag
	ret
;
	subttl	command routines
;+
;
; Load file data into buffer.
;
;-
load:	test	byte ptr ds:inhx8s,-1 ;split?
	jnz	@@5		;yes
	mov	si,offset hexext ;default extension
	call	ifile		;open input file
@@1:	; read next record
	call	rdhex		;read next record
	mov	al,ds:hextyp	;get record type
	test	al,al		;data?
	jnz	@@3		;no
	mov	es,ds:bufseg	;get seg addr of buf
	mov	cl,ds:hexlen	;get # data bytes
	xor	ch,ch		;CH=0
	jcxz	@@1		;none, ignore this rec
	mov	di,ds:hexadr	;get starting byte addr
	mov	ax,cx		;copy length
	dec	ax		;-1 (known NZ)
	add	ax,di		;find addr of last byte of xfr
	jc	@@2		;overflow, won't fit
	cmp	ax,ds:bufmax	;in range?
	ja	@@2		;no
	mov	si,offset hexdat ;point at data
	rep	movsb		;copy into buf
	jmp	short @@1	;around for more
@@2:	mov	si,offset adrran ;addr out of range
	jmp	fatal
@@3:	cmp	al,04h		;setting MSW of addr?
	je	@@1		;yes, ignore
;;; 01=EOF, 03=segment, others=error
	mov	bx,ds:hexhnd	;get handle
	test	bx,bx		;STDIN?
	jz	@@4
	mov	ah,3Eh		;no, func=close
	int	21h
@@4:	ret
@@5:	; split input files
	mov	si,offset hxlext ;default extension for low byte
	xor	bx,bx		;offset +0 of each word
	call	@@6		;do low bytes
	mov	si,offset hxhext ;default extension for high byte
	mov	bx,1		;offset +1 of each word
	;callr	<short @@6>	;do high bytes, return
@@6:	; load next file -- SI=default ext, BX=even/odd flag
	push	bx		;save even/odd flag
	call	ifile		;open input file
	pop	bx		;restore
@@7:	; read next record
	push	bx		;save even/odd flag
	call	rdhex		;read next record
	pop	bx		;restore
	mov	al,ds:hextyp	;get record type
	test	al,al		;data?
	jnz	@@10		;no
	mov	es,ds:bufseg	;get seg addr of buf
	mov	cl,ds:hexlen	;get # data bytes
	xor	ch,ch		;CH=0
	jcxz	@@7		;none, ignore this rec
	mov	di,ds:hexadr	;get starting word addr
	add	di,di		;*2 = byte addr
	add	di,bx		;+1 if high byte
	mov	ax,cx		;copy length
	dec	ax		;-1 (known NZ)
	add	ax,ax		;*2 since we're doing alternate bytes
	add	ax,di		;find addr of last byte of xfr
	jc	@@9		;overflow, won't fit
	cmp	ax,ds:bufmax	;in range?
	ja	@@9		;no
	mov	si,offset hexdat ;point at data
@@8:	movsb			;copy a byte
	inc	di		;skip intervening byte
	loop	@@8		;loop through all
	jmp	short @@7	;around for more
@@9:	mov	si,offset adrran ;addr out of range
	jmp	fatal
@@10:	cmp	al,04h		;setting MSW of addr?
	je	@@7		;yes, ignore
;;; 01=EOF, 03=segment, others=error
	mov	bx,ds:hexhnd	;get handle
	test	bx,bx		;STDIN?
	jz	@@11
	mov	ah,3Eh		;no, func=close
	int	21h
@@11:	ret
;+
;
; Open input file (handle in HEXHND).
;
; si	default extension
;
;-
ifile:	mov	dx,ds:fnctr	;get filename length
	test	dx,dx		;we have a file, right?
	jz	@@1		;no, use STDIN
	mov	bx,ds:fnptr	;get filename pointer
	call	defext		;form filename
	mov	ax,3D00h	;func=open /RONLY
	int	21h
	jnc	@@2
	mov	si,offset operr	;error msg
	jmp	fatal
@@1:	mov	dx,offset stdin	;print msg
	mov	ah,09h		;func=print
	int	21h
	xor	ax,ax		;handle=STDIN
@@2:	mov	ds:hexhnd,ax	;save handle
	mov	word ptr ds:fctr,0 ;nothing in buf yet
	ret
;+
;
; Dump buffer to file(s).
;
;-
dump:	test	byte ptr ds:inhx8s,-1 ;split?
	jnz	@@6		;yes
	mov	si,offset hexext ;default extension
	call	ofile		;open output file
	test	byte ptr ds:inhx32,-1 ;32-bit addressing?
	jz	@@1
	; set MSW of addr to zeros
	xor	ax,ax		;load 0
	mov	ds:hexadr,ax	;addr field is meaningless
	mov	word ptr ds:hexdat,ax ;2 bytes of addr in data field
	mov	byte ptr ds:hexlen,2 ;2 data bytes
	mov	byte ptr ds:hextyp,04h ;type=set high addr
	call	wrhex		;write record
@@1:	mov	si,ds:dmplst	;get list of ranges
@@2:	; write next range (SI=DMPLST ptr)
	lodsw			;get a word
	cmp	ax,-1		;end of list?
	je	@@7		;yes
	mov	ds:hexadr,ax	;save
@@3:	; write next record (SI points at 2nd word of DMPLST entry)
	mov	cx,[si]		;fetch end of range
	push	si		;save
	mov	si,ds:hexadr	;get byte addr
	sub	cx,si		;find # bytes until last byte of range
	jc	@@5		;last rec finished this range, skip
	cmp	cx,0Fh		;enough to do 10h bytes?
	jb	@@4		;no, stop short
	 mov	cx,0Fh		;at least, so take 10h (0Fh is offset of last)
@@4:	inc	cx		;+1 to get length of range
	mov	ds:hexlen,cl	;save length
	mov	ds:hextyp,ch	;type=data
	push	ds		;copy DS to ES
	pop	es
	mov	di,offset hexdat ;point at data field
	mov	ds,ds:bufseg	;DS:SI points into buffer
	push	cx		;save length
	rep	movsb		;copy
	push	es		;restore DS
	pop	ds
	call	wrhex		;write the rec
	pop	cx		;restore length
	add	ds:hexadr,cx	;advance ptr
	pop	si		;restore DMPLST ptr
	jmp	short @@3	;do next rec of range
@@5:	; finished this range
	pop	si		;restore DMPLST ptr
	lodsw			;skip end of range
	jmp	short @@2	;go start next range, if any
@@6:	jmp	short @@9
@@7:	; finished
	xor	ax,ax		;load 0
	mov	ds:hexadr,ax	;clear addr field
	mov	ds:hexlen,al	;no data
	inc	ax		;+1
	mov	ds:hextyp,al	;type=EOF
	call	wrhex		;write EOF rec
	call	flush		;flush last buffer to file, if needed
	mov	bx,ds:hexhnd	;get handle
	cmp	bx,0001h	;STDOUT?
	jz	@@8
	mov	ah,3Eh		;no, func=close
	int	21h
@@8:	ret
@@9:	; split output files
	mov	si,offset hxlext ;default extension for low byte
	xor	bx,bx		;offset +0 of each word
	call	@@10		;do low bytes
	mov	si,offset hxhext ;default extension for high byte
	mov	bx,1		;offset +1 of each word
	;callr	<short @@10>	;do high bytes, return
@@10:	; dump next file -- SI=default ext, BX=even/odd flag
	push	bx		;save even/odd flag
	call	ofile		;open output file
	pop	bx		;restore
	mov	si,ds:dmplst	;get list of ranges
@@11:	; write next range (SI=DMPLST ptr, BX=even/odd flag)
	lodsw			;get a word
	cmp	ax,-1		;end of list?
	je	@@7		;yes
	or	ax,bx		;skip to odd byte if appropriate
	mov	ds:hexad1,ax	;save
@@12:	; write next record (SI points at 2nd word of DMPLST entry)
	mov	cx,[si]		;fetch end of range
	push	si		;save
	mov	si,ds:hexad1	;get byte addr
	sub	cx,si		;find # bytes until last byte of range
	jc	@@15		;last rec finished this range, skip
	add	cx,1+1		;+1 to get length of range, +1 to round up to
				;next word (OK if we get only 1/2 of last word)
	rcr	cx,1		;we're only doing alternate bytes (catch CF)
	cmp	cx,10h		;enough to do 10h bytes?
	jb	@@13		;no, stop short
	 mov	cx,10h		;at least, so take 10h (0Fh is offset of last)
@@13:	mov	ds:hexlen,cl	;save length
	mov	ds:hextyp,ch	;type=data
	mov	ax,ds:hexad1	;get addr
	shr	ax,1		;find word addr
	mov	ds:hexadr,ax	;save
	push	ds		;copy DS to ES
	pop	es
	mov	di,offset hexdat ;point at data field
	mov	ds,ds:bufseg	;DS:SI points into buffer
	push	cx		;save length
@@14:	movsb			;copy a byte
	inc	si		;skip intervening byte
	loop	@@14
	push	es		;restore DS
	pop	ds
	push	bx		;save even/odd flag
	call	wrhex		;write the rec
	pop	bx		;restore
	pop	cx		;restore length
	add	cx,cx		;word count *2 to get change in byte addr
	add	ds:hexad1,cx	;advance ptr
	pop	si		;restore DMPLST ptr
	jmp	short @@12	;do next rec of range
@@15:	; finished this range
	pop	si		;restore DMPLST ptr
	lodsw			;skip end of range
	jmp	short @@11	;go start next range, if any
;+
;
; Open output file (handle in HEXHND).
;
; si	default extension
;
;-
ofile:	mov	dx,ds:fnctr	;get filename length
	test	dx,dx		;we have a file, right?
	jz	@@1		;no, use STDOUT
	mov	bx,ds:fnptr	;get filename pointer
	call	defext		;form filename
	xor	cx,cx		;mode=default
	mov	ah,3Ch		;func=create
	int	21h
	jnc	@@2
	mov	si,offset operr	;error msg
	jmp	fatal
@@1:	mov	dx,offset stdout ;print msg
	mov	ah,09h		;func=print
	int	21h
	mov	ax,0001h	;handle=STDOUT
@@2:	mov	ds:hexhnd,ax	;save handle
	mov	word ptr ds:fptr,offset fbuf ;nothing written to buf yet
	mov	word ptr ds:fctr,fbufl
	ret
;+
;
; Copy a filename into buffer and add a default extension if needed.
;
; bx	pointer
; dx	length (assumed non-zero)
; si	.ASCIZ extension
;
; dx	returns ptr to buffer (containing .ASCIZ filename)
;
;-
defext:	push	ds		;copy DS to ES
	pop	es
	mov	di,offset hexdat ;pt at buf
	xchg	bx,si		;swap ptrs
	mov	cx,dx		;copy length
	xor	dl,dl		;DL=0 (no '.' yet)
@@1:	; loop through all chars;  DL=1 if '.' seen
	lodsb			;get a char
	stosb			;save
	cmp	al,'/'		;pathname separator?
	je	@@5
	cmp	al,'\'
	je	@@5
	cmp	al,'.'		;extension separator (or part of path)?
	je	@@6
@@2:	loop	@@1		;loop through all characters
	xor	al,al		;load 0 in case we have extension already
	test	dl,dl		;seen a . since last / or \?
	mov	dx,offset hexdat ;[point at buf]
	jnz	@@4		;yes, just add the 0
	mov	si,bx		;get ptr to default extension
	mov	bx,di		;save ptr
@@3:	lodsb			;copy a byte
@@4:	stosb
	test	al,al		;is that it?
	jnz	@@3		;loop if not
	ret
@@5:	; / or \
	xor	dl,dl		;clear "." flag
	jmp	short @@2	;(period in path doesn't count as extension)
@@6:	; .
	mov	dl,1		;set "." flag
	jmp	short @@2
;+
;
; Skip white space (only!).
;
; CF=1 if EOL, otherwise:
; al	first non-blank char
; si,cx	updated to point at char in AL
;
;-
skip:	jcxz	@@2		;EOL already
@@1:	lodsb			;get a byte
	cmp	al,' '		;blank or ctrl char?
	ja	@@3		;no, stop (CF=0)
	loop	@@1		;loop
@@2:	stc			;hit EOL
	ret
@@3:	dec	si		;unget
	ret
;+
;
; Parse a word from the input line.
;
; ds:si	current position
; cx	# chars left
;
; On return:
; si	points at posn after last char of word
; cx	updated
; bx	points at begn of word if CF=0
; dx	length of word
;
;-
getw:	jcxz	@@2		;EOL already
@@1:	; look for beginning of word
	mov	bx,si		;in case word starts here
	lodsb			;get a char
	cmp	al,' '		;blank or ctrl?
	ja	@@4		;no
	loop	@@1		;loop
@@2:	stc			;no luck
	ret
@@3:	; look for end of word
	lodsb			;get a char
@@4:	cmp	al,' '		;blank or ctrl?
	jbe	@@6		;yes, end of word
	cmp	al,'/'		;switch?
	je	@@6
	cmp	al,'='		;value?
	je	@@6
	cmp	al,'a'		;lower case?
	jb	@@5
	cmp	al,'z'		;hm?
	ja	@@5
	and	al,not 40	;yes, convert
	mov	[si-1],al	;put back
@@5:	loop	@@3		;loop
	inc	si		;compensate for next inst
@@6:	dec	si		;unget
	mov	dx,si		;calc length
	sub	dx,bx		;CF=0
	ret
;+
;
; Look up a keyword in a table.
;
; ds:bx	keyword } from GETW
; dx	length  }
; ss:ax	table
;
; Returns CF=1 if not found, otherwise AX=number from table.
;
; This routine doesn't require that DS=CS, so it may be used to parse
; environment strings.
;
; si,cx preserved either way.
;
;-
tbluk:	push	cx		;save
	push	si
	push	ds
	mov	si,ax		;pt at table
	push	ds		;copy DS to ES
	pop	es
	push	cs		;and CS to DS
	pop	ds
	xor	ch,ch		;ch=0
@@1:	lodsw			;get length,,length to match
	test	al,al		;end?
	jz	@@4
	mov	cl,ah		;assume bad length
	cmp	al,dl		;is ours long enough?
	ja	@@2		;no
	sub	ah,dl		;too long?
	jc	@@2		;yes
	mov	cl,dl		;just right
	mov	di,bx		;point at keyword
	repe	cmpsb		;match?
	je	@@3
	add	cl,ah		;no, add extra length
@@2:	add	si,cx		;skip to end
	lodsw			;skip addr
	jmp	short @@1	;loop
@@3:	; got it
	mov	cl,ah		;get extra length
	add	si,cx		;skip to end
	lodsw			;get dispatch addr
	stc			;makes CF=0 below
@@4:	; not found
	cmc			;CF=-CF
	pop	ds		;restore regs
	pop	si
	pop	cx
	ret
;+
;
; Print a 16-bit number as 4 hex digits.
;
; ax	number
; es:di	buf ptr (updated on return)
;
;-
prhex4:	push	ax		;save
	mov	al,ah		;get high byte
	call	prhex2		;do it first
	pop	ax		;restore
	;callr	<short prhex2>	;do low byte, return
;
prhex2:	; as above, 2 digits from AL to ES:DI
	mov	ah,al		;copy
	and	ax,0FF0h	;isolate high, low bytes
	mov	cl,4		;shift count
	shr	al,cl		;right-justify high digit
	cmp	al,0Ah		;CF=1 if 0-9
	sbb	al,69h		;AL=96-9F or A1-A6 (AF=1 if 0-9, CF=1 always)
	das			;low byte -6 if 0-9, high byte -60h
	stosb			;save
	mov	al,ah		;as above for low digit
	cmp	al,0Ah
	sbb	al,69h
	das
	stosb			;save
	ret
;
	subttl	PIC16F84-specific routines
;+
;
; Set parameters for 16F84.
;
;-
set84:	mov	word ptr ds:bufmax,2140h*2-1 ;size of PIC16F84 image
	mov	word ptr ds:bufmax+2,0
	mov	word ptr ds:prgsiz,1024d ;size of program EEPROM (words)
	mov	word ptr ds:datsiz,64d ;size of data EEPROM (bytes)
	mov	word ptr ds:dmplst,offset dump84 ;list of ranges to dump
	mov	word ptr ds:read,offset rd84 ;read part to buf
	mov	word ptr ds:write,offset wr84 ;write buf to part
	mov	word ptr ds:verify,offset vfy84 ;verify part against buf
	mov	word ptr ds:erase,offset era84 ;erase part
	mov	word ptr ds:clear,offset clb84 ;clear buf
	ret
;+
;
; Read entire 16F84 program memory.
;
;-
rd84:	call	penter		;enter programming mode
	mov	es,ds:bufseg	;point at buf
	xor	di,di		;init offset
@@1:	; read next word of program memory
	push	di		;save
	mov	cl,04		;cmd=read data from program memory
	call	cmdr84
	pop	di		;restore
	stosw			;save word
	push	di		;save again
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	di		;restore
	mov	ax,di		;copy byte addr
	shr	ax,1		;/2 = word addr
	cmp	ax,ds:prgsiz	;done all?
	jb	@@1
	; switch to config space
	mov	ax,3FFFh	;all ones, no op if we really wrote it
	xor	cl,cl		;cmd=load configuration (set PC to 2000h)
	call	cmdx84
	mov	di,2000h*2	;point at config data
@@2:	; read next word of config memory
	push	di		;save
	mov	cl,04		;cmd=read data from program memory
	call	cmdr84
	pop	di		;restore
	stosw			;save word
	push	di		;save again
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	di		;restore
	cmp	di,2008h*2	;done all?
	jb	@@2
	call	pexit		;off
	call	penter		;on again to clear PC
	mov	di,2100h*2	;point at EEPROM memory
@@3:	; read next byte of data memory
	push	di		;save
	mov	cl,05		;cmd=read data from data memory
	call	cmdr84
	pop	di		;restore
	xor	ah,ah		;clear unused high byte
	stosw			;save byte
	push	di		;save again
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	di		;restore
	mov	ax,di		;copy byte addr
	sub	ax,2100h*2	;byte offset into data EEPROM space
	shr	ax,1		;byte addr /2 = word addr
	cmp	ax,ds:datsiz	;done all?
	jb	@@3
	callr	pexit		;exit programming mode, return
;+
;
; Write entire 16F84 program memory.
;
;-
wr84:	test	byte ptr ds:nerflg,-1 ;/NOERASE?
	jnz	@@1		;yes, skip
	 call	era84		;no, erase chip first
@@1:	call	penter		;enter programming mode
	mov	es,ds:bufseg	;point at buf
	xor	di,di		;init offset
@@2:	; read next data word to see if it's already at the right value
	; (avoids a slow 10 msec programming cycle if so)
	push	di		;save
	mov	cl,04		;cmd=read data from program memory
	call	cmdr84
	pop	di		;restore
	scasw			;matches?
	je	@@3		;yes, skip all this
	dec	di		;no, back up
	dec	di
	; write next data word
	mov	ax,es:[di]	;fetch data word
	push	di		;save
	mov	cl,02		;cmd=load data for program memory
	call	cmdx84
	mov	cl,10		;cmd=begin programming
	call	cmd84
	call	wait10		;wait 10 ms
	pop	di		;restore
	; verify word
	push	di		;save
	mov	cl,04		;cmd=read data from program memory
	call	cmdr84
	pop	di		;restore
	scasw			;matches?
	je	@@3		;yes
	 jmp	verr84		;verify error, report it
@@3:	push	di		;save again
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	di		;restore
	mov	ax,di		;copy byte addr
	shr	ax,1		;/2 = word addr
	cmp	ax,ds:prgsiz	;done all?
	jb	@@2
	; switch to config space
	mov	ax,3FFFh	;all ones, no op if we really wrote it
	xor	cl,cl		;cmd=load configuration (set PC to 2000h)
	call	cmdx84
	mov	di,2000h*2	;point at config data
@@4:	; read next word of config memory
	cmp	di,2004h*2	;do 2000-2003 and 2007
	jb	@@5
	cmp	di,2007h*2
	jae	@@5
	scasw			;2004-2006, skip the word
	jmp	short @@6
@@5:	push	di		;save
	mov	cl,04		;cmd=read data from program memory
	call	cmdr84
	pop	di		;restore
	scasw			;matches?
	je	@@6		;yes, skip all this
	dec	di		;no, back up
	dec	di
	; write next data word
	mov	ax,es:[di]	;fetch data word
	push	di		;save
	mov	cl,02		;cmd=load data for program memory
	call	cmdx84
	mov	cl,10		;cmd=begin programming
	call	cmd84
	call	wait10		;wait 10 ms
	pop	di		;restore
	; verify word
	push	di		;save
	mov	cl,04		;cmd=read data from program memory
	call	cmdr84
	pop	di		;restore
	scasw			;matches?
	jne	@@9		;no
@@6:	push	di		;save again
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	di		;restore
	cmp	di,2008h*2	;done all?
	jb	@@4
	call	pexit		;off
	call	penter		;on again to clear PC
	mov	di,2100h*2	;point at EEPROM memory
@@7:	; read next byte of data memory
	push	di		;save
	mov	cl,05		;cmd=read data from data memory
	call	cmdr84
	pop	di		;restore
	scasb			;matches?
	je	@@8		;yes, skip all this
	dec	di		;no, back up
	; write next data byte
	mov	al,es:[di]	;fetch data byte
	xor	ah,ah		;MSB=0
	push	di		;save
	mov	cl,03		;cmd=load data for data memory
	call	cmdx84
	mov	cl,10		;cmd=begin programming
	call	cmd84
	call	wait10		;wait 10 ms
	pop	di		;restore
	; verify byte
	push	di		;save
	mov	cl,05		;cmd=read data from data memory
	call	cmdr84
	pop	di		;restore
	scasb			;matches?
	jne	@@9		;no
@@8:	push	di		;save again
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	di		;restore
	inc	di		;skip unused high byte
	mov	ax,di		;copy byte addr
	sub	ax,2100h*2	;byte offset into data EEPROM space
	shr	ax,1		;byte addr /2 = word addr
	cmp	ax,ds:datsiz	;done all?
	jb	@@7
	callr	pexit		;exit programming mode, return
@@9:	xor	ah,ah		;clear high byte
	inc	di		;bump to next word as expected
	jmp	verr84		;verify error, report it
;+
;
; Verify entire 16F84 program memory.
;
;-
vfy84:	call	penter		;enter programming mode
	mov	es,ds:bufseg	;point at buf
	xor	di,di		;init offset
@@1:	push	di		;save
	mov	cl,04		;cmd=read data from program memory
	call	cmdr84
	pop	di		;restore
	scasw			;matches?
	jne	@@4		;no
	push	di		;save again
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	di		;restore
	mov	ax,di		;copy byte addr
	shr	ax,1		;/2 = word addr
	cmp	ax,ds:prgsiz	;done all?
	jb	@@1
	; switch to config space
	mov	ax,3FFFh	;all ones, no op if we really wrote it
	xor	cl,cl		;cmd=load configuration (set PC to 2000h)
	call	cmdx84
	mov	di,2000h*2	;point at config data
@@2:	; verify next word of config memory
	push	di		;save
	mov	cl,04		;cmd=read data from program memory
	call	cmdr84
	pop	di		;restore
	scasw			;matches?
	jne	@@4		;no
	push	di		;save again
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	di		;restore
	cmp	di,2008h*2	;done all?
	jb	@@2
	call	pexit		;off
	call	penter		;on again to clear PC
	mov	di,2100h*2	;point at EEPROM memory
@@3:	; verify next byte of data memory
	push	di		;save
	mov	cl,05		;cmd=read data from data memory
	call	cmdr84
	pop	di		;restore
	scasb			;matches?
	lea	di,[di+1]	;[advance to next word]
	jne	@@4		;no
	push	di		;save again
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	di		;restore
	mov	ax,di		;copy byte addr
	sub	ax,2100h*2	;byte offset into data EEPROM space
	shr	ax,1		;byte addr /2 = word addr
	cmp	ax,ds:datsiz	;done all?
	jb	@@3
	callr	pexit		;exit programming mode, return
@@4:	;jmp	short verr84	;verify error, report it
;+
;
; Report verification error.
; Chip is still in program/verify mode.
;
; ax	value read
; es:di	points one word past value expected in BUFSEG
;
;-
verr84:	dec	di		;point at failed word
	dec	di
	push	ax		;save value we got
	push	word ptr es:[di] ;value we expected
	shr	di,1		;byte addr /2 = word addr
	push	di		;address
	call	pexit		;exit programming mode
	push	ds		;copy DS to ES
	pop	es
	mov	di,offset veradr ;address
	pop	ax		;get addr
	call	prhex4		;convert to hex
	mov	di,offset verexp ;same for value expected
	pop	ax
	call	prhex4
	mov	di,offset vergot ;value we got
	pop	ax
	call	prhex4
	mov	si,offset vermsg ;point at string
	jmp	fatal
;+
;
; Clear 16F84 memory.
;
; Doing bulk erases of the program and data memory individually as described in
; the regular part of the doc doesn't seem to work (program memory comes out as
; all 2000h instead of 3FFFh for one thing), and in any case there's officially
; no way to clear the config word.  But, the procedure in section 4.1 of the
; Microchip DS30262B PIC16F8X programming document, which uses otherwise
; undocumented command opcodes 01 and 07 to disable code protection, also
; clears the entire device.
;
;-
era84:	call	penter		;enter programming mode
	mov	ax,3FFFh	;all ones, no op if we really wrote it
	xor	cl,cl		;cmd=load configuration (set PC to 2000h)
	call	cmdx84
	mov	cx,7		;# times to increment PC to get to 2007h
@@1:	push	cx		;save
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	cx		;restore
	loop	@@1
	mov	cl,01		;cmd=who knows?  (from section 4.1 of prog doc)
	call	cmd84
	mov	cl,07		;cmd=who knows what else?
	call	cmd84
	mov	cl,10		;cmd=begin programming
	call	cmd84
	call	wait10		;wait 10 ms
	mov	cl,01		;again, mystery cmds from Microchip doc
	call	cmd84
	mov	cl,07
	call	cmd84
	; clear data memory
	call	pexit		;reset PC
	call	penter
	mov	cx,ds:datsiz	;get # data locations
@@2:	push	cx		;save
	mov	ax,3FFFh	;load all ones
	mov	cl,03		;cmd=load data for data memory
	call	cmdx84
	mov	cl,10		;cmd=begin programming
	call	cmd84
	call	wait10		;wait 10 ms
	mov	cl,06		;cmd=increment PC
	call	cmd84
	pop	cx		;restore
	loop	@@2		;loop through all data memory
	callr	pexit		;exit programming mode, return
;+
;
; Clear buffer to match a blank 16F84 EEPROM.
;
;-
clb84:	push	es		;save
	mov	es,ds:bufseg	;point at buf
	mov	cx,2100h	;size in words
	mov	ax,3FFFh	;14 bits of ones
	xor	di,di		;offset=0
	rep	stosw		;clear whole buf
	xor	ah,ah		;all ones in RH, 0 in LH (8-bit bytes)
	mov	cl,40h		;fill EPROM area with all 00FFs
	rep	stosw
	pop	es		;restore
	ret
;+
;
; Send command to 16F84.
;
; bx	SDELAY timer value (updated on return)
; cl	command
;
;-
cmd84:	mov	al,cl		;copy command
	mov	cx,6		;6 bits
	callr	psend		;send it, return
;+
;
; Send command to 16F84 with transmitted data word.
;
; ax	data word (in low 14 bits)
; bx	SDELAY timer value (updated on return)
; cl	command
;
;-
cmdx84:	push	ax		;save
	mov	al,cl		;copy command
	mov	cx,6		;6 bits
	call	psend		;send it
	call	sdelay		;wait 2 more 838.1 ns clocks (1 usec for sure)
	call	sdelay
	pop	ax		;restore word
	and	ah,77		;trim high 2 bits
	add	ax,ax		;center between two zeros
	mov	cx,16d		;16 bits
	callr	psend		;send it, return
;+
;
; Send command to 16F84 with received data word.
;
; ax	returns data word (in low 14 bits, zeros in high two)
; bx	SDELAY timer value (updated on return)
; cl	command
;
;-
cmdr84:	mov	al,cl		;copy command
	mov	cx,6		;6 bits
	call	psend		;send it
	call	sdelay		;wait 4 more 838.1 ns clocks (1 usec for sure)
	call	sdelay
	mov	cx,16d		;16 bits
	call	precv		;receive it
	shr	ax,1		;right-justify
	and	ah,77		;trim high 2 bits
	ret
;
	subttl	serial I/O routines, actual hardware access
;+
;
; Enter programming mode.
;
; bx	returns SDELAY timer value (all synced up)
;
;-
penter:	mov	dx,ds:lcr	;point at line ctrl reg
	in	al,dx		;fetch value
	and	al,not break	;should be clear, but just in case
	out	dx,al		;power probably off, hold /MCLR to -12
	push	ax		;save
	push	dx
	mov	dx,ds:mcr	;point at modem ctrl reg
	xor	al,al		;clear /RTS, /DTR
	out	dx,al		;(holds RB<7:6> to -12)
	pop	dx		;restore LCR addr, value
	pop	ax
	or	al,break	;power on if not already, let /MCLR float up
	out	dx,al
	mov	cx,2		;# edges to count
	call	ldelay		;long delay to let voltage reg and PIC wake up
	call	sdel0		;set up for delay
	call	sdelay		;wait for next 838.1 ns tick
	ret
;+
;
; Exit programming mode.
;
;-
pexit:	mov	dx,ds:mcr	;point at modem ctrl reg
	xor	al,al		;clear /RTS, /DTR
	out	dx,al		;(set RB<7:6> back to -12)
	mov	dx,ds:lcr	;point at line ctrl reg
	in	al,dx		;fetch value
	and	al,not break	;power probably off, set /MCLR to -12
	out	dx,al
	call	sdelay		;make sure the chip sees this, in case we're
	callr	sdelay		;about to turn it back on
;+
;
; Send a bit sequence.
;
; ax	bits to send, LSB first
; cx	# bits (1-16.)
; bx	SDELAY value (must already be synced up), updated on return
;
;-
psend:	call	sdelay		;extend time since prev falling edge to >=1 us
	mov	dx,ds:mcr	;point at modem ctrl reg
@@1:	push	ax		;save data
	and	al,dtr		;isolate LSB as DTR (for RB7)
if dtr ne 1			;(DTR happens to be b0)
	.err			;next data bit goes in DTR
endif
	or	al,rts		;assert RTS as rising edge of clock
	out	dx,al		;write data bit, raise RTS (RB6) to +12
	call	waithb		;wait a half bit time
	and	al,not rts	;drop RTS (RB6) to -12
	out	dx,al		;(falling edge of clock)
	call	waithb		;wait the other half bit time
	pop	ax		;restore
	shr	ax,1		;right a bit
	loop	@@1		;loop through all bits
	ret
;+
;
; Receive a bit sequence.
;
; ax	returns bits received, LSB was first
; cx	# bits (1-16.)
; bx	SDELAY value (must already be synced up), updated on return
;
;-
precv:	push	cx		;save
@@1:	mov	dx,ds:mcr	;point at modem ctrl reg
	mov	al,rts		;drop DTR (our RB7 driver), raise RTS (RB6)
	out	dx,al
	call	waithb		;wait a half bit time
	xor	al,al		;drop RTS (RB6) to -12
	out	dx,al
	mov	dx,ds:msr	;point at modem status reg
	shr	di,1		;make space for new bit
	in	al,dx		;fetch new data bit (on RB7)
	test	al,cts		;1 or 0?
	jz	@@2		;0
	 or	di,8000h	;set MSB
@@2:	call	waithb		;wait the other half bit time
	loop	@@1		;loop through all
	pop	ax		;catch word length
	mov	cl,16d		;word size
	sub	cl,al		;find # bits to right-justify, if any
	shr	di,cl		;do it (zeros in LH)
	mov	ax,di		;copy
	ret
;+
;
; Init BX for first call to SDELAY.
;
; bx	returns starting (byte-swapped) time value
; all others preserved
;
;-
sdel0:	push	ax		;save
	cli			;ints off
	mov	al,06h		;;latch timer 0, mode 3
	out	43h,al
	in	al,40h		;;read it
	mov	bh,al		;;(save with bytes swapped)
	in	al,40h
	sti			;;ints on
	mov	bl,al		;BX=byte-swapped starting time
	pop	ax		;restore
	ret
;+
;
; Synchronous delay until next 838.1 ns clock tick.
;
; bx	starting timer value, updated on return
; all others preserved
;
;-
sdelay:	push	ax		;save
	cli			;ints off
	mov	al,06h		;;latch timer 0, mode 3
	out	43h,al
	in	al,40h		;;read it
	mov	ah,al
	in	al,40h
	sti			;;ints on
	cmp	ax,bx		;changed yet?
	je	short sdelay	;spin until it does
	mov	bx,ax		;set new starting value
	pop	ax		;restore
	ret
;+
;
; Wait a half-bit time.
;
; bx	SDELAY timer value, updated on return
; all others preserved
;
;-
waithb:	callr	sdelay		;just one tick seems to be enough
				;(it's way more than enough for the PIC, I'm
				;just worried about the response time of the
				;UART and RS232 drivers/receivers)
;+
;
; Wait 10 msec.
;
; bx	SDELAY timer value, updated on return
; all others preserved
;
;-
wait10:	push	cx		;save
	mov	cx,11932d	;count for 10 msec
@@10:	call	sdelay		;wait 838.1 nsec
	loop	@@10		;count edges
	pop	cx		;restore
	ret
;+
;
; Long delay.  Could use a counter with SDELAY but this is just easier.
;
; cx	# of BIOS 54 msec clock edges to count
; ax	trashed, others preserved
;
;-
ldelay:	push	es		;save
	xor	ax,ax		;load 0
	mov	es,ax		;point at BIOS data
@@1:	mov	ax,es:timer_low	;get time value
@@2:	cmp	ax,es:timer_low	;changed?
	je	@@2		;loop if not
	loop	@@1		;count it
	pop	es		;restore
	ret
;
	subttl	input file handling
;+
;
; Read next record from hex input file.
;
; HEXTYP, HEXLEN, HEXADR, HEXDAT are set up with data (to be interpreted by
; caller).
;
;-
rdhex:	push	es		;save
	push	ds		;copy DS to ES
	pop	es
@@1:	; :
	call	getc		;get next char
	cmp	al,':'		;start of rec?
	jne	@@1		;ignore
	mov	byte ptr ds:hexchk,0 ;init checksum
	; LL (length of data field)
	call	rdhex2		;get length of data field
	mov	ds:hexlen,al	;save
	; AAAA (starting address)
	call	rdhex2		;high byte
	push	ax		;save
	call	rdhex2		;low byte
	pop	bx		;catch
	mov	ah,bl
	mov	ds:hexadr,ax	;save
	; TT (type of record)
	call	rdhex2		;type code
	mov	ds:hextyp,al
	; DDDDDD... (optional data byte(s))
	mov	cl,ds:hexlen	;get # data bytes
	xor	ch,ch		;CH=0
	jcxz	@@3
	mov	di,offset hexdat ;point at buf
@@2:	push	cx		;save
	call	rdhex2		;get next data byte
	stosb			;save
	pop	cx		;restore
	loop	@@2		;loop through all
@@3:	; CC (checksum)
	call	rdhex2		;read and discard check byte
	test	byte ptr ds:hexchk,-1 ;should add up to 00
	jnz	@@4
	pop	es		;restore
	ret
@@4:	mov	si,offset badchk ;bad checksum
	jmp	fatal
;+
;
; Get next pair of hex digits.
;
; al	returns value
; di	preserved
;
;-
rdhex2:	call	rdhex1		;get high digit
	mov	cl,4		;bit count
	shl	al,cl		;make space for low digit
	push	ax		;save
	call	rdhex1		;get low digit
	pop	bx		;restore
	or	al,bl		;combine
	add	ds:hexchk,al	;add to checksum
	ret
;+
;
; Get next hex digit.
;
; al	returns value
; di	preserved
;
;-
rdhex1:	call	getc		;get a char
	sub	al,'0'		;see if digit
	cmp	al,10d		;is it?
	jb	@@2		;yes
	sub	al,'A'-'0'	;A-F?
	cmp	al,6
	jb	@@1		;yes
	sub	al,'a'-'A'	;a-f?
	cmp	al,6
	jae	rdhex1		;no, ignore
@@1:	add	al,0Ah		;A-F => 0Ah-0Fh
@@2:	ret
;+
;
; Get next character from FBUF.
;
; Since .HEX files should always stop us before we hit EOF, we give a fatal
; error if that happens.
;
; al	returns char
; di	preserved
;
;-
getc:	sub	word ptr ds:fctr,1 ;update count
	jc	@@1		;nothing in buf, go reload
	mov	si,ds:fptr	;get ptr
	lodsb			;get char
	mov	ds:fptr,si	;save
	and	al,177		;(trim parity bit in case botched serial xfr)
	ret
@@1:	mov	dx,offset fbuf	;point at buf
	mov	ds:fptr,dx	;(rewind ptr)
	mov	cx,fbufl	;buf size
	mov	bx,ds:hexhnd	;file handle
	mov	ah,3Fh		;func=read
	int	21h
	jc	@@2		;error
	test	ax,ax		;EOF?
	jz	@@3
	mov	ds:fctr,ax	;save length
	jmp	short getc
@@2:	mov	si,offset rderr	;error
	jmp	fatal
@@3:	mov	si,offset unxeof ;unexpected EOF
	jmp	fatal
;+
;
; Write next record to hex output file.
;
; HEXTYP, HEXLEN, HEXADR, HEXDAT must be set up on entry.
;
;-
wrhex:	; :
	mov	al,':'		;start of rec
	call	putc
	mov	byte ptr ds:hexchk,0 ;init checksum
	; LL (length of data field)
	mov	al,ds:hexlen	;length
	call	wrhex2
	; AAAA (starting address)
	mov	al,byte ptr ds:hexadr+1 ;high byte
	call	wrhex2
	mov	al,byte ptr ds:hexadr ;low byte
	call	wrhex2
	; TT (type of record)
	mov	al,ds:hextyp	;tpye code
	call	wrhex2
	; DDDDDD... (optional data byte(s))
	mov	cl,ds:hexlen	;get # data bytes
	xor	ch,ch		;CH=0
	jcxz	@@2
	mov	si,offset hexdat ;point at buf
@@1:	push	cx		;save
	lodsb			;get next data byte
	call	wrhex2
	pop	cx		;restore
	loop	@@1		;loop through all
@@2:	; CC (checksum)
	mov	al,ds:hexchk	;get checksum
	neg	al		;two's complement
	call	wrhex2
	mov	al,cr		;<CRLF>
	call	putc
	mov	al,lf
	callr	putc
;+
;
; Write a byte to the output file as 2 hex digits.
;
; al	byte to write
; si	preserved
;
;-
wrhex2:	add	ds:hexchk,al	;add to checksum
	mov	ah,al		;copy
	and	ax,0FF0h	;isolate high, low bytes
	mov	cl,4		;shift count
	shr	al,cl		;right-justify high digit
	cmp	al,0Ah		;CF=1 if 0-9
	sbb	al,69h		;AL=96-9F or A1-A6 (AF=1 if 0-9, CF=1 always)
	das			;low byte -6 if 0-9, high byte -60h
	push	ax		;save
	call	putc		;send high digit
	pop	ax		;restore
	mov	al,ah		;as above for low digit
	cmp	al,0Ah
	sbb	al,69h
	das
	;callr	<short putc>	;and return
;+
;
; Put next character in FBUF.
;
; al	char to send
; si	preserved
;
;-
putc:	mov	di,ds:fptr	;get ptr
	mov	[di],al		;save
	inc	word ptr ds:fptr
	dec	word ptr ds:fctr ;update count
	jz	@@1		;buf is full, go flush
	 ret
@@1:	;callr	<short flush>	;flush buf, return
;+
;
; Flush buffer to output file, prepare for more output.
;
; si	preserved
;
;-
flush:	mov	dx,offset fbuf	;point at buf
	mov	cx,dx		;copy
	xchg	cx,ds:fptr	;rewind ptr, get value
	sub	cx,dx		;find length
	jz	@@1		;nothing, skip
	mov	bx,ds:hexhnd	;file handle
	mov	ah,40h		;func=write
	int	21h
	jc	@@2		;error
@@1:	mov	word ptr ds:fctr,fbufl ;buf is empty
	ret
@@2:	mov	si,offset wrerr	;error
	jmp	fatal
;
	subttl	pure data
;
banner	db	'P84 V1.00  PIC16F84 programmer  '
	db	'By John Wilson <wilson@dbit.com>',cr,lf
	db	'Copyright (C) 1999 by D Bit.  All rights reserved.',cr,lf
	db	'This program may be freely distributed provided proper'
	db	' credit is given.',cr,lf
	db	'$'
;
jcltab	label	byte		;command line switches
	kw	<?->,help	;print help
	kw	<B-LANK>,blkcmd	;test to see if part is blank
	kw	<COM1->,com1	;set port to COM1:
	kw	<COM2->,com2	;set port to COM2:
	kw	<COM3->,com3	;set port to COM3:
	kw	<COM4->,com4	;set port to COM4:
	kw	<ERASE->,eracmd	;erase (only)
	kw	<H-ELP>,help	;print help
	kw	<INHX8M->,h8mcmd ;file type is Intel hex merged
	kw	<INHX8S->,h8scmd ;file type is Intel hex split
	kw	<INHX3-2>,h32cmd ;file type is Intel hex 32 bit (addressing)
	kw	<NOER-ASE>,nercmd ;don't erase before writing
	kw	<PIC16F84->,set84 ;chip type is 16F84
	kw	<R-EAD>,rdcmd	;read
	kw	<V-ERIFY>,vfycmd ;verify (only)
	kw	<W-RITE>,wrcmd	;write
	db	0		;end of list
;
jclhlp	label	byte		;command line help
	db	cr,lf
	db	'Usage:  P84 [filename[.HEX]] [/switches]',cr,lf
	db	cr,lf
	db	'Command switches:  (can specify more than one)',cr,lf
	db	'/BLANK    verify that part is blank',cr,lf
	db	'/ERASE    erase part (only)',cr,lf
	db	"/NOERASE  don't erase before writing",cr,lf
	db	'/READ     read part into file',cr,lf
	db	'/VERIFY   just verify (compare) file against part',cr,lf
	db	'/WRITE    write file to part (default if no other switches)'
	db	cr,lf,cr,lf
	db	'Command qualifiers:',cr,lf
	db	'/COMn     specify port for COM84 board (n=1-4)',cr,lf
	db	'/INHX8M   use merged .HEX file (default)',cr,lf
	db	'/INHX8S   use split .HXL/.HXH files for low/high bytes',cr,lf
	db	'/INHX32   use merged .HEX file w/32-bit addressing',cr,lf
	db	'$'
;
; Counted error messages, passed to FATAL:
;
	ascic	badjcl,'?Syntax error'
	ascic	allerr,'?Memory allocation error'
	ascic	operr,'?File open error'
	ascic	rderr,'?File read error'
	ascic	wrerr,'?File write error'
	ascic	unxeof,'?Unexpected end of input file'
	ascic	badchk,'?Bad checksum in hex record'
	ascic	adrran,'?Hex record addr out of range for selected part'
	ascic	nxport,'?Nonexistent port'
	ascic	swtcnf,'?Switch conflict'
;
; Buffer ranges to be dumped out by DUMP:
;
dump84	label	word		;PIC16F84 ranges
	dw	0,400h*2-1	;program memory, words 0-3FFh
	dw	2000h*2,2004h*2-1 ;ID memory, words 2000h-2003h
	dw	2007h*2,2008h*2-1 ;config memory, words at 2007h
				;PIP-02 gets confused if we send all of the
				;2000-2007 range (loses fuse data)
	dw	2100h*2,2140h*2-1 ;data EEPROM (piss-poor MPASM support)
				;this range is implied by example in desc. of
				;"DE" pseudo-op, but not actually specified
	dw	-1		;end of list
;
vermsg	db	verlen,'?Verify failed at address '
veradr	db	'HHHH:  expected '
verexp	db	'HHHH, got '
vergot	db	'HHHH',cr,lf,bel
verlen=	$-vermsg-1
;
; File extensions:
;
hexext	db	'.HEX',0
hxlext	db	'.HXL',0
hxhext	db	'.HXH',0
;
stdin	db	'(reading STDIN, type ^C to abort)',cr,lf,'$'
stdout	db	'(writing STDOUT, remove this and preceding lines)',cr,lf,'$'
;
	subttl	impure storage
;
fnptr	dw	?		;pointer to filename in command line
fnctr	dw	0		;length of filename in command line
com	dw	0		;COM port index (0/2/4/6 = COM1/2/3/4)
;
inhx8m	db	0		;NZ => merged Intel hex file
inhx8s	db	0		;NZ => split high/low Intel hex files
inhx32	db	0		;NZ => merged Intel hex file w/32-bit addrs
;
blkflg	db	0		;NZ => /BLANK command
eraflg	db	0		;NZ => /ERASE command
nerflg	db	0		;NZ => /NOERASE qualifier (to write cmd)
rdflg	db	0		;NZ => /READ command
vfyflg	db	0		;NZ => /VERIFY command
wrflg	db	0		;NZ => /WRITE command
;
	subttl	pure storage
;
port	dw	1 dup(?)	;(e.g. 3F8) base port addr
lcr	dw	1 dup(?)	;(e.g. 3FB) line ctrl reg
mcr	dw	1 dup(?)	;(e.g. 3FC) modem ctrl reg
msr	dw	1 dup(?)	;(e.g. 3FE) modem status reg
;
; Addrs of part-specific routines:
read	dw	1 dup(?)	;read part to buf
write	dw	1 dup(?)	;write buf to part
verify	dw	1 dup(?)	;verify part against buf
erase	dw	1 dup(?)	;erase part
clear	dw	1 dup(?)	;clear buf
;
bufseg	dw	1 dup(?)	;seg addr of memory buffer
bufmax	dw	2 dup(?)	;last valid offset of buf (DWORD)
				;32-bit addressing isn't really supported yet,
				;I'll finish it if/when I need to add support
				;for burning a device that would use it
prgsiz	dw	1 dup(?)	;size of program memory in words
datsiz	dw	1 dup(?)	;size of data memory in bytes
dmplst	dw	1 dup(?)	;list of ranges dumped by DUMP
				;(word pairs giving byte ranges, -1 terminates)
;
hexhnd	dw	1 dup(?)	;.HEX file handle
;
fptr	dw	1 dup(?)	;pointer into FBUF
fctr	dw	1 dup(?)	;# chars left in FBUF
fbuf	db	fbufl dup(?)	;file buffer
;
; Stuff from hex record:
hexchk	db	1 dup(?)	;checksum of hex record
hexlen	db	1 dup(?)	;length of data field
hexadr	dw	1 dup(?)	;address field
hextyp	db	1 dup(?)	;type field
hexad1	dw	1 dup(?)	;separate addr ctr for DUMP with INHX8S
hexdat	db	256d dup(?)	;up to 256. data bytes
;
	dw	200h dup(?)	;stack
	org	$+(10h-(($-start) and 0Fh)) ;round to paragraph boundary
pdl	label	word		;stack ends here
				;memory past this point is released
;
code	ends
	end	start
