Professional Documents
Culture Documents
TVOUT Explicacion
TVOUT Explicacion
Versión inicial.
R2 (2 de mayo de 2010):
-sistema reducido al uso de una interrupción, esto permitirá agregar sonido y/o
-Se agregó soporte para pausar video; la salida aún emite h sync
-pausa: pausa_render();
-resume: resume_render();
pronto.
R3 (9 de mayo de 2010):
-Nota:
(_RESOLUCIÓN_HORIZONTAL/8)*_RESOLUCIÓN_VERTICAL
-start_render(modo,hres,vres)
-Se agregó la función de inversión de gijs (foros arduino.cc) que invierte toda la
pantalla
-Se agregó una función de mapa de bits de pantalla completa, se ampliará muy
limitada.
byte, toda la impresión de caracteres ahora está restringida x que son múltiplos
de 8
-encapsoló todas las variables globales en estructuras. uno para la parte del
video y
-Se corrigió el número de líneas en el modo PAL, gracias amante en los foros
de arduino.cc.
-Se agregó la función de inversión de gijs (foros arduino.cc) que invierte toda la
pantalla
-Se agregó una función de mapa de bits de pantalla completa, se ampliará muy
limitada.
-start_render(modo,hres,vres)
-Limpié el código de renderizado.
-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().
-Se eliminó draw_box reemplazado con draw_rect, más rápido y más fácil.
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[] .
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<final;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();
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:
void loop(){
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.
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>
VOID SETUP()
{
INTRO();
VOID LOOP()
{
leer_sensor();
escribir_datos();
GRAFICO_SENSOR(INT(TEMPERATURE),FALSE);
GRAFICO_SENSOR(INT(HUMIDITY),TRUE);
TV.DELAY(1500);
}
//Dibujamos
TV.draw_line(2,93,2,14,WHITE); //eje Y
TV.draw_line(2,93,110,93,WHITE); //ejeX
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();
}
#include <VGAX.h>
void setup() {
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).
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:
Como output nos dará el array de bytes de la imagen, junto con definiciones
sobre el alto, ancho y el ancho en bytes (BWIDTH).
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.
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);
}
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];
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);
}
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.
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.
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).
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.
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).
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:
VGA Monocromático
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:
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);
};
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.
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>
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).
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.
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
Conexiones
Vídeo compuesto
#include <TVout.h>
TVout TV;
void setup() {
TV.begin(PAL);
TV.draw_rect(0, 0, 64, 96, WHITE, WHITE);
}
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
Izquierda: D3
Derecha: D2
Arriba: D4
Abajo: D5
Disparo: D10
El resultado final:
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).
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.
This works properly on new and old TVs via the A/V inputs.
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.
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.
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.
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.
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.
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.
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 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.
All coordinates have the (0,0) origin being the top-left corner of the
displayable playfield.
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.
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.
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.
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.