; File Name :'LCD.asm" ; Title :LCD routines ; Date : ; Version : ; Support telephone :765 287 1987 David B. VanHorn ; Support fax :765 287 1989 ; Support Email :dvanhorn@cedar.net ; Target MCU :AT90S8515 ; ; DESCRIPTION ; Generic single or dual line display, 8-40 chars, in 4 bit mode ; ;***************************************************************************; ; M O D I F I C A T I O N H I S T O R Y ; ; ; rev. date who why ; ---- -------- --- ------------------------------------------ ; 0.01 98.11.27 dvh Creation, working ; ;*************************************************************************** ;Pin ;number Symbol I/O Function 1 Vss - Power supply (GND) 2 Vcc - Power supply (+5V) 3 Vee - Contrast adjust 4 RS I 0 = Instruction input 1 = Data input 5 R/W I 0 = Write to LCD module 1 = Read from LCD module 6 E I Enable signal 7 DB0 I/O Data bus line 0 (LSB) 8 DB1 I/O Data bus line 1 9 DB2 I/O Data bus line 2 10 DB3 I/O Data bus line 3 11 DB4 I/O Data bus line 4 12 DB5 I/O Data bus line 5 13 DB6 I/O Data bus line 6 14 DB7 I/O Data bus line 7 (MSB) ;***************************************************************** ;HD44780 instruction set ; ;Instruction Code Description ; ; R R D D D D D D D D ; S W 7 6 5 4 3 2 1 0 ;Clear 0 0 0 0 0 0 0 0 0 1 Clears display and returns cursor to the home position (address 0) 1.64mS ; ;Home 0 0 0 0 0 0 0 0 1 X Returns cursor to home position (address 0). ; Also returns display being shifted the original position. ; DDRAM contents remains unchanged. 1.64mS ;Entry mode set ; 0 0 0 0 0 0 0 1 I/D S Sets cursor move direction (I/D), specifies to shift the display (S). ; These operations are performed during data read/write. 40uS ;Display On/Off control ; 0 0 0 0 0 0 1 D C B Sets On/Off of all display (D), cursor On/Off (C) and blink of cursor ; position character (B). 40uS ;Cursor/display shift ; 0 0 0 0 0 1 S/L R/L X X Sets cursor-move or display-shift (S/C), shift direction (R/L). ; DDRAM contents remains unchanged. 40uS ;Function set ; 0 0 0 0 1 DL N F X X Sets interface data length (DL), number of display line (N) ; and character font(F). 40uS ;Set CGRAM address ; 0 0 0 1 CGRAM address Sets the CGRAM address. ; CGRAM data is sent and received after this setting. 40uS ;Set DDRAM address ; 0 0 1 DDRAM address Sets the DDRAM address. ; DDRAM data is sent and received after this setting. 40uS 40uS ;Read busy-flag and address counter ; 0 1 BF DDRAM address Reads Busy-flag (BF) indicating internal operation ; is being performed and reads address counter contents. 0uS ;Write to CGRAM or DDRAM ; 1 0 write data Writes data to CGRAM or DDRAM. 40uS ;Read from CGRAM or DDRAM ; 1 1 read data Reads data from CGRAM or DDRAM. 40uS ; ;******************************************************************************** ; ; Bit name ; I/D 0 = Decrement cursor position ; 1 = Increment cursor position ; ; S 0 = No display shift ; 1 = Display shift ; ; D 0 = Display off ; 1 = Display on ; ; C 0 = Cursor off ; 1 = Cursor on ; ; B 0 = Cursor blink off ; 1 = Cursor blink on ; ; S/C 0 = Move cursor ; 1 = Shift display ; ; R/L 0 = Shift left ; 1 = Shift right ; ; DL 0 = 4-bit interface ; 1 = 8-bit interface ; ; N 0 = 1/8 or 1/11 Duty (1 line) ; 1 = 1/16 Duty (2 lines) ; ; F 0 = 5x7 dots ; 1 = 5x10 dots ; ; BF 0 = Can accept instruction ; 1 = Internal operation in progress ; ;******************************************************** ; ;Shown after reset (with N=0). ; ;Disp Positions DDRAM addresses ; 1*8 00..07 00h..07h ; 1*16 00..15 00h..0Fh ; 1*20 00..19 00h..13h ; 1*24 00..23 00h..17h ; 1*32 00..31 00h..1Fh ; 1*40 00..39 00h..27h ; ;************************************************* ; ;Shown after reset (with N=1). ; ;Disp Positions DDRAM addresses ;2*16 00..15 00h..0Fh + 40h..4Fh ;2*20 00..19 00h..13h + 40h..53h ;2*24 00..23 00h..17h + 40h..57h ;2*32 00..31 00h..1Fh + 40h..5Fh ;2*40 00..39 00h..27h + 40h..67h ;***************************************************** ; ;Shown after reset (with N=1). ; ;Disp Positions DDRAM addresses ;4*16 00..15 00h..0Fh + 40h..4Fh + 14h..23h + 54h..63h ;4*20 00..19 00h..13h + 40h..53h + 14h..27h + 54h..67h ' ;************************************************************ ; ;THINGS YOU NEED TO ALLOCATE ;.def TEMP =R16 ;Just space to put stuff ;.def TEMP2 =R17 ;Me too ;.def BITFLAGS =R25 ;X1XX XXXX 1= Display needs updating ; ;The routines are coded for bit 6, ; ;but you can easily change it. ;LCD variables ; ;.equ LCD_Lines=2 ;.equ LCD_LEN=16 ;.equ LCD_Size=LCD_Lines * LCD_Len ;LCD_OUT_BUF: .byte LCD_Size ;Size defined above ;LCD_OUT_TAIL: .byte 2 ;Tail pointer ; ;This is an example only, you can vary these settings, but this ;is what I used. ;************************************************************* ; ;HARDWARE YOU NEED TO SET UP ; ;Hardware pins assigned here to logical names. ; ;.equ LCD_DATA=PORTA ;LCD data lines interface ;Only D7-D4 used. D3-0 are open ;.equ LCD_INPUT=PINA ;LCD Input Pins, as above ;.equ LCD_CTRL=PORTD ;LCD control lines interface ;.equ LCD_E=5 ;LCD Enable control line ;.equ LCD_RW=6 ;LCD Read/Write control line .equ LCD_RS=7 ;LCD Register-Select control line .equ Half_Disp=((LCD_Size/2)-1) ; ;This is an example only, you can vary these settings, but this ;is what I used. ;************************************************************ ; ;OTHER SOFTWARE YOU NEED: ; ;A routine called MS_Delay, that will return after XX ms, with ;XX in TEMP, (ex: ldi TEMP,10 rcall MS_Delay should generate a ;10mS wait ; ;************************************************************ ; ;Call LCD_Init after powerup, to clear the display and get it ;ready to talk. ; ;First, call Say_Test, which will put the display image into ;the buffer in RAM. Then call Check_Display, which will cause ;the actual display to be updated. ; ;Make more prompt routines as needed. ; ;Check_Display should be the lowest priority task, run whenever ;you aren't doing anything special, or run it if it's important ;that the display get updated immediately. ; ;The scroll routines operate on the display buffer, and set the ;display dirty flag so that Check_Display will update the screen ; ;************************************************************ ;Prompt routines ;************************************************************ ; 12345678901234567890123456789012 Test_MSG: .db "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456" Say_Test: ldi R30,low(Test_MSG*2) ;Make the Z reg point at the table ldi R31,high(Test_MSG*2) ;preparing for the LPM instruction rcall LCD_STR_Nopad ;String to buf, buf to LCD ret ; ;*************************************************************************** ;Update the display, only if needed ;*************************************************************************** ; Check_Display: mov TEMP,Bitflags ;Get the display flag andi TEMP,$40 ;Remove all but the "Dirty Bit" breq CKDisp_Done ;If zero, then just exit rcall LCD_Spew ;Otherwise, update the display CKDisp_Done: ret ; ; ;**************************************************************** ; ;These routines roll the entire display memory from left to right, or right to left. ; Scroll_All_Left: push TEMP ; push TEMP2 ; push LOOP ; ldi TEMP,LCD_Size-1 ;How wide is your buffer, -1 mov LOOP,TEMP ; ldi ZH,high(LCD_OUT_BUF) ;Point at the beginning ldi ZL,low(LCD_OUT_BUF) ; ld TEMP2,Z+ ;Pick up the first one ldi YH,high(LCD_OUT_BUF) ; ldi YL,low(LCD_OUT_BUF) ; ;At this point, Z points to buffer+1, and Y points to buffer. ;Temp2 contains the first char, which will be over-written by ;the first shift. rjmp Scroll_Left_Loop ; Scroll_Top_Left: push TEMP ; push TEMP2 ; push LOOP ; ldi TEMP,Half_Disp ; mov LOOP,TEMP ; ldi ZH,high(LCD_OUT_BUF) ;Point at the beginning ldi ZL,low(LCD_OUT_BUF) ; ld TEMP2,Z+ ;Pick up the first one ldi YH,high(LCD_OUT_BUF) ; ldi YL,low(LCD_OUT_BUF) ; ;At this point, Z points to buffer+1, and Y points to buffer. ;Temp2 contains the first char, which will be over-written by ;the first shift. rjmp Scroll_Left_Loop ; Scroll_Bot_Left: push TEMP ; push TEMP2 ; push LOOP ; ldi TEMP,Half_Disp ;How wide is your buffer, -1 mov LOOP,TEMP ; ldi ZH,high(LCD_OUT_BUF+Half_Disp+1);Point at the beginning ldi ZL,low(LCD_OUT_BUF+Half_Disp+1) ; ld TEMP2,Z+ ;Pick up the first one ldi YH,high(LCD_OUT_BUF+Half_Disp+1); ldi YL,low(LCD_OUT_BUF+Half_Disp+1) ; rjmp Scroll_Left_Loop ; ; ;The Y and Z pointers are set, and TEMP2 contains the end char, TEMP has the ;number of chars to scroll. All we do here is pick them up, and set them down. ; Scroll_Left_Loop: ld TEMP,Z+ ;Pick up the second char and then point at ;the third st Y+,TEMP ;Store second at first, and then point at ;the second. dec Loop ;One less to shift. brne Scroll_Left_Loop ; st Y,TEMP2 ;Put the first char in the last cell mov TEMP,Bitflags ; ori TEMP,$40 ;Flag the display dirty pop LOOP ; pop TEMP2 ; pop TEMP ; ret Scroll_All_Right: push TEMP ; push TEMP2 ; push LOOP ; ldi TEMP,LCD_Size-1 ; mov LOOP,TEMP ; ldi ZH,high(LCD_OUT_BUF+LCD_Size) ;Point at the beginning ldi ZL,low(LCD_OUT_BUF+LCD_Size) ; ld TEMP2,Z ;Pick up the first one ld R0,-Z ;Post dec ldi YH,high(LCD_OUT_BUF+LCD_Size) ; ldi YL,low(LCD_OUT_BUF+LCD_Size) ; rjmp Scroll_Right_Loop ; Scroll_Top_Right: push TEMP ; push TEMP2 ; push LOOP ; ldi TEMP,Half_Disp ; mov LOOP,TEMP ; ldi ZH,high(LCD_OUT_BUF+Half_Disp) ;Point at the beginning ldi ZL,low(LCD_OUT_BUF+Half_Disp) ; ld TEMP2,Z ;Pick up the first one ld R0,-Z ;Toss ldi YH,high(LCD_OUT_BUF+Half_Disp) ; ldi YL,low(LCD_OUT_BUF+Half_Disp) ; rjmp Scroll_Right_Loop ; Scroll_Bot_Right: push TEMP ; push TEMP2 ; push LOOP ; ldi TEMP,Half_Disp ; mov LOOP,TEMP ; ldi ZH,high(LCD_OUT_BUF+LCD_Size-1) ;Point at the beginning ldi ZL,low(LCD_OUT_BUF+LCD_Size-1) ; ld TEMP2,Z ;Pick up the first one ld R0,-Z ;Toss ldi YH,high(LCD_OUT_BUF+LCD_Size-1) ; ldi YL,low(LCD_OUT_BUF+LCD_Size-1) ; rjmp Scroll_Right_Loop ; ; ;Same as Scroll_Left_Loop, but note the pre-dec rather than post inc. ; Scroll_Right_Loop: ld TEMP,Z ;Pick it up here ld R0,-Z ;This just does a post dec on Z st Y,TEMP ;Put it down there ld R0,-Y ;And a post dec on Y dec Loop ;One less to mess with brne Scroll_Right_Loop ;If not done, do more st Y,TEMP2 ;Put the first char in the last cell mov TEMP,Bitflags ; ori TEMP,$40 ;Flag the display dirty pop LOOP ; pop TEMP2 ; pop TEMP ; ret ; ;**************************************************************** ;Called with Z pointing to a string in rom. ;Seeks and displays forward, until we find a null. ;Shoves that text into the LCD_OUT_BUF ;**************************************************************** ; LCD_STR_NOPAD: push TEMP ; push TEMP2 ; push LOOP ; rcall LCD_STR2BUF ;Move a string in rom to LCD_OUT_BUF ori Bitflags,$40 ;Flag the display dirty pop LOOP ; pop TEMP2 ; pop TEMP ; ret ; ;**************************************************************** ;This routine moves a string of arbitrary size into LCD_OUT_BUF, ;and padds it with spaces at the end as needed. ;**************************************************************** ; LCD_STR2BUF: ;Point at beginning of LCD_OUT_BUF ldi YH,high(LCD_OUT_BUF) ; ldi YL,low(LCD_OUT_BUF) ; ldi TEMP2,0 ; mov LOOP,TEMP2 ; LCD_STR_Loop: lpm ;look up character ld TEMP,Z+ ;Move pointer to in string mov TEMP,R0 ;move to temp and TEMP,TEMP ;Is it null? breq LCD_STR_Done ;Yes, we're done, see if we need to fill inc LOOP ;Keep track of how many we've stored mov TEMP2,LOOP ; cpi TEMP2,LCD_Size+1;If full breq LCD_STR_DONE ;No need to fill, can't do anymore anyway st Y+,TEMP ;Output to ram buffer, inc after rjmp LCD_STR_Loop ; LCD_STR_Done: ret LCD_Fill: ;Now the LCD_OUT_BUF has the output data, but may contain junk ;after the desired text, so pad to the end with spaces inc LOOP ; mov TEMP2,LOOP ; cpi TEMP2,LCD_SIZE+1; breq LCD_Fill_Done ;If full, then we're ready to spew ldi TEMP,$20 ;(Space) st Y+,TEMP ; rjmp LCD_Fill ; LCD_Fill_Done: ret ; ;************************************************************** ;This routine moves the data in LCD_OUT_BUF to the display. ;Called only from IDLE, this updates the display to be current ;with the contents of the lcd buffer. ;************************************************************** ; LCD_SPEW: push TEMP ; push TEMP2 ; push LOOP ; rcall LCD_Clear ;Clear and home, all delays ;Point at the beginning of the LCD buffer ldi ZH,high(LCD_OUT_BUF) ; ldi ZL,low(LCD_OUT_BUF) ; ldi TEMP2,0 ; mov LOOP,TEMP2 ; LCD_SPEW_Loop: inc LOOP ; mov TEMP2,LOOP ; cpi TEMP2,Half_Disp+2 ;Are we pointing to the second line? brne LCD_SPEW_L2 ; rcall Bump_Pointer ; LCD_Spew_L2: mov TEMP2,LOOP ; cpi TEMP2,LCD_Size+1 ; breq LCD_SPEW_Done ; ld TEMP,Z+ ;Get a char, and inc Z rcall LCD_PUT_CHAR ; rjmp LCD_SPEW_Loop ; LCD_SPEW_Done: andi Bitflags,$BF ;Flag the display clean pop LOOP ; pop TEMP2 ; pop TEMP ; ret ; ; ;************************************************************* ;This just puts the pointer onto the start of the 2nd line ;************************************************************* ; Bump_Pointer: ldi TEMP,$C0 ;Set the display pointer to 40 rcall LCD_PUT_CMD ; ret ; ;************************************************************* ;The main powerup init routine. Only called after powerup ;************************************************************* ; LCD_INIT: ; Busy-flag is not yet valid for 15mS after powerup ldi TEMP,16 ; rcall MS_Delay ;So we wait 16ms ldi TEMP,$30 ;8-bit-interface, 2-lines, 5x7 font rcall LCD_Slam_CMD ;Don't look for busy, just send. ldi TEMP,5 ; rcall MS_Delay ; ldi TEMP,$30 ;8-bit-interface, 2-lines, 5x7 font rcall LCD_Slam_CMD ; ldi TEMP,2 ; rcall MS_Delay ; ldi TEMP,$30 ;8-bit-interface, 2-lines, 5x7 font rcall LCD_Slam_CMD ; ldi TEMP,2 ; rcall MS_Delay ; ldi TEMP,$20 ;4-bit-interface, 2-lines, 5x7 font rcall LCD_Slam_CMD ; ldi TEMP,2 ; rcall MS_Delay ; ldi TEMP,$0C ;disp on, curs.off, no-blink rcall LCD_Slam_CMD ; ldi TEMP,2 ; rcall MS_Delay ; ldi TEMP,$01 ;Clear rcall LCD_Slam_CMD ; ldi TEMP,2 ; rcall MS_Delay ; ldi TEMP,$06 ;Entry set rcall LCD_Slam_CMD ; ret ; ;************************************************************ ;Detect wether the display is busy or not. ;************************************************************ ; LCD_BUSY: push TEMP ;There's a char for the LCD in here ldi TEMP,$0F ;Make port A high bits inputs out DDRA,TEMP ; LCD_Busy_Loop: ;rcall Timed_Smack ;Since we could loop here a while... ;WARNING! The above line is needed to satisfy a system watchdog, as ;this routine could loop forever. The routine name is just an example cbi LCD_CTRL,LCD_RS ;Set LCD for command mode sbi LCD_CTRL,LCD_RW ;Set read mode sbi LCD_CTRL,LCD_E ;LCD E-line High nop ; in TEMP,LCD_Input ;Get the high nybble andi TEMP,$F0 ;Mask the junk mov TEMP2,TEMP ;Save a copy cbi LCD_CTRL,LCD_E ;LCD E-line Low sbi LCD_CTRL,LCD_E ;LCD E-line High in TEMP,LCD_Input ;Read busy flag + DDram address swap TEMP ;High is where the data is, but this is the low andi TEMP,$0F ;mask off any junk in the high or TEMP,TEMP2 ;put the nybbles together mov TEMP2,TEMP ;Make a copy cbi LCD_CTRL,LCD_E ;LCD E-line High andi TEMP,$80 ;Check Busy flag, High = Busy brne LCD_BUSY_Loop ;While "1",it's busy. ;make PORTA an output again, TEMP2 has RAM adrs ldi TEMP,$FF ; out DDRA,TEMP ; pop TEMP ;Put back the char to the LCD andi TEMP2,$7F ;Strip busy flag ret ; ;************************************************************* ;Clear and home the display ;************************************************************* ; LCD_CLEAR: ldi TEMP,$01 ;The command itself rcall LCD_Slam_CMD ; ldi TEMP,$02 ;Home rcall LCD_PUT_CMD ;Wait till not busy, then send. ret ; ;************************************************************** ;Sends character to LCD ;Required character must be in TEMP ;************************************************************** ; LCD_PUT_CHAR: rcall LCD_BUSY ;Wait for LCD to be ready (Busy saves TEMP) sbi LCD_CTRL,LCD_RS ;Set LCD in data mode rjmp LCD_Talk ; ; ;************************************************************** ;Sends command to LCD ;Required command must be in TEMP ;************************************************************** ; LCD_PUT_CMD: rcall LCD_BUSY ;Wait for LCD to be ready (Busy saves TEMP) LCD_Slam_CMD: ;Just like put, but no test for busy cbi LCD_CTRL,LCD_RS ;Set LCD in command mode ;rjmp LCD_Talk ;Fallthru ; ;************************************************************** ;The basic engine that talks to the LCD. ;Command or Data mode were set up above, in PUT_CMD, PUT_CHAR. ;SLAM_CMD exists only to serve for sending commands during the ;time that the busy flag can't be tested. ;************************************************************** ; LCD_Talk: push TEMP ;Save a copy push TEMP ;Save two copies ldi TEMP,$0F ;Make all selects high (inactive) out LCD_DATA,TEMP ; ldi TEMP,$F0 ;Data bits out, enables in out DDRA,TEMP ; pop TEMP ; ori TEMP,$0F ;Preserve pullups cbi LCD_CTRL,LCD_RW ;Set LCD in write mode out LCD_DATA,TEMP ;put the command on the data bus. sbi LCD_CTRL,LCD_E ;Toggle E high nop ; cbi LCD_CTRL,LCD_E ;LCD E-line Low pop TEMP ; swap TEMP ; ori TEMP,$0F ; out LCD_DATA,TEMP ;put the command on the data bus. sbi LCD_CTRL,LCD_E ;Toggle E high nop ; cbi LCD_CTRL,LCD_E ;LCD E-line Low ret ;The RET assures min adrs hold timing. ; ;************************************************************