;***********************************************
;*** 8 bit LCD interface for the AVR
;*** Version 1.0
;*** Date 25/5/1999
;*** Author : Stelios Koroneos - 
;*** E-mail k-tel@geocities.com
;*** Web page : http://come.to/Stelios_Cellar
;*********************************************
;*** This is a small program implementing an 8 bit
;*** LCD interface for the Avr 90s2313.
;*** It works on any 16x2 LCD that is based on the Hitachi
;*** HD44780 lcd controler and can be easily modified to work
;*** with other LCD's
;***
;*** Connections
;*** LCD CONTROLS
;*** PortB D0----> Lcd D0
;*** PortB D1----> Lcd D1
;*** PortB D2----> Lcd D2
;*** PortB D3----> Lcd D3
;*** PortB D4----> Lcd D4
;*** PortB D5----> Lcd D5
;*** PortB D6----> Lcd D6
;*** PortB D7----> Lcd D7

;*** PortD D4----> Lcd RS
;*** PortD D5----> Lcd WR
;*** PortD D6----> Lcd E

;*** SERIAL CONNECTIONS
;*** PortD D0----> Serial receive (RX)
;*** PortD D1----> Serial transmit (TX)

;*** BUTTONS
;*** PortD D2----> Button #1
;*** PortD D3----> Button #2

;*** A number of routines and macros are implemented which
;*** can be easily re-used
;*** LCD_Init - Initilize the LCD and set cursor at the beggining of first line 
;*** LCD_Command - Send a byte command to the LCD
;*** LCD_Write - Write a char at current possition of the cursor
;*** LCD_GotoXY - Move cursor to a specific position
;*** LCD_EPulse - Togle the E line High and then Low
;*** Delay40us - Delay 40 us 
;*** Delay100us - Delay 100 us
;*** Delay4ms - Delay 4ms
;*** NOTE : ALL DELAYS ARE CALCULATED FOR A 4MhZ CRYSTAL

;*** MACROS
;*** LCD_CLS - Clear LCD move cursor at beginning of first line
;*** LCD_Home - Move cursor to left edge of the display
;*** LCD_ON - Turn display on
;*** LCD_OFF - Turn display off
;*** LCD_CursorOn - Cursor will be displayed
;*** LCD_CursorOFF - No cursor will be displayed
;*** LCD_BlinkON - The chararcter at the cursor position will blink
;*** LCD_BlinkOFF - The chararcter at the cursor position will not blink
;*** LCD_ShiftCursorL - Shift cursor one position to the left
;*** LCD_ShiftCursorR - Shift cursor one position to the right
;*** LCD_ShiftDisplayL - Shift BOTH LINES 1 char to the Left
;*** LCD_ShiftDisplayR - Shift BOTH LINES 1 char to the Right

.nolist                       ;Suppress listing of include file

.include "2313def.inc"        ;Set 2313's data for the compiler

         .list



;***** Global register variables

.def     wreg =R16            ;General use working register

.def     wreg2 =R17           ;Genral use register 2
.def     CommandReg=R23
.def     DataReg=R24


;EEPROM related variables

.def     EEdrd =r0            ;result data byte
.def     EEard =r16           ;address to read from
.def     EErtmp =r0           ;temporary storage of address
.def     EEdrd_s =r1          ;result data byte

.def     Temp=r16
.def     Txbyte=r17

.def     LCD_XPos=r25
.def     LCD_Ypos=r26

;***** Other equates

;PortB D0-D7 goes to D0-D7 in the LCD
;PortD D4 is RS
;PortD D5 id RW
;PortD D6 is E 
.equ     lcdrs =PD4           ;LCD rs pin connected to PD4

.equ     lcdrw =PD5           ;LCD r/w pin connected to PD5

.equ     lcde =PD6            ;LCD e pin connected to PD6


.equ     FirstLine_Add =0b10000000 ;Base address of first line for 2x16 lcd
.equ     SecondLine_Add=0b11000000 ;Base address of second line

;*** Baud rates
;*** All baud rates are for a 4Mhz crystal 
;*** Check 2313's data sheet (table 14) to change to other crystals
.equ     B2400 =103
.equ     B4800 =51
.equ     B9600 =25
.equ     B19200 =12

.equ     LineLength=16
.equ     NoOfLines=2

.Macro   GOTO                 ;GOTO macro
;**********************************
;Accepts one parameter which is the address to jump
;Executes and inderect jump in order to support
;all AVR's, since 90s1200/2313 do not support
;direct jump

;The Z register points to the address


         ldi ZL,Low(@0 )      ;Load the low byte of the address
         ldi ZH,High(@0)      ;Load the high byte
         ijmp                 ;Jump

         .Endmacro

.macro   LCD_Cls
         ldi CommandReg,0b00000001
         rcall LCD_Command 
         rcall delay4ms
         ldi LCD_Xpos,0
         ldi LCD_Ypos,1 
         .endmacro

.macro   LCD_Home
         ldi CommandReg,0b00000010
         rcall LCD_Command 
         rcall delay4ms
         ldi LCD_Xpos,0
         ldi LCD_Ypos,1
         .endmacro

.macro   LCD_On
         ldi CommandReg,0b00001100
         rcall LCD_Command 
         rcall delay40us
         .endmacro

.macro   LCD_Off
         ldi CommandReg,0b00001000
         rcall LCD_Command 
         rcall delay40us
         .endmacro

.macro   LCD_CursorOn
         ldi CommandReg,0b00001110
         rcall LCD_Command 
         rcall delay40us
         .endmacro

.macro   LCD_CursorOff
         ldi CommandReg,0b00001100
         rcall LCD_Command 
         rcall delay40us
         .endmacro
.macro   LCD_BlinkOn
         ldi CommandReg,0b00001111
         rcall LCD_Command 
         rcall delay40us
         .endmacro
.macro   LCD_BlinkOff
         ldi CommandReg,0b00001110
         rcall LCD_Command 
         rcall delay40us
         .endmacro
.macro   LCD_ShiftCursorL
         ldi CommandReg,0b00010000
         rcall LCD_Command 
         rcall delay40us 
         .endmacro
.macro   LCD_ShiftCursorR
         ldi CommandReg,0b00010100
         rcall LCD_Command 
         rcall delay40us 
         .endmacro

.macro   LCD_ShiftDisplayL
         ldi CommandReg,0b00011000
         rcall LCD_Command 
         rcall delay40us 
         .endmacro
.macro   LCD_ShiftDisplayR
         ldi CommandReg,0b00011100
         rcall LCD_Command 
         rcall delay40us 
         .endmacro



         .cseg



; Interrupt vectors



.org     0

         rjmp reset           ;Reset Vector

         reti

         reti

         reti

         reti

         reti

         reti

         reti

         reti

         reti

         reti





; Main program entry point on reset



reset:   

         ldi wreg,RAMEND      ;Init Stack Pointer

         out SPL,wreg


;*************************************************

         ldi wreg,0x00        ;PortB Low

         out PORTB,wreg

         ldi wreg,0xFF        ;Make port B pin all outputs

         out DDRB,wreg

         ldi wreg,0x00        ;PortD Low

         out PORTD,wreg
         ldi wreg,0xFF        ;Make port D pin all outputs

         out DDRD,wreg


         rcall Lcd_Init       ;Init the LCD

;Print Init message

         ldi Wreg,InitString1 ;Get the address of the first line to display 
         out EEAR,Wreg        ;Move it to the Eeprom register
         rcall Display_Init_Message ;Show the message

         ldi Wreg,1           ;Move cursor to 2nd line
         ldi Wreg2,2
         rcall LCD_Gotoxy

         ldi Wreg,InitString2
         out EEAR,Wreg        ;
         rcall Display_Init_Message

         rcall LongDelay      ; Wait 1 sec
         rcall LongDelay      ; Wait 1 sec

         Lcd_CLS              ;Clear the display



;Init the UART
Uart_Init:
         ldi Wreg2,Baud       ;Get the baud rate from Eeprom
         rcall EERead_seq     ;get EEPROM data
         mov Temp,EEdrd_s

         out UBRR,Temp

         ldi Temp,0b00011000
         out UCR,Temp
Uart_loop:
         rcall Uart_GetChar   ;Wait till we receive a character

         cpi TxByte,1         ;Check for a Cursor Home (CTLR-A ASCII 1)
         brne Check_HideCursor
         LCD_Home             ;Call the macro 
         goto Uart_loop

Check_HideCursor:
         cpi txbyte,4         ;Check for Hide Cursor (CTRL-D ASCII 4)
         brne Check_UnderlineCursor
         LCD_CursorOff
         goto Uart_loop 

Check_UnderLineCursor:
         cpi txbyte,5         ;Check for Underline Cursor (CTRL-E ASCII 5)
         brne Check_BlinkingBlockCursor
         LCD_CursorOn         ;Turn cursor ON
         goto Uart_loop 

Check_BlinkingBlockCursor:
         cpi txbyte,6         ;Check for Blinking Block Cursor (CTRL-F ASCII 6)
         brne Check_Bell
         LCD_BlinkOn          ;Make the cursor blink
         goto Uart_loop 

Check_Bell:
         cpi txbyte,7         ;Check for a Bell (CTRL-G ASCII 7)
         brne Check_BackSpace
         goto Uart_loop       ;Do nothing as we do not have an piezo buzzer

Check_BackSpace:
         cpi txbyte,8         ;Check for BackSpace (CTRL-H ASCII 8)
         brne Check_VerticalTab

         cpi LCD_Xpos,0
         brne Dec_Xpos
         ldi Datareg,$20 
         rcall LCD_Write
         dec LCD_XPos
         mov wreg,LCD_Xpos
         mov wreg2,LCD_Ypos
         rcall LCD_GotoXY 
         goto Uart_loop
Dec_Xpos:
         dec Lcd_Xpos
         mov Wreg,LCD_Xpos
         mov Wreg2,LCD_Ypos
         rcall LCD_GotoXY
         ldi DataReg,$20
         rcall LCD_Write
         dec Lcd_Xpos
         mov Wreg,LCD_Xpos
         mov Wreg2,LCD_Ypos
         rcall LCD_GotoXY
         goto Uart_Loop


Check_VerticalTab:            ;Check for Vertical Tab (CTRL-K ASCII 11)
         cpi Txbyte,11
         brne Check_ClearScreen
         cpi LCD_Ypos,1 
         brne Cont_VerticalTab
         goto Uart_loop

Cont_VerticalTab:
         dec LCD_Ypos
         mov wreg,LCD_Xpos
         mov Wreg2,LCD_Ypos
         rcall LCD_GotoXY
         goto Uart_Loop

Check_ClearScreen:            ;Check for CLS (CTRL-L ASCII 12)
         cpi txbyte,12
         brne Check_CarriageReturn
         LCD_Cls 
         goto Uart_Loop

Check_CarriageReturn:         ;Check for CR (CTRL-M ASCII 13)
         cpi txbyte,13
         brne WriteLCD
         cpi LCD_Ypos,1
         breq CarriageReturn_Cont
         Goto Uart_Loop

CarriageReturn_Cont:

         inc LCD_Ypos
         ldi LCD_Xpos,0
         mov wreg,LCD_Xpos
         mov wreg2,LCD_Ypos
         rcall LCD_GOTOXY
         goto UART_Loop



WriteLCD:
         mov Datareg,Txbyte
         rcall LCD_Write 
;Theses were used for debug perposes
; rcall Uart_Putchar ;echoing back the received char and "$" 
; ldi txbyte,$24
; rcall UART_Putchar

Noexit:  
         goto uart_loop


;*********************************************************************
;****
;****
;*********************************************************************
Lcd_Init:

         rcall LongDelay      ; Wait approx 1 sec
         rcall delay4ms       ;
         rcall delay4ms       ; 
         rcall delay4ms       ; 
         rcall delay4ms       ; 
         rcall delay4ms       ; ;Wait 15ms


         cbi PortD,lcdrs      ;Clear RS
         cbi PortD,lcdrw      ;Clear RW
         ldi Wreg,$38
         out PortB,Wreg 
         rcall LCD_EPulse

         rcall Delay4ms       ; ;Wait 4ms
         out PortB,Wreg
         rcall LCD_EPulse

         Rcall Delay100us     ;Wait 100us
         Out PortB,Wreg
         Rcall LCD_EPulse

         Rcall Delay100us     ;Wait 100us
         Ldi Wreg,$04         ;entry mode set
         Out PortB,Wreg
         Rcall LCD_EPulse

         Rcall Delay4ms       ; ;Wait 4ms
         Ldi Wreg,$0e         ;display on
         Out PortB,Wreg
         Rcall LCD_EPulse

         Rcall Delay4ms       ; ;Wait 4ms
         Ldi Wreg,$01         ;Clear Display
         Out PortB,Wreg
         Rcall LCD_EPulse

         Rcall Delay4ms       ; ;Wait 4ms

         Ldi LCD_Xpos,0       ;Init the positioning values 
         Ldi LCD_Ypos,1
         Ret

;*********************************************************************
;****
;****
;*********************************************************************
LCD_Command:
         Cbi PortD,lcdrs
         Cbi PortD,lcdrw
         Out PortB,CommandReg
         rcall LCD_EPULSE
         Rcall Delay40us
         ret 


;*********************************************************************
;****
;****
;*********************************************************************
LCD_Write:
         cpi LCD_Xpos,LineLength ;Check if we are at the last Xpos
         brne LCD_WriteQuick  ;if not display the character

         ldi Wreg2,Line_Wrap  ;Check if Line wrap is on or off
         rcall EERead_seq     ;get the data from EEPROM 
         mov Temp,EEdrd_s

         cpi Temp,0
         breq LCD_WriteQuick  ;No line wrap

         ldi wreg,0           ;Point at the first row

         cpi LCD_Ypos,NoOfLines ;check if we are at the last line of the lcd
         brne Lcd_Write_NextLine ;If we are not move to the next line
         ldi wreg2,1 
         rcall LCD_Gotoxy
         rjmp LCD_WriteQuick 

Lcd_Write_NextLine:
         inc Lcd_Ypos
         mov wreg2,Lcd_Ypos
         rcall LCD_Gotoxy 



LCD_WriteQuick:
         Sbi PortD,lcdrs
         Out PortB,DataReg
         rcall LCD_EPULSE
         Rcall Delay40us
         inc LCD_Xpos
Lcd_Write_Exit:
         Ret 

;*******************************************
;LCD_GOTOXY
; Wreg contains the column (x)
; Wreg2 contains the Line (y)
;*******************************************
LCD_GotoXY:

         ldi CommandReg,FirstLine_Add
         cpi Wreg2,1          ;Check to see which line
         Breq Calc_Xpos
         ldi CommandReg,SecondLIne_Add
Calc_Xpos:
         or CommandReg,Wreg
         Rcall LCD_Command    ;Set DDRam Add
         Rcall Delay40us
         mov LCD_Xpos,Wreg    ;update the x and y possition
         mov LCD_Ypos,Wreg2   ;of the cursor
         Ret


;*********************************************************************
;****
;****
;********************************************************************* 
LCD_EPulse:
         Sbi PortD,lcde       ;E high
         nop
         nop
         nop
         nop
         Cbi PortD,lcde       ;E Low
         Ret


;***************************************************************************
;* 
;* EERead_seq
;*
;* This subroutine increments the address stored in EEAR and reads the 
;* EEPROM into the register variable "EEdrd_s".
;*
;*
;***************************************************************************


EERead_seq:
         in EErtmp,EEAR       ;get address
; inc EErtmp ;increment address
         out EEAR,EErtmp      ;output address
         sbi EECR,EERE        ;set EEPROM Read strobe
;This instruction takes 4 clock cycles since
;it halts the CPU for two clock cycles
         sbi EECR,EERE        ;set EEPROM Read strobe 2nd time
;This instruction takes 4 clock cycles since
;it halts the CPU for two clock cycles
         in EEdrd_s,EEDR      ;get data
         in EErtmp,EEAR       ;get address
         inc EErtmp           ;increment address
         out EEAR,EErtmp      ;store it 
         ret


;*********************************************************************
;****
;****
;*********************************************************************
Display_Init_Message:
         rcall EERead_seq     ;get EEPROM data
         mov Wreg2,EEdrd_s
         cpi Wreg2,0          ;Check if we have reached the End of string
         breq Display_Init_Message_End
         mov DataReg,EEdrd_s
         Rcall LCD_Write
         Rjmp Display_Init_Message 

Display_Init_Message_End:

         Ret


         ****************************************************
;Send a char using the UART port
;TxByte contains the char
;****************************************************
UART_Putchar:

UART_PutChar_Loop:
         sbis Usr,UDRE        ;wait till the last byte was transmited
         rjmp UART_Putchar_Loop



         out udr,Txbyte       ;Now its ok to send

UART_PutChar_End:Ret


         ****************************************************
;Get a char using the UART port
;TxByte contains the char
;****************************************************
UART_Getchar:

UART_GetChar_Loop:
         sbis Usr,RXC         ;wait till a character arrives
         rjmp UART_Getchar_Loop

         in Txbyte,UDR        ;Now its ok to read

UART_GetChar_End:Ret


;*********************************************************************
;****
;****
;*********************************************************************
Delay40us:
         ldi r21,$40
Loop3:   dec r21
         brne loop3 
         ret
;*********************************************************************
;****
;****
;*********************************************************************
Delay100us:
         ldi r21,$88
Loop:    dec r21
         brne loop 
         ret
;*********************************************************************
;****
;****
;*********************************************************************
Delay4ms:
         ldi r22,$1c
Loop1:   ldi r21,$00
Loop2:   dec r21
         brne loop2 
         dec r22
         brne loop1
         ret
;*********************************************************************
;****
;****
;*********************************************************************
LongDelay:
         ldi wreg,255         ; Delay for about 1 sec for the LCD to start
Longdelay_Loop:
         rcall delay4ms
         dec wreg
         brne LongDelay_Loop
         Ret



;*********************************************************************
;**** EEPROM Data
;****
;*********************************************************************
         .eseg
EmptyPos:.db 00               ;leave eeprom location 00 empty due to bug
;in AVR that randomly erases the contens
InitString1:.db "Serial LCD V 1.0",00 ;What the LCD will display on power up
InitString2:.db "(C) S.Koroneos",00 ;Strings are 0 ended
Baud:    .db B4800            ;Initial baud rate
Line_Wrap:.db 1               ;0 = No line wrap 1= Wrap to second line when reach last xpos
