-
Notifications
You must be signed in to change notification settings - Fork 14
CT.12: Controlador LCD 16x2
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
- 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
Todos los ejemplos se han probado con Icestudio 0.12. Usa esta versión o superior
- 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
Todos los ejemplos de este cuaderno técnico están accesibles en su repositorio en github
- Introducción
- Nivel 0: Bloques de temporización
- Familia de controladores
- Inicialización del LCD
-
Controladores de nivel 1
- Controlador en bucle abierto de 8 bits
-
Controlador en Bucle abierto de 4 bits
- Ejemplo 18: Impresión de caracteres de forma automática
- Ejemplo 19: Inicialización e impresión de caracteres de forma automática
- Ejemplo 20: Inicialización e impresión de caracteres de forma automática. Implementación II
- Bloque LCD-write-open-4-bits
- Ejemplo 21: Probando el controlador LCD-write-open-4-bits
-
Controlador en bucle cerrado de 8 bits
- Lectura del Busy Flag
- Ejemplo 22: Lectura y medición del busy flag
- Ejemplo 23: Ciclo de escritura y de lectura consecutivos
- Bloque LCD-cycle-rw
- Ejemplo 24: Probando el bloque LCD-cycle-rw
- Bloque LCD-io-8-bits
- Ejemplo 25: Probando el bloque LCD-io-8-bits
- Ejemplo 26: Circuito de repetición condicional de una acción
- Ejemplo 27: Esperando a que el busy flag sea 0
- Bloque LCD-wait-8-bits
- Ejemplo 28: Probando el bloque LCD-wait-8-bits
- Ejemplo 29: Implementando el controlador (I): Comandos de inicialización
- Ejemplo 30: Implementando el controlador (II): Comandos de inicialización
- Ejemplo 31: Implementando el controlador (III): Comandos normales y de inicialización
- Bloque LCD-write-busy-8-bits
- Ejemplo 32: Probando el bloque LCD-write-busy-8-bits
- Ejemplo 33: Midiendo el controlador LCD-write-busy-8-bits
-
Controlador en bucle cerrado de 4 bits
- Ejemplo 34: Generando ciclos en E
- El bloque LCD-cycle-4-bits
- Ejemplo 35: Probando el bloque LCD-cycle-4-bits
- Ejemplo 36: Leyendo el Flag de busy
- El Bloque LCD-io-4-bits
- Ejemplo 37: Probando el bloque LCD-io-4-bits
- El bloque LCD-cycle-rw-4-bits
- Ejemplo 38: Probando el bloque LCD-Cycle-rw-4-bits
- Ejemplo 39: Añadiendo retardos y esperas
- El bloque LCD-wait-4-bits
- Ejemplo 40: Probando el bloque LCD-wait-4-bits
- Ejemplo 41: Implementando el controlador (I): Inicialización e impresión de 'A' (Manual)
- Ejemplo 42: Implementando el controlador (II): Inicialización e impresión de
A
(Automático) - Bloque LCD-write-busy-4-bits
- Ejemplo 43: Probando el bloque LCD-write-busy-4-bits
-
Controladores de nivel 2
- Ejemplo 44: Inicialización del controlador
- Ejemplo 45: Comandos de configuración
- Ejemplo 46: Configurando el cursor mediante parámetros
- El bloque LCD-config
- Ejemplo 47: Probando el bloque LCD-config
- Ejemplo 48: Inicialización automática
- Controlador LCD-controller-busy-4-bits
- Controlador LCD-controller-open-4-bits
- Controlador LCD-controller-busy-8-bits
- Controlador LCD-controller-open-8-bits
- Tabla de comparación
-
Ejemplos básicos de uso del LCD
- Ejemplo 55: Impresión de una
A
con el pulsador - Ejemplo 56: Impresión de una
A
con el pulsador (LCD-shield) - Ejemplo 57: Impresión de "HOLA" con el pulsador
- Ejemplo 58: Impresión de "HOLA" con el pulsador (LCD-shield)
- Comando LOCATE
- Ejemplo 59: Mostrando el pulsador SW1 en el LCD
- Ejemplo 60: Mostrando el pulsador SW1 en el LCD (LCD-Shield)
- Ejemplo 61: Mostrando los pulsadores SW1 y SW2 en el LCD
- Ejemplo 62: Mostrando los pulsadores SW1 y SW2 en el LCD
- Ejemplo 63: Enviando comandos desde memoria
- Ejemplo 64: Enviando comandos desde memoria (LCD-shield)
- El script de Python Text2Mem
- Caracteres definidos por el usuario
- Ejemplo 65: Imprimiendo un carácter definido por el usuario
- Ejemplo 66: Imprimiendo un caraćter definido por el usuario (LCD-Shield)
- Ejemplo 67: Mostrar el estado del pulsador
- Ejemplo 68: Mostrar el estado del pulsador (LCD-Shield)
- Ejemplo 69: Ejemplo 1 de la colección iceCrystal
- Ejemplo 70: Ejemplo 1 de la colección iceCrystal (LCD-Shield)
- Ejemplo 55: Impresión de una
- Conclusiones
- Autor
- Licencia
- Créditos y agradecimientos
- Enlaces
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
- Hoja de datos del LCD HD4478OU: HD44780U.pdf
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
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
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
yRW
-
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
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
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
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:
- 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)
- Esperar un tiempo mínimo de 85ns
- Poner la señal E a 1. Y dejarla en estado alto al menos 490 ns
- Poner la señal E a 0. Dejarla en ese estado al menos 510ns (para que su periodo sea de 1000ns)
- 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
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:
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
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
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
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
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
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
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)
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
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
El cronograma generado es el mismo mostrado en los ejemplos anteriores
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
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
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
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
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)
El cronograma es el mismo que en los ejemplos anteriores
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
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
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
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
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
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:
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'
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.
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
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
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
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
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:
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:
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'
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'
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
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
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
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
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
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
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
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
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:
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 | Sí |
1 | 0 | 0 | No |
1 | 1 | 1 | Sí |
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:
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
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
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
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
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
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
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 sobrerw
yrs
. Cuandocmd
es 0 (Inicialización), entoncesrs
yrw
serán 0 tambien-
0: Comando de inicialización. Las señales
rw
yrs
del display se ponen a 0 - 1: Comando normal
-
0: Comando de inicialización. Las señales
-
rw
: Indica si se quiere realizar un ciclo de lectura o de escritura. Esta señal tiene prioridad sobrers
. En caso de realizarse una lectura la señalrs
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
ycmd
- 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 pinE
del LCD -
rw
: Conexión al pinrw
del LCD -
rs
: Conexión al pionrs
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
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
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:
Este ejemplo hace exactamente lo mismo que el 24, pero se usa el bloque LCD-io-8-bits
(25_controler-8bit-busy-04.ice)
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)
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
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
- Primero se emite un ciclo de escritura. Este ciclo finaliza cuando llega un tic por
done
- Ahora se emite un ciclo de lectura
- El busy flag se pone a 1 (El LCD está ocupado con el comando CLS)
- El tic aparece por la etiqueta
LOOP
, para que se genere un nuevo ciclo de lectura. Ahora endone
no hay tic - 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:
- Hay un tic en
LOOP
por lo que se ejecuta un nuevo ciclo de lectura (el último) - Último ciclo de lectura
- Se lee el flag de busy, y ahora vale 0
- Aparece un tic en
done
(en vez de en loop), ya que no hay que realizar nuevos ciclos de lectura - 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
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
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:
- Ciclo de escritura: Se escribe el carácter 'A' en el LCD
- Ciclo de lectura: Se lee el flag de busy del LCD
- El valor del flag de busy es 1 (Ocupado), por lo que se repite la lectura hasta que sea 0
- El flag ya es 0: El controlador termina y se activa el tic de
done
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
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
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
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:
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
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!!!!)
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)
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
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:
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)
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 es el que configura el pin DB7 como Entrada o salida
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 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)
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
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 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)
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)
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)
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 A
s
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
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
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ñalesE
,rs
yrw
(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
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)
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 cmd
es 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)
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
yB
. 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
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
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)
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
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
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
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
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
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
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
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
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:
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
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
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
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
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)
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
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
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
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 A
s)
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
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
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
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
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
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:
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
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:
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"
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:
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
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
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 😎️
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
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
Mismo ejemplo, pero para la LCD-Shield:
(68_example-06-switch-on-off-LCD-Shield.ice)
Esto es lo que aparece en el LCD:
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
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:
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!
- Juan González-Gómez (Obijuan)
- 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! 😄
- Unidad de PWM de frecuencia aproximada
- VGA Retro: Puesta en marcha. MonsterLED
- Pines de Entrada/Salida
- Control de LEDs
- SPI esclavo
- SPI Maestro
- Display SPI de 4 dígitos de 7 segmentos
- Entrada y Salida de Bits con Componentes Virtuales
- Memorias
- Entradas y pulsadores
- Señales del sistema. Medición con el LEDOscopio
- Controlador LCD 16x2
- Señales periódicas y temporización
- Buses: Medio compartido
- Memoria Flash SPI
- Conexión de LEDs en la Alhambra II. Placa AP‐LED8‐THT
- Periféricos PMOD
- Fundamentos. Sistema Unario
- Autómatas
- Pantallas de vídeo. Fundamentos. Display de 1x4 LEDs
- Pantallas de vídeo. Fundamentos. Matriz de 4x4 LEDs