Creo que con los fuentes es mas facil entender el problema .... asi que mejor los pongo ...
;*********************************** Librería "LCD_4BIT.INC" **************************************
;
; ===================================================================
; Del libro "MICROCONTROLADOR PIC16F84. DESARROLLO DE PROYECTOS"
; E. Palacios, F. Remiro y L. López.
; Editorial Ra-Ma.
www.ra-ma.es; ===================================================================
;
; Estas subrutinas permiten realizar las tareas básicas de control de un módulo LCD de 2
; líneas por 16 caracteres, compatible con el modelo LM016L.
;
; El visualizador LCD está conectado al Puerto B del PIC mediante un bus de 4 bits. Las
; conexiones son:
; - Las 4 líneas superiores del módulo LCD, pines <DB7
B4> se conectan a las 4
; líneas superiores del Puerto B del PIC, pines <RB7:RB4>.
; - Pin RS del LCD a la línea RA0 del PIC.
; - Pin R/W del LCD a la línea RA1 del PIC, o a masa.
; - Pin Enable del LCD a la línea RA2 del PIC.
;
; Se utilizan llamadas a subrutinas de retardo de tiempo localizadas en la librería RETARDOS.INC.
;
; *************************************************************************************************
list p=16F877 ; list directive to define processor
#include <p16F877.inc> ; processor specific variable definitions
errorlevel -302 ; suppress "register not in bank0, check page bits" message.
#include <retardos.inc>
CBLOCK 0x23
LCD_Dato
LCD_GuardaDato
LCD_GuardaDatosTRIS
LCD_Auxiliar1
LCD_Auxiliar2
ENDC
LCD_CaracteresPorLinea EQU .20 ; Número de caracteres por línea de la pantalla.
;#DEFINE LCD_Control PORTA
#DEFINE LCD_DATOS PORTB
#DEFINE LCD_DATOS_TRIS TRISB
#DEFINE LCD_RS LCD_DATOS,0
#DEFINE LCD_RW LCD_DATOS,1
#DEFINE LCD_ENABLE LCD_DATOS,2
; Bits de linea de datos del LCD
DB7 EQU 7 ; LCD dataline 7 (MSB)
DB6 EQU 6 ; LCD dataline 6
DB5 EQU 5 ; LCD dataline 5
DB4 EQU 4 ; LCD dataline 4
DB3 EQU 3 ; LCD dataline 3
DB2 EQU 2 ; LCD dataline 2
DB1 EQU 1 ; LCD dataline 1
DB0 EQU 0 ; LCD dataline 0 (LSB)
; LCD Module commands
DISP_ON EQU 0x00C ; Display on
DISP_ON_C EQU 0x00E ; Display on, Cursor on
DISP_ON_B EQU 0x00F ; Display on, Cursor on, Blink cursor
DISP_OFF EQU 0x008 ; Display off
CLR_DISP EQU 0x001 ; Clear the Display
ENTRY_INC EQU 0x006 ;
ENTRY_INC_S EQU 0x007 ; El mensaje se mueve hacia la derecha
ENTRY_DEC EQU 0x004 ;
ENTRY_DEC_S EQU 0x005 ; El mensaje se mueve hacia la izquierda
DD_RAM_ADDR EQU 0x080 ; Least Significant 7-bit are for address
DD_RAM_UL EQU 0x080 ; Upper Left coner of the Display
FUNC_4_BITS EQU 0x020 ; Modo 4 bits
FUNC_8_BITS EQU 0x030 ; Modo 8 bits
FUNC_4_N EQU 0x028 ; Modo 4 bits, 2 renglones
FUNC_4_F EQU 0x02C ; Modo 4 bits, 2 renglones, caracteres 5x10 pizeles
FUNC_8_N EQU 0x038 ; Modo 8 bits, 2 renglones
FUNC_8_F EQU 0x03C ; Modo 8 bits, 2 renglones, caracteres 5x10 pixeles
; Subrutina "LCD_Inicializa" ------------------------------------------------------------
;
; Inicialización del módulo LCD: Configura funciones del LCD, produce reset por software,
; borra memoria y enciende pantalla. El fabricante especifica que para garantizar la
; configuración inicial hay que hacerla como sigue:
;
LCD_Inicializa
call Retardo_5ms
; banksel ADCON1 ; Si se desea trabajar con un pines del conversor A/D, es necesario
; movlw 0x02 ; establecer los pines que se van a utilizar como digitales.
; movwf ADCON1 ; set RE2-RE0 as digital I/O
banksel LCD_DATOS_TRIS ; Configura las líneas conectadas al bus de datos
movlw b"00000000"
movwf LCD_DATOS_TRIS
clrf STATUS
bcf LCD_RW ; En caso de que esté conectado le indica
; que se va a escribir en el LCD.
bcf LCD_ENABLE ; Impide funcionamiento del LCD poniendo E=0.
bcf LCD_RS ; Activa el Modo Comando poniendo RS=0.
call Retardo_20ms
movlw FUNC_8_BITS
call LCD_EscribeLCD ; Escribe el dato en el LCD.
call Retardo_5ms
movlw FUNC_8_BITS
call LCD_EscribeLCD
call Retardo_200micros
movlw FUNC_8_BITS
call LCD_EscribeLCD
call Retardo_200micros
movlw FUNC_4_BITS ; Interface de 4 bits.
call LCD_EscribeLCD
; Ahora configura el resto de los parámetros:
call LCD_2Lineas4Bits5x7 ; LCD de 2 líneas y caracteres de 5x7 puntos.
call LCD_Borra ; Pantalla encendida y limpia. Cursor al principio
call LCD_CursorOFF ; de la línea 1. Cursor apagado.
call LCD_CursorIncr ; Cursor en modo incrementar.
return
; Subrutina "LCD_EscribeLCD" -----------------------------------------------------------
;
; Envía el dato del registro de trabajo W al bus de dato y produce un pequeño pulso en el pin
; Enable del LCD. Para no alterar el contenido de las líneas de la parte baja del Puerto B que
; no son utilizadas para el LCD (pines RB3:RB0), primero se lee estas líneas y después se
; vuelve a enviar este dato sin cambiarlo.
LCD_EscribeLCD
andlw b"11110000" ; Se queda con el nibble alto del dato que es el
movwf LCD_Dato ; que hay que enviar y lo guarda.
movf LCD_DATOS,W ; Lee la información actual de la parte baja
andlw b"00001111" ; del Puerto B, que no se debe alterar.
iorwf LCD_Dato,F ; Enviará la parte alta del dato de entrada
; y en la parte baja lo que había antes.
banksel LCD_DATOS_TRIS ; Acceso al Banco 1.
movf LCD_DATOS_TRIS,W ; Guarda la configuración que tenía antes LCD_DATOS_TRIS.
banksel LCD_DATOS
movwf LCD_GuardaDatosTRIS
banksel LCD_DATOS_TRIS
movlw b"00001111" ; Las 4 líneas inferiores del Puerto B se dejan
andwf LCD_DATOS,F ; como estaban y las 4 superiores como salida.
clrf STATUS ; Acceso al Banco 0.
movf LCD_Dato,W ; Recupera el dato a enviar.
movwf LCD_DATOS ; Envía el dato al módulo LCD.
bsf LCD_ENABLE ; Permite funcionamiento del LCD mediante un pequeño
nop
bcf LCD_ENABLE ; pulso y termina impidiendo el funcionamiento del LCD.
banksel LCD_DATOS ; Acceso al Banco 0.
movf LCD_GuardaDatosTRIS,W
banksel LCD_DATOS_TRIS ; Acceso al Banco 1.
movwf LCD_DATOS_TRIS ; Restaura el antiguo valor en la configuración de LCD_DATOS_TRIS.
clrf STATUS ; Acceso al Banco 0.
return
; Subrutinas variadas para el control del módulo LCD -----------------------------------------
;
; Los comandos que pueden ser ejecutados son:
;
LCD_CursorIncr ; Cursor en modo incrementar.
movlw b"00000110"
goto LCD_EnviaComando
LCD_Linea1 ; Cursor al principio de la Línea 1.
movlw b"10000000" ; Dirección 00h de la DDRAM
goto LCD_EnviaComando
LCD_Linea2 ; Cursor al principio de la Línea 2.
movlw b"11000000" ; Dirección 40h de la DDRAM
goto LCD_EnviaComando
LCD_Linea3 ; Cursor al principio de la Línea 3
movlw b"10010100" ; Dirección 14h de la DDRAM
goto LCD_EnviaComando
LCD_Linea4 ; Cursor al principio de la Línea 4
movlw b"11010100" ; Dirección 54h de la DDRAM
goto LCD_EnviaComando
LCD_PosicionLinea1 ; Cursor a posición de la Línea 1, a partir de la
iorlw b"10000000" ; dirección 00h de la DDRAM más el valor del
goto LCD_EnviaComando ; registro W.
LCD_PosicionLinea2 ; Cursor a posición de la Línea 2, a partir de la
iorlw b"11000000" ; dirección 40h de la DDRAM más el valor del
goto LCD_EnviaComando ; registro W.
LCD_PosicionLinea3 ; Cursor a posición de la Línea 3, a partir de la
iorlw b"10010100" ; dirección 00h de la DDRAM más el valor del
goto LCD_EnviaComando ; registro W.
LCD_PosicionLinea4 ; Cursor a posición de la Línea 4, a partir de la
iorlw b"11010100" ; dirección 40h de la DDRAM más el valor del
goto LCD_EnviaComando ; registro W.
LCD_OFF ; Pantalla apagada.
movlw b"00001000"
goto LCD_EnviaComando
LCD_CursorON ; Pantalla encendida y cursor encendido.
movlw b"00001110"
goto LCD_EnviaComando
LCD_CursorOFF ; Pantalla encendida y cursor apagado.
movlw b"00001100"
goto LCD_EnviaComando
LCD_Borra ; Borra toda la pantalla, memoria DDRAM y pone el
movlw b"00000001" ; cursor a principio de la línea 1.
goto LCD_EnviaComando
LCD_2Lineas4Bits5x7 ; Define la pantalla de 2 líneas, con caracteres
movlw b"00101000" ; de 5x7 puntos y conexión al PIC mediante bus de 4 bits.
; goto LCD_EnviaComando
; Subrutinas "LCD_EnviaComando" y "LCD_Caracter" ------------------------------------
;
; "LCD_EnviaComando". Escribe un comando en el registro del módulo LCD. La palabra de
; comando ha sido entregada a través del registro W. Trabaja en Modo Comando.
; "LCD_Caracter". Escribe en la memoria DDRAM del LCD el carácter ASCII introducido a
; a través del registro W. Trabaja en Modo Dato.
;
LCD_EnviaComando
bcf LCD_RS ; Activa el Modo Comando, poniendo RS=0.
goto LCD_Envia
LCD_Caracter
bsf LCD_RS ; Activa el "Modo Dato", poniendo RS=1.
call LCD_CodigoCGROM ; Obtiene el código para correcta visualización.
LCD_Envia
movwf LCD_GuardaDato ; Guarda el dato a enviar.
call LCD_EscribeLCD ; Primero envía el nibble alto.
swapf LCD_GuardaDato,W ; Ahora envía el nibble bajo. Para ello pasa el
; nibble bajo del dato a enviar a parte alta del byte.
call LCD_EscribeLCD ; Se envía al visualizador LCD.
btfss LCD_RS ; Debe garantizar una correcta escritura manteniendo
call Retardo_2ms ; 2 ms en modo comando y 50 µs en modo cáracter.
call Retardo_50micros
return
; Subrutina "LCD_CodigoCGROM" -----------------------------------------------------------
;
; A partir del carácter ASCII número 127 los códigos de los caracteres definidos en la
; tabla CGROM del LM016L no coinciden con los códigos ASCII. Así por ejemplo, el código
; ASCII de la "Ñ" en la tabla CGRAM del LM016L es EEh.
;
; Esta subrutina convierte los códigos ASCII de la "Ñ", "º" y otros, a códigos CGROM para que
; que puedan ser visualizado en el módulo LM016L.
;
; Entrada: En (W) el código ASCII del carácter que se desea visualizar.
; Salida: En (W) el código definido en la tabla CGROM.
;
LCD_CodigoCGROM
movwf LCD_Dato ; Guarda el valor del carácter y comprueba si es
LCD_EnheMinuscula ; un carácter especial.
sublw "ñ" ; ¿Es la "ñ"?
btfss STATUS,Z
goto LCD_EnheMayuscula ; No es "ñ".
movlw b"11101110" ; Código CGROM de la "ñ".
movwf LCD_Dato
goto LCD_FinCGROM
LCD_EnheMayuscula
movf LCD_Dato,W ; Recupera el código ASCII de entrada.
sublw "Ñ" ; ¿Es la "Ñ"?
btfss STATUS,Z
goto LCD_Grado ; No es "Ñ".
movlw b"11101110" ; Código CGROM de la "ñ". (No hay símbolo para
movwf LCD_Dato ; la "Ñ" mayúscula en la CGROM).
goto LCD_FinCGROM
LCD_Grado
movf LCD_Dato,W ; Recupera el código ASCII de entrada.
sublw "º" ; ¿Es el símbolo "º"?
btfss STATUS,Z
goto LCD_FinCGROM ; No es "º".
movlw b"11011111" ; Código CGROM del símbolo "º".
movwf LCD_Dato
LCD_FinCGROM
movf LCD_Dato,W ; En (W) el código buscado.
return
; Subrutina "LCD_DosEspaciosBlancos" y "LCD_LineaBlanco" --------------------------------
;
; Visualiza espacios en blanco.
LCD_LineaEnBlanco
movlw LCD_CaracteresPorLinea
goto LCD_EnviaBlancos
LCD_UnEspacioBlanco
movlw .1
goto LCD_EnviaBlancos
LCD_DosEspaciosBlancos
movlw .2
goto LCD_EnviaBlancos
LCD_TresEspaciosBlancos
movlw .3
LCD_EnviaBlancos
movwf LCD_Auxiliar1 ; (LCD_Auxiliar1) se utiliza como contador.
LCD_EnviaOtroBlanco
movlw " " ; Esto es un espacio en blanco.
call LCD_Caracter ; Visualiza tanto espacios en blanco como se
decfsz LCD_Auxiliar1,F ; haya cargado en (LCD_Auxiliar1).
goto LCD_EnviaOtroBlanco
return
; Subrutinas "LCD_ByteCompleto" y "LCD_Byte" --------------------------------------------
;
; Subrutina "LCD_ByteCompleto", visualiza el byte que almacena el registro W en el
; lugar actual de la pantalla. Por ejemplo, si (W)=b"10101110" visualiza "AE".
;
; Subrutina "LCD_Byte" igual que la anterior, pero en caso de que el nibble alto sea cero
; visualiza en su lugar un espacio en blanco. Por ejemplo si (W)=b"10101110" visualiza "AE"
; y si (W)=b"00001110", visualiza " E" (un espacio blanco delante).
;
; Utilizan la subrutina "LCD_Nibble" que se analiza más adelante.
;
LCD_Byte
movwf LCD_Auxiliar2 ; Guarda el valor de entrada.
andlw b"11110000" ; Analiza si el nibble alto es cero.
btfss STATUS,Z ; Si es cero lo apaga.
goto LCD_VisualizaAlto ; No es cero y lo visualiza.
movlw " " ; Visualiza un espacio en blanco.
call LCD_Caracter
goto LCD_VisualizaBajo
LCD_ByteCompleto
movwf LCD_Auxiliar2 ; Guarda el valor de entrada.
LCD_VisualizaAlto
swapf LCD_Auxiliar2,W ; Pone el nibble alto en la parte baja.
call LCD_Nibble ; Lo visualiza.
LCD_VisualizaBajo
movf LCD_Auxiliar2,W ; Repite el proceso con el nibble bajo.
; call LCD_Nibble ; Lo visualiza.
; return
; Subrutina "LCD_Nibble" ----------------------------------------------------------------
;
; Visualiza en el lugar actual de la pantalla, el valor hexadecimal que almacena en el nibble
; bajo del registro W. El nibble alto de W no es tenido en cuenta. Ejemplos:
; - Si (W)=b"01010110", se visualizará "6".
; - Si (W)=b"10101110", se visualizará "E".
;
LCD_Nibble
andlw b"00001111" ; Se queda con la parte baja.
movwf LCD_Auxiliar1 ; Lo guarda.
sublw 0x09 ; Comprueba si hay que representarlo con letra.
btfss STATUS,C
goto LCD_EnviaByteLetra
movf LCD_Auxiliar1,W
addlw "0" ; El número se pasa a carácter ASCII sumándole
goto LCD_FinVisualizaDigito ; el ASCII del cero y lo visualiza.
LCD_EnviaByteLetra
movf LCD_Auxiliar1,W
addlw "A"-0x0A ; Sí, por tanto, se le suma el ASCII de la "A".
LCD_FinVisualizaDigito
goto LCD_Caracter ; Y visualiza el carácter. Se hace con un "goto"
; para no sobrecargar la pila.