Software.

2009

BLOQUE 2: SOFTWARE.

En este bloque, simplemente se explicara el funcionamiento básico de todo el sistema para desde cómo leer un pulsador, mostrar letras en la pantalla, o comunicarse con el ordenador y php.

Puedes descargar este mismo documento en pdf

Todo el código fuente está en http://pastebin.com/f3713bc9f

Como ya sabemos, la pantalla de mi proyecto, 4 onLine, esta compuesta de 4 pantallas mas pequeñas de 8*8, es decir 16*16. El gran problema, fue cuando descubri que el funcionamiento básico con el cual pensaba que todo funcionaría bien, pues fue un fiasco.

A mi entender, para mostrar cualquier imágen por la pantalla de arduino, hace falta suministrar corriente (+) a una columna (o a varias para los colores) y tambien suministrar GND a las filas. Con esto consigues encender cualquier cosa en la pantalla. Pues bien, demostrado (y sólo hace falta un poco de lógica) que es verdad que puedes mostrar una amplia variedad de cosas, pero no puedes mostrar TODO.

Y esto lo descubri, cuando estaba con el generador de código binario (ver video) e intenté crear una letra y mostrarla. No es posible de mi método, por una sencilla razón. Si activas una fila y una columna, se encendera un píxel, pero habra partes de la pantalla por la cual circule corriente (negativa y positiva). El problema está cuando enciendes otro píxel (con su fila y columna, negativa y positiva respectivamente) y ésta fila en la cual circula negativo, y la columna del píxel anterior (en la cual circula positivo) se curzan. Entonces surge la magia.

Se enciende otro píxel aunque tu no quieras. Estube haciendo pruebas y finalmente ví que no llegaría a ninguna parte con esto, asi que decidí cambiar el método para mostrar por la pantalla, a uno que pudiese mostrar, ahora sí, CUALQUIER cosa de cualquier color admitido por la pantalla.

Al utilizar shift registers (74HC595) he diseñado varias funciones para poder mostrar datos por las pantallas.

Mi forma de gestionar las funciones ha sido por niveles. Se crean unas funciones básicas muy rudimentarias, las cuales son llamadas por otras funciones mas complejas, y éstas por otras que aun son más complejas.

El resultado, como más adelante veremos, es una función que se le pasa una palabra y la muestra por la pantalla, ocupandose de correr las letras hacia un lado, cargarlas desde la EEPROM, y mostrarlas. Pero primero vamos a ver el funcionamiento básico para mostrar cosas por la pantalla.

void mostrar(byte horizontal_1_8, byte horizontal_8_16, byte vertical_rojo_1_8, byte vertical_rojo_8_16, byte vertical_verde_1_8, byte vertical_verde_8_16)

{

digitalWrite(filaLatchPin, 0);

digitalWrite(colLatchPin, 0);

shiftOut(colDataPin, colClockPin, vertical_rojo_1_8); //rojo 1-8

shiftOut(filaDataPin, filaClockPin, horizontal_1_8); //horizontal

shiftOut(colDataPin, colClockPin, vertical_rojo_8_16); //rojo 8*16

shiftOut(filaDataPin, filaClockPin, horizontal_8_16);

shiftOut(colDataPin, colClockPin, vertical_verde_1_8); //verde 1-8

shiftOut(colDataPin, colClockPin, vertical_verde_8_16); //verde 1-16

digitalWrite(colLatchPin, 1);

digitalWrite(filaLatchPin, 1);

}

void shiftOut(int myDataPin, int myClockPin, byte myDataOut) {

int i=0;

int pinState;

pinMode(myClockPin, OUTPUT);

pinMode(myDataPin, OUTPUT);

digitalWrite(myDataPin, 0);

digitalWrite(myClockPin, 0);

for (i=7; i>=0; i–) {

digitalWrite(myClockPin, 0);

if ( myDataOut & (1<<i) ) {

pinState= 1;

}

else {

pinState= 0;

}

digitalWrite(myDataPin, pinState);

digitalWrite(myClockPin, 1);

digitalWrite(myDataPin, 0);

}

digitalWrite(myClockPin, 0);

}

Éstas son las dos funciones clave. La función shiftOut, se encarga única y exclusivamente de enviar bit a bit, los datos a todos los shifts registers. Pero para que funcione, la función superior, void mostrar() gestiona varias llamadas a shiftOut para poder mostrar algo por la pantalla.

La función mostrar tiene un funcionamiento muy simple. Cierra los latchPin de los shift registers. Envia bit a bit la información a cada uno de los shift registers, mediante la función shift out, y al abrir los latchPin, se muestra aquello que los shift registers han recibido en cada una de sus patillas (8). Si reciben 0, envian LOW. Si reciben 1, envian HIGH.

Para que la función mostrar() sea un poco mas entendible por los seres humanos, los parametros de entrada, que no son nada mas que los bits para cada fila y columna de cada color, los he etiquetado de una manera mas coherente.

byte horizontal_1_8, byte horizontal_8_16, byte vertical_rojo_1_8, byte vertical_rojo_8_16, byte vertical_verde_1_8, byte vertical_verde_8_16

En este caso, por ejemplo, byte horizontal_8_16, es el byte de la fila 8 a 16 (por eso dice horizontal). Simlemente hay que pasar un byte (8 bits) del tipo B11101001 para que mueste exactamente lo que hay. 0 apagado, 1 encendido.

Pero antes he explicado el gran problema de mostrar información de éste método ya que para imágenes y formas complejas, hay interferencias en los píxeles. Es por eso que decidi implementar un nuevo método.

El funcionamiento de éste nuevo método también es muy simple. Me baso en cualquier pantalla de un dispositivo moderno, aunque no estoy del todo seguro que sea asi.

Consiste en barrer una serie de píxeles de arriba a abajo de la pantalla, de una forma muy rápida, asi se podrán mostrar todas las formas, posiciones y colores posibles. En realidad no hay movimiento, pero es nuestro cerebro el que ve una secuencia de imágenes continua, y ve una letra a quieta, pero realmente sólo se esta mostrando una pequeña parte de esa letra. Asi que me pongo manos a la obra, y creo la función barrer().

Se puede barrer de muchas, muchísimas formas, pero para evitar problemas, decido utilizar la menos arriesgada, eso si, la mas larga y la que mas espacio ocupa.

Para barrer cualquier cosa, utilizo un array de bytes de 32 bytes. Es decir, que hay

32 bytes de 8 bits cada byte. Esos 8 bits representan media pantalla en horizontal, es decir, representan una fila.

La primera fila son 16 bits, es decir el array[0] y el array[1]. La siguiente serán los dos bytes que siguen y asi hasta llegar a 31. (array empieza desde n-1 posiciones). Es decir que hay 32 posiciones, y se leen de 16 en 16 bits, si cada variable es de 8, por cada fila se leen 2 bytes o 16 bits.

Este sistema lo diseñe especialmente para mostrar letras, todas del mismo color, asi que sólo se podria mostrar cualquier forma en la pantalla, pero TODA del mismo color. Luego ya diseñé otra cosa para poder mostrar cualquier cosa en cualquier color. La función barrer() es la mas utilizada en el programa ya que siempre que se muestra algo por pantalla, se llama a ésta funcion o a otra parecida a ésta.

void barrer(int color,int medida, byte arreglo[]) //0 rojo 1 verde 2 naranja

{

int contador =0;

int ciclos = 0;

for(int y=0; y<medida; y++) {

if(ciclos<8)

{

if(color == 0)

{

mostrar(num[y+1], nulo, arreglo[contador], arreglo[contador+1], nulo, nulo);

}

else if(color == 1)

{

mostrar(num[y+1], nulo, nulo, nulo, arreglo[contador], arreglo[contador+1]);

}

else

{

mostrar(num[y+1], nulo, arreglo[contador], arreglo[contador+1], arreglo[contador], arreglo[contador+1]);

}

}

else

{

if(color == 0)

{

mostrar(nulo, num[y-7], arreglo[contador], arreglo[contador+1], nulo, nulo);

}

else if(color == 1)

{

mostrar(nulo, num[y-7], nulo, nulo, arreglo[contador], arreglo[contador+1]);

}

else

{

mostrar(nulo,num[y-7], arreglo[contador], arreglo[contador+1], arreglo[contador], arreglo[contador+1]);

}

}

ciclos+=1;

contador+=2;

if(ciclos == 16)

{

ciclos = 0;

}

}

}

Bien el funcionamiento se basa en la función de mostrar. Se enciende la primera fila, se va al array que se ha pasado como parametro, y se cogen los dos primeros bytes (16 bits). Entonces se representan sobre la primera fila. Luego se apaga la primera fila y se pasa a la segunda donde hay otros 16 bits esperando (que son las columnas). Se realiza ésto una vez en las 16 filas que hay. El resultado es que se barre una imágen en la cual se puede mostrar cualquier cosa, eso si, toda del mismo color. Mediante un selector de numeros puedes seleccionar el color (1-2-3).

De esta forma, tenemos solucionado el principal problema, pero nos vuelve a salir otro. Para mostrar, por ejemplo, todo el abecedario, mas los numeros, que son unos 37 caracteres, requerimos de 37 arrays de 32 bytes, es decir 1184 bytes (mas de 1 kilobyte) de memoria RAM.

Asi que si represento todo el abecedario y los numeros (que es lo básico para poder mostrar palabras) me quedo sin memoria ram. Recordemos que aun queda todo el programa por ejecutarse y ya consumo mas de 1kbyte de RAM.

Después de diseñar todo el abecedario y las letras con mi generador de código binario (ver video) decido almacenar todo esto en una memoria EEPROM externa, por i2c. En este caso una 24LC256 con 32kbytes de almacenamiento. (256kilobits / 8 bits)

Estas son todas las letras y numeros en binario.

const byte a[] = {B00000000, B00000000, B00000111, B11100000, B00001111, B11110000, B00011111, B11111000, B00011100, B00111000, B00011000, B00011000, B00011000, B00011000, B00011111, B11111000, B00011111, B11111000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00000000, B00000000, B00000000, B00000000};

const byte b[] = {B00000000, B00000000, B00000000, B00000000, B00011111, B11000000, B00011111, B11100000, B00011000, B01100000, B00011000, B01100000, B00011000, B01100000, B00011111, B11000000, B00011111, B11100000, B00011000, B01100000, B00011000, B01100000, B00011000, B01100000, B00011111, B11000000, B00011111, B11000000, B00000000, B00000000, B00000000, B00000000};

const byte c[] = {B00000000, B00000000, B00000000, B00000000, B00000011, B11100000, B00000111, B11100000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00000111, B11100000, B00000011, B11100000, B00000000, B00000000, B00000000, B00000000};

const byte d[] = {B00000000, B00000000, B00000000, B00000000, B00001111, B11000000, B00001111, B11100000, B00001100, B00110000, B00001100, B00110000, B00001100, B00110000, B00001100, B00110000, B00001100, B00110000, B00001100, B00110000, B00001100, B00110000, B00001100, B00110000, B00001111, B11100000, B00001111, B11000000, B00000000, B00000000, B00000000, B00000000};

const byte e[] = {B00000000, B00000000, B00000000, B00000000, B00001111, B11100000, B00001111, B11100000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001111, B11100000, B00001111, B11100000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001111, B11100000, B00001111, B11100000, B00000000, B00000000, B00000000, B00000000};

const byte f[] = {B00000000, B00000000, B00000000, B00000000, B00001111, B11100000, B00001111, B11100000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001111, B11000000, B00001111, B11000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00000000, B00000000, B00000000, B00000000};

const byte g[] = {B00000000, B00000000, B00000000, B00000000, B00000111, B11000000, B00001111, B11000000, B00011000, B00000000, B00011000, B00000000, B00011000, B00000000, B00011001, B11100000, B00011001, B11100000, B00011000, B01100000, B00011000, B01100000, B00011000, B01100000, B00001111, B11100000, B00000111, B11100000, B00000000, B00000000, B00000000, B00000000};

const byte h[] = {B00000000, B00000000, B00000000, B00000000, B00001100, B01100000, B00001100, B01100000, B00001100, B01100000, B00001100, B01100000, B00001100, B01100000, B00001111, B11100000, B00001111, B11100000, B00001100, B01100000, B00001100, B01100000, B00001100, B01100000, B00001100, B01100000, B00001100, B01100000, B00000000, B00000000, B00000000, B00000000};

const byte i[] = {B00000000, B00000000, B00000011, B10000000, B00000011, B10000000, B00000000, B00000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000000, B00000000, B00000000, B00000000};

const byte j[] = {B00000000, B00000000, B00000000, B00000000, B00001111, B11110000, B00001111, B11110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00001100, B00110000, B00001100, B00110000, B00001100, B00110000, B00001100, B00110000, B00001111, B11100000, B00000111, B11000000, B00000000, B00000000, B00000000, B00000000};

const byte k[] = {B00000000, B00000000, B00000000, B00000000, B00011000, B11000000, B00011000, B11000000, B00011001, B10000000, B00011011, B00000000, B00011110, B00000000, B00011100, B00000000, B00011100, B00000000, B00011110, B00000000, B00011011, B00000000, B00011001, B10000000, B00011000, B11000000, B00011000, B11000000, B00000000, B00000000, B00000000, B00000000};

const byte l[] = {B00000000, B00000000, B00000000, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001111, B11110000, B00001111, B11110000, B00000000, B00000000, B00000000, B00000000};

const byte m[] = {B00000000, B00000000, B00000000, B00000000, B00011000, B00011000, B00011100, B00111000, B00011110, B01111000, B00011011, B11011000, B00011001, B10011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00000000, B00000000, B00000000, B00000000};

const byte n[] = {B00000000, B00000000, B00000000, B00000000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011100, B00011000, B00011110, B00011000, B00011011, B00011000, B00011001, B10011000, B00011000, B11011000, B00011000, B01111000, B00011000, B00111000, B00011000, B00111000, B00011000, B00111000, B00000000, B00000000, B00000000, B00000000};

const byte o[] = {B00000000, B00000000, B00000011, B11000000, B00001111, B11110000, B00011100, B00111000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011100, B00111000, B00001111, B11110000, B00000011, B11000000, B00000000, B00000000};

const byte p[] = {B00000000, B00000000, B00000000, B00000000, B00011111, B10000000, B00011111, B11000000, B00011000, B01100000, B00011000, B01100000, B00011000, B11100000, B00011111, B11000000, B00011111, B10000000, B00011000, B00000000, B00011000, B00000000, B00011000, B00000000, B00011000, B00000000, B00011000, B00000000, B00000000, B00000000, B00000000, B00000000};

const byte q[] = {B00000000, B00000000, B00000111, B11100000, B00001111, B11110000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B01011000, B00011000, B01111000, B00001111, B11110000, B00000111, B11111000, B00000000, B00001100};

const byte r[] = {B00000000, B00000000, B00011111, B10000000, B00011111, B11000000, B00011000, B11000000, B00011000, B11000000, B00011000, B11000000, B00011111, B11000000, B00011111, B10000000, B00011100, B00000000, B00011110, B00000000, B00011011, B00000000, B00011001, B10000000, B00011000, B11000000, B00011000, B01100000, B00000000, B00000000, B00000000, B00000000};

const byte s[] = {B00000000, B00000000, B00001111, B11000000, B00011111, B11000000, B00011000, B00000000, B00011000, B00000000, B00011000, B00000000, B00011111, B11000000, B00001111, B11100000, B00000000, B01100000, B00000000, B01100000, B00000000, B01100000, B00000000, B01100000, B00001111, B11000000, B00001111, B10000000, B00000000, B00000000, B00000000, B00000000};

const byte t[] = {B00000000, B00000000, B00000000, B00000000, B00001111, B11110000, B00001111, B11110000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000000, B00000000, B00000000, B00000000};

const byte u[] = {B00000000, B00000000, B00000000, B00000000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011100, B00111000, B00001111, B11110000, B00000111, B11100000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000};

const byte v[] = {B00000000, B00000000, B00000000, B00000000, B00110000, B00001100, B00110000, B00001100, B00110000, B00001100, B00011000, B00011000, B00011000, B00011000, B00001100, B00110000, B00001100, B00110000, B00000110, B01100000, B00000110, B01100000, B00000011, B11000000, B00000011, B11000000, B00000001, B10000000, B00000000, B00000000, B00000000, B00000000};

const byte w[] = {B00000000, B00000000, B00000000, B00000000, B01100001, B10000110, B01100001, B10000110, B01100001, B10000010, B00110011, B11001100, B00110010, B01001100, B00110010, B01001100, B00010010, B01001000, B00010010, B01001000, B00011110, B01111000, B00001100, B00110000, B00001100, B00110000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000};

const byte x[] = {B00000000, B00000000, B00000000, B00000000, B00110000, B00001100, B00011000, B00011000, B00001100, B00110000, B00000110, B01100000, B00000110, B01100000, B00000011, B11000000, B00000011, B11000000, B00000110, B01100000, B00001100, B00110000, B00011000, B00010000, B00110000, B00001100, B01100000, B00000110, B00000000, B00000000, B00000000, B00000000};

const byte letray[] = {B00000000, B00000000, B00110000, B00011000, B00111000, B00111000, B00011100, B01110000, B00001111, B11100000, B00000111, B11000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000011, B10000000, B00000000, B00000000, B00000000, B00000000};

const byte z[] = {B00000000, B00000000, B00000000, B00000000, B00011111, B11110000, B00011111, B11110000, B00000000, B00110000, B00000000, B01100000, B00000000, B11000000, B00000001, B10000000, B00000011, B00000000, B00000110, B00000000, B00001100, B00000000, B00011000, B00000000, B00011111, B11110000, B00011111, B11110000, B00000000, B00000000, B00000000, B00000000};

byte num0[] = {B00000000, B00000000, B00000111, B11100000, B00001111, B11110000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00011000, B00111000, B00011000, B01011000, B00011000, B10011000, B00011001, B00011000, B00011010, B00011000, B00011100, B00011000, B00001111, B11110000, B00000111, B11100000, B00000000, B00000000, B00000000, B00000000};

byte num1[] = {B00000000, B00000000, B00000001, B10000000, B00000011, B10000000, B00000111, B10000000, B00001101, B10000000, B00011001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000001, B10000000, B00000011, B11000000, B00000000, B00000000, B00000000, B00000000};

byte num2[] = {B00000000, B00000000, B00000011, B11000000, B00001111, B11100000, B00001100, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B01100000, B00000000, B11000000, B00000001, B10000000, B00000011, B00000000, B00000110, B00000000, B00000111, B11110000, B00000111, B11110000, B00000000, B00000000, B00000000, B00000000};

byte num3[] = {B00000000, B00000000, B00001111, B00000000, B00001111, B10000000, B00000000, B11000000, B00000000, B11000000, B00000000, B11000000, B00000111, B11000000, B00000111, B11000000, B00000000, B11000000, B00000000, B11000000, B00000000, B11000000, B00000000, B11000000, B00001111, B10000000, B00001111, B00000000, B00000000, B00000000, B00000000, B00000000};

byte num4[] = {B00000000, B00000000, B00000000, B11100000, B00000001, B11100000, B00000011, B01100000, B00000110, B01100000, B00001100, B01100000, B00001111, B11111000, B00001111, B11111000, B00000000, B01100000, B00000000, B01100000, B00000000, B01100000, B00000000, B01100000, B00000000, B01100000, B00000000, B01100000, B00000000, B00000000, B00000000, B00000000};

byte num5[] = {B00000000, B00000000, B00001111, B11110000, B00001111, B11110000, B00001100, B00000000, B00001100, B00000000, B00001100, B00000000, B00001111, B11100000, B00001111, B11110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B01110000, B00001111, B11100000, B00001111, B11000000, B00000000, B00000000, B00000000, B00000000};

byte num6[] = {B00000000, B00000000, B00001111, B11110000, B00011111, B11110000, B00011000, B00000000, B00011000, B00000000, B00011000, B00000000, B00011000, B00000000, B00011111, B11100000, B00011111, B11110000, B00011000, B00110000, B00011000, B00110000, B00011111, B11110000, B00001111, B11100000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000};

byte num7[] = {B00000000, B00000000, B00001111, B11110000, B00001111, B11110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B11111000, B00000000, B11111000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00000000, B00000000, B00000000};

byte num8[] = {B00000000, B00000000, B00001111, B11100000, B00011111, B11110000, B00011000, B00110000, B00011000, B00110000, B00011000, B00110000, B00011000, B00110000, B00011111, B11110000, B00011111, B11110000, B00011000, B00110000, B00011000, B00110000, B00011000, B00110000, B00011000, B00110000, B00011111, B11110000, B00001111, B11100000, B00000000, B00000000};

byte num9[] = {B00000000, B00000000, B00001111, B11100000, B00011111, B11110000, B00011000, B00110000, B00011000, B00110000, B00011000, B00110000, B00011111, B11110000, B00001111, B11110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00110000, B00000000, B00000000};

Reduzco mucho el tamaño para que no me ocupe 10 paginas. Asi se ve perfectamente la cantidad de información que se trata (para un arduino es bastante).

Antes de empezar a explicar el funcionamiento del juego y de la comunicación, quiero explicar el sistema de mostrar letras, que fue el primero que diseñé.

El problema de crear una animación compleja, es que debe ser como una pelicula antigua de Disney. Tienes que escribir todos los pasos de la animacion (fotogramas) y luego reproducirlos seguidamente para dar la sensacion de movimiento.

El problema es que si tengo mas de 37 caracteres no puedo crear un movimiento completo para cada carácter (8 pasos) por que requeriría 8 arrays mas por cada carácter. Esto daría 296 arrays por 32 bytes cada array, 9478 bytes, casi 9 kilobytes, imposible.

Una solución algo mas factible, pero mas dificil de implementar es manipular en tiempo real bit a bit cada carácter para irlo desplazando e ir creando la animación. Es por eso que me pongo manos a la obra e investigo el tratamiento de datos bit a bit, con los operadores bit a bit. (la referencia de arduino lo explica a la perfección).

Para eso, he de cargar la letra de la memoria EEPROM, otra dificultad añadida. En la memoria EEPROM, los datos estan en diferentes, digamos diferentes pisos, cada piso con un numero. Para acceder a una letra, he de saber el piso donde esta, es por eso que tengo un array donde esta escrito.

int letras[] = {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,480,512,544,576,608,640,672,704,736,768,800,832,864,896,928,960,992,1024,1056,1088,1120};

Al ser de 8bits, y cada letra ocupar 32bytes, los numeros van de 32 en 32 por cada letra. Para obtener una letra hay que dirigirse al array y utilizar la función que viene a continuación:

void obtenerLetra(char letra[]) //guarda en tempLetra la letra a obtener

{

int buscar;

switch(letra[0])

{

case 97:

buscar = letras[0];

break;

case 98:

buscar = letras[1];

break;

case 99:

buscar = letras[2];

break;

case 100:

buscar = letras[3];

break;

case 101:

buscar = letras[4];

break;

case 102:

buscar = letras[5];

break;

case 103:

buscar = letras[6];

break;

case 104:

buscar = letras[7];

break;

case 105:

buscar = letras[8];

break;

case 106:

buscar = letras[9];

break;

case 107:

buscar = letras[10];

break;

case 108:

buscar = letras[11];

break;

case 109:

buscar = letras[12];

break;

case 110:

buscar = letras[13];

break;

case 111:

buscar = letras[14];

break;

case 112:

buscar = letras[15];

break;

case 113:

buscar = letras[16];

break;

case 114:

buscar = letras[17];

break;

case 115:

buscar = letras[18];

break;

case 116:

buscar = letras[19];

break;

case 117:

buscar = letras[20];

break;

case 118:

buscar = letras[21];

break;

case 119:

buscar = letras[22];

break;

case 120:

buscar = letras[23];

break;

case 121:

buscar = letras[24];

break;

case 122: //z

buscar = letras[25];

break;

case 48: //numeros

buscar = letras[26];

break;

case 49:

buscar = letras[27];

break;

case 50:

buscar = letras[28];

break;

case 51:

buscar = letras[29];

break;

case 52:

buscar = letras[30];

break;

case 53:

buscar = letras[31];

break;

case 54:

buscar = letras[32];

break;

case 55:

buscar = letras[33];

break;

case 56:

buscar = letras[34];

break;

case 57://9

buscar = letras[35];

break;

}

for(int y=0; y<32; y++)

{

tempLetra[y] = readEEPROM(eeprom,y+buscar);

}

}

Esta función sustituye cada carácter en ASCII por su equivalente en el array de letras, y hace la petición a la memoria EEPROM. Copia el resultado de la petición en un array temporal llamado tempLetra[32].

Para leer y escribir de la memoria EEPROM conectada a los pines analógicos 4 y 5, utilizo la librería Wire.h (i2c) y estras dos funciones:

void writeEEPROM(int deviceaddress, unsigned int eeaddress, byte data ) {

Wire.beginTransmission(deviceaddress);

Wire.send((int)(eeaddress >> 8)); // MSB

Wire.send((int)(eeaddress & 0xFF)); // LSB

Wire.send(data);

Wire.endTransmission();

}

byte readEEPROM(int deviceaddress, unsigned int eeaddress ) {

byte rdata = 0xFF;

Wire.beginTransmission(deviceaddress);

Wire.send((int)(eeaddress >> 8)); // MSB

Wire.send((int)(eeaddress & 0xFF)); // LSB

Wire.endTransmission();

Wire.requestFrom(deviceaddress,1);

if (Wire.available()) rdata = Wire.receive();

return rdata;

}

Entonces para pedir una letra, simplemente llamo a la función obtenerLetra(), le paso la letra que quiero, y automaticamente tengo la letra en el array tempLetra.

Bien, una vez obtengo la letra, necesito crear la animacion, y unirla junto a las otras letras de la palabra. Iremos paso a paso. Primero he de crear una función para poder desplazar bits hacia un lado y hacia el otro, completando con ceros. Esto da un efecto de movimiento de la imágen hacia el lado que nosotros queramos. La función mover() se ocupa de eso:

void mover(byte arreglo[], int tamano, char direccion[])

{

byte data1;

byte data2;

unsigned int byte16;

int contador = 0;

for(int y=0; y<tamano; y++){

data1 = arreglo[contador];

data2 = arreglo[contador+1];

byte16 = (data1<<8)|(data2);

if(direccion == «izquierda»)

{

byte16 = byte16 << 1;

}

else

{

byte16 = byte16 >> 1;

}

data1 = byte16; //autorrecorte

data2 = byte16 >> 8; //mover datos y autorrecorte

arreglo[contador] = data2; //invertir los datos al mostrar

arreglo[contador+1] = data1;

contador+=2;

}

}

Le pasamos el array a desplazar, el tamaño del array, para que la función se pueda adaptar en un futuro a arrays mayores o menores que 32, y un string con la dirección. “izquierda” o “derecha”. Asi de simple. La función nos mueve la letra una posición, en caso de que queramos mas posiciones, es cuestión de meter la función dentro de un loop, y asi ejecutarla el numero de veces que queramos.

La esencia de la función son los “bitshift operators”. “Bitshift left” y Bitshift right”. Izquierda y derecha. Estos operadores desplazan el byte hacia un lado y completan con ceros. Es perfecto para nosotros. Para manipularlos, la función copia los datos a bytes temporales para trabajar mejor.

Pero la función mover sola no hace nada. Ahora podemos cargar la letra y moverla . ¿qué nos falta?. Pues una función que lo engobe todo, cargue la letra en el momento adecuado, y muestre la palabra con la animación. Esta es la función mostrar Palabra.

void mostrarPalabra(char palabra[], int medida, int color)

{

int tamano = 32*medida;

byte arreglo[tamano];

int ciclo = 0;

for(int y=0; y<=medida; y++)

{

switch(palabra[y])

{

case 97:

obtenerLetra(«a»);

copiarLetra(tempLetra,arreglo);

break;

case 98:

obtenerLetra(«b»);

copiarLetra(tempLetra,arreglo);

break;

case 99:

obtenerLetra(«c»);

copiarLetra(tempLetra,arreglo);

break;

case 100:

obtenerLetra(«d»);

copiarLetra(tempLetra,arreglo);

break;

case 101:

obtenerLetra(«e»);

copiarLetra(tempLetra,arreglo);

break;

case 102:

obtenerLetra(«f»);

copiarLetra(tempLetra,arreglo);

break;

case 103:

obtenerLetra(«g»);

copiarLetra(tempLetra,arreglo);

break;

case 104:

obtenerLetra(«h»);

copiarLetra(tempLetra,arreglo);

break;

case 105:

obtenerLetra(«i»);

copiarLetra(tempLetra,arreglo);

break;

case 106:

obtenerLetra(«j»);

copiarLetra(tempLetra,arreglo);

break;

case 107:

obtenerLetra(«k»);

copiarLetra(tempLetra,arreglo);

break;

case 108:

obtenerLetra(«l»);

copiarLetra(tempLetra,arreglo);

break;

case 109:

obtenerLetra(«m»);

copiarLetra(tempLetra,arreglo);

break;

case 110:

obtenerLetra(«n»);

copiarLetra(tempLetra,arreglo);

break;

case 111:

obtenerLetra(«o»);

copiarLetra(tempLetra,arreglo);

break;

case 112:

obtenerLetra(«p»);

copiarLetra(tempLetra,arreglo);

break;

case 113:

obtenerLetra(«q»);

copiarLetra(tempLetra,arreglo);

break;

case 114:

obtenerLetra(«r»);

copiarLetra(tempLetra,arreglo);

break;

case 115:

obtenerLetra(«s»);

copiarLetra(tempLetra,arreglo);

break;

case 116:

obtenerLetra(«t»);

copiarLetra(tempLetra,arreglo);

break;

case 117:

obtenerLetra(«u»);

copiarLetra(tempLetra,arreglo);

break;

case 118:

obtenerLetra(«v»);

copiarLetra(tempLetra,arreglo);

break;

case 119:

obtenerLetra(«w»);

copiarLetra(tempLetra,arreglo);

break;

case 120:

obtenerLetra(«x»);

copiarLetra(tempLetra,arreglo);

break;

case 121:

obtenerLetra(«y»);

copiarLetra(tempLetra,arreglo);

break;

case 122:

obtenerLetra(«z»);

copiarLetra(tempLetra,arreglo); //800

break;

case 20:

copiarLetra(espacio,arreglo);

break;

case 48:

obtenerLetra(«0»);

copiarLetra(tempLetra,arreglo);

break;

case 49:

obtenerLetra(«1»);

copiarLetra(tempLetra,arreglo);

break;

case 50:

obtenerLetra(«2»);

copiarLetra(tempLetra,arreglo);

break;

case 51:

obtenerLetra(«3»);

copiarLetra(tempLetra,arreglo);

break;

case 52:

obtenerLetra(«4»);

copiarLetra(tempLetra,arreglo);

break;

case 53:

obtenerLetra(«5»);

copiarLetra(tempLetra,arreglo);

break;

case 54:

obtenerLetra(«6»);

copiarLetra(tempLetra,arreglo);

break;

case 55:

obtenerLetra(«7»);

copiarLetra(tempLetra,arreglo);

break;

case 56:

obtenerLetra(«8»);

copiarLetra(tempLetra,arreglo);

break;

case 57:

obtenerLetra(«9»);

copiarLetra(tempLetra,arreglo);

break;

}

ciclo = 0;

for(int a=0; a<2; a++)

{

mover(arreglo,32,»derecha»);

}

for(int b=0; b<14; b++)

{

if(ciclo == 0)

{

for(int c=0; c<10; c++)

{

barrer(color,16,arreglo);

}

}

else{

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

{

barrer(color,16,arreglo);

}

}

ciclo = 1;

mover(arreglo,32,»izquierda»);

}

}

}

A esta función, simplemente se le introduce la palabra a pasar, los caracteres que tiene la palabra, y el color de la palabra, mediante numeros con 1 – 2 – 3. Rojo verde naranja.

Igual que la función que carga la palabra, identifica los caracteres en ASCII y utiliza la función de copiarLetra, que simplemente es un loop que copia la letra en un array temporal. El motivo de esto es simple. La función mover nos mueve los caracteres modificando el array que los contiene, es por eso que debemos utilizar un array temporal, para no dañar los caracteres originales.

Mediante unos loops for, al final de la función se barre y se muestra durante un tiempo. El numero de veces que se ejecuta el loop, corresponde al tiempo en el que se mostrara la letra. Aun no he sacado una relación tiempo/numero de veces, pero es cuestión de tiempo. Habria que ver la velocidad de trabajo de arduino, que no la sé. (16Mhz no se si es una referencia o no).

Con esto , finalmente podemos mostrar cualquier carácter ya definido, de una manera comoda. Esta función se llama en los menúes (es correcto! RAE) y en diferentes partes del juego, como en el chat.

Ahora paso a explicar el funcionamiento básico del juego, que gira entorno a un array (como no). Éste array, latente tanto en modo de 1 jugador, como en modo online es el encargado de llevar toda la información, es el que se transfiere en el modo online, y es el que se ve afectado por muchas funciones que lo copian, lo escriben, lo interpretan y lo modifican.

El array se llama tablero. Es un entero. “int tablero[8][8]”. He decidido dividir mi pantalla de 16 x 16 en fichas de 2 x 2, es decir caben 64 fichas, 8*8 (aquí el array).

A continuacion, voy a poner el prototipo de todas las funciones que modifican el array, asi se puede ver perfectamente que y como modifica o interactúa con el array.

void mostrarArrayTablero();

void borrarTablero();

void escribirArrayTableroSobre(int fila, int columna, int valor);

void escribirArrayTablero(int fila, int columna, int valor);

void refreshTablero();

void combinarArrays(byte array1[], byte array2[], byte destino[]);

void resetTablero();

Aquí es donde entra en juego la posibilidad de mostrar varios colores y todas las formas posibles. Me explico.

Tenemos un array de 8 x 8, entendible por los humanos, y es con el cual trabajamos siempre. Desde mostrarlo, añadir fichas… Pero el principal problema esta en ¿como mostrarlo por la pantalla? Debemos tranformarlo. Mediante la función refreshTablero() se copian los datos del array tablero, a dos arrays de tipo byte de 32. Son ésto los que son interpretados por la función mostrarArrayTablero(), que los lee y los muestra de la misma forma en que se muestran las letras.

Pero ¿por que dos arrays?. Muy simple. Por los colores. Un array representa al color verde y otro al rojo. En caso de que queramos sólo un color, uno debe estar con los valores y otro a cero. En caso de que queramos naranja, deberemos mostrar valores iguales en los dos arrays. Y con esto podemos combinar los tres colores a la vez en el tablero! La funcion mostrarArrayTablero() es la que se encaga de todo esto.

void mostrarArrayTablero()

{

int contador =0;

int ciclos = 0;

for(int y=0; y<16; y++) {

if(ciclos<8)

{

mostrar(num[y+1], nulo, arrayTableroRojo[contador], arrayTableroRojo[contador+1], arrayTableroVerde[contador], arrayTableroVerde[contador+1]);

}

else

{

mostrar(nulo,num[y-7], arrayTableroRojo[contador], arrayTableroRojo[contador+1], arrayTableroVerde[contador], arrayTableroVerde[contador+1]);

}

ciclos+=1;

contador+=2;

if(ciclos == 16)

{

ciclos = 0;

}

}

}

Una vez podemos mostrar datos en el tablero, necesitamos poder escribir datos. Nos enfrentamos a otra dificultad. No puedo añadir datos al tablero directamente, porque sino sobreescribo los datos anteriores, cosa que no quiero, porque pierdo el progreso.

Nosotros y el juego escribimos directamente en el array y seguidamente se ejecuta la función que copia los datos de este array a los dos anteriores.

Para eso, recurriendo a la referencia de arduino, volviendo a los operadores lógicos, encuentro el bitshift OR. No voy a explicarlo, pero el caso es que hace una operación lógica al utilizarlo y el resultado es que si añadimos datos nuevos, (es decir 1) se sobreescriben. Un ejemplo practico.

byte a = B10101010;

byte b = B01010101;

byte c = a | b; //resultado = 11111111;

De esta forma, opera la funcion escribiArrayTablero(), en la que se le pasa la fila, la columna y el valor a escribir.

void escribirArrayTablero(int fila, int columna, int valor)

{

int lugar = fila*4;

int par = 2;

int impar1 = 1;

int impar3 = 3;

int constante1;

int constante2;

if(columna<=3)

{

constante1 = 0;

constante2 = par;

}

else

{

constante1 = impar1;

constante2 = impar3;

}

if(valor == 0) //nulo

{

arrayTableroRojo[lugar+constante1] = arrayTableroRojo[lugar+constante1] | nulo;

arrayTableroRojo[lugar+constante2] = arrayTableroRojo[lugar+constante2] | nulo;

arrayTableroVerde[lugar+constante1] = arrayTableroVerde[lugar+constante1] | nulo;

arrayTableroVerde[lugar+constante2] = arrayTableroVerde[lugar+constante2] | nulo;

}

else if(valor == 1) //rojo

{

arrayTableroRojo[lugar+constante1] = arrayTableroRojo[lugar+constante1] | datosTablero[columna];

arrayTableroRojo[lugar+constante2] = arrayTableroRojo[lugar+constante2] | datosTablero[columna];

arrayTableroVerde[lugar+constante1] = arrayTableroVerde[lugar+constante1] | nulo;

arrayTableroVerde[lugar+constante2] = arrayTableroVerde[lugar+constante2] | nulo;

}

else if(valor == 2) //verde

{

arrayTableroRojo[lugar+constante1] = arrayTableroRojo[lugar+constante1] | nulo;

arrayTableroRojo[lugar+constante2] = arrayTableroRojo[lugar+constante2] | nulo;

arrayTableroVerde[lugar+constante1] = arrayTableroVerde[lugar+constante1] | datosTablero[columna];

arrayTableroVerde[lugar+constante2] = arrayTableroVerde[lugar+constante2] | datosTablero[columna];

}

else if(valor == 3) //naranja

{

arrayTableroRojo[lugar+constante1] = arrayTableroRojo[lugar+constante1] | datosTablero[columna];

arrayTableroRojo[lugar+constante2] = arrayTableroRojo[lugar+constante2] | datosTablero[columna];

arrayTableroVerde[lugar+constante1] = arrayTableroVerde[lugar+constante1] | datosTablero[columna];

arrayTableroVerde[lugar+constante2] = arrayTableroVerde[lugar+constante2] | datosTablero[columna];

}

}

De esta forma al añadir datos nuevos no se borran los datos anteriores. Pero habra casos en los cuales, si queramos añadir un dato y que se borre el anterior, por ejemplo al crear una animación en el tablero. Es facil, la funcion escribirArrayTableroSobre() es identica a la función anterior, sustituyendo el operador | por un operador de asignación (=). Y con estas dos funciones básicas se crean el resto de funciones, por ejemplo reset tablero, que no deja de ser un for, y metiendo todos los valores en 0, o por ejemplo borrarTablero() que borra toda la información externa del tablero y se limita a copiar el array tablero[8][8].

Finalmente falta el apartado de comunicación. En la que entra tambien en jeugo el código en php. Las funciones de comunicación son las siguientes.

void enviarDatos(int dato);

void enviarTablero();

int recibirDatos();

La comunicación entre arduino y php es algo complicada. Como explico en el video, nunca sabemos cuando php va a estar activo para enviar / recibir datos, de manera que la comunicación se hace con un método de confirmación.

Php envia a arduino un numero. Según el numero, arduino detecta lo que quiere php, si es enviar o recibir datos.

En caso de enviar, es simple, php vuelve a enviar un dato (1) a arduino. Arduino, al recibir el 1, envia el primer dato, y hasta que no reciba otro 1 no enviara el siguiente dato.

Php lo tiene fácil, al recibir un dato envia un uno. Ambas partes tienen fijado un tiempo (un loop) y saben cuantos paquetes tienen que enviar y recibir.

Para mas informacion revisa los códigos en php.

Todo el código fuente está en http://pastebin.com/m181b400b.


Introduccion: 4onLine

2009

4 onLine es el proyecto que he desarrollado para el concurso Let Arduino play Contest 2009. El concurso consiste en desarrollar un video juego en el cual se pueda interactuar con cualquiera de las versiones de la famosa placa arduino. En mi caso, he utilizado una placa arduino duemilanove con el Atmel Atmega 328 (32k flash).

He de reconocer que ha sido un proyecto difícil, ya que, para empezar es mi primer videojuego con la plataforma arduino. El juego completo consta de unas 1400 lineas de código, algo que no está nada mal para un arduino. Tambien hay bastante código en php, que gestiona la conexión de el videojuego a través del mundo.

Con lo referente al hardware, podeis descargar un pdf con todas las imagenes con lo referente al montaje.

Teneis un video aqui del proyecto funcionando.