Display's gráfico Nokia 5110 - ¿ Cómo utilizarlos ?


Por esas cuestiones locas de la cantidad producida y la baja demanda actual por parte de los compradores estas pantallas utilizadas en sus años dorados por la mayoría de los teléfonos Nokia de primera generación GSM e incluso de algunos TDMA mas antiguos hoy se consiguen por una fracción del dinero que cuesta, por ejemplo una pantalla alfanumérica de 4 líneas y 20 caracteres. Y como si ya de por sí lo económico no fuera suficiente motivo para optar por ellas encima son seriales por lo que la comunicación entre un sistema electrónico y estas puede realizarse con sólo cinco cables de datos. Tengamos siempre en mente que las pantallas alfanuméricas basadas en controlador HD4478x  necesitan como mínimo 6 hilos de comunicación y se recomienda 7 y que una pantalla gráfica de 128 x 64 requiere de DOCE hilos de comunicación por lo que tener una pequeña pantalla, de buena definición, bajo consumo y pocos cables de conexión es muy interesante!. Aprendamos mas sobre ellas y a emplearlas correctamente.

Aquí vemos un ejemplar de esta pantalla montada en un circuito impreso que permite utilizarla apropiadamente, claro está que no se encontraba configurada de esta forma cuando formaba parte de un teléfono de bolsillo pero por suerte con los millones de pantallas disponibles que hay en china gracias a los excedentes de stock hoy tenemos estos módulos que son además de económicos y de fácil adquisición muy simples de montar por su formato y aún mas fácil de utilizar.

Hay ocho conexiones eléctricas entre el módulo de la pantalla y el resto del circuito, dos de estas conexiones son las terminales destinadas a la alimentación representadas por masa o GND en el terminal número 8 y positivo o VCC en el terminal 6. Hay que prestar atención porque algunas pantallas vienen con la electrónica necesaria para funcionar tanto en sistemas de 5V como de 3,3V mientras que la gran mayoría sólo admiten 3,3V de echo la que empleo yo para estas pruebas, que no es la de la foto precedente admite únicamente 3,3 voltios. En el caso de las pantallas que únicamente funcionen a 3,3V los pines de datos no son tolerantes a 5V y muy posiblemente al poco tiempo de parecer funcionar bien termine por averiarse o directamente deje de funcionar. Para compatibilizar una de estas pantallas con un microcotrolador a 5V o un Arduino por ejemplo es suficiente con conectar intercalado en cada vía una resistencia de 10K la que resolverá eficientemente la caída de tensión requerida. El terminal 7, rotulado como BL, permite encender la luz de fondo. Para que esto suceda es necesario poner esta conexión a masa y para ello se recomienda un transistor NPN gobernado directamente desde la aplicación microcontrolada. Los cinco terminales o pines restantes se emplean para entablar la comunicación entre el sistema y la pantalla. Dicha comunicación es de un solo sentido, por supuesto hacia la pantalla pero no se puede indagar estado alguno ni recibir de ella ningún tipo de datos, sólo se pueden enviar o escribir datos. El terminal mas importante de los cinco es el de selección de dispositivo, o CE (también llamado SCE) cuyo estado lógico es negado por lo que mientras esté en alto todo lo que se transmita por el bus será inocuo al controlador de la pantalla mientras que cuando este pin este a nivel bajo el controlador de la pantalla prestará atención a la información entrante. Esto incluso vale para el terminal de reset el cual es informático o lógico y no eléctrico. De esta forma podemos tener dos o mas pantallas en total paralelo de las líneas RES, D/C, SCLK y SDIN y únicamente dejamos individualizadas las líneas de SCE de cada pantalla para determinar en cual queremos operar.

La condición de reset se produce cuando el pin correspondiente va a nivel lógico bajo ya que al igual que SCE también se encuentra negado o invertido. Para que el controlador de la pantalla acepte la condición de reset además de bajarse la línea RES se debe asegurar previamente en bajo SCE, caso contrario el reset no tendrá efecto alguno. Vuelta en alto la línea RES el controlador de la pantalla vuelve a esperar el bit entrante como el de mayor peso a recibir sin importar que durante el tiempo que apareció la condición de reset estuviere en medio de una recepción que ha quedado trunca. Dependiendo del estado de la línea D/C el controlador le dará tratamiento de comando al dato entrante de encontrarse D/C en bajo o bien lo mandará a la memoria de visualización de encontrarse dicha señal en alto. La línea de reloj o SCLK le indica por medio de flancos ascendentes cuando leer cada bit presente en la línea SDIN por tal motivo es en ese preciso instante que se debe asegurar estado estable de SDIN, cuando sucede el flanco ascendente en SCLK. Hay dos formas de enviar datos al controlador en forma puntual (un único byte) o en forma masiva (uno o mas bytes). La forma puntual permite que un byte ya sea de control o dato viaje y ahí finalice la comunicación.

Seleccionada la pantalla bajando SCE y estableciendo correctamente el estado de D/C conforme van sucediendo los pulsos ascendentes (de cero a uno) de la línea de reloj o SCLK se van leyendo los bits enviados hacia la pantalla empezando por el de mayor peso. Llegado al bit 0 se da por terminada la comunicación y se vuelve en alto la línea SCE.

La otra forma de enviar datos hacia la pantalla es por medio de una comunicación bulk o masiva la cual habilita el envío de mas de un byte sin volver a la condición de parada entre cada envío como se observa en la siguiente gráfica de tiempos:

Vemos en esta gráfica que si bien comienza como la anterior ni bien termina de procesarse el flanco ascendente del bit cero del primer byte enviado se acomoda D/C de ser necesario y se procede al envío del byte siguiente y repite indefinidamente.

Estas pantallas operan a una velocidad de 4MHz por lo que tienen un tiempo de clock de 250 nano segundos o un cuarto de micro segundo, un tiempo mas que corto para la mayoría de las aplicaciones cotidianas! Entendida la interfase eléctrica comencemos con la estructura interna:

Philips ha desarrollado un controlador específico, el PCD8544 y como nos tiene acostumbrados tenemos una excelente hoja de datos disponible en este link. Dentro del chip controlador tenemos por un lado la sección dedicada a la generación de los voltajes necesarios para una correcta polarización del display LCD y su posterior etapa de manejo de filas y columnas. Los retenedores o latches y los registros de desplazamientos permiten explorar efectivamente la pantalla a fin que para el ojo humano esté toda encendida apropiadamente. El bloque de memoria se encarga de almacenar los ceros y unos que se convertirán en pixeles apagados o encendidos respectivamente y permitirán representar en imágenes los datos recibidos desde la aplicación. Los contadores de direcciones permiten determinar en que posición o dirección de memoria va cada byte recibido ayudando la tarea de control desde el exterior. Por último y muy importante el buffer o etapa de entradas permite recibir señales desde fuera del chip y con ellas guardar datos en la RAM para ser mostrados en pantalla. El oscilador se encarga de las distintas temporizaciones tanto de las comunicaciones como del mantenimiento de la memoria dinámica e incluso los sistemas de generación de tensiones positivas y negativas (bajo cero) para el correcto funcionamiento del LCD. Los pines rotuladosT1 a T4 sirven sólo a fines de evaluación o testeo del sistema mas no están conectados a las placas de pantallas.

Vemos en la imagen precedente la correspondencia entre el bloque de memoria (amarillo) y la pantalla LCD (gris) donde cada bit de dicha memoria se corresponde con un pixel en la pantalla. La pantalla está formada por 84 columnas y 48 filas de pixeles organizados en bytes. Por ende tenemos 84 x 48 bits / 8 bits (1 byte) = 504 bytes de memoria. Por fortuna para el desarrollador la manera en que se apuntan estas posiciones de memoria está automatizada a través de los contadores de direccionamiento que vimos en los bloques internos del controlador mas arriba en este texto y si bien tenemos un comando que permite posicionar el cursor en determinada dirección para luego escribir en ella si no enviamos ese comando de posicionamiento del cursor cada vez que se envíe un dato a la memoria la posición apuntada aumentará en uno automáticamente o sea que con solo enviar datos el barrido de carga de los mismos se produce solo. No exite un área de memoria no visible como sucede en los controladores alfauméricos de Hitachi ni tampoco existen sets de caracteres internos en el controlador. Lo primero implica que para desplazar la imagen en pantalla, animarla o darle movimiento se debe redibujar toda vez, no hay áreas no visibles que al desplazar el offset se vuelvan visibles y lo segundo implica que para hacer aparecer en pantalla un simple "HOLA!" no basta con eniar las cuatro letras y el signo de exclamación sino que deberemos "dibujar" todo punto por punto en grupos de ocho puntos. Se puede direccionar cualquiera de las 84 columnas existentes pero sólo se puede acceder a las filas por medio de su byte completo. En otras palabras no tenemos la posibilidad de direccionar la fila 43 puntualmente pero sí podemos recargar el byte que la contiene o sea el comprendido en la página 5 de memoria. Si bien en esta instancia no parece algo negativo este detalle lo cierto es que en la práctica sólo podremos referirnos a "renglones" completos de 8 bits o filas en vez de a una línea de píxeles en particular. O bien deberemos destinar espacio de memoria de trabajo en la aplicación que comande esta pantalla para hacer manejos de bits que permitan moverse con mas libertad. En la práctica esto no es necesario y con las capacidades propias del controlador alcanza para la mayoría de las aplicaciones.

#include "nokia5110.h"

void setup()
{
   LCDReset();
   delay(500);
}

void loop()
{
   for(int ciclo=0;ciclo<504;ciclo++) LCDEnviar(DATO, 0b00010001);
   while(true);
}

 

Aquí un ejemplo mas que simple de las bondades del incremento automático en la dirección de memoria, para completar toda la pantalla que ya se dijo tiene 504 bytes basta con un simple ciclo for por dicha cantidad de iteraciones. En cada uno de estos ciclos enviamos el dato a la memoria del LCD y éste será motrado de inmediato. Para formar esas líneas alcanza con enviar de los ocho bits que forman un byte dos bits en 1 (el de menor peso de cada nibble) y con eso se generan unos lindos renglones. Pueden bajar este simple programa haciendo clic acá.

Como ya se mencionó tenemos una línea o cable destinado a indicarle al display si lo que va a recibir es un dato o un comando y esa línea es D/C veremos ahora en la tabla de funciones como trabaja y una descripción detallada de cada una de estas instrucciones:

La primer función, Write data, es la única en donde D/C se encuentra en alto y nos permite enviar un byte a la memoria del controlador y por a la pantalla en forma de imagen. Dicho byte debe ser pasado al controlador por medio de los bits de datos D7 a D0 respectivamente y por tratarse de un dato a mostrar es que vemos en alto la línea D/C. Todas las demás instrucciones o funciones son con dicha línea en bajo.

De las restantes siete funciones Philips decidió agruparlas según la tarea que realicen. Las funciones destinadas al control del display y a la posición del cursor o direccionamiento de memoria se las denomina funciones básicas y son accesibles siempre que el bit representado con una H dentro de Function Set esté en cero. En tanto las funciones dispuestas para configurar parámetros eléctricos del display como la temperatura (por calidez de luz), la polarización y el contraste sólo son accesibles estando el bit H en la instrucción Function Set en uno.

Pero Function Set no se limita a disponer que set de comandos se va a utilizar sino que además tiene otros dos bits muy importantes. El primero de ellos PD o "Power Dowm" es el encargado de pasar el display a bajo consumo (al poner un uno en este bit) para lo cual apaga los osciladores, los generadores e inversores de tensión y por todo ello la pantalla se apaga quedando igual que cuando está desconectada. Pero el contenido de la memoria (la imagen formada en 0's y 1's) se mantiene por lo que al volver este bit a cero el sistema se reactiva, se vuelven a encender osciladores y generadores de tensión y la imagen vuelve a aparecer tal cual estaba antes de entrar en bajo consumo. El otr bit disponible, V, determina en que sentido se realiza el incremento automático de dirección de memoria luego de recibir un byte y aquí hay que hacer una aclaración muy importante porque he visto en otros sitios que interpretan a este bit de manera errónea atribuyéndole la función de apaisar o girar el contenido de pantalla cuando nada tiene que ver con eso! Estando este bit en cero el direccionamiento se produce horizontalmente de izquierda a derecha y cuando se completa la primer fila de 84 columnas se pasa a la siguiente fila de otras 84 columnas y así hasta completar la sexta fila. Al completarse la última palabra de la última fila, lo que sería el byte final el cursor vuelve a comenzar desde la parte superior izquierda tal como muestra la siguiente imagen:

En cambio cuando el bit V de Function Set se encuentra en uno el direccionamiento es vertical haciendo que el primer byte recibido quede en el primer byte del banco 0 pero el siguiente registro NO va a guardarse en el segundo byte del banco 0 sino en el primer registro del banco 1. Y el tercer byte será ubicado en el primer registro del banco 2 y así hasta el byte sexto que quedará en el primer registro de memoria del último banco o fila. El siguiente byte recibido se guardará en el segundo registro del banco o fila 0 y así se vuelve a repetir una y otra vez tal como muestra la siguiente imagen:

Nótese que la pantalla sigue estando a lo ancho y no cambian de sitio las referencias X e Y sino que cambia la secuencia de llenado de la memoria del controlador conforme van arribando los bytes por los pines de comunicación. En este video pueden ver en funcionamiento un programa que precisamente muestra en pantalla un texto cargado con V en 0 y con V en 1 según se presione cada botón. Por supuesto el programa está disponible para ser bajado haciendo clic acá. En general la pantalla se opera con V en 0 y de echo es como el fabricante denomina al modo V=0 Normal y al V=1 Vertical.

La función "Display control" permite establecer el valor de dos bits, D y E y la combinación de ambos establece si la imagen se verá normal (positivo) o invertida (negativo). El bit D muestra u oculta el contenido de la memoria en pantalla. NO borra la memoria, no es un ClearScreen o algo así, la memoria sigue intacta sólo que no muestra en pantalla el contenido de la misma cuando se encuentra en 0. En tanto el bit E permite invertir la representación de la pantalla básicamente poniendo en negro todo pixel cuyo dato sea un 0 y en blanco todo pixel cuyo dato sea un 1. Por ende: si invertimos la pantalla estando D en 0 tendremos TODA la pantalla ennegrecida! Podemos ver el efecto de estos dos bits en este video y pueden bajar el programa cargado para el ejemplo haciendo clic acá.

Continuando con las funciones básicas (bit H=0) Set Y Address of RAM nos permite establecer el cursor verticalmente seleccionando en cual de los seis bancos de memoria o renglones de ocho bits de ancho queremos que se sitúe por tal sólo admite valores entre 0 y 5 donde 0 es el de la parte superior de la pantalla y 5 el de la parte inferior. En tanto Set X Address of RAM nos permite hacer lo propio con la columna donde queremos posicionar el cursor y aquí si es bit a bit puesto que los bytes de memoria están organizados de esa manera como se vio en imágenes previas de la memoria. Por tal motivo esta función admite cualquier valor entre 0 y 83. Si bien en la librería que creé para utilizar estas pantallas la función gotoXY toca ambas a la vez para posicionar el cursor en un byte predeterminado nadie le prohíbe a uno hacer uso individual de éstas aunque ahora no se me ocurre necesidad de hacerlo pero uno nunca sabe que puede necesitar en el futuro.

Las siguientes funciones permiten al programador establecer tres parámetros generalmente al inicio del programa para la correcta visualización de la imagen. Esto es necesario porque no todos los LCD's tienen las mismas características eléctricas ni ópticas y dependiendo de ellas es que se deben ajustar estos parámetros. Como Philips diseño el controlador para ser empleado con mas de una pantalla producida por mas de un fabricante es muy positivo tener estos valores ajustables con tanta facilidad y para acceder a ellos primero debemos establecer H en 1 y luego ajustar el contenido de ellos. Es buena costumbre si se encontró un valor como en este caso H en 0 volverlo a ese estado al dejar de ocuparlo. Por lo que administrar estas funciones nos va a requerir adicionalmente de dos sentencias mas, primero subir H y luego volver a bajarla.

La función "Temperature control" permite seleccionar el coeficiente de temperatura dependiendo del voltaje del LCD. Esta función dispone de dos bits que se pueden poner en 0 o 1 combinadamente por lo que nos dá cuatro opciones de valores de 0 a 3. En este video pueden ver como afecta a la imagen el ajuste de TEMP. También pueden bajar el programa para realizar esta experiencia haciendo clic acá.

A menudo se asocia la polarización del LCD como el factor de ángulo de visión permitiendo ajustar con este parámetro la mejor visión posible desde donde se encuentra el observador, incluso algunos televisores LCD de la primera generación tenían este parámetro ajustable. Por supuesto que Philips también tiene esto y la función que lo permite regular es "BIAS system". Disponemos de tres bits para toquetear y por ende combinados nos da siete posibles posiciones o ajustes. En la pantalla que usé para las pruebas la cual tiene sus heridas de guerra desde sobre exposición al calor y al sol hasta caídas y mojaduras (si, la he cuidado poco!) no vi grandes cambios, les ofrezco que ustedes saquen sus propias conclusiones mirando este video y pueden bajarse el programa para ensayarlo en sus laboratorios haciendo clic acá.

El último parámetro disponible y curiosamente el único de todos que suele poder acceder el usuario final de la aplicación a través de algún menú de configuración o algo así es el que ajusta el contraste o voltaje de polarización del LCD y la función que permite hacerlo siembre con el bit H=1 es "Set VOP"; con siete bits de margen dándonos 128 posibles valores entre 0 y 127 aunque no suelen usarse valores por encima de 110 ni por debajo de 50 porque ya queda demasiado oscuro o demasiado lavado respectivamente. Como no podía ser de otra forma podemos ver este parámetro en funcionamiento en este video y pueden bajarse el programa para ensayarlo en sus laboratorios haciendo clic acá.

Hasta aquí la explicación de la pantalla, su controlador y la descripción detallada de parámetros y como funcionan. Hay que remarcar que estas pantallas sólo muestran imágenes y por ende las tres pantallas mostradas abajo tienen lo mismo, puntos en marcados (en negro) y puntos sin marcar (apagados, que no se ven).

En los tres ejemplos tenemos un mapa de bits conformado por 504 bytes donde algunos bits están en cero y otros están en uno; cada bit en uno se corresponde a un punto negro en la pantalla. Nadie les prohíbe hacerse un set de caracteres mas elaborado que el del clásico 7x5 que hice en la librería nokia5110.h pueden incluso hacer letras del tamaño de la empleada en el logo de mi sitio, en la pantalla de la izquierda, el tema es ponerse y hacerlo; y tener presente que al estar estructuradas en franjas o bloques de ocho bits de ancho estas pantallas van a necesitar hacer al menos una tabla por cada bloque de ocho bits vertical y al menos un byte por cada columna. Incluso pueden hacer segmentos facetados como los empleados en displays de 7 segmentos para representaciones numéricas. En lo que respecta a este punto las únicas limitantes son la imaginación y capacidad del programador y el espacio disponible dentro del microcontrolador.

Por tanto el set de caracteres se logra enviando a la memoria del LCD cinco bytes de información mas uno vacío de separación entre caracteres por cada letra que queramos que aparezca en pantalla.  Para que aparezca en pantalla HOLA tendremos que hacer al menos seis envíos por letra o sea 24 envíos. Estos son los cinco bytes que forman la H {0x7f, 0x08, 0x08, 0x08, 0x7f}, estos los que forman la O {0x3e, 0x41, 0x41, 0x41, 0x3e}, estos forman la L {0x7f, 0x40, 0x40, 0x40, 0x40} y estos la A {0x7e, 0x11, 0x11, 0x11, 0x7e}. Veamos el paso a paso:

Inicialmente tenemos la pantalla sin nada escrito y el cursor se encuentra apuntando al primer byte.

Enviamos al LCD el dato 0x7F el cual en binario representa 01111111 (todos sus bits en 1 excepto el de mayor peso, como vemos en la imagen aparece el primero de los lados de la H

Enviamos ahora el dato 0x08 visto en binario sería 00001000 (todos los bits en cero excepto contando de arriba hacia abajo en pixeles de la pantalla el ubicado en la cuarta posición haciendo aparecer así en forma incipiente la barra media de la letra H

Volvemos a enviar el mismo dato que antes, 0x08 y vuelve a producir el mismo efecto, continúa con la barra media de la letra H

Una vez mas enviamos 0x08 (ver en el párrafo previo inmediato que estos son los datos indicados! Y se continúa creando la barra media de la H

Terminada la barra media cerramos la H enviando 0x7F que vendría a representar todos los puntos en negro excepto el inferior

El siguiente byte que se envía y que no está representado en la tabla para no ocupar innecesariamente espacio de ROM es un 0x00 o todos los bits en cero con lo cual se crea una suficiente separación entre caracteres. De esto se encarga el programa cuando envía los datos.

Si queremos letras mas separadas tendremos que poner no uno sino DOS espacios o bytes vacíos.

Comenzando con la O se envía 0x3C que pasado a binario vendría a ser 00111100 si paramos los ceros y unos y nos los imaginamos como puntos encendidos solo los del medio entendemos que está formando el fondo de la C o su parte izquierda.

Siguiendo con la O y mirando la tabla del párrafo anterior vemos que se envía 0x41 que en binario sería 01000001 ya nos vamos imaginando los incipientes techo y piso de la O, es todo muy imaginativo pero cuando se le toma el truco sale muy fácil incluso sin papel y lápiz

El tercer byte enviado para formar la O sigue siendo 0x41 continuando con el piso y techo de la letra

El cuarto byte de la O otra vez es 0x41...

El quinto y último byte de la O, al igual que el primero es 0x3C dando por cerrada la letra

Restan para otra oportunidad las letras L y A, ya se van imaginando a donde terminaría este documento! Este proceso se logró automatizar con la función LCDLetra la cual dependiendo del valor con el que es llamada busca en la tabla los cinco bytes que correspondan y si eso ya no simplificó las cosas la función LCDTexto se encarga de llamar a la función LCDLetra tantas veces como letras tenga el texto en su interior entonces si bien nuestra pantalla no tiene juego de caracteres interno lo cierto es que para que aparezca HOLA con nuestra librería basta con escribir en el sketch LCDTexto("HOLA"); y saldrá el texto donde se encuentre el cursor al momento.

Si hacer un set de caracteres no representó demasiado desafío por supuesto que convertir una imagen de un logotipo en una seguidilla de ceros y unos si lo va a ser! Para facilitar ese proceso tenemos el programa LCDAssistant el cual pueden bajar haciendo clic acá.

Este programa recibe un archivo de mapa de bits o .bmp y lo convierte en un archivo de texto en cuyo contenido está la tabla disponible para ser copiada y pegada en el programa a realizar. Para un correcto funcionamiento con estas pantallas respetar los parámetros seteados y únicamente establecer el nombre de la tabla acorde a la necesidad, no es obligatorio que se llame logo.

Pueden bajar mi librería nokia5110.h haciendo clic acá y pueden bajar un archivo comprimido con todos los programas juntos vistos antes haciendo clic acá.

En mi librería incorporo tres tamaños de letra partiendo de la mas pequeña a la que llamo LCDLetra y ocupa siete pixeles de alto por cinco de ancho mas un espacio libre a la derecha, sigue LCDDoble que ocupa diez bits de ancho y 14 de alto a lo que se le suma dos bits de lado por separación, le sigue LCDTriple que no hay que ser muy pensativo para deducir que ocupa el triple que la medida inicial, tres pixeles de lado y tres de vertical osea 21 x 15 pixeles con tres de separación lateral y por último LCDGuaso que ocupa cuatro por cuatro. Personalmente creo que como mucho emplearé alguna vez el tamaño triple pero en general no pasaré del doble, son cosas que se ven con el uso. Está hecha en entorno Arduino y yo la estuve utilizando hasta ahora con ESP32, de echo esta nota parte desde una puesta en dicha sección. Pero en breve voy a clonarla para Arduino con placas Atmega y también para usarla con PIC's. 

Espero que les haya sido de utilidad toda esta información, de esa manera vale la pena el tiempo dispensado. He aprendido mucho durante la redacción y sobre todo al hacer los diferentes programas. Cualquier inquietud mi email esta en mi nombre.

Pablo Canello, 18/04/2020