;-----------------------------------------------------------------------------
; Full-duplex software UART
;
; This version is for the Scenix SX Microcontroller, and runs on the
; Parallax SX-Key Demo Board.  Assemble with Microchip's MPASM assembler.
;
; Copyright 1996, 1998 Eric Smith
;
; Permission is granted to use this code or portions thereof for
; non-commercial purposes provided that the copyright notice is preserved
; intact and that credit is given to the author.  For any other use, a
; license may be obtained from the author; send email to eric@brouhaha.com.
;
; $Id: fduart.asm,v 1.3 1998/11/05 06:35:07 eric Exp eric $
;-----------------------------------------------------------------------------

	radix	dec

	errorlevel	-302
	errorlevel	-305

	processor	16c57	; really a Scenix SX, but MPASM doesn't
				; know about those


;-----------------------------------------------------------------------------
; Scenix SX18AC/SX28AC register definitions
;-----------------------------------------------------------------------------

indf	equ	00h
rtcc	equ	01h
pcl	equ	02h
status	equ	03h
fsr	equ	04h
porta	equ	05h
portb	equ	06h
portc	equ	07h

; bits in status:
c	equ	0
z	equ	2


;-----------------------------------------------------------------------------
; ASCII characters
;-----------------------------------------------------------------------------

lf	equ	00ah
cr	equ	00dh


;-----------------------------------------------------------------------------
; Scenix instruction macros
;-----------------------------------------------------------------------------

ret	macro
	data	0ch	;RET	;return without destroying W register
	endm

reti	macro
	data	0eh
	endm

retiw	macro
	data	0fh
	endm

pagex	macro	n		;(MPASM already uses "PAGE")
	data	10h|n	;PAGE	;write N into bits PA2:PA0 (N = 0-7)
	endm

bankx	macro	n		;(MPASM already uses "BANK")
	data	18h|(n>>5)	;BANK	;write N into bits FSR7:FSR5 (N = 0-7)
	endm

mode	macro	n
	data	50h|n	;MODE	;write N into MODE register (N = 0-F)
	endm


;-----------------------------------------------------------------------------
; target-specific definitions
;-----------------------------------------------------------------------------

rx_ring_size	equ	010h
tx_ring_size	equ	010h


xtalfreq	equ	50000000

b115200		equ	-145	; xtalfreq/(3*115200)


rxport		equ	porta
rxbit		equ	2
rxinv		equ	0

txport		equ	porta
txbit		equ	3
txinv		equ	0


button_port	equ	portb
maxbutton	equ	4


porta_init	equ	0ffh
porta_tris	equ	(1<<rxbit)

portb_init	equ	0ffh
portb_tris	equ	(1<<maxbutton)-1	; button inputs

portc_init	equ	0ffh
portc_tris	equ	000h


;-----------------------------------------------------------------------------
; RAM usage
;-----------------------------------------------------------------------------

		org	008h
; globals

rxbyte:		res	1	; this must be global rather than in ser_vars
				; rxbyte may >only< be used in interrupt handler

ser_temp:	res	1	; ser_temp  must be global rather than in ser_vars
				; ser_temp may not be used in interrupt handler

msg_temp:	res	1	; this could be in main_vars

temp:		res	1
temp2:		res	1
temp3:		res	1

led_stat:	res	1	; this should be in button_vars, but I got lazy
				; and referenced it directly from main


		org	010h

main_vars	=	$

		org	030h

button_vars	=	$

but_press_cnt:	res	maxbutton
but_mask:	res	1
but_poll_cnt:	res	1
but_stat:	res	1	; button status, 1 bit per button
				; mainline code will clear bit to acknowledge

		org	050h

ser_vars	=	$

uartspeed:	res	1

uartstat:	res	1

; bits in uartstat:
rbf	equ	7
tbf	equ	6
rxovr	equ	5
rxfrm	equ	4
rxphase	equ	1
txphase	equ	0

rx_ring_ip:	res	1
rx_ring_op:	res	1
rx_ring_cnt:	res	1

tx_ring_ip:	res	1
tx_ring_op:	res	1
tx_ring_cnt:	res	1

rxbits:		res	1
rxbitcnt:	res	1
rxmask:		res	1

txbyte:		res	1
txbitcnt:	res	1

		org	070h

rx_ring:	res	rx_ring_size

		org	090h

tx_ring:	res	tx_ring_size


;-----------------------------------------------------------------------------
; UART macros
;-----------------------------------------------------------------------------

ringadv	macro	ptr,base,size
	local	pow2,aligned,bit,val
pow2	set	!(size&(size-1))
aligned	set	pow2&&((base&(size-1))==0)

	if	aligned
val	set	size
bit	set	0
	while	val>1
bit	set	bit+1
val	set	val>>1
	endw
	endif

	incf	ptr

	if	aligned&&!(base&(1<<bit))
	bcf	ptr,bit
	else
	movf	ptr,w
	xorlw	base+size
	movlw	base
	btfsc	status,z
	movwf	ptr
	endif
	endm


;-----------------------------------------------------------------------------
; interrupt entry point
; all subroutine entry points must be in first page of ROM
;-----------------------------------------------------------------------------

	org	0

int	call	ser_int
	call	but_int
	bankx	ser_vars
	movf	uartspeed,w
	retiw


;-----------------------------------------------------------------------------
; jump table
; all subroutine entry points must be in first page of ROM
;-----------------------------------------------------------------------------

uartinit:
	goto	uartinit_x
xmit:	goto	xmit_x
recv:	goto	recv_x
rxavail:
	goto	rxavail_x
msgout:	goto	msgout_x

buttoninit:
	goto	buttoninit_x
getbutstat:
	goto	getbutstat_x
clrbutstat:
	goto	clrbutstat_x


;-----------------------------------------------------------------------------
; tables
;-----------------------------------------------------------------------------

msg_table:	addwf	pcl
banner		equ	$-(msg_table+1)
		dt	cr,lf,"Hello, world!",cr,lf
		dt	"This is a test to see if very long messages",cr,lf
		dt	"lose characters.  If they do, there must be",cr,lf
		dt	"a problem with buffer management.",cr,lf,0
button_msg	equ	$-(msg_table+1)
		dt	"button ",0
pressed_msg	equ	$-(msg_table+1)
		dt	" pressed",cr,lf,0


;-----------------------------------------------------------------------------
; button interrupt handler code
;-----------------------------------------------------------------------------

but_int:
	bankx	button_vars
	incf	but_poll_cnt	; poll button every 256 ticks

	movf	but_poll_cnt,w	; if but_poll_cnt > 3, return
	andlw	0fch
	btfss	status,z
	ret

	movf	but_poll_cnt,w	; must be between 0 and 3
	addwf	pcl
	goto	but_set_input
	goto	but_read_input
	goto	led_set_output
	ret

led_set_output:
	movf	led_stat,w
	xorlw	0ffh
	movwf	button_port
	movlw	0
	tris	button_port
	ret

but_set_input:
	movlw	(1<<maxbutton)-1
	tris	button_port
	ret

but_read_input:
	movlw	button_vars
	movwf	fsr
	movlw	001h
	movwf	but_mask

butint1:
	movf	button_port,w
	andwf	but_mask,w
	btfss	status,z
	goto	butrel

	btfsc	indf,5		; already pressed?
	goto	butint9

	incf	indf
	btfss	indf,5
	goto	butint9

	movf	but_mask,w
	iorwf	but_stat
	goto	butint9

butrel:
	clrf	indf

butint9:
	incf	fsr
	bcf	status,c
	rlf	but_mask
	btfss	but_mask,maxbutton
	goto	butint1
	ret


;-----------------------------------------------------------------------------
; serial interrupt handler code
;-----------------------------------------------------------------------------

ser_int:
	bankx	ser_vars

	if	rxbit==0	; get current receive sample in one instruction
	rrf	rxport,w	;   if possible, otherwise three instructions
	else
	if	rxbit==7
	rlf	rxport,w
	else
	bcf	status,c
	btfsc	rxport,rxbit
	bsf	status,c
	endif
	endif

	rlf	rxbits

	btfsc	uartstat,rxphase
	goto	rxint
	btfsc	uartstat,txphase
	goto	txint

; housekeeping phase

	bsf	uartstat,txphase	; transmit phase next

	btfss	uartstat,rbf	; has the receiver collected a byte?
	goto	try_xmit_ring	; no, check transmitter

	movf	rx_ring_cnt,w	; is the receive buffer full?
	xorlw	rx_ring_size
	btfss	status,z
	goto	rx_ok		; no, go store the char
	bsf	uartstat,rxovr	; yes, set the overrun flag
	goto	try_xmit_ring	;   and skip storing the char

rx_ok:	movf	rx_ring_ip,w	; store character in receive buffer
	movwf	fsr
	movf	rxbyte,w	; rxbyte must be global!!!
	movwf	indf
	bankx	ser_vars

	ringadv	rx_ring_ip,rx_ring,rx_ring_size

	incf	rx_ring_cnt

	bcf	uartstat,rbf

try_xmit_ring:
	btfsc	uartstat,tbf	; is the transmitter busy?
	ret			; yes, don't do anything

	movf	tx_ring_cnt,w	; is there anything in the ring?
	btfsc	status,z
	ret			; no, don't do anything

	movf	tx_ring_op,w	; move one character from the ring to the
	movwf	fsr		;   transmitter
	movf	indf,w
	bankx	ser_vars
	movwf	txbyte

	decf	tx_ring_cnt	; decrement tx char count

	ringadv	tx_ring_op,tx_ring,tx_ring_size	; advance pointer

	bsf	uartstat,tbf	; now the transmitter will be busy
	ret


rxint:	bcf	uartstat,rxphase	; housekeeping phase next

	if	rxinv
	movlw	7
	xorwf	rxbits
	endif

	movf	rxmask,w
	btfss	status,z
	goto	rxgetbit

	movlw	9
	movwf	rxbitcnt

	movlw	2
	btfss	rxbits,2
	goto	rxstart9
	movlw	1
	btfss	rxbits,1
	goto	rxstart9
	movlw	4

	btfsc	rxbits,0
	ret

rxstart10:
	incf	rxbitcnt
rxstart9:
	movwf	rxmask
	ret

rxgetbit:
	andwf	rxbits,w	; extract the appropriate sample bit
	bcf	status,c	;   into the carry flag
	btfss	status,z
	bsf	status,c

	decfsz	rxbitcnt	; is it a stop bit?
	goto	rxdbit		; no, it's a data bit

	btfss	status,c	; is the stop bit a 1 like it's supposed to be?
	bsf	uartstat,rxfrm	; no, framing error!

	bsf	uartstat,rbf

	btfss	rxmask,2	; could there be a start bit in this group?
	goto	rxnost		;  no

	btfsc	rxbits,0	; is there in fact a start bit?
	goto	rxnost		;  no

	movlw	4		; yes, start up again
	movwf	rxmask
	movlw	10
	movwf	rxbitcnt
	ret

rxnost:	clrf	rxmask
	ret

rxdbit:	rrf	rxbyte
	ret


txint:	bcf	uartstat,txphase
	bsf	uartstat,rxphase	; receive phase next

	movf	txbitcnt	; are we transmitting?
	btfss	status,z
	goto	txputbit

	btfss	uartstat,tbf	; is there a char awaiting transmission?
	ret

	if	txinv
	bsf	txport,txbit	; send start bit
	else
	bcf	txport,txbit	; send start bit
	endif

	movlw	9		; 8 data bits plus a stop bit
	movwf	txbitcnt

	ret

txputbit:
	bsf	status,c
	rrf	txbyte

	if	txinv
	btfss	status,c
	bsf	txport,txbit
	btfsc	status,c
	bcf	txport,txbit
	else
	btfss	status,c
	bcf	txport,txbit
	btfsc	status,c
	bsf	txport,txbit
	endif

	decfsz	txbitcnt	; any more bits to send?
	ret			;   yes
	bcf	uartstat,tbf	;   no, the transmit buffer is now empty
	ret


;-----------------------------------------------------------------------------
; UART initialization
; caller must restore register bank
; returns with register bank 010h selected
;-----------------------------------------------------------------------------

uartinit_x:
	bankx	ser_vars
	clrf	uartstat
	clrf	rxmask
	clrf	txbitcnt

	clrf	rx_ring_cnt
	movlw	rx_ring
	movwf	rx_ring_ip
	movwf	rx_ring_op

	clrf	tx_ring_cnt
	movlw	tx_ring
	movwf	tx_ring_ip
	movwf	tx_ring_op

	movlw	b115200
	movwf	uartspeed

	clrf	rtcc

	bankx	010h
	ret


;-----------------------------------------------------------------------------
; serial transmit character
; blocks until space if available in buffer
; returns with register bank 010h selected
;-----------------------------------------------------------------------------

xmit_x:	bankx	ser_vars
	movwf	ser_temp

xmit0:	movf	tx_ring_cnt,w	; is there room for a character?
	xorlw	tx_ring_size
	btfsc	status,z
	goto	xmit0

	movf	tx_ring_ip,w	; store char in buffer
	movwf	fsr
	movf	ser_temp,w	; must be global!!!
	movwf	indf
	bankx	ser_vars

	ringadv	tx_ring_ip,tx_ring,tx_ring_size	; advance pointer

	incf	tx_ring_cnt	; increment tx char count
				; (this must not be done until after character
				; has been stored in buffer)

	bankx	010h
	ret


;-----------------------------------------------------------------------------
; rxavail
; returns count of received characters available
; returns with register bank 010h selected
;-----------------------------------------------------------------------------

rxavail_x:
	bankx	ser_vars
	movf	rx_ring_cnt,w
	bankx	010h
	ret


;-----------------------------------------------------------------------------
; serial receive character
; blocks until a character is available
; returns with register bank 010h selected
;-----------------------------------------------------------------------------

recv_x:	bankx	ser_vars
	movf	rx_ring_cnt
	btfsc	status,z
	goto	recv

	movf	rx_ring_op,w
	movwf	fsr
	movf	indf,w		; get character from buffer
	movwf	ser_temp	; must be global!!!
	bankx	ser_vars

	ringadv	rx_ring_op,rx_ring,rx_ring_size	; advance pointer

	decf	rx_ring_cnt	; decrement rx char count
				; (this must not be done until after character
				; has been pulled from buffer)

	movf	ser_temp,w

	bankx	010h
	ret


;-----------------------------------------------------------------------------
; message output
;-----------------------------------------------------------------------------

msgout_x:
	movwf	msg_temp
msglp:	movf	msg_temp,w
	call	msg_table
	xorlw	0
	btfsc	status,z
	ret
	call	xmit
	incf	msg_temp
	goto	msglp


;-----------------------------------------------------------------------------
; button routines
;-----------------------------------------------------------------------------

buttoninit_x:
	bankx	button_vars
	clrf	but_stat
	clrf	led_stat
	bankx	010h
	ret

; return button state in W
getbutstat_x:
	bankx	button_vars
	movf	but_stat,w
	bankx	010h
	ret

; clear buttons that are selected by bits set in W
clrbutstat_x:
	bankx	button_vars
	xorlw	0ffh
	andwf	but_stat
	bankx	010h
	ret


;-----------------------------------------------------------------------------
; reset
;-----------------------------------------------------------------------------

reset:	
	clrf	fsr

	movlw	porta_init
	movwf	porta
	movlw	porta_tris
	tris	porta

	movlw	portb_init
	movwf	portb
	movlw	portb_tris
	tris	portb

	movlw	portc_init
	movwf	portc
	movlw	portc_tris
	tris	portc

	movlw	0dfh
	option

	call	uartinit
	call	buttoninit

	movlw	09fh		; enable RTCC interrupt
	option


;-----------------------------------------------------------------------------
; main
;-----------------------------------------------------------------------------

main:	movlw	banner
	call	msgout

loop:
	call	rxavail
	btfsc	status,z
	goto	loop1
	call	recv
	call	xmit

loop1:	call	getbutstat
	btfsc	status,z
	goto	loop9

	movwf	temp
	call	clrbutstat
	movf	temp,w

	movlw	001h
	movwf	temp2

	movlw	'0'
	movwf	temp3

loop2:	movf	temp,w
	andwf	temp2,w
	btfsc	status,z
	goto	loop3

	movf	temp,w
	xorwf	led_stat

	movf	' '		; don't know why, but this fixes the bug that
	call	xmit		;   loses the leading 'b' in the button message
	movlw	button_msg
	call	msgout
	movf	temp3,w
	call	xmit
	movlw	pressed_msg
	call	msgout

loop3:	incf	temp3
	bcf	status,c
	rlf	temp2
	btfss	temp2,maxbutton
	goto	loop2

loop9:	goto	loop

		
;-----------------------------------------------------------------------------
; reset vector
;-----------------------------------------------------------------------------

	errorlevel	-306	; don't display "crossing page boundary" warning
	org	07ffh
	goto	reset
	errorlevel	+306


;-----------------------------------------------------------------------------
; fuses
;-----------------------------------------------------------------------------

_DEVICE	equ	0x44e4fa

	errorlevel	-220	;don't display "address exceeds range" warning
	org	1010h
	data	_DEVICE&0xfff	;configuration bits (TURBO, SYNC, OPTIONX, etc.)
	data	_DEVICE>>12	; (PINS, CARRYX, BOR40, BANKS, PAGES)
	errorlevel	+220	;restore warning message

	end