;Serial I/O routines using the 8051's built-in UART.
;Almost all of these should use CIN and COUT, so they
;could pretty easily be adapted to other devices which
;could have similar single character I/O routines,
;including the 8051's UART using interrupts and buffers
;in memory.

;Much of this code appears in PAULMON1... see the
;PAULMON1.EQU file for an example of how to use some
;of these routines.


    ;timer reload calculation
    ; baud_const = 256 - (crystal / (12 * 16 * baud))
				
.equ	baud_const, 252	         ;19200 baud w/ 15 MHz
;.equ	 baud_const, 243	 ;4800 baud w/ 12 MHz


;---------------------------------------------------------;
;							  ;
;	       Subroutines for serial I/O		  ;
;							  ;
;---------------------------------------------------------;


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

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

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

	;get 2 digit hex number from serial port
	; c = set if ESC pressed, clear otherwise
	; psw.5 = set if return w/ no input, clear otherwise
ghex:
ghex8:	clr	psw.5
ghex8c:
	acall	cin		;get first digit
	acall	upper
	cjne	a, #27, ghex8f
ghex8d: setb	c
	clr	a
	ret
ghex8f: cjne	a, #13, ghex8h
	setb	psw.5
	clr	c
	clr	a
	ret
ghex8h: mov	r2, a
	acall	asc2hex
	jc	ghex8c
	xch	a, r2		;r2 will hold hex value of 1st digit
	acall	cout
ghex8j:
	acall	cin		;get second digit
	acall	upper
	cjne	a, #27, ghex8k
	sjmp	ghex8d
ghex8k: cjne	a, #13, ghex8m
	mov	a, r2
	clr	c
	ret
ghex8m: cjne	a, #8, ghex8p
ghex8n: acall	cout
	sjmp	ghex8c
ghex8p: cjne	a, #21, ghex8q
	sjmp	ghex8n
ghex8q: mov	r3, a
	acall	asc2hex
	jc	ghex8j
	xch	a, r3
	acall	cout
	mov	a, r2
	swap	a
	orl	a, r3
	clr	c
	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:
	acall	cin
	acall	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:acall	cout
	acall	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
	acall	asc2hex
	jc	ghex16c
	xch	a, r5
	lcall	cout
	mov	a, r5
	push	acc
	acall	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


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


PHEX16:
	PUSH	ACC
	MOV	A,DPH
	ACALL	PHEX
	MOV	A,DPL
	ACALL	PHEX
	POP	ACC
	RET


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






;first we initialize all the registers we can, setting up
;for serial communication.

poweron:

	mov	sp, #0x30
	clr	psw.3		;set for register bank 0 (init needs it)
	clr	psw.4
	orl	PCON,#10000000b   ; set double baud rate
	MOV	TMOD,#00010001b
	MOV	SCON,#01010000b  ; Set Serial for mode 1 &
				 ; Enable reception
	ORL	TCON,#01010010b  ; Start timer 1 both timer

	mov	a, #baud_const
	mov	th1, a

	setb	ti		;ti is normally set in this program
	clr	ri		;ri is normally cleared

	;jump to main program from here...



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
        setb    f0
        jz      pint8c
        clr     f0
        add     a, #'0'
        lcall   cout
pint8c: mov     a, b
        mov     b, #10
        div     ab
        jnb     f0, pint8d
        jz      pint8e
pint8d: add     a, #'0'
        lcall   cout
pint8e: 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
	acall	pint16x
	jz	pint16b
	add	a, #'0'
	lcall	cout
	setb	psw.5

pint16b:mov	r4, #232	;thousands digit
	mov	r5, #3
	acall	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
	acall	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


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




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    


.equ    str_buf, 0x20           ;16 byte buffer
.equ    max_str_len, 19

getstr: ;get a string and store in an internal ram buffer
        ;  str_buf = beginning of the buffer
        ;  max_str_len = max number of char to receive
        ;   (buffer must be one larger for null termination)

        mov     r0, #str_buf
gstrz:  mov     @r0, #0                 ;fill buffer with zeros
        inc     r0
        cjne    r0, #(str_buf+max_str_len+1), gstrz
        mov     r0, #str_buf
gstr_in:lcall   cin
        lcall   isascii
        jnc     gstr_ctrl
        cjne    r0, #(str_buf+max_str_len), gstradd
        sjmp    gstr_in
gstradd:lcall   cout
        mov     @r0, a
        inc     r0
        sjmp    gstr_in
gstr_ctrl:
        cjne    a, #13, gstrc2          ;carriage return
        clr     a
        mov     @r0, a
        ret
gstrc2: cjne    a, #8, gstrc3           ;backspace
gstrbk: cjne    r0, #str_buf, gstrbk2
        sjmp    gstr_in
gstrbk2:mov     a, #8
        lcall   cout
        mov     a, #' '
        lcall   cout
        mov     a, #8
        lcall   cout
        dec     r0
        sjmp    gstr_in
gstrc3: cjne    a, #127, gstrc4         ;delete
        sjmp    gstrbk
gstrc4:
        sjmp    gstr_in                 ;ignore all others




pstrbuf:  ;print the string in the internal ram buffer
        mov     r0, #str_buf
pstrbuf2:
        mov     a, @r0
        jz      pstrbuf3
        lcall   cout
        inc     r0
        sjmp    pstrbuf2
pstrbuf3:
        ret





        ;get unsigned integer input to acc
gint8u:
        mov     r0, #0          ;r0 holds sum so far
        mov     r1, #0          ;r1 counts number of characters
gi8_in: lcall   cin
        mov     r2, a           ;r2 is temp holding space for input char
        clr     c
        subb    a, #'0'
        jc      gi8_ctrl
        subb    a, #10
        jnc     gi8_ctrl
        mov     a, r0
        mov     b, #10
        mul     ab
        xch     a, b
        jnz     gi8_in
        mov     a, r2
        clr     c
        subb    a, #'0'
        add     a, b
        jc      gi8_in
        mov     r0, a
        mov     a, r2
        lcall   cout
        inc     r1
        sjmp    gi8_in
gi8_ctrl:
        mov     a, r2
        cjne    a, #13, gi8c2
        mov     a, r0
        ret
gi8c2:  cjne    a, #8, gi8c3
gi8bk:  cjne    r1, #0, gi8bk2
        sjmp    gi8_in
gi8bk2: mov     a, #8
        lcall   cout
        mov     a, #' '
        lcall   cout
        mov     a, #8
        lcall   cout
        mov     a, r0
        mov     b, #10
        div     ab
        mov     r0, a
        sjmp    gi8_in
gi8c3:  cjne    a, #127, gi8c4
        sjmp    gi8bk
gi8c4:
        sjmp    gi8_in



isascii:        ;is acc an ascii char, c=1 if yes, c=0 if no
        push    acc
        cjne    a, #0x7F, isasc2
        sjmp    isasc_no
isasc2: anl     a, #10000000b
        jnz     isasc_no
        pop     acc
        push    acc
        anl     a, #11100000b
        jz      isasc_no
        setb    c
        pop     acc
        ret
isasc_no:
        clr     c
        pop     acc
        ret

