You are on page 1of 85

R1 (30 de abril de 2010):

Versión inicial.

R2 (2 de mayo de 2010):

-Añadido soporte PAL:

-para renderizar en pal llame a start_render(_PAL);

-para renderizar en NTSC llame a start_render(_NTSC);

-Video centrado mejor.

-sistema reducido al uso de una interrupción, esto permitirá agregar sonido y/o

soporte para sondear el UART/SPI/ect. una vez por línea de exploración.

-ahora representa medios fotogramas completos (262 líneas) en lugar de 255


líneas para NTSC

-Fuentes modificadas para que tengan un aspecto menos horrible.

-renderizado de líneas ligeramente más rápido.

-Se solucionó el problema con la representación de líneas que no funcionaba


en el último píxel horizontal.

-Se agregó soporte para pausar video; la salida aún emite h sync

-pausa: pausa_render();

-resume: resume_render();

-Esto causa un problema de Vsync para el primer fotograma, se recupera


después

pronto.

R3 (9 de mayo de 2010):

-Añadido horz_res(), vert_res(), char_line(), get_pixel(x,y)

-horz_res/vert_res obtiene la resolución de la pantalla

-char_line obtiene la cantidad de caracteres que caben en una línea

-get_pixel obtiene el estado de x,y devuelve 1 para blanco 0 para negro.


-reescribió las funciones de renderizado de líneas

-Se corrigieron errores que impedían cambios en la resolución y la escala de


píxeles.

-centra automáticamente la pantalla verticalmente

-Se agregó mega soporte para arduino, no probado

-Pin de sincronización: pin11

-Pin de vídeo: pin12

-Ahora se admite cambiar la resolución de reproducción.

para hacerlo cambie la resolución virtual y horizontal en video_properties.h

-Nota:

(_RESOLUCIÓN_HORIZONTAL/8)*_RESOLUCIÓN_VERTICAL

debe ser menor que la cantidad de memoria que tiene el arduino.

R4 (18 de mayo de 2010):

-Se agregó soporte para la configuración de resolución en tiempo de ejecución.

-start_render(modo,hres,vres)

-Limpié el código de renderizado.

-Si se requiere velocidad/bajo uso de memoria, se recomienda utilizar R3 y

la resolución se cambia en el archivo video_properties.h.

-Se agregó soporte sanguino, no probado. (El Sanguino es un sistema de


microcontrolador compatible con Arduino basado en el microcontrolador
ATmega644P, el más poderoso procesador DIP de la línea de
microcontroladores de ATmega de Microchips. En el proyecto se incluye
soporte para el microcontrolador ATmega1284P, que es más poderoso incluso
que el original en ciertos aspectos.12Como el Arduino, puede ser usado en
diversos proyectos como impresoras 3D, en robótica o sistemas electrónicos
que necesiten microcontroladores.)

R4.x (19 de mayo de 2010):

-soporte sanguino fijo, funciona correctamente en R4.6


R5 (27 de mayo de 2010):

-Se corrigió un error de renderizado que impedía una pantalla completamente


blanca.

-Se corrigió que la función get_pixel realmente funcione ahora.

-revertida a la versión original de la función draw_line ahora funciona


correctamente

-Se agregó la función de inversión de gijs (foros arduino.cc) que invierte toda la
pantalla

-Se agregó un sistema de generación de tonos nootrópicos (foros arduino.cc).

-Se agregó una función de mapa de bits de pantalla completa, se ampliará muy
limitada.

R6 (junio de 2010) (actualmente Beta en 5.9):

-API pasó a ser más "similar a Arduino", se incluyeron macros

mantener la compatibilidad, con la excepción de start_render(mode,x,y) que

no tiene tal capa de compatibilidad.

-creó un método de relleno que llenará la pantalla con blanco o negro o

invertir la pantalla. clear_screen() e invert() ahora son llamadas macro de fill()

-Cambié a una fuente de 8x8 debido a la simplicidad y velocidad de


renderizado por parte del

byte, toda la impresión de caracteres ahora está restringida x que son múltiplos
de 8

para reflejar esta x ahora se toma como un múltiplo de 8 píxeles.

-encapsoló todas las variables globales en estructuras. uno para la parte del
video y

uno para la parte de audio.

-la interrupción ahora usa el vector de desbordamiento del temporizador1.


Dejando comparar el partido b para

iniciando el renderizado (todavía no lo hace)

-la parte del controlador de línea de la interrupción de línea ahora es manejada


por un puntero de función
y funciones que corresponden a cada uno de los 3 tipos de línea posibles.

-Se cambió el renderizado real a un bucle de ensamblaje que funcionará para


resoluciones

hasta 192 píxeles (a 16 mhz). Esto corta el flash requerido.

por 1,5 kbytes.

-Se movieron las definiciones específicas de hardware al archivo


hardware_setup.h.

-Se corrigió el número de líneas en el modo PAL, gracias amante en los foros
de arduino.cc.

-Función de círculo agregada gracias Andy Crook

-Función de cuadro agregada gracias Andy Crook

R5 (27 de mayo de 2010):

-Se corrigió un error de renderizado que impedía una pantalla completamente


blanca.

-Se corrigió que la función get_pixel realmente funcione ahora.

-revertida a la versión original de la función draw_line ahora funciona


correctamente

-Se agregó la función de inversión de gijs (foros arduino.cc) que invierte toda la
pantalla

-Se agregó un sistema de generación de tonos nootrópicos (foros arduino.cc).

-Se agregó una función de mapa de bits de pantalla completa, se ampliará muy
limitada.

R4.x (19 de mayo de 2010):

-soporte sanguino fijo, funciona correctamente en R4.6

R4 (18 de mayo de 2010):

-Se agregó soporte para la configuración de resolución en tiempo de ejecución.

-start_render(modo,hres,vres)
-Limpié el código de renderizado.

-Si se requiere velocidad/bajo uso de memoria, se recomienda utilizar R3 y

la resolución se cambia en el archivo video_properties.h.

-Se agregó soporte sanguino, no probado.

Beta1.0 (2 de diciembre de 2010)

-Motor de fuentes renovado para utilizar menos memoria flash. Se debe llamar
a select_font(). Consulte la wiki para obtener más detalles.

-Sistema de impresión completo para todos los tipos de datos, no más print_str,
en su lugar use print().

-reescribir draw_circle más rápido usa diferentes argumentos.

-Se eliminó draw_box reemplazado con draw_rect, más rápido y más fácil.

-Se agregó draw_row dibujo de líneas horizontales muy rápido.

-Se agregó draw_column dibujo de columnas muy rápido.

-Se agregaron funciones de fuerza para el retraso de salida de la línea de inicio


y la escala vertical.

-eliminar fs_bitmap y reemplazarlo con la función de mapa de bits general.

-Función de cambio agregada para mover la pantalla en cualquier dirección.

-delay ahora toma ms retrasos, delay_frames todavía toma fotogramas.

-millis reemplazo devuelve ms desde que se llamó a comenzar.

-Se agregaron rutinas de renderizado de 4 y 3 ciclos para resoluciones


horizontales de hasta 256.

-Se agregaron funciones de gancho ciego vertical y horizontal para sondeo


automático.

-Función final agregada para limpiar el sistema.

-comenzó a reducir la cantidad de memoria

Funciones TVOut
begin (modo, x, y). Establece el modo y resolución especificada de funcionamiento.
modo -> PAL = 1 NTSC = 0
x -> La resolución horizontal, este debe ser un múltiplo de 8.
y -> Resolución vertical.
hres () . Proporciona la resolución horizontal.
vres () . Proporciona la resolución vertical.
set_pixel (x, y, color). Pinta un punto(pixel)
x -> Coordenada x del punto
y -> Coordenada y del punto
Color -> WHITE = 1 BLACK = 0 INVERT = 2
clear_screen(). Borra la pantalla.
draw_line (x0, y0, x1, y1, color) Dibuja una línea desde (x0, y0) a (x1, y1) con un color
dado.
x0 y x1 -> Coordenada x del punto de inicio y final
y0 y y1 -> Coordenada y del punto de inicio y final
Color -> WHITE = 1 BLACK = 0 INVERT = 2
draw_rect (x, y, w, h, color, fillcolor) Dibuja un rectángulo en (x, y) con las
dimensiones (h, w), y rellenar con el color dado.
x -> Coordenada x del punto
y -> Coordenada y del punto
h -> Longitud en x
w -> Longitud en y
Color -> WHITE = 1 BLACK = 0 INVERT = 2
Fillcolor -> WHITE = 1 BLACK = 0 INVERT = 2 NOFILL=-1 Parámetro
optativo
draw_circle (x, y, radio, color, fillcolor) Dibuje un círculo en (x, y) con el radio dado, y
rellenar con el color dado.
x -> Coordenada x del punto
y -> Coordenada y del punto
radio -> Longitud del radio del círculo
Color -> WHITE = 1 BLACK = 0 INVERT = 2
Fillcolor -> WHITE = 1 BLACK = 0 INVERT = 2 NOFILL=-1 Parámetro
optativo
bitmap(x,y,bmp,i,width,height) Dibuja un mapa de bits en las coordenadas x, y.
x -> Coordenada x del punto
y -> Coordenada y del punto
select_font (fuente). Seleccione la fuente que desea imprimir.
fuente -> font4x6 font6x8 font8x8 font8x8ext
print (). Imprime una cadena de texto.
println (). Imprime una línea de texto.
delay(ms). Retardo un n ms.

Salida TV
Esquema Eléctrico:

Veámos un ejemplo sencillo con dos simples pantallas. En la primera se muestra texto con la
temperatura recogida con una NTC y adquirida por Arduino en su entrada 5. Después de un
delay, se muestra un bitmap (ver el vídeo del resultado al final de esta entrada).

He utilizado la última versión beta disponible en el momento de escribir esta entrada. Existe
una función para mostrar imagenes almacenadas en memoria flash. Para crear imagenes,
puedes utilizar el software Image2Code
(http://sourceforge.net/projects/image2code/files/).
El software permite abrir directamente una imagen y convertirla a un array en C:

Una vez hecho click sobre Convert, se te abrira el editor de textos con el código de la foto.
Usa la función
reemplazar para eliminar "{" y "}".
Ahora ya tienes tu imagen preparada para grabarla en tu Arduino. Lo único que falta es crear
un array de char donde guardarás dicha imagen. Para ello hay que crear dos archivos, uno *.h
donde estará la declaración de la variable y uno *.cpp donde se guardará el array obtenido
con Image2Code.
En mi ejemplo, guardaré la imagen creada en un array llamado ok[] .

Empecemos con el *.h, en mi caso le he llamado bitmap.h.

Para el *.cpp, en mi caso bitmap.cpp. (en la tercera línea, se pone el tamaño de la imagen
(74x96).
#include "bitmap.h"
PROGMEM const unsigned char ok[] = {
74,96,
//pegar aquí el código obtenido con Image2Code
};
Código del sketch:

#include <TVout.h>
#include <fontALL.h>
#include "bitmap.h"

TVout TV;
int final=8;
int mydelay=10;
double temperatura=0.0;

void setup()

{
TV.start_render(PAL);
TV.clear_screen();

void loop()
{

TV.select_font(font4x6);
TV.print_str(2,0*8,"Rumbeando con Arduino By IgOrR");

TV.select_font(font8x8);

temperatura=analogRead(5)*5.0/1023.0;
TV.set_cursor(2,5*8);
//Muestro voltage
TV.print("Analog 5= ");
TV.print(temperatura,2);
TV.print("V");
//Muestro grados
TV.set_cursor(2,6*8);
TV.print("Temp = ");
temperatura=-27.025*temperatura + 112.95;
TV.print(temperatura,2);

for (int i=0;i<3;i++)

{
for (int i=0;i<final;i++)
{
TV.print_str(0,9*8," ");
TV.print_str(i*6,9*8,"Toma ya!");
TV.delay_frame(mydelay);
}

for (int i=final;i>0;i--)


{
TV.print_str(0,9*8," ");
TV.print_str(i*6,9*8,"Toma ya!");
TV.delay_frame(mydelay);
}
}

TV.clear_screen();
TV.bitmap(30,0,ok,0,0,0);
TV.delay_frame(300);
TV.clear_screen();

[TUTORIAL] Reutilizando e-waste que no es e-waste: TVs


analógicos y Monitores con TVOut y VGAx- PARTE 1/2
25 SEPTIEMBRE, 2018 / CIRUJADIGITAL

Hola a todos, en este tutorial vamos a estar viendo como darle vida a
monitores VGA y TVs analógicos que, dada la acelerada implementación de
nuevos estándares en materia de calidad de señal, han comenzado a abundar
como e-waste (aunque en realidad funcionen). La idea va a ser la de darles
una nueva vida para nuestros proyectos. Es una buena forma de combatir la
obsolescencia programada y de conseguir una forma barata (y a veces, mucho
mas atractiva) de mostrar información útil sobre nuestros proyectos. En ese
sentido, estaremos usando las librerías TVOut y VGAx para Arduino (UNO
en el caso de VGAx, aunque recientemente salió un port para el ESP8266 con
mucha más resolución, pero con problemas de flickereo y la imposibilidad de
usar el Serial y el Wifi del micro). En los ejemplos finales, yo utilizo ambas
librerías con dos Arduino Nano, pero funcionan bien en la UNO, y en el caso
de TVOut, en la Mega2560. Quedará en ustedes probar como anda en el
ESP8266 (con mas resolución). Quieren ver ejemplos andando? Esto hice
hace un tiempo en VGAx
VGAx y TVOut corriendo en Arduino Nano
Este tutorial se va a dividir en dos partes. En esta primera parte vamos a estar
viendo el funcionamiento básico de TVOut. En el segundo estaremos viendo
VGAx, y en ambos casos veremos ejemplos de las principales funcionalidades
de ambas librerías. La idea de los tutoriales no es entrar de forma extensiva en
la generacion de video compuesto (TVOut) o señal VGA, para eso pueden
consultar tanto los repositorios de ambas librerias o ver proyectos que
anteceden a esto como el de Nick Gammon. Tampoco vamos a estar
inventando la rueda (video compuesto se genera con microcontroladores hace
tiempo). La idea es reutilizar, a bajo costo, y darle una alternativa visual a los
proyectos que realicen. Mirá el resultado final .

TVOut
TVOut es una libreria que ya tiene bastante tiempo entre nosotros, pero que no
deja de ser interesante. En el ejemplo final que vamos a ver acá, la idea va a
ser msotrar los usos que puede tener para visualizar datos en pantalla, como
los de un sensor de Humedad y Temepratura DHT11. Pero en este punto tengo
que aclarar que en las nuevas versiones del IDE de Arduino (y del core del
mismo), no pude compilar las librerias SimpleDHT y TVOut si utilizaba al
mismo tiempo el Hardware Serial para debugguear. Pero no importa.
Sigamos.
1. Bajar la librería.Lo primero que hay que hacer es bajar la librería, ya
sea desde su repositorio e instalarla manualmente o desde el gestor de
librerias. Tambien les recomiendo instalar SimpleDHT desde el mismo gestor,
si van a hacer elejemplo con el sensor DHT11.
2. Un paso importante: la libreria nos permite trabajar con fuentes
tipograficas pre armadas -muy util para mostrar datos-, pero para poder
usarlas tenemos que entrar a la carpeta de Arduino\libraries\TVOut\
TVoutfonts y mover su contenido a Arduino\libraries\TVOut
3. Diagrama de conexion.Este es el diagrama de conexión que utilizaremos
en el tutorial.Las resistencias se sueldan al pin central de un cable RCA, y son
de 470ohms y 1000ohms (1K). El buzzer es opcional,dado que tanto TVOut
como VGAx permiten generar tonos. El pin 9 corresponde al Sync, y el 7 al
Video. Pueden prescindir tanto del DHT11 como del buzzer para las

pruebas.
4. La estructura básica. Normalmente, todo programa que utiliza TVOut
debe tener la sigueinte estructura:
#include <TVout.h> //Incluimos la librería.
#include <fontALL.h> // Si vamos a utilzar fuentes tipograficas
TVout TV; //Creamos nuestra instancia de la libreria TVOut.
void setup(){
// Inicializamos la salida devideo compuesta:

TV.begin(NTSC,120,96); // 120×96 es la resolucion maxima


soportada.Podemos elegir la norma PAL o NTSC.
//Listo, a partir de acá hacemos lo que queremos.

void loop(){

//Acá hacemos lo que queremos

5. Conociendo funcionalidades básicas. TVOut tiene muchas


funcionalidades que no vamos a cubrir en este tutorial,pero la idea es que
puedan indagar más en ellas utilizando la wiki del proyecto. Pero vamos a
comentar algunas de las mas significativas. Algo MUY IMPORTANTE: la
esquina superior izquierda de la pantalla corresponde al 0,0 de
coordenadas. De ahi hacia la derecha y hacia abajo, tenemos la resolucion.
TV.delay(ms) . Solo podemos utilizar este tipo de demora al utilizar la
libreria TVOut. Es igual al delay clásico (en este ejemplo abusamos del delay,
pero en lo posible trabajen con milis() )
TV.clear_screen() . Esta funcion sencillamente limpia la pantalla, dejandola
en negro. Ideal para cuando mostramos ciertos graficos y queremos mostrar
otros luego.
TV.set_pixel(x,y,color). Esta funcion coloca un pixel en la pantalla
(recuerden la resolucion máxima). En color, podemos indicar «0» (negro) o
«1» (blanco).
TV.draw_line(x_inicio,y_inicio,x_final,y_final, COLOR). Esta función nos
permite dibujar lineas, indicando posición de inicio y fin. en COLOR
podemos elegir WHITE o BLACK.
TV.bitmap(x,y,const unsigned char). También podemos mostrar bitmaps
simples almacenados en PROGMEM como arrays de bytes. Ej: const
unsigned char mi_imagen[], indicando en los primeros dos elementos del
array las dimensiones de la imagen.
6. Funciones de escritura. En el caso de la escritura, la librería nos va a pedir
indicar lo siguiente:
TV.set_cursor(x,y) . La posicion en la pantalla donde escribiremos el texto.
TV.select_font(font6x8). El tipo de fuente que usaremos. La librería incluye
los siguientes tamaños: 4×6, 6×8 y 8×8.
TV.print(x,y,String). Imprime texto en la posición indicada.
TV.println(«Texto de prueba»). Imprime una línea de texto en la última
posición dada por TV.set_cursor(x,y). La próxima llamada a TV.println() se
hará en la línea siguiente a la última llamada.
7. Nuestro ejemplo explicado. Ahora que conocemos las funciones
principales de la librería, vamos a usar todo en un ejemplo concreto: Mostrar
los valores de temperatura y humedad de un sensor DHT11 en un gráfico
cartesiano al mejor estilo dashboard IoT low resolution, así como los valores
propiamente mencionados en formato numérico. La estructura básica de
nuestro programa es esta:
#include <TVout.h>
#include <fontALL.h>

#include <SimpleDHT.h> //Para utilizar el DHT11


TVout TV; //Creamos nuestra instancia de la libreria TVOut.
//Datos para conectar el DHT11:
int pinDHT11 = A0; //Pin
SimpleDHT11 dht11(pinDHT11); //Instanciamos
//Variables para almacenar temp y hum:
byte temperature = 0;
byte humidity = 0;
int err = SimpleDHTErrSuccess;
//Variables para graficar
byte map_pix;
byte x_temp=3;
byte x_hum=3;
byte y;
void setup()
{
intro(); //Mostramos texto en pantalla, separados por delays.
}
void loop()
{
leer_sensor(); //Hacemos una lectura del sensor DHT11
escribir_datos(); //Mostramos esos datos en pantalla
grafico_sensor(int(temperature),false); //Graficamos el valor
proporcionalmente
grafico_sensor(int(humidity),true);

TV.delay(1500); //Hacemos todo cada 1500ms.


}
Hasta acá, el programa inicializa las librerías TVOut y SimpleDHT,
declaramos algunas variables que vamos a usar, y agrupamos las funciones
principales del programa, que se ejecutan secuencialmente. Pasemos a hablar
de cada una de las funciones (intro(), leer_sensor(), escribir_datos(),
grafico_sensor() ).

void intro(){
TV.begin(NTSC,120,96);
TV.select_font(font6x8);
TV.println(«Ciruja Digital 2018\n»);
TV.delay(1000);
TV.select_font(font4x6);
TV.println(«Tutorial: TVOUT y VGAX\n»);
TV.delay(2000);
TV.println(«Grafico de Sensor DHT11\n»);
TV.delay(2000);
TV.clear_screen();
TV.delay(2000);
TV.select_font(font4x6);
}
En la funcion intro(), inicializamos la generación de video compuesto
(TV.begin()), y luego vamos escribiendo diferentes mensajes que se van
actualizando. La duración de los mismos la da el delay() que coloquemos.
Finalmente limpiamos la pantalla y seleccionamos otro tamaño de fuente para
desplegar los datos a continuación.
void leer_sensor(){
if ((err = dht11.read(&temperature, &humidity, NULL)) !=
SimpleDHTErrSuccess) {
return;
}
}
En esta función encapsulamos la lectura del sensor DHT11. Buscando
capturar posibles errores, colocamos un return -que se dispara cuando hay un
error de lectura. Colocamos esta función al principio del loop, por lo que ante
un error, no se intenta graficar nada (queda en pantalla lo ultimo graficado).
Los parámetros de dht11.read() se corresponden con las
variables temperature y humidity previamente declaradas.
void escribir_datos(){
TV.set_cursor(0,0);
TV.println(«Temperatura y humedad DHT11\n»);
TV.set_cursor(4,10);
TV.print(«T: «);
TV.print(int(temperature));
TV.print(» oC H: «);
TV.print(int(humidity));
TV.println(» o/o «);
}
Esta función nos escribe en pantalla los valores de temperatura y humedad
previamente leídos. Es importante mencionar que no pueden pasarse como
parámetros las variables y texto en formato cadena sumándolos, pero sí
pueden enviarse individualmente. Los valores de temperatura y humedad en
este caso tienen que pasarse a enteros (int), dado que en formato byte no se
visualizan.

void grafico_sensor(int sensor, boolean linea_punteada)


{
//Dibujamos los ejes
TV.draw_line(2,93,2,14,WHITE); //eje Y
TV.draw_line(2,93,110,93,WHITE); //ejeX
//Mapeamos los valores a la región de pantalla donde los mostraremos.
map_pix= map(sensor, 0,95, 14,92);

//Para una mejor visualización, reflejamos respecto a la resolución total de


pantalla vertical.
y= TV.vres()-map_pix;
if (linea_punteada)
{
x_temp=x_temp+1;

TV.set_pixel(x_temp,y,1);
x_temp=x_temp+1;
TV.set_pixel(x_temp,y,0);

}
else
{
x_hum=x_hum+1;
TV.set_pixel(x_hum,y,1);
x_hum=x_hum+1;
TV.set_pixel(x_hum,y,1);

if(x_temp>110 || x_hum>110)
{
x_temp= 3;
x_hum=3;
TV.clear_screen();
}
}

Finalmente algunos comentarios sobre la función que grafica (la parte mas
relevante del código). La misma toma como parámetros la variable a medir y
si la misma se graficará con linea punteada o no. Por un lado, las
funciones draw_line grafican los ejes cartesianos donde dibujaremos. Luego
mapeamos los valores leidos por el sensor a la región en pantalla donde los
mostraremos. Con TV.vres() obtenemos la resolución vertical en pixeles, a la
cual le restamos el valor mapeado. Esto nos dará la posición en pantalla en la
región «desde abajo» (es decir, en relación a los ejes cartesianos que
graficamos). Luego tenemos dos casos: si llamamos a la función
con linea_punteada=true, se graficaran dos pixeles por valor, uno blanco y
otro negro. De lo ccontrario, ambos serán blancos. Finalmente, cuando las
variables que cuentan la cantidad de pixels en X superen el valor 110 (120 es
el maximo de ancho de pantalla), la pantalla se limpia y las variables que
cuentan los pixels en X vuelven a la altura X de los ejes graficados, X=3 en
este caso.
Ahora sí, el código completo, algunas fotos y en la próxima parte, VGAx.
//DEMO PARA EL TUTORIAL DE TVOUT- CIRUJA DIGITAL 2018
//RESISTENCIA DE 470R: AL PIN 7
// RESISTENCIA DE 1K: AL PIN 9
#INCLUDE <TVOUT.H> //LIBRERÍAS NECESARIAS
#INCLUDE <FONTALL.H> // SI VAMOS A UTILZAR FUENTES TIPOGRAFICAS

#INCLUDE <SIMPLEDHT.H>

TVOUT TV; //CREAMOS NUESTRA INSTANCIA DE LA LIBRERIA TVOUT.

//DATOS PARA CONECTAR EL DHT11


INT PINDHT11 = A0; //PIN
SIMPLEDHT11 DHT11(PINDHT11); //INSTANCIAMOS

//VARIABLES PARA ALMACENAR TEMP Y HUM.


BYTE TEMPERATURE = 0;
BYTE HUMIDITY = 0;
INT ERR = SIMPLEDHTERRSUCCESS;

//VARIABLES PARA GRAFICAR


BYTE MAP_PIX;
BYTE X_TEMP=3;
BYTE X_HUM=3;
BYTE Y;

VOID SETUP()
{
INTRO();

VOID LOOP()
{

leer_sensor();

escribir_datos();
GRAFICO_SENSOR(INT(TEMPERATURE),FALSE);

GRAFICO_SENSOR(INT(HUMIDITY),TRUE);

TV.DELAY(1500);
}

VOID GRAFICO_SENSOR(INT SENSOR, BOOLEAN LINEA_PUNTEADA)


{

//Dibujamos

TV.draw_line(2,93,2,14,WHITE); //eje Y
TV.draw_line(2,93,110,93,WHITE); //ejeX

map_pix= map(sensor, 0,95, 14,92);


//y=map_pix;
y= TV.vres()-map_pix;
if (linea_punteada)
{
x_temp=x_temp+1;
//y= TV.vres()-map_pix;

TV.SET_PIXEL(X_TEMP,Y,1);
X_TEMP=X_TEMP+1;
TV.SET_PIXEL(X_TEMP,Y,0);

}
else
{
x_hum=x_hum+1;
TV.set_pixel(x_hum,y,1);
x_hum=x_hum+1;
TV.set_pixel(x_hum,y,1);

}
IF(X_TEMP>110 || X_HUM>110)
{
X_TEMP= 3;
X_HUM=3;
TV.CLEAR_SCREEN();
}

[TUTORIAL] Reutilizando e-waste que no es e-waste: TVs


analógicos y Monitores con TVOut y VGAx- PARTE 2/2
25 SEPTIEMBRE, 2018 / CIRUJADIGITAL

En la entrega anterior, pudimos visualizar en un TV de TRC, mediante una


señal de video compuesto generada por Arduino, el gráfico de un sensor
DHT11. Ahora vamos a explorar las potencialidades de VGAx. La librería, en
su versión para Arduino UNO al menos, soporta una resolución de 120×60 y
ofrece la posibilidad de brindar imágenes a color en Video graphics array
(osea, vga) en monitores que soporten este formato de imagen. La versión
para ESP8266 soporta más resolución, pero aún así limita otras funciones
como la conectividad Wifi. A fines de no complejizar de más este tutorial, es
una visita obligada la documentación que figura en el repositorio del
proyecto VGAx, dado que allí está explicada la forma en la que trabaja la
librería, el esquema de conexiones, qué podemos hacer y que no, cómo
generar el color (no se pueden generar todos los colores, hay que elegir en
grupos de a 4 según las resistencias que pongamos). Desde el vamos, vamos a
neceistar un conector hembra DSUB15 para VGA, 2 resistencias de 470ohms
y 2 de 68ohms.
A la izquierda, imagen a 4 colores generada en VGAx apartir de un bitmap
convertido a array de bytes
¿Qué podemos hacer con VGAx?
 Poner pixeles en pantalla (de a uno, o de a 4)
 Poner texto y datos en pantalla usando fuentes personalizadas.
 Generar bitmaps y mostrarlos en pantalla, o sprites.
 Generar tonos de audio simples.
 Como les dije en su momento, esto fue lo que hice con la librería antes.
Algunas consideraciones particulares de VGAx
 La misma ocupa mucho espacio, particularmente en SRAM, si estan
pensando en optimizar sus programas, háganlo. Si estan pensando en
utilizar librerias pesadas como Wire para usar I2C, olvídenlo. En
general, el código suele compilar «a tope», aunque nunca da problemas
de inestabilidad. ¿Optimizable? Quizás.
 El autor -Sandro Maffiodi- nos comenta que el PORTD del Atmega328
se encuentra totalmente reservado por la librería, y por ende no es
posible usarlo para otra cosa. Pero los pines 10 al 13 están disponibles
para comunicación SPI (en teoría).
 La señal de video opera por interrupciones. No se pueden agregar más
interrupciones o se corta la señal.
 Los 3 timers del Atmega328 se utilizan en la generación de
sincronismo horizontal y vertical, y en la eliminación de saltos
molestos (jittering). Dado que el TIMER0 se usa para las funciones de
demora en Arduino (millis(), delay(), micros(), delayMicroseconds() ),
en lugar de esas funciones hay que usar las que provee la librería.
 Intentando hacer el ejemplo para mostrarles, la idea era usar el DHT11
y graficar lo mismo que en el tutorial anterior. Pero dado que la libreria
SimpleDHT trabaja con las funciones mencionadas arriba -Y PARA
NO COMPLICARLA PARA UDS.- decidí leer sencillamente un LDR
en una entrada analogica (K.I.S.S. = keep it simple and stupid).¿Se
puede modificar la libreria SimpleDHT para poder trabajar con VGAx?
Seguro, y se podria crear otra también para leer los 40bits del DHT11,
pero el objetivo de este tutorial no es ese.
 Cada pixel ocupa dos bits. Los posibles valores de los colores son 00,
01, 01 y 11. El color asignado a cada par de bits va a depender de cómo
conecten las resistencias de 470 ohms. En particular, veremos que
VGAx posee una función para colocar 4 pixels en línea (pueden ser de
diferentes colores), por lo que deberemos sumarlos y escribirlos en
Hexadecimal (no lo duden, vayan acá). Por ejemplo, si nuestros colores
son negro(00), rojo(01), verde(10) y amarillo (11), entonces si
quisieramos poner una linea donde tuvieramos un pixel negro, otro
verde, y dos amarillos, nos quedaría así: 00101111, que en hexadecimal
es 2F ( y que al usarlo al programar, debemos colocarle 0x delante, es
decir 0x2F )
Uso de la librería.

En esta parte, si bien Sandro Maffiodo lo explica bastante bien en la


documentación, no está de más volverlo a escribir. La libreria se incluye y se
inicializa de la siguiente manera:

#include <VGAX.h>

VGAX vga; //creamos una instancia de la clase VGAX

void setup() {

vga.begin(); //Inicializamos la generacion de imagen VGA


}

A partir de acá, podemos empezar a hablar de las funciones principales que


tiene la librería:

Anuncio publicitario
vga.putpixel(x, y, color) Esta función coloca un pixel en pantalla en las
coordenadas especificadas, con el color que le asignemos (podemos escribirlo
en el orden que tienen en la secuencia de bits (0 primer color, 1 segundo
color,etc.)
vga.delay(ms) como el delay comun, pero usar este con la libreria
vga.putpixel4(x, y, suma de los 4 pixels en hex) pone 4 pixels en linea. El
ultimo numero es la suma de los valores de cada pixel en un byte 00 01 10 11.
vga.clear(color) Esto limpia la pantalla llenándola de un color (00 para
negro).
VGAX_HEIGHT Constante con la resolución vertical
VGAX_WIDTH Constante con la resolución horizontal.
VGAX_BWIDTH Constante con ancho de pantalla para pixeles agrupados
en bytes (4 pixels por byte). Entonces si la resolución horizontal es 120
pixeles, BWIDTH=30. Esto es se usa al momento de mostrar mapas de bits
donde hay que leer lineas de 4 pixeles.
vga.tone(freq) Permite generar un tono en la frecuencia asignada. Los
sonidos salen por el pin A0
vga.noTone() Detiene la generacion de tonos
Escribiendo texto y variables en pantalla
En la carpeta de la libreria vgax en la carpeta tools, Sandro Maffiodo nos
provee de 2 herramientas para generar fuentes tipograficas y pasar archivos
de imagen a arrays de bits. Si trabajan con bitmaps, es muy importante que los
escalen a la resolución de pantalla antes de trabajarlos. Para poder escribir,
hay que incluir las fuentes generadas. La librería incluye un tipo de fuente
pequeña -que es la que usaremos- Para más detalles sobre esta parte, ver
acá. Las fuentes se deben incluir en un char array, junto con dos definiciones
sobre su altura en pixels y la cantidad de símbolos incluidos.
Dado que el manejo de la RAM es muy limitado, los textos estáticos deben
declararse al inicio del programa con esta estructura, guardandolos en
PROGMEM (es decir, en memoria de programa, no en la memoria volátil).

static const char cadena_texto[] PROGMEM=» VGAX Hello World!»;


¿Y como las mostramos en pantalla? usamos la
función vga.printPROGMEM():
VGA.PRINTPROGMEM(BYTE *FNT, BYTE GLYPHSCOUNT, BYTE
FNTHEIGHT, BYTE HSPACE, BYTE VSPACE, CONST CHAR *STR, CHAR
DX, CHAR DY0, BYTE COLOR)
Así:
VGA.PRINTPROGMEM((BYTE*)FNT_NANOFONT_DATA,
FNT_NANOFONT_SYMBOLS_COUNT, FNT_NANOFONT_HEIGHT, 2, 3,
CADENA_TEXTO, 10, 10, 0);

El primer parámetro nos pide el nombre del array donde esta guardada la
fuente, el segundo las definiciones donde guardamos la cantidad de símbolos
del array y la altura en pixeles de los mismos, luego el espacio horizontal y
vertical entre ambos, el char array a mostrar, la posición X e Y, y finalmente
el color. En este caso la fuente está guardada en el arrayde
bytes fnt_nanofont_data, la cantidad de símbolos en la
definición FNT_NANOFONT_SYMBOLS_COUNT, la altura
en FNT_NANOFONT_HEIGHT, el espaciado horizontal es 2 pixeles, el
vertical 3, la cadena de texto sale de la constante de caracteres cadena_texto, y
la posición X e Y es 10 en ambos casos. El color en este caso es negro (ocupa
la posición 0 en todos los casos).
¿Y si queremos mostrar una variable? Primero tenemos que convertirla a un
char array. En este caso, usamos sprintf para convertir una variable byte a un
char array:

sprintf(tiemp,»%03u»,tiempo_contador); //convierto el byte al char array con


el que anda printSRAM.

El primer parámetro es el char array, y el segundo la variable «de origen».


Luego utilizamos vga.printSRAM() de forma similar al texto estático:
vga.printSRAM((byte*)fnt_nanofont_data,
FNT_NANOFONT_SYMBOLS_COUNT, FNT_NANOFONT_HEIGHT, 1,
1, tiemp, 25, 5, 2); //var tiemp en pantalla

Algo muy importante: Si actualizan texto en un mismo lugar, tengan en cuenta


que si no borran el fondo, el texto comienza a pisarse hasta no entenderse
nada. Una opción sería utilizar vga.clear(0), pero nos borraría toda la pantalla.
¿Que se puede hacer? En el ejemplo final, usamos vga.putpixel4 dentro un
bucle for y llenamos todo el espacio ocupado por el texto, pero con el color de
fondo, justo antes de reescribirlo. Esto nos «limpia» el espacio de escritura en
cada ciclo, sin borrar toda la pantalla.
Desplegando bitmaps
Como se ve en la primera imagen de esta entrada, podemos desplegar bitmaps
según la escala de colores elegida. Primero tenemos que ir a Arduino\libraries\
vgax\tools y abrir 2bitimage en un browser. Luego tendremos que elegir la
imagen a transformar en un array de bits. La imagen debe contener o
parecerse lo mayor posibles a la combinación de colores elegida.

Como output nos dará el array de bytes de la imagen, junto con definiciones
sobre el alto, ancho y el ancho en bytes (BWIDTH).

¿Como lo mostramos en pantalla? Sencillo:

vga.copy((byte*)varname);
¿Y como centramos la imagen? Si ponen una imagen menor al tamaño de la
resolución, les saldra en la esquina superior izquierda. Si quieren centrarla,
tienen que crear una imagen del ancho de la pantalla (y del alto), y centrar en
el medio o donde sea la imagen que desean (todo esto con otro software). Y
luego generar el array. Con esa imagen que ocupe toda la pantalla.
¿Sprites? Se los dejo a ustedes, no es muy difícil, sólo que deberan indagar
sobre masked blit y otras cosas.
Nuestro ejemplo completo
Para nuestro ejemplo vamos a necesitar un LDR conectado de A2 a +5V, y
una resistencia de 10K (o de otro valor) conectada de A2 a GND. Acá pueden
ver la conexión tanto de TVOut como de VGAx en Arduino Nano. Miralos
andando.

Chaos, pero funciona


El código del ejemplo:
#INCLUDE <VGAX.H>

VGAX VGA; //CREAMOS UNA INSTANCIA DE LA CLASE VGAX


//FONT GENERATED FROM BITFONZI – BY SANDRO MAFFIODO
#DEFINE FNT_NANOFONT_HEIGHT 6
#DEFINE FNT_NANOFONT_SYMBOLS_COUNT 95
//DATA SIZE=570 BYTES
CONST UNSIGNED CHAR
FNT_NANOFONT_DATA[FNT_NANOFONT_SYMBOLS_COUNT]
[1+FNT_NANOFONT_HEIGHT] PROGMEM={
{ 1, 128, 128, 128, 0, 128, 0, }, //GLYPH ‘!’ CODE=0
{ 3, 160, 160, 0, 0, 0, 0, }, //GLYPH ‘»‘ CODE=1
{ 5, 80, 248, 80, 248, 80, 0, }, //GLYPH ‘#’ CODE=2
{ 5, 120, 160, 112, 40, 240, 0, }, //GLYPH ‘$’ CODE=3
{ 5, 136, 16, 32, 64, 136, 0, }, //GLYPH ‘%’ CODE=4
{ 5, 96, 144, 104, 144, 104, 0, }, //GLYPH ‘&’ CODE=5
{ 2, 128, 64, 0, 0, 0, 0, }, //GLYPH »’ CODE=6
{ 2, 64, 128, 128, 128, 64, 0, }, //GLYPH ‘(‘ CODE=7
{ 2, 128, 64, 64, 64, 128, 0, }, //GLYPH ‘)’ CODE=8
{ 3, 0, 160, 64, 160, 0, 0, }, //GLYPH ‘*’ CODE=9
{ 3, 0, 64, 224, 64, 0, 0, }, //GLYPH ‘+’ CODE=10
{ 2, 0, 0, 0, 0, 128, 64, }, //GLYPH ‘,’ CODE=11
{ 3, 0, 0, 224, 0, 0, 0, }, //GLYPH ‘-‘ CODE=12
{ 1, 0, 0, 0, 0, 128, 0, }, //GLYPH ‘.’ CODE=13
{ 5, 8, 16, 32, 64, 128, 0, }, //GLYPH ‘/’ CODE=14
{ 4, 96, 144, 144, 144, 96, 0, }, //GLYPH ‘0’ CODE=15
{ 3, 64, 192, 64, 64, 224, 0, }, //GLYPH ‘1’ CODE=16
{ 4, 224, 16, 96, 128, 240, 0, }, //GLYPH ‘2’ CODE=17
{ 4, 224, 16, 96, 16, 224, 0, }, //GLYPH ‘3’ CODE=18
{ 4, 144, 144, 240, 16, 16, 0, }, //GLYPH ‘4’ CODE=19
{ 4, 240, 128, 224, 16, 224, 0, }, //GLYPH ‘5’ CODE=20
{ 4, 96, 128, 224, 144, 96, 0, }, //GLYPH ‘6’ CODE=21
{ 4, 240, 16, 32, 64, 64, 0, }, //GLYPH ‘7’ CODE=22
{ 4, 96, 144, 96, 144, 96, 0, }, //GLYPH ‘8’ CODE=23
{ 4, 96, 144, 112, 16, 96, 0, }, //GLYPH ‘9’ CODE=24
{ 1, 0, 128, 0, 128, 0, 0, }, //GLYPH ‘:’ CODE=25
{ 2, 0, 128, 0, 0, 128, 64, }, //GLYPH ‘;’ CODE=26
{ 3, 32, 64, 128, 64, 32, 0, }, //GLYPH ‘<‘ CODE=27
{ 3, 0, 224, 0, 224, 0, 0, }, //GLYPH ‘=’ CODE=28
{ 3, 128, 64, 32, 64, 128, 0, }, //GLYPH ‘>’ CODE=29
{ 4, 224, 16, 96, 0, 64, 0, }, //GLYPH ‘?’ CODE=30
{ 4, 96, 144, 176, 128, 112, 0, }, //GLYPH ‘@’ CODE=31
{ 4, 96, 144, 240, 144, 144, 0, }, //GLYPH ‘A’ CODE=32
{ 4, 224, 144, 224, 144, 224, 0, }, //GLYPH ‘B’ CODE=33
{ 4, 112, 128, 128, 128, 112, 0, }, //GLYPH ‘C’ CODE=34
{ 4, 224, 144, 144, 144, 224, 0, }, //GLYPH ‘D’ CODE=35
{ 4, 240, 128, 224, 128, 240, 0, }, //GLYPH ‘E’ CODE=36
{ 4, 240, 128, 224, 128, 128, 0, }, //GLYPH ‘F’ CODE=37
{ 4, 112, 128, 176, 144, 112, 0, }, //GLYPH ‘G’ CODE=38
{ 4, 144, 144, 240, 144, 144, 0, }, //GLYPH ‘H’ CODE=39
{ 3, 224, 64, 64, 64, 224, 0, }, //GLYPH ‘I’ CODE=40
{ 4, 240, 16, 16, 144, 96, 0, }, //GLYPH ‘J’ CODE=41
{ 4, 144, 160, 192, 160, 144, 0, }, //GLYPH ‘K’ CODE=42
{ 4, 128, 128, 128, 128, 240, 0, }, //GLYPH ‘L’ CODE=43
{ 5, 136, 216, 168, 136, 136, 0, }, //GLYPH ‘M’ CODE=44
{ 4, 144, 208, 176, 144, 144, 0, }, //GLYPH ‘N’ CODE=45
{ 4, 96, 144, 144, 144, 96, 0, }, //GLYPH ‘O’ CODE=46
{ 4, 224, 144, 224, 128, 128, 0, }, //GLYPH ‘P’ CODE=47
{ 4, 96, 144, 144, 144, 96, 16, }, //GLYPH ‘Q’ CODE=48
{ 4, 224, 144, 224, 160, 144, 0, }, //GLYPH ‘R’ CODE=49
{ 4, 112, 128, 96, 16, 224, 0, }, //GLYPH ‘S’ CODE=50
{ 3, 224, 64, 64, 64, 64, 0, }, //GLYPH ‘T’ CODE=51
{ 4, 144, 144, 144, 144, 96, 0, }, //GLYPH ‘U’ CODE=52
{ 3, 160, 160, 160, 160, 64, 0, }, //GLYPH ‘V’ CODE=53
{ 5, 136, 168, 168, 168, 80, 0, }, //GLYPH ‘W’ CODE=54
{ 4, 144, 144, 96, 144, 144, 0, }, //GLYPH ‘X’ CODE=55
{ 3, 160, 160, 64, 64, 64, 0, }, //GLYPH ‘Y’ CODE=56
{ 4, 240, 16, 96, 128, 240, 0, }, //GLYPH ‘Z’ CODE=57
{ 2, 192, 128, 128, 128, 192, 0, }, //GLYPH ‘[‘ CODE=58
{ 5, 128, 64, 32, 16, 8, 0, }, //GLYPH ‘\’ CODE=59
{ 2, 192, 64, 64, 64, 192, 0, }, //GLYPH ‘]’ CODE=60
{ 5, 32, 80, 136, 0, 0, 0, }, //GLYPH ‘^’ CODE=61
{ 4, 0, 0, 0, 0, 240, 0, }, //GLYPH ‘_’ CODE=62
{ 2, 128, 64, 0, 0, 0, 0, }, //GLYPH ‘`’ CODE=63
{ 3, 0, 224, 32, 224, 224, 0, }, //GLYPH ‘A’ CODE=64
{ 3, 128, 224, 160, 160, 224, 0, }, //GLYPH ‘B’ CODE=65
{ 3, 0, 224, 128, 128, 224, 0, }, //GLYPH ‘C’ CODE=66
{ 3, 32, 224, 160, 160, 224, 0, }, //GLYPH ‘D’ CODE=67
{ 3, 0, 224, 224, 128, 224, 0, }, //GLYPH ‘E’ CODE=68
{ 2, 64, 128, 192, 128, 128, 0, }, //GLYPH ‘F’ CODE=69
{ 3, 0, 224, 160, 224, 32, 224, }, //GLYPH ‘G’ CODE=70
{ 3, 128, 224, 160, 160, 160, 0, }, //GLYPH ‘H’ CODE=71
{ 1, 128, 0, 128, 128, 128, 0, }, //GLYPH ‘I’ CODE=72
{ 2, 0, 192, 64, 64, 64, 128, }, //GLYPH ‘J’ CODE=73
{ 3, 128, 160, 192, 160, 160, 0, }, //GLYPH ‘K’ CODE=74
{ 1, 128, 128, 128, 128, 128, 0, }, //GLYPH ‘L’ CODE=75
{ 5, 0, 248, 168, 168, 168, 0, }, //GLYPH ‘M’ CODE=76
{ 3, 0, 224, 160, 160, 160, 0, }, //GLYPH ‘N’ CODE=77
{ 3, 0, 224, 160, 160, 224, 0, }, //GLYPH ‘O’ CODE=78
{ 3, 0, 224, 160, 160, 224, 128, }, //GLYPH ‘P’ CODE=79
{ 3, 0, 224, 160, 160, 224, 32, }, //GLYPH ‘Q’ CODE=80
{ 3, 0, 224, 128, 128, 128, 0, }, //GLYPH ‘R’ CODE=81
{ 2, 0, 192, 128, 64, 192, 0, }, //GLYPH ‘S’ CODE=82
{ 3, 64, 224, 64, 64, 64, 0, }, //GLYPH ‘T’ CODE=83
{ 3, 0, 160, 160, 160, 224, 0, }, //GLYPH ‘U’ CODE=84
{ 3, 0, 160, 160, 160, 64, 0, }, //GLYPH ‘V’ CODE=85
{ 5, 0, 168, 168, 168, 80, 0, }, //GLYPH ‘W’ CODE=86
{ 3, 0, 160, 64, 160, 160, 0, }, //GLYPH ‘X’ CODE=87
{ 3, 0, 160, 160, 224, 32, 224, }, //GLYPH ‘Y’ CODE=88
{ 2, 0, 192, 64, 128, 192, 0, }, //GLYPH ‘Z’ CODE=89
{ 3, 96, 64, 192, 64, 96, 0, }, //GLYPH ‘{‘ CODE=90
{ 1, 128, 128, 128, 128, 128, 0, }, //GLYPH ‘|’ CODE=91
{ 3, 192, 64, 96, 64, 192, 0, }, //GLYPH ‘}’ CODE=92
{ 3, 96, 192, 0, 0, 0, 0, }, //GLYPH ‘~’ CODE=93
{ 4, 48, 64, 224, 64, 240, 0, }, //GLYPH ‘£’ CODE=94
};
//IMAGE GENERATED FROM 2BITIMAGE – BY SANDRO MAFFIODO
#DEFINE IMG_CIRUJA_WIDTH 120
#DEFINE IMG_CIRUJA_BWIDTH 30
#DEFINE IMG_CIRUJA_HEIGHT 60
//DATA SIZE=1800 BYTES
CONST UNSIGNED CHAR IMG_CIRUJA_DATA[IMG_CIRUJA_HEIGHT]
[IMG_CIRUJA_BWIDTH] PROGMEM={
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, {
0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 170, 170, 171, 255, 255, 255, 255,
255, 255, 255, 192, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170,
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0,
0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170,
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0,
0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 170,
170, 170, 170, 178, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0,
0, 0, 0, 10, 170, 170, 170, 170, 170, 170, 170, 171, 250, 170, 170, 170, 170, 170,
128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 170, 170,
160, 255, 250, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0,
0, 10, 170, 170, 170, 170, 170, 128, 42, 191, 242, 170, 170, 170, 170, 170, 128, 0,
0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 168, 42, 170, 170,
240, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10,
170, 170, 170, 170, 162, 170, 170, 170, 160, 42, 170, 170, 170, 170, 128, 0, 0, 0,
0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 10, 170, 170, 170, 170,
130, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170,
170, 170, 168, 42, 170, 170, 170, 170, 168, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0,
0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 168, 170, 170, 170, 170, 170, 170,
42, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170,
162, 170, 170, 170, 170, 170, 170, 42, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0,
0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 42, 130, 170, 170, 170, 170, 170, 170, 138, 170,
170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 58, 178, 170,
170, 170, 170, 170, 170, 162, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0,
0, 0, 0, 10, 170, 170, 191, 250, 170, 170, 171, 234, 170, 170, 162, 170, 170, 170,
128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 175, 234, 170, 170,
255, 250, 170, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0,
0, 10, 170, 170, 175, 234, 170, 171, 255, 255, 255, 170, 168, 170, 170, 170, 128,
0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 175, 234, 170, 175, 251,
255, 255, 234, 168, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0,
10, 170, 170, 171, 170, 170, 191, 170, 250, 175, 250, 168, 170, 170, 170, 128, 0,
0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 171, 170, 171, 254, 170, 250,
171, 250, 168, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10,
170, 170, 168, 170, 171, 250, 170, 250, 191, 234, 168, 170, 170, 170, 128, 0, 0, 0,
0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 168, 170, 175, 234, 170, 251, 255,
170, 171, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170,
170, 168, 170, 191, 170, 170, 255, 250, 170, 171, 234, 170, 170, 128, 0, 0, 0, 0, 0,
0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 168, 170, 191, 238, 171, 255, 234, 170,
175, 234, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170,
170, 170, 175, 255, 175, 254, 170, 170, 175, 234, 170, 170, 128, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 42, 171, 255, 175, 250, 170, 170, 175,
250, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 42,
170, 170, 170, 170, 170, 170, 191, 250, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0,
0, 0, 0, 0, 0, 10, 170, 170, 170, 138, 170, 170, 170, 170, 170, 170, 10, 162, 170,
170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 138, 170,
170, 170, 170, 170, 170, 10, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0,
0, 0, 0, 10, 170, 170, 170, 162, 170, 170, 170, 170, 170, 170, 42, 170, 170, 170,
128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 168, 170, 170,
170, 170, 170, 168, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0,
0, 10, 170, 170, 170, 170, 42, 170, 170, 170, 170, 162, 170, 170, 170, 170, 128, 0,
0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 138, 170, 170, 170,
170, 138, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10,
170, 170, 170, 170, 160, 58, 170, 170, 168, 42, 170, 170, 170, 170, 128, 0, 0, 0, 0,
0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 170, 63, 170, 170, 130, 170,
170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170,
170, 170, 170, 191, 252, 0, 42, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 170, 191, 250, 170, 170, 170, 170,
170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170,
170, 170, 190, 170, 170, 170, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0,
0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 170, 42, 170, 170, 170, 170, 170, 170,
170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170,
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0,
0, 0, 0, 0, 0, 10, 170, 170, 174, 170, 170, 170, 170, 170, 234, 170, 170, 170, 170,
170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 171, 238, 255, 235, 170,
234, 234, 175, 251, 175, 239, 254, 175, 186, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0,
0, 0, 0, 10, 174, 238, 239, 239, 171, 235, 234, 171, 187, 191, 174, 250, 190, 250,
128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 186, 190, 239, 174, 187, 175,
234, 175, 191, 250, 190, 234, 254, 234, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0,
0, 10, 250, 187, 251, 174, 251, 255, 234, 174, 254, 239, 186, 235, 254, 234, 128,
0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 250, 187, 187, 190, 239, 251, 170,
191, 239, 239, 187, 171, 191, 254, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0,
10, 254, 191, 187, 250, 254, 171, 170, 191, 175, 254, 235, 170, 191, 254, 128, 0,
0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10,
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 128, 0, 0, 0,
0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 10, 170,
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 128, 0, 0, 0, 0, 0,
0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, }, };

STATIC CONST CHAR STR0[] PROGMEM=»CIRUJADIGITAL 2018-VGAX LDR»;

INT LDR;

INT MAP_PIX;

INT X_TEMP=36;
INT Y;

VOID SETUP() {
VGA.BEGIN(); //INICIALIZAMOS LA GENERACION DE IMAGEN VGA
VGA.COPY((BYTE*)IMG_CIRUJA_DATA); //MOSTRAMOS UNA IMAGEN
GUARDADA EN UN ARRAY DE BYTES
VGA.PRINTPROGMEM((BYTE*)FNT_NANOFONT_DATA,
FNT_NANOFONT_SYMBOLS_COUNT, FNT_NANOFONT_HEIGHT, 1, 1, STR0, 2,
55, 1); //TEXTO INICIAL

VGA.DELAY(6000);
VGA.CLEAR(0);
}

VOID LOOP() {

LDR = ANALOGREAD(A2);
ESCRIBIR_DATOS();
GRAFICO_SENSOR(LDR);
VGA.DELAY(100);
}

VOID GRAFICO_SENSOR(INT SENSOR){


//DIBUJAMOS LOS EJES
FOR (BYTE I =2; I< VGAX_WIDTH; I++) { //EJEX
VGA.PUTPIXEL(I,VGAX_HEIGHT-2,1);
}

FOR (BYTE I =2; I< VGAX_HEIGHT; I++) { //EJEY


VGA.PUTPIXEL(3,I,1); //
}

MAP_PIX= MAP(SENSOR, 0,1023, 2,60);

Y= VGAX_WIDTH-MAP_PIX;
X_TEMP=X_TEMP+1;

VGA.PUTPIXEL(X_TEMP,Y,3);

IF(X_TEMP>154) {
X_TEMP= 36;

VGA.CLEAR(0);
}

VOID ESCRIBIR_DATOS(){
CHAR CHAR_TEMP[4];

FOR (INT I=12; I<=16;I++) //CON ESTO LIMPIAMOS CONSTANTEMENTE LOS


PIXELS DONDE SE ACTUALIZA LA VARIABLE.
{ VGA.PUTPIXEL4(I,10,0XFF);
VGA.PUTPIXEL4(I,11,0XFF);
VGA.PUTPIXEL4(I,12,0XFF);
VGA.PUTPIXEL4(I,13,0XFF);
VGA.PUTPIXEL4(I,14,0XFF);
}
SPRINTF(CHAR_TEMP,»%03U»,LDR); //CONVIERTO EL BYTE AL CHAR
ARRAY CON EL QUE ANDA PRINTSRAM.
SPRINTF(CHAR_ARRAY;»%03U»,VARIABLE BYTE)

VGA.PRINTPROGMEM((BYTE*)FNT_NANOFONT_DATA,
FNT_NANOFONT_SYMBOLS_COUNT, FNT_NANOFONT_HEIGHT, 1,
1, STR0, 4, 2, 2); //IMPRIME CHAR ARRAY EN PROGMEM

VGA.PRINTSRAM((BYTE*)FNT_NANOFONT_DATA,
FNT_NANOFONT_SYMBOLS_COUNT, FNT_NANOFONT_HEIGHT, 1,
1, CHAR_TEMP, 50, 10, 2); //VALOR

}
VOID ESCRIBIR_DATOS(){
TV.SET_CURSOR(0,0);
TV.PRINTLN(«TEMPERATURA Y HUMEDAD DHT11\N»);
TV.SET_CURSOR(4,10);
TV.PRINT(«T: «);
TV.PRINT(INT(TEMPERATURE));
TV.PRINT(» OC H: «);
TV.PRINT(INT(HUMIDITY));
TV.PRINTLN(» O/O «);
}

VOID LEER_SENSOR(){
IF ((ERR = DHT11.READ(&TEMPERATURE, &HUMIDITY, NULL)) !=
SIMPLEDHTERRSUCCESS) {
RETURN;
}
}

VOID INTRO(){
TV.BEGIN(NTSC,120,96);
TV.SELECT_FONT(FONT6X8);
TV.PRINTLN(«CIRUJA DIGITAL 2018\N»);
TV.DELAY(1000);
TV.SELECT_FONT(FONT4X6);
TV.PRINTLN(«TUTORIAL: TVOUT Y VGAX\N»);
TV.DELAY(2000);
TV.PRINTLN(«GRAFICO DE SENSOR DHT11\N»);
TV.DELAY(2000);
TV.CLEAR_SCREEN();
TV.DELAY(2000);
TV.SELECT_FONT(FONT4X6);
}

Arduino - Generación de señales de VGA con hardware estádar


Buenas!
Navegando en viejos almacenes de demoscene, dí con un proyecto que me llamó
poderosamente la atención: Craft de Linus Åkesson; Una obra de arte en miniatura basada en
un ATmega88 que genera señales de VGA, los efectos y hasta sonido!
El hallazgo coincidió con el recién adquirido clon chino de Arduino Mega 2560 y con mis
ganas de generar contenido para el blog así que... ¿Porqué no hacer una librería para generar
señales de VGA con hardware estándar?

Manos a la obra
Empezaré con una introducción teórica, para luego discutir la implementación, el hardware y
por último el software asociado (librería).

Introducción Teórica
Se hace referencia a continuación a los tubos de rayos catódicos (monitor de vidrio), cuando
los mismos están prácticamente en desuso, reemplazados por pantallas LCD / LED. De todas
formas, a nivel eléctrico el estándar se ha mantenido y lo que hacen los monitores modernos es
"emular" el comportamiento de un crt para mantener la compatibilidad.

Funcionamiento de un monitor VGA


A groso modo un monitor o tubo de rayos catódicos (crt) es un tubo al vacío de vidrio con una
película de fósforo en la parte frontal (pantalla) sobre la que se hace incidir un haz de rayos
catódicos.
El haz (o los múltiples haces), que "recorre" (barre) constantemente la pantalla de izquierda a
derecha y de arriba a abajo (normalmente) al incidir sobre la película de fósforo genera un
punto iluminado (pixel) que conforma la imagen mostrada en la pantalla.

El movimiento de barrido del haz se realiza por medio de los llamados deflectores.
El control de deflexión y cañón es manejado, en el caso de un monitor VGA por 5 señales
eléctricas (5 pines + tierra del DB15 del monitor):
Sincronización Horizontal (H-SYNC): Señal digital (pulso 0 - 5V) asociada al deflector vertical
del crt (o a su equivalente en un led / lcd) que marca el inicio de una línea, determina la
resolución horizontal y asegura que el monitor muestre los pixeles de la misma entre los
margenes izquierdo y derecho del área visible de la pantalla.
Sincronización Vertical (V-SYNC): Señal digital (pulso 0 - 5V) asociada al deflector vertical del
crt (o a su equivalente en un led / lcd) que marca el inicio de un "frame", determina la resolución
vertical y asegura que las líneas horizontales estén entre el margen superior y inferior del área
visible de la pantalla.
R, G, B: Señales analógicas en el rango 0V (oscuro) a 0.7V (máxima intensidad) asociadas al
cañón de electrones del crt (o a su equivalente en un led / lcd) que controlan la intensidad de
cada componente de color que combinados conforman el color pixel a pixel, linea a linea de la
pantalla.
El estándar es muy estricto en cuanto a los tiempos de las formas de onda. Cualquier
variación genera deformaciones, parpadeos, problemas de alineación, etc.
Cada modo de los muy variados tiene tu respectivo diagramas de tiempo; Para mas
información consultar VGA Timings.
A continuación diagramas / tablas de formas de onda y tiempos del modo VGA 640 x 480 x
60hz.

Diagrama de tiempos simplificado para el modo VGA 640x480 60hz

Tabla de tiempos
Representación gráfica alternativa
Análisis de Implementación
¿Porqué 640x480? Primero es el más estándar de los modos (introducido por IBM para las
PS/2 por el año 1987). Segundo es el de menor resolución y por ende el mas "alcanzable".
320x200 y 320 x240 son variantes del 640x480 y 640x400 (cuatro pixeles por pixel) y no hay
beneficios para la implementación.
El primer escollo de la implementación tienen que ver con la frecuencia de reloj del los pixeles:
25.175mhz.
¿Que es este valor? La frecuencia (velocidad) a la que deben variar las señales analógicas de
los 3 componentes de color (R, G y B) para conseguir la resolución horizontal de 640 pixeles.
Demás está decir que esa frecuencia de reloj es inalcanzable con hardware estándar (Arduino
basado en microcontrolador de familia ATmega).
Para nuestro caso en particular, utilizando DAC's de escaleras de resistencias (se explicará
posteriormente), la frecuencia máxima alcanzable será de 16mhz / 3 (ciclos de instrucción por
pixel) ~5,3mhz. La máxima resolución alcanzable con esta configuración será de ~135pixeles
en ancho por 480 líneas de alto (sin límites).
Se plantea una alternativa para el modo monocromático utilizando el puerto serial de
controlador para generar las señales. En este caso la frecuencia máxima alcanzable será de
16mhz / 2 (máxima frecuencia del usart) = 8mhz. La máxima resolución alcanzable con esta
configuración será de ~203pixeles en ancho por 480 líneas de alto.
Con reloj de 20mhz (en algunos casos viene / se puede reemplazar) mejorará levemente el
rendimiento y se podrá subir ligeramente la resolución.
La utilización con reloj de 8mhz queda directamente descartada.
Implementación
Circuito
Se plantean dos circuitos diferentes para la generación de color en los modos color y
monocromático.

VGA Color
Como mencionaba anteriormente, para los niveles de RGB es necesario generar señales
analógicas en el rango [0V - 0.7V].
Como primera medida, la familia de microcontroladores AVR ATmega de Atmel carecen de
DAC's; pero aún si los tuvieran, se requiere de una frecuencia según especificación de 25.175
mhz (en criollo, el DAC tiene que ser capaz de "cambiar de valor" 25 millones de veces por
segundo), lo que directamente escapa a controladores de gama baja / media,
independientemente de la marca.
Utilizaré entonces los clásicos y conocidos DAC's conformados por resistencias en escalera
(http://es.wikipedia.org/wiki/Red_R-2R).

Este tipo de topología es probablemente la forma mas simple y económica de construir un


DAC para generar una señal analógica (Vout) a partir de los datos digitales del puerto digital al
que las resistencias estén conectadas (a(0) .. a(n-1)),
Para nuestro caso usaremos 2 redes de 3 bits para los componentes rojo / verde y una de 2
bits para los componentes azules.
Pueden generarse entonces 8 niveles de rojo / verde y 4 de azul siendo la máxima cantidad
de colores representables 8 * 8 * 4 = 256 (RGB 3-3-2).
En el esquema se utilizan las E/S digitales [22 - 29] (corresponden al PORTA del
microcontrolador) para los DAC en escalera de los componentes de color y PWM [12, 13] para
las señales de sincronismo. El puerto utilizado puede modificarse desde el include de
configuración de la librería (ArduinoVGAConfig.h).

R4, R9 y R16: Están contempladas para el caso de que algún monitor no tenga los 75ohms
según especificación, en cuyo caso, se necesitarían para "compensar" ya que el cálculo del
DAC ladder es para esa impedancia de carga.

VGA Monocromático
El circuito para el modo monocromático (blanco y negro) es bastante mas sencillo. A
diferencia del circuito para generación de VGA color se necesitan solo dos niveles de color (dos
estados), blanco y negro. El negro se obtiene a partir de la aplicación de 0V en cada uno de los
canales de color.
El blanco a partir de la aplicación de 0.7V (intensidad máxima) en cada uno de los canales.
Requiere de muy pocos componentes y puede ser construido en protoboard o directamente
dentro de las "tapitas" del conector DB15.

Cuenta con solo 5 resistencias de las cuales son 3 las estrictamente necesarias (R1, R2 y
R3).
R1, R2 y R3 están calculadas para generar una caída de tensión de ~0.7V en la resistencia
interna del monitor (75ohms).
R4 y R5 son limitadoras de corriente con la finalidad de proteger el puerto del controlador del
Arduino ante cualquier eventualidad.
En el esquema se utiliza la E/S digital 18 (TX del USART1 del microcontrolador) para los
componentes de color, PWM [12, 13] para las señales de sincronismo y tierra (se puede usar
cualquiera). Los puertos utilizados puede modificarse desde el include de configuración de la
librería (ArduinoVGAConfig.h).
Software
A nivel software se deben generar por una lado las señales de sincronismo, las de video y
proveer al usuario de una interfaz (clase pública) con rutinas que permitan modificar el
contenido de la pantalla.

Señales de sincronismo

Las señales de sincronismo deben ser generadas con la mayor exactitud / precisión posible.
Utilizaré para ello uno de los Timers de 16bits de la familia de microcontroladores ATmega.
El timer es un periférico de hardware accesible desde el código vía registros capaz de ejecutar
rutinas cada intervalos específicos (interrupción), medir tiempos y generar formas de onda.

A partir del Timer se generará:


Interrupción cada 31,8uS (horizontal)
Pulso de sincronismo horizontal de 3.8uS generado por módulo Ouput Compare del Timer
Señal de video
Pulso de sincronismo vertical generado desde la interrupción

Memoria de video
Espacio de memoria RAM asignado como buffer intermedio entre la interfaz de usuario (clase
pública) y la librería de generación de señales de bajo nivel.
Las funciones gráficas de la librería de control vuelcan contenido sobre la memoria de video.
La sección de bajo nivel de la librería, a la tasa de refresco correspondiente con el modo
(60hz), vuelca el contenido en la pantalla crt (genera las formas de onda acorde al contenido).

Para el modo VGA color se utilizará un byte por pixel.


Para una resolución de 640 x 480 pixeles se requerirían 640*480 = 307200bytes (El Arduino
mega2560 cuenta con 8192 bytes de ram); Demás está decir que una de las limitantes del
modo color con hardware estándar será entonces la memoria RAM disponible.
En los ejemplos utilizaré una resolución de 64 x 64 = 4096bytes. Estos parámetros podrán
modificarse en el include de configuración de la librería (ArduinoVGAConfig.h).
Para el modo VGA monocromático se utilizará un bit por pixel (8 pixeles por byte).
Utilizaré una resolución de 160 x 120 pixeles = 2400bytes. Estos parámetros se podrán
modificar también desde el #include de configuración de la librería para permitir su utilización a
costas de reducir la resolución en Arduinos de baja gama.

Generación de señales de color


Propongo dos conjuntos de rutinas para la generación de señales para los dos modos, ambas
dentro de la misma librería y configurables por condicionales de compilación
(ArduinoVGAConfig.h).

VGA Color

Para la generación de señales a color utilizaré a nivel software uno de los puertos estándar
disponibles en el microcontrolador del Arduino.
En la figura se observa uno de los DAC escalera (componente rojo) conectado a 3 pines de un
puerto del microcontrolador.
Los 3 bits en estado bajo corresponderán a una tensión a la salida de la red de 0V (menor
intensidad de rojo = negro).
Alternando el estado de los pines del controlador se pueden obtener diferentes niveles de
tensión (escalera) hasta alcanzar el máximo de 0.7V (mayor intensidad de rojo).
El mismo esquema se repite para los 3 componentes de color (rojo, verde, azul).
La resistencia de carga de 75ohms corresponde a la impedancia interna del monitor en las
líneas de color (R, G, B).

Los componentes de color para el relleno de una línea horizontal se obtienen a partir de
alternar tantos bytes por el puerto como pixeles haya de resolución; En nuestro caso, unos
128bytes.
La salida de datos por el puerto por cuestiones de tiempo debe necesariamente de escribirse
en ensamblador:

//carga en registro el contenido del puntero [Y] (apunta a la memoria de video)


//incrementa Y en 1 (siguiente pixel)
LD R0,Y+ //2 ciclos de reloj
//pone en el puerto A el contenido del registro
OUT PORTA, R0 // 1 ciclo de reloj
LD R0,Y+
OUT PORTA, R0
...
LD R0,Y+
OUT PORTA, R0

VGA Monocromático

Como mencionaba en párrafos anteriores, utilizaré un puerto de comunicaciones USART para


generar las formas de onda.
El puerto USART de los controladores ATmega cuentan con las siguientes características
interesantes para nuestra aplicación:
(1) Soportan el modo SPI con lo que se eliminan los elementos de sincronismo (start bit, stop
bit) de la línea de datos.
(2) Tienen doble buffer de transmisión, que permite "ir cargando" el contenido del segundo
byte a transmitir cuando todavía se está transmitiendo el primero (el puerto SPI no puede
usarse para esta aplicación por este motivo).
(3) Máximo baudrate soportado: FOSC / 2. Supera ampliamente en frecuencia a la operación
sobre puerto estándar.

En la figura se observa el pin de la transmisión del USART conectado a los 3 pines de los
componentes de color del monitor VGA.
Las resistencias de 75ohms corresponden a las impedancias internas de cada una de las
entradas de color del monitor.
El divisor resistivo formado por cada resistencia de 470ohms y la impedancia interna de
75ohms esta calculado para una caída de tensión de ~0.7V cuando el pin TX del controlador
esté en estado alto (1) y de 0V cuando el pin esté en estado bajo (0).

Los componentes de color para el relleno de una línea horizontal se obtienen a partir de enviar
tantos bytes por el puerto serial como pixeles / 8 haya de resolución; En nuestro caso, para 160
bytes corresponden 20 bytes enviados por el USART del controlador.
A 8 pixeles por byte, el color de cada pixel será negro si el bit correspondiente es 0 y blanco
(0.7v para Rojo, Verde y Azul) si el bit es 1.

La salida de datos por el puerto por cuestiones de tiempo debe necesariamente de escribirse
en ensamblador:

//carga en registro el contenido del puntero [Y] (apunta a la memoria de video)


//incrementa Y en 1 (siguiente pixel)
LD R0, Y+
//transmite por USART el contenido del registro
STS UDR1, R0
//carga contenido del siguiente pixel
LD R0, Y+
//demora hasta que esté libre el doble buffer para el siguietne byte a transmitir
NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP
//transmite por USART el contenido del registro
STS UDR1, R0
...
LD R0, Y+
NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP
STS UDR1, R0

Librería
La librería está compuesta por una clase pública para el usuario (ArduinoVGA.cpp) y una
librería de bajo nivel (ArduinoVGAEngine.cpp) con las rutinas de generación de señal.
La misma es de código abierto (licencia a definir) y puede ser descargada desde la web
de SourceForge desde aquí.

Importante
(1) Por cuestiones de rendimiento es necesario desactivar el Timer0, utilizado para las
rutinas de demora: millis(), micros(), etc. Se provee de una rutina de generación de
demoras como reemplazo (VGA.Delay()).
No es posible utilizar estas rutinas como así tampoco otros periféricos con la
generación de interrupciones activa.
Es una limitación importante de la librería y uno de los costos por no utilizar hardware
externo.
(2) El ~92% del tiempo el microcontrolador estará dedicado a la generación de señales
de VGA. Solo en el ~8% restante (denominado "tiempo de blanqueo") el controlador
estará "relativamente liberado" para ejecutar el código de usuario (1,5mS de 16,7ms) así
que habrá que "ingeniárselas".

Clase pública

class ArduinoVGA {
public:
Init(void);
void PutPixel(unsigned short x, unsigned short y, unsigned char
color);
void PutPixel(unsigned short x, unsigned short y, unsigned char r,
unsigned char g, unsigned char b);
unsigned char RGB2Color(unsigned char r, unsigned char g, unsigned
char b);
void ClrScr(unsigned char color);
void ClrScr(unsigned char r, unsigned char g, unsigned char b);
void ClrScr(void);
void Delay(unsigned short ms);
void Line(short x0, short y0, short x1, short y1, unsigned char
color);
void Rectangle(short x0, short y0, short x1, short y1, unsigned
char color);
void FillRectangle(short x0, short y0, short x1, short y1,
unsigned char color);
void Triangle(short x0, short y0, short x1, short y1, short x2,
short y2, unsigned char color);
void Polygon(short x[], short y[], unsigned char vertexCount,
unsigned char color);
void Circle(short x0, short y0, short radius, unsigned char
color);
void FontLoad(const unsigned char *font);
void FontColor(unsigned char color, unsigned char
backgroundColor);
void GotoXY(unsigned char x, unsigned char y);
void Write(char *s);
void Write(unsigned char x, unsigned char y, char *s);
};

Descripción de los métodos

Init();
Inicializa la librería.

PutPixel(x, y, color);
Pone un pixel en las coordenadas en pixeles [x, y] de color RGB empaquetado [0..255].

PutPixel(x, y, r, g, b);
Pone un pixel en las coordenadas en pixeles [x, y] de componentes de color r [0..7], g [0..7] y b
[0..3].

RGB2Color(r, g, b);
Convierete color de tres componentes [r], [g], [b] a empaquetado de 8 bits [0..255].
El formato del empaquetado (byte) será de bit mas significativo a menos significativo
[BBGGGRRR].

ClrScr(color);
Borra la pantalla pintando con color RGB empaquetado [0..255].

ClrScr(r, g, b);
Borra la pantalla pintando con color de componentes r [0..7], g [0..7] y b [0..3].

ClrScr();
Borra la pantalla (negro).

Delay(ms);
Genera demora en ms.
Como mencionábamos anteriormente, varias de las rutinas estándar de Arduino son
desactivadas durante la inicialización de esta librería por cuestiones de rendimiento.

Line(x0, y0, x1, y1, color);


Dibuja línea desde coordenadas en pixeles [x0, y0] a coordenadas [x1, y1] de color RGB
empaquetado [0..255].

Rectangle(x0, y0, x1, y1, color);


Dibuja un rectángulo con coordenadas de vértice superior izquierdo [x0, y0], [x1, y1] de vértice
inferior derecho y de color RGB empaquetado [0..255].

FillRectangle(x0, y0, x1, y1, color);


Dibuja un rectángulo relleno con coordenadas de vértice superior izquierdo [x0, y0], [x1, y1] de
vértice inferior derecho y de color RGB empaquetado [0..255].

Triangle(x0, y0, x1, y1, x2, y2, color);


Dibuja un triángulo con vértices de coordenadas [x0, y0], [x1, y1] y [x2, y2] de color RGB
empaquetado [0..255].

Polygon(x[], y[], vertexCount, color);


Dibuja un polígono de [vertexCount] vértices de coordenadas {[x[0], y[0]] .. [x[vertexCount - 1],
y[vertexCount - 1]]} de color RGB empaquetado [0..255].

Circle(x0, y0, radius, color);


Dibuja circunsferencia con centro en coordenadas en pixeles [x0, y0], radio en pixeles [radius]
y color RGB empaquetado [0..255].

FontLoad(*font);
Selecciona fuente [font] almacenada en memoria flash.
Se utilizan las fuentes del proyecto arduino-tvout ya incluidas dentro de la librería.

FontColor(color, backgroundColor);
Configura color de fuente y color de fondo. Ambos parámetros color RGB
empaquetado [0..255].

GotoXY(x, y);
Posiciona puntero de escritura en coordenadas en pixeles [x, y]

Write(char *s);
Escribe cadena de caracteres en posición de puntero de escritura.
Modifica la posición del puntero (permite escribir múltiples cadenas una a continuación de la
otra).

Write(x, y, *s);
Escribe una cadena [s] en las coordenadas en pixeles [x, y].
Modifica la posición del puntero (permite escribir múltiples cadenas una a continuación de la
otra).

Instalación / Utilización
La instalación es la "clásica" del IDE Arduino:
Menú Programa -> Include Library -> Add .ZIP Library
Para más información consultar la documentación oficial: Installing Additional Arduino
Libraries

Una vez instalada la librería en el IDE, en un Sketch nuevo o existente se debe proceder con
la inclusión de la librería: Programa -> Include Library -> Arduino VGA.
Los métodos de la clase publica están disponibles vía la instancia global VGA. (VGA.PutPixel,
VGA.ClrScr, VGA.Line, VGA.Circle, etc).

Sketch de ejemplo

#include <ArduinoVGA.h>
#include <ArduinoVGAConfig.h>
#include <ArduinoVGAEngine.h>
#include <font6x8.h>

// codificado dentro del método setup()


// la librería desactiva varias funcionalidades de Arduino por
cuestiones
//de rendimiento.
void setup() {
// inicialización de la librería
VGA.Init();
// carga fuente a utilizar por las rutinas de escritura
VGA.FontLoad(font6x8);
// bucle infinito
while (1){
// dibuja circulo de radio, posición y aleatoria de la pantalla
VGA.Circle(random(RX), random(RY), random(10) + 1, random(2));
// selecciona color de fuente aleatorio y color de fondo negro
VGA.FontColor(random(2), 0);
// escribe cadena "Hola mundo!" en posición aleatoria de la
pantalla
VGA.Write(random(RX), random(RY), "Hola mundo!");
}
}

// la rutina no se llama nunca en este ejemplo


// jamás se retorna de la rutina setup()
void loop() {
}

Otros ejemplos de uso

A continuación un demo para cada uno de los modos.


Ambos forman parte de la librería y pueden ser abiertos como Sketch's de ejemplo de la
librería (Archivo -> Ejemplos -> ArduinoVGA).

VGA Color
Para el modo color algunos fractales y un par de efectos de demoscene en resolución 64 x
64 (no puedo mas por cuestiones de RAM).

Efectos según orden de aparición:


Fuego (básico por cuestiones de RAM)
Fractales
Sierpisky
Mandelbrot
Prueba de fuentes
Shadebos

VGA Monocromático
Para el modo monocromático programé una librería de gráficos de tortuga para nada pulida
para hacer algunos fractales recursivos.
La resolución elegida en este caso es de 160 x 120.

Efectos según orden de aparición:


Fractales
Arbol
Dragón
Sierpisky
Koch
Prueba de fuentes
Bolas sobre curvas de Lissajous
Figuras geométricas

Conclusiones
 El resultado es bastante bueno considerando las importantes limitaciones producto de
la memoria RAM y la frecuencia de reloj.
 En modo monocromático cuenta con resolución aceptable y es relativamente utilizable
para aplicaciones varias.
 En modo color es bastante limitado (resolución), es con fines de experimentación y no
se le puede pedir mucho mas.
 Tiene unos cuantos detalles que estimo iré puliendo con el tiempo.

En lo próximo
Con la misma idea voy a diseñar un shield con un microcontrolador mas potente
(tentativamente algún de la familia ATxmega), memoria RAM onboard y algún que otro chiche,
comandado vía SPI por un Arduino y su correspondiente librería de control.
Agregaré alguna que otra función a la librería de interfaz (ArduinoVGA).
Crearé repositorio con el contenido aquí presentado (Sourceforge probablemente).
Trabajaré la librería de bajo nivel (generación de señales) con el fin de exprimir al máximo el
hardware y poder mejorar levemente la resolución en los diferentes modos.
Corregiré pequeños detalles de la implementación.
Documentaré el código fuente.

Doy por terminado este artículo, espero el contenido haya sido de utilidad.
Cualquier duda no duden en consultar, nos vemos en la siguiente entrada.
Saludos, Luis.-

Arduino Pong
Vemos cómo montar una
consola de videojuegos
retro con Arduino y unos
pocos cables. Con sonido y
salida de video para
televisor.
Introducción

Después de la construcción de las dos consolas arcade


para emulación de videojuegos retro que vimos en
un artículo pasado, comprobamos que la pantalla en
modo analógico (vídeo compuesto) que montamos en
una de ellas no estaba a la altura en cuanto a la calidad
de imagen que Raspberry Pi puede ofrecer. Pensando en
utilizar esta consola de un modo alternativo, se nos
ocurrió que sería una buena plataforma para
experimentar con Arduino y las posibilidades que tiene
de generar una señal de video. Veamos como...

Conexiones

Antes de empezar tenemos que preparar la conexión de


la salida de video compuesto. La librería TVOut que
vamos a utilizar, y que describiremos más adelante,
emite la señal de sincronismo en el pin D9 y la de
luminosidad de los píxeles en el pin D7 de Arduino. La
documentación nos explica cómo construir un sencillo
mezclador y conversor digital-analógico con dos simples
resistencias de 470Ω y 1kΩ conectadas de la siguiente
forma:
Lo que traducido al mundo real queda así (ojo a la
perspectiva de la foto que desalinea las conexiones de
las resistencias; están pinchadas en las columnas de la
breadboard correspondientes a los pines D7 y D9 de
Arduino). Como vemos en la foto, hemos utilizado un
Arduino Nano, pero serviría cualquiera de los Arduino
basados en ATmega m168 o m328:
TVOut

Para producir la señal de video en Arduino, vamos a


utilizar la fantástica librería TVOut desarrollada por
Myles Metzler. La documentación se encuentra en el
sitio donde se distribuyó originalmente la librería, es
decir aquí. Pero para empezar lo mejor es echar un
vistazo a los ejemplos que hay en el propio repositorio
como éste para el estándar PAL. Cargando el sketch
DemoPAL en el Arduino y conectándolo como hemos
visto antes a un televisor, podemos ver lo siguiente:

Vídeo compuesto

Producir una señal de video compuesto en blanco y


negro es relativamente sencillo. Sólo hay que generar
una señal de 1V de amplitud que señalice los principios
de frame y de línea (sincronismo vertical y sincronismo
horizontal respectivamente) y que dibuje las líneas
traduciendo directamente la luminosidad en un nivel de
señal más alto o más bajo para representar los píxeles
más o menos brillantes.

Fijándonos en el código de la demo anterior, hacemos un


pequeño sketch que inicializa la librería en modo PAL y
que dibuja la mitad izquierda de la pantalla en blanco y
la mitad derecha en negro, cosa que nos facilitará el
estudio de la señal. La inicialización en modo PAL por
defecto, supone utilizar una matriz de píxeles de 128 en
horizontal por 96 en vertical para la escena. Así pues
nuestro sketch dibujará un rectángulo de 64x96 píxeles
comenzando desde la esquina superior izquierda de la
pantalla (0, 0):

#include <TVout.h>

TVout TV;

void setup() {
TV.begin(PAL);
TV.draw_rect(0, 0, 64, 96, WHITE, WHITE);
}

El resultado en pantalla es el siguiente:


Hemos conectado un osciloscopio a la misma salida de
video compuesto que introducimos en el televisor.
Vamos a analizar sobre una captura del osciloscopio las
partes de que se compone:
Lo que estamos viendo aquí corresponde a dos lineas
completas en pantalla. La señal PAL se compone de 625
de estas lineas. Como vemos en el televisor, una gran
cantidad de estas señales serán distintas al principio y
al final ya que formarán parte de los bordes superior e
inferior. Aquí vamos a centrarnos en un par de lineas de
la parte central que contengan parte de blanco. Hemos
marcado 4 zonas que pasamos a explicar:

 A: La señal empieza haciendo un pulso negativo de


aproximadamente -0,3V. Esto es la señal de sincronismo
horizontal, lo que le indica al televisor que tiene que pasar
a dibujar la siguiente línea horizontal. Es el equivalente en
el mundo del vídeo al retorno de carro en los ficheros de
texto o en las antiguas máquinas de escribir. Hemos
desplazado la señal hacia abajo de manera que el
siguiente escalón que representa el valor de luminosidad
mínimo (negro) se asimile a 0V ya que resulta más
intuitivo así, pero los televisores sólo tienen en cuenta las
diferencias relativas, por lo que es indiferente dónde
situemos la referencia del 0.
 B: Entramos en la linea base de la señal, la que hemos
asimilado al valor 0V. Esto equivale a la luminosidad
mínima, es decir, al color negro. Vemos negro al principio
de cada linea porque la representación de la matriz de
píxeles o escena que maneja TVOut incluye un borde
negro alrededor. Así la zona B corresponde al borde
izquierdo.
 C: La señal se eleva hasta los 0,7V (de manera que entre
pico y pico - A y C - tengamos alrededor de 1V). Esto
corresponde al valor de luminosidad máximo, el color
blanco. El tiempo que la señal permanece en este valor
máximo se corresponde con la longitud de la parte de la
linea que dibujamos en blanco.
 D: Volvemos a negro. Aquí estamos viendo en realidad dos
partes de la imagen que se confunden. Primero la parte de
la matriz de píxeles que corresponde a la linea negra
(mitad derecha de la pantalla), y después el borde
derecho, que también es negro.

Existe al principio de cada frame otra señal que le indica


al televisor que además de empezar una nueva linea
(como lo que indica el pulso de sincronismo horizontal),
tiene que empezar un nuevo frame, es decir volver al
punto de partida que se encuentra arriba a la izquierda.
Esto se hace con otra señal especial
denominada sincronismo vertical que aquí no vamos a
ver por no extendernos demasiado. Baste decir que
consiste en una serie de pulsos parecidos al pulso
de sincronismo horizontal pero más anchos y
numerosos.

Si modificamos el programa para dibujar toda la escena


de la pantalla en blanco (TV.draw_rect(0, 0, 128, 96, WHITE,
WHITE);), la señal correspondiente a las líneas vista en el
osciloscopio se modifica consecuentemente:
Vemos claramente cómo la zona C se ha hecho el doble
de ancha, a costa de la zona D que ahora sólo incluye la
zona de borde.

Como puede verse, generar una señal de video se reduce


a situar unos valores de tensión determinados en los
pines de Arduino respetando unos tiempos muy precisos
para que la imagen tenga perfiles rectos y no produzca
parpadeos o fallos de sincronismo. Internamente la
librería TVOut desempeña esta tarea usando
interrupciones, de manera que nos deja a nosotros el
resto del tiempo para dibujar nuestra escena del juego
en la matriz de píxeles que representa la pantalla.

Videojuegos
Una vez que hemos aprendido un poco sobre las
posibilidades y el funcionamiento interno de Arduino y la
librería TVOut, podemos programar sencillos juegos. La
propia librería TVOut incluye soporte para sonido si
conectamos un buzzer al pin D11 de Arduino. Para no
hacer este post muy extenso y convertirlo en un taller
sobre desarrollo de videojuegos, vamos a limitarnos a
cargar juegos ya programados por otras personas por
medio de esta misma librería. De todas formas, dejamos
por aquí este enlace que además de a TVOut, hace
referencia a una librería para gestionar controles de
juego (gamepads) que sería otra parte importante para
desarrollar un videojuego sobre Arduino. En el enlace
anterior encontramos también otro enlace hacia una
lista de juegos que pueden instalarse en el montaje
hecho con Arduino presentado en este artículo. Allí
podemos encontrar juegos como los siguientes:

Modificación EmuPi

Como hemos comentado en la introducción, todo esto


empezó pensando en utilizar la segunda unidad de EmuPi
construida (a las que dedicamos este artículo) con algo
más sencillo que Raspberry Pi, es decir, que utilizara una
salida de video compuesto de manera nativa. Así pues el
paso final fue integrar el montaje anterior hecho con
Arduino en una de las unidades EmuPi. Respecto a lo que
hemos visto en el artículo, sólo quedaba mapear el stick
y uno de los pulsadores para que se convirtieran en los
controles de juego. Por defecto la
librería Controllers que se encarga de gestionar el
controlador de juego utiliza los siguientes pines:

 Izquierda: D3
 Derecha: D2
 Arriba: D4
 Abajo: D5
 Disparo: D10

Por tanto sólo tuvimos que puentear los pulsadores


correspondientes de la EmuPi a los pines de Arduino. La
librería establece una resistencia pullup en los pines que
utiliza, de manera que cuando el pin se conecta a GND
es cuando se detecta la pulsación y se libera la
pulsación cuando el pulsador se abre. Por tanto, los
pulsadores los conectaremos con un cable al pin
correspondiente de Arduino y con otro a GND.

El resultado final:

PD: Comentar para terminar que el artículo se denomina


Arduino Pong porque el primer proyecto que
encontramos que implementara un juego con salida de
vídeo para Arduino se basaba en este tipo de juego y fue
así como bautizamos nuestro propio proyecto.
Introduction
This project is to create a circuit that faithfully (as much as possible)
emulates all 4 paddle games on the old TV game "pong" chip - the AY-
3-8500. It uses an Atmel AVR processor - the ATmega328 (or
ATmega168) as used on the Arduino UNO (etc) boards. It can either
be created using individual components (very cheaply, with small part
count) or can use an Arduino board with additional components
attached.

The idea behind this project was to produce a 4 play game as accurate
as possible to the originals.

I am an avid collector of the old consoles (see here) and have detailed
knowledge as to the circuitry and functionality of the original arcade
Pong (which I have also rebuilt).

I have seen other simple implementations of Pong on the Arduino, so I


knew it should be possible to recreate.

The popular 4/6 game consoles normally used the AY-3-8500 (PAL
version) or AY-3-8500-1 (NTSC version). I have several of the PAL
versions so I was able to do an accurate side-by-side comparison of
what I did with the original to ensure all games are as faithful as
possible.

The NTSC version is currently based on datasheet information, but will


be compared and updated to the actual AY-3-8500-1 chip when it
arrives.

This works properly on new and old TVs via the A/V inputs.

NOTE: MY DIAGRAMS BELOW SHOW BLACK OBJECTS ON A


WHITE BACKGROUND FOR CLARITY AND EASE OF READING
THIS PAGE. IN REALITY, THE DISPLAY IS WHITE OBJECTS ON A
BLACK BACKGROUND. ORIGINAL MANUALS ALSO TENDED TO
ADOPT THIS WAY OF SHOWING SCREENS.

Specification
On the AY-3-8500, six games are possible : Tennis, Soccer, Squash
and Solo, along with two shooting games.
I did not emulate the shooting games (although possible to do so)
because additional gun hardware needs to be built/created and these
will ONLY work with older CRT televisions. The gun picks up light
flicker when focused on a bright area of the screen (ie. the target). The
gun circuitry then registers a "hit" pulse if the trigger button is pressed
while the light is detected. The new flat TVs don't produce the 50Hz
flicker like the older CRT ones so no signal is available for the gun to
pick up.

The original could switch between games without interrupting play -


this is maintained in my emulated version.

Also implemented:
Bat sizes - small or large.
Angles - 20 degrees (easy) or 20/40 degrees (hard) and will deflect the
ball based on which part of the paddle is hit
Ball speed - slow or fast.
Reset button.
Automatic serve (no manual option at the moment, but can be added
easily if needed).
Game selection - instead of a multi-way switch, I decided to use a
single pushbutton to change games.
Attacking paddles allow pass-through with deflection.

The start point and direction of the ball (after reset) is the same as the
original.

The ball speed and bat sizes can be changed instantly at any time, as
on the original. The bat angles can also be changed any time and will
be affected when a paddle is hit - again, same as the original.

The original playfield display consisted of 230 vertical lines (PAL). The
clock was 2MHz, giving a pixel duration of 0.5uS. There are 73 or 79
pixels per line (see below).

The ATmega328 only has 2K of RAM, and a full bitmap of 230 x 80 (ie.
10 bytes per line) requires 2300 bytes so cannot fit.

The vertical resolution is therefore halved to 115 lines, each taking up


2 scan lines on the screen, restoring the original size. This does not
affect the displayed field because the original objects on the screen
occupy multiples of 2 lines. The paddles on the original chip would
move in 2 line increments anyway, so that is the same here. Note: The
ball is drawn at the FULL resolution of the full 230x80 ensuring a
perfectly smooth movement (see below). The left and right edges
require higher resolution and my code achieves this (see below).

Important differences between the original 1976


and 1977 game chips
In order to create an accurate reproduction, I carefully followed the
datasheets and also took several photographs of the screen produced
by the original chip.

What I need to point out here, though, is that I discovered a while ago
that there are actually TWO versions of the AY-3-8500 PAL chip. The
chip must have been partially redesigned in 1977 because the 1976
versions have the following differences.

1. The 1976 version of the playfield is wider - 79 pixels instead of 73 on


the 1977 version.
2. The 1976 bats are narrower than the 1977 version, even though the
datasheet states they are 0.5uS (1 pixel) wide.
3. The 1976 version has a smaller ball (3 lines high) than the 1977
version (5 lines high, and wider).

My photos of the screens (inverted colours) of the 1976 (left) and 1977
(right) versions of the original chip are shown here:
To see the differences in more detail, here are my actual screen
photos (same scale) of the two versions merged on top of each other
(showing all 5 bats on each):
The 1976 chip display is shown in green and the 1977 one is shown in
red. Both use the same 2MHz clock, so the pixels across the top and
bottom of the window have the same placement.

In order to create a faithful reproduction, I either had to pick one


version, or have a suitable option to switch between them. I did the
latter, so what I present here can emulate either of the chips.

This therefore required some conditional code to be introduced to the


drawing and incidence testing (ball hitting the bat or the wall).
Additionally, the X-placement of the bats, scores and walls are no
longer constant so needed to be put into variables that are set
depending on the chip version chosen.

Important differences between the original PAL


and NTSC chips
There are some differences which will be visible:

1. The scores on the NTSC version occupy 20 scanlines (4 lines per


block) but the PAL version occupies 30 scanlines (6 lines per block) -
as on the datasheets. The NTSC numbers are noticeably shorter.
2. The vertical net/centre line on the NTSC version is SOLID but the
PAL version is DOTTED (as can be seen on the diagrams above).
3. The NTSC picture has fewer scanlines, so a lower vertical
resolution. This results in the ball appearing to travel at slightly steeper
angles on the NTSC version because each step therefore has a larger
Y displacement. Additionally, this lower resolution makes the bats
appear longer.
My recreation shown here FULLY implements the differences
experienced on the two chips as shown below...

PAL on the left, NTSC on the right.

Differences between the original and emulated


version
The original chips are NOT processors - they are combinational logic -
gates/flip-flops within one chip. As a result, some signal paths go
through more gates than others, so some of the signals are slightly
stretched or delayed when compared to others. The ATmega version,
however, has the same timing for all, so there is a small difference, but
not noticeable unless you do a close side-by-side comparison of the
screens produced.

Redrawing the screen


Drawing the playfield

Due to the height of the playfield (taking up most of the screen) there is
very little time left on each screen to do the processing, so I needed to
try various methods to get everything redrawn in the few milliseconds
that is available.

First option is to note the difference between the previous and new
positions of the bats and the ball and clear any pixels no longer to be lit
and then light the new ones as needed.

Second option is to completely clear the screen and re-draw everything


each frame.

After trying both, the second option is actually better - the screen clear
is very quick and ensures that all artefacts are cleared properly
ensuring every screen is clean. There is sufficient time to do this
between each frame.

Edge drawing

The edges of the screen are drawn internally as solid vertical lines.

The sides of the screen on the original chips are, however, dotted
(every other line is blank) - this would require a vertical resolution of
230 pixels to do it. Each line occupies two scanlines so I was able to
achieve the same effect by destructively modifying that line on the
screen after one scan line was done so that the left and right pixels are
cleared. So, when the second scanline is drawn for that same line the
gap appears.
The screen is completely redrawn each frame so this destructive
method works with no issue.

Drawing the ball (smooth movement)


The 2K limitation on the ATmega328 results in a limitation to the
resolution available, so a 80x115 screen is reserved for the playfields.

115 vertical pixels with a pixel height of 2 lines per pixel gives a screen
that is exactly the same as the originals.

However, to ensure the ball speed is exactly the same as the original
results in some jitter when using this resolution as the ball normally
works at twice the vertical resolution of 80x230 (note: same horizontal
resolution, however) as is shown below...
A smooth ball traversal required a higher resolution. However, 2K
bytes is not sufficient to support the required resolution for smooth ball
movement. To achieve this, what is needed is for the screen resolution
to remain at 80x115 for the main part of the screen but switch to
double the resolution for the area occupied by the ball. This is what I
did. I created an additional 5 line screen which would be displayed at 1
line per pixel instead of 2 lines per pixel, and this would be dynamically
positioned up or down the screen to enclose the ball. This way, it was
then possible to create the ball of the correct height - 3 or 5 lines
depending on whether the 1976 or 1977 chip is being emulated.

During each screen refresh, the ball routine copies a section of the low
res screen into the high res screen then draws the ball in there. The
display render code is then switched between the two lines per pixel
main field to the 1 line per pixel ball area then switched back to
complete the screen. This is illustrated in the following diagram:
This implementation now produces a perfectly smooth ball movement,
faithfully recreating the original.
Bat and ball intercept
As in the original, the angle of bounce or deflection depends on which
part of the bat is hit. The angle option switch defines if ball angles are
restricted to two angles (+/-20 degrees) or whether there are 4 possible
angles (+/- 20 degrees and +/- 40 degrees).
The bat is divided into four segments which define which of the angles
the ball will leave.
The bat will either bounce the ball if the ball is travelling towards the
player or deflect the ball if it is travelling away from the player.

The diagram below illustrates this behaviour:

The ball speed (x and y increments) match the original specifications,


and are shown below:
Sounds
The original produced 500Hz beeps when hitting the walls, 1KHz
beeps when hitting the bats and 2KHz beeps when scoring. This has
been faithfully reproduced. The connection diagram below shows the
output going to a line-out socket, but will readily drive a small sounder.

The software
The video display driver is a cut-down TV Out library (with sound), so
full credit for this goes to the original author. The code for this is
attached to the end of my game code because I needed to modify the
existing library specific for this game. All code can be maintained in
one place. I have modified it as I needed my own screen resolution to
match the original chips. All code not relevant has been removed. I
have also renamed the methods to match my naming convention.
Comments have been removed (most methods are self-explanatory
based on the name) but the original library can be obtained here if you
require more details.
As mentioned above, the playfield that is being displayed is either
73x115 or 79x115 pixels. The TV Out library requires the x dimension
to be divisible by 8, so a suitable size is 80x115 pixels for both
versions. I wrote an updated display line handler to display the pixels
at the correct width needed to match the original. Additionally, I
updated the renderer to auto-switch resolutions when drawing the ball
area, as mentioned above.

The ball y-coordinate has been scaled by a factor of 2 so that the


speed matches the original. So, in the code there is a virtual y
coordinate and the actual ball y coordinate. The double-resolution
screen allows the ball to be positioned at a required vertical resolution
of twice the rest of the screen.

All coordinates have the (0,0) origin being the top-left corner of the
displayable playfield.

A summary of each of the methods used in my code is shown below.


Please refer to the actual code for more detail.
setup
Standard Arduino entry point.
Sets the analogue and switch input pins
Call the reset method to initialise defaults.
Initialise and start the video display

reset
Resets the score, ball position, and ball velocities ready to start a game.
The squash player is reset to 1.
Attract mode is turned off.

loop
Standard Arduino program run loop, after setup is complete.
This is the main gameplay loop and is synchronised to run once per frame.
Wait for synchronisation
Check the chip version pin to set the appropriate sizes and positions of the playfield and all
displayable objects. (configureSelectedChipVersion method)
Clear the screen
Set the game (setGame method)
Draw the scores (drawScores method)
Draw the game boundary (drawBoundary method)
Configure the running game (setGameOptionsFromSwitches method)
Draw all paddles that are needed for the current game (drawPaddles method)
Check if the reset switch is pressed, if so, reset the score and come out of the loop now
Move the ball to the new position (new pos=old pos + velocity step)
If the new position of the ball touches a wall then beep and swap the velocity direction to make it
bounce
If the new position of the ball touches the left or right edge of the playfield then:
if the ball is visible then
a score is incremented if no player has yet reached 15
a beep is sounded
the ball is repositioned HORIZONTALLY to be 1/3 of the playfield in from where it touched
the edge. This gives a short time before it comes back in to play.
the ball is made invisible
if 15 is reached then gameplay is over (switches to attract mode)
if the ball is invisible then
it is made visible
If the ball is visible then draw the ball (drawBall method)

setGameOptionsFromSwitches
Read the ball speed switch and adjust the ball velocity if needed
Read the angles switch and assign the angles flag ready for the next bat hit
Read the paddle height switch and assign the paddle height variable to the required size

drawPaddles
Read the paddles
Convert the paddle value to vertical screen coordinates
Draw the paddles (drawPaddle method) - two for tennis and squash, four for soccer and one for
solo. This routine will also check if the ball is in contact and make it bounce/deflect as needed.

configureSelectedChipVersion
Read the version selection line and set the position, width and field size variables to match.

drawPaddle
The passed paddle number (1..5) will determine the x coordinate to use. The passed paddle
value determines the y value.
Draw the paddle
If the ball is visible and not in attract mode then a check is done to see if the ball is touching the
current paddle.
If it is touching then
beep
if the paddle is a defending paddle (based on paddle number and ball direction) then reverse
the x velocity to make it bounce.
if the paddle is an attacking paddle (ie facing the same way as the ball travel) then the x
velocity is not changed, allowing it to pass through (being deflected as needed)
the y velocity (direction) depends on where the paddle was hit and may be one of 2
(up/down) or 4 (two speed up, two speed down) velocities

drawBall
Copy the ball part of the playfield to the double resolution area.
Set either 3 pixels (1x3) on the 1976 version or 10 pixels (2x5) on the 1977 version at the ball
x/y location on the double resolution screen area.

setBallPixel
Used by the drawBall method to set a pixel on the double resolution area.

drawDigit
Draw a bitmap of a digit specified at the location parameters

drawScores
Draw each score digit by calling the drawDigit method. Don't draw the 10's digit if score is less
than 10

drawBoundary
Draw the top and bottom lines
Draw the goal walls if soccer selected (game 2)
Draw a solid wall on the left if squash or solo selected (game 3 and 4)
Draw a dotted line down the middle of the field if tennis or soccer selected (game 1 and 2)

setGame
Check if the game select button has been pressed for 4 consecutive frames (80mS) to eliminate
contact bounce.
If so, set the game number to the next game

other
I have defined the required bitmaps for the 7 segment digits and are
stored in program memory.

Source code and hex files


The Arduino source code (sketch) plus a hex file (for programming
directly) is HERE.(game files)
Update 30th Jul 2013: Change the "version select" from PB5, ATmega pin 19 to PB4, ATmega
pin 18 to avoid LED boot flash conflict if using the Arduino bootloader. Ensure you follow the
updated schematic or pinout (below).
Update 25th August 2013: PAL/NTSC selection, plus faster drawing of the borders and centre
line, needed for the faster NTSC timings.
Update 20th July 2018: Update for latest Arduino compiler (1.8.5) compatibility.

You do not need Ardhuino software or hardware/bootloader - you can


program the ATmega328 directly using the HEX file if you have a
suitable programmer. You can also compile the code for an
ATmega168 as it is small enough to fit.
If programming using the HEX file manually, ensure the programmer
will set the correct chip configuration - XT clock, divide by 1, no clock
out.

The hardware
Here is a diagram showing the ATMEL and ARDUINO pin designations
for the ATMEGA328. Also shown is the pin function as used within this
project.
Update 30th Jul 2013: Pinout and schematic - change the "version select" from PB5, ATmega
pin 19 to PB4, ATmega pin 18 to avoid LED boot flash conflict if using the Arduino bootloader.
Ensure you have the latest code (above). Additionally, potentiometers turned around so that
clockwise = up, anticlockwise = down, as on original consoles.
Pins that do not have a game pin designation are to be left
unconnected.

Complete wiring required for this game is shown here:

The audio components are set for a standard line-input. However,


these can be replaced with whatever is required to produce the sound
for example a peizo sounder or speaker in series with a 100R resistor.
The potentiometers shown are 47K, but can be anything between 10K
and 100K or more. Larger values will result in a lower current drain.

All switch inputs are pulled high with internal resistors, so only need to
switch to ground.

The power can be anything from about 3V to 5V, so will run off 2 or 3
AA batteries, as an example.

The dotted connection will switch the chip to emulate the 1976 (wider)
version. This can be wired, ignored or connected to a switch.

The switches are optional, and the required pins can either be left open
or tied to ground if that functionality change is not needed.

Below is a prototype/test construction. This is using a peizo element in


series with a 220R resistor as the sounder. The two buttons are reset
and game selection. As you can see very few components are needed
to make a complete game.

How to play
If you are familiar with the original 70's games then you already know -
gameplay here is identical.
If you are not, then here are a couple of scans from the General
Instruments "GIMINI" datasheets showing the principles involved...
Screenshots
Here are screenshots of my ATmega / Arduino PAL version running on
a PAL monitor. The diagonal streak is the ball path during exposure.

Chip mode set to new (1977) version:

Chip mode set to old (1976) version:

Patterning is not visible on-screen - it is merely due to the scan lines


and is a common artefact when resizing TV images on a computer.
These are actual screenshots from an earlier version. New version has
more accurate left/right edges as described above. Picture to be
updated soon.

You might also like