subtitle "lcd_lib.asm" ;;; ********************************************************************* ;;; Library Name: lcd_lib.asm ;;; Purpose: Routines for communication with ;;; a standard 14-pin LCD using the ;;; Stanley LCD-type interface. ;;; Various interface methods are supported as ;;; outlined in supporting documentation. ;;; Revision: 0.3 ;;; Date: 03 October 2005 ;;; Author: L. Wyard-Scott ;;; Copyright: Public Domain ;;; Device: PIC16F873, PIC16F874, PIC16F876, PIC16F877. ;;; PIC16F873A, PIC16F874A, PIC16F876A, PIC16F877A. ;;; Special Directions: See supporting documentation for assembly ;;; directives used to establish the LCD interface ;;; method, etc. ;;; This file makes use of the del_lib library. ;;; ********************************************************************* ;;; Future Directions: ;;; - support for SPI forms of LCD interface. The framework is ;;; already more-or-less there (revision 0.0), but not complete. ;;; ********************************************************************* ;;; Revision History: ;;; 0.3 - 23 February 2006 ;;; - Repaired TxStringK and TxStringKN by moving a banksel ;;; and adding a missing one. Operation was undpredictable ;;; in programs with variables across multiple banks. ;;; - Added call to LCD_Wait in LCD_Data_Read. This deficiency ;;; was located by Gloria Leung in 2004. Thanks, Gloria! ;;; 0.2 - 03 October 2005 ;;; - Added support for 16F87XA devices. ;;; 0.1 - 24 February 2004 ;;; - Minor updates, including fixing some MPLAB-acceptable ;;; directives that GPASM choked on. ;;; - Hard-coded the name of the configuration file: ;;; lcd_config.h ;;; 0.0 - 27 October 2003 ;;; - Creation (parallel interface only). ;;; ********************************************************************* list ; Turn on list output. Note that device ; type needs to be specified to the ; assembler (on the command line, or through ; the MPLAB IDE). ;; Include register, bit, and other info specific to ;; the specified device. ifdef __16F873 #include messg "Assembling lcd_lib.asm for PIC16F873." endif ifdef __16F874 #include messg "Assembling lcd_lib.asm for PIC16F874." endif ifdef __16F876 #include messg "Assembling lcd_lib.asm for PIC16F876." endif ifdef __16F877 #include messg "Assembling lcd_lib.asm for PIC16F877." endif ifdef __16F873A #define A_Device #include messg "Assembling lcd_lib.asm for PIC16F873A." endif ifdef __16F874A #define A_Device #include messg "Assembling lcd_lib.asm for PIC16F874A." endif ifdef __16F876A #define A_Device #include messg "Assembling lcd_lib.asm for PIC16F876A." endif ifdef __16F877A #define A_Device #include messg "Assembling lcd_lib.asm for PIC16F877A." endif #include "del_lib.h" ; Include the delay routine headers. ;;; -------------------------------------------------------------------- ;;; Assembler Equates Section. Define assembly-time constants here ;;; using the EQU assembler directive. ;;; -------------------------------------------------------------------- CD_LCD EQU 0 RS EQU 1 ; LCD Read/Write control line RW EQU 2 ; LCD Register Select control line E EQU 3 ; LCD Enable control line ;;; Defines for possible hardware interface methods. ;;; The actual method used is specified in lcd_config.h #define SPI_ALL 1 #define SPI_DATA 2 #define SPI_DATA_DBF 4 #define PARALLEL 8 #include "lcd_config.h" ;;; Ensure that appropriate defines have been specified in lcd_config.h. ifndef METHOD error "No LCD hardware interface method (METHOD) specified" endif if METHOD == SPI_ALL messg "LCD hardware interface method: all-SPI (data and control)." endif if METHOD == SPI_DATA messg "LCD hardware interface method: SPI data, digital control." endif if METHOD == SPI_DATA_DBF messg "LCD hardware interface method: SPI data with added feedback from LCD data7, digital control." endif if METHOD == PARALLEL messg "LCD hardware interface method: Parallel." ifndef DATA_WIDTH error "Parallel interface data width (DATA_WIDTH 4|8) not specified." else if (DATA_WIDTH != 4) && (DATA_WIDTH != 8) error "Parallel interface width (DATA_WIDTH) must be 4 or 8." endif if (DATA_WIDTH == 4) messg "Parallel interface data width set to 4 bits." endif if (DATA_WIDTH == 8) messg "Parallel interface data width set to 8 bits." endif endif ifndef DATA_PORT error "Data port (DATA_PORT) not specified." else messg "Data port address set." if DATA_PORT == PORTA messg "LCD_Init does not set PORTA as digital I/O. Ensure your code does this prior to calling LCD_Init." endif endif if DATA_WIDTH == 4 ifndef DATA_LSB_PIN error "Data least-signficant (DATA_LSB_PIN) bit not defined." else if DATA_LSB_PIN > 4 error "No room to allocate 4 data lines with LSBit as specified by DATA_LSB_PIN." endif endif endif endif ; METHOD == PARALLEL if (METHOD == PARALLEL) || (METHOD == SPI_DATA) || (METHOD==SPI_DATA_DBF) ;; Require specification of the 3 control pins. ifndef CTRL_E_PORT error "LCD E control port (CTRL_E_PORT) not defined." else messg "LCD E register defined." if CTRL_E_PORT == PORTA messg "LCD_Init does not set PORTA as digital I/O. Ensure your code does this prior to calling LCD_Init." endif endif ifndef CTRL_E_PIN error "LCD E control port pin (CTRL_E_PIN) not defined." else messg "LCD E pin defined." endif ifndef CTRL_RW_PORT error "LCD R/W* control port (CTRL_RW_PORT) not defined." else messg "LCD R/W* register defined" if CTRL_RW_PORT == PORTA messg "LCD_Init does not set PORTA as digital I/O. Ensure your code does this prior to calling LCD_Init." endif endif ifndef CTRL_RW_PIN error "LCD R/W* control port pin (CTRL_RW_PIN) not defined." else messg "LCD R/W* pin defined" endif ifndef CTRL_RS_PORT error "LCD RS control port (CTRL_RS_PORT) not defined." else messg "LCD RS register defined" if CTRL_RS_PORT == PORTA messg "LCD_Init does not set PORTA as digital I/O. Ensure your code does this prior to calling LCD_Init." endif endif ifndef CTRL_RS_PIN error "LCD RS control port pin (CTRL_RS_PIN) not defined." else messg "LCD RS pin defined" endif endif if (METHOD == SPI_DATA_DBF) ;; Require specification of the "busy flag" (input) pin and '595 OE* pin. ifndef CTRL_BF_PORT error "LCD busy flag port (CTRL_BF_PORT) not defined." else messg "LCD busy flag register specified" if CTRL_BF_PORT == PORTA messg "LCD_Init does not set PORTA as digital I/O. Ensure your code does this prior to calling LCD_Init." endif endif ifndef CTRL_BF_PIN error "LCD busy flag port pin (CTRL_BF_PIN) not defined." else messg "LCD busy flag pin defined" endif ifndef CTRL_OE_PORT error "'595 OE* port (CTRL_OE_PORT) not defined." else messg "'595 output enable control register defined." if CTRL_OE_PORT == PORTA messg "LCD_Init does not set PORTA as digital I/O. Ensure your code does this prior to calling LCD_Init." endif endif ifndef CTRL_OE_PIN error "'595 OE* port pin (CTRL_OE_PIN) not defined." else messg "'595 OE* pin defined." endif endif if (METHOD != PARALLEL) ;; All SPI methods require specification of the latch ;; signal port and pin. error "SPI forms of the LCD interface are not yet supported" error "by this library (Revision 0.0)" ifndef SPI_LATCH_PORT error "SPI register latch port (SPI_LATCH_PORT) not defined." else messg "SPI register latch register defined." endif ifndef SPI_LATCH_PIN error "SPI register latch pin (SPI_LATCH_PIN) not defined." else messg "SPI register latch pin defined." endif ;; Include the SPI library header. #include messg "SPI Interface methods require use of the SPI library." messg "Ensure that spi_lib.o is linked into the project." endif ;;; --------------------------------------------------------------------- ;;; Variable Address Assignments. ;;; --------------------------------------------------------------------- UDATA ; Start of the uninitialized data section. ; The following statements reserve memory, ; the address of which is allocated by the ; linker. ;;; As parameters to LCD_TxStringK, LCD_TxStringKN. LCD_PTRLOW: res 1 LCD_PTRHIGH: res 1 global LCD_PTRLOW, LCD_PTRHIGH ;;; The following are local variables which do not need to be declared ;;; globally and can be shared with others. LCD_TempVARS: UDATA_OVR W_SAVE res 1 ; Temporary register to save W. NUMBYTESTOSEND res 1 ; Number of bytes to send in LCD_TxStringN ;; Variables used in 4-bit parallel mode only. if (METHOD == PARALLEL) if (DATA_WIDTH == 4) DATA_TEMP res 1 ; Temporary location for manipulation of data. READ_DATA res 1 ; Temporary location for reading of data ; from the LCD. endif endif ;;; -------------------------------------------------------------------- ;;; Macros. ;;; -------------------------------------------------------------------- ;;; ******************************************************************** ;;; Subroutine Name: LCD_WRITE_MACRO ;;; Description: Writes a byte to the LCD. ;;; Requires: The byte to write in W, the value of the ;;; register select (RS) output (0|1) as a macro ;;; parameter. ;;; Returns: Nothing. ;;; Locations Affected: W_SAVE. ;;; DATA_TEMP (4-bit parallel only) ;;; ******************************************************************** LCD_WRITE_MACRO: macro RSBit ;; Save the W register contents. banksel W_SAVE movwf W_SAVE if (METHOD == PARALLEL) banksel CTRL_E_PORT ;; Establish RS, RW and E, in that order. if (RSBit == 1) bsf CTRL_RS_PORT,CTRL_RS_PIN endif if (RSBit == 0) bcf CTRL_RS_PORT,CTRL_RS_PIN endif ;; Assert a write cycle. bcf CTRL_RW_PORT,CTRL_RW_PIN ;; Enable the LCD. bsf CTRL_E_PORT,CTRL_E_PIN if (DATA_WIDTH == 8) ;; Place the data on the bus. movwf DATA_PORT ;; Select the bank with TRISx. bsf STATUS,RP0 ;; Make the data output. clrf DATA_PORT ;; Unassert E, thereby latching in the data. bcf STATUS,RP0 ; Change back to bank 0. bcf CTRL_E_PORT,CTRL_E_PIN ;; Make data bus input. bsf STATUS,RP0 movlw 0xFF movwf DATA_PORT endif ; (DATA_WIDTH == 8) if (DATA_WIDTH == 4) ;; Make the data output (without affecting other bits). bsf STATUS,RP0 ; Bank with TRISx. movlw (0x0F << DATA_LSB_PIN) xorlw 0xFF andwf DATA_PORT,F ;; Make all the data lines low so the value ;; can be ORed in. bcf STATUS,RP0 movlw (0x0F << DATA_LSB_PIN) xorlw 0xFF andwf DATA_PORT,F ;; Mask off the lower nibble of the data. banksel W_SAVE movf W_SAVE,W andlw 0xF0 banksel DATA_TEMP movwf DATA_TEMP ;; Align the upper nibble. variable count count = (4 - DATA_LSB_PIN) while count rrf DATA_TEMP,F count -= 1 endw ;; Insert the data into the location zeroed-out, above. movf DATA_TEMP,W banksel DATA_PORT iorwf DATA_PORT,F ;; Unassert E. bcf STATUS,RP0 ; Change back to bank 0. ;; Deal with the LSNibble in a similar manner as the ;; MSNibble. bcf CTRL_E_PORT,CTRL_E_PIN ;; Deal with the LSNibble in a similar manner. ;; Zero-out the data so it can be ORed in. banksel DATA_PORT movlw (0x0F << DATA_LSB_PIN) xorlw 0xFF andwf DATA_PORT,F banksel W_SAVE movf W_SAVE,W andlw 0x0F ; Isolate LSNibble. banksel DATA_TEMP movwf DATA_TEMP count = DATA_LSB_PIN while count rlf DATA_TEMP,F count -= 1 endw movf DATA_TEMP,W banksel DATA_PORT ;; Assert E. bsf CTRL_E_PORT,CTRL_E_PIN iorwf DATA_PORT,F ;; Unassert E. banksel CTRL_E_PORT bcf CTRL_E_PORT,CTRL_E_PIN ;; Make the data bus input. bsf STATUS,RP0 ; Bank with TRISx. movlw (0x0F << DATA_LSB_PIN) iorwf DATA_PORT,F endif ; (DATA_WIDTH ==4) ;; Restore W. banksel W_SAVE movf W_SAVE,W endif ; (METHOD==PARALLEL) endm ; End of macro definition. ;;; ******************************************************************** ;;; Subroutine Name: LCD_READ_MACRO ;;; Description: Reads a byte from the LCD. ;;; Requires: The value of the register select (RS) ;;; output (0|1) as a macro parameter. ;;; Returns: The read value in W. ;;; Locations Affected: READ_DATA, DATA_TEMP (4-bit parallel mode only). ;;; ******************************************************************** LCD_READ_MACRO: macro RSBit if (METHOD == PARALLEL) banksel CTRL_E_PORT ;; Establish RS, RW and E, in that order. if (RSBit == 1) bsf CTRL_RS_PORT,CTRL_RS_PIN endif if (RSBit == 0) bcf CTRL_RS_PORT,CTRL_RS_PIN endif ;; Assert a read cycle. bsf CTRL_RW_PORT,CTRL_RW_PIN if (DATA_WIDTH == 8) ;; Ensure that the databus is input. bsf STATUS,RP0 ; Select bank with TRISx. movlw 0xFF movwf DATA_PORT ;; Enable the LCD. bcf STATUS,RP0 bsf CTRL_E_PORT,CTRL_E_PIN ;; Read the data. movf DATA_PORT,W ;; Finish the read cycle. bcf CTRL_E_PORT,CTRL_E_PIN return endif ; (DATA_WIDTH == 8) if (DATA_WIDTH == 4) ;; Ensure that the databus is input. bsf STATUS,RP0 ; Bank with TRISx. movlw (0x0F << DATA_LSB_PIN) iorwf DATA_PORT,F ;; Enable the LCD. bcf STATUS,RP0 bsf CTRL_E_PORT,CTRL_E_PIN goto $+1 ; Slight delay. ;; Read the data MSNibble. movf DATA_PORT,W ;; Indicate to the LCD that the read is done. bcf CTRL_E_PORT,CTRL_E_PIN ;; Isolate the data read. andlw (0x0F << DATA_LSB_PIN) banksel READ_DATA movwf READ_DATA ;; Align the MSNibble data correctly in READ_DATA. count = (4 - DATA_LSB_PIN) while count rlf READ_DATA,F count -= 1 endw ;; Start the read cycle for the LSNibble. banksel CTRL_E_PORT bsf CTRL_E_PORT,CTRL_E_PIN ;; Read the data LSNibble. movf DATA_PORT,W ;; Indicate to the LCD that the read is done. bcf CTRL_E_PORT,CTRL_E_PIN ;; Isolate the data read. andlw (0x0F << DATA_LSB_PIN) banksel DATA_TEMP movwf DATA_TEMP count = DATA_LSB_PIN while count rrf DATA_TEMP,F count -= 1 endw ;; OR together the MSNibble (in READ_DATA) and ;; the LSNibble (in DATA_TEMP). movf DATA_TEMP,W iorwf READ_DATA,F movf READ_DATA,W return endif ; (DATA_WIDTH == 4) endif ; (METHOD == PARALLEL) endm ; End of macro definition. ;;; -------------------------------------------------------------------- ;;; Subroutines. ;;; -------------------------------------------------------------------- CODE ;;; ******************************************************************** ;;; Subroutine Name: LCD_Init ;;; Description: Initializes the LCD for 2-line, 5x7 char, ;;; cursor on, blinking block off, auto-increment, ;;; no screen shift operation. ;;; Requires: Nothing. ;;; Returns: Nothing. ;;; Locations Affected: W_SAVE is modified. ;;; ******************************************************************** LCD_Init: global LCD_Init ; Make callable outside the module. ;; Save W. banksel W_SAVE movwf W_SAVE ;; Initialize the ports used for interfacing. if (METHOD == PARALLEL) if DATA_WIDTH == 4 ;; Data width is 4, parallel interface. banksel DATA_PORT ; Select bank 0. ;; Initialize all data to 0. bcf DATA_PORT,DATA_LSB_PIN bcf DATA_PORT,(DATA_LSB_PIN + 1) bcf DATA_PORT,(DATA_LSB_PIN + 2) bcf DATA_PORT,(DATA_LSB_PIN + 3) bsf STATUS,RP0 ; Select bank with TRISx in it. ;; Make all data lines input. bsf DATA_PORT,DATA_LSB_PIN bsf DATA_PORT,(DATA_LSB_PIN + 1) bsf DATA_PORT,(DATA_LSB_PIN + 2) bsf DATA_PORT,(DATA_LSB_PIN + 3) else ;; Data width is 8, parallel interface. banksel DATA_PORT ; Select bank 0. clrf DATA_PORT ; Initialize data to 0. bsf STATUS,RP0 ; Select bank with TRISx in it. movlw 0xFF ; Make all the pins input. movwf DATA_PORT endif ; (DATA_WIDTH==4) endif ; (METHOD==PARALLEL) if (METHOD == PARALLEL) || (METHOD == SPI_DATA) || (METHOD == SPI_DATA_DBF) ;; Make all control pins output (and inactive). banksel CTRL_E_PORT ;; Initialize to inactive. bcf CTRL_E_PORT,CTRL_E_PIN bcf CTRL_RW_PORT,CTRL_RW_PIN bcf CTRL_RS_PORT,CTRL_RS_PIN ;; Make output. bsf STATUS,RP0 ; Select bank with TRISx in it. bcf CTRL_E_PORT,CTRL_E_PIN bcf CTRL_RW_PORT,CTRL_RW_PIN bcf CTRL_RS_PORT,CTRL_RS_PIN endif if (METHOD != PARALLEL) ;; The method is of the SPI variety. Initialize the ;; latch pin control output. banksel SPI_LATCH_PORT bcf SPI_LATCH_PORT,SPI_LATCH_PIN bsf STATUS,RP0 bcf SPI_LATCH_PORT,SPI_LATCH_PIN ;; !!! Initialize the SPI hardware. call SPI_Init endif if (METHOD == SPI_DATA_DBF) ;; The method uses a dedicated digital input to read ;; the LCD's busy flag. ;; Initialize this as an input. bcf STATUS,RP1 bsf STATUS,RP0 ; Bank with TRISx. bsf CTRL_BF_PORT,CTRL_BF_PIN endif ;; ----------------------------------------------------- ;; Initialization of the LCD occurs here. This uses ;; the "software initialization" method outlined in the ;; module datasheet. Although it is not always necessary, ;; it is recommended. ;; ----------------------------------------------------- ;; Delay for 20 milliseconds (15 recommended) to give the ;; module time to get its act together. movlw 20 call DELAY_Wx1ms ;; The first command changes according to the data width. if (METHOD == PARALLEL) if (DATA_WIDTH == 8) ;; 8-bit parallel interface. movlw 0x38 ; Function set - 8 data lines, 2 lines of 5x7 chars. call LCD_Ctrl_Write_NoWait ;; Wait more than 4.1 ms. movlw 5 call DELAY_Wx1ms ;; Second round of function set. movlw 0x38 call LCD_Ctrl_Write_NoWait ;; Wait for more than 100 us. movlw 1 call DELAY_Wx1ms ;; Third round of function set. movlw 0x38 call LCD_Ctrl_Write_NoWait ;; A fourth round will occur, below. movlw 0x38 else ;; 4-bit parallel interface. ;; This process is a little different, as the first ;; writes to the LCD only use the MSNibble. The LSnibble ;; is not transferred, thereby eliminating the use of ;; LCD_Ctrl as it currently stands. ;; Indicate a control byte is about to be written. banksel CTRL_RS_PORT bcf CTRL_RS_PORT,CTRL_RS_PIN bcf CTRL_RW_PORT,CTRL_RW_PIN ;; Make the data output. bsf STATUS,RP0 ; Bank with TRISx. movlw (0x0F << DATA_LSB_PIN) xorlw 0xFF andwf DATA_PORT,F ;; Enable the LCD. bcf STATUS,RP0 bsf CTRL_E_PORT,CTRL_E_PIN ;; ------------------------------------------------- ;; Write 0x3 to DB7-DB4 to indicate 8-bit mode. ;; This is bizarre, but what is required for the ;; software reset. ;; Clear the lines so data can be ORed in. movlw (0x0F << DATA_LSB_PIN) xorlw 0xFF andwf DATA_PORT,F movlw (0x03 << DATA_LSB_PIN) iorwf DATA_PORT,F ;; Unassert E. This completes round 1 of function set. bcf CTRL_E_PORT,CTRL_E_PIN ;; Delay for more than 4.1 milliseconds. movlw 5 call DELAY_Wx1ms ;; ------------------------------------------------- ;; Strobe E, thereby completing round 2 of function set. ;; The data on the lines and the control signals can remain ;; unchanged. banksel CTRL_E_PORT bsf CTRL_E_PORT,CTRL_E_PIN goto $+1 ; A short delay. bcf CTRL_E_PORT,CTRL_E_PIN ;; Delay 100 us minimum (1000 actual). movlw 1 call DELAY_Wx1ms ;; ------------------------------------------------- ;; Same process as above for round 3 of function set. banksel CTRL_E_PORT bsf CTRL_E_PORT,CTRL_E_PIN goto $+1 ; A short delay. bcf CTRL_E_PORT,CTRL_E_PIN movlw 1 call DELAY_Wx1ms ;; ------------------------------------------------- ;; Now set the interface to 4-bit mode. banksel DATA_PORT movlw (0x0F << DATA_LSB_PIN) xorlw 0xFF andwf DATA_PORT,F movlw (0x02 << DATA_LSB_PIN) iorwf DATA_PORT,F banksel CTRL_E_PORT bsf CTRL_E_PORT,CTRL_E_PIN goto $+1 bcf CTRL_E_PORT,CTRL_E_PIN movlw 1 call DELAY_Wx1ms ;; ------------------------------------------------- ;; The rest of the initialization is carried out, ;; below. movlw 0x28 endif endif if (METHOD == SPI_DATA) || (METHOD == SPI_DATA_DBF) movlw 0x38 ; Function set - 8 data lines, 2 lines of 5x7 chars. endif if (METHOD == SPI_ALL) movlw 0x28 ; Function set - 4 data lines, 2 lines of 5x7 chars. endif ;; Uses the value in W, specified in one of the conditional ;; assembly blocks, above. call LCD_Ctrl_Write movlw 0x0E ; Display control - display, cursor on. call LCD_Ctrl_Write movlw 0x01 ; Clear screen. call LCD_Ctrl_Write movlw 0x06 ; Entry mode set - increment, no screen shift. call LCD_Ctrl_Write ;; Restore W. banksel W_SAVE movf W_SAVE,W return ;;; ******************************************************************** ;;; Subroutine Name: LCD_Wait ;;; Description: Blocks until the LCD module is ready to receive ;;; a command. This is performed by continually ;;; reading from the LCD control register until ;;; the busy flag is clear. ;;; Requires: Nothing. ;;; Returns: Nothing. ;;; Locations Affected: ;;; W_SAVE ;;; READ_DATA, DATA_TEMP (4-bit parallel mode only). ;;; ******************************************************************** LCD_Wait: global LCD_Wait ; Make callable outside the module. banksel W_SAVE ; Save W. movwf W_SAVE LCD_Wait_Loop: call LCD_Ctrl_Read andlw 0x80 ; Check the Busy Flag. btfss STATUS,Z goto LCD_Wait_Loop ; Loop and read again if BF=1. LCD_Wait_Done: banksel W_SAVE movf W_SAVE,W ; Restore W. return ;;; ******************************************************************** ;;; Subroutine Name: LCD_Data_Write ;;; Description: Writes a byte of data to either DDRAM or ;;; CGRAM (depending on the last address set ;;; command). ;;; Requires: W contains the byte to write. ;;; Returns: Nothing. ;;; Locations Affected: W_SAVE is modified. ;;; ******************************************************************** LCD_Data_Write: global LCD_Data_Write ; Make callable outside the module. ;; Block until the LCD is ready. call LCD_Wait LCD_Data_Write_NoWait: ;; Call a macro to do the work. RS = 1. LCD_WRITE_MACRO 1 return ;;; ******************************************************************** ;;; Subroutine Name: LCD_Ctrl_Write ;;; Description: Writes a control byte to the LCD's control ;;; register. ;;; Requires: W contains the byte to write (the command). ;;; Returns: Nothing. ;;; Locations Affected: W_SAVE is modified. ;;; ******************************************************************** LCD_Ctrl_Write: global LCD_Ctrl_Write ; Make callable outside the module. ;; Block until the LCD is ready. call LCD_Wait LCD_Ctrl_Write_NoWait: ;; Call a macro to do the work. RS = 0. LCD_WRITE_MACRO 0 return ;;; ******************************************************************** ;;; Subroutine Name: LCD_Data_Read ;;; Description: Reads a byte of data from either DDRAM or ;;; CGRAM (depending on the last address set ;;; command). ;;; Requires: Nothing. ;;; Returns: W contains the byte read. ;;; Locations Affected: READ_DATA, DATA_TEMP (4-bit parallel mode only). ;;; ******************************************************************** LCD_Data_Read: global LCD_Data_Read ; Make callable outside the module. call LCD_Wait ; Added for revision 0.3. ;; Call a macro to do the work. RS = 1. LCD_READ_MACRO 1 return ;;; ******************************************************************** ;;; Subroutine Name: LCD_Ctrl_Read ;;; Description: Reads a control byte from the LCD's control ;;; register. ;;; Requires: Nothing. ;;; Returns: W contains the byte read. ;;; Locations Affected: READ_DATA, DATA_TEMP (4-bit parallel mode only). ;;; ******************************************************************** LCD_Ctrl_Read: global LCD_Ctrl_Read ; Make callable outside the module. ;; Call a macro to do the work. RS = 0. LCD_READ_MACRO 0 return ;;; ******************************************************************** ;;; Subroutine Name: LCD_TxString ;;; Description: Sends a NULL-terminated string in RAM to the LCD. ;;; To send a string constant in ROM, see subroutine ;;; LCD_TxStringK. ;;; Requires: IRP (STATUS:7) and FSR point to the string ;;; in RAM. (See PIC16F87X data sheet section 2.5 ;;; for more information on indirect addressing.) ;;; Returns: Nothing. ;;; Locations Affected: W will contain 0 (NULL). IRP, FSR will point ;;; to the NULL of the string. ;;; ******************************************************************** LCD_TxString: global LCD_TxString ; Make this subroutine callable outside ; the module. ;; This routine requires no RAM bank switching since ;; INDF, StATUS, and FSR all appear in every bank. movf INDF,W ; Get the character pointed to by ; indirect addressing. btfsc STATUS,Z ; Exit if the NULL is found. return call LCD_Data_Write ; Send the character out. incf FSR,F ; Increment the LSByte of the pointer. btfsc STATUS,C ; Check to see if there has been a carry. bsf STATUS,IRP ; If so, ensure that a wrap to an upper ; bank occurs. goto LCD_TxString ;;; ******************************************************************** ;;; Subroutine Name: LCD_TxStringN ;;; Description: Sends N characters of a string to the LCD. ;;; This routine will transfer characters ;;; past any terminating NULL, if the number of ;;; specified characters is beyond the NULL. ;;; To send a NULL-terminated string in RAM, see ;;; subroutine LCD_TxString ;;; To send a string constant in ROM, see subroutine ;;; LCD_TxStringK. ;;; Requires: W contains the number of characters to transmit. ;;; IRP (STATUS:7) and FSR point to the string ;;; in RAM. (See PIC16F87X data sheet section 2.5 ;;; for more information on indirect addressing.) ;;; Returns: Nothing. ;;; Locations Affected: W will contain 0. IRP, FSR will point ;;; to the last transmitted byte. ;;; ******************************************************************** LCD_TxStringN: global LCD_TxStringN ; Make this subroutine callable outside ; the module. ;; Save the character count. banksel NUMBYTESTOSEND movwf NUMBYTESTOSEND LCD_TxStringNLoop: movf INDF,W ; Get the character pointed to by ; indirect addressing. call LCD_Data_Write ; Send the character out. ;; Determine if all desired characters are sent out. banksel NUMBYTESTOSEND decf NUMBYTESTOSEND,F btfsc STATUS,Z ; If zero, then the routine is done. return incf FSR,F ; Increment the LSByte of the pointer. btfsc STATUS,C ; Check to see if there has been a carry. bsf STATUS,IRP ; If so, ensure that a wrap to an upper ; bank occurs. goto LCD_TxStringNLoop ;;; ******************************************************************** ;;; Subroutine Name: LCD_TxStringK ;;; Description: Sends a NULL-terminated ;;; string constant (a string that resides in ;;; in the code space) to the LCD. ;;; This routine calls another (specialized) ;;; subroutine, LCD_GetStringKChar ;;; in order to overcome the ;;; PIC CPU limitation not allowing pointers ;;; to constants. ;;; To send a string in RAM, see LCD_TxString. ;;; Requires: PTRHigh - high byte of address of string ;;; constant. ;;; PTRLow - low byte of address of string ;;; constant. ;;; Returns: Nothing. ;;; Locations Affected: PTRHigh, PTRLow will point at the NULL. ;;; W will contain the NULL. ;;; ******************************************************************** LCD_TxStringK: global LCD_TxStringK ; Make this subroutine callable outside ; the module. ;; This routine uses a "trick" to allow specification of ;; and arbitrary string constant by PTRHigh:PTRLow. ;; The trick is to make a subroutine call to an intermediate ;; piece of code which then uses a "computed goto" to ;; jump to a string character. ;; When making a subroutine call, the return address ;; gets pushed onto the stack. The intermediate subroutine ;; itself does not pull the value off the stack, but ;; instead passes control to the table.. ;; This is slightly different than the suggested method ;; by Microchip in AN556 (Implementing a Table Read) ;; wherein the table itself includes an instruction to ;; complete a computed goto statement. LCD_TXStringKLoop: banksel LCD_PTRLOW ;; Get a character. call LCD_GetStringKChar ;; Return to this page if the string constant was ;; located in another page. pagesel LCD_TXStringKLoop ;; Check to see if it is a NULL. andlw 0xFF ; Does nothing but set/clear Z in STATUS. btfsc STATUS,Z return ; String transmission complete. ;; Display the character. call LCD_Data_Write banksel LCD_PTRLOW ;; Increment the pointer. incf LCD_PTRLOW,F ; Low byte. btfsc STATUS,Z incf LCD_PTRHIGH,F ; High byte, if there is a carry ; from the low byte. ;; Process another character. goto LCD_TXStringKLoop LCD_GetStringKChar: ;; Address of string is PTRHigh:PTRLow. ;; Use a computed GOTO to get the character to send. movf LCD_PTRHIGH,W movwf PCLATH ; High byte of address. movf LCD_PTRLOW,W movwf PCL ; Low Byte of address. ;; Execution should never past here. In a way, ;; this is a subroutine without a return statement. ;; The actual return is contained along with the ;; string 'table' data. ;;; ******************************************************************** ;;; Subroutine Name: LCD_TxStringKN ;;; Description: Sends N bytes of a string constant (a string ;;; that resides in the code space) to the LCD. ;;; This routine calls another (specialized) ;;; subroutine, LCD_GetStringKChar ;;; in order to overcome the ;;; PIC CPU limitation not allowing pointers ;;; to constants. ;;; To send a string in RAM, see LCD_TxString. ;;; Requires: PTRHigh - high byte of address of string ;;; constant. ;;; PTRLow - low byte of address of string ;;; constant. ;;; W - The number of bytes to send. ;;; Returns: Nothing. ;;; Locations Affected: PTRHigh, PTRLow will point at the last byte ;;; sent. ;;; NUMBYTESTOSEND is modified. ;;; ;;; W will contain the NULL. ;;; ******************************************************************** LCD_TxStringKN: global LCD_TxStringKN ; Make this subroutine callable outside ; the module. ;; This routine uses a "trick" to allow specification of ;; and arbitrary string constant by PTRHigh:PTRLow. ;; The trick is to make a subroutine call to an intermediate ;; piece of code which then uses a "computed goto" to ;; jump to a string character. ;; When making a subroutine call, the return address ;; gets pushed onto the stack. The intermediate subroutine ;; itself does not pull the value off the stack, but ;; instead passes control to the table.. ;; This is slightly different than the suggested method ;; by Microchip in AN556 (Implementing a Table Read) ;; wherein the table itself includes an instruction to ;; complete a computed goto statement. banksel NUMBYTESTOSEND movwf NUMBYTESTOSEND LCD_TXStringKNLoop: banksel LCD_PTRLOW ;; Get a character. call LCD_GetStringKChar ;; Return to this page if the string constant was ;; located in another page. pagesel LCD_TXStringKNLoop ;; Display the character. call LCD_Data_Write ;; Check if there are more bytes to send. banksel NUMBYTESTOSEND decfsz NUMBYTESTOSEND,F goto LCD_TXStringKNIncPointer return LCD_TXStringKNIncPointer: banksel LCD_PTRLOW ;; Increment the pointer. incf LCD_PTRLOW,F ; Low byte. btfsc STATUS,Z incf LCD_PTRHIGH,F ; High byte, if there is a carry ; from the low byte. ;; Process another character. goto LCD_TXStringKNLoop END ;;; The rest of this code (after the END directive) is orignally from ;;; debug.asm. It is retained for when I get around to implementing ;;; the SPI-type LCD interface. ;;; ******************************************************************** ;;; Subroutine Name: ;;; Description: ;;; Requires: ;;; Returns: ;;; Locations Affected: ;;; ******************************************************************** ;******************************************************************* ; LCD Commands ;******************************************************************* ;******************************************************************* ; The LCD Module Subroutines ;******************************************************************* ;******************************************************************* ; Function subroutine to initialize LCD to run an Optex 2x20 ; LCD display with an 8 bit wide data path and seperate ; control lines. See data sheet for LCD to see required ; initialization flowchart implimented below. ; receives: nothing ; uses: W, temp ; returns: nothing ;********************************************************************* initializeLCD banksel BANK0 ; Select Bank 0 bcf PORTC,CD_LCD ; Set chip select on 595 to high banksel BANK1 ; Select Bank 1 bcf TRISC,CD_LCD ; Set chip select RC0 as outputs banksel BANK0 ; Select Bank 0 call initializeSPI ; Initialize SPI ; Initialize output on shift register clrf temp ; Initialize data bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD movlw 0x06 ; min 4.1ms call delayWx1ms ; Send 8 Bit interface command clrf temp ; Initialize data movlw 0x30 ; Command for 8-bit interface movwf temp bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD clrf temp ; Initialize data movlw 0x30 ; Command for 8-bit interface movwf temp bsf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD clrf temp ; Initialize data movlw 0x30 ; Command for 8-bit interface movwf temp bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD movlw 0x01 ; min 100us call delayWx1ms ; Send 8 Bit interface command clrf temp ; Initialize data movlw 0x30 ; Command for 8-bit interface movwf temp bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD clrf temp ; Initialize data movlw 0x30 ; Command for 8-bit interface movwf temp bsf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD clrf temp ; Initialize data movlw 0x30 ; Command for 8-bit interface movwf temp bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD movlw 0x01 ; min 40us call delayWx1ms ; Send 8 Bit interface command clrf temp ; Initialize data movlw 0x30 ; Command for 8-bit interface movwf temp bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD clrf temp ; Initialize data movlw 0x30 ; Command for 8-bit interface movwf temp bsf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD clrf temp ; Initialize data movlw 0x30 ; Command for 8-bit interface movwf temp bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD ; Send 4 Bit interface command clrf temp ; Initialize data movlw 0x20 ; Command for 4-bit interface movwf temp bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD clrf temp ; Initialize data movlw 0x20 ; Command for 4-bit interface movwf temp bsf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD clrf temp ; Initialize data movlw 0x20 ; Command for 4-bit interface movwf temp bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD movlw 0x28 ; Command for 4-bit interface call sendLCDCmd ; and sets 2 lines and 5x7 font movlw 0x08 ; Display off, Cursor off, blink off call sendLCDCmd movlw 0x01 ; Clear screen call sendLCDCmd movlw 0x06 ; Entry command :increment, no shift call sendLCDCmd movlw 0x0F ; Display on, Cursor on, blink on call sendLCDCmd movlw 0x80 ; Set output to first line? call sendLCDCmd return ;******************************************************************* ; Function subroutine to send an ascii character contained in ; W to the LCD on pins <7:0> of Port B. ; receives: W ; uses: W,char, temp ; returns: nothing ;********************************************************************* sendLCDChar movwf char swapf char,F clrf temp ; Initialize data movwf temp movlw 0xf0 andwf temp,F bcf temp,E ; Initialize E to low bsf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD bsf temp,E ; Initialize E to low bsf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD bcf temp,E ; Initialize E to low bsf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD movf char,W clrf temp ; Initialize data movwf temp movlw 0xf0 andwf temp,F bcf temp,E ; Initialize E to low bsf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD bsf temp,E ; Initialize E to low bsf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD bcf temp,E ; Initialize E to low bsf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD movlw 0x01 ; min 40us call delayWx1ms return ;******************************************************************* ; Function subroutine to send a command byte contained in ; W to the LCD on pins <7:0> of Port B. ; receives: W ; uses: W,char,temp ; returns: nothing ;******************************************************************* sendLCDCmd movwf char swapf char,F clrf temp ; Initialize data movwf temp movlw 0xf0 andwf temp,F bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD bsf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD movf char,W clrf temp ; Initialize data movwf temp movlw 0xf0 andwf temp,F bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD bsf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD bcf temp,E ; Initialize E to low bcf temp,RS ; Initialize RS to low bcf temp,RW ; Initialize RW to low movf temp,W call transferSPI bsf PORTC,CD_LCD ; Clock the latch on the LCD 595 bcf PORTC,CD_LCD movlw 0x01 ; min 40us call delayWx1ms return ;;; -------------------------------------------------------------------- ;;; Constant Data (if not placed along with the subroutines. ;;; --------------------------------------------------------------------