Skip to content

CT.12: Controlador LCD 16x2

Juan Gonzalez-Gomez edited this page May 21, 2024 · 122 revisions

Descripción

Experimentos y puesta en marcha de un display de cristal líquido (LCD) de dos filas de 16 caracteres. Creación del controlador de bajo nivel desde cero

Historial

  • 2022-Nov-01: Version inicial del cuaderno técnico
  • 2024-Mayo-21: Ejemplos adaptados a la nueva toolchain: apio-0.9.4. Eliminado el error en la verificación. Probados con icestudio 0.12. Los pantallazos de los ejemplos no se han actualizado todavía

Icestudio

Todos los ejemplos se han probado con Icestudio 0.12. Usa esta versión o superior

Colecciones

  • iceLCD: Colección para este cuaderno técnico, con todos los bloques controladores del LCD y sus sub-bloques. Descargar e instalar. Usa esta versión o superior
  • iceSignals: Bloques de temporización usados para la construcción de los controladores del LCD

Ejemplos

Todos los ejemplos de este cuaderno técnico están accesibles en su repositorio en github

Contenido

Introducción

Los Displays de Cristal líquido (LCDs) consumen muy poco y se han utilizado durante muchas décadas para mostrar información en los dispositivos electrónicos. Aunque actualmente han sido reemplazados por las pantallas OLED, que permiten mostrar gráficos en color, los LCDs todavía se usan bastante, por su sencillez, bajo consumo y bajo precio

En este cuaderno técnicos crearemos un controlador básico desde cero, partiendo de la hoja de datos. La mayoría de los LCDs vienen con el chip HD4478OU o compatible. Nuestro primer paso será bajarnos esta hoja de datos

El problema lo descomponemos en 3 Niveles. En cada uno se resuelve un problema y se implementan una serie de bloques de Icestudio:

  • Nivel 0: Acceso al medio. Primero hay que estudiar la temporización y la conexión física entre el LCD y la FPGA
  • Nivel 1: Controlador de escritura. Hacer un bloque capaz de enviar un comando/dato al LCD (suponiendo que el LCD ya está inicializado)
  • Nivel 2: Controlador de escritura con inicialización. Es el controlador final. Primero se inicializa el LCD y luego queda listo para en envío de datos/comandos

Nivel 0: Bloques de temporización

Empezamos por el nivel físico: los pines de conexión entre la FPGA y el LCD, así como las señales de control y la temporización a aplicar para comunicarse con el LCD

Conexión física entre el LCD y la FPGA

El LCD tiene un total de 11 pines de interfaz con la FPGA, divididos en dos bloques

  • Bus de Control: 3 bits con las señales de control: E, RS y RW
  • Bus de Datos: 8 bits con las señales de datos: D0 - D7

Además dispone de las señales de alimentación (VCC, VSS), la señal para el contraste (VO) y opcionalmente dos pines más (A, K) para activar o no la retroiluminación

Los datos transmitidos entre la FPGA y el LCD son de 8 bits, a través de los 8 pines de datos (D0 - D7). Sin embargo, para ahorrar pines, la comunicación se puede realizar usando sólo 4 pines (D4 - D7) y dejando el resto al aire. Así, disponemos de dos tipos de conexionado:

  • Interaz de 8 bits: Se necesitan en total 11 pines disponibles en la FPGA: 3 para las señales de control y 8 para los datos
  • Interfaz de 4 bits: Se necesitan en total 7 pines en la FPGA: 3 para las señales de control y 4 para los datos

El tipo de conexión a utilizar (8 ó 4 bits) se establece en la fase de configuración de LCD. En este cuaderno técnico desarrollaremos controladores para trabajar con interfaces de 4 y 8 bits

Protocolo de Comunicación entre la FPGA y LCD

Una vez realizadas las conexiones físicas, con todos los cables tirados, lo siguiente es estudiar cómo es el protocolo de comunicación para el envío de datos desde la FPGA al LCD (escritura), y del LCD a la FPGA (lectura)

En total tenemos 3 señales de control: E, RS y RW

El sentido de la comunicación lo determina la señal RW:

  • Escritura: RW=0. Los datos van de la FPGA al LCD
  • Lectura: RW=1. Los datos van desde el LCD a la FPGA

Dentro del LCD hay 2 registros: El registro de datos (DR) y el registro de instrucción (IR). Las escrituras en el registro de instrucción sirven para enviar comandos al LCD, como borrar la pantalla, colocar el cursor en una posición determinada, configurar el LCD, etc... Las escrituras en el registro de datos sirven para imprimir caracteres o definir nuestros propios caracteres

La selección del registro a usar se hace mediante la señal RS:

  • Registro de instrucción: RS=0
  • Registro de datos: RS=1

Por último, la señal E sirva para indicar el momento exacto en el que realizar la lectura/escritura del registro seleccionado. Se trata de un pulso activo en flanco de bajada. Esto significa que tanto la escritura como la lectura se hacen cuando llega un flanco de bajada en E

Cronograma y Temporización

Lo primero que hacemos es buscar el cronograma de las operaciones de escritura y lectura, en la hoja de datos. Es necesario dedicar bastante tiempo a su estudio, hasta que lo entendamos perfectamente. El cronograma contiene la información sobre los tiempos de las señales, que debemos respectar para lograr una implementación correcta

Si violamos alguna de estas especificaciones, el funcionamiento no está garantizado. Puede que en nuestro controlador funcione con un LCD concreto, pero que luego no lo haga con otros. O puede que unas veces sí, y otras no. O simplemente puede que no funcione nada. La información del cronograma son especificaciones del fabricante, y es nuestra obligación como diseñadores respetarlas

Escritura en LCD

Tomaremos como referencia el Cronograma de los ciclos de escritura, que son los más habituales. Se encuentra en la página 58:

Observamos que hay muchísima información, que tenemos que procesar. La mayoría de las veces los cronogramas NO ESTÁN A ESCALA. Esto significa que a priori no podemos deducir si un tiempo es muy grande o pequeño en comparación con otro. Debemos mirar su valor. En la página 49 se encuentra la tabla con los valores temporales del cronograma

En esta figura se muestra el cronograma re-hecho, añadiendo los valores de las temporizaciones (en ns). Las transiciones de las señales se han hecho "cuadradas", suponiendo que tanto los flancos de subida como de bajada son de 0ns. Su tiempo es de 20ns, según la tabla. Este tiempo se ha añadido al resto de tiempos

La clave para entender el cronograma es recordar la definición de dos conceptos: El tiempo de setup y el tiempo de hold. Antes de escribir cualquier valor en un registro, es necesario que permanezca estable durante un tiempo (el tiempo de setup) y luego, tras el flanco corespondiente a su escritura, el dato debe permanecer estable durante el tiempo de hold

Así, vemos que para escribir un dato en el LCD, que se hace en el flanco de bajada de E, es preciso que al menos esté puesto en el bus de datos con 245ns de antelación, y tras el flanco de bajada, debe permanecer sin cambios al menos 10ns

Estos son los valores mínimos. No pueden ser menores, pero sí que pueden ser mayores

También vemos que el periodo de la señal E es, al menos, de 1000ns (1µs). Es decir, que la frecuencia máxima de la señal E es de 1Mhz

Ahora ya podemos describir verbalmente lo que debe ocurrir para poder realizar una escritura en el LCD:

  1. Establecer el registro a escribir (señal RS). Establecer RW=0 para que sea una escritura. El dato a escribir también se puede colocar en este instante. La señal E debe estar en reposo (0)
  2. Esperar un tiempo mínimo de 85ns
  3. Poner la señal E a 1. Y dejarla en estado alto al menos 490 ns
  4. Poner la señal E a 0. Dejarla en ese estado al menos 510ns (para que su periodo sea de 1000ns)
  5. En este momento ya se podría comenzar otro ciclo de escritura

Sería posible hacer un circuito que cumpla exactamente con lo anterior, sin embargo, lo podemos diseñar de forma más simplificada si usamos una temporización aproximada más simétrica

Este cronograma cumple con las especificaciones (los tiempos mínimos se garantizan), pero es más sencilla de implementar, por su simetría:

Basta con generar un ciclo de E desfasado 90 grados, donde el pulso de 500ns ocupa la parte central. En el comienzo hay 250ns y en la parte final otros 250ns. De esta forma se cumplen todos los tiempos de setup y hold. La escritura se simplifica: basta con escribir los valores de Rs,RW y el dato, y generar un periodo de una señal de 1Mhz, pero desfasada 90 grados

Lectura del LCD

Además de enviar información desde la FPGA al LCD (escritura) también es posible lo contrario: leer desde el LCD. Con ello podemos leer el contenido de la memoria del LCD, que previamente hemos escrito. Sin embargo la principal utilidad de la lectura es leer el flag de ocupado (BF, Busy Flag) que nos indica cuándo está el LCD listo para realizar la siguiente escritura

Como referencia partimos del cronograma de lectura, disponible en la página 58 de la hoja de datos

Es muy parecido al de escritura, con algunas diferencias que comentaremos a continuación. Tampoco está a escala, por lo que no queda reflejado qué tiempos son más largos que otro. Los valores los vemos en la tabla de la página 49

El cronograma re-hecho con los valores de los tiempos es el siguiente:

Observamos que el dato enviado por el LCD NO ESTÁ LISTO hasta que no ha pasado un tiempo de 385ns desde el flanco de subida de E. Este es un tiempo máximo: está garantizado que el dato estará listo, aunque podría estarlo antes. El dato deja de estar disponible 5ns tras el flanco de bajada

Este es el cronograma que implementaremos, en el que la señal E está desfasada 90 grados. Se cumplen con las especificaciones del cronograma anterior:

Bloque LCD-cycle

Comenzamos la implementación de nuestro controlador por el bloque LCD-Cycle: es el encargado de generar la señal E para realizar ciclos de lectura y de escritura

Para implementarlo nos fijamos en la señal E de los cronogramas de lectura y escritura. Comenzaremos por la escritura. Cuando quereamos realizar un ciclo de escritura tenemos que hacer que la señal E permanezca a 0 durante 250ns, luego se debe poner a a 1 durante 500ns y finalmente volver a 0 durante otros 250ns

¿Cómo implementamos esto? La forma más fácil y directa es utilizando los bloques Sys-delay-xN-k que vimos en el Cuaderno técnico 11: Señales del sistema y medición con el LEDOscopio

Ejemplo 1: Primera implementación

Dado que estamos haciendo una implementación para la placa Alhambra-II, sabemos que los tiempos de 250ns y 500ns se corresponden con 3 y 6 ciclos de reloj respectivamente. Basta con colocar tres bloques encadenados

En este ejemplo se genera la señal E y se saca por un pin para medirlo con el analizador lógico. Además, se han añadido las señales de busy y done

(01_lcd_cycle-1.ice)

La señal busy nos indica que actualmente se está realizando un ciclo (de lectura o escritura) y que por tanto no se puede comenzar otro hasta que no finalice el actual. La señal done emite un tic cuando este ciclo ha finalizado. La señal de start la genera el usuario al apretar el pulsador SW1. La medimos también con el analizador lógico ya que es la señal que nos indica el comienzo del ciclo

Esta es la medición obtenida con el Pulse View

Comprobamos que efectivamente el cronograma es el esperado. La señal E está activa durante 500ns (6 ciclos), y está a 0 durante los primeros 250ns (3 ciclos) y los 250ns últimos. La señal de busy dura exactamente 1µs (el tiempo de un ciclo de lectura o escritura)

Recursos consumidos
LC 51

Esta implementación ocupa 51 bloques lógicos

Ejemplo 2: Cuatro Slots de 250ns

Otra implementación similar es utilizar 4 bloques de retraso de 250ns, que hacen un total de 1000ns. Pero en vez de conectarlos en serie, utilizamos una máquina de contar de 2 bits para repetir la pausa 4 veces, generando los 4 slots de 250ns

El valor del cable n indica el número de slot. Según el valor de n, generamos la salida correspondiente. Así, queremos que en los slots 1 y 2 la señal E esté a 1, y en los slots 0 y 3 (inicial y final) esté a 0

(02_lcd_cycle-2.ice)

Además, al final del Slot 2 se genera un tic en la señal dwn (down) para indicar que viene el flanco de bajada del pulso E. Este tic lo usaremos en los ciclos de lectura

Este es el cronograma del ejemplo. Es el mismo que el del ejemplo 2, pero se ha incluido la señal dwn

Recursos consumidos
LC 49

Esta implementación ocupa 49 bloques lógicos

Ejemplo 3: Periodo en ciclos

El siguiente paso es especificar el valor del periodo total en ciclos. Este valor depende de la frecuencia del sistema de cada placa. En el caso de la Alhambra-II, como la frecuencia es de 12Mhz, necesitamos 12 ciclos (1µs * 12Mhz = 12). Pero si tuviésemos una placa, de por ejemplo 24Mhz, necesitaríamos 24 ciclos para tener el mismo periodo de 1µs

En este ejemplo partimos del valor del periodo en ciclos y lo dividimos entre 4 para obtener la duración de los 4 slots. En nuestro ejemplo como el periodo es de 12 ciclos (1µs), los slots son de 3 ciclos (250ns)

La división entre 4 es en realidad un desplazamiento de 2 bits hacia la derecha. Se implementa utilizando el bloque div4-uint4 de la colección iceArith

(03_lcd_cycle-3.ice)

Y el cronograma obtenido es el esperado. Obtenemos un ciclo de 1µs

Sin embargo, ahora si ponemos por ejemplo un valor del periodo de 8 ciclos, nos aparece un nuevo cronograma en el que los slots son ahora de 2 ciclos. Este cronograma No cumple especificaciones (en el caso de la Alhambra II), pero se pone aquí como ejemplo de que nuestro circuito paramétrico funciona bien

Y este sería el cronograma correspondiente a un periodo de 4 ciclos

Este circuito está dimensionado para placas de 12Mhz. Si se quiere utilizar para otras placas hay que usar bloques sys-delay de más bits, así como cambiar también el tamaño de la constante. Luego hay que asegurarse que la temporización se cumple (ciclos de E de 1µs)

Descripción del bloque

El bloque LCD-write es el encargado de generar la señal E para realizar tanto ciclos de lectura como escritura en el LCD. Este bloque es la base para la implementación de los bloques de niveles superiores, y sirve tanto para los LCDs con interfaces de 4-bits como de 8-bits

En esta figura se muestran todas sus entradas y salidas:

Para que sirva para FPGAs con cualquier frecuencia de reloj del sistema, por la entrada T_cyc hay que introducir el número de ciclos del sistema necesarios para conseguir un ciclo de 1000ns (1µs). Para el caso de la tarjeta Alhambra-II, como tiene un reloj de 12Mhz se necesitan 12 ciclos

La entrada start arranca el ciclo de acceso. La señal busy está activa durante todo el ciclo de acceso. Su duración debe ser siempre superior o igual a 1000ns. Por ello, en caso de usarse una placa diferente a la Alhambra-II, deberá comprobarse con el analizador lógico que se cumple este requisito (o de lo contrario se están violando las especificaciones del LCD y no nos funcionará)

La señal E se conecta directamente a la entrada E del LCD. La señal down emite un tic cuando hay un flanco de bajada en E. Se utiliza por los bloques supriores para relizar la lectura de datos del LCD (opcional).

Finalmente, la señal done emite un tic cuando ha finalizado el ciclo completo de acceso. Es decir, que se ha generado un pulso en E y ha transcurrido un total de 1µs desde el comienzo

La implementación es la mostrada en el ejemplo 3

Ejemplo 4: Probando el bloque LCD-cycle

En este ejemplo se prueba el bloque LCD-cycle con el analizador lógico, para una tarjeta Alhambra-II, que necesita 12 ciclos de reloj del sistema para generar 1µs

(04_lcd_cycle-4.ice)

El cronograma generado es el mismo mostrado en los ejemplos anteriores

Bloque LCD-cycle-ns

Para hacer más sencilla la utilización del bloque LCD-cycle, usamos el bloque ns de la colección iceSignal que calcula el número de ciclos de reloj necesarios para obtener el tiempo en ns indicado. Para el caso del LCD este tiempo es de 1000ns. Ahora, en vez de trabajar en ciclos, lo hacemos en ns. Los ciclos se calculan automáticamente en función del periodo en ns y de la frecuencia del reloj del sistema en Hz

Este cálculo se realiza durante la síntesis. Es decir, que este cálculo NO OCUPA RECURSOS EN LA FPGA. El sintetizador calcula los ciclos y esta constante es la que se usa en el circuito. A todos los efectos, el bloque ns es como si no estuviese. Es una ayuda para los humanos, para que podamos trabajar en unidades de tiempo en vez de en ciclos del sistema

Ejemplo 5: Periodo en ns

Este ejemplo es el mismo que el 4, pero usando el bloque ns y pasando como parámetros la frecuencia del reloj y el periodo para E. Como es para la tarjeta Alhambra-II, utilizamos un valor de 12_000_000 Hz para la frecuencia del sistema

(05_lcd_cycle-5.ice)

El parámetro del periodo para E es el valor objetivo. El valor real obtenido puede ser diferente. En el caso de un valor de 1000ns para la frecuencia de 12Mhz, las cuentas salen exactas (12 ciclos) y se obtiene el mismo valor que el objetivo. Pero por ejemplo si se especifica una frecuencia de 800ns, se obtendrá una frecuencia real de 666ns (Los ciclos necesarios son 9.6, que se redondean a 10. Y luego se dividen entre 4 para obtener los cilos del slot: 2.5. Pero la división es entera por lo que el valor final del periodo del slot es 2, lo que da un periodo final de 2*4 = 8 ciclos, que equivale a un periodo de 666.6ns)

Si se usa una placa diferente a la Alhambra-II, hay que realizar mediciones para asegurar que el periodo de E salga mayor o igual a 1000ns

Descripción del bloque

El bloque LCD-cycle-ns es muy parecido al LCD-cycle pero tiene 2 parámetros de entrada:

  • Fsys: Frecuencia del reloj del sistema, en Hz. El valor por defecto es de 12_000_000Hz (12Mhz)
  • TEns: Valor objetivo para el periodo de la señal E, en nanosegundos (ns). El valor por defecto es de 1000ns

El parámetro TEns típicamente no se modificará, salvo que se esté usando un LCD que tenga unas características diferentes (un valor de 1000ns debería funcionar para la mayoría)

El parámetro Fsys sólo se establece si la placa usada tiene una frecuencia de reloj del sistema diferente a 12Mhz

Ejemplo 6: Probando el bloque LCD-cycle-ns

En este ejemplo se usa el bloque LCD-cycle-ns directamente, y se miden sus salidas con un analizador lógico. Como se está probando en la placa Alhambra-II, no hay que introducir ningún parámetro porque se usan los valores por defecto (Periodo de E de 1000ns, y frecuencia del reloj de 12Mhz)

(06_lcd_cycle-6.ice)

El cronograma es el mismo que en los ejemplos anteriores

Familia de controladores

Aunque también es posible leer información de la memoria del LCD, lo utilizaremos como un periférico exclusivamente de salida, por lo que los controladores sólo realizarán la escritura. Y son por tanto, similares a otros controladores de escritura, como el del puerto serie

En total hay 4 familias de controladores, agrupadas por dos criterios: Interfaz de datos y feedback

Según el feedback, los controladores pueden ser en Bucle abierto o Bucle cerrado. Según la interfaz los tenemos de 8 y 4 bits

  • Controladores en bucle abierto: Tras la escritura de datos/commandos en el LCD se espera un tiempo fijo para garantizar que se queda libre antes de escribir el siguiente dato. Son los controladores más sencillos, pero son más lentos. Se elige como tiempo de espera el del comando más lento
  • Controladores en bucle cerrado: Se lee el flag de ocupado (busy flag) del LCD para determinar si se puede escribir o no. Estos controladores son más complejos y consumen más recursos, pero son más rápidos
  • Interfaz de 8 bits: Se envía el dato al LCD usando los 8 pines de datos. Son más sencillos y más rápidos, pero se consumen más pines
  • Interfaz de 8 bits: Se envía el dato al LCD usando 4 pines de datos. Son más complejos y más lentos, pero consumen menos pines

Las entradas de los controladores son:

  • rs: Indica si escribimos un dato o un comando
  • dataw[7:0]: Dato a escribir en el LCD (siempre es de 8 bits)
  • write: Tic que realiza la escritura

Las salidas son:

  • rs: Señal rs del LCD
  • rw: Señal rw del LCD
  • E: Señal E del LCD
  • dat[]: Bus de datos del LCD (será de 8 ó 4 bits según el tipo de interfaz configurado)
  • busy: Indica que el controlador del LCD está ocupado escribiendo el DATO. Mientras busy esté a 1 NO se puede realizar ninguna otra escritura
  • done: Escritura en el LCD completada

Todos estos controladores los denominamos de nivel 1, ya que se apoyan en el del nivel 0 (LCD-cycle). El de nivel 0 se encarga de la temporización de los ciclos de lectura/escritura. El de nivel 1 de la temporización a nivel de comandos/datos, y con él ya podemos manejar el LCD

El controlador más sencillo de nivel 1 es el de bucle abierto e interfaz de 8 bits

Inicialización del LCD

La Inicialización del LCD se hace en los controladores de nivel 2, sin embargo, en esta sección vamos a aprender a inicializarlo para probar los controladores de nivel 1

El LCD, una vez alimentado, hay que inicializarlo para poder usarlo. Esto se hace enviando una serie de comandos. Dado que inicialmente el LCD puede funcionar con una interfaz de 8 ó 4 bits, los 3 primeros comandos son especiales: Hay que escribir el valor 0x3 en los 4 bits de mayor peso del LCD

Los detalles se encuentran en las figuras 23 y 24 de la hoja de datos, en las páginas 45 y 46

En este apartado iremos poco a poco construyendo ejemplos para inicializar y utilizar el LCD usando el bloque LCD-cycle-ns

LCD con interfaz de 8 bits

Primero hay que enviar los 3 primeros comandos que tienen el valor 0x3 en los 4 bits de mayor peso, y cualquier otro (es indiferente) en los 4 de menor. Por ello los pondremos a 0 y usaremos el comando 0x30

Tras ellos hay que enviar el comando de configuración del LCD para una interfaz de 8 bits y dos líneas: 0x38. Es el comando Function set:

Por defecto, si partimos de un LCD apagado, lo encendemos y lo configuramos enviando los comandos anteriores, sólo veremos una pantalla en blanco. Para comprobar que funciona vamos a usar el comando display control

Ejemplo 7: Inicialización manual a 8 bits

Como primer ejemplo, haremos un circuito para inicializar el LCD a 8 bits manualmente. Debemos enviar los comandos: 0x30, 0x30, 0x30, 0x38. Para comprobar lo que va ocurriendo, cada comando se envía manualmente al apretar el pulsador SW1. Además, enviaremos el comando 0x0F para hacer parpadear el LED, y verificar que el LCD está funcionando

Para enviar esos 5 comandos debemos poner rs a 0. La manera más sencilla de enviarlos secuencialmente es conectando 5 registros en cascada, cada uno inicializado con un comando. Las entradas de load de estos registros están conectadas a la señal done del bloque LCD-cycle-ns de manera que al terminar un ciclo de escritura se actualizan los registros con el siguiente comando

(07_init-8bits-1.ice)

La secuencia que se ve en el LCD es la mostrada en esta figura

Inicialmente el LCD está apagado (sin alimentación). Esto se consigue quitando el cable USB de la Alhambra II. Al alimentar el LCD (conectar la Alhambra por el USB) se enciende y se muestra la primera línea con 16 caracteres negros

En los LEDs se muestra el siguiente comando a enviar cuando se apriete el pulsador SW1

Al apretar el pulsador 3 veces se envían los tres comandos 0x30. El LCD no cambia: se sigue viendo igual que antes

Ahora volvemos a apretar el pulsador SW1 para enviar el comando 0x38. El LCD se configura con una interfaz de 8 bits y la primera línea se apaga. El LCD está encendido pero la pantalla está en blanco

Por último volvemos a pulsar el botón SW1 para enviar el comando 0x0F. Se mostrará el cursor parpadeante en la primera posición de la pantalla (Esquina superior izquierda). En esta animación se muestra el cursor parpadeando

¡El LCD está inicializado y listo para funcionar!

Esta secuencia es siempre la misma si se parte del LCD apagado. Si una vez inicializado se hace un reset de la FPGA y se vuelve a inicializar el LCD, la secuencia cambia:

Al hacer reset de la FPGA los pines pasan a Alta impedancia y se producen pulsos espúreos en el LCD (la señal E cambia) por lo que el LCD lo interpreta como una escritura, y avanza el cursor. La FPGA se ha inicializado, pero NO el LCD (porque no se le ha quitado la alimentación). En estas condiciones, al apretar el pulsador SW1 una vez, el LCD recibe el comando 0x30 y la línea superior se pone en negro (aunque se puede seguir viendo cómo parpadea el cursor)

Al apretar el pulsador 2 veces más, se vuelve a enviar el comando 0x30, y no ocurre nada nuevo. Al apretarlo la cuarta vez, el display se configura nuevamente para 8 bits, la línea superior deja de estar negra y se vuelve a ver el cursor parpadeando. Al apretar la quinta vez no ocurre nada (ya que el LCD sigue configurado igual que inicialmente: display on, cursor on y parpadeo on)

La secuencia de inicialización completa debe incluir comandos para limpiar el LCD y llevarlo a su posición orignal, de forma que el LCD esté siempre en el mismo estado, independientemente de si la inicialización ha sido tras encenderlo o bien porque se ha hecho un reset de la FPGA

De momento usaremos esta mini-secuencia para comprobar de forma fácil si el LCD funciona o no

Ejemplo 8: Inicialización manual e impresión del carácter 'A'

Para comprobar que la inicialización desde 0 funciona correctamente, vamos a imprimir el carácter 'A'. Para ello hay que poner la señal rs a 1 y enviar por el bus de datos el carácter ASCII 'A'

En este nuevo circuito, la señal rs está a 0 durante los primeros 5 comandos y luego debe ponerse a 1. Hay una manera muy sencilla de hacerlo: colocando 5 biestables DFF en serie, y un bit a 1 al final

El circuito es el siguiente:

(08_init-8bits-2.ice)

Ahora, una vez que el cursor está parpadeando, si apretamos el pulsador (la sexta vez desde el encendido del LCD), aparecerá la letra 'A' en la primera posición. Cada vez que se vuelva a apretar el pulsador SW1, aparecerá una nueva 'A' justo a continuación de la otra

Si en este estado inicializamos la FPGA (botón de reset de la Alhambra II), la secuencia del LCD se muestra en esta figura:

Al apretar el botón de reset el cursor avanza dos posiciones. Al apretar el pulsador SW1 la primera línea se pone oscura. Al apretar dos veces SW1 vuelve a aparecer lo que había antes. Al enviar los comandos 0x38 y 0x0F se queda igual. Y finalmente al apretarlo la sexta vez se imprime una nueva letra 'A'

Ejemplo 9: Inicialización manual y completa, e impresión del carácter 'A'

En este circuito se realiza la inicialización completa, de forma manual. Hay que añadir dos comandos más, para lograr que bien tras la alimentación o bien mediante el reset de la FPGA el LCD comience siempre en el mismo estado

El primer comando es Entry mode set, para establecer el modo de funcionamiento del LCD al escribir un carácter: Mover el cursor a la derecha/izquierda, o bien desplazar la pantalla a derecha/izquierda

El otro comando a añadir es CLS, para borrar la pantalla y situar el cursor en su posición original

El nuevo circuito es el mostrado en esta figura. Es el mismo que el del ejemplo 8, pero añadiendo los comandos 0x06 y 0x01.

(09_init-8bits-3.ice)

Al apretar 7 veces el pulsador SW1 se completa la inicialización del LCD, con el cursor parpadeando en la primera posición. Las sucesivas presiones de SW1 imprimen una 'A'

Si ahora se resetea la FPGA y se repite el proceso, el resultado será el mismo. Ya tenemos el proceso de inicialización listo

Ejemplo 10: Inicialización manual e impresión desde memoria

El circuito se puede simplificar si los comandos/datos los almacenamos en una memoria en vez de en registros aislados. Utilizamos una tabla de 16 posiciones de 9 bits. El bit más significativo de estos 9 bits se corresponde con rs: es el bit que indica si se trata de un comando o de un dato. Los 8 bits menos significativos contienen el comando/dato

Usamos un contador para referenciar la instrucción actual. Cada vez que se aprieta el pulsador se escribe el comando actual y se incrementa el contador

(10_init-8bits-4.ice)

En la memoria situamos todos los comandos de inicialización, así como los datos a imprimir en el LCD. En este ejemplo se imprimen los caracteres: "ABC". Como el proceso es manual, hay que apretar el pulsador SW1 10 veces

Cuando la siguiente instrucción sea la número 10, el pulsador se bloquea, y las sucesivas pulsaciones NO TENDRÁN EFECTO

Tras apretar 10 veces el pulsador, en el LCD obtendremos esto:

Si apretamos más veces, no ocurrirá nada

La ventaja de usar una memoria es que ahora podemos colocar más comandos/datos simplemente añadiéndolos en la memoria y modificando el número total de instrucciones en el parámetro del comparador. El contenido de la memoria es el siguiente:

0_30 //-- Cmd: Init 0
0_30 //-- Cmd: Init 1
0_30 //-- Cmd: Init 2
0_38 //-- Cmd: Function set: 2-lineas, 8-bits
0_0F //-- Cmd: Display on, cursor on, blink on
0_06 //-- Cmd: Entry mode set
0_01 //-- Cmd: Display CLS
1_41 //-- Print A
1_42 //-- Print B
1_43 //-- Print C

Este circuito nos sirve de base para probar nuevos comandos, o escribir mensajes diferentes. La limitación es que sólo podemos poner 16 comandos. Para poner más, hay que utilizar una memoria más grande

LCD con interfaz de 4 bits

La interfaz de 4 bits nos permite utilizar el LCD usando 7 pines de la FPGA en lugar 11. La diferencia a nivel de manejo está en la inicialización,que es ligeramente diferente, y los comandos hay que enviarlos en dos partes de 4 bits: Primero los 4-bits de mayor peso y luego los 4 bits de menor

Ejemplo 11: Pines de los montajes

Para las pruebas usaremos dos montajes. Uno es el mismo que para 8 bits: LCD amarillo, conectado a 8 bits, pero SOLO utilizaremos 4 bits de datos. Este montaje nos permite probar tanto los ejemplos para 8 bits como los de 4 bits sin cambiar cables:

El otro es la LCD Shield conectada a la Alhambra II, que es de color azul (Así se distinguen fácilmente)

En esta figura se muestran los pines usados en uno y otro montaje. Los circuitos de ejemplo serán los mismos para ambos montajes, sólo cambiando la asignación de los pines:

(11_init-4bits-montajes.ice)

Ejemplo 12: Inicialización manual a 4 bits

La inicialización a 4 bits comienza de la misma manera que con 8: Se envía tres veces el comando '0x3' (4 bits). La diferencia está en el siguiente comando. Se envía '0x2' para especificar que la interfaz es a 4-bits

A partir de este momento el resto de comandos hay que enviarlos en grupos de dos nibbles, mandando primero el nibble de mayor peso. El primer comando es el de configurar el LCD a 2 líneas: 0x28. Luego se envía el que enciende la pantalla, activa el cursor y activa el parpadeo: 0x0F

(12_init-4bits-1.ice)
(12_init-4bits-LCD-shield.ice)

Tras pulsar el botón SW1 8 veces se puede ver el cursor parpadeando. Esto funciona para los dos montajes:

Ejemplo 13: Inicialización manual e impresión del carácter 'D'

Para comprobar si la inicialización es correcta, y que el LCD está funcionando con normalidad, hay que comprobar que se imprimen caracteres. Por ello, hay que variar la señal rs. Inicialmente a 0. Una vez enviados todos los nibbles de configuración hay que poner rs a 1 para imprimir un carácter

Por ello hay que encadenar 8 biestables D, que producen un 0 en rs en las 8 primeras pulsaciones, y a partir de ahí llegarán los 1s

(13_init-4bits-2.ice)
(13_init-4bits-2-LCD-shield.ice)

Tras pulsar 8 veces SW1, el cursor estará parpadeando en la primera posición. A partir de ahí, cada vez que se pulse dos veces SW1 se imprimirá el carácter 'D'

Ejemplo 14: Inicialización manual y completa, e impresión del carácter 'D'

Para que la inicialización sea completa y el LCD siempre parta del mismo estado, bien desde el encendido o bien desde la inicialización de la FPGA, sólo hay que añadir dos comandos adicionales: entry mode set (0x06) y cls (0x01). Como la interfaz es de 4 bits, hay que añadir 4 registros de 4-bits y 4 biestables DFF:

(14_init-4bits-1.ice)
(14_init-4bits-1-LCD-shield.ice)

Ahora hay que apretar 12 veces el pulsador para inicializar, y otras dos veces para imprimir el carácter 'D'

Ejemplo 15: Inicialización manual e impresión desde memoria

El circuito se puede simplificar si los nibbles los almacenamos en una memoria en vez de en registros aislados. Utilizamos una tabla de 32 posiciones de 5 bits. El bit más significativo de estos 5 bits se corresponde con rs: es el bit que indica si se trata de un comando o de un dato. Los 4 bits menos significativos contienen el nibble del comando/dato

Usamos un contador para referenciar el nibble actual. Cada vez que se aprieta el pulsador se escribe el nibble actual y se incrementa el contador

(15_init-4bits-1.ice)
(15_init-4bits-1-LCD-shield.ice)

En la memoria situamos todos los nibbles de los comandos de inicialización, así como los nibbles de los datos a imprimir en el LCD. En este ejemplo se imprimen los caracteres: "ABC". Como el proceso es manual, hay que apretar el pulsador SW1 18 veces

Cuando el siguiente nibble sea el número 18, el pulsador se bloquea, y las sucesivas pulsaciones NO TENDRÁN EFECTO

Tras apretar 18 veces el pulsador, en el LCD obtendremos esto:

Si apretamos más veces, no ocurrirá nada

La ventaja de usar una memoria es que ahora podemos colocar más comandos/datos simplemente añadiéndolos en la memoria y modificando el número total de instrucciones en el parámetro del comparador. El contenido de la memoria es el siguiente:

0_3 //-- Cmd: Init 0
0_3 //-- Cmd: Init 1
0_3 //-- Cmd: Init 2
0_2 //-- Cmd: Function set: 4--bits
0_2 //-- Cmd: Lines: 2x16
0_8
0_0 //-- cmd: Display on, cursor on, blink on
0_F
0_0 //-- Cmd: Entry mode set
0_6
0_0 //-- Cmd: Display CLS
0_1
1_4 //-- Print A
1_1
1_4 //-- Print B
1_2
1_4 //-- Print C
1_3

Este circuito nos sirve de base para probar nuevos comandos, o escribir mensajes diferentes. La limitación es que sólo podemos poner 32 nibbles. Para poner más, hay que utilizar una memoria más grande

Controladores de nivel 1

En este apartado construiremos los 4 controladores de 1, partiendo de los bloques del nivel 0. La construicción se irá haciendo progresivamente, resolviendo los problemas uno a uno

Controlador en Bucle abierto de 8 bits

Ahora que ya sabemos cómo inicializar el LCD manualmente, e imprimir caracteres, vamos a seguir construyendo el controlador. Este proceso lo haremos iterativamente, añadiendo nuevos componentes cada vez

El siguiente paso es hacer que la inicialización se haga automáticamente, y no tener que estar apretando el pulsador SW1

Esto se consigue generando una pausa de 5ms tras el bloque LCD-write-ns, y luego enviando el siguiente comando

Ejemplo 16: Inicialización e impresión de caracteres de forma automática

Partimos del ejemplo 10, en el que se inicializaba manualmente el LCD usando una memoria. Añadimos el bloque de temporización para esperar 5ms tras la ejecución de cada comando. También cambiamos el contenido de la memoria para que se imprima el mensaje "HOLA" al apretar el pulsador

(16-controler-8bits-msg-1.ice)

En esta figura se muestra el resultado: Cada vez que se aprieta el pulsador SW1, aparece el mensaje HOLA en la primera posición de la primera línea, y el cursor se queda parpadeando en la posición siguiente a la 'A'

Y aquí se puede ver la animación:

Este es el contenido de la memoria, con los 11 comandos que se envían:

0_30 //-- Cmd: Init 0
0_30 //-- Cmd: Init 1
0_30 //-- Cmd: Init 2
0_38 //-- Cmd: Function set: 2-lineas, 8-bits
0_0F //-- Cmd: Display on, cursor on, blink on
0_06 //-- Cmd: Entry mode set
0_01 //-- Cmd: Display CLS
1_48 //-- Print H
1_4F //-- Print O
1_4C //-- Print L
1_41 //-- Print A

Bloque LCD-write-open-8-bits

En el ejemplo anterior vemos la pinta que tiene el controlador en bucle abierto de 8 bits: Es básicamente un bloque LCD-cycle-ns seguido de una temporización de 5ms. Esto lo agrupamos en el nuevo bloque LCD-write-open-8bits:

Este controlador tiene 2 parámetros:

  • Fsys: Frecuencia del sistema (en Hz). Por defecto: 12_000_000
  • ms: Tiempo de espera del comando. Debe ser el valor del comando más lento. Valor por defecto 5ms

Las entradas son:

  • rs: Registro del LCD en el que se quiere escribir:
    • 0: Registro de Instrucción
    • 1: Registro de datos
  • din[7:0]: Dato a escribir en el LCD (8-bits)
  • write: Tic de escribura. Cuando se recibe se comienza con el proceso de escritura en el LCD

Las salidas son:

  • busy: Indica que el controlador está ocupado, con el comando anterior. No se pueden enviar nuevos comandos hasta que no haya terminado el actual
  • rs: Conexión al Pin rs del LCD
  • E: Conexión al pin R del LCD
  • RW: Conexión al pin RS del LCD
  • D[7:0]: Conexión a los pines de datos del LCD
  • done: Tic que indica que la escritura actual ha finalizado (y que por tanto se puede comenzar un nuevo ciclo de escritura)

La implementación del bloque se muestra en esta figura:

Cuando se recibe el tic de write se registra el dato de din[] y la entrada rs. De esta forma, esos valores permanecen estables durante todo el ciclo de escritura. A su vez, el tic de write genera un ciclo en la señal E. Cuando termina, se ejecuta el temporizador para esperar 5m. Una vez finalizado, se emite un tic de done. La señal de busy se calcula como la unión entre la señal de busy del bloque LCD-cycle-ns y la del temporizador

Ejemplo 17: Probando el controlador LCD-write-open-8bits

En este ejemplo usamos el controlador LCD-write-open-8bits para imprimir el mensaje FPGAwars en el LCD al apretar el pulsador SW1. Primero se realiza la inicialización y luego se imprime el mensaje

(17-controler-8bits-msg-2.ice)

Además se ha sustituido el comando 0x0F por 0x0C para que el cursor ni parpadee ni sea visible. Este es el resultado en el LCD:

En la memoria hay en total 15 comandos. Los 7 primeros son para realizar la inicialización y los 8 siguientes para imprimir el mensaje en pantalla:

0_30 //-- Cmd: Init 0
0_30 //-- Cmd: Init 1
0_30 //-- Cmd: Init 2
0_38 //-- Cmd: Function set: 2-lineas, 8-bits
0_0C //-- Cmd: Display on, cursor off, blink off
0_06 //-- Cmd: Entry mode set
0_01 //-- Cmd: Display CLS
1_46 //-- Print F
1_50 //-- Print P
1_47 //-- Print G
1_41 //-- Print A
1_77 //-- Print w
1_61 //-- Print a
1_72 //-- Print r
1_73 //-- Print s

Al apretar el pulsador la máquina de contar arranca y se envía al LCD el comando situado en la dirección 0. Cuando termina se pasa al siguiente... y así hasta ejecutar los 15

Controlador en Bucle abierto de 4 bits

El controlador en Bucle abierto de 4 bits es similar al de 8 bits: Hay que enviar el dato y luego esperar un tiempo para que se ejecute. Sin embargo, este dato hay que trocearlo en dos partes de 4-bits (nibbles) y enviar estas dos partes por separado. Y luego hay que realizar la pausa

Cada vez que se envía un nibble hay que generar un ciclo en la señal E. Entre ambos nibbles no hay que realizar ninguna pausa (se puede hacer directamente)

El problema está en que ahora hay que distinguir entre los 4 primeros comandos de inicialización, y el resto. Los primeros son de sólo 4 bits: hay que enviar cada nibble y esperar 5 ms. En el resto de comandos hay que enviar sus dos nibbles uno a continuación del otro y luego esperar los 5ms

Ejemplo 18: Impresión de caracteres de forma automática

Iremos haciendo el controlador incrementalmente. Dado que tenemos dos tipos de comandos, los de inicialización de 4bits y los de 8bits (que deben ser troceados), comenzaremos primero haciendo un ejemplo en el que sólo se envían estos últimos. Es un ejemplo que imprime en el LCD la cadena "Test1"

Sin embargo, dado que no se envían los 4 comandos de inicialización, este ejemplo SOLO FUNCIONA SI PREVIAMENTE HEMOS DEJADO EL LCD INICIALIZADO (Por ejemplo cargando el ejemplo 15)

(18-controler-4bits-msg-1.ice)
(18-controler-4bits-msg-1-LCD-shield.ice)

Una vez ejecutado, esto es lo que vemos en el LCD (para ambos montajes):

En la memoria hay 9 comandos (de 8 bits). La máquina de contar los direcciona, para acceder a ellos. Cada comando se separa en su dos nibbles y mediante un multiplexor se decide qué nibble es el que hay que enviar al LCD

En esta primera implementación usamos dos bloques LCD-cycle-ns conectados en serie. Cada uno de ellos se encarga de un nibble. Lo hacemos así porque es más sencillo. En las sucesivas implementaciones lo optimizaremos

Una vez enviado el nibble de menor peso (el segundo), se procede a activar el temporizador para esperar los 5ms de rigor para que el comando se ejecute

Ejemplo 19: Inicialización e impresión de caracteres de forma automática

Nos falta completar la parte de la inicialización. Para los cuatro primeros comandos NO hay que enviar el nibble de menos peso, sólo el de mayor. Es decir, que tenemos que hacer que el segundo bloque LCD-cycle-ns sea OPCIONAL

Esto lo logramos añadiendo un demultiplexor en la señal done del bloque que envía el nibble más significativo (este siempre hay que enviarlo). Según el estado de su señal de selección, envía el tic de done al siguiente bloque LCD-cycle-ns (para enviar el nibble 2) o bien directamente al temporizador

(19-controler-4bits-msg-2.ice)
(19-controler-4bits-msg-2-LCD-shield.ice)

La salida del comparador mayor que se conecta al demultiplexor. Utilizamos la condición de que los compandos cuya dirección sea mayor o igual a 4 para separar entre los comandos de inicialización y el resto

De esta forma, para los primeros 4 comandos NO se envía el nibble de menor peso. Pero para el resto de comandos (n>=4) sí

El resultado se muestra en esta figura:

Ejemplo 20: Inicialización e impresión de caracteres de forma automática. Implementación II

En el ejemplo anterior hemos usado dos bloques LCD-cycle-ns, por su sencillez, pero esto no es óptimo. Podemos ahorrar más recursos implementando la misma idea pero de otra forma

Utilizando una máquina de contar de 1 bit, podemos activar dos veces consecutivas un único bloque LCD-cycle-ns. Además, tenemos que añadir la condición de que la segunda activación (la del nibble 2) sea opcional: para unos comandos sí queremos que se ejecute, y para otros comandos no

20-controler-4bits-msg-3.ice
20-controler-4bits-msg-3-LCD-shield.ice

La lógica para determinar si el segundo nibble (de menor peso) se envía al LCD o no se resume en esta tabla de verdad. Aquí están todos los casos, donde n1 es el nibble (n1=0: mayor peso, n1=1: menor peso) y cmd indica si es un comando normal (cmd=1) o por el contrario es uno de inicialización especial (cmd=0)

La salida de la tabla es la señal sel de selección del demultiplexor

n1 cmd sel Envío del nibble de menor peso
0 0 1 Si
0 1 1
1 0 0 No
1 1 1

Observando la columna de la señal sel, y desarrollándola por la tercera fila (cuando sel=0), obtenemos la función lógica:

sel = not(n1) + cmd

Se implementa con una puerta not y una or

Hay una máquina de contar de 4 bits que indica la instrucción actual (n). Cuando n<4, se trata de un comando de inicializacińo especial. Y cuando n>=4 es normal. Esto lo detectamos con el comparador mayor o igual que

El resultado se muestra en esta figura:

Bloque LCD-write-open-4-bits

Esta es la pinta que tiene el bloque LCD-write-open-4bits, que es el controlador en bucle abierto de un LCD con interfaz de 4 bits:

Es muy parecido al bloque LCD-write-open-8bits, con la misma interfaz de entrada salvo que tiene una entrada adicional:

  • cmd: Indica el tipo de comando. 0: Comando especial de inicialización, 1: Comando normal

Los pines del bus de datos son de 4 bits, que se conectan a los 4 bits más significativos del LCD: DB7-DB4

La implementación es similar a la del ejemplo 20, pero registrando las entradas rs, cmd, din[], para que tengan valores estables durante toda la ejecución del controlador

Ejemplo 21: Probando el controlador LCD-write-open-4-bits

Este es un ejemplo de uso del controlador' LCD-write-open-4-bits'. Al apretar el pulsador SW1 se inicializa el LCD y se imprime la cadena Artemis I. Mediante un comparador Mayor o igual que indicamos al controlador que los 4 primeros comandos son especiales (sólo se envía el nibble de mayor peso), y que el resto son comandos normales

(21-controler-4bits-msg-4.ice)
(21-controler-4bits-msg-4-LCD-shield.ice)

Esto es lo que aparece en el LCD:

Este es el contenido de la memoria

0_30 //-- Cmd Init0,
0_30 //-- Cmd Init1,
0_30 //-- Cmd Init2,
0_20 //-- Cmd: Function set: 4--bits
0_28 //-- Cmd: Lines: 2x16
0_0C //-- cmd: Display on, cursor off, blink off
0_06 //-- Cmd: Entry mode set
0_01 //-- Cmd: Display CLS
1_41 //-- Print "Artemis I"
1_72 
1_74
1_65
1_6d
1_69,
1_73,
1_20,
1_49

Controlador en Bucle cerrado de 8-bits

Este controlador es más complejo. Ahora, en vez de escribir directamente en el LCD y esperar un tiempo fijo a que el comando termine (esto lo hace en los controladores en bucle abierto), leemos primero del LCD el flag de busy, que en la hoja de datos la denotan con las letras BF (Busy flag).

Si BF es 0, significa que el LCD está disponible, y se le puede enviar el siguiente comando. Por el contrario, si es 1 hay que seguir esperando

La ventaja de los controladores en bucle cerrado es que son más rápidos, ya que no se pierde tiempo esperando: Sólo se espera lo justo y necesario. Este tipo de controladores más rápidos nos pueen interesar en aplicaciones en las que nececitemos una frecuencia de refresco mayor, como por ejemplo en animaciones

Por contra, estos controladores son más complejos

Al igual que hemos hecho con el resto de controladores, lo empezaremos a diseñar incrementalmente a partir del bloque LCD-cycle-ns, haciendo ejemplos que añaden poco a poco funcionalidad. Y terminaremos encapsulándolo todo en el bloque LCD-write-close-8-bits

Lectura del Busy flag

El flag de ocupado (BF) se lee por el pin de datos del LCD DB7. Para leerlo hay que establecer las siguientes señales:

  • rs = 0 (Seleccionar registro de instrucciones)
  • rw = 1 (Ciclo de lectura)

Y luego emitir el pulso E (que ya conocemos)

Es necesario utilizar un pin de entrada/salida de la FPGA, que debe ser configurado como entrada previamente a la lectura de BF

Ejemplo 22: Lectura y medición del Busy Flag

En este primer ejemplo se realiza un ciclo de lectura en el LCD, y se muestra en el analizador el resultado. El Flag de Busy llega por el pin DB7 del LCD, y se captura en un biestable D conectado al LED7 para verlo

Como el funcionamiento es manual, cuando se apriete SW1 para hacer la lectura, BF estará a 0 (ya que habrán pasado más de 5ms desde la anterior escritura)

El pin DB7 se configura como E/S utilizando el bloque InOut_right de la Colección iceIO. Se configura como ENTRADA cada vez que se emite un ciclo que sea de lectura (rw=1). El resto del tiempo estará configurado como SALIDA. El valor que sacamos cuando no está en salida es 1

(22_controler-8bit-busy-01.ice)

El valor que llega por la etiqueta Pin_DB7 se envía al analizador lógico (para observarlo) y es el valor que se captura en el biestable para mostrarlo en el LED7. Esta captura se realiza en el flanco de bajada de la señal dwn

En esta figura se muestra el resultado en el analizador lógico:

Se ve como tras el flanco de subida de E, la señal DB7 se pone a 0. Es decir, que efectivamente BF es 0

Ejemplo 23: Ciclo de escritura y de lectura consecutivos

En este experimento generamos dos ciclos de acceso consecutivos al LCD. El primero es de escritura y el segundo de lectura. Esto nos permitirá observar que tras escribir el comando, BF se pone 1 para indicar que está ocupado

Estos dos ciclos se generan fácilmente encadenando dos bloques LCD-cycle-ns. La señal rs se fija a 0. La señal rw tiene que tomar inicialmente el valor 0 (Escritura) y luego 1 (Lectura). Esto lo hacemos con un biestable D inicializado a 0, conectado a la constante 1

El valor a escribir da igual. Usamos por ejemplo el valor 01 que se corresponde con el comando cls

(23_controler-8bit-busy-02.ice)

En el cronograma vemos lo que ha sucedido

Si observamos el flanco de bajada de la señal E, que es donde se hace la captura, el valor de BF es 1. Efectivamente el LCD está ocupado procesando el comando anterior (cls). Hasta que no se ponga nuevamente a 0 no se puede enviar el siguiente comando.

Este valor también se captura en un biestable y se muestra en el LED7. Vemos cómo está también encendido

Sin embargo, si ahora volvemos a apretar el pulsador se generan dos ciclos de lectura porque la señal rw se ha quedado a 1. El nuevo valor de BF es 0 porque ya se ha procesado el comando anterior. Esto también lo pdemos ver en el cronograma

Bloque LCD-cycle-rw

Nuestro controlador en bucle cerrado tiene que realizar ciclos de escritura, igual que el resto de controladores, pero también ciclos de lectura para obtener el valor del flag de busy (BF)

Para ayudarnos en su diseño, usaremos el bloque LCD-cycle-rw que amplía las capacidades del bloque LCD-cycle-ns añadiendo la lógica necesaria para realizar la lectura. El valor de los pines de control del LCD (rw y rs) depende del tipo de ciclo (lectura/escritura). También depende de tipo de comando usado: normal o especial de inicialización

Este es el aspecto del bloque LCD-cycle-rw:

Estas son las señales de entrada:

  • cmd: Indica si se trata de un comando Normal o de Inicialización. En el caso de ser de inicialización, siempre serán ciclos de escritura. Esta señal tiene prioridad sobre rw y rs. Cuando cmd es 0 (Inicialización), entonces rs y rw serán 0 tambien
    • 0: Comando de inicialización. Las señales rw y rs del display se ponen a 0
    • 1: Comando normal
  • rw: Indica si se quiere realizar un ciclo de lectura o de escritura. Esta señal tiene prioridad sobre rs. En caso de realizarse una lectura la señal rs del LCD se pone a 0 (para leerse el flag de busy)
    • 0: Ciclo de escritura
    • 1: Ciclo de lectura. La señal rs se pone a 0
  • rs: Selección de registro. Se indica el registro objetivo del LCD del que leer/escribir. Su valor está condicionado por las señales de entrada más prioritarias: rw y cmd
    • 0: Registro de instrucción del LCD
    • 1: Registro de datos del LCD (Memorias)
  • pin_BF: Pin por el que llega la información del flag de busy (BF)
  • start: Comenzar un nuevo ciclo (de lectura o escritura) en el LCD

Estas son las señales de salida:

  • busy: Se pone a 1 mientras se está generando el ciclo de acceso al LCD. Nos permite saber si el controlador está ocupado o está disponible
  • E: Conexión al pin E del LCD
  • rw: Conexión al pin rw del LCD
  • rs: Conexión al pion rs del LCD
  • oen: Señal de control para establecer el sentido del pin DB7
    • 0: Pin de entrada (para lectura de BF)
    • 1: Pin de salida
  • BF: Valor del flag de busy del LCD (en caso de haberse realizado un ciclo de lectura). El valor está disponible una vez que el controlador ha terminado
    • 0: LCD disponible (no ocupado)
    • 1: LCD ocupado
  • done: Tic que indica que el controlador ha finaliado. Es seguro leer el flag de Busy (BF)

Esta es la implementación del controlador:

Está formado por el bloque LCD-cycle-ns al que se le ha añadido toda la lógica combinacional necesaria para generar las señales de salida en función las entradas (y las prioridades asignadas). En el caso de realizarse una lectura, se captura el flag de busy en el momento preciso, y se deja almacenado en un biestable

Ejemplo 24: Probando el bloque LCD-cycle-rw

En este circuito de ejemplo se emiten dos ciclos de trabajo en el LCD, al apretar el pulsador SW1. El primero es de escritura y el segundo de lectura

(24_controler-8bit-busy-03.ice)

Como son dos ciclos consecutivos, en el segundo el bit de busy está a 1. Los resultados los comprobamos con el analizador lógico:

Se comprueba cómo en el segundo ciclo se obtiene el valor del flag de busy. También se puede ver que aunque la señal rs la tenemos a 1 en el circuito, al realizarse la lectura se pone a 0. También comprobamos cómo la dirección del pin DB7 cambia (señal oen) según se trate de un ciclo de lectura o de escritura

Bloque LCD-io-8-bits

El bloque LCD-io-8-bits encapsula la lógica necesaria para la configuración del pin D7 (Por donde viene el flag de busy) como entrada/salida, haciendo que los circuitos se simplifiquen

Hay dos señales de entrada:

  • dout[7:0]: Valor a enviar a los pines del LCD
  • oen: Configuración de la salida: 1-salida, 0-entrada

Estas son las dos señales de salida del bloque:

  • dpin[7:0]: A los pines del LCD
  • DB7_in: Señal D7 de entrada (por donde llega el Busy flag desde el LCD)

La implementación se muestra en esta figura:

Ejemplo 25: Probando el bloque LCD-io-8-bits

Este ejemplo hace exactamente lo mismo que el 24, pero se usa el bloque LCD-io-8-bits

(25_controler-8bit-busy-04.ice)

Ejemplo 26: Circuito de repetición condicional de una acción

El siguiente comportamiento a implementar en nuestro controlador es el de leer el flag de busy del LCD y esperar hasta que se ponga a 0, es decir, esperar hasta que el LCD termine de ejecutar la acción anterior

Esto lo implementamos mediante un bucle espacial: Hay un tic que recorre una máquina, y al salir de ella vuelve a entrar (la salida está conectada de nuevo con la entrada). Pero si se cumple una condición esta realimentación no se produce, finalizando el bucle

Este principio lo entendemos mejor en el siguiente circuito de ejemplo, que se puede probar con pulsadores y LEDs (sin LCD y sin analizador lógico)

(26_loop_example.ice)

Se utiliza como máquina de ejemplo un Delay de dos ciclos. Esta máquina se arranca al apretar el pulsador SW1. También se pone a 1 el Flag de Busy (Biestable RS). Un demultiplexor es el encargado de seleccionar lo que se debe hacer en función del estado del Flag de busy: Si este flag está a 1, el tic de done de la máquina se vuelve a meter por start, haciendo un bucle espacial

Este bucle se mantiene hasta que se aprieta el pulsador SW2 y el flag de busy se pone a 0. Entonces ahora el demultiplexor selecciona el otro camino y envía el tic hacia la etiqueta DONE, lo que provoca que se encienda el LED0 durante 1 segundo. Ahora la máquina ya no se está ejecutando en bucle

Ejemplo 27: Esperando que el busy flag sea 0

En este ejemplo emitimos dos ciclos en el LCD. El primero es de escritura. Se envía el comando CLS. Y a continuación se hace un ciclo de lectura. Mientras que el flag de busy sea 1, es decir, mientras el LCD esté ocupado ejecutando el comando CLS, se siguen realizando ciclos de lectura

Una vez que el flag de busy se pone a 0, que nos indica que el LCD ha terminado la operación anterior y ya está listo, se enciende el LED0 durante un segundo

(27_controler-8bit-busy-05.ice)

En este cronograma se muestra el funcionamiento en los primeros instantes

  1. Primero se emite un ciclo de escritura. Este ciclo finaliza cuando llega un tic por done
  2. Ahora se emite un ciclo de lectura
  3. El busy flag se pone a 1 (El LCD está ocupado con el comando CLS)
  4. El tic aparece por la etiqueta LOOP, para que se genere un nuevo ciclo de lectura. Ahora en done no hay tic
  5. El nuevo ciclo de lectura ha terminado, pero hay que repetirlo nuevamente porque el flag de busy sigue a 1

Este proceso se repite hasta que el flag de busy se pone a 0. La parte final la vemos en este cronograma:

  1. Hay un tic en LOOP por lo que se ejecuta un nuevo ciclo de lectura (el último)
  2. Último ciclo de lectura
  3. Se lee el flag de busy, y ahora vale 0
  4. Aparece un tic en done (en vez de en loop), ya que no hay que realizar nuevos ciclos de lectura
  5. El tiempo que transcurre entre los dos tics de done indican el tiempo que ha tardado el comando en ejecutarse: 1.3ms

Como estamos leyendo el flag de busy, podemos determine el tiempo exacto que tarda el comando en ejecutarse. El valor que hemos medido es de 1.3ms aproximadamente

Si ahora probamos con otro comando, como por ejemplo el escribir una 'A' (Hay que cambiar rs a 1), vemos que el tiempo que nos sale es de 29.2 µs:

En la tabla 6 (página 24 de la hoja de datos) se muestran los tiempos máximos de cada instrucción. El tiempo del CLS no está indicado, pero sí el del home (que es de 1.52ms), similar al que hemos obtenido con el CLS

El tiempo de escritura de los caracteres en el LCD es de 37µs, similar al que hemos obtenido nosotros

El bloque LCD-wait-8-bits

Este bloque es similar al LCD-cycle-rw: generar los ciclos de acceso al LCD (en lectura y escritura). Su interfaz, además, es la misma. La misión de este bloque es generar los retrasos adecuados a la operación a realizar. Se resumen a continuación:

  • Comandos de inicialización (cmd=0): Se genera un ciclo de escritura y se añade un retraso de 5ms
  • Comando normal (cmd=1) de escritura (rw=0): Se genera un ciclo de escritura y se añade un retraso de 8 ciclos
  • Lectura (cmd=1, rw=1): Se genera un ciclo de lectura y se ESPERA hasta que el flag de busy se ponga a 0

La implementación se muestra en esta figura

Todo gira en torno al bloque LCD-cycle-rw. Si se trata de una lectura y el flag de busy está a uno, se entra en el bucle. Este bucle genera ciclos de lectura hasta que el LCD esté disponible

En el caso de un ciclo de escritura o que el flag de busy esté a cero, se añade un retraso de 8 ciclos, y se pasa a la siguiente fase. Este retraso de 8 ciclos se ha añadido experimentalmente, para dejar un tiempo entre la lectura y la escritura. Si no se añaden estos ciclos, a veces se "come" caracteres

En la fase 2 se añade el retardo de 5ms en caso de tratarse de un comando especial de inicialización

Ejemplo 28: Probando el bloque LCD-wait-8-bits

En este circuito realizamos un ciclo de escritura y uno de lectura consecutivos, usando el bloque LCD-wait-8-bits. El LCD se queda listo para recibir el siguiente comando, ya que este bloque realiza las esperas necesarias (comprobando que el flag de busy esté a 0)

El comando que se usa es el de impresión: Se imprime el carácter 'A' en el LCD, que sólo se verá si previamente hemos dejado el LCD inicializado (ya que este ejemplo todavía no realiza esta inicialización)

(28_controler-8bit-busy-06.ice)

Con el analizador lógico vemos el cronograma y comprobamos que funciona correctamente:

Esto es lo que sucede en los puntos marcados en amarillo:

  1. Ciclo de escritura: Se escribe el carácter 'A' en el LCD
  2. Ciclo de lectura: Se lee el flag de busy del LCD
  3. El valor del flag de busy es 1 (Ocupado), por lo que se repite la lectura hasta que sea 0
  4. El flag ya es 0: El controlador termina y se activa el tic de done

Ejemplo 29: Implementando el controlador (I): comandos de inicialización

A partir del bloque LCD-wait-8-bits ya se puede construir el controlador. Lo iremos construyendo paso a paso, resolviendo los problemas uno a uno. Primero implementaremos el controlador para comandos normales

Para enviar un comando normal primero hay que leer del LCD (ciclo de lectura) hasta que el busy flag se ponga a 0, indicando que el LCD está listo para ejecutar el comando. A continuación hay que generar el ciclo de escritura. Es decir, hay que activar 2 veces el bloque LCD-wait-8-bits, usando una máquina de contar de 1 bit

Esto es lo que se hizo en el ejemplo 28

Ahora tenemos que implementar los comandos de inicialización especiales. En estos comandos sólo hay que hacer una escritura, porque el LCD todavía no está inicializado y NO se puede leer el Flag de busy. Por ello, el ciclo de lectura HAY QUE SALTARLO

En este circuito se añade la lógica adicional para la implementación de los comandos de inicialización. Cuando cmd es 0, la lectura NO se realiza (Se salta el bloque LCD-wait-8-bits)

(29_controler-8bit-busy-07.ice)

El circuito se pruede probar sin analizador lógico. Tras apretar 6 veces el pulsador SW1, el LCD se inicializa y el cursor se queda parpadeando en la posición (0,0). Observamos, que la inicialización funciona. Efectivamente sólo se realiza un ciclo de escritura con cada pulsación del botón

Ejemplo 30: Implementando el controlador (II): comandos de inicialización

El siguiente paso es que todos estos comando de inicialización se envíen de golpe, para comprobar que la temporización funciona bien. Por ello colocamos una máquina de contar de 3 bits, y hacemos que se ejecute 6 veces. En cada ejecución se envía un comando de inicialización

(30_controler-8bit-busy-08.ice)

Ahora con sólo apretar el pulsador SW1 una vez se realiza la inicialización completa y el cursor se pone a parpadear en la posición inicial

Ejemplo 31: Implementando el controlador (III): comandos normales y de inicialización

El siguiente paso es hacer una prueba de inicialización seguida de la impresión del carácter 'A'. Ahora la señal cmd se mantiene a 0 durante los primeros 6 comandos y luego se pone a 1 para indicar que el séptimo dato es un comando normal

También hay que poner la señal rs_in a 0 durante los 6 comandos de inicialización y luego a 1 para hacer la impresión

Este es el circuito:

(31_controler-8bit-busy-09.ice)

Ahora al apretar el pulsador (tras un reset) se inicializa, se borra la pantalla y se imprime una A. Si se aprieta una segunda vez se imprimen 7 A seguidas (porque las señales cmd y rs_in están a 1, y el dato está con A)

¡Nuestro controlador funciona! Tanto con comandos de inicialización como con comandos normales

Bloque LCD-write-busy-8-bits

En esta figura se muestran todos los pines del bloque LCD-write-busy-8bits

La señal de salida dout no se conecta directamente a los pines del LCD, sino que tiene que pasar primero por el bloque LCD-io para que el pin DB7 se configure como entrada o salida

ESta es la implementación del bloque:

Ejemplo 32: Probando el bloque LCD-write-busy-8-bits

En este ejemplo se inicializa el LCD y se imprime el mensaje LCD-write-busy8 al apretar el pulsador SW1. Los 4 primeros comandos son de inicialización, por lo que cmd se pone a 0. El resto de comandos son normales, y cmd se pone a 1

(32_controler-8bit-busy-10.ice)

Esto es lo que aparece en el LCD al apretar el pulsador sw1

Ejemplo 33: Midiendo el controlador LCD-write-busy-8-bits

Partimos del ejemplo 32 y sacamos por los pines DD0-DD5 las señales que queremos medir con el analizador lógico

(33_controler-8bit-busy-11.ice)

Esto es lo que obtenemos con el PulseView:

El tiempo total que tarda en imprimirse el mensaje es de 21.8ms. Se ven los 4 comandos iniciales: Son 4 pulsos en E que están separados una distancia de 5ms. Así, la inicialización tarda 20ms

Ahora ampliamos la zona con los comandos normales, que no son de inicialización:

**

El tiempo que tardan es de 1.8ms. Un tiempo muchísimo más rápido que si hubiésemos utilizado el controlador en bucle abierto, que tardaría 5ms por comando: Un total de 105ms (2 órdenes de magnitud más rápido!!!!)

Controlador en Bucle cerrado de 4 bits

Este es el controlador más complicado de todos. Igual que en los casos anteriores, este controlador lo iremos constuyendo incrementalmente, resolviendo problemas en cada capa. Partimos del bloque LCD-cycle-ns, del nivel 0

El primer problema a resolver es el de emitir dos ciclos seguidos para un comando normal, y sólo uno cuando se trata de un comando de inicialización. En este caso, además, la señal de rs debe ser 0, y la de rw también 0. Además, en cada caso se debe enviar el nibble correspondiente (primero el de mayor peso y luego el de menor)

Ejemplo 34: Generando ciclos en E

En este ejemplo se generan uno o dos ciclos en E según el tipo de comando. Si es un comando normal (cmd=1), se generan dos ciclos (y se multiplexa el dato). Si es un comando de inicialización (cmd=0) sólo se emite uno (y sólo se envía el byte de mayor peso del dato)

(34_controler-4bit-busy-01.ice)

Esto es lo que sucede cuando se emite un comando normal: Aparecen los dos pulsos en E

Y cuando se emite un comando de inicialización sólo aparece un pulso en E

El bloque LCD-cycle-4-bits

Este bloque genera 1 ó 2 ciclos de la señal E en función de si la señal de entrada cmd es 0 (Inicialización) ó 1 (Comando normal). También obtiene el tic de lectura necesario cuando se quiera leer el flag de busy

Estas son las E/S del bloque:

La implementación se muestra en esta figura:

Ejemplo 35: Probando el bloque LCD-cycle-4-bits

Este ejemplo es el mismo que el 34, pero usando el bloque LCD-cycle-4-bits. Los resultados son los mismos

(35_controler-4bit-busy-02.ice)

Ejemplo 36: Leyendo el flag de Busy

El siguiente problema a resolver es el de realizar la escritura de un dato en dos etapas: nibble superior y nibble inferior, y la lectura del flag de busy. Pero si el comando es de inicialización, entonces sólo se realiza la escritura de un nibble, o nada en el caso de lectura

En este circuito se realizan dos ciclos. El primero es de escritura (del carácter "A") y el segundo de lectura. El flag de busy se lee y se muestran por el LED7

(36_controler-4bit-busy-03.ice)

Esto es lo que se ve en el analizador lógico:

Si ahora se vuelve a apretar SW1, se realizan dos ciclos de lectura. Ahora el flag de busy está a 0

El bloque LCD-io-4-bits

El bloque LCD-io-4-bits es el que configura el pin DB7 como Entrada o salida

Ejemplo 37: Probando el bloque LCD-io-4-bits

Es el mismo ejemplo 36, pero usando el bloque LCD-io-4-bits

(37_controler-4bit-busy-04.ice)

Los resultados son los mismos que el ejempo 36

El bloque LCD-cycle-rw-4-bits

El bloque LCD-cycle-rw-4-bits nos permite emitir ciclos de escritura o bien ciclos de lectura. En el caso de la lectura se lee el flag de busy

La salida n indica el nibble a enviar en cada momento: Bien el de mayor peso o bien el de menor

(Implementación)

Ejemplo 38: Probando el bloque LCD-cycle-rw-4-bits

Este es el mismo ejemplo que el 36 y 37, pero usando el bloque LCD-cycle-rw-4-bits

(38_controler-4bit-busy-05.ice)

Los resultados son los mismos

Ejemplo 39: Añadiendo retardos y esperas

El siguiente paso es realizar las esperas necesarias según el tipo de comando:

  • Comando de inicialización (cmd=0): La espera es 5ms
  • Comando normal (cmd=1):
    • En la escritura se esperan 8 ciclos
    • La lectura se repite mientras que BF=1. En cuanto BF es 0, se termina

En este circuito de ejemplo se imprime una "A" en el LCD (Si es que previamente estaba incializado), y se espera a que BF sea 0: La inicialización se puede realizar previamente cargando el ejemplo 19

(39_controler-4bit-busy-06.ice)

Este es el resultado visto con el analizador lógico

Se comprueba cómo tras efectuarse la escritura, comienza la lectura y el flag de busy se pone a 1. Se continúa leyendo el flag de busy hasta que se pone a 0

El bloque LCD-wait-4-bits

El bloque LCD-wait-4-bits es similar al de 8 bits: Se encarga de las temporizaciones. Tiene las mismas señales de entrada y salida que el de 8 bits, pero el de 4 bits tiene la salida n que indica el nibble actual: 0 (de mayor peso) y 1 (de menor peso)

Ejemplo 40: Probando el bloque LCD-wait-4-bits

Este es el mismo ejemplo que los anteriores, pero usando el bloque LCD-wait-4-bits. Se escribe una "A" en el LCD (pero es necesario que se haya inicializado anteriormente el LCD) y luego se espera a que la señal de busy se ponga a 0

(40_controler-4bit-busy-07.ice)

Ejemplo 41: Implementando el controlador (I): Inicialización e impresión de A (Manual)

En este ejemplo se inicializa el controlador, y se imprime el carácter A con cada pulsación del botón SW1. En los comandos normales, primero se hace una lectura hasta que el bit de busy es 0, y luego la escritura del comando/dato. En los comandos de inicialización sólo se hace la escritura

En total se envían 8 comandos. Los 4 primeros son de inicialización (La señal cmd está a 0). Los 4 siguientes son de configuración (cmd está a 1 pero rs es 0). Y el último es un comando de impresión (cmd=1, rs=1)

(41_controler-4bit-busy-08.ice)

Esto es lo que se ve en el analizador lógico cuando se envían los comandos de inicialización (los 4 primeros):

Se emite un ciclo de escritura, y luego se esperan 5ms (En los comandos de inicialización hay que esperar 5ms). En esta otra figura se muestra la segunda parte del cronograma, donde se ve que la señal E no se vuelve a activar

Y esta es la captura cuando se envían comando de configuración. En total hay 4 pulsos en E. Los dos primeros se corresponden con los dos nibbles del ciclo de Lectura (donde se lee el flag de busy). Los dos últimos son los dos nibbles del ciclo de escritura

En el caso del último comando, el de impresión del caracter A, el cronograma es similar, pero ahora rs se pone a 1 en el ciclo de escritura

Al conectar la placa, y tras realizar 8 pulsaciones del botón SW1, el LCD se verá así:

Posteriores pulsaciones de SW1 harán que aparezcan más caracteres A (Uno por cada pulsación)

Ejemplo 42: Implementando el controlador (II): Inicialización e impresión de A (Automática)

El siguiente paso es hacer que se envían automáticamente todos los comandos, de inicialización, configuración e impresión, uno detrás de otro para comprobar que funciona bien a la máxima velocidad

Basta con ampliar el ejemplo 41 añadiendo una máquina de contar de 8

(42_controler-4bit-busy-09.ice)

Ahora podemos ver todo lo que pasa en esta captura. Primero se ven los 4 comandos de inicialización (que duran 5ms). Luego el resto de comandos. El tiempo total que tarda es de 21.408ms

En esta captura se ve la parte final. Los ciclos de lectura del flag de busy hasta que se pone a 0 y se pasa al último ciclo de escritura, donde se imprime la 'A' (Con rs=1 y rw=0)

La primera vez que se aprieta el pulsador SW1 se inicializa el LCD y se imprime una única A:

Si se aprieta una vez más, sin haber apretado el reset previamente, aparecerán 8 caracteres A:

Sucesivas pulsación de SW1 añaden grupos de 8 As

Bloque LCD-write-busy-4-bits

El bloque LCD-write-busy-4-bits es igual al de 8-bits. Las señales son las mismas excepto dout que ahora es de 4-bits en vez de 8

Ejemplo 43: Probando el bloque LCD-write-busy-4-bits

En este ejemplo se inicializa el LCD y se imprime la cadena LCD-write-busy4

(43_controler-4bit-busy-10.ice)

Esta es la lectura en el analizador. Los 22 comandos se ejecutan en un total de 21.870ms

Controladores de nivel 2

Los controladores de nivel 2 resuelven el problema de la inicialización. Arrancan al alimentar el circuito y quedan listos para su uso. También, en el caso de los controladores en bucle cerrado, se incorpora en estos controladores el control de la bidireccionalidad del pin del flag de busy

El resultado es que los controladores de nivel 2 son más sencillos de utilizar

En esta figura se muestra el aspecto de los 4 controladores de nivel 2:

Los 4 controladores tienen la misma interfaz: los pines de entrada/salida son los mismos para todos excepto el pin dout que dependen del interfaz del LCD (4 u 8 bits)

  • Parámetros:
    • Fsys: Frecuencia del reloj del sistema (12_000_000 Hz por defecto)
    • C: Establecer el estado del cursor en la inicialización del Display
      • 0: No mostrar cursor
      • 1: Cursor visible
    • B: Parpadeo del cursor (Blink)
      • 0: Sin parpadeo
      • 1: Cursor parpadea
  • Entradas:
    • rs: Selección del registro del LCD a escribir
      • 0: Registro de Instrucción
      • 1: Registro de datos
    • din[7:0]: Dato/comando a escribir en el LCD
    • write: Tic de escritura del comando
  • Salidas:
    • busy: Estado del LCD
      • 0: LCD listo para una nueva escritura
      • 1: LCD ocupado con la escritura en curso
    • ctrl[2:0]: Bus de control del LCD. Formado por las señales E,rs y rw (De mayor a menor peso)
    • dout: Bus de datos del LCD. El tamaño depende del tipo de interfaz: 4 u 8 bits
    • init_done: Inicialización del LCD completada (tic)
    • Done: Escritura completada

Igual que con los controladores de nivel 1, los de nivel 2 los iremos construyendo ejemplo a ejemplo. Partimos del nivel 1, y vamos resolviendo los problemas poco a poco. Estos problemas los encapsularemos en bloques

Ejemplo 44: Inicialización del controlador

El controlador final se encarga de inicializar el LCD, enviando los comandos necesarios según el tipo de interfaz usada (Bus de 8 ó 4 bits). Una vez inicializado, se comporta igual que los controladores de nivel 1: se encarga de escribir datos/comandos en el LCD

De esta manera, el usuario se olvida de la inicialización y se centra en enviar datos o comandos al LCD, de una forma sencilla. Este envío, además, es IGUAL para todos los controladores, con independencia de su tipo

Si no se indica lo contrario, usaremos por defecto el controlador LCD-Controller-busy-4-bits: El controlador en bucle cerrado con interfaz de 4 bits

El primer problema a resolver es el de añadir un circuito para que se envíen todos los comandos de inicialización. Esto ya lo hemos hecho antes en otros ejemplos. Pero ahora lo sistematizaremos

Definimos Comandos de inicialización como aquellos que se tienen que enviar nada más encender el LCD para que se quede configurado con la interfaz de datos elegida (8 ó 4 bits). Tienen unas restricciones de tiempo diferentes al resto de comandos. Los diferenciamos con la señal cmd que tiene que estar a 0

En este circuito se envían los 4 comandos de inicialización para un display con interfaz de 4 bits. El envío se realiza cuando se aprieta el pulsador SW1. El display se pondrá en blanco (o si ya lo teníamos inicializado se quedará como está).

(44_controller-4bit-busy-01-init.ice)

Al apretar el pulsador SW1 se arranca la máquina de contar que direcciona la memoria de comandos. Empieza con el primer comando. Los 4 comandos se envían secuencialmente al controlador. Cada vez que se ejecuta uno, se aciva la señal next que hace que la máquina de contar se incremente para apuntar al siguiente comando

Una vez que se han enviado los 4 comandos, la máquina de contar finaliza y emite un tic por done, que hace que se encienda el LED0 para indicar al usuario que se ha termiando

Los resultados se ven con el analizador lógico

Como son comandos de inicialización, el controlador emite 4 pulsos en E. La espera entre ellos es la establecido: 5ms. En total ha tarda 20ms (aprox)

Ejemplo 45: Comandos de configuración

El siguiente paso es enviar comandos de configuración al LCD para completar la inicialización. Son comandos normales (cmd=1) que se envían al registro de instrucciones (rs=0)

Nuestros controladores de nivel 2 usarán 3 comandos de configuración:

  • Configurar el display para trabajar con 2 líneas
  • Establecer el estado inicial del cursor: visible/no visible y parpadeo/no parpadeo
  • Borrar el LCD. El cursor se sitúan en la primera posición y se borra lo que hubiese previamente

En este circuito de ejemplo se hace la inicialización y la configuración. Para distinguir entre ambos tipos de comandos se utiliza un comparador: Los 4 primeros comandos de la memoria son de inicialización, y los 3 restantes de configuración. Ahora la señal cmd no es constante. Vale 0 inicialmente y luego se pone a 1

(45_controller-4bit-busy-02-init.ice)

Ahora, al apretar el pulsador sw1, veremos en el LCD que aparece el cursor en la primera posición, parpadeando

En esta imagen se muestran los resultados con el Analizador:

El tiempo total empleado es de 20.087ms. Los 4 primeros comandos son muy lentos: Tardan 5ms cada uno. Pero los 3 de configuraciones son mucho más rápidos. Si hacemos zoom en esa parte lo podremos ver mejor. El tiempo que cmdes 1 es el usado para saber su duración

Los comandos de configuración ha tardado 58.6µs (Aunque este tiempo variará ligeramente de una ejecución a otra)

Ejemplo 46: Configurando el cursor mediante parámetros

El cursor se configura con el comando Display control que ya conocemos. Podemos hacer que sea visible o no, y en caso de ser visible, podemos establecer el modo normal o parpadeo

Como los comandos de configuración están situados en una memoria, para configurarlos habría que editar el valor de este comando en la memoria. Sin embargo sería mucho más interesante incluir dos parámetros binarios que nos permitan establecer estos modos:

  • Parámetro C (Cursor): Establecer la visibilidad del cursor:
    • 0: No visible
    • 1: Visible
  • Parámetro B (Blink): Establecer el modo de parpadeo del cursor:
    • 0: Si parpadeo
    • 1: El cursor parpa Para lograr esto lo hacemos en dos fases. Primero construimos un valor de 8 bits a partir de los parámetros C y B. Para ello usamos el formato del comando Display control

Después "mapeamos" este valor en la memoria, de forma que cuando se solicite la dirección 5 (que es donde está situado el comando en esta configuración), se devuelva el valor creado a partir de los parámetros C y B.

Este mapeo se implementa fácilmente con un comparador y un multiplexor. El comparador detecta cuándo ha llegado la dirección 5, y el multiplexor, en ese caso, devuelve el valor calculado. En caso contrario (direcciones diferentes a la 5) se devuelve lo que hay en memoria

Este es el circuito:

(46_controller-4bit-busy-03-init.ice)

El contenido de la memoria es el mismo que en el ejemplo anterior:

30 //-- Init 0
30 //-- Init 1
30 //-- Init 2
20 //-- Funct Set. 4 bits
28 //-- Funct Set. 2 lines
0F //-- Disp ctrl: Disp on. Curso on. Blink ON
01 //-- CLS

En la dirección 5 se encuentra el comando de Display control, que tiene un valor de 0xFF: Cursor encendido y parpadeo activo. Sin embargo, ese NO es el valor que se usa, sino que se sustituye por el nuevo creado, y que está disponible en la etiqueta Disp_ctrl. En este ejemplo se ha activado el cursor para que sea visible, pero se ha eliminado el parpadeo. Por ello, al ejecutarlo esto es lo que aparecerá en la pantalla del LCD:

Establecer el modo del cursor es ahora extremadamente fácil: Basta con establecer los parámetros C y B

La temporización es la misma que en el ejemplo anterior

El bloque LCD-config

Para realizar toda esta parte de la inicialización, encapsulamos la tabla con los comandos de inicialización y configuración, junto al resto de elementos de construcción del comando display control y su mapeo en el bloque LCD-config. Este bloque es genérico y nos sirve inicializar los 4 controladores, aunque será necesario establecer valores adecuados de todos sus parámetros

Ejemplo 47: Probando el bloque LCD-config

En este ejemplo es similar al 46, pero usando el bloque LCD-config. El circuito queda mucho más compacto y fácil de entender

(47_controller-4bit-busy-04-config.ice)

Ejemplo 48: Inicialización automática

El siguiente problema a resolver es el de la inicialización automática del controlador. Nada más arrancar el circuito, queremos que el controlador se inicialice, y quede listo para su uso. Según la información de la hoja de datos, hay que esperar 15ms para que el LCD arranque y se pueda inicializar

Por ello basta con introducir un temporizador de 15ms, activado por el componente start (tic inicial en el ciclo 0)

(48_controller-4bit-busy-05-init-auto.ice)

Para medirlo con el analizador lógico, dado que ahora hay que hacer primero un reset, nos sincronizamos con el flanco de bajada de la señal tic_ini, que indica que la FPGA se ha inicializado

Los resultados los vemos en esta figura:

El arranque del LCD tarda 15ms, que es la espera que hemos añadido. Se inicializa en 20.1ms. El tiempo total desde que se aprieta reset hasta que se puede usar es de 35.1ms aproximadamente

Controlador LCD-controller-busy-4-bits

Ya tenemos todos los elementos para crear nuestro primero controlador de nivel 2. Empezamos por el más complejo: el controlador en bucle cerrado con interfaz de 4 bits

Los controladores de nivel 2 funcionan en dos estados. Al arrancar el circuito se comienza en el estado de inicialización, y se envían todos los comandos de inicialización y configuración. El cursor queda en el estado establecido por los parámetros C y B

Una vez que se ha completado, se emite un tic por init_done y el controlador entra en el segundo estado, donde se envían los comandos del usuario

Implementación

El estado del circuito se obtiene en la señal busy_ini. Según su valor, cmd2, rs2, din2 y write2 provienen bien del bloque de configuración o bien de las entradas del usario, a través de los multiplexores

En esta figura se muestra su implementación:

Una vez completada la inicialización, busy_ini será siempre 0, por lo que se estará en el modo usuario indefinidamente

Ejemplo 49: Probando el controlador LCD-controller-busy-4-bits

Es es el circuito más sencillo para probar el LCD: Tras la inicialización del LCD se imprime el carácter A

(49_controller-4bit-busy-06-test.ice)

Al hacer reset (o encender el circuito) se inicializa el LCD. Tras ello se emite un tic por init_done, que se introduce otra vez por write, lo que provoca que se imprima el carácter A (Y a su derecha estará el cursor parpadeando)

Este controlador, es similar al de escritura en el puerto serie. Ambos son controaldores de caracteres: envían el dato que llega por su entrada. En un caso por el puerto serie para que salga en la consola y en el otro para que se imprima en el LCD

Ejemplo 50: Midiendo el controlador LCD-controller-busy-4-bits

Este ejemplo es igual que el 49, pero se han sacado las señales necesarias para realizar las mediciones con el Analizador lógico. Este será nuestro Circuito de referencia para comparar los 4 controladores

(50_controller-4bit-busy-07-measure.ice)

Esta es la medición con el Analizador:

El tiempo de inicialización es de 35.084ms. El tiempo en imprimir la A es más corto. Lo podemos ver haciendo zoom:

Tarda 1.33ms. Este tiempo en realida incluye el tiempo de espera del comando CLS (que es bastante grande)

Este es el resumen de los datos de este circuito:

  • Tiempo total: 36.42ms
  • Recursos: 145 LC

Controlador LCD-controller-open-4-bits

Este controlador trabaja con una interfaz de datos de 4 bits y se usa en bucle abierto. Como se muestra en los apartados siguientes, este controlador es más sencillo, y por tanto ocupa menos recursos. Pero por contra tarda más tiempo

Implementación

La implementación es similar a la del controlador LCD-controller-busy-4-bits, pero usando el bloque LCD-write-open-4-bits para escribir en el LCD. Como es en bucle abierto, NO se usa el bloque LCD-io-4-bits

Ejemplo 51: Probando y midiendo el controlador LCD-controller-open-4-bits

Usamos el mismo ejemplo de referencia para probar el controlador, y medirlo: Un ejemplo hola mundo que inicializa el LCD e imprime el carácter A

(51_controller-4bit-open-01-measure.ice)

Esta es la medición con el Analizador:

El tiempo de inicialiación es de 50.022ms (50ms aprox), y el tiempo en escribir el carácter A es de 5ms

Este es el resumen de los datos de este circuito:

  • Tiempo total: 55.025ms
  • Recursos: 114 LC

Ejemplo 52: Pruebas con la LCD-shield

El controlador LCD-controller-open-4-bits es el que hay que usar con la LCD-shield. Este es el mismo programa de referencia pero adaptado para la LCD-shield. Sólo cambia el pinout. Se han eliminado todos los pines de las mediciones

(52_controller-4bit-open-02-LCD-shield.ice)

Esto es lo que se ve en la LCD-shield:

Controlador LCD-controller-busy-8-bits

Este controlador trabaja con una interfaz de datos de 8 bits y se usa en bucle cerrado. Es por tanto más complejo pero tarda menos tiempo

Implementación

La implementación es similar a la del controlador LCD-controller-busy-4-bits, pero usando el bloque LCD-write-busy-8-bits para escribir en el LCD. También se usa el bloque LCD-io-8-bits

La inicialización cambia. Estos son los 6 comandos de inicialización que se usan en el bloque LCD-config:

30 //-- Init 0
30 //-- Init 1
30 //-- Init 2
38 //-- Cmd: Function set: 2-lineas, 8-bits
0F //-- Disp ctrl: Disp on. Curso on. Blink ON
01 //-- CLS

La instrucción que configura el cursor está en la dirección 4, a diferencia del de 4 bits que está en la 5

Ejemplo 53: Probando y midiendo el controlador LCD-controller-busy-8-bits

Este es el circuito de referencia

(53_controller-8bit-busy-01-measure.ice)

Y esta es la medición con el Analizador lógico

En esta imagen se ha hecho zoom para ver el tiempo de inicialización y el tiempo de impresión de la A

Este es el resumen de los datos de este circuito:

  • Tiempo total: 36.368ms
  • Recursos: 130 LC

Controlador LCD-controller-open-8-bits

Este controlador trabaja con una interfaz de datos de 8 bits y se usa en bucle abierto. Es por tanto más simple pero tarda más tiempo

Implementación

La implementación es similar a la del controlador LCD-controller-busy-8-bits, pero usando el bloque LCD-write-open-8-bits para escribir en el LCD

La inicialización es la misma que el LCD-controller-busy-8-bits:

30 //-- Init 0
30 //-- Init 1
30 //-- Init 2
38 //-- Cmd: Function set: 2-lineas, 8-bits
0F //-- Disp ctrl: Disp on. Curso on. Blink ON
01 //-- CLS

La señal cmd proveniente del bloque LCD-config no se usa, ya que todos los comandos (de inicialización y normales) se envían de la misma forma (y con el mismo retardo de 5ms)

Ejemplo 54: Probando y midiendo el controlador LCD-controller-open-8-bits

Este es el circuito de referencia

(54_controller-8bit-open-01-measure.ice)

Y esta es la medición con el Analizador lógico

Este es el resumen de los datos de este circuito:

  • Tiempo total: 50.014ms
  • Recursos: 105 LC

Tabla de comparación

Controlador Interfaz Tamaño (LC) Tiempo total (ms) Tiempo impresión (ms) Comentarios
Open 8-Bits 105 50.014 5 (5.002) Más pequeño
Open 4-Bits 114 55.025 5 (5.003) Más lento
Busy 8-Bits 130 36.368 1.334 Más rápido
Busy 4-Bits 145 36.42 1.334 Más grande

Los controladores en bucle cerrado son Más rápidos, pero consumen más recursos que los de bucle abierto. Por contra, los de bucle abierto son más pequeños (menos recursos), pero son más lentos

Los controladores de 8-bits son ligeramente más rápidos que los de 4-bits, pero consumen menos recursos

Ejemplos básicos de uso del LCD

Ahora que ya tenemos todos los controladores listos, vamos a usarlos para hacer unos circuitos mínimos de uso del LCD. Usaremos el LCD-controller-busy-4-bits que es el que menos pines necesita, y también el LCD-controller-open-4-bits que es el que necesitamos para usar la LCD-shield

Ejemplo 55: Impresión de una A con el pulsador

El LCD se inicializa y se sitúa el cursor en la parte superior izquierda (sin parpadeo). Cada vez que se aprieta el pulsador SW1 se imprime una A

(55_example-01-print-A-button.ice)

Este es el resultado cuando se aprieta el pulsador 10 veces (Aparecen 10 As)

Ejemplo 56: Impresión de una A con el pulsador (LCD-shield)

Este es el mismo ejemplo que el 55, pero para la LCD-Shield

(56_example-01-print-A-button-LCD-shield.ice)

Esto es lo que se muestra en el LCD al apretar 10 veces el pulsador SW1

Ejemplo 57: Impresión de "HOLA" con el pulsador

En este ejemplo se imprime la cadena "HOLA" en el LCD al apretar el pulsador. Para imprimir esta cadena hay que activar 4 veces el controlador del LCD, pasándole en cada vez el carácter correspondiente. Esto se hace con una Máquina de contar de 2 bits

(57_example-02-print-HOLA-button.ice)

Los caracteres de la cadena están almacenados en 4 registros de 8 bits, conectados entre ellos. El registro de la derecha es el que contiene el siguiente carácter a imprimir. Cuando se ha terminado de imprimir el caraćter anterior, se activa la señal next, y los registros se actualizan con el siguiente carácter

Cuando la máquina de contar termina (n=3), se activa la señal rst que inicializa de nuevo los registros. De esta forma si se vuelve a apretar el pulsador SW1, se imprime de nuevo la cadena "HOLA", a continuación de la anterior

Ejemplo 58: Impresión de "HOLA" con el pulsador (LCD-shield)

Este es el mismo ejemplo anterior, pero para la LCD-shield

(58_example-02-print-HOLA-button-LCD-shield.ice))

Y esto es lo que aparece en el LCD al apretar el pulsador SW1

Comando LOCATE

El LCD tiene un total de 32 caracteres, 16 en cada una de las dos líneas. Estos caracteres se encuentra en la memoria DDRAM. El comando LOCATE permite seleccionar la posición de esta memoria donde colocar el cursor

Este es el formato del comando:

La memoria DDRAM tiene un tamaño de 128 bytes (Direcciones de 7 bits). Se divide en dos bloques de 64 bytes. Cada bloque se corresponde con una Línea virtual del LCD. Es decir, que virtualmente, el LCD podría tener 2 líneas de 64 caracteres.

Sin embargo, en los LCDs 16x2, estas líneas virtuales son de 32 caracteres, de los que sólo 16 se ven en un momento determinado. Esta idea se muestra en este dibujo:

La primero línea (línea 0) comienza en la dirección 0x00 y llega hasta la 0x1F (Pero sólo son visibles en el LCD real desde la 0x00 hasta la 0x0F)
La segunda línea comienza en la dirección 0x40, y llega hasta la 0x5F (Pero sólo son visibles desde la 0x40 hasta la 0x4F)

Usando uno de los comandos del LCD es posible desplazar el LCD para mostrar información del LCD virtual

Ejemplo 59: Mostrando el pulsador SW1 en el LCD

En este ejemplo se muestra en el LCD el estado del pulsador SW1 en binario:

  • 0: Si no está apretado
  • 1: Si está apretado

Esta información se está constantemente enviando al LCD, de manera que siempre se está mostrando el estado del pulsador en la posición superior izquierda (Fila 0, columna 0)

(59_example-03-Display-BIT.ice)

En cada actualización del LCD se envían 2 comandos: Uno para posicionar el cursor en la primera posición (Locate 0,0), y el otro con el estado del pulsador: Dígito '0' ó '1'

Cada vez que termina un refresco se resetean los registros, para volver al estado inicial, y se comienza un nuevo refresco

Al arrancar, como el pulsador no está apretado, aparece un 0 en la primera posición:

Al apretar el pulsador SW1 aparecerá un 1

Y al soltarlo otra vez se verá el 0

Ejemplo 60: Mostrando el pulsador SW1 en el LCD (LCD-Shield)

Este es el mismo ejemplo pero para la LCD-Shield

(60_example-03-Display-BIT-LCD-shield.ice)

Al ejecutarlo se muestra un 0 en la posición superior izquierda

Al apretar el pulsador SW1 aparece un 1:

Ejemplo 61: Mostrando los pulsadores SW1 y SW2 en el LCD

En este ejemplo se muestran en el LCD el estado de ambos pulsadores SW1 y SW2. En cada ciclo se hacen 4 escrituras en el LCD. Primero el comando LOCATE, luego el estado del pulsador SW1, el carácter separador - y por último el estado del pulsador SW2

(61_example-04-Display-2xBIT.ice)

Al arrancar se muestra el estado de ambos pulsadores: "0-0", ya que no están pulsados. Pulsando cada uno de ellos, de forma independiente, se muestra el estado en el LCD. Por ejemplo, si se pulsa SW2 se verá: "0-1". En esta figura se muestran los 4 estados

Ejemplo 62: Mostrando los pulsadores SW1 y SW2 en el LCD (LCD-shield)

Mismo ejemplo pero para la LCD-Shield

(62_example-04-Display-2xBIT-LCD-Shield.ice)

Y esto es lo que se ve en el LCD:

Ejemplo 63: Enviando comandos desde memoria

En este ejemplo se imprime la cadena "FPGAwars" en la línea superior y "Controlador LCD" en la inferior. Para ello hay que usar el comando locate dos veces. Para situarse al comienzo de la línea 1, y luego para hacerlo al comienzo de la línea 2

Cuando se mezclan datos a imprimir junto a comandos, resulta útil utilizar una memoria con palabras de 9-bits. El bit de mayor peso se corresponde con rs y nos indica si se trata de un Comando (rs=0) o de un dato (rs=1)

Utilizando una Máquina de contar se recorre esta memoria y se escriben los contenidos en el LCD

(63_example-05-memory-string.ice)

Al arrancar el circuito se imprime automáticamente el mensaje:

Este es el contenido de la memoria:

0_80 //-- Locate (0,0)
1_46 //-- Print "F"
1_50 //-- Print "P"
1_47 //-- Print "G"
1_41 //-- Print "A"
1_77 //-- Print "w"
1_61 //-- Print "a"
1_72 //-- Print "r"
1_73 //-- Print "s"
0_C0 //-- locate (0,1)
1_43 //-- Print "C"
1_6f //-- Print "o"
1_6e //-- Print "n"
1_74 //-- Print "t"
1_72 //-- Print "r"
1_6f //-- Print "o"
1_6c //-- Print "l"
1_61 //-- Print "a"
1_64 //-- Print "d"
1_6f //-- Print "o"
1_72 //-- Print "r"
1_20 //-- Print " "
1_4c //-- Print "L"
1_43 //-- Print "C"
1_44 //-- Print "D"

Ejemplo 64: Enviando comandos desde memoria (LCD-Shield)

Este es el circuito para la LCD-Shield

(64_example-05-memory-string-LCD-Shield.ice)

Y esto es lo que aparece en el LCD:

El script de python Text2Mem

Para generar fácilmente el contenido de la memoria, con las cadenas a imprimir en el LCD, podemos usar el script Test2Mem.py

#!/usr/bin/python

import sys

if len(sys.argv) == 1:
    #-- No arguments given. String to convert
    str = "FPGAwars"
else:
    #-- Get the string from the argument
    str = sys.argv[1]

# Convert the string to a list of hexadecimal bytes
list_ascii = [f'1_{ord(char):x} //-- PRINT {char}' for char in str]

# Create the memory string: Join all the elements
mem_content = "\n".join(list_ascii)

print(f'Input string: {str}')
print("Memory contents: ")

print()
print(mem_content)
print()

Por ejemplo, si queremos que en el LCD se imprima la cadena "Artemis I", generamos el contenido de la memoria de esta forma:

$ python3 Test2Mem.py "Artemis I"
Input string: Artemis I
Memory contents: 

1_41 //-- PRINT A
1_72 //-- PRINT r
1_74 //-- PRINT t
1_65 //-- PRINT e
1_6d //-- PRINT m
1_69 //-- PRINT i
1_73 //-- PRINT s
1_20 //-- PRINT  
1_49 //-- PRINT I

Luego sólo hay que hacer un "copy&paste" y meterlo en el bloque de memoria de Icestudio

Caracteres definidos por el usuario

Los LCDs 16x2 nos permiten definir hasta 8 caracteres, que se almacean en la memoria CGRAM. Cada caracter es una matrix de 5x8 puntos. Para cada caracter se necesitan 8 bytes para su definición: uno para cada línea

En esta figura se muestra un carácter definido por el usuario (un muñeco), y el valor de los bytes de las 8 líneas

La CGRAM tiene en total 64 bytes. Las direcciones de la CGRAM son de 6 bits: Desde la 0x00 hasta la 0x3F

Para definir un carácter hay que escribir los valores de sus líneas en la dirección correspondiente de la CGRAM. Para ello hay que utilizar el comando Set CGRAM Address

Primero se establece la dirección del carácter (De la primera fila) con el comando Set CGRAM. Las direcciones correspondientes a los caracters 0 - 7 son: 0x40, 0x48,0x50, 0x58, 0x60, 0x68, 0x70 y 0x78. Este comando hace que las sucesivas escrituras de datos se hagan en la CGRAM. Por cada escritura se incrementa la siguiente dirección

Una vez enviados todos los datos a la CGRAM (correspondientes a los caracteres que se quieran definir), hay que enviar el comando LOCATE (Set DDRAM address). Las siguientes escrituras se muestran en el LCD

Ejemplo 65: Imprimiendo un carácter definido por el usuario

En este ejemplo se define el carácter 0 (el monigote de la figura del apartado anterior) y luego se imprimen un par de ellos, separados por un espacio

(65_example-05-user-chars.ice)

El circuito es el mismo que el del ejemplo 63: Se envían los comandos/datos almacenados en la memoria. Pero ahora el contenido de la memoria es diferente:

0_40 //-- Set CGRAM addr (Car 0)
1_0E //--  ***  
1_1F //-- *****
1_0E //--  ***
1_04 //--   *  
1_1F //-- *****
1_04 //--   *
1_0A //--  * * 
1_11 //-- *   * 
0_80 //-- Set DDRAM addr (Locate (0,0))
1_00 //-- PRINT carácter 0
1_20 //-- PRINT espacio
1_00 //-- PRINT caracter 0

Y esto es lo que se muestra en el LCD al cargarlo en la FPGA

Nos aparecen los dos monigotes 😎️

Ejemplo 66: Imprimiendo un carácter definido por el usuario (LCD-Shield)

Este es el mismo ejemplo pero para la LCD-Shield:

(66_example-05-user-chars-LCD-Shield.ice)

Y esto es lo que sale en el LCD

Ejemplo 67: Mostrar estado del pulsador

Este ejemplo es similar al del ejemplo 59: Mostrar el estado del pulsador SW1 en la primera posición del LCD. PERO ahora usan dos caracteres definidos por el usuario, para representar una barra deslizante de dos posiciones

Por ello, es necesario combinar 2 circuitos: uno que defina los nuevos caracteres, y otro que haga constantemente el pintado en el LCD del estado del pulsador. En el circuito está dividido en 2 fases: La fase de definición (fase 1) y la fase de pintado (fase 2)

(67_example-06-switch-on-off.ice)

Para cada fase se usa una Máquina de contar conectada al controlador del LCD. Esta conexión está controlada por unos multiplexores, de forma que durante la fase 1, se conecta la máquina 1. Y durante la fase 2, la máquina 2. Se utiliza la señal busy1 de la máquina uno para indicar en qué fase estamos

En esta figura se muestra lo que aparece en el LCD:

Este es el contenido de la memoria, en el que se definen los dos caracteres (0 y 1):

0_40 //-- Car 0. Linea 0
1_0E //--  ###
1_11 //-- #   #
1_11 //-- #   #
1_0E //--  ###
1_0E //--  ###
1_0E //--  ###
1_0E //--  ###
1_0E //--  ###
1_0E //--  ### --- Car 1
1_0E //--  ###
1_0E //--  ###
1_0E //--  ###
1_0E //--  ###
1_11 //-- #   #
1_11 //-- #   #
1_0E //--  ###
0_80

Ejemplo 68: Mostrar estado del pulsador (LCD-Shield)

Mismo ejemplo, pero para la LCD-Shield:

(68_example-06-switch-on-off-LCD-Shield.ice)

Esto es lo que aparece en el LCD:

Ejemplo 69: Ejemplo 1 de la colección iceCrystal

Es el ejemplo 1 de la colección iceCrystal, pero migrado al nuevo controlador. Se imprimen las palabras FPGA-WARS# junto a un logo formado por 8 caracteres creados por el usuario

(69_example-07-user-chars.ice)

Esto es lo que se muestra en el LCD:

Este es el contenido de la memoria:

0_40 //-- Car 0. Linea 0
1_00 //--        
1_01 //--     *
1_03 //--    **
1_01 //--     *
1_01 //--     *
1_0F //--  ****
1_18 //-- **   
1_1B //-- ** **
1_00 //--        Car 1. Linea 0
1_11 //-- *   *
1_1B //-- ** **
1_11 //-- *   *
1_11 //-- *   *
1_1F //-- *****
1_11 //-- *   *
1_15 //-- * * *
1_00 //--        Car 2. Linea 0
1_11 //-- *   *
1_1B //-- ** **
1_11 //-- *   *
1_11 //-- *   * 
1_1F //-- *****
1_01 //--     *
1_0F //--  ****
1_00 //--        Car 3
1_10 //-- *      
1_18 //-- **   
1_10 //-- *    
1_10 //-- *    
1_1E //-- ****
1_03 //--    **
1_0B //--  * **
1_19 //-- **  *  Car 4 
1_1B //-- ** **
1_1B //-- ** **
1_0F //--  ****
1_01 //--     *
1_03 //--    **
1_01 //--     *
1_00 //--
1_11 //-- *   *  Car 5
1_17 //-- * ***
1_17 //-- * ***
1_1F //-- *****
1_11 //-- *   *
1_1B //-- ** **
1_11 //-- *   *
1_00 //--
1_09 //--  *  *  Car 6
1_0D //--  ** *
1_01 //--     *
1_1F //-- *****
1_11 //-- *   *
1_1B //-- ** **
1_11 //-- *   *
1_00 //--
1_0B //--  * **  Car 7
1_03 //--    **
1_0B //--  * **
1_1E //--  ****
1_10 //--  *
1_18 //--  **   
1_10 //--  *
1_00 //--
0_80 //-- LOCATE 0,0
1_7E //-- PRINT ->
1_46 //-- PRINT F
1_50 //-- PRINT P
1_47 //-- PRINT G
1_41 //-- PRINT A
1_B0 //-- PRINT -
1_57 //-- PRINT W
1_41 //-- PRINT A
1_52 //-- PRINT R
1_53 //-- PRINT S
1_23 //-- PRINT #
1_20
1_00 //-- PRINT 0
1_01 //-- PRINT 1
1_02 //-- PRINT 2
1_03 //-- PRINT 3
0_C0 //-- LOCATE 0,1
1_FC //-- PRINT (marcianito)
1_46 //-- PRINT F
1_50 //-- PRINT P
1_47 //-- PRINT G
1_41 //-- PRINT A
1_B0 //-- PRINT -
1_57 //-- PRINT W
1_41 //-- PRINT A
1_52 //-- PRINT R
1_53 //-- PRINT S
1_23 //-- PRINT #
1_20 //-- PRINT " "
1_04 //-- PRINT 4
1_05 //-- PRINT 5
1_06 //-- PRINT 6
1_07 //-- PRINT 7

Ejemplo 70: Ejemplo 1 de la colección iceCrystal (LCD-Shield)

Mismo ejemplo que el 69, pero para la LCD-Shield:

(70_example-07-user-chars-LCD-Shield.ice)

Esto es lo que aparece en el LCD:

Conclusiones

Ya disponemos de un controlador de caracteres para el LCD (con sus 4 variables). A este nivel, podemos imprimir caracteres individuales en el LCD, así como enviar comandos. Es similar a otros controladores de caracteres, como el del puerto serie

Sigue siendo todavía un controlador de bajo nivel. Para imprimir cadenas o mostrar información más compleja es necesario añadir más circuitería. Por eso, todavía necesitamos crear controladores de mayor nivel, que nos faciliten el acceso y que nos permitan hacer aplicaciones más avanzadas fácilmente. Una de las cosas que necesitamos es poder crear componentes que se conecten mediante un Bus al controlador del LCD. Y así poder combinar comportamientos. Poco a poco, iremos subiendo de nivel. Seguimos avanzando. ¡Vamos!

Autor

Licencia

Créditos y agradecimientos

  • Carlos Venegas. Es el autor de la colección iceCrystal con el controlador original (y ejemplos) para manejo de LCDs con interfaz de 4 y 8 b8ts. ¡Muchísimas gracias! 😄

Enlaces

Clone this wiki locally