Full Version : David Vanhorn's 4-Bit LCD Routines (AVR ASM)
avr >>ASSMBLER ROUTINES >>David Vanhorn's 4-Bit LCD Routines (AVR ASM)


Admin5- 04-19-2006
David Vanhorn's LCD4bit.asm

Handler for generic character based LCD's in 4 bit mode

CODE

; 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.
;
;************************************************************



Forumer™ is Voted #1 Free Forum Hosting provider
Build your own community today with the largest message board hosting company.