Posts etiquetados ‘arduino’

Arduino es una plataforma prácticamente infinita para todo tipo de aplicaciones como lo hemos visto en este humilde blog de entre millones que existen en la web. En este espacio comenzamos con aplicaciones de Hacking, luego incursionamos en la fascinante disciplina de la Robótica y últimamente avanzamos sobre el increíble universo de los Videojuegos.

Anteriormente publicamos juegos (basados en la librería VGAx desarrollada por Sandro Maffiodo) como, por ejemplo:

También construimos juegos más sencillos basados simplemente en LEDs y pulsadores como

Y no hace mucho tiempo atrás comenzamos con la construcción de videojuegos utilizando matrices de LEDs

En esta ocasión construiremos un juego muy sencillo orientado a quienes recién se inician en este maravilloso mundo de los microcontroladores, particularmente en Arduino, y desean obtener resultados satisfactorios a corto plazo ya que no creo que haya alguna emoción más intensa para un inventor, que ver alguna de sus creaciones funcionando. Utilizaremos solamente un display LCD de 16 columnas 2 filas, y un pulsador.

El juego que crearemos pertenece al género “Endless Runner“, del inglés corredor infinito cuya definición es la siguiente:

Género donde el jugador debe avanzar de manera irremediable en una misma dirección, generalmente escapando de algún enemigo o peligro, y cuyo objetivo es avanzar lo máximo posible antes de morir.

Generalmente las acciones principales son saltar y esquivar obstáculos, pero muchos juegos de este tipo incluyen también algún tipo de ataque, tanto cuerpo a cuerpo como con armas.

Desde la decáda de los 80’s con juegos como “Jump Bug” o “Moon Patrol” hasta el día de hoy se encuentran de manera casi omnipresente en nuestras vidas. Como ejemplo inmediato tenemos el mundialmente conocido navegador web del gigante Google, “Chrome“, que a partir de “Chrome Canary” incluye un divertido juego “endless runner” en el cual controlamos a un simpático T-Rex. Aunque parezca increíble existen en la web competencias de mayor puntaje obtenido en el juego. Meses atrás un gigante del entretenimiento, Netflix, ha incluido un juego “endless runner” en el cual podemos seleccionar personajes como Pablo Escobar o Marco Polo.

El diagrama esquemático de nuestro divertido y sencillo juego se observa en la siguiente imagen:

(click para ampliar)

Lista de Componentes
1 Display LCD 16×2 con controlador Hitachi HD44780 o compatible
1 Resistencia 3K3Ω 0.25W
1 Resistencia 220Ω 0.25W
1 Pulsador N.A. o Push-Button N.A.

#include <LiquidCrystal.h>

#define PIN_BUTTON 2

#define SPRITE_RUN1 1
#define SPRITE_RUN2 2
#define SPRITE_JUMP 3
#define SPRITE_JUMP_UPPER '.'         // Use the '.' character for the head
#define SPRITE_JUMP_LOWER 4
#define SPRITE_TERRAIN_EMPTY ' '      // User the ' ' character
#define SPRITE_TERRAIN_SOLID 5
#define SPRITE_TERRAIN_SOLID_RIGHT 6
#define SPRITE_TERRAIN_SOLID_LEFT 7

#define HERO_HORIZONTAL_POSITION 1    // Horizontal position of hero on screen

#define TERRAIN_WIDTH 16
#define TERRAIN_EMPTY 0
#define TERRAIN_LOWER_BLOCK 1
#define TERRAIN_UPPER_BLOCK 2

#define HERO_POSITION_OFF 0          // Hero is invisible
#define HERO_POSITION_RUN_LOWER_1 1  // Hero is running on lower row (pose 1)
#define HERO_POSITION_RUN_LOWER_2 2  //                              (pose 2)

#define HERO_POSITION_JUMP_1 3       // Starting a jump
#define HERO_POSITION_JUMP_2 4       // Half-way up
#define HERO_POSITION_JUMP_3 5       // Jump is on upper row
#define HERO_POSITION_JUMP_4 6       // Jump is on upper row
#define HERO_POSITION_JUMP_5 7       // Jump is on upper row
#define HERO_POSITION_JUMP_6 8       // Jump is on upper row
#define HERO_POSITION_JUMP_7 9       // Half-way down
#define HERO_POSITION_JUMP_8 10      // About to land

#define HERO_POSITION_RUN_UPPER_1 11 // Hero is running on upper row (pose 1)
#define HERO_POSITION_RUN_UPPER_2 12 //                              (pose 2)

LiquidCrystal lcd(7,  //RS
                  8,  //E
                  3,  //DB4
                  4,  //DB5
                  5,  //DB6
                  6   //DB7
                  );

char terrainUpper[TERRAIN_WIDTH + 1];
char terrainLower[TERRAIN_WIDTH + 1];
volatile boolean buttonPushed = false;

void initializeGraphics() {
	byte graphics[] = {
        // Run position 1
        B01100,
        B01100,
        B00000,
        B01110,
        B11100,
        B01100,
        B11010,
        B10011,
        // Run position 2
        B01100,
        B01100,
        B00000,
        B01100,
        B01100,
        B01100,
        B01100,
        B01110,
        // Jump
        B01100,
        B01100,
        B00000,
        B11110,
        B01101,
        B11111,
        B10000,
        B00000,
        // Jump lower
        B11110,
        B01101,
        B11111,
        B10000,
        B00000,
        B00000,
        B00000,
        B00000,
        // Ground
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        B11111,
        // Ground right
        B00011,
        B00011,
        B00011,
        B00011,
        B00011,
        B00011,
        B00011,
        B00011,
        // Ground left
        B11000,
        B11000,
        B11000,
        B11000,
        B11000,
        B11000,
        B11000,
        B11000,
    };

    int i;

    // Skip using character 0, this allows lcd.print() to be used to
    // quickly draw multiple characters
    for (i = 0; i < 7; ++i) {
        lcd.createChar(i + 1, &graphics[i * 8]);
    }

    for (i = 0; i < TERRAIN_WIDTH; ++i) {
        terrainUpper[i] = SPRITE_TERRAIN_EMPTY;
        terrainLower[i] = SPRITE_TERRAIN_EMPTY;
    }
}

// Slide the terrain to the left in half-character increments
void advanceTerrain(char* terrain, byte newTerrain) {
    for (int i = 0; i < TERRAIN_WIDTH; ++i) {
        char current = terrain[i];
        char next = (i == TERRAIN_WIDTH-1) ? newTerrain : terrain[i+1];

        switch (current) {
            case SPRITE_TERRAIN_EMPTY:
                terrain[i] = (next == SPRITE_TERRAIN_SOLID) ? SPRITE_TERRAIN_SOLID_RIGHT : SPRITE_TERRAIN_EMPTY;
            break;

            case SPRITE_TERRAIN_SOLID:
                terrain[i] = (next == SPRITE_TERRAIN_EMPTY) ? SPRITE_TERRAIN_SOLID_LEFT : SPRITE_TERRAIN_SOLID;
            break;

            case SPRITE_TERRAIN_SOLID_RIGHT:
                terrain[i] = SPRITE_TERRAIN_SOLID;
            break;

            case SPRITE_TERRAIN_SOLID_LEFT:
                terrain[i] = SPRITE_TERRAIN_EMPTY;
            break;
        }
    }
}

boolean drawHero(byte position, char* terrainUpper, char* terrainLower, unsigned int score) {
    boolean collide = false;
    char upperSave = terrainUpper[HERO_HORIZONTAL_POSITION];
    char lowerSave = terrainLower[HERO_HORIZONTAL_POSITION];
    byte upper, lower;

    switch (position) {
        case HERO_POSITION_OFF:
            upper = lower = SPRITE_TERRAIN_EMPTY;
        break;

        case HERO_POSITION_RUN_LOWER_1:
            upper = SPRITE_TERRAIN_EMPTY;
            lower = SPRITE_RUN1;
        break;

        case HERO_POSITION_RUN_LOWER_2:
            upper = SPRITE_TERRAIN_EMPTY;
            lower = SPRITE_RUN2;
        break;

        case HERO_POSITION_JUMP_1:

        case HERO_POSITION_JUMP_8:
            upper = SPRITE_TERRAIN_EMPTY;
            lower = SPRITE_JUMP;
        break;

        case HERO_POSITION_JUMP_2:

        case HERO_POSITION_JUMP_7:
            upper = SPRITE_JUMP_UPPER;
            lower = SPRITE_JUMP_LOWER;
        break;

        case HERO_POSITION_JUMP_3:

        case HERO_POSITION_JUMP_4:

        case HERO_POSITION_JUMP_5:

        case HERO_POSITION_JUMP_6:
            upper = SPRITE_JUMP;
            lower = SPRITE_TERRAIN_EMPTY;
        break;

        case HERO_POSITION_RUN_UPPER_1:
            upper = SPRITE_RUN1;
            lower = SPRITE_TERRAIN_EMPTY;
        break;

        case HERO_POSITION_RUN_UPPER_2:
            upper = SPRITE_RUN2;
            lower = SPRITE_TERRAIN_EMPTY;
        break;
    }

    if (upper != ' ') {
        terrainUpper[HERO_HORIZONTAL_POSITION] = upper;
        collide = (upperSave == SPRITE_TERRAIN_EMPTY) ? false : true;
    }

    if (lower != ' ') {
        terrainLower[HERO_HORIZONTAL_POSITION] = lower;
        collide |= (lowerSave == SPRITE_TERRAIN_EMPTY) ? false : true;
    }

    byte digits = (score > 9999) ? 5 : (score > 999) ? 4 : (score > 99) ? 3 : (score > 9) ? 2 : 1;

    // Draw the scene
    terrainUpper[TERRAIN_WIDTH] = '\0';
    terrainLower[TERRAIN_WIDTH] = '\0';
    char temp = terrainUpper[16-digits];
    terrainUpper[16-digits] = '\0';
    lcd.setCursor(0,0);
    lcd.print(terrainUpper);
    terrainUpper[16-digits] = temp;
    lcd.setCursor(0,1);
    lcd.print(terrainLower);

    lcd.setCursor(16 - digits,0);
    lcd.print(score);

    terrainUpper[HERO_HORIZONTAL_POSITION] = upperSave;
    terrainLower[HERO_HORIZONTAL_POSITION] = lowerSave;

    return collide;
}

// Handle the button push as an interrupt
void buttonPush() {
    buttonPushed = true;
}

void setup() {
    pinMode(PIN_BUTTON, INPUT_PULLUP);

    // Digital pin 2 maps to interrupt 0
    attachInterrupt(0, buttonPush, FALLING);

    initializeGraphics();

    lcd.begin(16, 2);
}

void loop(){
    static byte heroPos = HERO_POSITION_RUN_LOWER_1;
    static byte newTerrainType = TERRAIN_EMPTY;
    static byte newTerrainDuration = 1;
    static boolean playing = false;
    static boolean blink = false;
    static unsigned int distance = 0;

    if (!playing) {
        drawHero((blink) ? HERO_POSITION_OFF : heroPos, terrainUpper, terrainLower, distance >> 3);

        if (blink) {
        lcd.setCursor(0,0);
        lcd.print("Press Start");
        }

        delay(250);
        blink = !blink;

        if (buttonPushed) {
            initializeGraphics();
            heroPos = HERO_POSITION_RUN_LOWER_1;
            playing = true;
            buttonPushed = false;
            distance = 0;
        }

        return;
    }

    // Shift the terrain to the left
    advanceTerrain(terrainLower, newTerrainType == TERRAIN_LOWER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY);
    advanceTerrain(terrainUpper, newTerrainType == TERRAIN_UPPER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY);

    // Make new terrain to enter on the right
    if (--newTerrainDuration == 0) {
        if (newTerrainType == TERRAIN_EMPTY) {
            newTerrainType = (random(3) == 0) ? TERRAIN_UPPER_BLOCK : TERRAIN_LOWER_BLOCK;
            newTerrainDuration = 2 + random(10);
        } else {
            newTerrainType = TERRAIN_EMPTY;
            newTerrainDuration = 10 + random(10);
        }
    }

    if (buttonPushed) {
        if (heroPos <= HERO_POSITION_RUN_LOWER_2)
            heroPos = HERO_POSITION_JUMP_1;

        buttonPushed = false;
    }

    if (drawHero(heroPos, terrainUpper, terrainLower, distance >> 3)) {
        playing = false; // The hero collided with something. Too bad.
    } else {
        if (heroPos == HERO_POSITION_RUN_LOWER_2 || heroPos == HERO_POSITION_JUMP_8) {
            heroPos = HERO_POSITION_RUN_LOWER_1;
        } else if ((heroPos >= HERO_POSITION_JUMP_3 && heroPos <= HERO_POSITION_JUMP_5) && terrainLower[HERO_HORIZONTAL_POSITION] != SPRITE_TERRAIN_EMPTY) {
            heroPos = HERO_POSITION_RUN_UPPER_1;
        } else if (heroPos >= HERO_POSITION_RUN_UPPER_1 && terrainLower[HERO_HORIZONTAL_POSITION] == SPRITE_TERRAIN_EMPTY) {
            heroPos = HERO_POSITION_JUMP_5;
        } else if (heroPos == HERO_POSITION_RUN_UPPER_2) {
            heroPos = HERO_POSITION_RUN_UPPER_1;
        } else {
            ++heroPos;
        }

        ++distance;
    }

    delay(100);
}

Anuncios

Arduino es una plataforma prácticamente infinita para todo tipo de aplicaciones como lo hemos visto en este humilde blog de entre millones que existen en la web. En este espacio comenzamos con aplicaciones de Hacking, luego incursionamos en la fascinante disciplina de la Robótica y últimamente avanzamos sobre el increíble universo de los Videojuegos.

Anteriormente publicamos juegos (basados en la librería VGAx desarrollada por Sandro Maffiodo) como, por ejemplo:

También construimos juegos más sencillos basados simplemente en LEDs y pulsadores como

Y no hace mucho tiempo atrás comenzamos con la construcción de videojuegos utilizando matrices de LEDs

En esta ocasión continuaremos con la construcción de juegos utilizando matrices de LEDs controladas por el circuito integrado MAX7219.

Tetris es un videojuego de puzzle originalmente diseñado y programado por Alekséi Pázhitnov en la Unión Soviética. Fue lanzado el 6 de junio de 1984, mientras trabajaba para el Centro de Computación Dorodnitsyn de la Academia de Ciencias de la Unión Soviética en Moscú, RSFS de Rusia. Su nombre deriva del prefijo numérico griego tetra- (todas las piezas del juego, conocidas como Tetrominós que contienen cuatro segmentos) y del tenis, el deporte favorito de Pázhitnov.

En el Tetris se juega con los tetrominós, el caso especial de cuatro elementos de poliominós. Los poliominós se han utilizado en los rompecabezas populares por lo menos desde 1907, y el nombre fue dado por el matemático Solomon W. Golomb en 1953. Sin embargo, incluso la enumeración de los pentominós data de la antigüedad.

El juego (o una de sus muchas variantes) está disponible para casi cada consola de videojuegos y sistemas operativos de PC, así como en dispositivos tales como las calculadoras gráficas, teléfonos móviles, reproductores de multimedia portátiles, PDAs, reproductores de música en red e incluso como huevo de pascua en productos no mediáticos como los osciloscopios. También ha inspirado servicios de mesa y ha sido jugado en los costados de varios edificios, manteniendo el récord de ser el juego completamente funcional más grande del mundo gracias al esfuerzo de estudiantes holandeses en 1995 que iluminaron 15 pisos del Departamento de Ingeniería Eléctrica en la Universidad Técnica de Delft.

Mientras que las versiones de Tetris se vendieron para una amplia gama de plataformas de ordenadores domésticos de los años 1980, fue la muy exitosa versión portátil para la Game Boy lanzada en 1989 que estableció al juego como uno de los más populares de todos los tiempos. La edición número 100 del Electronic Gaming Monthly tuvo al Tetris en el primer lugar como el “mejor juego de todos los tiempos”. En 2007, Tetris ocupó el segundo lugar en los “100 mejores videojuegos de todos los tiempos” para IGN. Ha vendido más de 70 millones de copias. En enero de 2010, se anunció que el Tetris ha vendido más de 100 millones de unidades para teléfonos celulares (móviles) sólo desde el año 2005.

Efectos mentales de Tetris

De acuerdo con el Dr. Richard Haier, jugar al Tetris de forma prolongada puede llevar a una actividad cerebral más eficiente durante el juego.

La primera vez que se juega al Tetris, aumentan la función y actividad cerebral, incrementándose también el consumo de energía y glucosa por parte de este. A medida que el jugador de Tetris se vuelve más hábil, el cerebro reduce su consumo de energía y glucosa, indicando una actividad cerebral más eficiente para el juego. Jugar al Tetris de forma moderada (media hora al día por un período de tres meses) incrementa las funciones cognoscitivas tales como “pensamiento crítico, razonamiento, procesamiento del lenguaje”, elevándose también el espesor de la corteza cerebral.

El juego puede provocar que se imaginen combinaciones de Tetris de forma involuntaria aun cuando no se esté jugando (el llamado Efecto Tetris), aunque esto puede ocurrir con cualquier videojuego o situación real que proyecte las imágenes o escenarios de forma repetida, tales como rompecabezas.

La Matriz de LEDs

Una matriz LEDs es un display formado por múltiples LEDs en distribución rectangular. Existen distintos tamaños, siendo el más habitual los cuadrados de 8×8 LEDs.
Podemos combinar varios módulos para formar un display mucho mayor. En estos display podemos mostrar textos, dibujos o animaciones, como desplazar un texto (scroll).
Por lo demás, son diodos LEDs totalmente normales, organizados en forma de matriz, que tendremos que multiplexar para poder iluminar uno u otro punto.
Si los diodos se unen por el positivo, se dice que son matrices de Ánodo común, y si se une por el negativo decimos que son de Cátodo común. Dependiendo del fabricante podemos encontrar de ambos tipos.

Encender una matriz de LEDs directamente con Arduino requiere emplear una gran cantidad de pines, lo cual supondría un gran desperdicio de recursos. Por este motivo, lo normal es siempre utilizar un controlador específicamente diseñado para esta función. Un controlador habitualmente empleado por ser barato y sencillo es el circuito integrado MAX7219.

Circuito Integrado MAX7219

Encender una matriz de 8×8 LED requeriría 16 señales digitales y un trabajo constante del procesador para refrescar la imagen. Eso es una cantidad enorme de recursos para cualquier autómata, que estaríamos desperdiciando para simplemente encender un display.
Por este motivo, utilizamos el circuito integrado MAX7219 que está especialmente diseñado para encender displays de 7 segmentos y matrices de LEDs, liberando al procesador para hacer tareas mucho más valiosas y productivas.
La comunicación con el circuito integrado MAX7219 se realiza mediante el bus SPI por lo que sólo se requieren 3 pines de Arduino (SS, MOSI y SCK). Además, ni siquiera “ocupamos” en su totalidad estos pines, ya que con el mismo bus podemos controlar múltiples dispositivos.
Por último, las placas MAX7219 generalmente incorporan un puerto de entrada y salida, de forma que podemos combinar múltiples controladores sin ninguna dificultad.
MAX7219 Datasheet

El diagrama esquemático de esta variante de Arduino Tetris se observa en la siguiente imagen:

(click para ampliar)

Lista de Componentes
1 Resistencia 10KΩ 0.25W
5 Pulsadores N.A. o Push-Buttons N.A.
1 Transistor NPN 2SC1815
2 Módulos con Matrices de LEDs 8×8 con Circuito Integrado MAX7219 c/u. Cátodo Común [Color Rojo en caso de aplicar Red Mod]
1 Buzzer Pasivo 5V
1 Placa Arduino Nanino
Librería LedControl desarrollada por wayoda

#include <LedControl.h>

LedControl lc = LedControl(12, 11, 10, 2); // (dataPin, clockPin, csPin, totalDevices)

int lc0[] = {0, 0, 0, 0, 0, 0, 0, 0};
int lc1[] = {0, 0, 0, 0, 0, 0, 0, 0};
long active[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
long screen[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tmpCol = 0;


int figura = 0;
int figuraNext = 0;
int fromLeft = 0;
int fromRight = 0;
int angle = 0;
int colCheck = 0;
int moveCheck = 0;
int score = 0;
int started = 0;
int lcRows = 16;
int lcCols = 8;
int allLines = 0;
int currLines = 0;
int brickDelay = 0;
int defDelay = 500;
int level = 0;

boolean sound = true;
//Pinos

int rotate_button = 2;
int left_button   = 3;
int down_button   = 4;
int right_button  = 5;
int start_button  = 6;
int speaker_pin   = 7;
int sound_button  = 8;

byte X[8] =
{
  0b00000,
  0b10001,
  0b01010,
  0b00100,
  0b01010,
  0b10001,
  0b00000,
  0b00000
};

byte O[8] =
{
  0b00000,
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b00000,
  0b00000
};

byte L[8] =
{
  0b11000,
  0b11000,
  0b11000,
  0b11000,
  0b11000,
  0b11111,
  0b11111,
  0b00000
};

byte J[8] =
{
  0b00011,
  0b00011,
  0b00011,
  0b00011,
  0b00011,
  0b11111,
  0b11111,
  0b00000
};

byte T[8] =
{
  0b00000,
  0b00000,
  0b11111,
  0b11111,
  0b01110,
  0b01110,
  0b00000,
  0b00000
};

byte I[8] =
{
  0b01100,
  0b01100,
  0b01100,
  0b01100,
  0b01100,
  0b01100,
  0b01100,
  0b00000
};

byte Z[8] =
{
  0b00000,
  0b00000,
  0b11110,
  0b11110,
  0b01111,
  0b01111,
  0b00000,
  0b00000
};

byte S[8] =
{
  0b00000,
  0b00000,
  0b01111,
  0b01111,
  0b11110,
  0b11110,
  0b00000,
  0b00000
};

//Nuty
int length = 99;
char notes[] = "EbCDCbaaCEDCbbCDECaa DFAGFEECEDCbbCDECaa EbCDCbaaCEDCbbCDECaa DFAGFEECEDCbbCDECaa ECDbCab ECDbCEAJ ";
int beats[] =      // Som
{
  2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 4, 2,
  2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 4, 1,
  2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 4, 2,
  2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 4, 1,
  5, 5, 5, 5, 5, 5, 7, 2, 5, 5, 5, 5, 2, 2, 5, 5, 3
};
int tempo = 128;        // Tempo


void playTone(int tone, int duration) {
  for (long i = 0; i < duration * 1000L; i += tone * 2) {
    digitalWrite(speaker_pin, HIGH);
    delayMicroseconds(tone);
    digitalWrite(speaker_pin, LOW);
    delayMicroseconds(tone);
  }
}


void playNote(char note, int duration) {
  char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' , 'D', 'E', 'F', 'G', 'J', 'A', 'B'};
  int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956, 850, 760, 716, 637, 603, 568, 507 };

  for (int i = 0; i < 14; i++) {
    if (names[i] == note) {
      playTone(tones[i], duration);
    }
  }
}


void updateColumn(int colnum)
{
    lc0[colnum] = active[colnum] >> (lcRows / 2);
    lc1[colnum] = active[colnum];
    lc.setColumn(0,colnum,(screen[colnum] >> (lcRows / 2)) | lc0[colnum]);
    lc.setColumn(1,colnum,screen[colnum] | lc1[colnum]);
}



void buttonDelay(int bdelay)
{
    if(brickDelay > bdelay)
    {
        brickDelay -= bdelay;
    }
    delay(bdelay);
}


void splashScreen()
{
     int up[] =
     {
         B11101110,   //   o o o   o o o
         B01001000,   //     o     o
         B01001100,   //     o     o o
         B01001000,   //     o     o
         B01001110,   //     o     o o o
         B00000000,   //
         B11101110,   //   o o o   o o o
         B01001010    //     o     o   o
     };

     int down[] =
     {
         B01001100,   //     o     o o
         B01001010,   //     o     o   o
         B01001001,   //     o     o     o
         B00000000,   //
         B01000111,   //     o       o o o
         B01000100,   //     o       o
         B01000010,   //     o         o
         B01001110    //     o     o o o
     };

     for(int rownum = 0; rownum < 8; rownum++)
     {
         lc.setRow(0,rownum,up[rownum]);
         lc.setRow(1,rownum,down[rownum]);
     }
}


void setup() {
  pinMode(speaker_pin,  OUTPUT);
  pinMode(rotate_button,INPUT_PULLUP);
  pinMode(down_button,  INPUT_PULLUP);
  pinMode(right_button, INPUT_PULLUP);
  pinMode(left_button,  INPUT_PULLUP);
  pinMode(start_button, INPUT_PULLUP);
  pinMode(sound_button, INPUT_PULLUP);

  lc.shutdown(0,false);
  lc.shutdown(1,false);

  lc.setIntensity(0,5);
  lc.setIntensity(1,5);

  lc.clearDisplay(0);
  lc.clearDisplay(1);
  Serial.begin(9600);

  randomSeed(analogRead(0));
}


//LOOP
void loop()
{



 if(started == 0)
 {
     splashScreen();

     for (int i = 0; i < length; i++)
     {

 if(digitalRead(sound_button) == LOW)
 {
  sound =! sound;
  delay(300);
  }

         if(digitalRead(start_button) == LOW)
         {
             started = 1;
             break;
         }
         if (notes[i] == ' ')
         {
             delay(beats[i] * tempo);   //Pausa
         }
         else
         {
          if (sound == 1){
             playNote(notes[i], beats[i] * tempo);
          }
          else
          {
             digitalWrite(speaker_pin,LOW);
            }
         }


        delay(tempo / 2);
     }
 }



 else
 {

     lc.clearDisplay(0);
     lc.clearDisplay(1);
     memset(lc0, 0, sizeof(lc0));
     memset(lc1, 0, sizeof(lc1));
     memset(active, 0, sizeof(active));
     memset(screen, 0, sizeof(screen));
     tmpCol = 0;

     while(started == 1)
     {

         if(allLines < 100)
         {
             level = 0;              //Level 0
         }
         else if(allLines < 200)
         {
             level = 1;              //Level 1
         }
         else if(allLines < 300)
         {
             level = 2;              //Level 2
         }
         else if(allLines < 400)
         {
             level = 3;              //Level 3
         }
         else if(allLines < 500)
         {
             level = 4;              //Level 4
         }
         else
         {
             level = 5;              //Level 5
         }


         defDelay = (5 - level) * 100;
         brickDelay = defDelay;


         if(figura == 0)
         {
              figura = random(1,8);
         }
         else
         {
             figura = figuraNext;
         }
         figuraNext = random(1,8);
         angle = 0;



         switch(figura)
         {
             case 1:
             //"O"
                 active[3] = 131072 + 65536;
                 active[4] = 131072 + 65536;
                 fromLeft = 3;
                 fromRight = 3;
                 break;

             case 2:
             //"L"
                 active[3] = 262144 + 131072 + 65536;
                 active[4] = 65536;
                 fromLeft = 3;
                 fromRight = 3;
                 break;

             case 3:
             //"J"
                 active[3] = 65536;
                 active[4] = 262144 + 131072 + 65536;
                 fromLeft = 3;
                 fromRight = 3;
                 break;

             case 4:
             //"T"
                 active[2] = 131072;
                 active[3] = 131072 + 65536;
                 active[4] = 131072;
                 fromLeft = 2;
                 fromRight = 3;
                 break;

             case 5:
             //"I"
                 active[3] = 524288 + 262144 + 131072 + 65536;
                 fromLeft = 3;
                 fromRight = 4;
                 break;

             case 6:
             //"Z"
                 active[2] = 131072;
                 active[3] = 131072 + 65536;
                 active[4] = 65536;
                 fromLeft = 2;
                 fromRight = 3;
                 break;

             case 7:
             //"S"
                 active[2] = 65536;
                 active[3] = 131072 + 65536;
                 active[4] = 131072;
                 fromLeft = 2;
                 fromRight = 3;
                 break;
         }



         for(int krok = 0; krok < lcRows + 1; krok++)
         {
             colCheck = 0;


             for(int i = 0; i < (lcCols / 2); i++)
             {
                 if((digitalRead(left_button) == LOW) && (fromLeft > 0))
                 {
                     moveCheck = 0;
                     for(int colnum = fromLeft; colnum < (lcCols - fromRight); colnum++)
                     {
                         if((active[colnum] & screen[colnum - 1]) == 0)
                         {
                             moveCheck++;
                         }
                     }

                     if(moveCheck == (lcCols - fromLeft - fromRight))
                     {
                         for(int colnum = (fromLeft - 1); colnum < (lcCols - fromRight); colnum++)
                         {
                             if(colnum < (lcCols - 1))
                             {
                                 active[colnum] = active[colnum+1];
                             }
                             else
                             {
                                 active[colnum] = 0;
                             }
                             updateColumn(colnum);
                         }
                         fromLeft--;
                         fromRight++;
                         playNote('E', 10);
                         buttonDelay(200);
                     }
                 }
             }





             for(int i = 0; i < (lcCols / 2); i++)
             {
                 if((digitalRead(right_button) == LOW) && (fromRight > 0))
                 {
                     moveCheck = 0;
                     for(int colnum = fromLeft; colnum < (lcCols - fromRight); colnum++)
                     {
                         if((active[colnum] & screen[colnum + 1]) == 0)
                         {
                             moveCheck++;
                         }
                     }

                     if(moveCheck == (lcCols - fromLeft - fromRight))
                     {
                         for(int colnum = (lcCols - fromRight); colnum > (fromLeft - 1); colnum--)
                         {
                             if(colnum > 0)
                             {
                                 active[colnum] = active[colnum-1];
                             }
                             else
                             {
                                 active[colnum] = 0;
                             }
                             updateColumn(colnum);
                         }
                         fromLeft++;
                         fromRight--;
                         playNote('E', 10);
                         buttonDelay(200);
                     }
                 }
             }



             if(digitalRead(down_button) == LOW)
             {
                 brickDelay = 0;
                 playNote('b', 10);
             }
             else
             {
                 brickDelay = defDelay;
             }



             for(int i = 0; i < (lcCols / 2); i++)
             {
                 if(digitalRead(rotate_button) == LOW)
                 {

                     switch(figura)
                     {
                         case 1:
                         //"O"
                             break;

                         case 2:
                         //"L"
                             switch(angle)
                             {
                                 case 0:
                                 // . o .        . . .
                                 // . o .  --->  o o o
                                 // . o o        o . .
                                     if((fromLeft > 0)
                                     && (((active[fromLeft + 1] | (active[fromLeft + 1] << 1)) & screen[fromLeft - 1]) == 0))
                                     {
                                         active[fromLeft - 1] = (active[fromLeft + 1] | (active[fromLeft + 1] << 1));
                                         updateColumn(fromLeft - 1);
                                         active[fromLeft] = (active[fromLeft + 1] << 1);
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = (active[fromLeft + 1] << 1);
                                         updateColumn(fromLeft + 1);
                                         fromLeft--;
                                         angle = 1;
                                     }
                                     break;

                                 case 1:
                                 // . . .        o o .
                                 // o o o  --->  . o .
                                 // o . .        . o .
                                     if((((active[fromLeft + 2] << 1) & screen[fromLeft]) == 0)
                                     && ((((active[fromLeft + 1] << 1) | (active[fromLeft + 1] >> 1)) & screen[fromLeft + 1]) == 0))
                                     {
                                         active[fromLeft] = (active[fromLeft + 2] << 1);
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = active[fromLeft + 1] | (active[fromLeft + 1] << 1) | (active[fromLeft + 1] >> 1);
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = 0;
                                         updateColumn(fromLeft + 2);
                                         fromRight++;
                                         angle = 2;
                                     }
                                     break;

                                 case 2:
                                 // o o .        . . o
                                 // . o .  --->  o o o
                                 // . o .        . . .
                                     if((fromRight > 0)
                                     && (((active[fromLeft] >> 1) & screen[fromLeft]) == 0)
                                     && ((((active[fromLeft + 1] << 1) & active[fromLeft + 1]) & screen[fromLeft + 1]) == 0))
                                     {
                                         active[fromLeft] = (active[fromLeft] >> 1);
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = active[fromLeft];
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = ((active[fromLeft + 1] << 1) | active[fromLeft + 1]);
                                         updateColumn(fromLeft + 2);
                                         fromRight--;
                                         krok--;
                                         angle = 3;
                                     }
                                     break;

                                 case 3:
                                 // . . o        . o .
                                 // o o o  --->  . o .
                                 // . . .        . o o
                                     if(((((active[fromLeft] << 1) | (active[fromLeft] >> 1)) & screen[fromLeft + 1]) == 0)
                                     && (((active[fromLeft] >> 1) & screen[fromLeft + 2]) == 0)
                                     && (krok < lcRows))
                                     {
                                         active[fromLeft] = 0;
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = (active[fromLeft + 2] | (active[fromLeft + 2] >> 1));
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = ((active[fromLeft + 2] >> 1) & (active[fromLeft + 2] >> 2));
                                         updateColumn(fromLeft + 2);
                                         fromLeft++;
                                         krok++;
                                         angle = 0;
                                     }
                                     break;
                             }
                             break;

                         case 3:
                         //"J"
                             switch(angle)
                             {
                                 case 0:
                                 // . o .        o . .
                                 // . o .  --->  o o o
                                 // o o .        . . .
                                     if((fromRight > 0)
                                     && ((((active[fromLeft] << 2) | (active[fromLeft] << 1)) & screen[fromLeft]) == 0)
                                     && (((active[fromLeft] << 1) & screen[fromLeft + 2]) == 0))
                                     {
                                         active[fromLeft] = ((active[fromLeft] << 2) | (active[fromLeft] << 1));
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = ((active[fromLeft + 1] << 1) & (active[fromLeft + 1] >> 1));
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = active[fromLeft + 1];
                                         updateColumn(fromLeft + 2);
                                         fromRight--;
                                         krok--;
                                         angle = 1;
                                     }
                                     break;

                                 case 1:
                                 // o . .        . o o
                                 // o o o  --->  . o .
                                 // . . .        . o .
                                     if((krok < lcRows)
                                     && ((((active[fromLeft + 1] << 1) | (active[fromLeft + 1] >> 1)) & screen[fromLeft + 1]) == 0)
                                     && (((active[fromLeft + 2] << 1) & screen[fromLeft + 2]) == 0))
                                     {
                                         active[fromLeft] = 0;
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = (active[fromLeft + 1] | (active[fromLeft + 1] << 1) | (active[fromLeft + 1] >> 1));
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = (active[fromLeft + 2] << 1);
                                         updateColumn(fromLeft + 2);
                                         fromLeft++;
                                         krok++;
                                         angle = 2;
                                     }
                                     break;

                                 case 2:
                                 // . o o        . . .
                                 // . o .  --->  o o o
                                 // . o .        . . o
                                     if((fromLeft > 0)
                                     && (((active[fromLeft + 1] >> 1) & screen[fromLeft - 1]) == 0)
                                     && ((((active[fromLeft + 1] >> 1) | (active[fromLeft + 1] >> 2)) & screen[fromLeft + 1]) == 0))
                                     {
                                         active[fromLeft - 1] = (active[fromLeft + 1] >> 1);
                                         updateColumn(fromLeft - 1);
                                         active[fromLeft] = active[fromLeft - 1];
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = (active[fromLeft] | (active[fromLeft + 1] >> 2));
                                         updateColumn(fromLeft + 1);
                                         fromLeft--;
                                         angle = 3;
                                     }
                                     break;

                                 case 3:
                                 // . . .        . o .
                                 // o o o  --->  . o .
                                 // . . o        o o .
                                     if((((active[fromLeft] >> 1) & screen[fromLeft]) == 0)
                                     && ((((active[fromLeft] << 1) | (active[fromLeft >> 1])) & screen[fromLeft + 1]) == 0))
                                     {
                                         active[fromLeft] = (active[fromLeft] >> 1);
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = ((active[fromLeft + 1] << 1) | active[fromLeft + 2]);
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = 0;
                                         updateColumn(fromLeft + 2);
                                         fromRight++;
                                         angle = 0;
                                     }
                                     break;
                             }
                             break;

                         case 4:
                         //"T"
                             switch(angle)
                             {
                                 case 0:
                                 // . . .        . o .
                                 // o o o  --->  o o .
                                 // . o .        . o .
                                     if(((active[fromLeft + 1] << 1) & screen[fromLeft + 1]) == 0)
                                     {
                                         //active[fromLeft]
                                         active[fromLeft + 1] = active[fromLeft + 1] | (active[fromLeft + 1] << 1);
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = 0;
                                         updateColumn(fromLeft + 2);
                                         fromRight++;
                                         angle = 1;
                                     }
                                     break;

                                 case 1:
                                 // . o .        . o .
                                 // o o .  --->  o o o
                                 // . o .        . . .
                                     if((fromRight > 0)
                                     && ((active[fromLeft] & screen[fromLeft + 2])== 0))
                                     {
                                         //active[fromLeft]
                                         active[fromLeft + 1] = active[fromLeft + 1] & (active[fromLeft + 1] << 1);
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = active[fromLeft];
                                         updateColumn(fromLeft + 2);
                                         fromRight--;
                                         krok--;
                                         angle = 2;
                                     }
                                     break;

                                 case 2:
                                 // . o .        . o .
                                 // o o o  --->  . o o
                                 // . . .        . o .
                                     if((((active[fromLeft + 1] >> 1) & screen[fromLeft + 1]) == 0)
                                     && (krok < lcRows))
                                     {
                                         active[fromLeft] = 0;
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = active[fromLeft + 1] | (active[fromLeft + 1] >> 1);
                                         updateColumn(fromLeft + 1);
                                         //active[fromLeft + 2]
                                         fromLeft++;
                                         krok++;
                                         angle = 3;
                                     }
                                     break;

                                 case 3:
                                     if((fromLeft > 0)
                                     && ((active[fromLeft + 1] & screen[fromLeft - 1]) == 0))
                                     {
                                         active[fromLeft - 1] = active[fromLeft + 1];
                                         updateColumn(fromLeft - 1);
                                         active[fromLeft] = active[fromLeft] & (active[fromLeft] >> 1);
                                         updateColumn(fromLeft);
                                         fromLeft--;
                                         angle = 0;
                                     }
                                     break;
                             }
                             break;

                         case 5:
                         //"I"
                             switch(angle)
                             {
                                 case 0:
                                 // . o . .        . . . .
                                 // . o . .  --->  o o o o
                                 // . o . .        . . . .
                                 // . o . .        . . . .
                                     if((fromLeft > 0)
                                     && (fromRight > 1)

                                     && ((((((active[fromLeft] >> 1) & (active[fromLeft] << 2)) & screen[fromLeft - 1]) & screen[fromLeft + 1]) & screen[fromLeft + 2]) == 0))
                                     {
                                         active[fromLeft - 1] = ((active[fromLeft] >> 1) & (active[fromLeft] << 2));
                                         updateColumn(fromLeft - 1);
                                         active[fromLeft] = active[fromLeft - 1];
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = active[fromLeft];
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = active[fromLeft];
                                         updateColumn(fromLeft + 2);
                                         fromLeft--;
                                         fromRight -= 2;
                                         krok -= 2;
                                         angle = 1;
                                     }
                                     break;

                                 case 1:
                                 // . . . .        . . o .
                                 // o o o o  --->  . . o .
                                 // . . . .        . . o .
                                 // . . . .        . . o .
                                     if((krok < (lcRows - 1))
                                     && (((active[fromLeft] << 1) | (active[fromLeft] >> 1) | (active[fromLeft] >> 2)) & screen[fromLeft + 2]) == 0)
                                     {
                                         active[fromLeft] = 0;
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = 0;
                                         updateColumn(fromLeft + 1);

                                         active[fromLeft + 2] = (active[fromLeft + 2] | (active[fromLeft + 2] << 1) | (active[fromLeft + 2] >> 1) | (active[fromLeft + 2] >> 2));
                                         updateColumn(fromLeft + 2);
                                         active[fromLeft + 3] = 0;
                                         updateColumn(fromLeft + 3);
                                         fromLeft += 2;
                                         fromRight++;
                                         krok += 2;
                                         angle = 2;
                                     }
                                     break;

                                 case 2:
                                 // . . o .        . . . .
                                 // . . o .  --->  . . . .
                                 // . . o .        o o o o
                                 // . . o .        . . . .
                                     if((fromLeft > 1)
                                     && (fromRight > 0)

                                     && ((((((active[fromLeft] << 1) & (active[fromLeft] >> 2)) & screen[fromLeft - 2]) & screen[fromLeft - 1]) & screen[fromLeft + 1]) == 0))
                                     {
                                         active[fromLeft - 2] = ((active[fromLeft] << 1) & (active[fromLeft] >> 2));
                                         updateColumn(fromLeft - 2);
                                         active[fromLeft - 1] = active[fromLeft - 2];
                                         updateColumn(fromLeft - 1);
                                         active[fromLeft] = active[fromLeft - 1];
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = active[fromLeft];
                                         updateColumn(fromLeft + 1);
                                         fromLeft -= 2;
                                         fromRight--;
                                         krok--;
                                         angle = 3;
                                     }
                                     break;

                                 case 3:
                                 // . . . .        . o . .
                                 // . . . .  --->  . o . .
                                 // o o o o        . o . .
                                 // . . . .        . o . .
                                     if((krok < (lcRows))
                                     && (((active[fromLeft] >> 1) | (active[fromLeft] << 1) | (active[fromLeft] << 2)) & screen[fromLeft + 1]) == 0)
                                     {
                                         active[fromLeft] = 0;
                                         updateColumn(fromLeft);

                                         active[fromLeft + 1] = (active[fromLeft + 1] | (active[fromLeft + 1] >> 1) | (active[fromLeft + 1] << 1) | (active[fromLeft + 1] << 2));
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = 0;
                                         updateColumn(fromLeft + 2);
                                         active[fromLeft + 3] = 0;
                                         updateColumn(fromLeft + 3);
                                         fromLeft++;
                                         fromRight += 2;
                                         krok++;
                                         angle = 0;
                                     }
                                     break;
                             }
                             break;

                         case 6:
                         //"Z"
                             switch(angle)
                             {
                                 case 0:
                                 // . . .        . o .
                                 // o o .  --->  o o .
                                 // . o o        o . .
                                     if(((active[fromLeft + 1] & screen[fromLeft]) == 0)
                                     && (((active[fromLeft + 1] << 1) & screen[fromLeft + 1]) == 0))
                                     {
                                         active[fromLeft] = active[fromLeft + 1];
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = (active[fromLeft + 1] << 1);
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = 0;
                                         updateColumn(fromLeft + 2);
                                         fromRight++;
                                         angle = 1;
                                     }
                                     break;

                                 case 1:
                                 // . o .        o o .
                                 // o o .  --->  . o o
                                 // o . .        . . .
                                     if((fromRight > 0)
                                     && ((((active[fromLeft] << 2) & (active[fromLeft] << 1)) & screen[fromLeft]) == 0)
                                     && (((active[fromLeft] & active[fromLeft + 1]) & screen[fromLeft + 2]) == 0))
                                     {
                                         active[fromLeft] = ((active[fromLeft] << 2) & (active[fromLeft] << 1));
                                         updateColumn(fromLeft);
                                         //active[fromLeft + 1]
                                         active[fromLeft + 2] = (active[fromLeft] >> 1);
                                         updateColumn(fromLeft + 2);
                                         fromRight--;
                                         krok--;
                                         angle = 2;
                                     }
                                     break;

                                 case 2:
                                 // o o .        . . o
                                 // . o o  --->  . o o
                                 // . . .        . o .
                                     if((krok < lcRows)
                                     && (((active[fromLeft + 1] >> 1) & screen[fromLeft + 1]) == 0)
                                     && (((active[fromLeft + 2] << 1) & screen[fromLeft + 2]) == 0))
                                     {
                                         active[fromLeft] = 0;
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = (active[fromLeft + 1] >> 1);
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = (active[fromLeft + 2] | (active[fromLeft + 2] << 1));
                                         updateColumn(fromLeft + 2);
                                         fromLeft++;
                                         krok++;
                                         angle = 3;
                                     }
                                     break;

                                 case 3:
                                 // . . o        . . .
                                 // . o o  --->  o o .
                                 // . o .        . o o
                                     if((fromLeft > 0)
                                     && (((active[fromLeft] & active[fromLeft + 1]) & screen[fromLeft - 1]) == 0)
                                     && (((active[fromLeft + 1] >> 1) & screen[fromLeft + 1]) == 0))
                                     {
                                         active[fromLeft - 1] = (active[fromLeft] & active[fromLeft + 1]);
                                         updateColumn(fromLeft - 1);
                                         //active[fromLeft]
                                         active[fromLeft + 1] = (active[fromLeft - 1] >> 1);
                                         updateColumn(fromLeft + 1);
                                         fromLeft--;
                                         angle = 0;
                                     }
                                     break;
                             }
                             break;

                         case 7:
                         //"S"
                             switch(angle)
                             {
                                 case 0:
                                 // . . .        o . .
                                 // . o o  --->  o o .
                                 // o o .        . o .
                                     if(((active[fromLeft + 1] << 1) & screen[fromLeft]) == 0)
                                     {
                                         active[fromLeft] = (active[fromLeft + 1] << 1);
                                         updateColumn(fromLeft);
                                         //active[fromLeft + 1]
                                         active[fromLeft + 2] = 0;
                                         updateColumn(fromLeft + 2);
                                         fromRight++;
                                         angle = 1;
                                     }
                                     break;

                                 case 1:
                                 // o . .        . o o
                                 // o o .  --->  o o .
                                 // . o .        . . .
                                     if((fromRight > 0)
                                     && (((active[fromLeft + 1] << 1) & screen[fromLeft + 1]) == 0)
                                     && (((active[fromLeft] & (active[fromLeft] << 1)) & screen[fromLeft + 2]) == 0))
                                     {
                                         active[fromLeft] = (active[fromLeft] & active[fromLeft + 1]);
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = (active[fromLeft + 1] << 1);
                                         updateColumn(fromLeft + 1);
                                         active[fromLeft + 2] = (active[fromLeft] << 1);
                                         updateColumn(fromLeft + 2);
                                         fromRight--;
                                         krok--;
                                         angle = 2;
                                     }
                                     break;

                                 case 2:
                                 // . o o        . o .
                                 // o o .  --->  . o o
                                 // . . .        . . o
                                     if((krok < lcRows)
                                     && (((active[fromLeft + 1] >> 1) & screen[fromLeft + 2]) == 0))
                                     {
                                         active[fromLeft] = 0;
                                         updateColumn(fromLeft);
                                         //active[fromLeft + 1]
                                         active[fromLeft + 2] = (active[fromLeft + 1] >> 1);
                                         updateColumn(fromLeft + 2);
                                         fromLeft++;
                                         krok++;
                                         angle = 3;
                                     }
                                     break;

                                 case 3:
                                 // . o .        . . .
                                 // . o o  --->  . o o
                                 // . . o        o o .
                                     if((fromLeft > 0)
                                     && ((active[fromLeft + 1] & ((active[fromLeft + 1] >> 1)) & screen[fromLeft - 1]) == 0)
                                     && ((active[fromLeft + 1] & screen[fromLeft]) == 0))
                                     {
                                         active[fromLeft - 1] = (active[fromLeft + 1] & (active[fromLeft + 1] >> 1));
                                         updateColumn(fromLeft - 1);
                                         active[fromLeft] = active[fromLeft + 1];
                                         updateColumn(fromLeft);
                                         active[fromLeft + 1] = (active[fromLeft - 1] << 1);
                                         updateColumn(fromLeft + 1);
                                         fromLeft--;
                                         angle = 0;
                                     }
                                     break;
                             }
                             break;
                     }
                     playNote('E', 10);
                     buttonDelay(200);
                 }
             }


             //Restart
             if(digitalRead(start_button) == LOW)
             {
                 memset(lc0, 0, sizeof(lc0));
                 memset(lc1, 0, sizeof(lc1));
                 memset(active, 0, sizeof(active));
                 memset(screen, 0, sizeof(screen));
                 score = 0;
                 allLines = 0;
                 figura = 0;
                 break;
             }

             for(int colnum = 0; colnum < lcCols; colnum++)
             {

                 if((screen[colnum] & (active[colnum] >> 1)) == 0)
                 {
                     colCheck++;
                 }

                 else
                 {
                     colCheck = 0;
                     if(krok == 0)
                     {
                         started = 0;
                     }
                 }
             }

             if((colCheck == lcCols) && (krok < lcRows))
             {
                 for(int colnum = 0; colnum < lcCols; colnum++)
                 {
                     active[colnum] = active[colnum] >> 1;
                     updateColumn(colnum);
                 }
             }
             else
             {
                 break;
             }
             delay(brickDelay);
         }

         for(int colnum = 0; colnum < lcCols; colnum++)
         {
             screen[colnum] = screen[colnum] | (lc0[colnum] << (lcRows / 2));
             screen[colnum] = screen[colnum] | lc1[colnum];
             lc0[colnum] = 0;
             lc1[colnum] = 0;
             active[colnum] = 0;
         }



         currLines = 0;
         for(int rownum = 0; rownum < lcRows; rownum++)
         {
             colCheck = 0;
             for(int colnum = 0; colnum < lcCols; colnum++)
             {
                 if(((screen[colnum] >> rownum) & 1) == 1)
                 {
                     colCheck++;
                 }
             }
             if(colCheck == lcCols)
             {
                 //Animacja kasowania
                 for(int colnum = 0; colnum < lcCols; colnum++)
                 {
                     tmpCol = ~((int) round(pow(2, rownum)));
                     screen[colnum] = screen[colnum] & tmpCol;
                     updateColumn(colnum);

                     switch(currLines)
                     {
                         case 0:
                             playNote('b', 20);
                             break;
                         case 1:
                             playNote('D', 20);
                             break;
                         case 2:
                             playNote('F', 20);
                             break;
                         case 3:
                             playNote('A', 20);
                             break;
                     }
                     delay(30);

                     tmpCol = (int) (round(pow(2, rownum)) - 1);
                     tmpCol = screen[colnum] & tmpCol;
                     screen[colnum] = (screen[colnum] >> (rownum + 1));
                     screen[colnum] = (screen[colnum] << rownum);
                     screen[colnum] = screen[colnum] | tmpCol;

                 }


                 for(int colnum = 0; colnum < lcCols; colnum++)
                 {
                     updateColumn(colnum);
                 }
                 rownum--;
                 currLines++;
                 allLines++;
             }
         }

         if(currLines > 0)
         {
             score += (int) round(pow(4, currLines-1));
         }
    }

gameOver();
// == Game Over ==

 }
}

void gameOver()
{
  playNote('F', 80);
  playNote('A', 60);
  playNote('F', 80);
  playNote('A', 60);

     int cima[] =
     {
         B11111111,   //     o     o o o
         B11111111,   //   o o o    o
         B11111111,   //   o   o     o o
         B11111111,   //     o     o
         B11111111,   //     o     o o o
         B11111111,   //
         B11111111,   //   o o o   o o o
         B11111111    //     o     o   o
     };

     int baixo[] =
     {
         B11111111,   //     o     o o
         B11111111,   //     o     o   o
         B11111111,   //     o     o     o
         B11111111,   //
         B11111111,   //     o       o o o
         B11111111,   //     o       o
         B11111111,   //     o         o
         B11111111    //     o     o o o
     };

 for(int rownum = 8; rownum >= 0; rownum--)
     {
         lc.setRow(1,rownum,baixo[rownum]);
         delay(100);
     }

     for(int rownum = 8; rownum >= 0; rownum--)
     {
         lc.setRow(0,rownum,cima[rownum]);
         delay(100);
     }

     delay(1800);

}

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

Red Mod
La cinta Kapton es resistente al calor y eléctricamente aislante que se usa mucho en electrónica e impresión 3D. Si has estado en esta afición por un tiempo hay una buena probabilidad de que tengas un rollo a mano.

Una o dos capas de cinta Kapton aplicada a las matrices las hacen menos propensas a lavarse bajo luz brillante. El color de la cinta es lo suficientemente similar a los LEDs rojos que brillan a través con poca dificultad, mientras que la mayoría de la luz ambiente está bloqueada.

La película de enmascarar “Rubylith” (empleada en diseño gráfico) probablemente funcionaría igual de bien, o quizás mejor. Este film puede conseguirse en tiendas de arte decentes de la vieja escuela.

Arduino es una plataforma prácticamente infinita para todo tipo de aplicaciones como lo hemos visto en este humilde blog de entre millones que existen en la web. En este espacio comenzamos con aplicaciones de Hacking, luego incursionamos en la fascinante disciplina de la Robótica y últimamente avanzamos sobre increíble universo de los Videojuegos.

Anteriormente publicamos juegos (basados en la librería VGAx desarrollada por Sandro Maffiodo) como, por ejemplo:

Y juegos más sencillos basados simplemente en LEDs y pulsadores como

En esta ocasión continuaremos con la construcción de juegos sencillos, pero algo más elaborados utilizando matrices de LEDs controladas por el circuito integrado MAX7219.
Pong (o Tele-Pong) fue un videojuego de la primera generación de videoconsolas publicado por Atari, creado por Nolan Bushnell y lanzado el 29 de noviembre de 1972. Pong está basado en el deporte de tenis de mesa (o ping pong). La palabra Pong es una marca registrada por Atari Interactive, mientras que la palabra genérica «pong» es usada para describir el género de videojuegos «bate y bola». La popularidad de Pong dio lugar a una demanda de infracción de patentes y ganada por parte de los fabricantes de Magnavox Odyssey, que poseía un juego similar.
Pong es un juego de deportes en dos dimensiones que simula un tenis de mesa. El jugador controla en el juego una paleta moviéndola verticalmente en la parte izquierda de la pantalla, y puede competir tanto contra un oponente controlado por computadora, como con otro jugador humano que controla una segunda paleta en la parte opuesta. Los jugadores pueden usar las paletas para pegarle a la pelota hacia un lado u otro. El objetivo consiste en que uno de los jugadores consiga más puntos que el oponente al finalizar el juego. Estos puntos se obtienen cuando el jugador adversario falla al devolver la pelota.

Este videojuego es un verdadero clásico y es sumamente adictivo para jugar durante breves intervalos de tiempo. Ya sea esperando un micro o taxi, alguna hora o minutos libres en la Universidad o Colegio, o simplemente de noche hasta que logramos conciliar el sueño.
Esta variante contiene tres niveles de dificultad aumentado la velocidad de desplazamiento de la pelota a medida que avanzamos de nivel. En el último nivel la pelota se desplaza muy rápidamente, no apto para cardíacos, y si logramos superar este nivel ocurre una secuencia que podrán observar en el gameplay que se encuentra al final de la publicación y solo apto para verdaderos fanáticos.

La Matriz de LEDs

Una matriz LEDs es un display formado por múltiples LEDs en distribución rectangular. Existen distintos tamaños, siendo el más habitual los cuadrados de 8×8 LEDs.
Podemos combinar varios módulos para formar un display mucho mayor. En estos display podemos mostrar textos, dibujos o animaciones, como desplazar un texto (scroll).
Por lo demás, son diodos LEDs totalmente normales, organizados en forma de matriz, que tendremos que multiplexar para poder iluminar uno u otro punto.
Si los diodos se unen por el positivo, se dice que son matrices de Ánodo común, y si se une por el negativo decimos que son de Cátodo común. Dependiendo del fabricante podemos encontrar de ambos tipos.

Encender una matriz de LEDs directamente con Arduino requiere emplear una gran cantidad de pines, lo cual supondría un gran desperdicio de recursos. Por este motivo, lo normal es siempre utilizar un controlador específicamente diseñado para esta función. Un controlador habitualmente empleado por ser barato y sencillo es el circuito integrado MAX7219.

Circuito Integrado MAX7219

Encender una matriz de 8×8 LED requeriría 16 señales digitales y un trabajo constante del procesador para refrescar la imagen. Eso es una cantidad enorme de recursos para cualquier autómata, que estaríamos desperdiciando para simplemente encender un display.
Por este motivo, utilizamos el circuito integrado MAX7219 que está especialmente diseñado para encender displays de 7 segmentos y matrices de LEDs, liberando al procesador para hacer tareas mucho más valiosas y productivas.
La comunicación con el circuito integrado MAX7219 se realiza mediante el bus SPI por lo que sólo se requieren 3 pines de Arduino (SS, MOSI y SCK). Además, ni siquiera “ocupamos” en su totalidad estos pines, ya que con el mismo bus podemos controlar múltiples dispositivos.
Por último, las placas MAX7219 generalmente incorporan un puerto de entrada y salida, de forma que podemos combinar múltiples controladores sin ninguna dificultad.
MAX7219 Datasheet

El diagrama esquemático de esta variante de Arduino Pong se observa en la siguiente imagen:

(click para ampliar)

Lista de Componentes
1 Resistencia 10KΩ 0.25W
1 Potenciómetro Lineal 10KΩ
1 Transistor NPN 2SC1815
2 Módulos con Matrices de LEDs 8×8 con Circuito Integrado MAX7219 c/u. Cátodo Común [Color Rojo en caso de aplicar Red Mod]
1 Buzzer Pasivo 5V
1 Placa Arduino Nanino
Librería LedControl desarrollada por wayoda

#include <LedControl.h>

#define DATA_PIN    12
#define CLOCK_PIN   11
#define CS_PIN      10
#define NUM_DEVICES 2
#define KNOB_PIN    A0
#define RANDOM_PIN  A1
#define SPEAKER_PIN 7

#define N_a   440
#define N_f   349
#define N_cH  523
#define N_eH  659
#define N_fH  698
#define N_gS  415

int step_col, step_row, subidas, OLD_X, sensor, row, col, sc, sr, dly;
int row_bar = 0;
int X = 4;
int cnt = 0;
int desarma = 0;

boolean showBars_enabled = false;

LedControl LC = LedControl(DATA_PIN, CLOCK_PIN, CS_PIN, NUM_DEVICES);

byte MSG[8 * 20];
byte SPACE[8] = {
    B00000000,
    B00000000,
    B00000000,
    B00000000,
    B00000000,
    B00000000,
    B00000000,
    B00000000
};
byte W[8] = {
    B00000000,
    B01000010,
    B01000010,
    B01000010,
    B01000010,
    B01011010,
    B01100110,
    B00000000
};
byte I[8] = {
    B00000000,
    B00111100,
    B00011000,
    B00011000,
    B00011000,
    B00011000,
    B00111100,
    B00000000
};
byte N[8] = {
    B00000000,
    B01000010,
    B01100010,
    B01010010,
    B01001010,
    B01000110,
    B01000010,
    B00000000
};
byte E[8] = {
    B00000000,
    B01111110,
    B01000000,
    B01111100,
    B01000000,
    B01000000,
    B01111110,
    B00000000
};
byte R[8] = {
    B00000000,
    B01111100,
    B01000010,
    B01111100,
    B01001000,
    B01000100,
    B01000010,
    B00000000
};

void loose()
{
    delay(80);

    for(row = 0; (row < 16); row++) {
        setROW(row, 0xFF);
        tone(SPEAKER_PIN, (row * 500), 10);
        delay(20);
    }

    delay(130);

    for(row = 15; (row >= 0); row--) {
        setROW(row, 0x00);
        tone(SPEAKER_PIN, (row * 500), 10);
        delay(20);
    }
}

void showBars(boolean show)
{
    showBars_enabled = show;

    if(show) {
        row_bar = (2 + random(5));
    }

    setROW(row_bar, (show == true ? 0xFF : 0x00));
}

void setLED(int l, int c, boolean state)
{
    int disp = 0;

    if(l > 7) {
        l = (l - 8);
        disp = 1;
    }

    LC.setLed(disp, l, c, state);
}

void setROW(int r, char ch)
{
    int disp = 0;

    if(r > 7) {
        r = (r - 8);
        disp = 1;
    }

    LC.setRow(disp, r, ch);
}

void copyLetter(byte *ch,int pos)
{
    int i;

    for(i = 0; (i < 8); i++) {
        *(MSG + i + (pos * 8)) = *( ch + i);
    }
}

void showMSG(int qtde, int tempo)
{
    int idx;

    LC.clearDisplay(0);
    LC.clearDisplay(1);

    for(idx = 0; (idx < (8 * qtde)); idx++) {
        for(row = 0; (row < 16); row++) {
            setROW(row, *(MSG + row + idx));
        }
		
        delay(tempo);
    }

    LC.clearDisplay(0);
    LC.clearDisplay(1);
}

void beep(int frequencyInHertz, long timeInMilliseconds)
{
    int x;
    long delayAmount = (long)(1000000 / frequencyInHertz);
    long loopTime = (long)((timeInMilliseconds * 1000) / (delayAmount * 2));

    for(row = 0; (row < 16); row++) {
        setROW(row,0xFF);
    }

    for (x = 0; (x < loopTime); x++) {
        digitalWrite(SPEAKER_PIN, HIGH);
        delayMicroseconds(delayAmount);
        digitalWrite(SPEAKER_PIN, LOW);
        delayMicroseconds(delayAmount);
    }

    LC.clearDisplay(0);
    LC.clearDisplay(1);

    delay(20);
}

void march()
{
    beep(N_a, 500);
    beep(N_a, 500);
    beep(N_a, 500);
    beep(N_f, 350);
    beep(N_cH, 150);

    beep(N_a, 500);
    beep(N_f, 350);
    beep(N_cH, 150);
    beep(N_a, 1000);

    beep(N_eH, 500);
    beep(N_eH, 500);
    beep(N_eH, 500);
    beep(N_fH, 350);
    beep(N_cH, 150);

    beep(N_gS, 500);
    beep(N_f, 350);
    beep(N_cH, 150);
    beep(N_a, 1000);
}

void winner()
{
    copyLetter(SPACE, 0);
    copyLetter(SPACE, 1);

    copyLetter(W, 2);
    copyLetter(I, 3);
    copyLetter(N, 4);
    copyLetter(N, 5);
    copyLetter(E, 6);
    copyLetter(R, 7);

    copyLetter(SPACE, 8);
    copyLetter(SPACE, 9);

    showMSG(9, 80);

    march();

    setup();
}

void setup() {
    pinMode(KNOB_PIN, INPUT);      // Potentiometer 10K Linear (GND, KNOB_PIN, +5v)
	pinMode(RANDOM_PIN, INPUT);    // Random Seed
    pinMode(SPEAKER_PIN, OUTPUT);  // Passive Buzzer

    LC.shutdown(0, false);
    LC.setIntensity(0, 8);
    LC.clearDisplay(0);

    LC.shutdown(1, false);
    LC.setIntensity(1, 8);
    LC.clearDisplay(1);

    randomSeed(analogRead(RANDOM_PIN) * millis());

    loose();

    dly = 500;

    setLED(15, X, true);
    setLED(15, (X + 1), true);
    setLED(15, (X + 2) ,true);
    OLD_X = (-1);
    subidas = 0;

    step_col = 1;
    step_row = 1;

    sc = step_col;
    sr = step_row;

    row = ((-1) + random(3));
    col = random(8);

    showBars(false);
}

void loop()
{
    sensor = analogRead(KNOB_PIN);

    X = map(sensor, 0, 980, 1, 6);

    if(X != OLD_X) {
        OLD_X = X;
        setROW(15, 0x00);
        setLED(15, (X - 1),true);
        setLED(15, X, true);
        setLED(15, (X + 1), true);
    }

    if(cnt == 0) {
        setLED(row, col, false);

        if((subidas == 1 || random(2) == 1) && showBars_enabled == false && row == 0) {
            showBars(true);
            subidas = 0;
            desarma = (2 + random(5));
        }

        if((subidas == desarma) && (showBars_enabled == true)) {
            showBars(false);
            subidas = 0;
        }

        if(col == 7) {
            sc = -step_col;
            tone(SPEAKER_PIN, 1000, 20);
        }

        if(col == 0) {
            sc = step_col;
            tone(SPEAKER_PIN, 1000, 20);
        }

        if(row == 0) {
            sr = step_row;
            subidas++;
            dly -= 5;
            tone(SPEAKER_PIN, 1000, 20);
        }

        if(dly <= 190) {
            winner();
        }

        if(showBars_enabled == true && sr > 0 && row < row_bar) {
            if(row == (row_bar - 1)) {
                sr = -step_row;
                tone(SPEAKER_PIN, 1000, 20);
            }
        }

        if(row == 14)
        {
            if(col >= (X - 1) && col <= (X + 1)) {
                sr = -step_row;
				tone(SPEAKER_PIN, 1500, 20);
			}
            else if(col == (X - 2) && (sc > 0)) {
                sr = -step_row;
                sc = -step_col;
                tone(SPEAKER_PIN, 1500, 20);
            }
            else if(col == (X + 2) && (sc < 0)) {
                sr = -step_row;
                sc = step_col;
                tone(SPEAKER_PIN, 1500, 20);
            }
        }

        row += sr;
        col += sc;

        if(col == 8) {
            col = 7;
        }

        if(col == (-1)) {
            col = 0;
        }

        setLED(row, col, true);

        if(row == 15) {
            setup();
        }
    }

    cnt++;

    if(cnt == dly) {
        cnt = 0;
    }
}

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

Red Mod
La cinta Kapton es resistente al calor y eléctricamente aislante que se usa mucho en electrónica e impresión 3D. Si has estado en esta afición por un tiempo hay una buena probabilidad de que tengas un rollo a mano.

Una o dos capas de cinta Kapton aplicada a las matrices las hacen menos propensas a lavarse bajo luz brillante. El color de la cinta es lo suficientemente similar a los LEDs rojos que brillan a través con poca dificultad, mientras que la mayoría de la luz ambiente está bloqueada.

La película de enmascarar “Rubylith” (empleada en diseño gráfico) probablemente funcionaría igual de bien, o quizás mejor. Este film puede conseguirse en tiendas de arte decentes de la vieja escuela.

En publicaciones anteriores hemos visto diferentes mecanismos de control para nuestro robot, tanto manuales (Construyendo Nuestro Propio Robot Desde Cero – Parte 3) como autónomos (Arduino Robot Seguidor de Líneas y Laberintos). Ahora veremos una alternativa más, también inalámbrica, al igual que en la publicación anterior (Arduino Robot Controlado por Bluetooth y Android App) y que en estos últimos años ha crecido de manera exponencial en el mercado. El lector perspicaz ya advirtió que se trata de la tecnología Wi-Fi (Wireless Fidelity), omnipresente en el mercado y en nuestros hogares.

En cuanto empieces a buscar algo de Wi-Fi económico, te encontrarás con el módulo Wi-Fi ESP8266, fabricado por ESPRESSIF Systems, el cual es muy parecido a los módulos Bluetooth de la publicación anterior, y que al igual que ellos incluye toda la electrónica necesaria para la comunicación RF en la banda Wi-Fi, así como la pila TCP/IP y se comunica con nosotros a través de un puerto serie.

De hecho, exactamente al igual que los módulos Bluetooth HC-06 y HC-05, se gobierna mediante comandos AT, algo que ya no tiene secretos para los seguidores de este blog, y todo por un precio similar al de los módulos Bluetooth.
Lo primero, es decir que es un módulo muy sencillo y diseñado desde el principio con la “Internet of Things” en mente (IoT), y por eso incluye todo lo necesario para conectarse a un punto de acceso Wi-Fi o actuar por sí mismo como punto de acceso Wi-Fi mediante comandos AT, vía un puerto serie, que puede ser configurado a diferentes velocidades.

Este circuito integrado es una pequeña maravilla tecnológica. Está diseñado y pensado para ser la solución ideal para todo el que necesite un “Todo en uno Wi-Fi” para proyectos de hobby, IoT o producto comercial sin complicaciones. Y lo más importante, lo consigue.

  • Voltaje de alimentación: 3.3V
  • Procesador interno de 32 bits a 80 MHz (se puede incrementar hasta 160MHz).
  • 80KB de DRAM.
  • 35KB de IRAM, memoria rápida para el procesador.
  • 512KB (hasta 1MB, dependiendo de la versión del módulo) de memoria flash para nuestros programas.
  • Pila TCP/IP Wi-Fi a 2.4 GHz (b/g/n).
  • 30 metros de alcance teórico.

Nota del Autor: de hecho, el módulo ESP8266 incluye un pequeño procesador interno que podríamos programar para funcionar de modo autónomo y dispone de un par de puertos GPIO (General Purpose Input Output) para su uso como activadores. Es posible programarlo desde el IDE Arduino, y más interesante aún, con MicroPython.

Lista de Componentes
1 Plataforma Turtle2WD con placa Arduino UNO R3 y Shield de Control de Motores ensamblados anteriormente.
1 Adaptador Sparkfun FTDI USB – UART TTL 3.3V
1 Módulo Wi-Fi ESP8266 (ESP-01)
1 Convertidor Bidireccional de Niveles Lógicos Sparkfun.
1 Capacitor Electrolítico 1000uF 6.3V
2 Baterías AA 1.5V de Alto Rendimiento
1 Portapilas para 2 Baterías AA

Antes de instalar el módulo en nuestro robot, modificaremos algunos parámetros de su configuración mediante comandos AT. Para llevar a cabo esta tarea sólo necesitamos descargar el documento que contiene el set de instrucciones AT ESP8266 y un emulador de terminal RS232.

Set de Instrucciones AT ESP8266

Termite 3.3
Tamaño: 294KB
SHA1 Hash: 5A7DEBD9E1F245EB87FAEF397187752D5CFF1841
Descarga     Mirror

A continuación conectaremos nuestro módulo al Adaptador FTDI USB – UART TTL 3.3V como se muestra en la siguiente imagen:

(click para ampliar)

Ahora, el emulador de terminal (en nuestro caso, Termite) debe estar configurado de la siguiente manera, que son los parámetros por defecto de nuestro módulo Wi-Fi.

Habiendo logrado una conexión exitosa en el paso anterior, ahora debemos enviar los comandos AT necesarios para configurar nuestro módulo. En la siguiente imagen se observa la secuencia de comandos enviados:

AT
AT+CWSAP_DEF="TURTLE2WD","",1,0,4
AT+UART_DEF=19200,8,1,0,0

Nuestros comandos enviados se encuentran en color azul, en color verde se encuentran las respuestas de nuestro módulo y el eco local de la consola. Con la ejecución exitosa de estos comandos modificamos el SSID de nuestro módulo por “TURTLE2WD” para identificarlo fácilmente y redujimos la velocidad del puerto serie a 19200bps lo cual nos permite utilizar cables de mayor longitud para el montaje en caso de ser necesario.

Habiendo finalizado exitosamente los pasos anteriores, compilaremos y cargaremos en nuestro Arduino Uno R3 el sketch correspondiente, cuyo código fuente se encuentra a continuación:

/*
Caracteristicas:
- Control por Wi-Fi utilizando el modulo ESP8266
- Interfaz web con jquey para envio de peticiones GET
- Id de botones para envio de datos
W: Adelante
S: Atras
A: Izquierda
D: Derecha
L: Inicio/Parada Modo Vehiculo

Q: Incrementar Velocidad
T: Disminuir Velocidad
X: Encendido/Apagado Luz Frontal

- Detencion automatica en caso de detectar bordes.
*/

#define     PWM_RIGHT   3     // (Control Velocidad Motor Derecho) Timer2 8 bits D3
#define     PWM_LEFT    11    // (Control Velocidad Motor Izquierdo) Timer2 8 bits D11
#define     DIR_A       12    // (Control de Direccion de Motores) D12
#define     DIR_B       13    // (Control de Direccion de Motores) D13
#define     MAX_SPEED   200   // Velocidad Maxima
#define     MIN_SPEED   100   // Velocidad Minima
// -----

// Iluminacion
#define     LED_LIGHT   7
// -----

// Sensores Reflectivos Detectores de Bordes
#define     BORDER_L    A2    // Sensor Izquierdo Externo
#define     BORDER_R    A5    // Sensor Derecho Externo

// Serial commands
#define     DIR_FORWARD     'W'
#define     DIR_BACKWARD    'S'
#define     DIR_LEFT        'A'
#define     DIR_RIGHT       'D'
#define     START_STOP_V    'L'
#define     INC_SPEED       'Q'
#define     DEC_SPEED       'T'
#define     FRONT_LIGHT     'X'
// -----

#define     DEBUG   false

boolean stopped;

byte speed, connectionid;

char data;

void forward() {
    digitalWrite(DIR_A, LOW);
    digitalWrite(DIR_B, LOW);
}

void backward() {
    digitalWrite(DIR_A, HIGH);
    digitalWrite(DIR_B, HIGH);
}

void rotateleft() {
    digitalWrite(DIR_A, LOW);
    digitalWrite(DIR_B, HIGH);
}

void rotateright() {
    digitalWrite(DIR_A, HIGH);
    digitalWrite(DIR_B, LOW);
}

void stop() {
    analogWrite(PWM_LEFT, 0);
    analogWrite(PWM_RIGHT, 0);
}

void setspeed(boolean sp) {
    if (sp) {
        if (speed < MAX_SPEED) {
            speed += 50;
            analogWrite(PWM_LEFT, speed);
            analogWrite(PWM_RIGHT, speed);
        }
    }
    else
    {
        if (speed > MIN_SPEED) {
            speed -= 50;
            analogWrite(PWM_LEFT, speed);
            analogWrite(PWM_RIGHT, speed);
        }
    }
}

void setlight() {
    digitalWrite(LED_LIGHT, !(digitalRead(LED_LIGHT)));
}

boolean isborder() {
    if ((digitalRead(BORDER_L) == HIGH) || (digitalRead(BORDER_R) == HIGH))
        return true;
    else
        return false;
}

String sendData(String command, const word timeout, boolean debug) {
    String response = "";

    Serial.print(command); // send the read character to the esp8266

    unsigned long time = millis();

    while ((time + timeout) > millis()) {
        while(Serial.available()) {
            // The esp has data so display its output to the serial window
            char c = Serial.read(); // read the next character.
            response += c;
        }
    }

    if (debug)
        Serial.print(response);

    return response;
}

void setup() {
    pinMode(PWM_LEFT, OUTPUT);
    pinMode(PWM_RIGHT, OUTPUT);
    pinMode(DIR_A, OUTPUT);
    pinMode(DIR_B, OUTPUT);
    pinMode(LED_LIGHT, OUTPUT);

    pinMode(BORDER_L, INPUT);
    pinMode(BORDER_R, INPUT);

    digitalWrite(LED_LIGHT, LOW);

    stopped = true;

    speed = MIN_SPEED;

    Serial.begin(19200);

    sendData("AT+CIPMUX=1\r\n", 1000, DEBUG); // configure for multiple connections
    sendData("AT+CIPSERVER=1,80\r\n", 1000, DEBUG); // turn on server on port 80
}

void loop() {
    if (Serial.available()) {
        if (Serial.find("+IPD,")) {
            delay(100);
            connectionid = (Serial.read() - 48);

            Serial.find("cmd=");
            data = Serial.read();

            if (data == START_STOP_V) {
                if (stopped) {
                    forward();

                    analogWrite(PWM_LEFT, speed);
                    analogWrite(PWM_RIGHT, speed);

                    stopped = false;
                }
                else
                {
                    stop();
                    stopped = true;
                }
            }

            switch (data) {
                case DIR_FORWARD:
                    if (!stopped)
                        forward();
                    break;

                case DIR_BACKWARD:
                    if (!stopped)
                        backward();
                    break;

                case DIR_LEFT:
                    if (!stopped)
                        rotateleft();
                    break;

                case DIR_RIGHT:
                    if (!stopped)
                        rotateright();
                    break;

                case INC_SPEED:
                    if (!stopped)
                        setspeed(true);
                    break;

                case DEC_SPEED:
                    if (!stopped)
                        setspeed(false);
                    break;

                case FRONT_LIGHT:
                    setlight();
                    break;

                default:
                    break;
            }

            String closecommand = "AT+CIPCLOSE=" + String(connectionid) + "\r\n";
            sendData(closecommand, 100, DEBUG);
        }
    }

    if (isborder())
        stop();
}

En este punto nos encontramos en la etapa final. A continuación conectaremos nuestro módulo Wi-Fi a la placa Arduino UNO R3, como se observa en la siguiente imagen:

(click para ampliar)

Por último necesitamos la interfaz web desde la cual controlaremos nuestro robot. Consta de un documento HTML y jQuery, una biblioteca multiplataforma de JavaScript, que permite simplificar la manera de interactuar con los documentos HTML, manipular el árbol DOM, manejar eventos, desarrollar animaciones y agregar interacción con la técnica AJAX a páginas web. jQuery es la biblioteca de JavaScript más utilizada.

Interfaz Web
Tamaño: 27.5KB
SHA1 Hash: 0C9AE81B92C8243C362C2DFA94C79065D8D3447E
Descarga


En publicaciones anteriores hemos visto diferentes mecanismos de control para nuestro robot, tanto manuales (Construyendo Nuestro Propio Robot Desde Cero – Parte 3) como autónomos (Arduino Robot Seguidor de Líneas y Laberintos). Ahora veremos una alternativa más, también inalámbrica, mucho más eficiente que los rayos infrarrojos (IR) y muy popular entre los teléfonos móviles (sí, controlaremos nuestro robot a través de nuestro teléfono móvil/tablet). De entre todos los módulos de control inalámbricos existentes para Arduino, el Bluetooth es, en mi humilde opinión, el más interesante. Es un módulo de muy bajo costo, muy sencillo de conectar (solo posee dos pines omitiendo VCC y GND), funciona como un puerto serie estándar, es compatible con cualquier dispositivo existente el mercado actual, como laptops, tablets y teléfonos móviles, lo que le otorga una ventaja más a las mencionadas anteriormente: versatilidad.

Básicamente existen 3 tipos de módulos Bluetooth en el mercado actual:

  • HC-05: Maestro/Esclavo. Puede conectarse a otros módulos (Modo Maestro) o aceptar conexiones de otros módulos comportándose como puente inalámbrico serie (Modo Esclavo).
  • HC-06: Esclavo. Sólo puede aceptar conexiones de otros módulos, comportándose como puente inalámbrico serie (Modo Esclavo).
  • HC-07: Esclavo (de muy bajo costo, menor a HC-06 y escasa documentación. Set de comandos AT limitado).

Para controlar nuestro robot utilizaremos el último módulo de la lista, HC-07, ya que sólo necesitamos establecer una conexión desde nuestro teléfono móvil/tablet hacia nuestro robot para enviar los comandos necesarios.

Lista de Componentes
1 Plataforma Turtle2WD con placa Arduino UNO R3 y Shield de Control de Motores ensamblados anteriormente.
1 Módulo Bluetooth HC-07.
Teléfono móvil o Tablet con Sistema Operativo Android 2.3.3 o superior.
Aplicación “Arduino Bluetooth Controller” (disponible en Google Play Store).

La aplicación “Arduino Bluetooth Controller” nos ofrece diferentes modos para controlar nuestro robot, incluyendo una terminal para enviar y/o recibir datos mediante el enlace bluetooth. Para nuestro caso particular, utilizaremos los modos “Vehicle Mode” y “Controller Mode“. El primero nos permite controlar nuestro robot utilizando el giroscopio y acelerómetro de nuestro teléfono móvil/tablet y el segundo nos permite controlar nuestro robot mediante un joystick táctil.

En primer lugar, debemos descargar e instalar en nuestro teléfono móvil/tablet la aplicación mencionada anteriormente disponible en Google Play Store.
Habiendo finalizado exitosamente el paso anterior, compilaremos y cargaremos en nuestro Arduino Uno R3 el sketch correspondiente, cuyo código fuente se encuentra a continuación:

/*
Caracteristicas:
- Aplicacion "Arduino Bluetooth Controller" (FREE - Play Store)
- Manejo por enlace bluetooth mediante las teclas:
W: Adelante
S: Atras
A: Izquierda
D: Derecha
L: Inicio/Parada Modo Vehiculo
V: Inicio/Parada Modo Joystick

Q: Incrementar Velocidad
T: Disminuir Velocidad
x: Encendido/Apagado Luz Frontal

- Detencion automatica en caso de detectar bordes.
*/

#define     PWM_RIGHT   3     // (Control Velocidad Motor Derecho) Timer2 8 bits D3
#define     PWM_LEFT    11    // (Control Velocidad Motor Izquierdo) Timer2 8 bits D11
#define     DIR_A       12    // (Control de Direccion de Motores) D12
#define     DIR_B       13    // (Control de Direccion de Motores) D13
#define		MAX_SPEED	200   // Velocidad Maxima
#define		MIN_SPEED	100   // Velocidad Minima
// -----

// Iluminacion
#define     LED_LIGHT   7
// -----

// Sensores Reflectivos Detectores de Bordes
#define		BORDER_L	A2    // Sensor Izquierdo Externo
#define		BORDER_R	A5    // Sensor Derecho Externo

// Serial commands
#define     DIR_FORWARD		'W'
#define     DIR_BACKWARD	'S'
#define     DIR_LEFT		'A'
#define     DIR_RIGHT		'D'
#define     START_STOP_V	'L'
#define		START_STOP_J	'V'
#define		INC_SPEED		'Q'
#define		DEC_SPEED		'T'
#define		FRONT_LIGHT		'X'
// -----

boolean stopped;

byte speed;

char data;

void forward() {
	digitalWrite(DIR_A, LOW);
	digitalWrite(DIR_B, LOW);
}

void backward() {
	digitalWrite(DIR_A, HIGH);
	digitalWrite(DIR_B, HIGH);
}

void rotateleft() {
	digitalWrite(DIR_A, LOW);
	digitalWrite(DIR_B, HIGH);
}

void rotateright() {
	digitalWrite(DIR_A, HIGH);
	digitalWrite(DIR_B, LOW);
}

void stop() {
	analogWrite(PWM_LEFT, 0);
	analogWrite(PWM_RIGHT, 0);
}

void setspeed(boolean sp) {
	if (sp) {
		if (speed < MAX_SPEED) {
			speed += 50;
			analogWrite(PWM_LEFT, speed);
			analogWrite(PWM_RIGHT, speed);
		}
	}
	else
	{
		if (speed > MIN_SPEED) {
			speed -= 50;
			analogWrite(PWM_LEFT, speed);
			analogWrite(PWM_RIGHT, speed);
		}
	}
}

void setlight() {
	digitalWrite(LED_LIGHT, !(digitalRead(LED_LIGHT)));
}

boolean isborder() {
	if (!((digitalRead(BORDER_L) == LOW) && (digitalRead(BORDER_R) == LOW)))
		return true;
	else
		return false;
}

void setup() {
	pinMode(PWM_LEFT, OUTPUT);
	pinMode(PWM_RIGHT, OUTPUT);
	pinMode(DIR_A, OUTPUT);
	pinMode(DIR_B, OUTPUT);
	pinMode(LED_LIGHT, OUTPUT);

	pinMode(BORDER_L, INPUT);
	pinMode(BORDER_R, INPUT);

	digitalWrite(LED_LIGHT, LOW);

	stopped = true;

	speed = MIN_SPEED;

	Serial.begin(9600);
}

void loop() {
	if (Serial.available()) {

		data = Serial.read();

		if ((data == START_STOP_J) || (data == START_STOP_V)) {
			if (stopped) {
				forward();

				analogWrite(PWM_LEFT, speed);
				analogWrite(PWM_RIGHT, speed);

				stopped = false;
			}
			else
			{
				stop();
				stopped = true;
			}
		}

		switch (data) {
			case DIR_FORWARD:
				if (!stopped)
					forward();
				break;

			case DIR_BACKWARD:
				if (!stopped)
					backward();
				break;

			case DIR_LEFT:
				if (!stopped)
					rotateleft();
				break;

			case DIR_RIGHT:
				if (!stopped)
					rotateright();
				break;

			case INC_SPEED:
				if (!stopped)
					setspeed(true);
				break;

			case DEC_SPEED:
				if (!stopped)
					setspeed(false);
				break;

			case FRONT_LIGHT:
				setlight();
				break;

			default:
				break;
		}
	}

	if (isborder())
		stop();
}

Ahora procedemos a conectar nuestro módulo Módulo Bluetooth HC-07 de la siguiente manera:

Módulo Bluetooth HC-07 –> Arduino Uno R3
TX      –>   D0 (RX)
RX     –>   D1 (TX)
5V     –>   5V
GND  –>  GND

Ahora encenderemos nuestro robot y lo buscamos en nuestro teléfono móvil/tablet (normalmente con el nombre “HC-07“, aunque esto puede variar) y lo emparejaremos con nuestro dispositivo, la contraseña por defecto es “1234” (sin comillas) o “0000” (sin comillas). En la siguiente imagen observamos nuestro dispositivo emparejado con nuestro robot:

Habiendo finalizado exitosamente el paso anterior, el cual es fundamental para continuar, ahora lanzaremos la aplicación “Arduino Bluetooth Controller“, que aparecerá en nuestro teléfono móvil/tablet con el nombre “ArduinoRC“, observaremos lo que se muestra en la siguiente imagen:

Presionamos el botón “Proceed“.
Luego nos aparecerá una lista de los dispositivos emparejados a nuestro teléfono móvil/tablet. Seleccionamos nuestro robot:

Si la conexión se estableció correctamente, nos aparecerá lo que observamos en la siguiente imagen y presionamos el botón “Vehicle Mode“:

Ahora debemos personalizar la configuración de la aplicación, para esto debemos presionar el menú desplegable y seleccionar la opción “Set Commands“, como se muestra en la siguiente imagen:

Ahora procedemos a personalizar la configuración según las siguientes imágenes:

Finalizada exitosamente la configuración, podremos controlar los movimientos de nuestro robot utilizando el giroscopio y acelerómetro de nuestro teléfono móvil/tablet. El botón de Android controla “Inicio/Parada” de nuestro robot.

Habiendo configurado y verificado el correcto funcionamiento de este modo de control, a continuación regresaremos al inicio de nuestra aplicación y presionaremos el botón “Controller Mode“.

Ahora debemos personalizar la configuración de la aplicación, para esto debemos presionar el menú desplegable y seleccionar la opción “Set Commands“, como se muestra en la siguiente imagen:

Ahora procedemos a personalizar la configuración según las siguientes imágenes:

Finalizada exitosamente la configuración, podremos controlar los movimientos de nuestro robot utilizando el joystick táctil de la aplicación. En la siguiente imagen observamos la función de cada botón del joystick:


Los robots seguidores de línea son robots muy sencillos, que cumplen una única misión: seguir una línea marcada en el suelo normalmente de color negro sobre un tablero blanco (normalmente una línea negra sobre un fondo blanco). Son considerados los “Hola mundo” de la robótica.

Estos robots pueden variar desde los más básicos (van tras una línea única) hasta los robots que recorren laberintos, como el que construiremos en esta publicación. Todos ellos, sin embargo, poseen (por lo general) ciertas partes básicas comunes entre todos:

Sensores: Un rastreador detecta la línea a seguir por medio de sensores. Hay muchos tipos de sensores que se pueden usar para este fin; sin embargo, por razones de costos y practicidad, los más comunes son los sensores infrarrojos (IR), que normalmente constan de un LED infrarrojo y un fototransistor, la línea a seguir, puede ser de color negro con fondo blanco o línea blanca con fondo negro. Nuestro robot ofrecerá ambas opciones modificando simplemente un par de líneas en su código fuente. Utilizaremos 4 módulos detectores cuyo componente principal será el sensor óptico reflectivo TCRT5000 fabricado por Vishay Semiconductors, el cual incluye un filtro que bloquea la luz visible (luz ambiente) lo cual nos evitará varios dolores de cabeza y riesgos de ACV. En las siguientes imágenes observamos el sensor y su correspondiente módulo ya ensamblado. La tensión de alimentación es de 5V que obtendremos de nuestra tarjeta de control Arduino UNO R3 y dicho módulo entrega un valor lógico alto [1] cuando no se refleja la luz infrarroja en la superficie y un valor lógico bajo [0] cuando existe reflejo.

Motores: El robot se mueve utilizando motores. Dependiendo del tamaño, el peso, la precisión del motor, entre otros factores, éstos pueden ser de varias clases: motores de corriente continua (nuestro robot), motores paso a paso o servomotores liberados para giro continuo.

Ruedas: Las ruedas del robot son movidas por los motores. Normalmente se usan ruedas de materiales anti-deslizantes para evitar fallas de tracción. Su tamaño es otro factor a tener en cuenta a la hora de armar el robot.

Fuente de energía: El robot obtiene la energía que necesita para su funcionamiento de baterías o de una fuente de corriente alterna, siendo esta última menos utilizada debido a que le resta independencia al robot.

Tarjeta de control: La toma de decisiones y el control de los motores están generalmente a cargo de un microcontrolador. La tarjeta de control contiene dicho elemento, junto a otros componentes electrónicos básicos que requiere el microcontrolador para funcionar. Utilizaremos una tarjeta de control Arduino Uno R3 en nuestro robot.

Todos los rastreadores basan su funcionamiento en los sensores. Sin embargo, dependiendo de la complejidad del recorrido, el robot debe ser más o menos complejo (y, por ende, utilizar más o menos sensores).

Los rastreadores más simples utilizan 2 sensores, ubicados en la parte inferior de la estructura, uno junto al otro. Cuando uno de los dos sensores detecta el color blanco, significa que el robot está saliendo de la línea negra por ese lado. En ese momento, el robot gira hacia el lado contrario hasta que vuelve a estar sobre la línea. Esto en el caso de los seguidores de línea negra, ya que también hay seguidores de línea blanca. Como se mencionó anteriormente, nuestro robot ofrecerá ambas opciones modificando simplemente un par de líneas en su código fuente.

Lista de Componentes
1 Plataforma Turtle2WD con placa Arduino UNO R3 y Shield de Control de Motores ensamblados anteriormente.
4 Módulos Detectores con Sensor Óptico Reflectivo TCRT5000.
4 Separadores Plásticos 5mm
1 Papel Afiche 100cm x 70cm
1 Rollo de Cinta de Papel Color Blanco 10mm

En primer lugar debemos instalar los cuatro módulos detectores en la parrilla de montaje de sensores de la plataforma Turtle2WD como se observa en las siguientes imágenes:

(click para ampliar)

(click para ampliar)

(click para ampliar)

(click para ampliar)

IMPORTANTE: lo que haremos a continuación es crucial para el correcto funcionamiento del robot. Conectaremos a la placa Arduino los cuatro módulos detectores respetando la numeración y nomenclatura utilizada a continuación que se corresponde con el código fuente provisto, de lo contrario el funcionamiento del robot será errático.

Vista trasera de la plataforma Turtle2WD, de izquierda a derecha (parte frontal de la plataforma: portapilas).

Sensor Izquierdo Externo (Salida “D0”)  –>  A2
Sensor Izquierdo Interno (Salida “D0”)  –>  A3
Sensor Derecho Interno (Salida “D0”)    –>  A4
Sensor Derecho Externo (Salida “D0”)    –>  A5

Los pines “A0” de todos los módulos quedan sin conexión ya que son las salidas analógicas de los sensores y no las utilizaremos en este caso.

Los pines “GND” y “VCC” (5V), los conectaremos a sus correspondientes pines “GND” y “5V” en la placa Arduino para suministrarles una tensión de 5V a cada módulo.

Habiendo conectado correctamente los cuatro sensores reflectivos, sólo resta compilar y cargar en nuestro Arduino UNO R3 el sketch correspondiente, cuyo código fuente se encuentra a continuación:

/*
Caracteristicas:
- Seguidor de Linea (LINEA BLANCA/FONDO NEGRO) o (LINEA NEGRA/FONDO BLANCO).
*/

// Control de Motores: Direccion y Velocidad
// Vista desde atras del movil, de izquierda a derecha
// Parte frontal del movil: Portapilas
#define     PWM_RIGHT   3     // (Control Velocidad Motor Derecho) Timer2 8 bits D3
#define     PWM_LEFT    11    // (Control Velocidad Motor Izquierdo) Timer2 8bits D11
#define     DIR_A       12    // (Control de Direccion de Motores) D12
#define     DIR_B       13    // (Control de Direccion de Motores) D13
// -----

// Sensores Reflectivos
// Vista desde atras del movil, de izquierda a derecha
// Parte frontal del movil: Portapilas
#define     SENSOR_LO   A2    // (Sensor Izquierdo Externo)
#define     SENSOR_LI   A3    // ((Sensor Izquierdo Interno))
#define     SENSOR_RI   A4    // (Sensor Derecho Interno)
#define     SENSOR_RO   A5    // (Sensor Derecho Externo)
// -----

// -----

#define    WEIGHT_LO    8
#define    WEIGHT_LI    4
#define    WEIGHT_RI    2
#define    WEIGHT_RO    1

#define    VMAX         90 //PWM
#define    REDUCTF      0.1f

#define    BLACK        1
#define    WHITE        0

#define    BACKGROUND   BLACK
#define    LINE         WHITE

byte state, leftwheel, rightwheel;

void scan() {
    state = ((digitalRead(SENSOR_LO) == LINE) * WEIGHT_LO) + ((digitalRead(SENSOR_LI) == LINE) * WEIGHT_LI) + ((digitalRead(SENSOR_RI) == LINE) * WEIGHT_RI) + ((digitalRead(SENSOR_RO) == LINE) * WEIGHT_RO);
}

void forward() {
    rightwheel = VMAX;
    leftwheel = VMAX;
    analogWrite(PWM_RIGHT, rightwheel);
    analogWrite(PWM_LEFT, leftwheel);
}

void turnleft() {
    leftwheel = 0;
    rightwheel = VMAX;
    analogWrite(PWM_RIGHT, rightwheel);
    analogWrite(PWM_LEFT, leftwheel);
    
	do {
        scan();
    } while(state != 2 && state != 6);
    //} while(state < 8);
    //} while(state != 2;
}

void turnright() {
    leftwheel = VMAX;
    rightwheel = 0;
    analogWrite(PWM_RIGHT, rightwheel);
    analogWrite(PWM_LEFT, leftwheel);

    do {
        scan();
    } while(state != 4 && state != 6);
    //} while(state != 4 && state != 6);
    //} while(state != 4 && state != 6);
}

void leftcorrection() {
    leftwheel = leftwheel - leftwheel * REDUCTF;
    rightwheel = VMAX;
    analogWrite(PWM_RIGHT, rightwheel);
    analogWrite(PWM_LEFT, leftwheel);
}

void rightcorrection() {
    rightwheel = rightwheel - rightwheel * REDUCTF;
    leftwheel = VMAX;
    analogWrite(PWM_RIGHT, rightwheel);
    analogWrite(PWM_LEFT, leftwheel);
}

void setup() {
    pinMode(PWM_RIGHT, OUTPUT);
    pinMode(PWM_LEFT, OUTPUT);
    pinMode(DIR_A, OUTPUT);
    pinMode(DIR_B, OUTPUT);

    pinMode(SENSOR_LO, INPUT);
    pinMode(SENSOR_LI, INPUT);
    pinMode(SENSOR_RI, INPUT);
    pinMode(SENSOR_RO, INPUT);

    digitalWrite(DIR_A, LOW);
    digitalWrite(DIR_B, LOW);
}

void loop() {
    scan();

    switch (state) {
        case 0:
            // 0000 -> FORWARD
            forward();
            break;

        case 1:
            // 0001 RIGHT CURVE -> TURN RIGHT
            turnright();
            break;

        case 2:
            // 0010 GOING LEFT -> RIGHT CORRECTION
            rightcorrection();
            break;

        case 3:
            // 0011 RIGHT CURVE -> TURN RIGHT
            turnright();
            break;

        case 4:
            // 0100 GOING RIGHT -> LEFT CORRECTION
            leftcorrection();
            break;

        // case 5:
            // // 0101 ???
            // break;

        // case 6:
            // 0110 GOING TO AN UNDEFINED SIDE -> NOTHING TO DO HERE
            // forward();
            // break;

        case 7:
            // 0111 RIGHT CURVE -> TURN RIGHT
            turnright();
            break;

        case 8:
            // 1000 LEFT COURVE -> TURN LEFT
            turnleft();
            break;

        case 9:
            // 1001
            turnright();
            break;

        // case 10:
            // 1010 ??? SHOULD NEVER HAPPEN SAME AS 5
            // break;

        // case 11:
            // // 1011 ??? SAME AS 13
            // break;

        case 12:
            // 1100 LEFT COURVE -> TURN LEFT
            turnleft();
            break;

        // case 13:
            // 1101 ??? SAME AS 11
            // break;

        case 14:
            // 1110 LEFT CURVE -> TURN LEFT SAME AS 7
            turnleft();
            break;

        case 15:
            // 1111 T -> TURN TO RIGHT
            turnright();
            break;

        default:
            // KEEP STATUS
            break;
    }
}

Finalmente, pegando la cinta de papel sobre el afiche diseñaremos el recorrido a realizar por nuestro robot, que puede ser desde una simple línea recta, un bucle en forma de “O”, o un laberinto como el que observamos en el vídeo que se encuentra la final de la publicación.
Antes de accionar el interruptor de encendido para que nuestro robot entre en acción debemos ubicarlo sobre el punto de partida de la siguiente manera:

(click para ampliar)

La memoria, en sentido general, es una función del cerebro y a la misma vez un proceso psíquico que nos permite codificar, almacenar y posteriormente recuperar la información o las vivencias. La memoria surge como resultado de las conexiones sinápticas entre neuronas que, a lo largo del tiempo, crean una serie de redes neuronales de forma que los recuerdos se mantienen relativamente estables en el tiempo.

La memoria es el único paraíso del que no podemos ser expulsados.
(Jean Paul)

En relación con el alcance temporal de la memoria ésta se clasifica en: memoria a corto plazo, a mediano plazo y a largo plazo.

La memoria a corto plazo tiene una capacidad muy limitada, tanto en volumen de almacenamiento como en tiempo de permanencia. La información se puede mantener en la memoria a corto plazo mediante la repetición.

Por ejemplo, cuando nos dicen un número de teléfono y lo vamos repitiendo hasta encontrar un papel donde apuntarlo.

Si uno presta atención a esta información y la elabora (analiza, comprende, relaciona con otras ideas) puede pasar de la memoria a corto plazo a la memoria a largo plazo.

La memoria a largo plazo es prácticamente ilimitada tanto en capacidad como en duración.

La memoria a corto plazo, también denominada “memoria operativa”, es un sistema a partir del cual la persona maneja la información que se obtiene de una interacción directa con el ambiente que le rodea. Generalmente esta información se encuentra limitada a 7 elementos, con una variación de más o menos 2 ítems y puede mantenerse durante una media que oscila entre los 15 y los 30 segundos. No obstante, la memoria a corto plazo se verá más o menos limitada en relación con las capacidades de cada persona y el entrenamiento que ha realizado a lo largo de la vida.

Generalmente la memoria a corto plazo funciona a partir de tres principios muy sencillos: el efecto de primacía, el efecto de recencia y la significatividad.

El efecto de primacía hace referencia al hecho de que las personas recuerdan mejor las cosas que suceden inicialmente (ya sea los primeros ítems de una lista o las primeras palabras de una conversación). El efecto de recencia, al contrario, se refiere a nuestra excelente memoria para los hechos o datos que se presentan al final de una lista o una situación. Así, la tendencia de la memoria a corto plazo será la de transferir a la memoria a largo plazo los datos primeros o últimos; obviando buena parte de los hechos o datos intermedios. No obstante, si las informaciones intermedias tienen un gran significado emocional para las personas, entonces éstas adquirirán la primacía absoluta.

La memoria a corto plazo cumple varias funciones:

  • Retención de la información por un periodo corto de tiempo.
  • Apoyo al aprendizaje del nuevo conocimiento.
  • Comprensión del ambiente.
  • Facilitación del proceso de solución de problemas.

En esta ocasión crearemos un juego con Arduino para entrenar y mejorar el rendimiento de nuestra memoria a corto plazo.

El circuito esquemático de este juego de entrenamiento de la memoria se observa en la siguiente imagen:

Lista de Componentes
5 Resistencias 10KΩ 0.25W
4 Resistencias 390Ω 0.25W
1 LED 5mm Azul [Alto brillo]
1 LED 5mm Amarillo [Alto brillo]
1 LED 5mm Verde [Alto brillo]
1 LED 5mm Rojo [Alto brillo]
4 Pulsadores N.A. o Push-Button N.A.
1 Transistor NPN 2SC1815
1 Buzzer Pasivo 5V
1 Placa Arduino Nanino

/*
  Memory Game with Arduino
  Based on a project by Jeremy Wilson
  Modified by Rui Santos
  Visit: http://randomnerdtutorials.com
*/

#define BLUE_BUTTON     2
#define YELLOW_BUTTON   3
#define GREEN_BUTTON    4
#define RED_BUTTON      5
#define BLUE_LED        7
#define YELLOW_LED      8
#define GREEN_LED       9
#define RED_LED         10
#define BUZZER          12

// Constants
const int tones[] = {1915, 1700, 1519, 1432, 2700}; // tones when you press the LED's - the last one is when you fail.

// Variables
int buttonState[] = {0, 0, 0, 0};         // current state of the button
int lastButtonState[] = {0, 0, 0, 0};     // previous state of the button
int buttonPushCounter[] = {0, 0, 0, 0};
int game_on = 0;
int wait = 0;
int currentlevel = 1; // This is the level (also the number of button presses to pass to next level)
int rando = 0; //initialize random integer for loopgame_on. Will be from 1-4 later.
int butwait = 500; //amount of time to wait for next button input (ghetto de-bounce)
int ledtime = 500; //amount of time each LED flashes for when button is pressed
int n_levels = 10; //number of levels until the game is won
int pinandtone = 0; //This integer is used when the sequence is displayed
int right = 0; //This variable must be 1 in order to go to the next level
int speedfactor = 5; //This is the final speed of the lights and sounds for the last level. This increases as more games are won
int leddelay = 200; //Initializing time for LED. This will decrease as the level increases

long rand_num = 0; //initialize long variable for random number from 0-100.

void playTone(int tone, int duration) {
    for(long i = 0; i < duration * 1000L; i += tone * 2) {
        digitalWrite(BUZZER, HIGH);
        delayMicroseconds(tone);
        digitalWrite(BUZZER, LOW);
        delayMicroseconds(tone);
    }
}

void setup() {
    // initialize inputs:
    randomSeed(analogRead(0));
    pinMode(BLUE_BUTTON, INPUT);
    pinMode(YELLOW_BUTTON, INPUT);
    pinMode(GREEN_BUTTON, INPUT);
    pinMode(RED_BUTTON, INPUT);

    // initialize outputs:
    pinMode(BLUE_LED, OUTPUT);
    pinMode(YELLOW_LED, OUTPUT);
    pinMode(GREEN_LED, OUTPUT);
    pinMode(RED_LED, OUTPUT);
    pinMode(BUZZER, OUTPUT);
}

void loop() {

    int n_array[n_levels];
    int u_array[n_levels];
    int i;

    //clears arrays both "n_array" and "u_array" and starts a new game
    if(game_on == 0) {
        for(i = 0; i < n_levels; i = i + 1) {
            n_array[i] = 0;
            u_array[i] = 0;
            rand_num = random(1, 200);

            if(rand_num <= 50)
                rando = 0;
            else if(rand_num > 50 && rand_num <= 100)
                rando = 1;
            else if(rand_num > 100 && rand_num <= 150)
                rando = 2;
            else if(rand_num <= 200)
                rando = 3;
            //saves a random number in our n_array
            n_array[i] = rando;
        }
        game_on = 1;
    }

    //shows the user the current sequence
    if(wait == 0) {
        delay(200);
        i = 0;

        for(i = 0; i < currentlevel; i = i + 1) {
            leddelay = ledtime/(1+(speedfactor/n_levels)*(currentlevel - 1));
            pinandtone = n_array[i];
            digitalWrite(pinandtone+7, HIGH);
            playTone(tones[pinandtone], leddelay);
            digitalWrite(pinandtone+7, LOW);
            delay(100/speedfactor);
        }
        wait = 1;
    }

    i = 0;
    int buttonchange = 0;
    int j = 0; // This is the current position in the sequence

    while(j < currentlevel) {
        while(buttonchange == 0) {
            for(i = 0; i < 4; i = i + 1) {
            buttonState[i] = digitalRead(i + 2);
            buttonchange = buttonchange + buttonState[i];
            }
        }
        for(i = 0; i < 4; i = i + 1) {
            if(buttonState[i] == HIGH) {
                digitalWrite(i+7, HIGH);
                playTone(tones[i], ledtime);
                digitalWrite(i+7, LOW);
                wait = 0;
                u_array[j] = i;
                buttonState[i] = LOW;
                buttonchange = 0;
            }
        }
        if(u_array[j] == n_array[j]) {
            j++;
            right = 1;
        }
        else {
            right = 0;
            i = 4;
            j = currentlevel;
            wait = 0;
        }
    }

    if(right == 0) {
        delay(300);
        i = 0;
        game_on = 0;
        currentlevel = 1;
        for(i = 0; i < 4; i = i + 1) {
            digitalWrite(i + 7, HIGH);
        }
        playTone(tones[4], ledtime);
        for(i = 0; i < 4; i = i + 1) {
            digitalWrite(i + 7, LOW);
        }
        delay(200);
        for(i = 0; i < 4; i = i + 1) {
            digitalWrite(i+7, HIGH);
        }
        playTone(tones[4], ledtime);
        for(i = 0; i < 4; i = i + 1) {
            digitalWrite(i + 7, LOW);
        }

        delay(500);
        game_on = 0;
    }

    //if you insert the right sequence it levels up
    if(right == 1) {
        currentlevel++;
        wait = 0;
    }

    //if you finish the game
    if(currentlevel == n_levels) {
        delay(500);
        // The following is the victory sound:

        int notes[] = {2, 2, 2, 2, 0, 1, 2, 1, 2};
        int note = 0;
        int tempo[] = {200, 200, 200, 400, 400, 400, 200, 200, 600};
        int breaks[] = {100, 100, 100, 200, 200, 200, 300, 100, 200};

        for(i = 0; i < 9; i = i + 1) {
            note = notes[i];
            digitalWrite(note + 7, HIGH);
            playTone(tones[note], tempo[i]);
            digitalWrite(note + 7, LOW);
            delay(breaks[i]);
        }

        //sets game_on to 0, so it restarts a new game
        game_on = 0;
        currentlevel = 1;
        n_levels = n_levels + 2;
        speedfactor = speedfactor + 1;
    }
}

(click para ampliar)

(click para ampliar)

  • Agradecimientos: A mi prometida por haber grabado el siguiente video demo, por su tolerancia y comprensión. Sin ella nada de esto sería posible. TE AMO POR Y PARA SIEMPRE M.L.V!!! ❤ ❤ ❤ ❤