;=======================================================================
;TITLE:         wireless_display.src
;
;PURPOSE:       receive data from a noisy rs-232 connection (presumably
;		a 433Mhz wireless connection) and display characters on 
;		an LCD display.
;
;AUTHOR:        Danny Havenith
;	Copyright (c) 2008 Danny Havenith
; Use, modification and distribution is subject to the Boost Software
; License, Version 1.0. (See copy at 
; http://www.boost.org/LICENSE_1_0.txt)
;
; UART VP and associated subroutines:
; Copyright © [01/26/1999] Scenix Semiconductor, Inc. All rights reserved.
;
; CRC16 checksum calculation retrieved from sxlist.com, attributed to
; Nikolai Golovchenko.
;
; LCD display control routines inspired by a reference implementation by 
; Chris Fogelklou at Scenix Semiconductor
;
;REVISIONS:
; see SVN-logs
; This is revision: $Id: wireless_display.src 462 2009-08-30 22:36:18Z danny $
;
;CONNECTIONS:
; see the IFDEF SMD part in the source.
;
;=======================================================================

;; define this symbol to skip most wait-loops
;SIMULATION		equ 1
;-------------------------- DEVICE DIRECTIVES --------------------------

		DEVICE		SX28,OSCXT1,TURBO
		DEVICE		STACKX, OPTIONX

		IRC_CAL		IRC_SLOW

		RESET		Initialize
;------------------------------ CONSTANTS ------------------------------
; LCD Definitions
; LCD_Interface Constants.
;  Commands
LCD_Clear		equ	%00000001
LCD_Cursor_Home		equ	%00000010
LCD_Entry_Mode		equ	%00000100
LCD_Display_control	equ	%00001000
LCD_Cursor		equ	%00010000
LCD_Function		equ	%00100000
LCD_Set_CGRam		equ	%01000000
LCD_Set_DDRam		equ	%10000000
;  Control Bits
LCD_S			equ	%00000001	; Display Shift
LCD_B			equ	%00000001	; Cursor Blink
LCD_ID			equ	%00000010	; Cursor Position Decrement
LCD_C			equ	%00000010	; Cursor off/ on
LCD_RL			equ	%00000100	; Cursor direction (Right = 1 left =0)
LCD_F			equ	%00000100	; Character resolution 1= 5x10, 0= 5x7
LCD_D			equ	%00000100	; Display 0n = 1 off = 0
LCD_SC			equ	%00001000	; Shift 0=cursor 1=diaplay
LCD_N			equ	%00001000	; Number od lines 0 for 1 line 
LCD_DL			equ	%00010000	; Data lines 0 for 4 data lines 1 for 8 data lines


LCD_Delay	equ	1

;------------------------------ --------- ------------------------------
; The following constants are meant to be changed to configure for 
; diverse hardware.

Frequency		EQU	4_000_000 ; clock frequency
BaudRate		EQU	2400 	  ; serial port baudrate
InterruptsPerBit 	EQU	7	; samples per serial bit, minimum of 3
; above numbers boil down to (4e6 / (2400 * 7)) = 238 cycles per interrupt
; a baudrate of 9600 and ipb of 3 means (4e6 / (9600 * 3)) = 139 cycles/interrupt

; uncomment the following EQU if using a MAX232, since we need to
; reverse the rs-232 signals while not using a MAX232
UsingMAX232	EQU 1		; we're using a max232 

;------------------------------ --------- ------------------------------
; some derived constants (derived from the ones above)
; These are not meant to be changed manually.
;
		; clock ticks per interrupt
InterruptPeriod	EQU	Frequency/(InterruptsPerBit * BaudRate) + 1
		; value to put in W to obtain the clock ticks per interrupt
		; formulated in this particular way to get rid of 'Literal 
		; truncated to 8 bits' warning
		; If you still get that warning, it probably means the interrupt
		; period is larger than 256. Reconsider your baudrate or InterruptsPerBit
RetiwValue	EQU 	256-InterruptPeriod 

; number of interrupts to pass between detection of the start bit ("flank") and
; the middle of the first bit, plus one (see code for that 'plus one').
StartDelay	EQU InterruptsPerBit/2 + InterruptsPerBit + 1

		; port definitions

; note that rb.7 is currently doubly defined as both rs232 output and LED output,
; this works since the rs232 out is currently unused. If it were used we cannot use 
; the led anymore...
SerialOut	EQU	rb.7 ; pins are serial output and
SerialIn	EQU	ra.0 ; input respectively.

; pin allocation is dependent on the definition of the symbol 'SMD'. SMD means
; new SMD print layout and new pin allocations
SMD		=	1	; remove line if not using the (new)SMD print layout
IFDEF SMD
LCD_LOW_NIBBLE	=	1		; lcd data will be in low nibble of register

led		= 	rb.7
lcd_control	=	ra
lcd_RS		=	ra.1		; 0 = i--nstruction, 1 = data
lcd_RW		=	ra.2		; 0 = write, 1 = read
lcd_E		=	ra.3		; 1,1-->0 is the LCD enable
lcd_data	= 	rb		; databits must be in upper nibble see (1)
lcd_DB4		=	rb.0		; DB4 = Data bus line 4 (LSB)
lcd_DB5		=	rb.1
lcd_DB6		=	rb.2
lcd_DB7		=	rb.3		; DB7 = Data bus line 7 (MSB)

ELSE

led		= 	rb.1
lcd_control	=	rb
lcd_RS		=	rb.5		; 0 = i--nstruction, 1 = data
lcd_RW		=	rb.6		; 0 = write, 1 = read
lcd_E		=	rb.7		; 1,1-->0 is the LCD enable
lcd_data	= 	rc		; databits must be in upper nibble see (1)
lcd_DB4		=	rc.4		; DB4 = Data bus line 4 (LSB)
lcd_DB5		=	rc.5
lcd_DB6		=	rc.6
lcd_DB7		=	rc.7		; DB7 = Data bus line 7 (MSB)

ENDIF

IFDEF LCD_LOW_NIBBLE
LCD_DATA_MASK	EQU	0fh		; (1)
LCD_UNUSED_MASK	EQU	0f0h
LCD_BF			equ	%00001000	; Busy Flag,  0 = not busy
ELSE
LCD_DATA_MASK	EQU	0f0h		; (1)
LCD_UNUSED_MASK	EQU	0fh
LCD_BF			equ	%10000000	; Busy Flag,  0 = not busy
ENDIF



;------------------------------ VARIABLES ------------------------------
			ORG	$08
; 'global' bank, registers that can be accessed regardles of the current
; bank.

; temporary registers for interrupt routines
int_temp1 	DS	1
int_temp2	DS 	1

; temporary registers for non-interrupt routines
function_temp	DS	1
function_temp2	DS	1
function_temp3  DS	1

			ORG $10
SERIAL		=       $	;UART bank
tx_high		ds      1	;hi byte to transmit
tx_low		ds      1	;low byte to transmit
tx_count	ds      1	;number of bits sent
tx_divide	ds      1	;xmit timing (/16) counter
rx_count	ds      1	;number of bits received
rx_divide	ds      1	;receive timing counter
rx_byte		ds      1	;buffer for incoming byte
flags		ds	1	;contains uart and packet protocol flags.

rx_flag		EQU flags.0
buffer_full_flag EQU flags.1
buffer_writeahead_full EQU flags.2

;packet_address EQU rc ; use this one if an address is encoded in rc
packet_address	ds	1 ; hardcoded packet address, make room for sizes of packets
packet_state	ds	1

		; of the following states it's important that ps_noise is the last
		; one. Also, the states need to be in sync with the jumptable furtheron.
ps_preamble	EQU	0	; we've received our first zero
ps_address	EQU	1	; we've received $55, expect an addres byte
ps_data		EQU	2	; we've received a correct address, receive data
ps_checksum1	EQU	3	; we've received our data, now expect a checksum
ps_checksum2	EQU	4	; we've received our first checksum byte, now expect a second one
ps_blocked	EQU	5	; we can't receive because all buffers are full
ps_size		EQU	6
ps_noise	EQU 	7	; we're receiving noise

packet_end_preamble EQU	$55	; code that ends a preamble
packet_writecount ds	1

; define a round-robin buffer in the last x bytes of ram.
; $d0 gives me 32 bytes of round-robin buffer, $b0 would give me 48 bytes, etc.
buffer_start	EQU	$d0	; buffer end is always $ff
buffer_writeptr ds	1	; current write-pointer, updated after receiving a full packet
buffer_writeahead ds	1	; write-ahead pointer, updated after every data byte received

buffer_readptr	ds	1	; current read-pointer

; used for crc calculations during packet receive
crc_hi		ds	1
crc_lo		ds	1


		ORG $30
SENDTEXT	=	$	;SENDTEXT bank
LCD_BANK	= 	$
string		ds	1	;used by send_string to store the address in memory
output_dir	ds	1	; should data go to the LCD or to serial?


; lcd driver registers
lcd_temp	ds	1		; Temporary register
lcd_count	ds	1		; Temporary register

delay_regs	=	$

dlycnt1		ds	1		; register used by delay routine
dlycnt2		ds	1		; register used by delay routine

			ORG $30
		


;---------------------------- SETTINGS ---------------------------

		FREQ	Frequency
	
WKED_W		equ	$0A		;Write MIWU/RB Interrupt edge setup, 0 = falling, 1 = rising
WKEN_W		equ	$0B		;Write MIWU/RB Interrupt edge setup, 0 = enabled, 1 = disabled
ST_W		equ	$0C		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
LVL_W		equ	$0D		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
PLP_W		equ	$0E		;Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
DDIR_W		equ	$0F		;Write Port Direction

IFDEF SMD

; IFDEF UsingMAX232
;RA_latch	equ	%00001101		;SX18/20/28/48/52 port A latch init
; ELSE
RA_latch	equ	%00000001		;SX18/20/28/48/52 port A latch init
; ENDIF
RA_DDIR		equ	%11110001		;see under pin definitions for port A DDIR value
RA_LVL		equ	%00001110		;SX18/20/28/48/52 port A LVL value
RA_PLP		equ	%11110000		;SX18/20/28/48/52 port A PLP value

RB_latch	equ	%00000000		;SX18/20/28/48/52 port B latch init
RB_DDIR		equ	%00001111		;SX18/20/28/48/52 port B DDIR value
RB_ST		equ	%11111111		;SX18/20/28/48/52 port B ST value
RB_LVL		equ	%00000000		;SX18/20/28/48/52 port B LVL value
RB_PLP		equ	%00001111		;SX18/20/28/48/52 port B PLP value

RC_latch	equ	%00000000		;SX18/20/28/48/52 port C latch init
RC_DDIR		equ	%00000000		;SX18/20/28/48/52 port C DDIR value
RC_ST		equ	%11111111		;SX18/20/28/48/52 port C ST value
RC_LVL		equ	%00000000		;SX18/20/28/48/52 port C LVL value
RC_PLP		equ	%11111111		;SX18/20/28/48/52 port C PLP value

ELSE

 IFDEF UsingMAX232
RA_latch	equ	%00001101		;SX18/20/28/48/52 port A latch init
 ELSE
RA_latch	equ	%00001111		;SX18/20/28/48/52 port A latch init
 ENDIF
RA_DDIR		equ	%11111101		;see under pin definitions for port A DDIR value
RA_LVL		equ	%00000011		;SX18/20/28/48/52 port A LVL value
RA_PLP		equ	%11111110		;SX18/20/28/48/52 port A PLP value

RB_latch	equ	%00000000		;SX18/20/28/48/52 port B latch init
RB_DDIR		equ	%00000100		;SX18/20/28/48/52 port B DDIR value
RB_ST		equ	%11111111		;SX18/20/28/48/52 port B ST value
RB_LVL		equ	%00000000		;SX18/20/28/48/52 port B LVL value
RB_PLP		equ	%11111011		;SX18/20/28/48/52 port B PLP value

RC_latch	equ	%00000000		;SX18/20/28/48/52 port C latch init
RC_DDIR		equ	%11111111		;SX18/20/28/48/52 port C DDIR value
RC_ST		equ	%11111111		;SX18/20/28/48/52 port C ST value
RC_LVL		equ	%00000000		;SX18/20/28/48/52 port C LVL value
RC_PLP		equ	%11111111		;SX18/20/28/48/52 port C PLP value

ENDIF

;-------------------------- MACRO DEFINITIONS --------------------------
		; increase a round-robin buffer pointer at the end of the memory range.
IncBufferPtr	MACRO register
		inc	register
		mov	w, #buffer_start
		snz
		mov	register, w
		setb	register.4
		ENDM

; These macros control whether our serial line is high-active
; or low-active.
Rs232Up		MACRO pin
			IFDEF UsingMAX232 THEN
				setb pin
			ELSE
				clrb pin
			ENDIF
		ENDM

Rs232Down	MACRO pin
			IFDEF UsingMAX232 THEN
				clrb pin
			ELSE
				setb pin
			ENDIF
		ENDM

; this macro is very clock-speed specific. For higher clockspeeds you may want to call nopdel.
Pause250ns	MACRO
		nop
		nop
		ENDM

; transfer the state of the rs232 input pin to the carry flag
Rs232PinToCarry MACRO pin
		sb	pin			;get current rx bit
		IFDEF UsingMAX232 THEN
	        	clc
		ELSE
			stc
		ENDIF
	        snb	pin			; 
		IFDEF UsingMAX232 THEN
			stc
		ELSE
	        	clc		
		ENDIF
		ENDM

		ORG 0
Interrupt
	;*********************************************************************************
	; Virtual Peripheral: Universal Asynchronous Receiver Transmitter (UART) 
	; These routines send and receive RS232 serial data, and are currently
	; configured (though modifications can be made) for the popular
	; "No parity-checking, 8 data bit, 1 stop bit" (N,8,1) data format.
	; RECEIVING: The rx_flag is set high whenever a valid byte of data has been
	; received and it is the calling routine's responsibility to reset this flag
	; once the incoming data has been collected.
	; TRANSMITTING: The transmit routine requires the data to be inverted
	; and loaded (tx_high+tx_low) register pair (with the inverted 8 data bits
	; stored in tx_high and tx_low bit 7 set high to act as a start bit). Then
	; the number of bits ready for transmission (10=1 start + 8 data + 1 stop)
	; must be loaded into the tx_count register. As soon as this latter is done,
	; the transmit routine immediately begins sending the data.
	; This routine has a varying execution rate and therefore should always be
	; placed after any timing-critical virtual peripherals such as timers,
	; adcs, pwms, etc.
	; Note: The transmit and receive routines are independent and either may be
	;	removed, if not needed, to reduce execution time and memory usage,
	;	as long as the initial "BANK serial" (common) instruction is kept.
	;
	;       Input variable(s) : tx_low (only high bit used), tx_high, tx_count
	;       Output variable(s) : rx_flag, rx_byte
	;       Variable(s) affected : tx_divide, rx_divide, rx_count
	;       Flag(s) affected : rx_flag
	;
	;*********************************************************************************
SerialVP
		bank	SERIAL			;switch to serial register bank
:transmit	decsz	tx_divide		;only execute the transmit routine
		jmp	:receive		; 
		mov	w,#InterruptsPerBit	;
		mov	tx_divide,w		; 
		test    tx_count		;are we sending?
		snz				; 
		jmp	:receive		;
		stc                             ;yes, ready stop bit
		rr      tx_high			; and shift to next bit
		rr      tx_low			; 
		dec     tx_count		;decrement bit counter
		snb	tx_low.6		;output next bit
		Rs232Up	SerialOut		; 
		sb	tx_low.6		; 
		Rs232Down SerialOut		; 
		
		; transfer input to carry flag.
:receive        Rs232PinToCarry SerialIn

		test    rx_count                ;currently receiving byte?
		sz				; 
		jmp	:rxbit                  ;if so, jump ahead
		mov     w,#9                    ;in case start, ready 9 bits
		sc				;skip ahead if not start bit
		mov     rx_count,w              ;it is, so renew bit count
		mov     w,#StartDelay		;ready 1.5 bit periods plus one
		mov     rx_divide,w		; 
:rxbit          decsz	rx_divide		;middle of next bit?
          	jmp	:rxdone			;
		mov	w,#InterruptsPerBit	;yes, ready 1 bit period
		mov	rx_divide,w		; 

		dec     rx_count                ;last bit?
		sz                              ;if not
		rr      rx_byte                 ; then save bit
		snz                             ;if so,
		jmp	handle_byte
:rxdone						; else, exit

		mov 	w, #RetiwValue
		retiw

handle_byte	mov	w, packet_state
		jmp 	pc+w
		jmp	:pk_preamble
		jmp 	:pk_address
		jmp	:pk_data
		jmp 	:pk_checksum1
		jmp	:pk_checksum2
		jmp	end_packet_handling; ps_blocked, we discard this byte.
		jmp	:pk_size
		;jmp	:pk_noise

:pk_noise	; we've received noise so far, a zero may mean start of preamble
		setb	led
		mov 	w, rx_byte	; this sets the zero flag if zero
		mov	w, #ps_preamble	; this does not affect flags
		snz	
		mov 	packet_state, w

		mov 	w, #RetiwValue
		retiw		

:pk_preamble	; we've received a zero before, if we get a zero again, stick
		; in this state. If we receive a $55, go to the next state
		; anything else->back to noise
		mov	w, rx_byte
		jz	end_packet_handling
		xor	w, #packet_end_preamble
		;sz
		mov	w, #ps_noise
		snz
		mov	w, #ps_address
		mov	packet_state, w

		mov 	w, #RetiwValue
		retiw		
				
:pk_address
		; we're expecting an address byte. See if the byte
		; matches our address and move to the next state (data)
		; if it does. If we receive anything else, we move to 
		; noise state again.
		mov	w, rx_byte
		xor	w, packet_address
		mov	w, #ps_noise
		snz
		mov	w, #ps_size
		mov	packet_state, w

		mov 	w, #RetiwValue
		retiw		

:pk_size	
		; We're expecting a size byte. 
		; the packet data size minus one will be in the lower nibble
		; so for a packet of one byte, we'll receive a zero, for a packet of 
		; sixteen bytes, we'll receive 0x0f (fifteen).
		; Fill packet_writecount with the actual expected byte count 
		; (the received value plus one).
		; note that the limit of packet sizes to 16 bytes is artificial, 
		; we could accept any size up to 256 by changing the 'and' instruction.
		; However, I'm reserving the first 4 bits of the address byte 
		; for other, future purposes (packet resend/repeat counter).
		and	rx_byte, #$0f
		inc	rx_byte
		mov	packet_writecount, rx_byte
			
		; now we're ready to collect data.
		mov	packet_state, #ps_data
		mov	buffer_writeahead, buffer_writeptr
		movb	buffer_writeahead_full, buffer_full_flag

		mov 	w, #RetiwValue
		retiw		

; data bytes will be written into the round-robin buffer. During receive, only the
; write-ahead pointer is updated.
; After successfully receiving
; a complete packet (all bytes received and checksum ok), the write pointer is updated
; with the value of the write-ahead pointer.
; As soon as the write-ahead pointer hits the read-pointer, the buffer_writeahead_full flag 
; will be set.
:pk_data	; when we're in this state, we gather databytes in the buffer
		; pointed to by packet_writeptr
		jb	buffer_writeahead_full, :pk_buffer_full

:pk_continue_data
		clrb	led	; switch the led on.
		mov	int_temp1, rx_byte
		mov 	fsr, buffer_writeahead
		mov	ind, int_temp1
		bank	SERIAL	; restore bank after FSR manipulation
		call	crc_update

		
		mov	w, #ps_checksum1
		dec	packet_writecount
		snz	
		mov 	packet_state, w
		
		IncBufferPtr buffer_writeahead
		mov 	w, buffer_writeahead
		xor	w, buffer_readptr
		
		snz	;signal that we cannot fit any more bytes
		setb	buffer_writeahead_full
		
		mov 	w, #RetiwValue
		retiw		

		; during the previous byte, the buffer became full
		; test if someone has read from the buffer since.
		; if not, we're going to discard the whole packet
:pk_buffer_full	
		clrb	buffer_writeahead_full
		mov	w, buffer_writeahead
		xor	w, buffer_readptr
		jnz	:pk_continue_data	; we can continue
		
		; we received one byte too many, discard packet and go to noise state
		mov	packet_state, #ps_noise
				
		mov 	w, #RetiwValue
		retiw		
		
:pk_checksum1
		; receive first checksum byte
		mov 	w, rx_byte
		call	crc_update
		mov 	packet_state, #ps_checksum2

		mov 	w, #RetiwValue
		retiw		

:pk_checksum2
		; receive second checksum byte, 
		mov 	w, rx_byte
		call	crc_update

		; next state will be ps_noise
		mov 	packet_state, #ps_noise

		setb	led	; switch led off

		; verify that the crc is zero.
		mov	w, buffer_writeahead
		test	crc_hi
		snz
		test	crc_lo
		snz
		mov	buffer_writeptr, w
		snz
		xor	w, buffer_readptr
		snz
		setb	buffer_full_flag
		
		clr	crc_hi	; reset checksum
		clr	crc_lo

end_packet_handling
		mov w, #RetiwValue
		retiw		



;************************************************************************
;
; Update crc checksum with new data in W. MSb goes first.
;
;************************************************************************
crc_temp EQU	int_temp1
count	EQU	int_temp2

;X^16+X^12+X^5+1   (CCITT polynomial)
crc_POLY_HIGH   EQU $10
crc_POLY_LOW    EQU $21

crc_update
        mov     crc_temp, w         ;save data in temp
        mov     w, #8           ;init counter
        mov     count, w        ;
crc_loop
        ;XOR the carry (MSb of crc) and the next bit of data (starting from
        ;MSb). If result is zero, don't invert any bits in the checksum.
        ;If result is one, invert the checksum bits as determined by the
        ;polynomial.

        mov     w, crc_temp
        and	w, #$80
        xor     crc_hi, w
	
        clc                     ;Rotate crc one place left and
        rl      crc_lo          ;move the MSb to carry
        rl      crc_hi          ;

        sc                      ;check the result
         jmp    crc_next        ;skip inversion if result is zero

        mov     w, #crc_POLY_HIGH       ;higher byte of polynomial
        xor     crc_hi, w
        mov     w, #crc_POLY_LOW        ;lower byte of polynomial
        xor     crc_lo, w
crc_next
        rl      crc_temp            ;prepare next bit
        decsz   count
        jmp    crc_loop
        ret
;************************************************************************



					
;------------------------ INITIALIZATION ROUTINE -----------------------
Initialize
		
		mov 	m, #ST_W			;point MODE to write ST register
		mov     !rb,#RB_ST            	;Setup RB Schmitt Trigger, 0 = enabled, 1 = disabled
		mov     !rc,#RC_ST            	;Setup RC Schmitt Trigger, 0 = enabled, 1 = disabled

		mov 	m, #LVL_W			;point MODE to write LVL register
		mov     !ra,#RA_LVL            	;Setup RA CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov     !rb,#RB_LVL            	;Setup RB CMOS or TTL levels, 0 = TTL, 1 = CMOS
		mov     !rc,#RC_LVL            	;Setup RC CMOS or TTL levels, 0 = TTL, 1 = CMOS

		mov 	m, #PLP_W
		mov 	!ra,#RA_PLP            	;Setup RA Weak Pull-up, 0 = enabled, 1 = disabled
		mov     !rb,#RB_PLP            	;Setup RB Weak Pull-up, 0 = enabled, 1 = disabled
		mov     !rc,#RC_PLP            	;Setup RC Weak Pull-up, 0 = enabled, 1 = disabled

		mov     ra,#RA_latch          	;Initialize RA data latch
		mov     rb,#RB_latch         	;Initialize RB data latch
		mov     rc,#RC_latch          	;Initialize RC data latch

		mov 	m, #DDIR_W			;point MODE to write DDIR register
		mov	!ra,#RA_DDIR		;Setup RA Direction register, 0 = output, 1 = input		
		mov	!rb,#RB_DDIR		;Setup RB Direction register, 0 = output, 1 = input
		mov	!rc,#RC_DDIR		;Setup RC Direction register, 0 = output, 1 = input

; zero all ram (SX28)
		clr	fsr			;reset all ram banks
:zero_ram	sb	fsr.4			;are we on low half of bank?
		setb	fsr.3			;If so, don't touch regs 0-7
		clr	ind			;clear using indirect addressing
		incsz	fsr			;repeat until done
		jmp	:zero_ram

		BANK	SERIAL
		mov 	w, #buffer_start
		mov	buffer_writeptr, w
		mov	buffer_readptr, w
;		setb	led			; switch led off

		mov	!option,#%10001000	; enable rtcc interrupt

;---------------------------- MAIN PROGRAM -----------------------------

Main		mov	lcd_control, #0
		call 	@WaitASec
		call	@WaitASec

		call 	@lcd_init
		mov 	W, #LCD_Clear		; Clear the screen
		call 	@lcd_write_command	

		bank    SENDTEXT		
		setb	output_dir.0 ; output to LCD
		mov	w, #(CopyrightMessage // 256)
		call 	@send_string
		
		call 	@get_byte
		mov	function_temp, w
		mov 	W, #LCD_Clear		; Clear the screen
		call 	@lcd_write_command
		mov	w, function_temp
		jmp 	EnterFirst	
					
MainLoop	
		
		call 	@get_byte	; from round-robin buffer
EnterFirst
		xor	w, #13		; char = 13?
		jz	CursorHome
		xor	w, #(13 ^ 12)	; char = 12?
		jz	ClearScreen
		xor	w, #(12 ^ 11)	; char = 11?
		jz	CursorGoto
		
		xor	w, #11		; restore w (original char)
		call 	@lcd_write_data
		jmp MainLoop	

CursorHome	mov 	W, #LCD_Cursor_Home ; cursor home
		call 	@lcd_write_command	
		jmp  MainLoop

ClearScreen	mov	w, #LCD_Clear
		call	@lcd_write_command
		jmp	MainLoop

CursorGoto	call	@get_byte
		and	w, #$7f
		or	w, #LCD_Set_DDRam
		call	@lcd_write_command
		jmp MainLoop

		ORG	$200

	;*********************************************************************************
	; Function: 			lcd_init
	; 
	; Inputs:			None
	;	
	; Outputs:			None
	;	
	; Registers affected:		W
	;
	; Functions Called:		lcd_wait_busy,  lcd_write_command
	;
	;*********************************************************************************
	;*************************************************************************
	; LCD initialization code.
	; This code should be called at the beginning of the program to 
	; initialize the LCD display.  It only needs to be called once.
	;*************************************************************************


lcd_init
		mov 	W,#0			; Delays for 5.1ms at 50MIPS
		call 	@delay
		mov 	W,#0			; Delays for 5.1ms at 50MIPS
		call 	@delay
	; First, set the data length, number of display lines, and character font.
	;------------------------------------------------------------------------------------------------------
	;	RS-RA2 R/!W-RA3 DB7-RB7	DB6-RB6	DB5-RB5	DB4-RB4	DB3-RB3	DB2-RB2	DB1-RB1	DB0-RB0	Execution Time
	;	0	0	0	0	1	DL	N	F	*	*	40us
	;------------------------------------------------------------------------------------------------------
	; DL--Interface Data Length  		0 = 4-bit interface		1 = 8-bit interface
	; N --Number of Display Lines		0 = 1 line			1 = 2 lines
	; F --Character Font			0 = 5*7 dots			1 = 5*10 dots

		call	@lcd_wait_busy		; wait until the LCD is done initializing.
		mov 	W, #LCD_Function | LCD_C; to be sure, duplicate command to change to 4 bits on lower 4.
		call	@lcd_write_half_command



		mov 	W, #LCD_Function | LCD_N		; 
		call 	@lcd_write_command 	; set for for 4 bits, 2 lines, and 5*7 dots


	; Next, turn the display on, turn the cursor on, and turn cursor blink on (so we know LCD is alive)
	;------------------------------------------------------------------------------------------------------
	;	RS-RA2 R/!W-RA3 DB7-RB7	DB6-RB6	DB5-RB5	DB4-RB4	DB3-RB3	DB2-RB2	DB1-RB1	DB0-RB0	Execution Time
	;	0	0	0	0	0	0	1	D	C	B	40us
	;------------------------------------------------------------------------------------------------------
	; D --Display ON/OFF control		0 = Display OFF			1 = Display ON
	; C --Cursor ON/OFF control		0 = Cursor OFF			1 = Cursor ON
	; B --Blink ON/OFF control		0 = Blink OFF			1 = Blink ON


		mov 	W, #LCD_Display_Control | LCD_D; | LCD_C | LCD_B 

		call 	@lcd_write_command 	; turn display on, cursor on, and blink on..


	; Next, set display so that the cursor moves as characters are entered.
	;------------------------------------------------------------------------------------------------------
	;	RS-RA2 R/!W-RA3 DB7-RB7	DB6-RB6	DB5-RB5	DB4-RB4	DB3-RB3	DB2-RB2	DB1-RB1	DB0-RB0	Execution Time
	;	0	0	0	0	0	1	S/C	R/L	*	*	40us
	;------------------------------------------------------------------------------------------------------
	; S/C--Cursor move/Display Shift	0 = Cursor Move			1 = Shift Display 
	; R/L--Shift Direction			0 = Shift left			1 = Shift right

		mov 	W, #LCD_Cursor
		call 	@lcd_write_command 	; set for cursor move and display shift.
	
	; Next, set entry mode (cursor move direction, shift or no shift).
	;------------------------------------------------------------------------------------------------------
	;	RS-RA2 R/!W-RA3 DB7-RB7	DB6-RB6	DB5-RB5	DB4-RB4	DB3-RB3	DB2-RB2	DB1-RB1	DB0-RB0	Execution Time
	;	0	0	0	0	0	0	0	1	I/D	S	40us ~ 1.64ms
	;------------------------------------------------------------------------------------------------------
	; I/D--Increment/Decrement address	0 = Decrement Cursor Address	1 = Increment Cursor Address 
	; S  --Display shift			0 = No shift			1 = Shift

		mov 	W, #LCD_Entry_Mode | LCD_ID
		call 	@lcd_write_command 	; set for incrementing address and no shift..
	

		retp	; Return fron lcd_init

	;*********************************************************************************
	; Function:			lcd_write_command 
	; 
	; Inputs:			Command to write in W
	;	
	; Outputs:			None
	;	
	; Registers affected:		W ,  lcd_temp , lcd_count
	;
	; Functions Called:		nopdel,    NOTE: shared body with lcd_write_data  
	;
	;*********************************************************************************
lcd_write_half_command
		clrb 	lcd_RS		; Drive RS low so LCD knows to write COMMAND.
		bank	LCD_BANK		; Switch banks
		mov	lcd_temp,W	
		mov	lcd_count,#1
		jmp	lcd_start_write

lcd_write_command
	;------------------------------------------------------------------------------------------------------
	; This function writes the command in W to the LCD display, using the 8-bit interface.  The procedure is:
	; 1.  Clear RS
	; 2.  Set up R/!W
	; 3.  Write the data to the port
	;------------------------------------------------------------------------------------------------------
		clrb 	lcd_RS		; Drive RS low so LCD knows to write COMMAND.
		jmp 	lcd_write	; goto WRITE code

	;*********************************************************************************
	; Function: 			lcd_write_data
	; 
	; Inputs:			Data byte to write in W
	;	
	; Outputs:			None
	;	
	; Registers affected:		W,  lcd_temp , lcd_count
	;
	; Functions Called:		nopdel, NOTE BODY shared with lcd_write_command
	;
	;*********************************************************************************
send_lcd_byte
lcd_write_data
	;------------------------------------------------------------------------------------------------------
	; This function writes the data in W to the LCD display, using the 8-bit interface.
	; 1.  Set RS
	; 2.  Set up R/!W
	; 3.  Write the data to the port
	;------------------------------------------------------------------------------------------------------

		setb 	lcd_RS		; Drive RS high so LCD knows to write DATA.

lcd_write	
		bank	LCD_BANK		; Switch banks
		mov	lcd_temp,W	
		mov	lcd_count,#2 
lcd_start_write
		mov 	m, #DDIR_W
		mov	w,#00h
		mov	!lcd_data,w	; Switch the data pins to outputs
		clrb 	lcd_RW 		; Drive R/!W low so LCD knows to WRITE.

lcd_write_loop
		and	lcd_data,#LCD_UNUSED_MASK; don't destroy the data in the other 4 latches
IFDEF LCD_LOW_NIBBLE
		mov	W, <>lcd_temp
ELSE
		mov	W,lcd_temp
ENDIF
		and	W,#LCD_DATA_MASK
		or	lcd_data,W	; Write the data in W to the port latches.
		Pause250ns
		setb 	lcd_E		; Pulse LCD's enable pin.
		Pause250ns
		clrb 	lcd_E		; Force LCD to latch the data present on the data bus.
		Pause250ns
		swap	lcd_temp
		decsz	lcd_count
		jmp	lcd_write_loop

		; notice we're falling from lcd_write into lcd_wait_busy
;		retp			; Return from lcd_write_command and lcd_write_data

	;*********************************************************************************
	; Function: 			lcd_wait_busy
	; 
	; Inputs:			None
	;	
	; Outputs:			None
	;	
	; Registers affected:		W
	;
	; Functions Called:		nopdel
	;
	;*********************************************************************************
lcd_wait_busy

	; waits until the LCD is ready to accept a command.
	;------------------------------------------------------------------------------------------------------
	;	RS-RA2 R/!W-RA3 DB7-RB7	DB6-RB6	DB5-RB5	DB4-RB4	DB3-RB3	DB2-RB2	DB1-RB1	DB0-RB0	Execution Time
	;	0	1	BF	* ------------------DDRAM Address-------------- *	1us
	;------------------------------------------------------------------------------------------------------

		or	W,#LCD_DATA_MASK		; (1) 
		mov 	m, #DDIR_W
		mov	!lcd_data,W
		clrb 	lcd_RS		; clear RS for instruction
		setb 	lcd_RW		; set for READ.	
		Pause250ns
		setb 	lcd_E		; set enable high to read busy flag
		Pause250ns
		mov	W,lcd_data	; move the contents of the port to the W register
		clrb 	lcd_E		; clear LCD enable
		Pause250ns
		setb 	lcd_E		; set enable high to read busy flag
		Pause250ns
		clrb 	lcd_E		; clear LCD enable
		Pause250ns
		Pause250ns
		and 	W, #LCD_BF	; test W for zero (Z is cleared if LCD is busy)
		sb	Z		; Busy Flag is MSB of data bus
		jmp	lcd_wait_busy	; loop through again if the LCD is still busy
		setb	lcd_RW
		retp

	;*********************************************************************************
	; Function: 			nopdel
	; 
	; Inputs:			None
	;	
	; Outputs:			None
	;	
	; Registers affected:		None
	;
	; Functions Called:		None
	;
	;*********************************************************************************
nopdel	;	returns to main program in 11 cycles (11us@1MIPS) from call
		nop
		nop 
		nop
		nop
		nop
		nop
		nop
		nop
		retp		; return from nopdel

	;*********************************************************************************
	; Function: 			delay
	; 
	; Inputs:			value passed in W
	;	
	; Outputs:			None
	;	
	; Registers affected:		W, dlycnt1, dlycnt2
	;
	; Functions Called:		None
	;
	;*********************************************************************************
delay		; (delays for [((w-1) * 1ms )] at 1MIPS, or [((w-1) * 20us)] at 50MIPS ... 0<=W<=255) 
	;****************************************************************************************************** 
	; This function delays for ((W-1)*20us), plus/minus a few ns 
	;****************************************************************************************************** 
IFNDEF SIMULATION
		bank	LCD_BANK
		mov 	dlycnt1,W 
 
delay1 
		decsz 	dlycnt1; 
		jmp 	loop1; 
		retp	; return from delay	 
 
loop1 
		mov 	w,#166; 
		mov 	dlycnt2,W; 

loop; 
		nop 
		nop 
		nop 
		decsz 	dlycnt2; 
		jmp 	loop; 
		jmp 	delay1; 
ENDIF
		retp

WaitASec	
cnt0		EQU function_temp
cnt1		EQU function_temp2
cnt2		EQU function_temp3
IFNDEF SIMULATION
		clr cnt0
		clr cnt1
		mov cnt2, #4
:loop		djnz cnt0, :loop
		djnz cnt1, :loop
		djnz cnt2, :loop
ENDIF
		retp
						
		ORG	$400
;*****************************************************************************************
; UART Subroutines
;*****************************************************************************************

	;*********************************************************************************
	; Function: get_byte
	; Get byte via serial port and echo it back to the serial port
	; INPUTS:
	;	-NONE
	; OUTPUTS:
	;	-received byte in rx_byte
	;*********************************************************************************
xget_byte	bank 	SERIAL
		sb	rx_flag			;wait till byte is received
		jmp	xget_byte
		clrb    rx_flag			;reset the receive flag
		mov     w,rx_byte		;store byte (copy using W)
		;mov     byte,w			
		retp

	;*********************************************************************************
	; get one byte from the round-robin buffer, block if the buffer is empty.
	;*********************************************************************************
get_byte	bank	SERIAL

:ready_loop	; wait until there's something in the round-robin buffer

		; if buffer_readptr == buffer_writeptr then either the buffer is completely
		; empty, or the buffer is completely full. If full, the buffer_full_flag will
		; also be set.
		mov	w, buffer_readptr
		xor	w, buffer_writeptr
		jnz	:byte_available
		jnb	buffer_full_flag, :ready_loop

:byte_available
		clrb	buffer_full_flag
		; the reason why I can clear the buffer_full_flag at this point is a bit murky:
		; buffer_full_flag is copied by the ISR when a new packet is started to some
		; internal flag. Resetting this flag here can only safely be done when there
		; is at most 1 interrupt between this point and the actual increase of buffer_readptr,
		; otherwise I should wait until after the increase of buffer_readptr.
		mov	fsr, buffer_readptr
		mov	w, indf
		bank	SERIAL			; restore bank after fsr manipulation
		
		mov	function_temp, w
		
		IncBufferPtr buffer_readptr

		mov	w, function_temp
		retp

	;*********************************************************************************
	; send a character to either the serial line or the lcd display, depending on 
	; send_output.
	;*********************************************************************************
send_digit	or 	w, #'0'
send_character  ; bank    SENDTEXT ; not needed, already in SENDTEXT bank when reaching this
		jb	output_dir.0, @send_lcd_byte ; the skip will actually skip the page as well!
				
	;*********************************************************************************
	; Function: send_byte
	; Send byte via serial port
	; INPUTS:
	;	w 	-	The byte to be sent via RS-232
	; OUTPUTS:
	; 	outputs the byte via RS-232
	;*********************************************************************************
send_byte    	bank SERIAL

:wait        	test    tx_count                ;wait for not busy
		sz
		jmp	:wait                   ;

		mov     tx_high,w               ; store data byte
		clrb    tx_low.7                ; set up start bit
		mov     w,#10			;1 start + 8 data + 1 stop bit
		mov     tx_count,w
		retp                            ;leave and fix page bits

	;*********************************************************************************
	; Function: send_string
	; Send string pointed to by address in W register
	; INPUTS:
	;	w	-	The address of a null-terminated string in program
	;			memory
	; OUTPUTS:
	; 	outputs the string via RS-232 or LCD (see send_character)
	;*********************************************************************************
send_string	bank	SENDTEXT
 		mov     string,w                ;store string address
:loop        	mov     w,string                ;read next string character
		mov     m,#(StringPage>>8)      ;with indirect addressing 
		iread                           ;using the mode register
		test    w                       ;are we at the last char?
		snz                             ;if not=0, skip ahead
		jmp	:exit			;yes, leave & fix page bits
		call    send_character          ;not 0, so send character
		bank    SENDTEXT
		inc     string                  ;point to next character
		jmp     :loop                   ;loop until done
:exit		mov     m,#$0f                   ;reset the mode register
		retp

;*****************************************************************************************
;		String constants
;*****************************************************************************************
		org $500
StringPage	EQU $
CopyrightMessage
		DW 'Wireless Display 0.2'
		DW 'Danny Havenith      '
		DW 'Copyright (c) 2009  ',0

	
