Pantallas de video. Fundamentos. Matriz de 4x4 LEDs - Obijuan/Cuadernos-tecnicos-FPGAs-libres GitHub Wiki
Estudio de los fundamentos de los sistemas de vídeo, utilizando como ejemplo un display mínimo de 4x4 LEDs. Se diseñan progresivamente los controladores, partiendo de un LED y construyendo las matrices de 2x2, 3x3 y finalmente la matriz de 4x4 LEDs. En este último display se muestras sprites, animaciones, caracteres y mundos virtuales mayores de 4x4
- Icestudio: Todos los ejemplos se han probado con Icestudio-0.12. Usa esta versión o superior
- Ejemplos: Todos los ejemplos de este cuaderno técnico están accesibles en su repositorio en github
- Introducción
- Encendiendo un LED
- Fila de 2 LEDs
-
Matriz de 2x2 LEDs
- Ejemplo 5: Encendido del LED de la columna 1 y fila 0 (1,0)
- Ejemplo 6: Ráster horizontal lento en fila 0
- Ejemplo 7: Ráster horizontal lento. Ráster Y manual
- Ejemplo 8: Recorrido lento de la matriz 2x2
- Ejemplo 9: Lamp-test
- Señal de vídeo y sincronización
- Componente Refresh 2x2
- Ejemplo 10: Probando el componente Refresh
- Memoria de vídeo
- Generación directa de vídeo
- Generación del cursor
-
Matriz de 3x3 LEDs
- Ejemplo 21: Encendido del LED(0,0)
- Ejemplo 22: Recorrido lento de la matriz 3x3
- Ejemplo 23: Lamp-Test: Encendido de todos los LEDs
- Señal de vídeo
- Ejemplo 24: Dibujo de un sprite 3x3 desde memoria ROM
- Ejemplo 25: Ejemplos de sprites 3x3 para guardar en la memoria ROM
- Ejemplo 26: Secuencia manual de 2 sprites
- Ejemplo 27: Animación de dos sprites: trampa
- Ejemplo 28: Animación de cuatro sprites: Pixel rotando
- Ejemplo 29: Más animaciones
- Sprites y fotogramas
- Movimiento de un sprite 2x2
- Generación directa de vídeo
- Cursor en pantalla 3x3
-
Matriz de 4x4 LEDs
- Conexionado
- Prototipo
- Encendiendo LEDs
- Componente Refresh 4x4
- Señal de vídeo
- Dibujando sprites de 4x4
- Generador de caracteres
-
Movimiento de sprites por la pantalla
- Posiciones de un sprite 2x2 en la pantalla 4x4
- Señales de control del renderizado del sprite
- Ruta de datos para el renderizado del sprite
- Cálculo de la señal on-sprite
- Cálculo de la señal active
- Ejemplo 49: Movimiento del sprite con los pulsadores
- Ejemplo 50: Movimiento de un sprite animado con los pulsadores
- Pantalla de Caracteres
- Pantalla gráfica virtual
- Generación directa de vídeo
- Ejemplo 62: Aplicación HWPaint!
- Conclusiones
- Autor
- Licencia
- Enlaces
En el Cuaderno Técnico CT20: Pantallas de vídeo. Fundamentos. Display de 1x4 LEDs aprendimos los conceptos básicos sobre los sistemas de vídeo, aplicados a un display muy sencillo de 1x4 LEDs: Una única fila de 4 LEDs
Sin embargo, las pantallas presentan la información en dos dimensiones y por ello tienen sus píxeles organizados en una matriz de MxN (Columnas x Filas). Cada fila y cada columna de píxeles se identifican mediante un número. Y cada pixel queda unívocamente identificado por dos números: sus coordenadas, que indican en qué fila y en qué columna se encuentra
Así, el pixel de la esquina superior izquierda es el (0,0)
, el de la esquina superior derecha el (M-1, 0)
, el de la esquina inferior izquierda (0, N-1)
y el de la esquina inferior derecha (M-1, N-1)
En este cuaderno técnico aprenderemos a controlar una matriz de 4x4 LEDs, que tiene 16 LEDs en total. Así la representamos cuando todos los LEDs están apagados:
Encendiendo los LEDs que decidamos creamos la imagen a mostrar. En este ejemplo se muestra la imagen de la letra A
, en resolución de 4x4 píxeles
Aunque en la pantalla real vemos una A
, la realidad es que sólo hay un pixel activo cada vez, que se mueve una velocidad muy alta, como vimos en el cuaderno técnico 20. Si se recorre la pantalla a una velocidad mayor o igual a 50Hz, entonces se verá la 'A' perfectamente. Esto es debido a la ilusión óptica
El pixel activo está movido por el ráster, y ahora recorre la matriz de LEDs línea a línea, de izquierda a derecha y de arriba a abajo. En esta animación se muestra el ráster recorriendo todos los LEDs, a baja velocidad:
Si la frecuencia de refresco llega a los 50Hz, entonces veremos todos los píxeles de la pantalla encendidos
Para entender bien el funcionamiento del controlador de esta pantalla empezaremos por lo más básico: Encendiendo un LED. Luego progresivamente iremos haciendo los controladores para las pantallas de 2x2, 3x3 y finalmente 4x4 LEDs
El circuito típico para encender un LED ya lo conocemos. En el Cuaderno Técnico 16: Conexión de LEDs en la Alhambra II. Placa AP‐LED8‐THT vimos todo los detalles
El esquema para su conexionado es este:
La pata positiva del LED se conecta a un pin de la FPGA, como por ejemplo el Pin D0 de la Alhambra II. La otra pata se conecta directamente a GND. Sólo necesitamos 1 pin para encender/apagar el LED
Para conectar los LEDs formando una matriz, necesitamos controlar el LED utilizando 2 pines. El esquema de conexión es el mostrado en esta figura:
La pata positiva la conectamos a un pin que llamamos Selección de fila y la pata negativa al pin de Selección de columna
Para encender el LED hay que poner un 1
en la selección de fila, y un 0
en la selección de columna. El resto de valores NO hacen que se encienda el LED. Los mostramos en esta tabla de verdad
Selección de columna (D1) | Selección de fila (D0) + | LED |
---|---|---|
0 | 0 | Apagado |
0 | 1 | Encendido |
1 | 0 | Apagado |
1 | 1 | Apagado |
En este ejemplo encedemos el LED usando dos pines. El pin de selección de fila lo ponemos a 1
. En estas condiciones, si ponemos el pin de selección de columna a 0
, se enciende el LED, y si lo ponemos a 1
se apaga. Por ello, el LED se comporta con Lógica negativa
Este es el escenario. Conectamos el LED directamente a los pines hembra D0
y D1
de la Alhambra II. El pin positivo del LED (la pata más larga) está conectada al pin D0
En este ejemplo conectamos el pin de selección de columna (D1) al pulsador SW1
(Ej-01-LED-2pines-boton-logic-negativa.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
Para encender los LEDs es más cómodo utilizar la lógica positiva. La selección de la fila ya está con lógica positiva: al aplicar 1
se activa el LED, y en función de la otra pata se enciende o se apaga. Queremos que la columna también se seleccione con un 1
, por lo que añadimos una puerta NOT
Así es como queda la tabla de verdad:
Selección de columna (D1) | Selección de fila (D0) + | LED |
---|---|---|
0 | 0 | Apagado |
0 | 1 | Apagado |
1 | 0 | Apagado |
1 | 1 | Encendido |
Sólo si la fila está a 1
y la columna también está a 1
se enciende el LED
Este es el nuevo circuito:
(Ej-02-LED-2pines-boton-logic-positiva.ice)
Ahora al apretar el pulsador SW1
se enciende el LED, y al soltarlo se apaga. Efectivamente funciona con lógica positiva
En este vídeo de Youtube lo vemos en funcionamiento:
Vamos a construir un display 2x1, formado por 2 LEDs en la misma fila. Que estén en la misma fila significa que sus patas positivas (de selección de fila) están unidas, por lo que activamos ambos LEDs a la vez (decimos que activamos la fila). Luego, en función de cómo estén los pines de las columnas, los LEDs estarán encendidos o apagados
Este es el esquema:
Hemos añadido las puertas NOT para que la activación de las columnas sea en Lógica positiva
Necesitamos 3 pines en la FPGA para controlar este display. El pin de fila0 sirve para activar la fila de LEDs. Como sólo hay una fila, la denotamos como la fila 0. Si fila0=0
todos los LEDs de la fila están deshabilitados y no se pueden encender con independencia de cómo estén los pines de las columnas (col0 y col1). Si fila0=1
la fila está activa y el valor de sus LEDs ahora sí que está determinado por los pines de activación de las columnas: col0
y col1
Esta es la tabla de verdad:
Fila0 | Col0 | Col1 | LED0 | LED1 | Descripción |
---|---|---|---|---|---|
0 | ❌ | ❌ | OFF | OFF | Fila desactivada |
1 | 0 | 0 | OFF | OFF | Fila activada |
1 | 0 | 1 | OFF | ON | Fila activada |
1 | 1 | 0 | ON | OFF | Fila activada |
1 | 1 | 1 | ON | ON | Fila activada |
Para probar los ejemplos hemos construido una mini-placa con 2 LEDs, que tiene los 3 pines para conectarlo a la Alhambra-II
Comprobamos que la placa funciona encendiendo manualmente los LEDs, con los pulsadores SW1
y SW2
. La fila se deja activada enviando un 1
constante por fila0
(Ej-03-Fila-2x1-LEDs-manual.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
En este ejemplo usamos un contador unario one-hot de 2 bits para mostrarlo en los LEDs. El efecto que se consigue es que los LEDs se encienden alternativamente, a la frecuencia de 2 Hz. Con el pulsador SW1
se selecciona/deselecciona la fila 0. Cuando está seleccionada se muestra la animación en los LEDs. Cuando se desactiva ambos LEDs quedan apagados
(Ej-04-Fila-2x1-fila-on-off.ice)
También aprovechamos para ir separando las partes. El acceso a los LEDs del display se hace mediante las etiquetas col0, col1 y fila0. Por otro lado tenemos el ráster x, que recorre ambos LEDs a baja frecuencia. Y como circuito separado tenemos el que hace que se active/desactive la fila 0, mediante un biestable T
En este vídeo de Youtube lo vemos en funcionamiento:
Vamos a construir una matriz de 2x2 LEDs para luego diseñar un controlador que nos permita mostrar información y animaciones. Este es el esquema de conexionado:
Hay dos cables horizontales y dos verticales, que se cruzan pero sin estar conectados. Entre ellos se conectan los 4 LEDs. Las patas positivas se conectan a los cables de fila, y la negativas a los de columna. En las columnas añadimos las puertas NOT para trabajar con lógica positiva
Para encender, por ejemplo, el LED de la parte superior derecha, hay que seleccionar la fila 0 y la columna 1, poniendo un 1
en ambos cables. El LED es el que está en la posición (1,0)
Este es un prototipo de la matriz de 2x2 LEDs que se conecta directamente a la placa Alhambra-II
En total usamos 4 pines para la conexión. Dos se corresponden con las filas (fila 0 y fila 1) y otros dos para las columnas (columna 0 y columna 1). El número de pines es igual al que necesitamos si conectásemos 4 LEDs de la manera tradicional (como en el cuaderno técnico 20). Pero para matrices mayores a 2x2 ya empieza el ahorro de pines cuando se usan las matrices
Este es el mapa del display 2x2, que se deja como referencia
Comenzamos a probar el display, encendiendo el LED (1,0), que se encuentre en la columna 1 y la fila 0 (Esquina superior derecha). En este ejemplo lo encendemos directamente poniendo su valor con constantes. Este es el circuito en icestudio:
Este es el resultado al probarlo
Para recorrer toda la matriz de LEDs ahora hay 2 Rásters, uno para las columnas y otro para las filas. En este ejemplo se mueve el ráster X a baja frecuencia (2Hz), por lo que se están activando alternativamente las columnas 0 y 1. La fila 0 está todo el rato seleccionada, mediante un ráster Y constante
Hemos aprovechado para agrupar las columnas y filas en un array dos arrays de 2 pines. El Ráster X está formado por un registro de desplazamiento a la derecha de 2 bits. Su salida la conectamos directamente a los pines de las columnas. El Ráster Y está formado por una constante de dos bits, con un valor fijo (en unario) para dejar seleccionada la fila 0 todo el rato
Este es el circuito en Icestudio
(Ej-06-M-2x2-linea0-rasterx-lento.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
Ahora hacemos que el ráster Y sea también variable, pero activado con el pulsador SW1
. Cada vez que se aprieta el pulsador se selecciona la fila siguiente (Se conmuta entre las fila 0 y 1). El Ráster X se mueve autónomamente, a la frecuencia de 2 Hz
(Ej-07-M-2x2-rasx-lento-rasy-manual.ice )
En este vídeo de Youtube lo vemos en funcionamiento:
El raster recorre ahora la matriz completa, a velocidad lenta (2Hz). Empieza barriendo de izquierda a derecha la fila 0, luego pasa a la fila 1 y vuelve a comenzar. El ráster Y se incrementa cuando el ráster X vuelve al comienzo, lo que ocurre cuando se activa la señal endx
y llega el siguiente tick del corazón de 2 Hz
En esta animación se muestra el funcionamiento
Este es el circuito en Icestudio
(Ej-08-M-2x2-recorrido-matriz.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
En este ejemplo construimos el primer controlador, que tiene un raster matricial que recorre la pantalla línea a línea a la máxima velocidad. Se utiliza una señal de vídeo constante para encender todos los LEDs y comprobar que el refresco funciona. La señal de vídeo se demultiplexa y se envía a los LEDs
Este es el circuito en icestudio
Las señales de sincronización se miden con el analizador lógico. las veremos en el próximo apartado
Este es el resultado: se ven todos los LEDs encendidos, aunque con menor intensidad
La señal que se muestra en los LEDs de la pantalla 2x2 provienen de una señal de vídeo, igual que la del display 1x4 del cuaderno técnico 20. El pixel clock utilizado es de 12Mhz. Esta señal tiene 4 ciclos por cada frame, ya que en cada frame hay 4 píxeles. Los dos primeros ciclos se corresponden con los LEDs de las columnas 0 y 1 de la fila 0, y los dos siguientes los de la fila 1
Hay dos señales de sincronización:
- HSync: Sincronización horizontal. Indica cuándo se termina de recorrer una fila, y en el próximo ciclo se comienza la siguiente
- VSync: Sincronización vertical: Indica el final del frame actual. En el próximo ciclo comienza un frame nuevo
En esta figura se muestran una señal de vídeo genérica y las señales de sincronización para el display 2x2
Este es el resultado de la medición del ejemplo 9 anterior: el Lamp-test. La señal de vídeo está siempre a 1
El componente Refresh 2x2 recibe la señal de vídeo por la entrada y refresca la matriz de 2x2 LEDs. Este componente, además, saca las señales de sincronización, VSync, HSync, así como el ráster x y el ráster y que serán necesarios para los circuitos de generación del vídeo
Esta es la implementación:
Probamos el componente Refresh 2x2 haciendo un lamp-test, igual que en el ejemplo 9. Ahora queda todo mucho más simplificado, y listo para añadir circuitos más complejos
El resultado es el mismo que el del ejemplo 9
La información a mostrar en las pantallas se almacena en una memoria de vídeo. De ahí se saca serializada para generar la señal de vídeo que llega al controlador de refresco
La memoria de vídeo se puede implementar de diferentes maneras. Lo veremos en los siguientes ejemplos
Como estamos trabajando con un display pequeño, de sólo 2 filas de 2 píxles cada una, implementamos la memoria de vídeo usando registros de 2 bits. Utilizamos un registro por fila. Inicialmente estos registros están a 0, por lo que la pantalla está apagada al arrancar. Al apretar el pulsador SW1
se cargan los registros-fila con los valores correspondientes y se muestra la información en la pantalla (refrescándose continuamente)
(Ej-11-M-2x2-memoria-regs.ice)
Para generar la señal de vídeo se construye un serializador a partir de 3 multiplexores 2-1 unarios. Los dos primeros seleccionan de cada registro los bits de la columna actual, que está dada por el raster x. Luego, se usa otro multiplexor para seleccionar la fila, a partir del ráster y
Al cargar el circuito y pulsar el botón SW1
esto es lo que se muestra en la pantalla:
La señal de vídeo generada tiene 4 bits por frame, porque hay 4 píxeles en total. Como es un Frame fijo, siempre se muestra la misma imagen por lo que todos los frames son iguales. Tienen el valor: 1001
En este vídeo de Youtube lo vemos en funcionamiento:
Una forma muy común de mostrar información en los displays es utilizando una memoria ROM donde están almacenados los bits que indican el estado de cada pixel. Se pueden utilizar ROMs con diferentes organizaciones. En este ejemplo se usa una ROM 4x1, que tiene 4 posiciones cada una de 1 bit. La ventaja de usar memorias de 1 bit es que a su salida obtenemos directamente la señal de vídeo, sin necesidad de añadir un circuito serializador
La memoria ROM que utilizamos es unaria, organizada lógicamente en 2 filas y 2 columnas. Por su entrada se reciben los dos rásteres, x e y, en unario (one-hot), y se convierten a una dirección de 2 bits que se usa para direccionar una memoria estándard 4x1
Este es el circuito de ejemplo que muestra este patrón en los LEDs:
⚫🔴
🔴⚫
Este es el resultado al cargar el circuito en la Alhambra-II:
Esta es la señal de vídeo generada:
En este ejemplo se muestra este patrón fijo en el display 2x2:
⚫🔴
⚫🔴
Se utiliza una memoria ROM 2x2. Tiene dos direcciones 0 y 1, correspondientes a las filas 0 y 1 respectivamente. Cada fila contiene los 2 bits de las columnas 0 y 1. A la salida de la memoria se añade un multiplexor 2-1 (unario) para realizar la rasterización de los dos bits de la fila
La memoria ROM 2x2 es unaria. Utilizamos el ráster y para direccionar la fila correspondiente. Esta memoria se implementa a partir de una ROM 2x2 estándard, colocando un codificador para generar la dirección binaria
Este es el circuito de ejemplo
(Ej-13-M-2x2-memoria-ROM-2x2.ice)
Utilizar una memoria ROM 2x2 es más cómodo en Icestudio porque se usa una dirección por cada fila. Así es más fácil dibujar los sprites
En esta foto se muestra el resultado
Esta es la señal de vídeo generada:
En este ejemplo se guardan dos sprites de 2x2 píxeles en una memoria ROM de 4x2. Estos son los dos sprites
Sprite 0 Sprite 1
🔴⚫ ⚫🔴
⚫🔴 🔴⚫
Se utiliza una memoria ROM unaria de 4x2 bits, dividida en dos bloques de 2x2. Los bloques son el 0 y el 1, y en cada uno de ellos se almacena un sprite. La memoria tiene una entrada para indicar a qué bloque acceder, y otra entrada para especificar la fila dentro del sprite en ese bloque
Tanto la fila como el bloque están en unario. Se utilizan dos codificadores para generar la dirección de acceso a una memoria ROM 4x2 convencional
Los dos sprites se almacenan en la memoria, uno a continuación del otro (en binario)
Aquí están en modo texto, para poder hacer copy y paste:
10 //-- Sprite 0
01
01 //-- Sprite 1
10
Este es el circuito en Icestudio. Mediante el pulsador SW1
se seleccionar qué sprite es el que se quiere mostrar en el display. La señal sprite[1:0]
indica el bloque de memoria al que acceder (en unario one-hot). Este valor también se muestra en los LEDs, para depurar
(Ej-14-M-2x2-dos-sprites-manual.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
En este ejemplo se implementa una animación de un LED giratorio (un spinner hardware). Esta animación está formada por los siguientes 4 sprites:
Sprite 0 Sprite 1 Sprite 2 Sprite 3
🔴⚫ ⚫🔴 ⚫⚫ ⚫⚫
⚫⚫ ⚫⚫ ⚫🔴 🔴⚫
que se muestran en el display 2x2 secuencialmente, a velocidad de 8Hz, generando la animación de un LED que rota por la mini-pantalla. Los 4 sprites que componente esta animación se almacenan en una memoria ROM de 8x2. Cada sprite está en su propio bloque de 2x2 bits, por lo que decimos que esta ROM es de 4x2x2 (4 bloques de 2x2)
De momento seguimos utilizando memorias UNARIAS. Esta es implementación de la memoria ROM unaria 4x2x2:
Y esta es la memoria rellena con los 4 sprites:
Este contenido de la memoria está disponible en texto plano para poder hacer copy y paste:
10 //-- Sprite 0
00
01 //-- Sprite 1
00
00 //-- Sprite 2
01
00 //-- Sprite 3
10
La implementación del circuito en Icestudio es prácticamente igual que en el ejemplo 14, pero ahora añadimos un corazón de 8 Hz para generar la animación, y usamos la nueva memoria:
(Ej-15-M-2x2-4-sprites-animacion.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
La señal de vídeo se puede generar directamente, sin usar memoria, a partir de la posición del ráster. En los siguientes ejemplos se genera una línea horizontal, una línea vertical y una diagonal a partir de los rásteres x e y, usando puertas lógicas
Se dibuja una línea horizontal en la fila 0 (fila superior). Para ello se usa directamente uno de los bits del ráster y, que indica cuándo se está en la fila 0. Si esa señal se usa directamente como señal de vídeo se encienden los dos LEDs horizontales, de la fila superior
Este es el circuito en Icestudio:
(Ej-16-M-2x2-Video-direct-lineah.ice)
Esta es la señal de vídeo que se genera, junto con los valores de los rásteres:
El resultado es que se encienden los dos LEDs de la fila 0
Se dibuja una línea vertical en la columna 0 (columna izquierda). Se utiliza directamente el bit del ráster x que indica cuándo se está en la columna 0. El resultado es que se encienden los dos LEDs de la columna 0
Este es el circuito en Icestudio:
(Ej-17-M-2x2-Video-direct-lineah.ice)
Esta es la señal de vídeo que se genera, junto con los valores de los rásteres:
El resultado es que se encienden los dos LEDs de la columna 0
Se dibuja una línea diagonal desde el pixel (0,0) (esquina superior izquierda) hacia el pixel (1,1) (Esquina inferior derecha). Cada uno de los dos píxeles se detecta que pertenecen a la línea diagonal comprobando si la fila y la columna son iguales. Así, el pixel (0,0) se activa si el ráster x apunta a la columna 0. Y el ráster y a la fila 0. Y de forma similar con el pixel (1,1): que el ráster x apunte a la columna 1 y el raster y a la fila 1. Para ello utilizamos puertas AND, y finalmente combinamos el resultado con una puerta OR. Este es el circuito en Icestudio
(Ej-18-M-2x2-Video-direct-diagonal.ice)
Esta es la señal de vídeo que se genera, junto con los valores de los rásteres:
Este es el resultado en la pantalla
El cursor indica la posición actual de la pantalla donde el usuario introducirá el siguiente dato. Típicamente se muestra su posición encendiendo el pixel correspondiente o haciéndolo parpadear
El cursor se genera directamente en la señal de vídeo. No está almacenado en la memoria de vídeo, es ajeno a ella
Para tener un cursor necesitamos almacenar sus posiciones x e y en registros. Estas posiciones se comparan con las del ráster. Si la posición (x,y) del ráster es igual a la posición (xc,yc) del cursor, el pixel en cuestión contiene el cursor (y por tanto se pone a uno o se hace parpadear)
Este circuito muestra un cursor fijo en la posición (0,0) inicialmente. Con la tecla SW1
se desplaza hacia la derecha, volviendo a la izquierda cuando se llega al final. Con el botón SW2
se mueve verticalmente hacia abajo, volviendo de nuevo a la fila 0 cuando se llega al final
Este es el circuito en Icestudio:
(Ej-19-M-2x2-Video-direct-cursor.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
Este ejemplo es igual que el anterior (19) pero el cursor parpadea. Para lograr este parpadeo basta con utilizar un corazón de baja frecuencia (2 Hz en el ejemplo) y aplicar una AND con la señal de vídeo del cursor
Este es el circuito en Icestudio:
(Ej-20-M-2x2-Video-direct-cursor-parpadeante.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
El conexionado de la matriz de 3x3 LEDs es como el mostrado en esta figura
Este es un prototipo de la matriz de 3x3 LEDs (rojos) que se conecta directamente a la placa Alhambra-II
Este es el mapa del display 3x3, que se deja como referencia
En este primer ejemplo se enciende un LED de la matriz 3x3. El LED que se enciende es el (0,0), pero se puede cambiar simplemente poniendo el número de fila y columna (en unario one-hot) en las constantes
Este es el circuito en Icestudio:
Y este es el resultado:
El raster recorre la matriz completa, a velocidad lenta (2Hz). Empieza barriendo de izquierda a derecha la fila 0, luego pasa a la fila 1, luego a la fila 2 y vuelve a comenzar. El ráster Y se incrementa cuando el ráster X vuelve al comienzo, lo que ocurre cuando se activa la señal endx
y llega el siguiente tick del corazón de 2 Hz
En esta animación se muestra el funcionamiento
Este es el circuito en Icestudio
(Ej-22-M-3x3-raster-lento.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
La siguiente prueba es usar un ráster a su máxima velocidad para recorrer la matriz encendiendo todos los LEDs. Es decir, que se le envía la señal de vídeo constante 1
. La implementación del ráster, junto con sus señales de sincronización, lo hacemos en el componente Refresh 3x3:
Este es el circuito en Icestudio:
Y este es el resultado:
La señal de vídeo para la matriz 3x3 lleva la información del estado de cada uno de los 9 píxeles, en serie. La señal hsync indica el final de una fila, y se repite cada 3 píxeles. La señal vsync indica el final del frame, y que en el siguiente ciclo comienza uno nuevo. Se repite cada 9 píxeles
En el caso del ejemplo del Lamp Test, la señal de vídeo está a 1
para todos los píxeles. La velocidad del reloj (pixel clock) es de 12Mhz
En el caso de otros ejemplos la señal de vídeo alterna entre 0
y 1
, según el valor de cada pixel de la pantalla
Vamos a dibujar un sprite de 3x3, del mismo tamaño que la pantalla. El sprite es esta cruz:
⚫🔴⚫
🔴🔴🔴
⚫🔴⚫
Utilizamos una memoria ROM de 3x3 (unaria). En cada dirección está almacenada una fila del sprite, que se lee con el ráster y. Las direcciones son 0, 1 y 2, correspondientes a cada una de las filas de mismo número. Aunque las direcciones están en unario one-hot: 100, 010 y 001
En cada dirección se almacena una fila (3 píxeles), que luego se serializa con un multplexor 3-1, para generar la señal de vídeo que se muestra en la pantalla mediante refresco
Este es el circuito en Icestudio
El sprite que se muestra es una cruz. Está almacenada línea a línea en una ROM de 3x3. Un bit a 1
significa que el pixel (LED) correspondiente está encendido y 0
que está apagado
Este es el resultado al sintetizar y cargar el circuito en la Alhambra-II
Si colocamos un papel encima de la pantalla se verá mejor el sprite (aunque en las fotos se ve con menor intensidad, en la realidad se aprecia mucho mejor)
Esta es la señal de vídeo que se genera en el ejemplo, que transporta los bits del sprite para ser visualizados en el display
El sprite está almacenado en la memoria ROM 3x3:
Este es el contenido de la memoria en formato texto para poder hacer un copy & paste
010 //-- Cruz
111
010
En este circuito de Icestudio lo que se guardan son los contenidos de las memorias ROM 3x3 con diferentes sprites. Para utilizarlas hay que copiarlas y pegarlas en el ejemplo 24
(Ej-25-M-3x3-almacen-sprites.ice)
En esta imagen se muestran algunos de los sprites en la pantalla 3x3
Para mostrar 2 sprites diferentes en la pantalla, uno cada vez, utilizamos una memoria ROM 2x3x3, que contiene 2 bloques de 3x3 bits: El bloque 0 y el bloque 1. Por ello la memoria tiene 2 entradas: una para especificar el bloque y otro para indicar la fila dentro del bloque
Esta es la implementación de la memoria ROM 2x3x3:
En este ejemplo se selecciona entre estos dos sprites con el pulsador SW1
, correspondientes a los dígitos 0 y 1:
Sprite 0 Sprite 1
🔴🔴🔴 ⚫🔴⚫
🔴⚫🔴 ⚫🔴⚫
🔴🔴🔴 ⚫🔴⚫
Este es el circuito en Icestudio:
(Ej-26-M-3x3-animacion-2-sprites-manual.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
Este es el contenido de la memoria ROM:
111 // Sprite 0
101
111
0 //-- sin usar
010 // Sprite 1
010
010
Para crear una animación automática basta con sustituir el pulsador por un corazón, para alternar periódicamente entre un sprite y otro. En este ejemplo se anima una trampa de un videojuego, constituida por estos dos sprites
Sprite 0 Sprite 1
🔴🔴🔴 ⚫⚫⚫
⚫🔴⚫ ⚫⚫⚫
⚫🔴⚫ 🔴🔴🔴
Este es el circuito en Icestudio:
(Ej-27-M-3x3-animacion-2-sprites-trampa.ice)
Este es el contenido de la memoria ROM:
111 // Sprite 0
010
010
0 //-- sin usar
000 // Sprite 1
000
111
En este vídeo de Youtube lo vemos en funcionamiento:
Para generar animaciones de 4 sprites necesitamos una memoria ROM de 4x3x3. En este ejemplo se muestra la rotación de una barra de 3 LEDs. Estos son los sprites
Sprite 0 Sprite 1 Sprite 2 Sprite 3
⚫⚫⚫ 🔴⚫⚫ ⚫🔴⚫ ⚫⚫🔴
🔴🔴🔴 ⚫🔴⚫ ⚫🔴⚫ ⚫🔴⚫
⚫⚫⚫ ⚫⚫🔴 ⚫🔴⚫ 🔴⚫⚫
Este es el circuito en Icestudio
(Ej-28-M-3x3-animacion-4-spiner-led.ice)
Este es el contenido de la memoria ROM, en formato texto:
000 // Sprite 0
111
000
0 //--------
100 // Sprite 1
010
001
0 //----------
010 // Sprite 2
010
010
0 //----------
001 // Sprite 3
010
100
0
En este vídeo de Youtube lo vemos en funcionamiento:
En el ejemplo 29 se han recopilado 3 animaciones más, formadas por 4 sprites. Basta con hacer copy & paste del contenido de la memoria y llevarlo al ejemplo 28
(Ej-29-M-3x3-almacen-animaciones.ice)
- Animación 1: Pixel rotando en sentido horario
Sprite 0 Sprite 1 Sprite 2 Sprite 3
⚫⚫⚫ ⚫🔴⚫ ⚫⚫⚫ ⚫⚫⚫
🔴⚫⚫ ⚫⚫⚫ ⚫⚫🔴 ⚫⚫⚫
⚫⚫⚫ ⚫⚫⚫ ⚫⚫⚫ ⚫🔴⚫
- Animación 2: Pixel rebotando arriba y abajo
Sprite 0 Sprite 1 Sprite 2 Sprite 3
⚫⚫⚫ ⚫⚫⚫ ⚫🔴⚫ ⚫⚫⚫
⚫⚫⚫ ⚫🔴⚫ ⚫⚫⚫ ⚫🔴⚫
⚫🔴⚫ ⚫⚫⚫ ⚫⚫⚫ ⚫⚫⚫
- Animación 3: Trampa
Sprite 0 Sprite 1 Sprite 2 Sprite 3
🔴🔴🔴 ⚫⚫⚫ ⚫⚫⚫ ⚫⚫⚫
⚫🔴⚫ 🔴🔴🔴 ⚫⚫⚫ 🔴🔴🔴
⚫🔴⚫ ⚫🔴⚫ 🔴🔴🔴 ⚫🔴⚫
Las animaciones las dividimos en sus dos componentes: Sprites y fotogramas. Los sprites son los elementos visuales y los fotogramas indican qué se visualiza en cada instante
Así por ejemplo, si queremos animar una Trampa de un videojuego primero cremos los sprites. En nuestro caso necesitamos 3, que representan la trampa en la situación inicial de reposo, bajando y abajo del todo. Los llamamos sprites 0, 1 y 2:
Sprite 0 Sprite 1 Sprite 2
🔴🔴🔴 ⚫⚫⚫ ⚫⚫⚫
⚫🔴⚫ 🔴🔴🔴 ⚫⚫⚫
⚫🔴⚫ ⚫🔴⚫ 🔴🔴🔴
Estos sprites los almacenamos en una ROM
Para hacer la animación completa usamos 8 fotogramas: 4 con la trampa arriba (Sprite 0), uno en medio (Sprite 1), dos abajo (Sprite 2) y otro en medio:
0 1 2 3 4 5 6 7
🔴🔴🔴 🔴🔴🔴 🔴🔴🔴 🔴🔴🔴 ⚫⚫⚫ ⚫⚫⚫ ⚫⚫⚫ ⚫⚫⚫
⚫🔴⚫ ⚫🔴⚫ ⚫🔴⚫ ⚫🔴⚫ 🔴🔴🔴 ⚫⚫⚫ ⚫⚫⚫ 🔴🔴🔴
⚫🔴⚫ ⚫🔴⚫ ⚫🔴⚫ ⚫🔴⚫ ⚫🔴⚫ 🔴🔴🔴 🔴🔴🔴 ⚫🔴⚫
Sprite 0 Sprite 0 Sprite 0 Sprite 0 Sprite 1 Sprite 2 Sprite 2 Sprite 1
En otra ROM almacenamos la animación, definida como una secuencia de fotogramas. Cada dirección es el número de fotograma y su contenido es el número de sprite. Así, la ROM que define esta animación es como la siguiente:
Direccion | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
Nº sprite | 0 | 0 | 0 | 0 | 1 | 2 | 2 | 1 |
De esta forma podemos crear animaciones más largas y complejas, consumiendo menos espacio
Este es el circuito que implementa la animación de la trampa, con las dos memorias ROM, una para los números de los fotogramas y otra con los bits de los sprites
(Ej-30-M-3x3-animacion-8-fotogramas-trampa.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
La pantalla 3x3 es todavía pequeña. Sin embargo tiene el suficiente tamaño como para pintar en su interior sprites 2x2, de dos dimensiones. Podemos pintar cualquier sprite 2x2 (de los 16 posibles). Este es el que elegimos para las pruebas:
🔴⚫
⚫🔴
De los 4 pixeles del sprite, utilizamos el que está en la parte superior izquierda para definir la posición del sprite en la pantalla
En la pantalla 3x3 tenemos 9 posiciones diferentes. Este es el aspecto de la matriz cuando el sprite de prueba está situado en todas las posiciones:
En las posiciones (0,0), (1,0), (0,1) y (1,1) el sprite está totalmente visible. Hay 2 posiciones donde sólo se ve la primera columna del sprite, 2 posiciones donde sólo se ve su fila 0 y una donde sólo se ve el pixel de su parte superior izquierda
El sprite 2x2 está almacenado en una ROM 2x2. Para leer de ella el pixel que toque dibujar utilizamos dos registros, Xoff y Yoff. Con Yoff se direcciona la fila, y con Xoff el pixel de esa fila. Este es el esquema:
Cuando este sprite lo visualizamos en una pantalla 2x2, utilizamos directamente los dos componentes del ráster: rasX y rasY para direccionar la memoria. Sin embargo, para visualizarlo en una pantalla 3x3 o de mayor resolución, hay que calcular Xoff e Yoff en función de la posición actual del ráster
Supongamos que tenemos una pantalla 3x3, y que en ella pintamos un sprite 2x2 en la posición dada por las coordenadas del sprite, que denotaremos por (Sx, SY). El ráster recorre la pantalla. Las coordenadas del ráster son (rasX, rasY). En estas circunstancias podemos dividir la pantalla en 2 zonas: una en la que el ráster está sobre el sprite y otra donde el ráster está fuera del sprite. Esta segunda zona la llamamos Fondo
En esta figura se representa la posición de ráster cuando está en dos zonas. En la izquierda el ráster está fuera del sprite, en la posición (1,0). En la derecha se encuentra dentro del sprite, en la posición (2,2)
La señal on_sprite se pone a 1
cuando el ráster está sobre el sprite, y a 0
cuando está fuera del sprite. Para pintar el sprite en la pantalla utilizamos esta señal on_sprite. Es la encargada de seleccionar el pixel que viene del sprite o el que utilizamos para el fondo. Este es el circuito de renderizado
Lo siguiente es calcular Xoff e Yoff en función de la posición del ráster. Inicialmente están a cero. Las reglas para el cálculo de Xoff son:
- Xoff inicialmente está a 0
- Si el ráster está dentro del sprite (on_sprite=1) Xoff se debe incrementar
- Cuando el ráster llega al extremo derecho (fin de fila) Xoff se debe inicializar a 0. La señal que indica cuándo ocurre esto es hsync
Este es el circuito completo para calcular Xoff
El cálculo de yoff es un poco más complicado. Es necesario asignar un estado al sprite, que indica si está activo o no. El estado activo indica que este sprite está en proceso de renderizado. Es decir, que se está pintando. Se pinta línea a linea. Cuando el estado está activo significa que todavía quedan líneas por pintar
En esta figura se muestra el diagrama de estados. Inicialmente el sprite está desactivado. Cuando el ráster alcanza la primera línea del sprite se pasa al estado activo. La condición para que ocurra esto es: rasY = Sy, donde Sy es la coordenada Y del sprite.
Cuando el ráster alcanza la última línea del sprite (la segunda) y se activa hsync significa que se ha alcanzado la primera línea fuera del sprite. Por tanto el sprite ya se ha renderizado por completo y se desactiva. El contador Yoff se puede inicializar de diferentes formas. La que vamos a usar es mediante la señal vsync que indica que el frame actual se ha completado, por tanto se aprovecha para inicializar Yoff
Este es el circuito completo para calcular Yoff
Este es el circuito para calcular la señal on_sprite
En este ejemplo se mueve un sprite 2x2 por toda la pantalla usando los pulsadores SW1
y SW2
. Con el primero se incrementa la posición en X volviéndose a 0 al llegar al extremo derecho: 0,1,2,0... Con el segundo se hace lo mismo pero en la coordenada Y
Este es el sprite dibujado, inicialmente en la posición (0,0)
🔴⚫
🔴🔴
Los cálculos de la señales on_sprite y active se han insertado en el bloque Sprite-control. Esta es su implementación:
Este es el circuito completo en Icestudio:
Inicialmente el sprite está en la posición (0,0)
Si apretamos SW1
avanza un pixel hacia la derecha, hacia la posición (1,0)
Si apretamos SW2
baja un pixel, y se coloca en la posición (1,1)
En este vídeo de Youtube lo vemos en funcionamiento:
Este ejemplo es igual que el anterior, pero ahora el personaje está animado. Está formado por estos dos sprites que se alternan a baja frecuencia:
🔴⚫ ⚫🔴
🔴🔴 🔴🔴
Con los pulsadores SW1
y SW2
lo desplazamos por la pantalla
Este es el circuito en Icestudio:
(Ej-32-M-3x3-sprite-2x2-animacion.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
En la pantalla 3x3 podemos dibujar directamente sin necesidad de usar una memoria ROM. Este método se basa en especificar condiciones geométricas que debe cumplir el raster. Según estas condiciones se pueden generar fácilmente líneas horizontales, verticales, diagonales, cuadrados, rectángulos...
Veremos algunos ejemplos
Para dibujar una línea horizontal utilizamos la condición rasY=Y
, donde Y
es la cordenada vertical donde situar la línea horizontal
Este es el circuito en Icestudio:
El resultado es que se dibuja una línea horizontal en y = 1 (que en unario one-hot es 010)
Para dibujar una línea vertical utilizamos la condición rasX=X
, donde X
es la cordenada horizontal donde situar la línea vertical
Este es el circuito en Icestudio:
El resultado es que se dibuja una línea vertical en x = 1 (que en unario one-hot es 010)
Para dibujar una línea diagonal utilizamos la condición rasX=rasY
Este es el circuito en Icestudio:
El resultado es que se dibuja una línea diagonal que pasa por los puntos (0,0), (1,1) y (2,2)
El cursor está constituido por dos registros que almacenan sus coordenadas x e y. Cuando la posición actual del ráster (rasX, rasY) coincide con la del cursor (Cx,Cy) se activa el LED correspondiente. El cursor se genera directamente por hardware
En este ejemplo se utilizan 4 pulsadores externos para mover el cursor por la pantalla. Las coordenadas del cursor se almacenan en dos registros de desplazamiento a izquierda-derecha, en unario (one-hot), porque el raster de la pantalla 3x3 está en unario
Este es el Circuito en Icestudio
En este vídeo de Youtube lo vemos en funcionamiento:
Ya por fin vamos a trabajar con la matriz de 4x4. Sigue siendo de muy baja resolución, pero con 4x4 ya se pueden representar letras y números. Esto nos permite introducir nuevos conceptos
La matriz está formada por 4 filas y 4 columnas, numeradas de 0 a 3. Este es el conexionado
Se utilizan puertas NOT para introducir por las columnas bits en lógica positiva
El prototipo construido tiene 16 LEDs rojos, y 8 pines para conectarlo directamente a la placa Alhambra II
En esta foto está la matriz 4x4 conectada a la Alhambra II, en los pines D0 - D7
Una vez construido el prototipo, el siguiente paso es comprobar si funciona. Encenderemos LEDs aislados y recorreremos toda la matriz
Empezamos encendiendo un único LED. Esto ya lo sabemos hacer. Basta con especificar el bit correspondiente de la fila y de la columna. La forma natural de hacerlo es en el sistema unario (con codificación one-hot)
En este ejemplo encendemos el pixel de la posición (2,1): Columna 2 y fila 1. Lo especificamos directamente con un valor en unario de 4 bit (one-hot):
- Columna 2:
0010
- Fila 1:
0100
Este es el LED que se quiere encender:
Columna
0 0 1 0 (columna 2)
F 0 ⚫⚫⚫⚫
i 1 ⚫⚫🔴⚫
l 0 ⚫⚫⚫⚫
a 0 ⚫⚫⚫⚫
(Fila 1)
Este es el circuito en Icestudio:
(Ej-37-M-4x4-un-LED-unario.ice)
Este es el resultado
Para comprobar que todos los LEDs de nuestra matriz funcionan, y que están bien conectados, la recorremos encendiendo todos ellos, uno cada vez. Como estamos trabajando en unario, que es lo natural con matrices, para recorrer las columnas usamos un registro de desplazamiento en anillo. Cuando se llega al LED del extremo derecho, se incrementa la fila, que es a su vez otro registro de desplazamiento
Utilizando un Corazón que genere pulsos a una velocidad lenta (4 Hz en el ejemplo), logramos este efecto de recorrer la matriz
Este es el circuito en Icestudio
(Ej-38-M-4x4-Recorrido-lento-unario.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
A bajo nivel, las matrices de LEDs se controlan mediante números codificados en unario (one-hot), de forma que sólo hay activa una fila y una columna en un instante dado. Sin embargo, los números unarios escalan muy mal. Para manejar una pantalla de 32x32 LEDs, por ejemplo, necesitamos en total 64 bits, frente a los 10 que se necesitarían si usamos una codificación en binario. Y si la pantalla es por ejemplo de 640x400, necesitamos unos mil bits en unario, frente a los 19 que se usarían en sistema binario
El ahorro es grande si nos pasamos al sistema binario. Y eso es lo que vamos a hacer. Con una matriz de 4x4 el ahorro es bajo: 8 bits frente a 4, pero nos permitirá escalar hacia pantalla más grandes
Asi que, a partir de ahora, las coordenadas (columana, fila) las expresaremos en binario natural. En el caso de la matrix 4x4, estas dos coordenadas se expresan con números de 2 bits. Utilizando decodificadores 2 a 4 pasamos del sistema binario al unario
Ahora, para recorrer la matriz ya no usamos registros de desplazamiento, sino contadores
En este ejemplo encendemos el mismo LED que el de ejemplo 37, el que está en la posición (2,1), pero esta vez tanto la fila como la columna están especificadas en binario:
- Columna 2
- Fila 1
Este es el LED que se quiere encender:
Columna
0 1 2 3 (columna 2)
F 0 ⚫⚫⚫⚫
i 1 ⚫⚫🔴⚫
l 2 ⚫⚫⚫⚫
a 3 ⚫⚫⚫⚫
(Fila 1)
Este es el circuito en Icestudio:
(Ej-39-M-4x4-un-LED-binario.ice)
Este es el resultado
En este ejemplo se recorren todos los LEDs de la matriz 4x4 especificando las filas y las columnas mediante números binarios, a la velocidad de 4Hz. Como estamos trabajando en binario se utilizan contadores para recorrer todas las filas y columnas
Este es el circuito en Icestudio
(Ej-40-M-4x4-Recorrido-lento-binario.ice)
El resultado es exactamente el mismo que el del ejemplo 38
Para encender todos los LEDs y ver cómo la pantalla está totalmente encendida movemos el ráster a la máxima velocidad. Para ello utilizamos un contador del sistema que recorre las columnas y cuando llega al extremo derecho activa la señal HSync. Esta señal es la que se usa para incrementar el contador de filas
Este es el circuito en Icestudio:
Este es el resultado:
El componente Refresh 4x4 recibe la señal de vídeo por la entrada y refresca la matriz de 4x4 LEDs. Este componente, además, saca las señales de sincronización, VSync, HSync, así como el ráster x y el ráster y que serán necesarios para los circuitos de generación del vídeo
La diferencia con los componentes refresh anteriores es que ahora los ráster x e y son en binario, en vez de unario
Esta es la implementación:
Para probar el componente refresh 4x4 encendemos todos los LEDs. La señal de vídeo la ponemos a 1
constante
(Ej-42-M-4x4-refresh-lamp-test.ice)
Este es el resultado:
La señal de vídeo para la matriz 4x4 lleva la información del estado de cada uno de los 16 píxeles, en serie. La señal hsync indica el final de una fila, y se repite cada 4 píxeles. La señal vsync indica el final del frame, y que en el siguiente ciclo comienza uno nuevo. Se repite cada 16 píxeles
En esta figura se muestra un frame de una señal de vídeo genérica. Las señales de sincronización son iguales en cada frame. Lo que cambia de un frame a otro es la propia señal de vídeo, que llevará unos bits u otros. También se han incluido los valores de los rásteres X e Y
En el caso del ejemplo del Lamp Test, la señal de vídeo está a 1
para todos los píxeles. La velocidad del reloj (pixel clock) es de 12Mhz
Como el tamaño de la pantalla es de 4x4, el sprite más grande que podemos dibujar es de 4x4 píxeles. Utilizamos una memoria ROM de 4x4 para almacer este sprite. Para realizar animaciones necesitamos almacenar más de un sprite para lo que añadimos más bits en la entrada de direcciones de la memoria. Por cada bit nuevo que añadimos duplicamos la cantidad de sprites. Para representar un sprite las direcciones son de 2 bits. Al pasar a 3 bits podemos almacenar 2 sprites. Con 4 bits 4 sprites. Con 5 bits 8 sprites...
Por ello, la dirección de la memoria de vídeo se descompone en 2 campos. Supongamos una dirección de N bits: AN-1, AN-2,...,A2, A1, A0
- Número de sprite: (N-2) bits de mayor peso. Indica el número de sprite (An-1...A2)
- Fila del sprite: Los 2 bits de menos peso (A1 A0)
Comenzamos con el ejemplo más sencillo: Dibujar un único sprite de 4x4. Como sólo es 1 sprite, necesitamos una memoria ROM de 4x4
Este es el sprite que se muestra en el ejemplo: una bola
⚫🔴🔴⚫
🔴🔴🔴🔴
🔴🔴🔴🔴
⚫🔴🔴⚫
Este es el circuito en Icestudio:
La memoria ROM contiene las filas del sprite. El raster Y selecciona la fila en cada momento. Cada fila se serializa mediante un multiplexor 4-1, generando la señal de vídeo
Este es el resultado
En este ejemplo se muestra una animación de 2 sprites, que están almacenados en una ROM 8x4 (ROM 2x4x4). El bit de mayor peso se utiliza para seleccionar el sprite (0 ó 1). Así, el sprite 0 empieza en la dirección 0 (000), y el sprite 1 en la 4 (100)
Direccion | Sprite | Fila |
---|---|---|
000 | 0 | 0 |
001 | 0 | 1 |
010 | 0 | 2 |
011 | 0 | 3 |
100 | 1 | 0 |
101 | 1 | 1 |
110 | 1 | 2 |
111 | 1 | 3 |
Estos son los dos sprites de la animación:
Sprite 0 Sprite 1
🔴⚫🔴⚫ ⚫🔴⚫🔴
⚫🔴⚫🔴 🔴⚫🔴⚫
🔴⚫🔴⚫ ⚫🔴⚫🔴
⚫🔴⚫🔴 🔴⚫🔴⚫
Este es el circuito en Icestudio
(Ej-44-M-4x4-sprite-x2-manual.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
Este ejemplo es una animación de 9 sprites, que están almacenados en una ROM 16x4x4 (ROM 2x4x4). Los 4 bits de mayor peso de la dirección se utilizan para seleccionar el sprite (0-8). Estos son los 9 sprites de la animación:
⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫ 🔴⚫⚫⚫ ⚫🔴⚫⚫ ⚫⚫🔴⚫ ⚫⚫⚫🔴 ⚫⚫⚫⚫ ⚫⚫⚫⚫
⚫⚫⚫⚫ ⚫⚫⚫⚫ 🔴⚫⚫⚫ ⚫🔴⚫⚫ 🔴⚫🔴⚫ ⚫🔴⚫🔴 ⚫⚫🔴⚫ ⚫⚫⚫🔴 ⚫⚫⚫⚫
⚫⚫⚫⚫ 🔴⚫⚫⚫ ⚫🔴⚫⚫ ⚫⚫🔴⚫ ⚫⚫⚫🔴 🔴⚫⚫⚫ ⚫🔴⚫⚫ ⚫⚫🔴⚫ ⚫⚫⚫🔴
🔴🔴🔴🔴 ⚫🔴🔴🔴 ⚫⚫🔴🔴 ⚫⚫⚫🔴 ⚫⚫⚫⚫ ⚫⚫⚫⚫ 🔴⚫⚫⚫ 🔴🔴⚫⚫ 🔴🔴🔴⚫
Este es el circuito en Icestudio:
Aquí se muestra el contenido de la memoria ROM, con los 9 sprites:
0000 //--S0
0000
0000
1111
//--
0000 //-- S1
0000
1000
0111
//--
0000 //-- S2
1000
0100
0011
//--
1000 //-- S3
0100
0010
0001
//
0100 //-- S4
1010
0001
0000
//
0010 //-- S5
0101
1000
0000
//
0001 //-- S6
0010
0100
1000
//
0000 //-- S7
0001
0010
1100
//
0000 //-- S8
0000
0001
1110
En este vídeo de Youtube lo vemos en funcionamiento:
En este ejemplo se realiza una animación de una trampa de videojuego que sube y baja. Se utilizan 4 sprites para definir las posiciones de la trampa, y la animación consta de 10 fotogramas
Estos son los 4 sprites utilizados para la animación de la trampa, numerados del 0 al 3
Sprite 0 Sprite 1 Sprite 2 Sprite 3
🔴🔴🔴🔴 ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫
⚫🔴🔴⚫ 🔴🔴🔴🔴 ⚫⚫⚫⚫ ⚫⚫⚫⚫
⚫🔴🔴⚫ ⚫🔴🔴⚫ 🔴🔴🔴🔴 ⚫⚫⚫⚫
⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ 🔴🔴🔴🔴
La animación está formada por 10 fotogramas. Los 4 primeros se corresponden con el sprite 0, hay 2 con el sprite 1, dos con el sprite 2 y otros dos con el sprite 3
0 1 2 3 4 5 6 7 8 9
🔴🔴🔴🔴 🔴🔴🔴🔴 🔴🔴🔴🔴 🔴🔴🔴🔴 ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫
⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ 🔴🔴🔴🔴 ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫ 🔴🔴🔴🔴
⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ 🔴🔴🔴🔴 ⚫⚫⚫⚫ ⚫⚫⚫⚫ 🔴🔴🔴🔴 ⚫🔴🔴⚫
⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ 🔴🔴🔴🔴 🔴🔴🔴🔴 ⚫🔴🔴⚫ ⚫🔴🔴⚫
Sprite 0 Sprite 0 Sprite 0 Sprite 0 Sprite 1 Sprite 2 Sprite 3 Sprite 3 Sprite 2 Sprite 1
Ahora utilizamos dos memorias ROM. Una ROM de 4x4x4 (16x4) para almacenar los 4 sprites, y otra de 16x2 para indicar qué sprite se corresponde con cada uno de los 10 fotogramas
Este es el circuito en Icestudio:
(Ej-46-M-4x4-fotograma-10.ice)
- Contenido de la ROM de sprites:
1111 //--Sprite 0
0110
0110
0110
//--
0000
1111 //-- Sprite 1
0110
0110
//--
0000 //-- Sprite 2
0000
1111
0110
//--
0000 //-- Sprite 3
0000
0000
1111
- Contenido de la ROM de fotogramas:
//--Sprite
0 //-- Fotograma 0
0 //-- Fotograma 1
0 //-- Fotograma 2
0 //-- Fotograma 3
1 //-- Fotograma 4
2 //-- Fotograma 5
3 //-- Fotograma 6
3 //-- Fotograma 7
2 //-- Fotograma 8
1 //-- Fotograma 9
En este vídeo de Youtube lo vemos en funcionamiento:
Un uso muy importante de las pantallas es mostrar información textual. Se imitan las páginas de los libros, pero dibujando los caracteres electrónicamente, en vez de impresos en papel
La información textual está compuesta por caracteres, que representan las letras mayúsculas, mínúsculas, números, símbolos de puntuación, símbolos matemáticos etc.
Los caracteres no son más que sprites como los que ya conocemos: matrices de bits que indican el estado de los LEDs. El circuito que dibuja los caracteres lo denominamos el generador de caracteres. Tiene una memoria ROM donde se almacenan todos los caracteres, que denominamos CGROM. Cada carácter queda unívocamente representado por un número: el código del carácter
Una pantalla de 4x4 LEDs tiene una resolución muy baja, sin embargo es suficiente para representar caracteres de una manera "primitiva". Algunas letras no se distinguen bien, pero la gran mayoría sí
En esta página encontramos un juego de caracteres de 4x4: Fontstruct.com. Tiene letras mayúsculas, minúsculas, números y símbolos de puntuación. Esta es la pinta que tienen las letras y números:
Nuestro juego de caracteres tiene 85 símbolos: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,'?!*%()+_-/:<>=[]\^º
, con los siguientes códigos:
Carácter | A | B | C | D | E | F | G | H | I | J |
---|---|---|---|---|---|---|---|---|---|---|
Código | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Carácter | K | L | M | N | O | P | Q | R | S | T |
---|---|---|---|---|---|---|---|---|---|---|
Código | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
Hex | 0A | 0B | 0C | 0D | 0E | 0F | 10 | 11 | 12 | 13 |
Carácter | U | V | W | X | Y | Z | a | b | c | d |
---|---|---|---|---|---|---|---|---|---|---|
Código | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Hex | 14 | 15 | 16 | 17 | 18 | 19 | 1A | 1B | 1C | 1D |
Carácter | e | f | g | h | i | j | k | l | m | n |
---|---|---|---|---|---|---|---|---|---|---|
Código | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
Hex | 1E | 1F | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
Carácter | o | p | q | r | s | t | u | v | w | x |
---|---|---|---|---|---|---|---|---|---|---|
Código | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
Hex | 28 | 29 | 2A | 2B | 2C | 2D | 2E | 2F | 30 | 31 |
Carácter | y | z | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|---|---|
Código | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
Hex | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 3A | 3B |
Carácter | 8 | 9 | . | , | ' | ? | ! | * | % | ( |
---|---|---|---|---|---|---|---|---|---|---|
Código | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
Hex | 3C | 3D | 3E | 3F | 40 | 41 | 42 | 43 | 44 | 45 |
Carácter | ) | + | _ | - | / | : | < | > | = | [ |
---|---|---|---|---|---|---|---|---|---|---|
Código | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
Hex | 46 | 47 | 48 | 49 | 4A | 4B | 4C | 4D | 4E | 4F |
Carácter | ] | \ | ^ | º | espacio |
---|---|---|---|---|---|
Código | 80 | 81 | 82 | 83 | 84 |
Hex | 50 | 51 | 52 | 53 | 54 |
En total tenemos 26 letras mayúsculas en nuestro juego de caracteres. Esta es la pinta que tienen
A B C D E
⚫🔴🔴⚫ 🔴🔴🔴⚫ ⚫🔴🔴⚫ 🔴🔴🔴⚫ 🔴🔴🔴🔴
🔴⚫⚫🔴 🔴🔴🔴🔴 🔴⚫⚫🔴 🔴⚫⚫🔴 🔴🔴🔴⚫
🔴🔴🔴🔴 🔴⚫⚫🔴 🔴⚫⚫⚫ 🔴⚫⚫🔴 🔴⚫⚫⚫
🔴⚫⚫🔴 🔴🔴🔴⚫ ⚫🔴🔴🔴 🔴🔴🔴⚫ 🔴🔴🔴🔴
F G H I J
🔴🔴🔴🔴 ⚫🔴🔴🔴 🔴⚫⚫🔴 🔴🔴🔴🔴 ⚫⚫⚫🔴
🔴⚫⚫⚫ 🔴⚫🔴🔴 🔴🔴🔴🔴 ⚫🔴🔴⚫ ⚫⚫⚫🔴
🔴🔴🔴⚫ 🔴⚫⚫🔴 🔴⚫⚫🔴 ⚫🔴🔴⚫ 🔴⚫⚫🔴
🔴⚫⚫⚫ ⚫🔴🔴🔴 🔴⚫⚫🔴 🔴🔴🔴🔴 ⚫🔴🔴⚫
K L M N O
🔴⚫⚫🔴 🔴⚫⚫⚫ 🔴🔴🔴🔴 🔴⚫⚫🔴 ⚫🔴🔴⚫
🔴🔴🔴⚫ 🔴⚫⚫⚫ 🔴🔴🔴🔴 🔴🔴⚫🔴 🔴⚫⚫🔴
🔴🔴🔴⚫ 🔴⚫⚫⚫ 🔴⚫⚫🔴 🔴⚫🔴🔴 🔴⚫⚫🔴
🔴⚫⚫🔴 🔴🔴🔴🔴 🔴⚫⚫🔴 🔴⚫⚫🔴 ⚫🔴🔴⚫
P Q R S T
🔴🔴🔴⚫ ⚫🔴🔴⚫ 🔴🔴🔴⚫ ⚫🔴🔴🔴 🔴🔴🔴🔴
🔴⚫⚫🔴 🔴⚫⚫🔴 🔴⚫⚫🔴 🔴🔴🔴⚫ ⚫🔴🔴⚫
🔴🔴🔴⚫ 🔴⚫🔴⚫ 🔴🔴🔴⚫ ⚫⚫⚫🔴 ⚫🔴🔴⚫
🔴⚫⚫⚫ ⚫🔴⚫🔴 🔴⚫⚫🔴 🔴🔴🔴⚫ ⚫🔴🔴⚫
U V W X Y Z
🔴⚫⚫🔴 🔴⚫⚫🔴 🔴⚫⚫🔴 🔴⚫⚫🔴 🔴⚫🔴⚫ 🔴🔴🔴🔴
🔴⚫⚫🔴 🔴⚫⚫🔴 🔴⚫⚫🔴 ⚫🔴🔴⚫ 🔴🔴🔴⚫ ⚫⚫🔴⚫
🔴⚫⚫🔴 ⚫🔴🔴⚫ 🔴🔴🔴🔴 ⚫🔴🔴⚫ ⚫🔴⚫⚫ ⚫🔴⚫⚫
⚫🔴🔴⚫ ⚫🔴🔴⚫ 🔴⚫⚫🔴 🔴⚫⚫🔴 ⚫🔴⚫⚫ 🔴🔴🔴🔴
Este es el contenido en formato texto para almacenar en una memoria ROM (CGROM)
//-- Caracteres: Letras mayusculas 4x4
0110 //-- A
1001
1111
1001
//
1110 //-- B
1111
1001
1110
//
0110 //-- C
1001
1000
0111
//
1110 //-- D
1001
1001
1110
//
1111 //-- E
1110
1000
1111
//
1111 //-- F
1000
1110
1000
//
0111 //-- G
1011
1001
0111
//
1001 //-- H
1111
1001
1001
//
1111 //-- I
0110
0110
1111
//
0001 //-- J
0001
1001
0110
//
1001 //-- K
1110
1110
1001
//
1000 //-- L
1000
1000
1111
//
1111 //-- M
1111
1001
1001
//
1001 //-- N
1101
1011
1001
//
0110 //-- O
1001
1001
0110
//
1110 //-- P
1001
1110
1000
//
0110 //-- Q
1001
1010
0101
//
1110 //-- R
1001
1110
1001
//
0111 //-- S
1110
0001
1110
//
1111 //-- T
0110
0110
0110
//
1001 //-- U
1001
1001
0110
//
1001 //-- V
1001
0110
0110
//
1001 //-- W
1001
1111
1001
//
1001 //-- X
0110
0110
1001
//
1010 //-- Y
1110
0100
0100
//
1111 //-- Z
0010
0100
1111
Las mismas 26 letras mayúsculas tienen su correspondiente letra minúscula
a b c d e
⚫⚫⚫⚫ 🔴⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫🔴 ⚫⚫⚫⚫
⚫🔴🔴🔴 🔴🔴🔴⚫ ⚫🔴🔴🔴 ⚫🔴🔴🔴 ⚫🔴🔴⚫
🔴⚫⚫🔴 🔴⚫⚫🔴 🔴⚫⚫⚫ 🔴⚫⚫🔴 🔴🔴🔴🔴
⚫🔴🔴🔴 🔴🔴🔴⚫ ⚫🔴🔴🔴 ⚫🔴🔴🔴 ⚫🔴🔴🔴
f g h i j
⚫🔴🔴⚫ ⚫🔴🔴⚫ 🔴⚫⚫⚫ ⚫🔴⚫⚫ ⚫🔴⚫⚫
⚫🔴⚫⚫ 🔴🔴🔴⚫ 🔴🔴⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫
🔴🔴🔴⚫ ⚫⚫🔴⚫ 🔴⚫🔴⚫ ⚫🔴⚫⚫ ⚫🔴⚫⚫
⚫🔴⚫⚫ 🔴🔴⚫⚫ 🔴⚫🔴⚫ ⚫🔴⚫⚫ 🔴🔴⚫⚫
k l m n o
🔴⚫⚫⚫ 🔴⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫
🔴⚫🔴⚫ 🔴⚫⚫⚫ 🔴🔴🔴⚫ 🔴🔴🔴⚫ ⚫🔴🔴⚫
🔴🔴⚫⚫ 🔴⚫⚫⚫ 🔴🔴🔴🔴 🔴⚫⚫🔴 🔴⚫⚫🔴
🔴⚫🔴⚫ ⚫🔴⚫⚫ 🔴⚫⚫🔴 🔴⚫⚫🔴 ⚫🔴🔴⚫
p q r s t
🔴🔴🔴⚫ ⚫🔴🔴🔴 ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫🔴⚫⚫
🔴⚫⚫🔴 🔴⚫⚫🔴 🔴⚫🔴⚫ ⚫🔴🔴⚫ 🔴🔴🔴⚫
🔴🔴🔴⚫ ⚫🔴🔴🔴 🔴🔴⚫🔴 ⚫🔴⚫⚫ ⚫🔴⚫⚫
🔴⚫⚫⚫ ⚫⚫⚫🔴 🔴⚫⚫⚫ 🔴🔴⚫⚫ ⚫🔴🔴⚫
u v w x y z
⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫ 🔴⚫⚫🔴 ⚫⚫⚫⚫
🔴⚫⚫🔴 🔴⚫⚫🔴 🔴⚫⚫🔴 🔴⚫⚫🔴 🔴⚫⚫🔴 🔴🔴🔴⚫
🔴⚫⚫🔴 🔴⚫⚫🔴 🔴🔴🔴🔴 ⚫🔴🔴⚫ ⚫🔴🔴🔴 ⚫🔴⚫⚫
⚫🔴🔴🔴 ⚫🔴🔴⚫ 🔴⚫⚫🔴 🔴⚫⚫🔴 ⚫⚫⚫🔴 🔴🔴🔴⚫
Este es el contenido en formato texto para almacenar en una memoria ROM (CGROM)
//-- LETRAS MINUSCULAS
0000 //-- a
0111
1001
0111
//
1000 //-- b
1110
1001
1110
//
0000 //-- c
0111
1000
0111
//
0001 //-- d
0111
1001
0111
//
0000 //-- e
0110
1111
0111
//
0110 //-- f
0100
1110
0100
//
0110 //-- g
1110
0010
1100
//
1000 //-- h
1100
1010
1010
//
0100 //-- i
0000
0100
0100
//
0100 //-- j
0000
0100
1100
1000 //-- k
1010
1100
1010
//
1000 //-- l
1000
1000
0100
//
0000 //-- m
1110
1111
1001
//
0000 //-- n
1110
1001
1001
//
0000 //-- o
0110
1001
0110
//
1110 //-- p
1001
1110
1000
//
0111 //-- q
1001
0111
0001
//
0000 //-- r
1010
1101
1000
//
0000 //-- s
0110
0100
1100
//
0100 //-- t
1110
0100
0110
//
0000 //-- u
1001
1001
0111
//
0000 //-- v
1001
1001
0110
//
0000 //-- w
1001
1111
1001
//
0000 //-- x
1001
0110
1001
//
1001 //-- y
1001
0111
0001
//
0000 //-- z
1110
0100
1110
//
Esta es la pinta que tienen los 10 dígitos del sistema decimal
0 1 2 3 4
⚫🔴🔴⚫ ⚫🔴⚫⚫ 🔴🔴⚫⚫ 🔴🔴⚫⚫ 🔴⚫⚫🔴
🔴⚫🔴🔴 ⚫🔴⚫⚫ ⚫⚫🔴⚫ ⚫🔴🔴⚫ 🔴⚫⚫🔴
🔴🔴⚫🔴 ⚫🔴⚫⚫ ⚫🔴⚫⚫ ⚫⚫🔴⚫ 🔴🔴🔴🔴
⚫🔴🔴⚫ ⚫🔴⚫⚫ 🔴🔴🔴⚫ 🔴🔴⚫⚫ ⚫⚫⚫🔴
5 6 7 8 9
🔴🔴🔴🔴 🔴⚫⚫⚫ 🔴🔴🔴🔴 ⚫🔴🔴⚫ ⚫🔴🔴⚫
🔴🔴🔴⚫ 🔴🔴🔴⚫ 🔴⚫⚫🔴 🔴🔴🔴🔴 🔴⚫⚫🔴
⚫⚫⚫🔴 🔴⚫⚫🔴 ⚫⚫⚫🔴 🔴⚫⚫🔴 ⚫🔴🔴🔴
🔴🔴🔴⚫ ⚫🔴🔴⚫ ⚫⚫⚫🔴 ⚫🔴🔴⚫ ⚫⚫⚫🔴
Este es el contenido en formato texto para almacenar en una memoria ROM (CGROM)
0110 //-- 0
1011
1101
0110
//
0100 //-- 1
0100
0100
0100
//
1100 //-- 2
0010
0100
1110
//
1100 //-- 3
0110
0010
1100
//
1001 //-- 4
1001
1111
0001
//
1111 //-- 5
1110
0001
1110
//
1000 //-- 6
1110
1001
0110
//
1111 //-- 7
1001
0001
0001
//
0110 //-- 8
1111
1001
0110
//
0110 //-- 9
1001
0111
0001
En total hay 23 caracteres usados como símbolos de puntuación, operaciones matemáticas, etc... Entre estos símbolos está contenido el espacio, que en este juego de caracteres se ha puesto el último
. , ' ? !
⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫🔴⚫⚫ ⚫🔴🔴⚫ ⚫🔴⚫⚫
⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫🔴⚫⚫ ⚫⚫🔴⚫ ⚫🔴⚫⚫
⚫⚫⚫⚫ ⚫🔴⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫
⚫🔴⚫⚫ ⚫🔴⚫⚫ ⚫⚫⚫⚫ ⚫🔴⚫⚫ ⚫🔴⚫⚫
* % ( ) +
⚫🔴⚫⚫ 🔴⚫⚫🔴 ⚫🔴⚫⚫ ⚫⚫🔴⚫ ⚫⚫⚫⚫
🔴🔴🔴⚫ ⚫⚫🔴⚫ 🔴⚫⚫⚫ ⚫⚫⚫🔴 ⚫🔴⚫⚫
⚫🔴⚫⚫ ⚫🔴⚫⚫ 🔴⚫⚫⚫ ⚫⚫⚫🔴 🔴🔴🔴⚫
🔴⚫🔴⚫ 🔴⚫⚫🔴 ⚫🔴⚫⚫ ⚫⚫🔴⚫ ⚫🔴⚫⚫
_ - / : <
⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫🔴 ⚫⚫⚫⚫ ⚫⚫⚫⚫
⚫⚫⚫⚫ 🔴🔴🔴🔴 ⚫⚫🔴⚫ ⚫🔴⚫⚫ ⚫🔴🔴⚫
⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫🔴⚫⚫ ⚫⚫⚫⚫ 🔴⚫⚫⚫
🔴🔴🔴🔴 ⚫⚫⚫⚫ 🔴⚫⚫⚫ ⚫🔴⚫⚫ ⚫🔴🔴⚫
> = [ ] \
⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫🔴🔴⚫ ⚫🔴🔴⚫ 🔴⚫⚫⚫
🔴🔴⚫⚫ 🔴🔴🔴🔴 ⚫🔴⚫⚫ ⚫⚫🔴⚫ ⚫🔴⚫⚫
⚫⚫🔴⚫ ⚫⚫⚫⚫ ⚫🔴⚫⚫ ⚫⚫🔴⚫ ⚫⚫🔴⚫
🔴🔴⚫⚫ 🔴🔴🔴🔴 ⚫🔴🔴⚫ ⚫🔴🔴⚫ ⚫⚫⚫🔴
^ º espacio
⚫🔴⚫⚫ ⚫🔴⚫⚫ ⚫⚫⚫⚫
🔴⚫🔴⚫ 🔴⚫🔴⚫ ⚫⚫⚫⚫
⚫⚫⚫⚫ ⚫🔴⚫⚫ ⚫⚫⚫⚫
⚫⚫⚫⚫ ⚫⚫⚫⚫ ⚫⚫⚫⚫
- Este es el contenido en formato texto para almacenar en una memoria ROM
//-- Símbolos especiales
0000 //-- .
0000
0000
0100
//
0000 //-- ,
0000
0100
0100
//
0100 //-- '
0100
0000
0000
//
0110 //-- ?
0010
0000
0100
//
0100 //-- !
0100
0000
0100
//
0100 //-- *
1110
0100
1010
//
1001 //-- %
0010
0100
1001
//
0100 //-- (
1000
1000
0100
//
0010 //-- (
0001
0001
0010
//
0000 //-- +
0100
1110
0100
//
0000 //-- _
0000
0000
1111
//
0000 //-- -
1111
0000
0000
//
0001 //-- /
0010
0100
1000
//
0000 //-- :
0100
0000
0100
//
0000 //-- <
0110
1000
0110
//
0000 //-- >
1100
0010
1100
//
0000 //-- =
1111
0000
1111
//
0110 //-- [
0100
0100
0110
//
0110 //-- ]
0010
0010
0110
//
1000 //-- \
0100
0010
0001
//
0100 //-- ^
1010
0000
0000
//
0100 //-- º
1010
0100
0000
//
0000 //-- Espacio
0000
0000
0000
La CGROM (Character Generator ROM) es la memoria ROM que contiene todo el juego de caracteres, que en nuestro caso es de 85 caracteres. Para identificar cada carácter necesitamos por tanto un código de 7 bits. Esto nos permite numerar los caracteres del 0 a. 127, pero en nuestro caso sólo llegamos al 85
Cada carácter está formado por 4 filas de 4 bits. Para representar cada fila necesitamos 2 bits. En la CGROM almacenamos las 4 filas de todos los caracteres
La dirección de la CGROM se descompone en 2 campos: los 7 bits más significativos contienen el código del carácter y los 2 de menor peso el número de fila:
La CGROM consiste en una memoria ROM de 512x4 (128x4x4), que tiene direcciones de 9 bits. Su contenido está disponible en este fichero: cgrom-4x4-font.list.
En este ejemplo se muestran TODOS los caracteres almacenados en la CGROM. Cada vez que se aprieta el pulsador SW1
se muestra el siguiente carácter, hasta llegar al último (espacio) donde se vuelve a comenzar
Este es el circuito en Icestudio:
En el circuito de Icestudio el contenido de este fichero está copiado en el parámetro de la memoria ROM. Se podría incluir el fichero directamente, pero se ha dejado así para que el proyecto sólo dependa de un único fichero icestudio (y no haya que estar trabajando con el .ice y el .list)
En este vídeo de Youtube lo vemos en funcionamiento:
Para mostrar mensajes de texto en las pantallas lo que hacemos es escribir los caracteres (sus códigos) en una memoria que denominamos memoria de vídeo de texto. En ella se almacenan los códigos de los caracteres que queremos mostrar en la pantalla
La anchura de esta memoria es de 7 bits, que se corresponde con el tamaño del código de caracteres. El tamaño de la memoria depende de la longitud del mensaje a almacenar. La salida de la memoria de vídeo de texto se conecta directamente a la CGROM. Así podemos tener memorias de 3x7 (para mensajes cortos de hasta 8 caracteres), de 4x7 (hasta 16 caracteres), de 5x7 (hasta 32 caracteres), etc...
En este ejemplo se muestra en la pantalla el mensaje HOLA
de forma automática, enseñando un carácter cada segundo. Este mensaje se almacena en la Memoria de Vídeo de texto. Utilizamos una memoria ROM de 3x7. En ella almacenamos los códigos de los caracteres del mensaje. Este es el contenido de la memoria
//-- Mensaje de texto
0x07 // 'H'
0x0E // 'O'
0x0B // 'L'
0x00 // 'A'
0x55 // ' '
Esta memoria de texto se conecta directamente a la CGROM. Se direcciona con un contador de 3 bits módulo 5, porque el mensaje tiene en total 5 caracteres. El contador se incrementa a la frecuencia de 1Hz, por lo que se muestra un carácter por segundo
Este es el circuito en Icestudio
(Ej-48-M-4x4-memoria-texto.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
Típicamente los sprites que se dibujan son más pequeños que la propia pantalla. En los ejemplos de este cuaderno como trabajamos con bajas resoluciones, los sprites son del mismo tamaño que ellas
En este apartado vamos a dibujar sprites 2x2 en la pantalla 4x4. A diferencia de la pantalla 3x3, ahora utilizamos rásteres binarios en vez de unarios. Por esto los circuitos cambian
Para los experimentos vamos a utilizar el mismo sprite de prueba que usamos con la pantalla 3x3
🔴⚫
⚫🔴
Vamos a ir describiendo el circuito necesario para dibujar y mover el sprite, paso a paso
El sprite 2x2 puede estar en un total de 9 posiciones en las que es totalmente visible. Hay 5 posiciones adicionales donde NO sería visible, pero estos casos NO los comtemplamos. Siempre queremos que se vea el sprite completo. Tampoco queremos que se vea partido: un trozo por la derecha y otro por la izquierda
Estas son las 9 posiciones visibles: (0,0), (1,0), (2,0), (0,1), (1,1), (2,1), (0,2), (1,2) y (2,2)
Para realizar el renderizado del sprite se utilizan varias señales, que dependen de la posición del ráster:
- Offset_X: Posición x dentro del sprite. El valor de 0 se corresponde con la columna izquierda
- Offset_Y: Posición y dentro del sprite. El valor de 0 se corresponde con la fila superior
- Hsync: Se activa cuando el raster alcanza la columan del extremo derecho
- Vsync: Se activa cuando el ráster alcanza la última posición de la pantalla (3,3)
- On-sprite: Se activa cuando el ráster están sobre las posiciones del sprite
- Active: Se activa al entrar en el sprite (esquina superior izquierda) y permanece a 1 durante todo su renderizado. Se desactiva cuando se alcanza la esquina inferior derecha del sprite
En esta figura se muestra el estado de todas estas señales según va avanzando el ráster por la pantalla
El sprite se almacena en una memoria ROM 2x2, que tiene 1 bit para indicar la dirección (0, 1) y dos bits de salida con los pixeles de la fila. Se direcciona mediante un contador de 1 bit (que es un biestable T), llamado Offset_Y. La salida se lleva a un multiplexor 2-1 para sacar el pixel correspondiente según el valor del contador de 1 bit, llamado Offset_X
El contador offset_X se pone a 0 cuando el ráster alcanza el extremo derecho mediante la señal Hsync. El contador offset_Y se pone a 0 cuando el ráster termina de recorrer la pantalla, con la señal Vsync
La señal On_sprite se utiliza para sacar los bits del sprite cuando el ráster está sobre el sprite, y 0 en cuando está fuera del sprite
Por último queda el incremento de los contadores offset_x y offset_y. El contador offset_X se incrementa siempre que el ráster está sobre el sprite. El contador offset_Y se incrementa cuando el sprite está activo (Señal active a 1) y se alcanza el extremo derecho de la pantalla (señal HSync a on). Este es el circuito completo de la ruta de datos:
La señal on-sprite se calcula a partir del ráster (rasX, rasY) y de la posición actual del sprite, que está dada por las etiquetas Sx y Sy
La señal active es una señal de estado, que se activa cuando el ráster entra en el sprite, por la esquina superior izquierda, y se mantiene activa hasta que el ráster sale por el extremo inferior derecho del sprite. Su significado es: "El sprite actual está siendo renderizado"
En este ejemplo se mueve el sprite de prueba por todas las 9 posiciones donde es visible. Con el pulsador SW1
se incrementa la posición x. Cuando llega al extremo derecho vuelve a la columna inicial. Con el pulsador SW2
se incrementa la posición y. Cuando llega al extremo inferior vuelve a la fila inicial
Como las direcciones de la memoria del sprite 2x2 son de 1 bit, se utilizan biestables T como los contadores offX y offY
Para la posición actual del sprite se utilizan dos contadores de 2 bits, uno para la coordenada X (Sx) y otro para la coordenada Y (Sy). Ambos contadores son módulo 3 y se incrementan manualmente con los pulsadores SW1
y SW2
Este es el circuito en Icestudio:
En este vídeo de Youtube lo vemos en funcionamiento:
Este ejemplo es igual que el anterior, pero ahora el personaje está animado. Está formado por estos dos sprites que se alternan a baja frecuencia:
🔴⚫ ⚫🔴
🔴🔴 🔴🔴
Con los pulsadores SW1
y SW2
lo desplazamos por la pantalla
Este es el circuito en Icestudio:
(Ej-50-M-4x4-sprite-2x2-anim.ice)
La diferencia es que ahora en la ROM 2x2x2 tenemos almacenados los 2 sprites 2x2. Utilizamos un corazón que selecciona entre uno u otro, cada segundo. El que está seleccionado es el que se renderiza
En este vídeo de Youtube lo vemos en funcionamiento:
Las pantallas antiguas utilizadas para los primeros terminales de vídeo NO permitían acceso directo a los píxeles, sino que en ellas se mostraban caracteres en posiciones fijas de la pantalla. En este tipo de pantallas la resolución se expresa en caracteres. Así por ejemplo podemos tener una pantalla de 40x20 caracteres. Significa que hay 40 columnas y 20 filas. En cada posición se almacena un carácter
Actualmente las pantallas de los ordenadores permiten acceso a los píxeles, pero también acceso caracteres. Esto es lo que se llama modo texto y modo gráfico. Cuando se trabaja en modo texto, se especifican las coordenadas de los caracteres. Cuando se trabaja en modo gráfico se indican las coordenadas de cada pixel individual
Estos caracteres, a su vez, tiene una resolución en píxeles. Por ejemplo 8x8. Pero cuando se trabaja en modo texto no se tiene acceso a estos píxeles, ni tampoco se puede dibujar el carácter en cualquier posición de píxel
Nuestra pantalla tiene una resolución de pixeles (modo gráfico) de 4x4. La vamos a convertir en una pantalla de texto para trabajar en el modo texto. Para ello la dividimos en 2 filas de 2 caracteres cada una. Es decir, que el modo texto tiene una resolución de 2x2 (caracteres)
Los caracteres que vamos a usar tiene sólo una resolución de 2x2 píxeles. Es muy baja, por lo que no podemos representar letras ni números. No pasa nada. Definiremos otros símbolos de 2x2 píxeles
Este es nuestro repertorio de caracteres. En total hay 10 caracteres
┌ ┐ └ ┘
🔴🔴 🔴🔴 🔴⚫ ⚫🔴
🔴⚫ ⚫🔴 🔴🔴 🔴🔴
- _ 🟥 Blank
🔴🔴 ⚫⚫ 🔴🔴 ⚫⚫
⚫⚫ 🔴🔴 🔴🔴 ⚫⚫
\ /
🔴⚫ ⚫🔴
⚫🔴 🔴⚫
Los almacenamos en una CGROM de 16x2x2, que tiene capacidad para 16 caracteres, pero sólo hemos implementando 10. Este es el contenido de la memoria:
11 // S0: ┌
10
//
11 // S1: ┐
01
//
10 // S2: └
11
//
01 // S3: ┘
11
//
11 // S4: -
00
//
00 // S5: _
11
//
11 // S6: 🟥
11
//
00 // S7: Blank
00
//
10 // S8: \
01
//
01 // S9: /
10
Los caracteres a dibujar en la pantalla se encuentran almacenados en la memoria de vídeo del modo texto. En cada posición se encuentra el código del carácter. Las posiciones 0 y 1 se corresponden con la primera fila, y las posiciones 2 y 3 con la segunda fila
La pantalla se refresca fila a fila, utilizando el ráster. Cuando el ráster x está en las posiciones 0 y 1 de la primera fila, lo que hay que renderizar es la primera fila del primer carácter de la memoria de texto. Cuando el ráster x está en las posiciones 2 y 3 de la misma fila, se renderiza la primera fila del segundo carácter
Por tanto, de los 2 bits del ráster, utilizamos el de menor peso para seleccionar el pixel dentro de cada carácter, y el de mayor peso para seleccionar el carácter de la fila actual
Con el ráster y ocurre lo mismo: el bit de menor peso para seleccionar la fila dentro del carácter y el de mayor peso indica la fila de caracteres a usar
En este ejemplo se pintan los 4 caracters almacenados en la memoria de texto. Por defecto se dibujan los caracteres 0,1,2 y 3 que se corresponden con las esquinas de un rectángulo. Por tanto se muestra un rectángulo en la pantalla. Pero se han añadido otros contenidos que imprimen otros caracteres, para mostrar diferentes figuras
Este es el circuito en Icestudio:
(Ej-51-M-4x4-Pantalla-chars.ice)
Este es el resultado:
Aunque nuestra pantalla tiene una resolución muy baja, de 4x4 píxeles, podemos definir una pantalla virtual mayor y mostrar en nuestra pantalla física sólo una parte de la virtual. En realidad la pantalla virtual es una especie de mundo virtual que lo visualizamos a través de la pantalla física, que llamaremos cámara
La cámara está situada en la posición (offX, offY). En esta figura se muestra un mundo virtual de 12x8 píxeles, y la cámara 4x4 está situada en la posición (5,2)
Toda la información del mundo virtual se sitúa en la memoria de VIDEO pero sólo se renderiza en la pantalla física la porción visible. Para una pantalla virtual de 12x8 (columnas, filas) utilizamos una ROM de 8x12 (8 direcciones, palabras de 12 bits). Cada fila de la pantalla virtual se mapean en una dirección de la ROM
El ráster Y de la pantalla física toma los valores 0,1,2 y 3. Pasa saber a qué dirección de memoria se correspode la fila actual sólo hay que sumar la coordenada y de la cámara al ráster Y. Es decir, que la dirección a leer es: rasY + off_y
El ráster X se mueve horizontalmente en las coordenadas locales de la pantalla física: 0, 1, 2 y 3. Para leer los valores de estos píxeles hay que acceder al bit rasterx + offx
de la línea actual. Esto lo hacemos mediante un multiplexor de 12 a 1
En este ejemplo construimos un mundo virtual con resolución de 12x8, que representa unos pasadizos. Los píexeles encendidos representan muros o piedras, y los apagados son espacios libres para moverse: pasillos
Cuando la cámara está en la posición (5,2) esto es lo que se muestra en la pantalla física:
En la memoria ROM hay que aguardar el Laberinto:
🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴
🔴⚫⚫🔴⚫⚫⚫🔴⚫⚫⚫🔴
🔴🔴⚫🔴⚫🔴⚫⚫⚫🔴⚫🔴
🔴🔴⚫🔴⚫🔴🔴🔴🔴🔴⚫🔴
🔴⚫⚫🔴⚫🔴⚫⚫⚫⚫⚫🔴
🔴⚫🔴🔴⚫🔴⚫🔴🔴🔴🔴🔴
🔴⚫⚫⚫⚫🔴⚫⚫⚫⚫⚫🔴
🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴
Pero en formato binario:
111111111111
100100010001
110101000101
110101111101
100101000001
101101011111
100001000001
111111111111
Este es el circuito en Icestudio:
(Ej-52-M-4x4-Pantalla-virtual.ice)
Utilizando los pulsadores nos vemos por el mundo virtual: izquierda, derecha, arriba y abajo. Este es el escenario. Inicialmente la cámara está en la posición (0,0)
En esta figura se muestra el escenario cuando la cámara está en la posición (5,2)
En este vídeo de Youtube lo vemos en funcionamiento:
Una de las aplicaciones de la pantalla virtual es desplazar la cámara hacia la derecha para generar un efecto de desplazamiento del texto por la pantalla. En este ejemplo se muestra el texto "HOLA!!" desplazándose por la pantalla física 4x4
El mundo virtual es de 32x4 (32 columnas y 4 líneas). En él se activan los píxeles correspondientes al texto a mostrar:
⚫⚫⚫⚫🔴⚫⚫🔴⚫⚫🔴🔴⚫⚫🔴⚫⚫⚫⚫⚫🔴🔴⚫⚫🔴⚫🔴⚫⚫⚫⚫⚫
⚫⚫⚫⚫🔴🔴🔴🔴⚫🔴⚫⚫🔴⚫🔴⚫⚫⚫⚫🔴⚫⚫🔴⚫🔴⚫🔴⚫⚫⚫⚫⚫
⚫⚫⚫⚫🔴⚫⚫🔴⚫🔴⚫⚫🔴⚫🔴⚫⚫⚫⚫🔴🔴🔴🔴⚫⚫⚫⚫⚫⚫⚫⚫⚫
⚫⚫⚫⚫🔴⚫⚫🔴⚫⚫🔴🔴⚫⚫🔴🔴🔴🔴⚫🔴⚫⚫🔴⚫🔴⚫🔴⚫⚫⚫⚫⚫
La altura de la pantalla física (cámara) es la misma que la de la virtual. De esta forma simplemente moviendo la cárama hacia la derecha (incrementando su coordenada offx) se visualiza la siguiente parte del texto (y genera el efecto del texto desplazándose hacia la izquierda)
Este mundo virtual se almacena en una memoria ROM 4x32:
// | H | O | L | A |!|!| space
00001001001100100000110010100000
00001111010010100001001010100000
00001001010010100001111000000000
00001001001100111101001010100000
Este es el circuito en Icestudio:
(Ej-53-M-4x4-Pantalla-virtual-mensaje.ice)
Se utiliza un contador módulo 29 de 5 bits que representa la coordenada offX de la cámara. Los límites de la cámara son desde la posición 0 hasta la 28. En esa última posición se muestran los píxeles del 28 al 31 (28 + 0, 28 + 1, 28 + 2 y 28 + 3)
Este contador empieza en 0 y se incrementa a la frecuencia de 4 Hz. Con cada incremento la cámara se desplaza 1 pixel a la derecha en la pantalla virtual (causando el efecto de desplazamiento del texto un pixel a la izquierda en la pantalla física)
En este vídeo de Youtube lo vemos en funcionamiento:
La señal de vídeo se puede generar directamente, sin usar memoria, a partir de la posición del ráster. En los siguientes ejemplos se genera una línea horizontal, una línea vertical y una diagonal a partir de los rásteres x e y, usando puertas lógicas. La diferencia con la pantalla 3x3 es que ahora el ráster está en binario en vez de unario, y por tanto escala bien para otros tamaños de pantallas
Para dibujar una línea horizontal utilizamos la condición rasY=Y
, donde Y
es la cordenada vertical donde situar la línea horizontal
Este es el circuito en Icestudio:
(Ej-54-M-4x4-linea-horizontal.ice)
El resultado es que se dibuja una línea horizontal en y = 1
Para dibujar una línea vertical utilizamos la condición rasX=X
, donde X
es la cordenada horizontal donde situar la línea vertical
Este es el circuito en Icestudio:
(Ej-55-M-4x4-linea-vertical.ice)
El resultado es que se dibuja una línea vertical en x = 2
¿Y cómo se podría dibujar una línea diagonal que vaya desde la esquina superior izquierda (0,0) a la esquina inferior derecha (3,3)?. En este caso la condición a cumplir es que el píxel actual se encienda cuando las coordenadas X e Y del ráster (rasX, rasY) sean igual. Es decir, la condición rasX=rasY
Este es el circuito en Icestudio:
(Ej-56-M-4x4-linea-diagonal-principal.ice)
El resultado es que se dibuja la línea diagonal principal
Para dibujar la diagonal contraria, la que va desde la esquina superior derecha (3,0) hasta la esquina inferior izquierda (0,3), la condición que se debe cumplir es rasY = 3 - rasX
Para ello es necesario convertir rasX a valor negativo y luego sumar la constante 3
. Esto lo hacemos con los bloques -A
y +
. Una vez calculado ese término, se compara con rasY
Este es el circuito en Icestudio:
(Ej-57-M-4x4-linea-diagonal-secundaria.ice)
El resultado es que se dibuja la línea diagonal secundaria
Ya sabemos crear objetos geométricos sencillos, mediante la implementación de sus ecuaciones. Lo hemos aplicado a rectas. Lo que obtenemos es una señal de vídeo que al enviársela al display nos dibuja ese elemento gráfico
Estos elementos gráficos primitivos los podemos combinar entre ellos para crear elementos más complejos. Utilizaremos dos operaciones: la unión y la intersección
Esta técnica se denomina geometría constructiva
La operación de unión lo que hace es superponer las dos señales de vídeo procedentes de dos objetos gráficos. El resultado es que vemos en el display ambas imágenes superpuestas. Por ejemplo, podemos dibujar una X
mediante la unión de sus dos diagonales: /
U \
= X
La unión se implementa en hardware mediante una puerta OR
Dibujamos una 'X' mediante geometría constructiva. Como ya sabemos dibujar las dos diagonales, para obtener la X no hay más realizar la unión entre ambas. Esto se implementan mediante la operación lógica OR entre sus señales de vídeo
Este es el circuito en Icestudio
El resultado es que se dibuja la X
Dibujamos dos líneas perpendiculares mediante geometría constructiva, en las coordenadas (2,1)
⚫⚫🔴⚫
🔴🔴🔴🔴
⚫⚫🔴⚫
⚫⚫🔴⚫
Este es el circuito en Icestudio
(Ej-59-M-4x4-Dibujo-perpendiculares.ice)
El resultado es que se dibuja la cruz
La operación de intersección muestra las partes comunes de ambos elementos gráficos, y elimina el resto. Por ejemplo, si hacemos la intersección de las dos líneas perpendiculares del ejemplo 59, lo que obtenemos es un pixel encendido:
⚫⚫⚫⚫ ⚫⚫🔴⚫ ⚫⚫⚫⚫
🔴🔴🔴🔴 ∩ ⚫⚫🔴⚫ = ⚫⚫🔴⚫
⚫⚫⚫⚫ ⚫⚫🔴⚫ ⚫⚫⚫⚫
⚫⚫⚫⚫ ⚫⚫🔴⚫ ⚫⚫⚫⚫
La intersección se implementa en hardware mediante una puerta AND
En este ejemplo se dibuja la intersección de dos línea verticales, que es un pixel. Se usan las mismas líneas del ejemplo 59, que están en las coordenadas (2,1)
(Ej-60-M-4x4-Interseccion-pixel.ice)
Este es el resultado
El cursor está constituido por dos registros que almacenan sus coordenadas x e y. Cuando la posición actual del ráster (rasX, rasY) coincide con la del cursor (Cx,Cy) se activa el LED correspondiente. El cursor se genera directamente por hardware
También se puede ver desde el punto de vista de la geometría constructiva. La señal de vídeo del cursor es un pixel generado a partir de la intersección de una línea vertical y una horizontal, determinadas por su posición (Cx,Cy)
En este ejemplo se mueve un cursor mediante 4 teclas: arriba, abajo, izquierda y derecha. La posición del cursor (Cx,Cy) se almacena en dos contadores de 2 bits, con valores límites. El cursor no puede sobrepasar los límites del rectángulo de la pantalla
Este es el circuito en Icestudio
(Ej-61-M-4x4-cursor-cruz-teclas.ice)
En este vídeo de Youtube lo vemos en funcionamiento:
Como último ejemplo vamos a implementar la aplicación HWPAINT para el display 4x4. Es un circuito que nos permite dibujar, píxel a píxel, en la matrix de 4x4 LEDs. Mediante 4 teclas se mueve el cursor y se lleva a la posición donde queremos encender/apagar el LED. Con el pulsador SW1
se enciende. Con SW2
se apaga. Hay un tercer pulsador para alternar entre los dos modos de funcionamiento: Modo normal y modo edición
En el modo normal simplemente se visualiza el dibujo que está almacenado en la memoria de Vídeo. En el modo edición se muestra el cursor y se permite modificar el estado de los píxeles
En este vídeo de Youtube lo vemos en funcionamiento:
En el escenario tenemos el display 4x4 conectado a la tarjeta Alhambra II, 4 pulsadores para el movimiento del cursor y otro pulsador externo para el cambio de modo
Este es el circuito en Icestudio, que iremos desgranando poco a poco
El circuito de pantalla es el que ya conocemos. Pero ahora le introducimos una señal de vídeo que es la combinación de la del cursor y de la que viene de la memoria de vídeo
Utilizamos una puerta XOR para permitir que el cursor parpadee, incluso si en la posición actual el pixel está ya a 1
Las teclas de movimiento del cursor son externas, y se conectan utilizando los pull-ups internos de la FPGA. Los tics generados al apretar las teclas se reciben por las etiquetas left
, right
, up
y down
Las dos coordenadas del cursor (Cx,Cy) están almacenadas en dos contadores de 2-bits con límites. Con las teclas correspondientes estos contadores se incrementan o decrementan. El límite inferior es 0, lo que causa que el cursor no pueda moverse más hacia la izquierda cuando está en la primera columan, o que no pueda moverse más hacia arriba cuando está en la primera fila
El límite superior es 3, para que el cursor no pueda moverse más hacia la derecha cuando esté en la columan 3, ó más hacia abajo cuando está en la fila 3
Este circuito genera la señal de vídeo del cursor. Empezamos el circuito de izquierda a derecha. Por defecto la señal de cursor es 0
. Cuando se cumple que el ráster está sobre la posición del cursor (ambas componentes rasX,rasY == Cx,Cy), la señal se pone a 1 durante un ciclo. Esta es la señal a la salida de la primera AND. Y es la que hemos usados en el ejemplo 61 para mostrar un cursor que siempre está encendido en su posición
En la aplicación HWPaint queremos que el cursor parpadee por lo que le aplicamos una segunda AND con un pulso de mayor anchura (Frecuencia de 2Hz --> Anchura de 1/2 --> Periodo de 500ms. Está a 1
durante 1/4 --> 250ms)
Por último, esta señal la pasamos por una tercera AND para sólo dejarla pasar si estamos en modo edición. En caso de estar en modo normal, la señal del cursor se deja a 0
(no se visualiza el cursor). En modo edición se envía un 1
o un 0
alternativamente (porque el cursor parpadea)
Mediante un pulsador externo se alterna entre los dos modos de funcionamiento: Normal
/ Edición
. Este modo se encuentra almacenado en un biestable T, que por defecto está en modo edición. Adicionalmente este modo se muestra en un LED
Mediante los pulsadores SW1
y SW2
se enciende o apaga el pixel que está en la posición actual del cursor. El primer paso es codificar estos dos bits en uno sólo que indica el valor del pixel: 0
ó 1
. Esto se consigue mediante un codificador de 2 a 1. Su salida se lleva a la etiqueta value
Si NO hay ningún botón pulsado, la señal nz
está a 0
, por lo que la señal de write
está siempre a 0 y no se escribe nada en la memoria de vídeo
Cuando se apritea algún botón, la señal nz
se pone a 1
, y la señal write
se activa o no dependiente del modo. Sólo en el modo edición se genera la señal de write
para escribir en la memoria de vídeo
Finalmente tenemos la memoria de vídeo. Es una memoria de doble puerto. El puerto 1 es de sólo lectura. Esta lectura es combinacional, y se direcciona mediante el ráster. La componente x del ráster se usa como los bits de menor peso, y la componente y como los de mayor
El puerto 2 es de escritura y lectura (aunque la lectura no se usa). Se direcciona mediante la posición del cursor: Los bits de menor peso vienen de Cx
y los de mayor de Cy
. En esta dirección se escribe el valor actual del píxel (que depende de qué pulsador se haya apretado: sw1 o sw2)
Hemos aprendido a organizador los LEDs en una matriz bidimensional con la podemos mostrar información. Aunque la matriz de 4x4 tiene una resolución muy baja, nos ha permitido presentar todos los conceptos importantes, junto con ejemplo de uso. La información se muestra por refresco, a partir de una señal de vídeo. En cada instante sólo hay un pixel activo
Ahora ya tenemos el poder de mostrar información gráfica, sprites y texto. Y lo podemos hacer con pantallas de cualquier resolución. Los principios de funcionamiento son los mismos que para la matriz 4x4
Ya sabemos cómo construir una pantalla desde cero, con su controlador asociado. Un paso más hacia el diseño de nuestro propio ordenador desde cero...
- Juan González-Gómez (Obijuan)
- Fuentes de letras de 4x4 píxeles: Fontstruct.com