JHD162A y otros LCD que incluyen un HD44780

El JHD162A es uno de los exhibidores de cristal líquido más utilizados, permite desplegar 2 renglones cada uno con 16 caracteres e incluye un manejador HD44780 o equivalente. Aquí encontrarás circuitos y códigos de ejemplo para utilizarlo con el dsPIC30F4013 y con el PIC16F887. Ya que escribí los códigos en lenguaje C, te resultará fácil adaptarlos para otros procesadores; básicamente se requieren 3 terminales de un puerto de salida, y 4 cuatro terminales de un puerto bidireccional.

Fotos del LCD controlado por el dsPIC30F4013 y por el PIC16F887.
Figura1. Fotos del LCD controlado por el dsPIC30F4013 y por el PIC16F887.

El circuito de prueba para el dsPIC30F4013 se muestra en la figura 2, y lcd.c se lista a continuación:

#include <xc.h>
#pragma config FOSFPR = FRC
#pragma config WDT = WDT_OFF

void confBusLCD(void);
void comando(unsigned int com);
void dato(unsigned int dat);

int main (void)
{
 char r_uno[]="dsPIC30F4013";
 char r_dos[]="controla JHD162A";
 int n;
 
 TRISF=0xFF8F;  // RF4, RF5 y RF6 como salidas.
 confBusLCD();  // Configura el LCD para usar un bus de 4 bits.
 comando(0x0C); // Enciende el LCD sin cursor.
 comando(0x28); // 2 renglones, caracteres de 5 x 8, nuevamente bus de 4 bits.
 comando(0x01); // Limpia el exhibidor y pone las direcciones a 0.
 comando(0x82); // Apunta a la tercera columna del primer renglón.
 for (n=0; n<12; n++) dato(r_uno[n]); // Escribe el contenido de r_uno.
 comando(0xC0); // Apunta a la primera columna del segundo renglón.
 for (n=0; n<16; n++) dato(r_dos[n]); // Escribe el contenido de r_dos.
 while(1);
}

void confBusLCD(void)
{
 int t, estado;
 
 // Asigné ceros a t para producir retardos pequeños.
 PORTFbits.RF4=0; t=0;  // RS indica comando.
 PORTFbits.RF5=1; t=0;  // RW indica leer del LCD.
 do
 {
  PORTFbits.RF6=1; t=0; // Sube el habilitador E.
  estado=(PORTD&0x8);   // Lee bandera de ocupado (BF).
  PORTFbits.RF6=0; t=0; // Baja el habilitador E.
 } while (estado!=0);   // Espera a que el LCD esté desocupado.
 PORTFbits.RF5=0;       // RW indica escribir al LCD.
 TRISD=0xFFF0;          // RD3 a RD0 como salidas.
 PORTD=0x2;             // Ordena usar bus de 4 bits.
 PORTFbits.RF6=1; t=0;  // Sube el habilitador E.
 PORTFbits.RF6=0; t=0;  // Baja el habilitador E.
 TRISD=0xFFFF;          // Puerto D como entrada.
}

void comando(unsigned int com)
{
 int t, estado;
 
 // Asigné ceros a t para producir retardos pequeños.
 PORTFbits.RF4=0; t=0;  // RS indica comando.
 PORTFbits.RF5=1; t=0;  // RW indica leer del LCD.
 do
 {
  PORTFbits.RF6=1; t=0; // Sube el habilitador E.
  estado=(PORTD&0x8);   // Lee bandera de ocupado (BF).
  PORTFbits.RF6=0; t=0; // Baja el habilitador E.
  PORTFbits.RF6=1; t=0; // Sube el habilitador E.
  PORTFbits.RF6=0; t=0; // Baja el habilitador E.
 } while (estado!=0);   // Espera a que el LCD esté desocupado.
 PORTFbits.RF5=0;       // RW indica escribir al LCD.
 TRISD=0xFFF0;          // RD3 a RD0 como salidas.
 PORTD=(com>>4);
 PORTFbits.RF6=1; t=0;  // Sube el habilitador E.
 PORTFbits.RF6=0; t=0;  // Baja el habilitador E.
 PORTD=com;
 PORTFbits.RF6=1; t=0;  // Sube el habilitador E.
 PORTFbits.RF6=0; t=0;  // Baja el habilitador E.
 TRISD=0xFFFF;          // Puerto D como entrada.
}

void dato(unsigned int dat)
{
 int t, estado;
 
 // Asigné ceros a t para producir retardos pequeños.
 PORTFbits.RF4=0; t=0;  // RS indica comando.
 PORTFbits.RF5=1; t=0;  // RW indica leer del LCD.
 do
 {
  PORTFbits.RF6=1; t=0; // Sube el habilitador E.
  estado=(PORTD&0x8);   // Lee bandera de ocupado (BF).
  PORTFbits.RF6=0; t=0; // Baja el habilitador E.
  PORTFbits.RF6=1; t=0; // Sube el habilitador E.
  PORTFbits.RF6=0; t=0; // Baja el habilitador E.
 } while (estado!=0);   // Espera a que el LCD esté desocupado.
 PORTFbits.RF4=1; t=0;  // RS indica dato.
 PORTFbits.RF5=0;       // RW indica escribir al LCD.
 TRISD=0xFFF0;          // RD3 a RD0 como salidas.
 PORTD=(dat>>4);
 PORTFbits.RF6=1; t=0;  // Sube el habilitador E.
 PORTFbits.RF6=0; t=0;  // Baja el habilitador E.
 PORTD=dat;
 PORTFbits.RF6=1; t=0;  // Sube el habilitador E.
 PORTFbits.RF6=0; t=0;  // Baja el habilitador E.
 TRISD=0xFFFF;          // Puerto D como entrada.
}

Circuito de prueba para el JHD162A con el dsPIC30F4013 como controlador.Figura 2. Circuito de prueba para el JHD162A con el dsPIC30F4013 como controlador. En mi caso usé un LCD sin iluminación trasera.

El circuito de prueba para el PIC16F887 se muestra en la figura 3, y su versión de lcd.c se lista a continuación:

#include <xc.h>
#pragma config FOSC = XT
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config IESO = OFF
#pragma config FCMEN = OFF
// Usa la directiva siguiente dependiendo de tu programador y circuito.
// #pragma config LVP = OFF

void confBusLCD(void);
void comando(unsigned int com);
void dato(unsigned int dat);

int main (void)
{
 char r_uno[]="PIC16F887";
 char r_dos[]="controla JHD162A";
 int n;
 
 TRISE=0xF8;    // RE0, RE1 y RE2 como salidas.
 ANSEL=0;       // Terminales compartidas como digitales.
 confBusLCD();  // Configura el LCD para usar un bus de 4 bits.
 comando(0x0C); // Enciende el LCD sin cursor.
 comando(0x28); // 2 renglones, caracteres de 5 x 8, nuevamente bus de 4 bits.
 comando(0x01); // Limpia el exhibidor y pone las direcciones a 0.
 comando(0x83); // Apunta a la cuarta columna del primer renglón.
 for (n=0; n<9; n++) dato(r_uno[n]); // Escribe el contenido de r_uno.
 comando(0xC0); // Apunta a la primera columna del segundo renglón.
 for (n=0; n<16; n++) dato(r_dos[n]); // Escribe el contenido de r_dos.
 while(1);
}

void confBusLCD(void)
{
 int t, estado;
 
 // Asigné ceros a t para producir retardos pequeños.
 PORTEbits.RE0=0; t=0;  // RS indica comando.
 PORTEbits.RE1=1; t=0;  // RW indica leer del LCD.
 do
 {
  PORTEbits.RE2=1; t=0; // Sube el habilitador E.
  estado=(PORTD&0x80);  // Lee bandera de ocupado (BF).
  PORTEbits.RE2=0; t=0; // Baja el habilitador E.
 } while (estado!=0);   // Espera a que el LCD esté desocupado.
 PORTEbits.RE1=0;       // RW indica escribir al LCD.
 TRISD=0x0F;            // RD7 a RD3 como salidas.
 PORTD=0x20;            // Ordena usar bus de 4 bits.
 PORTEbits.RE2=1; t=0;  // Sube el habilitador E.
 PORTEbits.RE2=0; t=0;  // Baja el habilitador E.
 TRISD=0xFF;            // Puerto D como entrada.
}

void comando(unsigned int com)
{
 int t, estado;
 
 // Asigné ceros a t para producir retardos pequeños.
 PORTEbits.RE0=0; t=0;  // RS indica comando.
 PORTEbits.RE1=1; t=0;  // RW indica leer del LCD.
 do
 {
  PORTEbits.RE2=1; t=0; // Sube el habilitador E.
  estado=(PORTD&0x80);  // Lee bandera de ocupado (BF).
  PORTEbits.RE2=0; t=0; // Baja el habilitador E.
  PORTEbits.RE2=1; t=0; // Sube el habilitador E.
  PORTEbits.RE2=0; t=0; // Baja el habilitador E.
 } while (estado!=0);   // Espera a que el LCD esté desocupado.
 PORTEbits.RE1=0;       // RW indica escribir al LCD.
 TRISD=0x0F;            // RD7 a RD3 como salidas.
 PORTD=com;
 PORTEbits.RE2=1; t=0;  // Sube el habilitador E.
 PORTEbits.RE2=0; t=0;  // Baja el habilitador E.
 PORTD=(com<<4);
 PORTEbits.RE2=1; t=0;  // Sube el habilitador E.
 PORTEbits.RE2=0; t=0;  // Baja el habilitador E.
 TRISD=0xFF;            // Puerto D como entrada.
}

void dato(unsigned int dat)
{
 int t, estado;
 
 // Asigné ceros a t para producir retardos pequeños.
 PORTEbits.RE0=0; t=0;  // RS indica comando.
 PORTEbits.RE1=1; t=0;  // RW indica leer del LCD.
 do
 {
  PORTEbits.RE2=1; t=0; // Sube el habilitador E.
  estado=(PORTD&0x80);  // Lee bandera de ocupado (BF).
  PORTEbits.RE2=0; t=0; // Baja el habilitador E.
  PORTEbits.RE2=1; t=0; // Sube el habilitador E.
  PORTEbits.RE2=0; t=0; // Baja el habilitador E.
 } while (estado!=0);   // Espera a que el LCD esté desocupado.
 PORTEbits.RE0=1; t=0;  // RS indica dato.
 PORTEbits.RE1=0;       // RW indica escribir al LCD.
 TRISD=0x0F;            // RD7 a RD3 como salidas.
 PORTD=dat;
 PORTEbits.RE2=1; t=0;  // Sube el habilitador E.
 PORTEbits.RE2=0; t=0;  // Baja el habilitador E.
 PORTD=(dat<<4);
 PORTEbits.RE2=1; t=0;  // Sube el habilitador E.
 PORTEbits.RE2=0; t=0;  // Baja el habilitador E.
 TRISD=0xFF;            // Puerto D como entrada.
}

Circuito de prueba para el JHD162A con el PIC16F887 como controlador.

Figura 3. Circuito de prueba para el JHD162A con el PIC16F887 como controlador. La resistencia en la terminal 36 (PGM) sirve para evitar que el microcontrolador entre accidentalmente en modo de programación a bajo voltaje.

El propósito de las terminales de conexión del JHD162A se muestra en la tabla 1, mientras que las instrucciones básicas para su manejo están en la tabla 2.

Tabla 1. Terminales del JHD162A
Terminal Símbolo Función
1 VSS Tensión de referencia (GND, 0V).
2 VDD Tensión de alimentación (5 V).
3 VEE Tensión para ajustar el contraste.
4 RS Selector de registro.
RS=0 instrucciones.
RS=1 datos.
5 RW Selector de operación.
RW=1 escribir en el LCD.
RW=0 leer del LCD.
6 E Habilita el uso del bus.
7 DB0 Bus de datos: por él pasan la información a ser desplegada, los comandos y el estado del exhibidor.
El JHD162A se puede configurar para usar las 8 líneas o solamente 4, de DB7 a DB4.
8 DB1
9 DB2
10 DB3
11 DB4
12 DB5
13 DB6
14 DB7
15 LED+ Ánodo del led para iluminación trasera.
16 LED- Cátodo del led para iluminación trasera.
Tabla 2. Instrucciones para el LCD
Instrucción RS RW DB7 – DB0
Leer bandera de ocupado y dirección 0 1 FAAAAAAA
Encender 0 0 00001DCB
Configurar 0 0 001LNFxx
Modo de entrada 0 0 000001DS
Limpia el exhibidor 0 0 00000001
A casa 0 0 0000001x
Desplaza el cursor o la ventana visible 0 0 0001SRxx
Establece la dirección para la DDRAM 0 0 1AAAAAAA
Establece la dirección para la GGRAM 0 0 01GGGGGG
Escribe un dato 1 0 Dato
Lee un dato 1 1 Dato

Observa que:

  • El empleo del LCD implica comunicación bidireccional con el procesador que lo utiliza. Resulta evidente que el uC o uP envía al LCD lo que se quiere desplegar, pero el LCD debe avisar al circuito que lo utiliza cuándo puede recibir un comando o dato. Por lo anterior el puerto DB7-DB0 y su contraparte en el procesador son bidireccionales.
  • El flujo en las terminales de control RS, RW y E siempre va del procesador al LCD.
  • Se le indica al LCD a través de RW si su puerto DB7-DB0 debe operar como entrada o como salida.
  • El LCD ejecuta una tarea hasta que se le indica por medio de la terminal E.
  • Los comandos y datos “se parten” en dos y se envía primero la parte más significativa para poder usar sólo de DB7 a DB3, en lugar de DB7 a DB0.
  • Almacené las cadenas por visualizar en el LCD en dos arreglos: r_uno y r_dos (líneas 26 y 27 en el programa para el dsPIC).
  • Además de la función principal, los programas tienen 3 funciones: confBusLCD, comando y dato.
    El único comando que el LCD puede ejecutar en cualquier momento es el que sirve para leer la bandera de ocupado. Éste consiste en poner a 0 la línea RS, poner a 1 la línea RW, levantar el habilitador, leer el valor de la bandera de ocupado (BF, busy flag) en la terminal DB7 y bajar el habilitador. Si la bandera vale 0, el LCD puede recibir un dato para desplegar o un comando cualquiera, por ejemplo borra toda lo pantalla, o haz parpadear el cursor. Por lo anterior confBusLCD, comando y dato inician preguntando reiteradamente por el estado del LCD hasta que esté se encuentre desocupado: líneas 47 a 54, 68 a 77 y 94 a 103 respectivamente. En confBusLCD pregunto por la bandera de ocupado como si estuviera conectado el bus completo; mientras que para comando y dato hago la pregunta sabiendo que sólo alambré de DB7 a DB3.
  • El significado de las constantes L, N, F y x en la instrucción Configurar es: L=1 bus de 8 bits, L=0 bus de 4 bits; N=1 enciende los 2 renglones, N=0 un renglón; F=1 caracteres de 5 x 10 puntos, F=0 caracteres de 5 x 8 puntos; donde hay una x no importa si se asigna 0 ó 1. Así obtuve la constante 0x20; pero por la forma en que alambré, sólo asigno un 0x2 al puerto D en la función confBusLCD. Es decir, confBusLCD configura el JHD162A para usar un bus de 4 bits.
  • Los comentarios en los códigos indican el significado de las demás constantespara este caso particular, pero si quieres más información puedes consultar la hoja de datos del HD44780 (la tabla de comandos está en las páginas 24 y 25). En el capítulo 9 de Microcontroladores PIC16, fundamentos y aplicaciones encontrarás una explicación detallada y más ejemplos.
  • Los cambios entre los programas para el dsPIC30F4013, el PIC16F887 u otro procesador obedecen exclusivamente al cambio de puertos y a las líneas empleadas de cada puerto.
  • La ventaja más grande de leer la bandera de ocupado, en lugar de usar rutinas de retardo para esperar a que el LCD haga la tarea que se le asigna, es que: el programa trabaja independientemente de la velocidad del procesador. Considera que en el ejemplo con el dsPIC30F4013 usé su oscilador interno a 7.37 MHz, mientras que con el PIC16F887 usé un cristal a 1.8432 MHz. En las figuras 4 y 5 puedes ver fragmentos de la señal E en el circuito del dsPIC cuando usa su oscilador interno rápido (directiva #pragma config FOSFPR = FRC) y el interno de bajo consumo de energía o lento (directiva #pragma config FOSFPR = LPRC).
Fragmentos de la señal E en el circuito del dsPIC, oscilador interno a 7.37 MHz.
Figura 4. Fragmentos de la señal E en el circuito del dsPIC, oscilador interno a 7.37 MHz. Observa que la base de tiempo del osciloscopio está en 5 us.
Fragmentos de la señal E en el circuito del dsPIC, oscilador interno a 512 kHz.
Figura 5. Fragmentos de la señal E en el circuito del dsPIC, oscilador interno a 512 kHz. Observa que la base de tiempo del osciloscopio está en 50 us.

Hasta la próxima.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *