;***************************************************************************
;*
;* SERIAL2 COMMS FUNCTIONS
;*
;***************************************************************************
;
; these routines don't use the usart to communicate, it's busy...
; instead, we simulate a uart in software
; we could do this simply by using delays, but unfortunately we have to 
; do other things at the same time; in particular, we need to be able to 
; send and receive data simultaneously...
;
; So, we use a polled method driven by a timer interrupt and controlling two
; state machines; one to transmit and one to receive
;
; while we're at it, we count the ticks to give an (eventual)25Hz internal clock

	.def	int0 =		r20
	.def	int1 = 		r21
	.def    int2 =		r22     
	.def    int3 =		r23     

	.dseg

next_read_2:	.byte 1
next_write_2:	.byte 1
input_buffer_2:	.byte buffer_size
;local variables
uart_rxd:		.byte 1				;the byte being received
uart_txd:		.byte 1				;the byte being transmitted
uart_txtick:	.byte 1				;tick count for the transmitter
uart_rxtick:	.byte 1				;tick count for the receiver
uart_txbit:		.byte 1				;bit count for tx
uart_rxbit:		.byte 1				;bit count for rx
uart_status:	.byte 1				;uart status
	;status bits
	.equ		txbusy = 0			;set if a byte is in transmission
	.equ		rxbusy = 1			;set if a byte is being received
	.equ		rxframe = 2			;set if the stop byte isn't a one

;clock store
mickey:			.byte 1				;192 of these make 1/25th second
twentyfifth:	.byte 1				;25 of these make a second
second:			.byte 1				;60 of these make a minute
minute:			.byte 1				;60 of these to an hour
hour:			.byte 1				;we stop counting with hours

	.cseg

;we run a tick at four times baud rate; this lets us hit the start of a received
;character with an average error of +/- 1/8th of a bit; having detected the bit start
;sometime between t=0 and t=0.25, we can arrange to sample the incoming bits somewhere
;between 0.25 and 0.5 of a bit period in.
;
;we'd *like* to sample at 16 times baud rate, like the hardware does, but at baud*4
;we will be getting interrupts every 1667 clocks; the processor has other things to do.
;
;on each tick, we first implement the transmit tick process, then the receive tick
;we need to ensure we take significantly fewer than 1667 clocks on each tick
;
;this routine also works at 2400 baud; in this case the appropriate divider is 883

	.equ		uarttx = pd1		;can be any pin, need to change port 
	.equ		uartrx = pd0		;will be pd3,2 when debugged

ser2_tick:		;we arrive here on a tick
				;the txbusy bit is set by the ser2_out routine

	;the status reg is *not* automatically preserved
	in		int2,sreg
	push	int2
	push	zh
	push	zl
	
	;first we do the tx tick stuff
	lds		int2,uart_status
	andi	int2,1<<txbusy	;are we transmitting anything?
	breq	s2t_rx			;nope, see if we're receiving

	;otherwise, see what the tick count is
	lds		int2,uart_txtick
	inc		int2
	sts		uart_txtick,int2
	cpi		int2,4			;we only change state when the tick is zero/four
	brne	s2t_rx			;so if it isn't, we skip straight to receive
	clr		int2			;otherwise, reset the count
	sts		uart_txtick,int2

	;now we see which of the ten bits we're transmitting
	;the call to ser2_out also clears the bit count
	;by default the line is high (idle) unless transmitting

	lds		int2,uart_txbit
	cpi		int2,0			;bit 0 - the start bit
	brne	s2t_01			;nope, see what else
	cbi		portd,uarttx	;else clear the bit and wait for next tick
	inc		int2
	sts		uart_txbit,int2
	rjmp	s2t_rx

s2t_01:
	cpi		int2,9			;got to the stop bit yet?
	brge	s2t_02			;yep, go and deal with it
	inc		int2
	sts		uart_txbit,int2
	lds		int2,uart_txd
	ror		int2
	sts		uart_txd,int2	;shift the least sig bit into carry and save
	brcs	s2t_011
	cbi		portd,uarttx	;clear the signal
	rjmp	s2t_rx
s2t_011:
	sbi		portd,uarttx	;or set it depending on carry flag
	rjmp	s2t_rx

s2t_02:						;if we're here, then it's time for the stop bit
	brne	s2t_03			;must be more than nine, we're done
	inc		int2
	sts		uart_txbit,int2
	sbi		portd,uarttx	;set the line for the stop bit
	rjmp	s2t_rx

s2t_03:						;here when the stop bit is finished
	clr		int2
	sts		uart_txbit,int2	;reset the bit counter
	sbi		portd,uarttx	;make sure - just in case - the line is high/idle
	lds		int2,uart_status
	andi	int2,~(1<<txbusy)	;clear the busy bit
	sts		uart_status,int2

	;and now we can consider the input sampling
	;this works in an essentially similar way, but is not necessarily
	;synchronous to the byte being received.
	;if the rxbusy flag is not set, we check for a start bit on every
	;clock sample; once we find one we reset the clock to three, and then
	;we will sample on clock = zero
	;if the start bit as sampled is not a zero, we assume a line glitch

s2t_rx:

	lds		int2,uart_status
	andi	int2,(1<<rxbusy);are we already receiving a byte?
	brne	s2t_10			;yes, deal with it

	sbic	pind,uartrx		;else sniff at the rx line and if it's still high
	rjmp	s2t_x			;return

	;otherwise we assume we've found the start bit
	ldi		int2,2
	sts		uart_rxtick,int2	;set the tick to two
	lds		int2,uart_status
	sbr		int2,(1<<rxbusy)
	sts		uart_status,int2	;set the busy flag
	clr		int2
	sts		uart_rxbit,int2		;clear the bit counter
	sts		uart_rxd,int2		;and the data accumulator
	rjmp	s2t_x				;and wait for the next tick

s2t_10:
	;here if we think we're detecting something
	lds		int2,uart_rxtick	;what's the tick?
	inc		int2
	sts		uart_rxtick,int2	;increment it
	cpi		int2,4		
	brne	s2t_x				;if it's not four, we're done
	clr		int2
	sts		uart_rxtick,int2	;else clear it
	lds		int2,uart_rxbit
	inc		int2
	sts		uart_rxbit,int2		;increment the bit count
								;bit 1 = start
								;bit 2-9 = data
								;bit 10 = stop
	cpi		int2,1				;are we in the start bit?
	brne	s2t_11				;no, check further
	sbis	pind,uartrx			;if the line has gone high again
	rjmp	s2t_x

	lds		int2,uart_status
	cbr		int2,(1<<rxbusy)
	sts		uart_status,int2	;it must be a glitch, so un-busy ourselves
	rjmp	s2t_x				;and wait for next tick

s2t_11:
	cpi		int2,10
	brge	s2t_12				;more than 9? must be a stop bit, then

	;otherwise, we sample the line and set the carry to the value of the line
	clc
	sbic	pind,uartrx
	sec

	;and roll it in to the high bit of rxd
	lds		int2,uart_rxd
	ror		int2
	sts		uart_rxd,int2		;and save it
	rjmp	s2t_x				;and wait for the next bit

s2t_12:
	;if we're here, we are expecting a stop bit, or we've had it
	cpi		int2,10
	brne	s2t_13
	
	;assume it's a valid stop bit; if we cared more, here would be a good place
	;to check that the bit is in fact high at this bit
	;however, we just wait for the next bit
	rjmp	s2t_x

s2t_13:
	;by now, we've waited past the putative stop bit, so we can reset everything
	;for the next byte, and add the new byte to the buffer
	lds		int2,uart_status
	cbr		int2,(1<<rxbusy)
	sts		uart_status,int2

	;get the address we need to write to
	ldi 	zh,high(input_buffer_2)
	ldi		zl,low(input_buffer_2)
	lds		int2,next_write_2
	clr		int3
	add		zl,int2
	adc		zh,int3
	
	;now z points at the next space in the write buffer

	;for now, we assume that there are no framing or overrun errors
	lds		int3,uart_rxd	;grab the data; this clears the flags
	st		z,int3			;save it

	;now we need to increment the pointer
	inc		int2
	cpi		int2,buffer_size
	brne	s2t_14
	clr		int2
s2t_14:
	;note that we don't check to see if we've overwritten stuff we
	;haven't yet read; we can't stop the incoming data anyway
	sts		next_write_2,int2


s2t_x:

	;now we update the clock ticks
	lds		int2,mickey
	inc		int2
	sts		mickey,int2
	cpi		int2,192
	brne	s2t_xx
	clr		int2
	sts		mickey,int2

	lds		int2,twentyfifth
	inc		int2
	sts		twentyfifth,int2
	cpi		int2,50			;<--- change to fiftieths for 2400 baud
	brne	s2t_xx
	clr		int2
	sts		twentyfifth,int2
	
	lds		int2,second
	inc		int2
	sts		second,int2
	cpi		int2,60
	brne	s2t_xx
	clr		int2
	sts		second,int2
	
	lds		int2,minute
	inc		int2
	sts		minute,int2
	cpi		int2,60
	brne	s2t_xx
	clr		int2
	sts		minute,int2	

	lds		int2,hour
	inc		int2
	sts		hour,int2

s2t_xx:
	pop		zl
	pop		zh
	pop		int0
	out		sreg,int0
	reti

ser2_init:
	;intialise the serial to 1200, 8 bit
	;we set timer 1 to give a tick at four times every sample
	;so timer1=1667, and we se the interrupt to arrive at ser2_tick
	;
	;we also set the input and output pins to portd as defined in uarttx and uartrx

	clr		arg0
	out		tccr1a,arg0			;ctc, all pins normal

	ldi		arg0,0x09
	out		tccr1b,arg0			;ctc, no prescaler

	ldi		arg0,low(833);(1667)			
	ldi		arg1,high(833);(1667)
	out		ocr1ah,arg1
	out		ocr1al,arg0			;count to 1667

	in		arg0,timsk
	sbr		arg0,(1<<ocie1a)	
	out		timsk,arg0			;allow interrupts on output mask a
	
	clr		arg0				;set up the buffer pointers
	sts		next_read_2,arg0
	sts		next_write_2,arg0

	clr		arg0
	sts		uart_status,arg0	;unbusy the tx 

	sbi		ddrd,uarttx
	cbi		ddrd,uartrx			;and set the input and output pins

	sbi		portd,uarttx
	ret

ser2_out:
	;wait until the output buffer is available, then
	;output the data in arg0 to the serial port
	lds		arg1,uart_status
	andi	arg1,(1<<txbusy)
	brne	ser2_out			;wait till there is space in the buffer
	sts		uart_txbit,arg1		;clear the bit count
	sts		uart_txd,arg0		;write it
	lds		arg1,uart_status
	sbr		arg1,(1<<txbusy)
	sts		uart_status,arg1
	ret							;and return

ser2_print:
	;output a string from program memory
	;start of string is in Z
s2p_01:
	lpm		arg0,z+
	cpi		arg0,0
	breq	s2p_x
	rcall	ser2_out
	rjmp	s2p_01
s2p_x:
	ret

ser2_in:
	;this needs to be non-blocking, since the output routine *is* blocking
	;if the pointers next_read and next_write are the same, we assume there 
	;is nothing to read. It's possible that the write pointer has looped all
	;the way round and caught up with the read pointer... in which case, tough.
	;if the zero flag is set, there is no new data, else arg0 contains new data

	cli
	lds		arg0, next_read_2
	lds		arg1, next_write_2
	sei
	cp		arg0, arg1
	breq	s2i_x			;return with zero set if the pointers are the same

	ldi		zh,high(input_buffer_2)
	ldi		zl,low(input_buffer_2)
	clr		arg1
	add		zl,arg0
	adc		zh,arg1			;point to byte to read
	ld		arg1,z
	inc		arg0
	cpi		arg0,buffer_size
	brne	s2i_01
	clr		arg0
s2i_01:
	sts		next_read_2,arg0	;update pointer
	mov		arg0,arg1		;get data back into arg0
	clz						;clear zero flag

s2i_x:
	ret

ser2_inb:					;blocking version of ser_in
							;does not return till char is available
	rcall	ser2_in
	breq	ser2_inb
	ret
