Source: http://raulnd.blogspot.com/2012/
Timestamp: 2019-12-08 03:25:07+00:00

Document:
Una consola de 8 bits para Arduino: 2012
Esta semana me ha llegado el módulo de MegaRAM que pedí en Rugged Circuits. He hecho una prueba muy sencilla, he creado un buffer bidimensional del mismo tamaño que el mapa de bits que tengo en memoria de programa en la primera posición de memoria del módulo y he copiado toda la información en él.
He creado otra función para pintar lineas como la función RenderLine, pero esta vez en vez de utilizar la instrucción de ensamblador LPM (Load Program Memory) he utilizado LD (Load Indirect). LPM utiliza 3 ciclos, mientras que LD sólo utiliza 2. El acceso a SRAM necesita un ciclo más, así que invertimos el mismo tiempo en acceder a memoria de programa con LPM que a memoria SRAM con LD.
El shield de memoria ocupa 2 pins del puerto C que necesito para pintar, tendré que modificar la placa, pero antes es necesario probar que funciona, así que por ahora pintaré en el puerto F.
No me enrollaré más, os dejo el circuito y el código (página de descargas) por si quereis probarlo.
Esquema de conexiones al monitor con el shield MegaRAM.
Ahora sólo queda hacer un buen motor de tiles para generar mapas de juego y animaciones con sprites.
Etiquetas: SRAM
Por ahora he estado utilizando un framebuffer almacenado en memoria de programa (FLASH). El problema es que la memoria de programa es de sólo lectura y voy a necesitar poder escribir el buffer para generar movimiento. Si la resolución utilizada es 208x144 y cada pixel ocupa 1 byte, necesito un espacio de memoria de 29.25Kb para almacenar la escena que tiene que salir por pantalla.
208 * 144 * 1 = 29952 bytes
19952 / 1024 = 29.25 Kb
Arduino Mega dispone de 128Kb (-4Kb del bootloader) y sólo 8Kb de SRAM. Necesito una solución. Pues bien, como siempre, googleando se encuentran soluciones. He encontrado varios módulos de RAM para Arduino:
El primero, de Andy Brown, es un shield de 512Kb especial para Arduino Mega. Dispone de una librería fácil de utilizar, pero he contactado con el autor y no tiene stock:
512Kb SRAM expansion for the Arduino Mega (Andy's Workshop)
http://andybrown.me.uk/ws/2011/08/28/512kb-sram-expansion-for-the-arduino-mega-build/
El segundo que he encontrado, de Rugged Circuits, está basado en el primero, además utiliza la misma librería. También es de 512Kb:
QuadRAM (Rugged Circuits)
La misma empresa tiene otro shield más sencillo, de 128Kb, de momento este es el que he pedido para hacer pruebas, pero me gustaría probar con más.
MegaRAM (Rugged Circuits)
http://ruggedcircuits.com/html/megaram.htm
El cuarto que he encontrado, de Adam Ward, también de 512Kb, y sólo utiliza 15 pins.
Open Source SRAM Memory Board (Wardy's Projects)
http://wardyprojects.blogspot.com/p/open-source-sram-memory-board.html
Y el último, de Science Prog, es algo más sencillo, el ejemplo tiene una memoria de 8Kb y puede llegar hasta 64Kb.
Adding external memory to Atmega128 (Science Prog)
Mientras llega el pedido iré desarrollando un software de edición de tiles en VB para poder probar la RAM.
Etiquetas: Buffer, SRAM
No me había dado cuenta antes de mi error, los esquemas para euroconector que había seguido de Uzebox tienen la sincronía compuesta, utilizan el mismo cable tanto para la horizontal como para la vertical. Pero la entrada RGB del monitor 1084S las tiene por separado, mejor dicho, espera recibirlas por separado. Así que toca añadir un cable al DIN6 y conectarlo a Arduino en el pin 52. He modificado el código de la siguiente manera:
#define _SYNC     0B00000001
#define _HSYNC     0B00000001
#define _VSYNC     0B00000010
Sólo hay que activar el segundo bit menos significativo del puerto B (pin 52) para poder escribir la sincronía en él y ya está.
Cable banco: Sincronía vertical.
El código está en la sección de descargas.
Etiquetas: PAL, Sincronía
Para dar más resolución primero hay que entender cómo funciona la señal de video en una televisión. El sistema PAL utiliza 25 fotogramas por segundo, cada uno de los cuales está formado por dos imágenes. En las líneas impares del fotograma se pinta la primera imagen (campo impar) y en las pares la segunda (campo par), generando así una frecuencia de 50 imágenes por segundo. Entonces, cada fotograma es el resultado de entrelazar dos imágenes.
Las características del sistema PAL son las siguientes:
Líneas: 625
Líneas visibles: 576
Columnas: 720
Frecuencia de cuadro: 25Hz (25 fotogramas por segundo o 40ms por fotograma)
Frecuencia de campo: 50Hz (50 imágenes por segundo o 20ms por imagen)
Periodo de línea: 64us
Periodo visible de línea: 52us
Pórtico anterior: 1.5us (Señal HIGH)
Sincronismo horizontal: 4.7us (Señal LOW)
Pórtico posterior: 5.8us (Señal HIGH)
Pulso vertical: 27.3us
Pulso igualador: 2.35us
Con esta información sabemos que cada línea invierte 64us en total y que si la parte visible invierte 52us, nos quedan 12us para generar una señal de sincronía horizontal.
Ciclos de reloj de línea visible: 52us * 16MHz = 832 ciclos
¿Qué resolución horizontal podemos obtener con 832 ciclos?
Resolución horizontal Ciclos por píxel
Yo creo que la resolución buena es la de 208px, esto quiere decir que invertiremos 4 ciclos en pintar cada uno de los píxels que forman la línea.
Si el aspecto es 4:3, entonces la resolución vertical sería:
208px / 4 * 3 = 156px.
¿Cuál es la resolución vertical disponible que más se acerca si tenemos 576 líneas visibles?
Resolución vertical Líneas por pixel
El valor más cercano a 156 es 144, así que utilizando 4 líneas para cada píxel tenemos una resolución final de 208x144.
Imagen original (Batsocks)
Viendo el gráfico anterior, vemos que los 12us no visibles de cada línea visible se invierten en generar una señal de sincronía horizontal de la siguiente forma:
4.7us HSync + 5.8us Back Porch + 1.5us Front Porch = 12us
4.7us HSync + 5.8us Back Porch + 52us color + 1.5us Front Porch = 64us
La sincronía vertical se obtiene de la siguiente forma:
2.5 líneas: 5 pulsos de igualación
2.5 líneas: 5 pulsos vertical
Total: 7.5 líneas de sincronía por campo
Además, entre las líneas de sincronía vertical y las líneas visibles existen otras líneas reservadas a otros temas como por ejemplo teletexto o información digital como el nombre de la cadena que estamos viendo. Estas líneas no las utilizaremos.
Pues bien, hoy voy a intentar conseguir la resolución horizontal.
Para pintar un píxel utilizando 4 ciclos he probado el siguiente código:
#define SYNC     PORTB
#define PORT     PORTC
// Dibuja un pixel
#define DrawPx(color) PORT=color; NOP; NOP; NOP;
La instrucción para escribir en un puerto (PORT=color) tarda un ciclo.
La instrucción NOP espera un ciclo sin hacer nada.
Además, esta vez he utilizado interrupciones de timer cada 40000us (1 fotograma) así dejo libre el loop para el futuro código del juego.
El código lo podéis descargar aquí.
Después de las pruebas de color explicaré un poco como voy a pintar con una profundidad de color de 8 bits.
8 bits = 1 byte = 256 colores
Un color de 24 bits lo representamos utilizando un byte para cada uno de los componentes de la siguiente forma: RGB(255,255,255) o HEX(FFFFFF) para el blanco y RGB(0,0,0) o HEX(000000) para el negro. Y uno de 32 bits: ARGB(255,255,255,255) o HEX(FFFFFFFF) para el blanco opaco y ARGB(0,0,0,0) o HEX(00000000) para el negro transparente. Entonces ¿Cómo se consigue un color de 8 bits? De la siguiente forma:
Rojo: 3 bits
Verde: 3 bits
Azul: 2 bits
Así que 255 sería el blanco y 0 en negro. Si lo descomponemos en bits: BIN(11111111) y BIN(00000000).
Rojo: 11100000
Verde: 00011100
Azul: 00000011
Para representar esto con Arduino necesitamos utilizar un puerto de 8 bits (8 pins) para escribir todo el byte a la vez. Arduino Duemilanove y Arduino UNO cuentan con 3 puertos:
PORTB: Pins 8 .. 13 (6 bits)
PORTC: Pins A0 .. A5 (6 bits)
PORTD: Pins 0 .. 7 (8 bits)
Podríamos utilizar el puerto D y obtener 8 bits, pero los pins 0 y 1 se utilizan en la comunicación serie y perderíamos esta funcionalidad. Otras placas como el Arduino Nano, cuentan con 2 pins analógicos más, A6 y A7, por lo tanto tenemos:
PORTC: Pins A0 .. A7 (8 bits)
Pero si utilizamos este puerto para pintar 256 colores perderemos todas las entradas analógicas, y seguro que en el futuro las necesitamos para conectar mandos analógicos como por ejemplo dos potenciómetros para jugar al Pong.
Mi elección es Arduino Mega ya que cuenta con 11 puertos, del puerto A al puerto L, excepto el puerto I (Más info). Además de 16 pins analógicos dividido en 2 puertos (F y K), cuatro puertos serie y 8K de memoria RAM.
Arduino MEGA raw CPU port names
Imagen original (Westfw - CC BY-SA 2.0)
Utilizaré de momento el puerto C que es el que he estado utilizando hasta ahora y cuenta con 8 bits, del pin 30 al 37, para generar la señal de video y el bit 0 del puerto B para generar la sincronía (pin 53).
Y ¿Cómo convertimos los 8 bits de información digital a 3 componentes de señal analógica? Pues con un DAC (Digital to analog converter), concretamente con el método de las "resistencias ponderadas". Utilizando un número de resistencias igual al número de bits que queramos convertir y conectadas en paralelo. El bit más significativo tendrá la menor resistencia, el siguiente bit tendrá una resistencia del doble de valor y así sucesivamente hasta el menos significativo.
Veamos el ejemplo de Arduino Pong:
Se utilizan 2 bits para generar los valores de sincronía, negro, gris y blanco; siendo el gris el término medio entre el blanco y el negro:
En el siguiente esquema vemos que el bit más significativo (pin 9) tiene una resistencia de 450Ω y el menos significativo una de 900Ω, el doble. La resistencia de 75Ω es la resistencia de impedancia, que va conectada en paralelo a tierra. La impedancia para televisiones y monitores es de 75Ω.
Arduino Pong Schematic
(Alastair Parker)
Aquí dejo un link de la web de Javier Valcarce donde viene todo clarísimamente explicado.
Es hora de aplicar el DAC al código de ejemplo que utilicé en el post anterior. Para ello voy a utilizar los mismos valores de resistencias que utiliza Uzebox: 3K, 1K5 y 750Ω, además de la resistencia de impedancia de 75Ω.
Uzebox SCART Interface
Imagen original (Belogic - CC BY-SA 3.0)
Los canales rojo y verde se componen de 3 bits cada uno, o sea que tienen 8 valores posibles, desde B000 hasta B111. El canal azul sólo utiliza 2 bits, así que sus valores irán desde B00 hasta B11.
El código de ejemplo muestra una paleta de 256 colores a toda pantalla.
Para el próximo post: Más resolución…
Etiquetas: Bits, Byte, Puerto
Para hacer las pruebas con colores estoy utilizando un viejo monitor 1084S de Commodore que cuenta con entradas por componentes. Concretamente utilizaré el DIN de 6 pìns que consta de 3 pins para el color en RGB, dos pins de sincronía (vertical y horizontal) y otro pin de tierra.
1. Verde - 2. Sincronía horizontal - 3. Tierra - 4. Rojo - 5. Azul - 6. Sincronía vertical
Imagen original (Miguel Durán - CC BY-SA 2.5)
Navegando por la red encontré el código de Alastair Parker, un sencillo Pong en Arduino con toda la programación dentro del mismo sketch, sin utilizar ninguna librería. Buscando un poco más encontré un ejemplo en Daily Duino hecho por un tal Phizone a partir del código del Pong. El ejemplo en cuestión simplemente pinta unas letras en la pantalla. Descargué el código, lo cargué en un Arduino Nano, conecté las resistencias y el monitor por entrada de video compuesto y funcionó.
Luego me dije ¿Y si pruebo de utilizar la entrada RGB conectando la salida de video de Arduino con uno de los componentes y la salida de sincronía con alguno de los pins de sincronía? No esperaba ver colores, pero si lo deseaba y pasó, los elementos blancos de pantalla se veían del color del pin utilizado.
Esquema de Daily Duino
Esquema azul
El próximo paso fue crear 3 salidas de video y pintarlas a la vez en el puerto correspondiente. Además de intentar conseguir más resolución disminuyendo el tamaño del pixel. Para esto último había que conseguir delays más pequeños que el mínimo que ofrece Arduino (1 microsegundo). Esto se consigue con una instrucción de ensamblador llamada "nop" (no operation performed) que espera un ciclo de reloj sin hacer nada. Si Arduino utiliza un cristal de cuarzo de 16MHz, podemos calcular el tiempo de delay que representa un "nop" de la siguiente forma:
nop = 1 ciclo = 1/16 us = 62.5 ns
Así que tras probar varias combinaciones llegué a la conclusión de que con 6 nop's detrás de cada pixel podía llegar a pintar 84 columnas. Y ajustando el tiempo de delay vertical con 470 us después de cada fotograma, podía pintar 60 filas.
Barras en el monitor con una inscripción en negro
Podeis descargar el código de la prueba de color RGB de 3 bits aquí.

References: resolución 
 resolución 
 resolución 

Resolución 
 resolución 
 resolución 
 resolución 

Resolución 
 resolución 
 resolución 
 resolución