;  ************************************************************************************
;  *    SerialIO.asm  -   full duplex software UART
;  *    -- serial input/output based on timer interrupt
;  * 
;  *	Alan Probandt    - Portland Oregon USA   alan_probandt@yahoo.com 
;  *
;  * 	Code designed for the Atmel AVR Tiny11/12, works OK with Tiny26 or 90S1200
;  *
;  *	version 1.00  Sept 30, 2004
;  *	
;  *	3.579MHz color burst crystal operation   -- change 'Clock' variable value for other crystal values
;  *     
;  *
;  *   Use Terminal or other unkerned font 
;  *     in text editor to format this file correctly, set tab stop to 8
;
;  
;    				Atmel AVR Tiny11
;	  RESET/ 	PIN 1     | VCC  PIN 8  +5V
;	3.579MHz Xtal	PIN 2 PB3 | PB2  PIN 7 Soft UART Transmit Out
;	color burst	PIN 3 PB4 | PB1  PIN 6 
;	   	        PIN 4 Gnd | PB0  PIN 5 Soft UART Receive In
;    
;  ********************************************************************************************
;    How the program works
;
;
;   The Tiny11 timer overflows every half BAUD period (16 microseconds for MIDI - 53 uSec for 9600 baud)
; The code does not interrupt on the falling edge of the input start bit like hardware UARTs do.
; The interrupt routine checks if the MIDI input pin is low.  This will indicate the start bit 
; of a serial-in byte has occurred.  The timer interval is one half of a BAUD to always catch 
; an input start bit.
;   When an input start bit is seen, the In_Active and In_Phase flag are set.
; The Input counter value set at 9 by reset or the previous serial input activity.
; After start bit detection on the input pin, data is shifted in on every second timer interrupt.
;
;   New data from the UART is ready when the NewInputDataReady flag in InControl register is set.
; 
;   Serial output begins when the main code puts the data byte into the TxData register 
; and sets the Out_Active flag in the OutControl register.  Before loading new data to be sent,
; wait until the Out_Active flag is clear.  This means the previous tranmit is finished.
;
;  Resources Used:
;	four registers - static data between interrupts, plus one scratch register
;	Timer0, 2 I/O pins
;	88 program words, not including demo in main program
;
;  ********************************************************************************************

.nolist
.include "Tn11def.inc"
.list

.equ	Clock			= 3579545 ; television color burst frequency - cheap common crystal value
.equ	TimerPreScaler		= 1  ; clock divisor value, not AVR control register setting
.equ	BaudRate		= 38400
.equ	TimerInterval		= BaudRate * TimerPreScaler
.equ	ReloadCycleLatency	= 6  ; # of cycles between Overflow and TCNT0 having reloaded count value
.equ	HalfBAUDTime		= Clock/(TimerInterval * 2)
.equ	TimerReloadValue	=  (256-(HalfBAUDTime)) + ReloadCycleLatency

; software UART definitions 
.equ	SoftUARTout		= PortB         
.equ	SoftUARTin		= PinB
.equ	SoftUARTDDR		= DDRB
.equ	TxPin			= 2  ; PB2 - Tiny11 pin 7
.equ	RxPin			= 0  ; PB0 - Tiny11 pin 5

;*********************************************************************************************
;  Register definitions
;
; lower registers
; r0 is used for reading table data in program memory using the LPM instruction and ZH-ZL register pair

;  upper registers
.def	temp		= r16	; scratch register            
.def	irqtemp		= r17  	; interrupt's scratch register
.def	InControl	= r18   ; bits 0-3 are shift counter  bits 7-4 are status flags
;  Serial input control register flag values   
.equ	In_Active	= 7    
.equ	In_Phase	= 6     ; Flag indicates whether to read RxPin data on current Timer IRQ
.equ	NewInputDataReady = 5   ; set when a new byte is ready to be read from the RxData register

.def	OutControl	= r19	; bits 0-3 are shift counter  bits 7-4 are status flags 
; Serial output control register flag values   
.equ	Out_Active	= 7
.equ	Out_Phase	= 6	; Flag indicates whether to change TxPin data on current Timer IRQ

.def	TxData		= r20            
.def	RxData		= r21   
.def	quickloop		= r1
.def	bigloop		= r2
;*********************************************************************************************

.cseg
.org   $0000
		rjmp 	reset 	; Reset handler $000
		
;*****************************************************
;                                                        		
;	Timer Overflow Interrupt Code                                            		
;                                                    
;*****************************************************
		
.org  OVF0addr   ; $003  Timer 0 overflow handler  
;   Since interrupt vectors after this one aren't used, it's OK to put the timer IRQ 
; routine here and save two clock cycles of IRQ latency by using no RJMP instruction.  
; 16 cycles total per IRQ if no software UART activity.
		
		
OVT0:		ldi	irqtemp, TimerReloadValue
        	out	TCNT0, irqtemp	; restore the timer count to one half baud period  
        	
;                                           |             
;  	sbr#  bittest for condition        /?\ flow chart 
;	rjmp  -condition false 	          |   |n          
;	rjmp  -condition true            y|   |

          	sbrc	InControl, In_Active ; check for processing an input byte first.
          	rjmp	ReadActive
          	         	
       		sbic	SoftUARTin, RxPin    ; check for new start bit
        	rjmp	CheckIfOutActive ; no input activity, so check now for output
         	
NewInputStart:  sbr	InControl, (1 << In_Active) | (1 << In_Phase)
		cbr	InControl, (1 << NewInputDataReady)       
		rjmp	CheckIfOutActive  ; input complete, now check output
          	
ReadActive:     sbrc	InControl, In_Phase ; if clear, read the current data on the input pin now 
          	rjmp	InPhaseSet ; if set, send the next bit of transmitted data on next interrupt    
		rjmp	ReadInputData
		
InPhaseSet:	cbr	InControl, (1 << In_Phase)  ; this phase is inactive, so clear flag
                rjmp	CheckIfOutActive ; no input activity, so check now for output

ReadInputData:	mov	irqtemp, InControl  ; read counter inside of the InControl reg
		andi	irqtemp, 0b00001111 ; isolate counter from control flags
          	tst	irqtemp
          	breq	In_Done

          	cpi	irqtemp,1
		breq	In_StBit
          	
          	sec
		sbis	SoftUARTin, RxPin
		clc
NewInputBit:	ror	RxData
		          
In_StBit:	sbr	InControl, (1 << In_Phase) ; skip input activity on next timer interrupt

DecCntr:	mov	irqtemp, InControl   ; decrement counter inside of the InControl reg
		andi	irqtemp, 0b00001111  ; isolate counter from control flags
		dec	irqtemp
		andi	InControl, 0b11110000  ; erase old counter value from InControl register
		or	InControl, irqtemp   ; put new counter value into InControl register
		                                                            
		rjmp	CheckIfOutActive  ; input complete, now check output                    
          
In_Done:	ldi	InControl, 9 ; initialize input counter and Active flag (cleared)
		sbr	InControl, (1 << In_Phase) | (1 << NewInputDataReady)
		
		rjmp	CheckIfOutActive  ; input complete, now check output
		
		
          	
; @@@@@@@@  serial output section

CheckIfOutActive:
		sbrs	OutControl, Out_Active
LeaveTimerInterrupt:	; single exit point for interrupt routine
          	reti    ; no Serial data being processed, so leave timer interrupt
      	
          	
          	sbrc	OutControl, Out_Phase ; if clear, send the next bit of transmitted data now 
          	rjmp	OutPhaseSet ; if set, send the next bit of transmitted data on next interrupt    
		rjmp	DoOutput
		
OutPhaseSet:	cbr	OutControl, (1 << Out_Phase)  ; this phase is inactive, so clear flag    
                rjmp	LeaveTimerInterrupt ; no output activity, so leave interrupt handler   
                
                       
DoOutput:	mov	irqtemp, OutControl  ; check which bit of TxData is being transmitted now
		andi	irqtemp, 0b00001111  ; by reading the counter section of the control register
          	tst	irqtemp
          	breq	Out_Done
          	cpi	irqtemp, 10
          	breq	Out_DoStart
          	cpi	irqtemp,1
		breq	Out_DoStop
          	
		lsr	TxData
        	brcc	LowOutputData
        	sbi	SoftUARTout, TxPin
        	rjmp	DecOutCnt
LowOutputData:  cbi	SoftUARTout, TxPin
DecOutCnt:	mov	irqtemp, OutControl   ; decrement counter inside of the OutControl reg
		andi	irqtemp, 0b00001111  ; isolate counter from control flags
		dec	irqtemp
		andi	OutControl, 0b11110000  ; erase old counter value from OutControl register
		or	OutControl, irqtemp   ; put new counter value into OutControl register	
SetOutPhase:	sbr	OutControl, (1 << Out_Phase) ; skip output activity on next timer interrupt  
		rjmp	LeaveTimerInterrupt     	
          
Out_DoStart:	cbi	SoftUARTout, TxPin    
		rjmp	DecOutCnt
          
Out_DoStop:	sbi	SoftUARTout, TxPin 
		rjmp	DecOutCnt
          
Out_Done:	ldi	OutControl, 10 ; initialize output counter and Out_Active flag (cleared)
		rjmp	SetOutPhase

;;====================================================         
                              
		
;********************************************************
;
;	Main Code
;
;********************************************************	                        
RESET:
	ldi	temp, TimerReloadValue  ; load one half BAUD period into timer register       
	out	TCNT0, temp                                                                  
	ldi	temp, (1 << TOV0)   ;  unmask Timer interrupt and reset Overflow Flag        
	out	TIMSK, temp                                                                  
	ldi	temp, ( (0<<CS02) | (0<<CS01) | (1<<CS00) ) ; init pre-scaler for the counter here  
	out	TCCR0, temp  ; start timer interrupt  prescaler = divide clock by 8                                       
      
; initialize software UART
	sbi	SoftUARTout, TxPin ; start device with transmit pin high
	sbi	SoftUARTDDR, TxPin ; set transmit pin as output
	sbi	SoftUARTout, RxPin  ; enable pull-up resistor on receive pin
	cbi	SoftUARTDDR, RxPin ; set receive pin as input	PB0
	ldi	OutControl, 10 ; initialize output bit counter and clear flags
	ldi	InControl, 9  ; initialize input bit counter and clear flags

	sei	; enable all unmasked interrupts
	
;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
;
;  Main Demonstration Code
;
;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

main:	
	sbrs	InControl, NewInputDataReady	; new serial byte received from software UART?
	rjmp	main				; no, check again loop

	mov	temp, RxData			; yes, read it into temp register 
	cbr	InControl, (1 << NewInputDataReady) ; indicate Rx Data has been processed

testTx:	sbrc	OutControl, Out_Active		; wait until software UART is ready for new Tx data
	rjmp	testTx
	mov	TxData, temp			; and send new data back out in Transmit data register

	sbr	OutControl, (1 << Out_Active)   ; Start transmission by setting Tx Active flag
	
	rjmp	main
	

;delay routine - here if needed
dly:	clr	quickloop
	clr	bigloop
dly1:	dec	quickloop
	brne	dly1	
	dec	bigloop	
	brne	dly1
	ret
 	