;---------------------------------------------------------------------------
; Asynchronous RS-232 for the SX18AC
; Aaron Logue, Apr 2002
;
; The idea here is that we may want to have several different processes
; running, each one independently waiting for some amount of time to elapse
; before twiddling an output, polling an input pin, sending a message to
; another process, or changing states.  This scheduler provides a simple
; non-interrupt driven mechanism to allow processes to specify a time
; interval to sleep.
;
; mpasm /x- /l+ /e+ /w0 /p16C58A /aINHX8M /c- serial.asm
;---------------------------------------------------------------------------
	radix		dec
	include		mydefs.inc

;DEVICE	EQU	TURBO | SYNC | IRC | RC4MHZ | MORETRIM | TRIM9	; Internal RC clock
DEVICE	EQU	TURBO | SYNC | FOSCHI | FOSC2		; External oscillator

MHZ	EQU	20		; Clock rate in Mhz (4, 16, 20, or 50 is best)
JIF	EQU	8		; How many microseconds is one jiffy
PSCALE	EQU	8		; Prescaler ratio (1 if unused)

JIFTIKS	EQU	(MHZ * JIF / PSCALE)	; How many RTCC counts per jiffy
 IF (JIFTIKS == 0)
 ERROR Prescaler may be too high for jiffy granularity.
 ENDIF

JBITS	EQU	(104 / JIF)	; How many jiffies per 9600 baud bit
JBITST	EQU	JBITS / 2	; How many jifs to wait after leading edge
                                ; of start bit to sample middle of bit
JMILLI	EQU	(1000 / JIF)	; How many jiffies per millisecond

 IF (PSCALE == 1)		; Pick a value for OPTION register
OPTREG	EQU	0xC8
 ENDIF
 IF (PSCALE == 2)
OPTREG	EQU	0xC0
 ENDIF
 IF (PSCALE == 4)
OPTREG	EQU	0xC1
 ENDIF
 IF (PSCALE == 8)
OPTREG	EQU	0xC2
 ENDIF
 IF (PSCALE == 16)
OPTREG	EQU	0xC3
 ENDIF

PortA	EQU	05h
PortB	EQU	06h

TrisA	EQU	11110000b	; PortA I/O pin configuration
TrisB	EQU	00001000b	; PortB I/O pin configuration

#DEFINE	RxD	PortB,3		; Input:  RS-232 serial input
#DEFINE	TxD	PortB,1		; Output: RS-232 serial output
#DEFINE	Xmt	PortB,2		; Output: Pulse stream to transmitter

; Global variables
ELAPSEDJIF 	EQU	8	; Global, elapsed number of jiffies
LASTRTCC 	EQU	9	; Global, last value of RTCC
ELAPSEDRTCC	EQU	10	; Global, elapsed number of RTCC ticks
TEMP		EQU	11	; Global, general purpose

B_BITS		EQU	12	; Output for oscilloscope debugging

; Banked registers
RX_JIFS		EQU	16
RX_STATE	EQU	17
RX_COUNT	EQU	18
RX_CHAR		EQU	19

TX_JIFS		EQU	20
TX_STATE	EQU	21
TX_COUNT	EQU	22
TX_CHAR		EQU	23

; Process equates
RX_STARTBIT	EQU	1
RX_DATABITS	EQU	2
RX_STOPBIT	EQU	3
TX_STARTBIT	EQU	1
TX_DATABITS	EQU	2
TX_STOPBIT	EQU	3


	org	0		; Generate code here
	movlw	OPTREG		; Initialize OPTION register
	option

	mode	0xf
  	movlw	TrisA		; Move 0x00 to W to set direction
        tris	PortA		; W --> PortA direction bits

  	movlw	TrisB		; Move 0x00 to W to set direction
        tris	PortB		; W --> PortB direction bits

	clrf	PortA
	clrf	PortB
	clrf	B_BITS

	clrf	LASTRTCC
	clrf	ELAPSEDRTCC

main_loop
	; -------------------------------------------------------------------
	; Compute elapsed RTCC ticks.  This needs to be called often
	; enough to avoid losing time by wrapping.  1 jiffy costs 20 ticks
	; of detection overhead here.  The prescaler should be set high
	; enough that there's no chance of ever missing a jiffy.
	;
	movf	rtcc, w		; Load current RTCC counter
	movwf	TEMP		; Save an unchanging copy of it
	movf	LASTRTCC, w	; Load previous RTCC counter value
	subwf	TEMP, w		; Compute difference
	addwf	ELAPSEDRTCC, f	; Add difference to elapsed RTCC ticks
	movf	TEMP, w		; Save new previous RTCC counter value
	movwf	LASTRTCC	;  for next elapsed computation
	;
	; Compute elapsed jiffies.  Our clock speed should be high enough
	; and our jiffy period long enough that we don't have very many
	; elapsed jiffies each time through here.  That means that repeated
	; subtraction of the number of RTCC ticks per jiffy (JIFTIKS) from
	; elapsed RTCC ticks (ELAPSEDRTCC) should be more efficient than
	; dividing ELAPSEDRTCC by JIFTIKS.  The more instructions per jiffy,
	; the better, as long as jiffies are still granular enough to be
	; useful.  4 or 8 microseconds per jiffy at 50 Mhz is probably good.
	;
	clrf	ELAPSEDJIF	; Assume no jiffies have elapsed
	movlw	JIFTIKS		; Load number of ELAPSEDRTCC ticks per jiffy
ej_10
	subwf	ELAPSEDRTCC, f	; Subtract JIFTIKS from ELAPSEDRTCC
				; C is 1 if result is positive or zero
	btfss	STATUS, C	; Did we have enough ELAPSEDRTCC for a jiffy?
	goto	ej_20		;   sadly, no
	incf	ELAPSEDJIF, f	;   joyously, yes - we have a jiffy!
	goto	ej_10		; Go see if there's enough for another one
ej_20
	addwf	ELAPSEDRTCC, f	; Restore incomplete-jiffy elapsed RTCC count
	;
	; ELAPSEDJIF is now the number of elapsed jiffies since processes
	; were last called.
	; -------------------------------------------------------------------

	call	rx_serial_process
	call	tx_serial_process
	goto	main_loop


;---------------------------------------------------------------------------
; Asynchronous RS-232 Receiver
;
;	States: 0 polling for start bit
;		1 verify center of start bit
;		2 sample center of data bit (8 times)
;		4 verify center of stop bit; parse command
;---------------------------------------------------------------------------
rx_serial_process
	clrf	FSR		; Set bank 0
	movf	RX_JIFS, w	; sleep time remaining -> W
	btfsc	STATUS, Z	; skip if we're asleep
	goto	rx_running	; process is awake and running

	movf	ELAPSEDJIF, w	; elapsed jiffies -> W
	btfsc	STATUS, Z	; skip if jiffies have elapsed
	retlw	0		; no jiffies elapsed - keep sleeping

	subwf	RX_JIFS, f	; Subtract elapsed from remaining sleep time
				; C is 1 if result is positive or zero
	btfss	STATUS, C	; Was result negative?
	goto	rx_overslept	;   yes, time to wake up (also, we overslept)
	btfsc	STATUS, Z	; Was result zero?
	goto	rx_running	;   yes, time to wake up
	retlw	0		; keep sleeping
rx_overslept
	; RX_JIFS now contains a negative value; the amount that we overslept.
	; The next sleeper can add its desired sleep time to this value rather
	; than sleeping for a hardcoded value and get reduced sleep.  In this
	; way, we "catch up" and our wakeup times will be closer to the center
	; of received bits.  Oversleeping should not be an issue at higher
	; clock speeds.
	;	clrf	RX_JIFS		; Zero remaining sleep time
rx_running
	btfsc	RX_STATE, RX_STARTBIT
	goto	rx_verify_start
	btfsc	RX_STATE, RX_DATABITS
	goto	rx_read_data
	btfsc	RX_STATE, RX_STOPBIT
	goto	rx_verify_stop
; Poll for beginning of a start bit
rx_poll
	btfss	RxD		; Do we have a start bit?
	retlw	0		;   no, keep polling
	movlw	JBITST		;   yes
	movwf	RX_JIFS		; Sleep until center of start bit
	bsf	RX_STATE, RX_STARTBIT ; Advance to start bit verify state
	retlw	0
; Verify center of start bit
rx_verify_start
	btfss	RxD		; Is the start bit still there?
	goto	rx_reset	;   no, keep polling
	movlw	JBITS		;   yes - sleep until center of first data bit
	addwf	RX_JIFS, f	; Reduce by any time that we overslept
	movlw	8
	movwf	RX_COUNT	; Set data bit count to 8
	rlf	RX_STATE, f	; Advance to read data state
	retlw	0
; Gather data bits
rx_read_data

	call	toggle_B_bit	; toggle port B bit 2 to see when we sample

	bcf	STATUS, C	; Assume data bit is 0
	btfss	RxD		; If RS-232 voltage level is high, data is 0
	bsf	STATUS, C	; Data bit is 1
	rrf	RX_CHAR, f	; Shift carry into character register
	movlw	JBITS		; Sleep until center of next bit
	addwf	RX_JIFS, f	; Reduce by any time that we overslept
	decfsz	RX_COUNT, f	; Have we gathered 8 bits yet?
	retlw	0		;   no, keep gathering
	rlf	RX_STATE, f	; Advance to verify stop bit state
	retlw	0
; Verify stop bit
rx_verify_stop
	btfsc	RxD		; Is the stop bit clear?
	goto	rx_reset	;   no, throw data away
	movf	RX_CHAR, w	; Received character -> W
	call	parse_command	; Do something with received character
; Return to polling
rx_reset
	clrf	RX_STATE
	retlw	0

;---------------------------------------------------------------------------
; Asynchronous RS-232 Transmitter
;
;	States: 0 waiting for a character to transmit
;		1 sending start bit
;		2 sending data bits
;		4 sending stop bit
;---------------------------------------------------------------------------
tx_serial_process
	clrf	FSR		; Set bank 0
	movf	TX_JIFS, w	; sleep time remaining -> W
	btfsc	STATUS, Z	; skip if we're asleep
	goto	tx_running	; process is awake and running

	movf	ELAPSEDJIF, w	; elapsed jiffies -> W
	btfsc	STATUS, Z	; skip if jiffies have elapsed
	retlw	0		; no jiffies elapsed - keep sleeping

	subwf	TX_JIFS, f	; Subtract elapsed from remaining sleep time
				; C is 1 if result is positive or zero
	btfss	STATUS, C	; Was result negative?
	goto	tx_running	;   yes, time to wake up (also, we overslept)
	btfsc	STATUS, Z	; Was result zero?
	goto	tx_running	;   yes, time to wake up
	retlw	0		; keep sleeping
tx_running
	btfsc	TX_STATE, TX_STOPBIT
	goto	tx_send_stop
	btfsc	TX_STATE, TX_DATABITS
	goto	tx_send_data
	btfss	TX_STATE, TX_STARTBIT
	retlw	0		; Nothing to transmit
; Begin transmission
	bsf	TxD		; Output the start bit
	movlw	JBITS		; Sleep for one bit interval
	movwf	TX_JIFS
	movlw	8
	movwf	TX_COUNT	; Set data bit count to 8
	rlf	TX_STATE, f	; Advance to transmit data state
	retlw	0
tx_send_data
	rrf	TX_CHAR, f	; Rotate LSB into carry
	btfsc	STATUS, C	; Raise or lower the line depending on carry
	bcf	TxD
	btfss	STATUS, C
	bsf	TxD
	movlw	JBITS		; Leave it there for one bit interval
	addwf	TX_JIFS, f	; Reduce by any time that we overslept
	decfsz	TX_COUNT, f	; Have we transmitted 8 bits?
	retlw	0
	rlf	TX_STATE, f	; Advance to stop bit state
	retlw	0
tx_send_stop
	bcf	TxD		; Output the stop bit
	movlw	JBITS		; Leave it there for one bit interval
	addwf	TX_JIFS, f	; Reduce by any time that we overslept
	clrf	TX_STATE
	retlw	0

;---------------------------------------------------------------------------
; parse_command
;
;	Do something with received character
;---------------------------------------------------------------------------
parse_command
	movwf	TX_CHAR
	incf	TX_CHAR, f	; Shift it up one character
	bsf	TX_STATE, TX_STARTBIT
	retlw	0

;---------------------------------------------------------------------------
; toggle bit 2 on port B
;---------------------------------------------------------------------------
toggle_B_bit
	btfss	B_BITS, 2
	goto	tog10
	bcf	B_BITS, 2
	bcf	PortB, 2
	retlw	0
tog10
	bsf	B_BITS, 2
	bsf	PortB, 2
	retlw	0
	
	end
