Saltar al contenido

Crea tu propia PISTOLA y PORTAL de RICK y MORTY

Hace bastante tiempo que queremos hacer un espejo infinito pero nunca nos habíamos puesto manos a la obra. Sin embargo, hace unos días se nos ocurrió recrear la pistola y el portal de Rick y Morty y hacer que el espejo infinito sea el portal. Así que si eres fan de la serie, quédate, porque este post te interesa.

Resumen del proyecto - Cómo funciona la pistola y el portal de Rick y Morty

En la serie la pistola se encarga de abrir y cerrar el portal, así que en nuestro caso lo apagará y lo encenderá. Para ello vamos a poner un emisor de infrarrojos en la punta de la pistola y un receptor en el portal. 

Además, como queremos que el proyecto sea lo más parecido posible a la pistola y el portal de la serie, hemos buscado en Internet un diseño para imprimir la pistola en 3D. Por cierto, dos piezas tienen que ser negras, pero como nosotros no teníamos filamento de este color simplemente las hemos pintado.

La pistola tiene un encoder. El propio encoder tiene un botón integrado. La idea es que cuando pulses este botón, el emisor IR mande un mensaje para encender el portal si estaba apagado y viceversa. Si giras el encoder, el emisor envía un mensaje para cambiar el efecto del espejo infinito. 

La propia pistola tiene diodos leds, todos conectados al mismo pin, luego lo veremos. Cuando el portal está encendido, los leds se mantienen encendidos. Si el portal está apagado, los leds se apagan. Por último, cuando el emisor envía un mensaje, los leds de la pistola parpadean. 

Por otro lado está el portal, es decir, el espejo infinito. El espejo infinito tiene casi un metro de leds WS2812B. Simplemente tiene que recibir los mensajes IR que le manda la pistola y hacer una acción determinada dependiendo del mensaje. Esta acción puede ser encender los leds, apagarlos o cambiar de efecto. 

Lista de materiales

Aquí te dejamos la lista de materiaes con sus enlaces de compra. Hay algunos materiales que no tienen enlace. Eso significa que los hemos comprado en una tienda local de nuestra ciudad. ¡Espero que esta lista te sirva de ayuda!

Esquema eléctrico del proyecto

Esquema eléctrico pistola de Rick y Morty
Esquema eléctrico portal de Rick y Morty

Como puedes ver, para controlar tanto la pistola como el portal de Rick y Morty hemos utilizado un Arduino Nano. La batería de litio de la pistola proporciona 3.7V. Este voltaje es suficiente para el emisor IR, los diodos LED y el encoder, pero no para Arduino. Por ello hemos tenido que utilizar un regulador de tensión para transformar los 3.7V en 5V y poder alimentar al Arduino Nano.

Si vas a utilizar la misma librería que nosotros hemos utilizado para trabajar con el emisor y el receptor de infrarrojos, es muy importante que te asegures de conectar el emisor al pin digital 3. Solo funciona con este pin digital. El resto de componentes puedes cambiarlos de pin digital sin problemas.

El portal solo tiene un Arduino Nano, casi un metro de leds WS2812b y el receptor IR. Puedes conectar los leds y el receptor a los pines digitales que quieras. Aquí lo más importante es que alimentes los leds con una fuente de alimentación externa, ya que Arduino no es capaz de aportar la corriente necesaria y lo podrías quemar. 

Programación de la pistola y el portal de Rick y Morty

Estaría muy guay si haces tu propio programa para modificar como funciona tu pistola y portal de Rick y Morty y nos lo pasas por Instagram. Pero si quieres que quede exactamente igual que nuestro proyecto o no sabes programar, por aquí te dejamos nuestro código. Si no sabes cómo subir el código puedes revisar nuestro post de Cómo subir cualquier programa a Arduino.

Código del portal

#include <FastLED.h>
#include <IRremote.h>

#define LED_PIN     3
#define NUM_LEDS    60
#define BRIGHTNESS  20
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define UPDATES_PER_SECOND 100

const int RECV_PIN = 2;
IRrecv irrecv(RECV_PIN);
decode_results results;

CRGB leds[NUM_LEDS];
CRGBPalette16 currentPalette = RainbowColors_p;
TBlendType    currentBlending = LINEARBLEND;

extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;

bool on = false;
int effect = 0;   //0: Green Effect, 1: ColorPalette


void setup() {
  Serial.begin(500000);
  // put your setup code here, to run once:
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
  FastLED.setBrightness(  BRIGHTNESS );
  irrecv.enableIRIn();
  delay(1000);
  for(int i=0; i<NUM_LEDS; i++){
    leds[i].setRGB(0,0,0);
  }
  FastLED.show();
}


void loop() {
  static uint8_t startIndex = 0;

  if (irrecv.decode(&results)){
      Serial.println(results.value, HEX);
      if(results.value == 0xFFA25D){
        if(on==true) on = false;
        else on = true;
      }

      if(on==true){
        switch(results.value){
          case 0xFF30CF:
            effect = 0;
          break;
  
          case 0xFF18E7:
            effect = 1;
          break;
  
          case 0xFF7A85:
            effect = 2;
          break;
  
          case 0xFF10EF:
            effect = 3;
          break;
  
          case 0xFF38C7:
            effect = 4;
          break;
        }
      }
      irrecv.resume();
  }

  if(on == true){
    if(effect==0){
      greenEffect();
    }
    else if(effect==1){
      currentPalette = RainbowColors_p;
      startIndex = startIndex + 1; // motion speed 
      FillLEDsFromPaletteColors( startIndex);
    }
    else if(effect==2){
      currentPalette = RainbowStripeColors_p;
      startIndex = startIndex + 1; // motion speed 
      FillLEDsFromPaletteColors( startIndex);
    }

    else if(effect==3){
      currentPalette = CloudColors_p;
      startIndex = startIndex + 1; // motion speed 
      FillLEDsFromPaletteColors( startIndex);
    }

    else if(effect==4){
      currentPalette = PartyColors_p;
      startIndex = startIndex + 1; // motion speed 
      FillLEDsFromPaletteColors( startIndex);
    }
  }

  else{
    for(int i=0; i<NUM_LEDS; i++){
      leds[i].setRGB(0,0,0);
    }   
  }
  
  if(irrecv.isIdle()){
      FastLED.delay(1000 / UPDATES_PER_SECOND);
  }
}


void greenEffect(){
  static int greenTone = 255;
  static bool rising = true;
  static bool ledsOn[NUM_LEDS] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
                                 ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
                                 ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  static bool ledsOnAux[NUM_LEDS] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
                                    ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
                                    ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

  if(greenTone >= 255){
    rising = false;
    greenTone--;
  }
  
  else if(rising == true) greenTone++;
  else if(greenTone <= 20){
    rising = true;
    greenTone++;
  }
  else if(rising == false) greenTone--;

  for(int i=0; i<NUM_LEDS; i++){
    if(ledsOn[i] == 0) leds[i].setRGB(0,0,0);
    else leds[i].setRGB(0,greenTone,0);
  }

  for(int i=0; i<NUM_LEDS; i++){
    //Gestionar el primer led
    if(i==0){
      if(ledsOn[NUM_LEDS-1]==0 && ledsOn[i]==1) ledsOnAux[i] = 0;
      else if(ledsOn[NUM_LEDS-1]==1 && ledsOn[i]==0) ledsOnAux[i] = 1;
    }
    //Gestinar el segundo led
    else if(i==(NUM_LEDS-1)){
      if(ledsOn[i]==0 && ledsOn[i-1]==1) ledsOnAux[i] = 1;
      else if(ledsOn[i]==1 && ledsOn[i-1]==0) ledsOnAux[i] = 0;
    }
    //Gestionar el resto de LEDs
    else{
      if(ledsOn[i-1] == 0 && ledsOn[i] == 1) ledsOnAux[i] = 0;
      else if(ledsOn[i-1]==1 && ledsOn[i]==0) ledsOnAux[i] = 1;
    }
  }

  for(int i=0; i<NUM_LEDS; i++){
    ledsOn[i] = ledsOnAux[i];
  }
}


//COLOR PALETTE
void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
    uint8_t brightness = 255;
    
    for( int i = 0; i < NUM_LEDS; ++i) {
        leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
        colorIndex += 3;
    }
}


// There are several different palettes of colors demonstrated here.
//
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p,
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p.
//
// Additionally, you can manually define your own color palettes, or you can write
// code that creates color palettes on the fly.  All are shown here.

void ChangePalettePeriodically()
{
    uint8_t secondHand = (millis() / 1000) % 60;
    static uint8_t lastSecond = 99;
    
    if( lastSecond != secondHand) {
        lastSecond = secondHand;
        if( secondHand ==  0)  { currentPalette = RainbowColors_p;         currentBlending = LINEARBLEND; }
        if( secondHand == 10)  { currentPalette = RainbowStripeColors_p;   currentBlending = NOBLEND;  }
        if( secondHand == 15)  { currentPalette = RainbowStripeColors_p;   currentBlending = LINEARBLEND; }
        if( secondHand == 20)  { SetupPurpleAndGreenPalette();             currentBlending = LINEARBLEND; }
        if( secondHand == 25)  { SetupTotallyRandomPalette();              currentBlending = LINEARBLEND; }
        if( secondHand == 30)  { SetupBlackAndWhiteStripedPalette();       currentBlending = NOBLEND; }
        if( secondHand == 35)  { SetupBlackAndWhiteStripedPalette();       currentBlending = LINEARBLEND; }
        if( secondHand == 40)  { currentPalette = CloudColors_p;           currentBlending = LINEARBLEND; }
        if( secondHand == 45)  { currentPalette = PartyColors_p;           currentBlending = LINEARBLEND; }
        if( secondHand == 50)  { currentPalette = myRedWhiteBluePalette_p; currentBlending = NOBLEND;  }
        if( secondHand == 55)  { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; }
    }
}

// This function fills the palette with totally random colors.
void SetupTotallyRandomPalette()
{
    for( int i = 0; i < 16; ++i) {
        currentPalette[i] = CHSV( random8(), 255, random8());
    }
}

// This function sets up a palette of black and white stripes,
// using code.  Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette()
{
    // 'black out' all 16 palette entries...
    fill_solid( currentPalette, 16, CRGB::Black);
    // and set every fourth one to white.
    currentPalette[0] = CRGB::White;
    currentPalette[4] = CRGB::White;
    currentPalette[8] = CRGB::White;
    currentPalette[12] = CRGB::White;
    
}

// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
    CRGB purple = CHSV( HUE_PURPLE, 255, 255);
    CRGB green  = CHSV( HUE_GREEN, 255, 255);
    CRGB black  = CRGB::Black;
    
    currentPalette = CRGBPalette16(
                                   green,  green,  black,  black,
                                   purple, purple, black,  black,
                                   green,  green,  black,  black,
                                   purple, purple, black,  black );
}


// This example shows how to set up a static color palette
// which is stored in PROGMEM (flash), which is almost always more
// plentiful than RAM.  A static PROGMEM palette like this
// takes up 64 bytes of flash.
const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM =
{
    CRGB::Red,
    CRGB::Gray, // 'white' is too bright compared to red and blue
    CRGB::Blue,
    CRGB::Black,
    
    CRGB::Red,
    CRGB::Gray,
    CRGB::Blue,
    CRGB::Black,
    
    CRGB::Red,
    CRGB::Red,
    CRGB::Gray,
    CRGB::Gray,
    CRGB::Blue,
    CRGB::Blue,
    CRGB::Black,
    CRGB::Black
};

Código de la pistola

#include <TM1637Display.h>
#define ENCODER_DO_NOT_USE_INTERRUPTS
#include <Encoder.h>
#include <LowPower.h>
#include <IRremote.h>

#define DISPLAY_BRIGHTNESS 5  //De 0 a 7

const int leds = 12;

const int CLK = 7;
const int DIO = 6;

const int OnOffButton = 2;
volatile unsigned long lastTime = 0;
volatile int threshold = 200;

volatile bool auxportalOn = false;
bool portalOn = false;

int effect = 0;
bool effectChanged = false;
long pos = 0;

IRsend irsend;
Encoder myEnc(5, 4);  
TM1637Display display(CLK,DIO);

const uint8_t letterC[] = {SEG_A | SEG_F | SEG_E | SEG_D};

void setup() {
  // put your setup code here, to run once:
  delay(2000);
  pinMode(leds, OUTPUT);
  pinMode(OnOffButton, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(OnOffButton), OnOffHandler, FALLING);
  display.setBrightness(DISPLAY_BRIGHTNESS);
}

void loop() {
  // put your main code here, to run repeatedly:
  if(auxportalOn == true){
    if(portalOn == false){
      irsend.sendNEC(0xFFA25D, 32);
      effectChanged = true;
      portalOn = true;
      blinkleds();
    }
    long newPos = myEnc.read();
    if(newPos >= (pos+12)){
      effectChanged = true;
      pos = newPos;
      if(effect >= 4) effect = 0;
      else effect++;
    }
    else if(newPos <= (pos-12)){
      effectChanged = true;
      pos = newPos;
      if(effect <= 0) effect = 4;
      else effect--;
    }
    
    if(effectChanged == true){
      effectChanged = false;
      switch(effect){
        case 0:
          irsend.sendNEC(0xFF30CF, 32);
          display.clear();
          display.setSegments(letterC, 1, 0);
          display.showNumberDec(137, false,3,1);
        break;
        case 1:
          irsend.sendNEC(0xFF18E7, 32);
          display.clear();
          display.setSegments(letterC, 1, 0);
          display.showNumberDec(132, false,3,1);
        break;
        case 2:
          irsend.sendNEC(0xFF7A85, 32);
          display.clear();
          display.setSegments(letterC, 1, 0);
          display.showNumberDec(213, false,3,1);
        break;
        case 3:
          irsend.sendNEC(0xFF10EF, 32);
          display.clear();
          display.setSegments(letterC, 1, 0);
          display.showNumberDec(415, false,3,1);
        break;
        case 4:
          irsend.sendNEC(0xFF38C7, 32);
          display.clear();
          display.setSegments(letterC, 1, 0);
          display.showNumberDec(698, false,3,1);
        break;
      }
      blinkleds();
    }
  }

  else if(auxportalOn == false){
    irsend.sendNEC(0xFFA25D, 32);
    portalOn = false;
    blinkleds();
    delay(300);
    digitalWrite(leds, LOW);
    //display.clear();
    LowPower.idle(SLEEP_FOREVER, ADC_OFF, TIMER2_ON, TIMER1_ON, TIMER0_ON, 
                SPI_OFF, USART0_ON, TWI_OFF);
  }
}

void OnOffHandler(){
  if(lastTime > millis()){
    lastTime = 0;
  }

  if((millis()-lastTime)>threshold){
     if(auxportalOn==false) auxportalOn = true; 
     else auxportalOn = false;
     lastTime = millis();
  }
}

void blinkleds(){
  digitalWrite(leds, HIGH);
  delay(300);
  digitalWrite(leds, LOW);
  delay(300);
  digitalWrite(leds, HIGH);
  delay(300);
  digitalWrite(leds, LOW);
  delay(300);
  digitalWrite(leds, HIGH);
}