sábado, 17 de marzo de 2012

Movimiento y colisiones

A lo largo de esta semana he estado haciendo pruebas con diferentes tipos de scroll y he llegado a la conclusión que con el cristal de 16MHz no hay mucho que hacer. No hay que olvidar que lo que pretendo hacer es el software de una consola con un microcontrolador de 8 bits, intentando ahorrar en materiales, y que las antiguas consolas de 8 bits tenían el procesador para el juego, el chip de gráficos, el de sonido, el de RAM, el de ROM... Así que si un sólo chip tiene que hacer todas las tareas como es el caso, creo que con lo que tengo por ahora puedo estar más que contento.

Antes de limpiar el código y decidirme por fin a hacer una librería en condiciones he probado mover un sprite animado por la pantalla, y ya de paso probar una colisión sencilla. Además he querido probar de cambiar la animación cada vez que colisionase. El ejemplo es bastante pedestre, de momento todos los tiles del TileMap son sólidos excepto el tile 0. El movimiento es a escala de tile (de 8 en 8 pixels), en el futuro será pixel a pixel.

Fondo

Sprites







Cada vez que el pájaro cambia de posición hay que restaurar el fondo de la posición anterior para no dejar una estela con el último grupo de tiles que conforman el objeto.
El código está en la pestaña de descargas. Espero poder hacer pronto la librería y poder alojarla en algún sitio como Google Code para no saturar de links de descarga la pestaña.


Monster Bird: http://www.spriters-resource.com/other_systems/alexkiddmw/sheet/35938

domingo, 11 de marzo de 2012

Scroll

Otra prueba que quería hacer antes de ponerme a ordenar el código y montar las librerías finales (o casi finales) es la del scroll de pantalla. De momento he probado con scroll horizontal repitiendo la misma pantalla de forma infinita.

Teoría del scroll


Para generar el scroll hay que crear dos buffers del tamaño de la pantalla y pintarlos uno detrás de otro, al llegar al final, reseteamos la posición de memoria inicial donde pintamos y volvemos al principio.

Arduino mega cuenta con un espacio reservado de 64K de memoria externa, de la posición 0x2200 a la 0xFFFF. El shield que estoy utilizando, MegaRAM, tiene bloques de 32K y pueden utilizarse 2 simultáneos consiguiendo un total de memoria de 56K aproximadamente. El buffer que estoy utilizando es de 29952B (208x144), si utilizo 2 buffers, necesito 59904B (58.5K). No cabe. Y si utilizo un bloque de memoria para cada buffer no puedo hacer scroll ya que los datos no serían contiguos en memoria.

Solución 1:

Dividir la pantalla en 2 de forma horizontal, colocar los datos de la parte superior en un bloque de memoria y los datos de la parte inferior en el otro bloque. Cuando empieza el render de lineas activas hay que activar el primer bloque de memoria y al llegar a la mitad de lineas hay que activar el segundo bloque.

División en 2 bloques

Solución 2:

Quitar dos columnas de tiles (16px) del buffer. Esto tiene 4 ventajas, la primera es el ahorro de memoria:

192 * 144 = 27648B = 27K
27 * 2 = 54K

54K caben en un bloque de 56K. Otra ventaja es que ahora tendré un aspecto de pantalla de 4:3:

144 * 4 / 3 = 192

La tercera es que al quitar 16px gano 64 ciclos de reloj o 4us para liberar CPU. Cada pixel tarda 4 ciclos:

16 * 4 = 64 ciclos
64 / 16 = 4us

Y la cuarta ventaja es que con esta resolución me acerco más a los márgenes de seguridad. Si el ancho máximo que podía conseguir era de 208 y el margen es del 90%:

208 * 90 / 100 = 187.2px

Sigo estando fuera de márgenes pero había que buscarle algo bueno a perder resolución.

De entre las 2 soluciones de momento me quedo con la segunda. Si quisiese más resolución tendría que cambiar el cristal de la placa Arduino Mega por uno más rápido, por ejemplo, con 20MHz tendría 260px, pero mi intención es la de no modificar la electrónica básica de Arduino. Además, necesitaría más RAM y estaría en las mismas.

52us * 20MHz = 1040ciclos
1040 / 4 = 260

El siguiente video muestra el scroll de la opción 2:





Esta vez he subido el código con los 2 ejemplos, está en la pestaña de descargas.


lunes, 5 de marzo de 2012

Sprites animados

Un sprite animado es una secuencia de imágenes que se repite para dar la sensación de movimiento. Para poder animar sprites con mi código primero debo liberar el procesador. Ahora estoy pintando un fotograma cada 20000us, cada fotograma son 312 líneas, y cada línea dura 64us.

 64 * 312 = 19968us
20000 - 19968 = 32us

 Dispongo de 32us para ejecutar la lógica de programa, o lo que es lo mismo de media línea. Una forma de liberar procesador sería utilizar un timer de 64us para pintar cada línea de forma separada, y quitar todos los delays finales de cada una de las líneas de sincronía y de las líneas no visibles. Pero si alguna línea tardase más de la cuenta en ejecutarse se perdería la sincronía.

La mejor forma que he encontrado de hacerlo es cambiando los pins de sincronía por un pin PWM controlado por el Timer1 y que sea este el que se encargue de todo. Para remplazar los 2 pins de sincronía por uno sólo que haga por los dos he buscado el esquema interno del monitor 1084P y he encontrado esto:

Esquema del conector DIN6 en el monitor 1084P

 Así que las 2 sincronías van a parar al mismo sitio utilizando resistencias de 470. Cuando HSync es 1, VSync es 0 y viceversa. Así que conectando el pin que generará ahora la sincronía a cualquiera de las dos entradas de sincronía del monitor y la otra entrada a 0V tenemos sincronía con un sólo pin.


Esquema sincronía con PWM

No me he puesto a programar el código de sincronía PWM ya que existen un montón de ejemplos por la red, el mejor de todos es el que utiliza Mdmetzle (no se su nombre real) en su librería Arduino TVOut. He utilizado el código de inicialización del timer y las funciones de sincronía, he cambiado su función de render por la mía y he eliminado el sonido (por ahora).

Para hacer la prueba he utilizado el mismo TileMap que en el artículo anterior (quitando las 3 primeras plantas) y he incluido una animación de 4 fotogramas de Sonic girando.


Cuatro fotogramas = Sonic girando

En el siguiente código se puede ver que en el bucle principal de programa se pinta un fotograma diferente cada 100ms:

 
byte spr;
byte sprC = 0;
byte sprW = 3;

void loop()
{
// Game update code

spr = sprC * sprW; 

// Backbround
SetTile(2,11); 
SetTile(3,11); 
SetTile(4,11); 
SetTile(2,12); 
SetTile(3,12); 
SetTile(4,12); 
SetTile(2,13); 
SetTile(3,13); 
SetTile(4,13); 

// Character
SetSprite(2,11,spr); 
SetSprite(3,11,spr+1); 
SetSprite(4,11,spr+2); 
SetSprite(2,12,spr+12); 
SetSprite(3,12,spr+13); 
SetSprite(4,12,spr+14); 
SetSprite(2,13,spr+24); 
SetSprite(3,13,spr+25); 
SetSprite(4,13,spr+26); 

sprC = (sprC+1)%4; 
delay(100); 

}


El resultado final en un video con poca mala calidad, no tengo una cámara mejor:







El código está en la página de descargas.




Commodore Peripherial Documents: http://project64.c64.org/hw/peri.html
1084P Schematics: http://project64.c64.org/hw/1084p%20schematics.zip
Sprites Sonic: http://sdb.drshnaps.com/display.php?object=6468

sábado, 3 de marzo de 2012

TileSet & TileMap

Una vez conseguido el pintado desde SRAM toca hacer un motor de tiles para generar gráficos de forma que se consuman el mínimo de recursos posibles.

TileSet:
Es una colección de imágenes del mismo tamaño con las que podemos formar otras imágenes más grandes. Podemos compararlo con una paleta de colores, lo veremos más claro con la definición del tilemap.

TileMap:
Es una rejilla que contiene los índices de las imágenes del tileset. Imaginemos un mapa de bits, el mapa de bits es una cuadrícula en la que cada casilla tiene un color (un pixel). Pues a grandes rasgos, un tilemap es igual que mapa de bits pero cada una de las casillas es una imagen en vez de un color.

Un enlace donde se explica mejor la teoría de los tiles: Tilemapping - Juegos basados en tiles

Esta vez he utilizado para las pruebas unos tiles de Sonic the Hedgehog. He actualizado el software que convierte imágenes en código para poder generar el código del tileset. Lo podéis encontrar en la página de descargas.


TileSet

El siguiente código muesta como indexar los tiles en el TileMap:

 
...
byte TileMap[18][26] = {
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x0E, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x09, 0x00, 0x00, 0x0E, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x0F, 0x00, 0x00, 0x0E, 0x0F, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x0F, 0x02, 0x03, 0x0E, 0x0F, 0x00, 0x00, 0x08, 0x09, 0x0A, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, },
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x0F, 0x08, 0x09, 0x0E, 0x0F, 0x00, 0x00, 0x0E, 0x0F, 0x10, 0x11, 0x0A, 0x0B, 0x00, 0x00, 0x0E, 0x0F, 0x00, 0x00, },
  {0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x06, 0x07, 0x0E, 0x0F, 0x0E, 0x0F, 0x10, 0x11, 0x06, 0x07, 0x0E, 0x0F, 0x06, 0x07, },
  {0x0C, 0x0D, 0x0C, 0x0D, 0x0C, 0x0D, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0C, 0x0D, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0C, 0x0D, 0x0E, 0x0F, 0x0C, 0x0D, },
  {0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, },
  {0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, 0x18, 0x19, },
  {0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, },
  {0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, 0x1A, 0x1B, },
};
...
 


El resultado de la mezcla es el siguiente:


Resultado al indexar los tiles del TileSet en el TileMap


Y así se ve por pantalla:

Resultado en el monitor


En la página de descargas podéis encontrar el código del ejemplo, se llama Tiles.
En la próxima entrega: Sprites animados.




Tiles: VGMaps
Sonic: http://www.vgmaps.com/Atlas/MasterSystem/index.htm#SonicTheHedgehog


domingo, 26 de febrero de 2012

Pintando desde SRAM

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.

lunes, 20 de febrero de 2012

Framebuffer en 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)
http://ruggedcircuits.com/html/quadram.html

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)
http://www.scienceprog.com/adding-external-memory-to-atmega128/

Mientras llega el pedido iré desarrollando un software de edición de tiles en VB para poder probar la RAM.

martes, 14 de febrero de 2012

Bitmaps con más color

Después de ver cómo se pueden pintar mapas de bits con 16 colores es hora de ver si se puede hacer con más. Modificando el código que pintaba 208 barras en pantalla con una paleta de 256 colores debería poder hacer lo mismo que en el post anterior.

Así que he modificado la aplicación que transforma imágenes en código para poder utilizar 256 colores y me he decidido a cargar una imagen en Arduino.


Imagen de 256 colores

Resultado en pantalla

Se aprecia una gran pérdida de color en el rasterizado horizontal. Como si el monitor no diera más de sí. Me parece raro, si el monitor es PAL, debería aguantar mucha más resolución. Y con respecto al color, lo hemos visto pintando una paleta de 256 colores. Vuelvo a hacer la prueba, esta vez con una imagen más sencilla.

Alex Kidd a 256 colores

Resultado en el monitor

Sigue habiendo pérdidas. No sé a qué puede deberse. Seguiré investigando. De momento sigo haciendo pruebas con otras paletas. Ahora toca la EGA, una paleta de 64 colores, la misma que utilizaba Sega Master System. Esta vez se trata de un color de 6 bits, así, utilizando un puerto, tenemos 2 bits libres para las dos sincronías. El esquema es el siguiente:


Esquema de conexiones EGA

Vuelvo a probar nuevamente con la imagen de Alex Kidd. Debería funcionar a la primera, se trata de la misma paleta...

Alex Kidd a 64 colores (paleta original)
 
Resultado en el monitor

¡Perfecto! ¡Ahora otro!

Psycho Fox a 64 colores (paleta original).

Resultado en el monitor
Definitivamente, mi consola tendrá la misma paleta gráfica que Sega MasterSystem.

Y como siempre, el código en la pestaña de descargas.

lunes, 13 de febrero de 2012

Bitmaps RGBI

Los mapas de bits que voy a poner de ejemplo son a toda pantalla. Como ya dije en su día voy a utilizar una resolución de 208x144, así que cada imagen ocupará:

208 * 144 = 29952 B
29952 / 1024 = 29.25 KB

Arduino Mega dispone de 8KB de SRAM, así que hay que utilizar memoria de programa. Una vez creado el array de datos podemos acceder a él y copiar los datos que vamos a utilizar a SRAM, pero tenemos el inconveniente de que esto tarda un tiempo si lo hacemos desde Arduino. Pero si lo hacemos desde ASM sólo necesitamos 3 ciclos, nos sobra 1 para pintar. Para saber más acerca de los tiempos que tarda Arduino en ejecutar algunas de las instrucciones os recomiendo que miréis este link.

Esta semana he estado buscando información sobre assembler para microcontroladores AVR ya que no lo utilizo desde las prácticas de la universidad, hace por lo menos doce años. Encontré este documento en la web de Atmel con todas las instrucciones ASM para AVR.

Buscando un poco más he encontrado la web de Masami Watanabe que ha realizado varios ejemplos para pintar bitmaps con escalas de grises en NTSC. Él utiliza las siguientes instrucciones para pintar un píxel:

 
...
"lpm r0,z+\n\t"     // 1
"nop\n\t"
"out 0x08,r0\n\t"
...
 
LPM: Carga memoria de programa en el registro e incrementa el contador de posición de memoria de programa (Z).
NOP: Delay de un ciclo.
OUT: Escribe el registro en el puerto de salida.

Total: 3 ciclos (LPM) + 1 ciclo (NOP) + 1 ciclo (OUT) = 5 ciclos.

He creado una imagen de prueba y luego la he indexado con GIMP utilizando una paleta con los 16 colores, este es el resultado:

Imagen en 16 colores RGBI

Resultado en el monitor

Para pasar la imagen a código, he hecho una pequeña aplicación en VB.Net que lee la imagen pixel a pixel y genera el código del array. Está en la página de descargas.

Y como estoy contento con el resultado, he probado de pintar alguna foto:


Proceso para generar una imagen CGA válida.

Gan Buda de Kamakura. Una foto que hice en 2008.

Resultado en el monitor.

Mi gata

Resultado en el monitor.

El código del ejemplo está en la página de descargas.





PROGMEM:
http://arduino.cc/es/Reference/PROGMEM

Ready, Set, Oscillate! The Fastest Way to Change Arduino Pins:
http://www.billporter.info/ready-set-oscillate-the-fastest-way-to-change-arduino-pins/

8-bit AVR Instruction set:
http://www.atmel.com/Images/doc0856.pdf

Masami Watanabe:
http://homepage3.nifty.com/two_legs/

GIMP:
http://www.gimp.org/

sábado, 11 de febrero de 2012

RGBI: 4Bits = 16 colores

Dejando un poco de lado el tema de los 256 colores, he querido probar la entrada RGBI del monitor. Se trata de una señal de color de 4 bits, 3 de los cuales son RGB y el otro es la intensidad. Lo interesante de este puerto de entrada es que los niveles son TTL (Transistor-Transistor Logic), lo que quiere decir que es un puerto digital, por lo tanto el pin de Arduino se conecta directo al pin del monitor. El sistema RBGI es el utilizado por las tarjetas gráficas CGA (Color Graphics Adapter) utilizadas en los 80's.

Algunos modelos de Commodore 1084 tienen un conector D-SUB 9, el conector estándar de CGA. Este no es mi caso, mi conector es un DIN de 8 pins.

1. N/C - 2. Rojo - 3. Verde - 4. Azul - 5. Intensidad - 6. Tierra - 7. Sincronía horizontal - 8. Sincronía vertical
Imagen original (Autor: Mobius)



La paleta de colores RGBI es parecida que publiqué en el artículo RGB, pero con un tono más oscuro. Al utilizar un bit más, se multiplican por 2 los colores, resultado otra vez los mismos pero con un tono más claro.

El caso del color 6 es especial, algunos monitores utilizan el color, que según el RGBI sería el correcto (amarillo oscuro) y otros (IBM y cónicos) utilizan el marrón, que se consigue reduciendo a la mitad el componente verde. Mi monitor tiene marrón, ¿Es Commodore un clónico de IBM? No lo sé.




Así que he abierto el código "RGB_3Bits.ino" y haciendo una pequeña modificación y conectándolo al DIN8 he visto los 16 colores en mi monitor.


 
...

// Video out voltage levels
//                  RGBIVH
#define _SYNC     0b000000
#define _HSYNC    0b000001
#define _VSYNC    0b000010
#define _BLACK    0b000001
#define _BLUE     0b001001
#define _GREEN    0b010001
#define _CYAN     0b011001
#define _RED      0b100001
#define _MAGENTA  0b101001
#define _BROWN    0b110001
#define _LGRAY    0b111001
#define _GRAY     0b000101
#define _LBLUE    0b001101
#define _LGREEN   0b010101
#define _LCYAN    0b011101
#define _LRED     0b100101
#define _LMAGENTA 0b101101
#define _YELLOW   0b110101
#define _WHITE    0b111101

...

//video pins
#define R_PIN    32
#define G_PIN    33
#define B_PIN    34
#define I_PIN    35
#define V_PIN    36
#define H_PIN    37

...
 


Y como siempre...

Esquema de conexiones


Resultado en el monitor


Y, aunque pueda parecer un retroceso en mi investigación de la profundidad de color para la futura consola, no lo es, ya que por ahora es la forma más cómoda de trabajar al no haber componentes entre Arduino y el monitor. De hecho las dos míticas consolas de 8 bits de los 80's, NES y Master System, sólo utilizaban 52 y 64 colores respectivamente.

Pronto publicaré la pruebas que estoy haciendo con mapas de bits, por ahora podeis bajar el código RGBI aquí.

miércoles, 8 de febrero de 2012

Arreglando la sincronía vertical

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:

Antes:
 
...
// Sync
#define _SYNC     0B00000001
...
 

Ahora:
 
...
// Sync
#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.

sábado, 4 de febrero de 2012

Más resolución

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:
Aspecto: 4:3
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
832 1
416 2
208 4
104 8
52 16
... ...

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
576 1
288 2
192 3
144 4
... ...

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.

Sincronía:

Monochrome Composite Video
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
2.5 líneas: 5 pulsos de igualación
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:

 
...
// Puertos
#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.


Resultado en el monitor


El código lo podéis descargar aquí.

miércoles, 1 de febrero de 2012

Pintando con 8 bits


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:

 
// Video out voltage levels
#define _SYNC 0x00
#define _BLACK 0x01
#define _GRAY 0x02
#define _WHITE 0x03
 

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.

Esquema de conexión
Resultado en el monitor


El código de ejemplo muestra una paleta de 256 colores a toda pantalla.
Lo podéis descargar aquí.


Para el próximo post: Más resolución…

domingo, 29 de enero de 2012

RGB

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

Resultado en el monitor
Esquema rojo
Resultado en el monitor
Esquema verde
Resultado en el monitor
Esquema azul
Resultado en el monitor

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.
Aquí está el resultado:

Esquema RGB
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í.