Saltar al contenido

Construimos unas torres LED con forma de ADN - Ambilight y Vúmetro

Hace casi un año y medio hicimos unas torres led ambilight. Se encendían con los mismos colores que la pantalla del ordenador creando el efecto de que la pantalla es más grande. ¿Y por qué te contamos todo esto? Pues porque hemos construido la segunda versión de las torres. Esta vez no son de madera, las hemos impreso en 3D y ahora tienen forma de ADN y 3 modos de funcionamiento. 

Resumen del proyecto torres LED con forma de ADN

Como ya sabes, estas nuevas torres tienen forma de ADN. Cada torre tiene 19 pisos, y en cada piso hay una placa de metacrilato. El metacrilato tiene un propiedad muy interesante y es que si le aplicas luz, ésta se queda difuminada solo en sus bordes. Aprovechando esta propiedad hemos creado el diseño, el cual lo hemos impreso en nuestra Artillery X1. 

En cada piso además de la placa de metacrilato hay dos leds WS2812B. Por cierto si lijas un poco los bordes del metacrilato se verá mejor la luz difuminada.

Por otro lado, ya hemos mencionado que las torres led tienen 3 modos de funcionamiento. El primero son diferentes efectos de iluminación, el segundo es el de ambilight y el tercero es el de vúmetro. Este último modo consiste en encender y apagar los leds de las torres al ritmo de la música.

En esta página, junto al vídeo, tienes toda la información necesaria para poder repetir el proyecto. Tanto la lista de materiales, como el esquema eléctrico, el diseño 3D y el código que hemos desarrollado. Si decides repetir el proyecto ¡compártelo con nosotros en Instagram!

Lista de materiales

Wemos D1 Mini ESP8266

Wemos D1 Mini

Micrófono MAX9814

Micrófono MAX9814

Botón táctil TTP223

Botón táctil TTP223

PLA de color blanco

PLA de color blanco

Materiales comprados en tienda local: 38 placas de metacrilato de 10x10x0.8 cm y pintura en spray.

Diseño 3D para imprimir, esquema y códigos para descargar

Esquema eléctrico de las torres LED

Esquema eléctrico torres LED

El esquema eléctrico de las torres led es muy sencillo. Hay una torre principal y una torre secundaria. La torre principal tiene una ESP8266 el cual se encarga de controlar los leds, que están conectados a un pin digital, y de leer la señal eléctrica que genera el micrófono a partir de las ondas de sonido que escucha. 

El micrófono lo debes conectar a una entrada analógica. Nosotros hemos elegido la A0. El botón táctil sirve para cambiar de un modo de funcionamiento a otro y lo tienes que conectar a un pin digital. 

La torre secundaria solo tiene una tira led. Para ello hemos conectado el último led de la torre led principal al primer led de la torre secundaria. De esta forma es como si tuviéramos una sola tira led. El programa está preparado para funcionar de esta forma. 

Código

Este proyecto tiene dos programas. Uno es el de la ESP8266, el cual lo tienes que subir a la ESP8266 de la torre led principal. Puedes ir cambiando de un modo a otro pulsando el botón táctil. Sin embargo, para que funcione el ambilight, debes tener conectado el microcontrolador al portátil y ejecutar el segundo programa, que está desarrollado con Python.

Este segundo programa lo que hace es capturar la pantalla del ordenador, leer los colores de cada imagen capturada, calcular que color debe mostrar cada led de la torre y enviarle esta información a la ESP8266. Para que pueda hacer el último paso debes indicar en el programa cual es puerto al que esta conectado tu ESP8266. En mi caso es el «COM3». Si tus torres tienen más o menos pisos que las nuestras, también deberás modificar la variable «NUM_LEDS». 

				
					from turtle import width
import cv2 as cv
from mss import mss
from PIL import Image
import numpy as np
import serial
import time



ARDUINO= serial.Serial("COM3", 115200, timeout=3)

NUM_LEDS = 19

bounding_box = {'top': 0, 'left': 0, 'width': 1920, 'height': 1080}


def screenCapture():
    run = True
    sct = mss()

    with mss() as sct:
        while(run):
            
            screenshot = sct.grab(bounding_box)         #Aproximadamente 30fps
            screenshot = Image.frombytes(
                'RGB', 
                (screenshot.width, screenshot.height), 
                screenshot.rgb, 
            )
            screenshot = np.array(screenshot)
            screenshot = cv.cvtColor(screenshot, cv.COLOR_RGB2BGR)

            X_MAX, Y_MAX, channels = screenshot.shape

            
            X_MAX_div = X_MAX//(NUM_LEDS+1)           #X_MAX // (NUM_LEDS + 1)

            r_left = 0
            g_left = 0
            b_left = 0
            r_right = 0
            g_right = 0
            b_right = 0
            cont = 0

            ARDUINO.write(b'w')


            for i in range(0, X_MAX):
                if cont >= X_MAX_div:
                    b_left = b_left //((Y_MAX//4)*X_MAX_div)
                    g_left = g_left //((Y_MAX//4)*X_MAX_div)
                    r_left = r_left //((Y_MAX//4)*X_MAX_div)

                    b_right = b_right //((Y_MAX//4)*X_MAX_div)
                    g_right = g_right //((Y_MAX//4)*X_MAX_div)
                    r_right = r_right //((Y_MAX//4)*X_MAX_div)

                    sendColour(str(r_left)+"r", str(g_left)+"g", str(b_left)+"b",
                        str(r_right)+"p", str(g_right)+"l", str(b_right)+"k")
                
                
                    b_left = 0
                    g_left = 0
                    r_left = 0

                    b_right = 0
                    g_right = 0
                    r_right = 0

                    cont = 0

                for j in range(0, Y_MAX//4):
                    b_left += screenshot.item(i,j,0)
                    g_left += screenshot.item(i,j,1)
                    r_left += screenshot.item(i,j,2)
                
                for j in range(3*Y_MAX//4, Y_MAX):
                    b_right += screenshot.item(i,j,0)
                    g_right += screenshot.item(i,j,1)
                    r_right += screenshot.item(i,j,2)


                cont += 1

            if cv.waitKey(1) == ord('a'):
                cv.destroyAllWindows()
                run = False


def sendColour(r_left, g_left, b_left, r_right, g_right, b_right):
    

    ARDUINO.write(r_left.encode())
    ARDUINO.write(g_left.encode())
    ARDUINO.write(b_left.encode()) 
    
    
    ARDUINO.write(r_right.encode())
    ARDUINO.write(g_right.encode())
    ARDUINO.write(b_right.encode()) 
    
    

if __name__ == "__main__":
    time.sleep(3)
    screenCapture()
				
			

En caso de que modifiques las torres es posible que necesites hacer algunos cambios en el código. Sin embargo, el programa está preparado para que solo necesites cambiar el valor de ciertas variables:

  • NUM_LEDS: número total de leds contando ambas torres.
  • TOWER_NUM_LEDS: número de pisos que tienen tus torres.
  • button: pin digital al que está conectado el botón.
  • LED_PIN: pin digital al que están conectados los leds.
  • AUDIO_IN_PIN: entrada analógica a la que está conectada en el micrófono.
  • NOISE: filtro para el ruido. Cuanto mayor sea su valor más filtrará el ruido pero también necesitarás poner la música más alta para que el micrófono sea capaz de escucharla. 
				
					#include <dummy.h>
#include <FastLED.h>
//Audio
#define SAMPLES1 10
#define SAMPLES2 1500       // 500 o 2000
#define AUDIO_IN_PIN A0     //Entrada analógica a la que se ha conectado el microfono
#define NOISE 115          //Los valores que estén por debajo de NOISE se ignoran
#define DC_OFFSET 0

int peak = 0;
int vol1[SAMPLES1];
int vol2[SAMPLES2];
byte volCount1 = 0;
byte volCount2 = 0;


//LEDs
#define NUM_LEDS 76
#define TOWER_NUM_LEDS 19
#define MAX_BAR_HEIGHT TOWER_NUM_LEDS
#define HALF_BAR_HEIGHT (MAX_BAR_HEIGHT/2)
#define LED_PIN D4
#define COLOR_ORDER GRB
#define MAX_BRIGHTNESS 220
#define LED_TYPE WS2812B
CRGB leds[NUM_LEDS];
byte base_hue = 0;  //HSV

//COLORPALETTE
#define UPDATES_PER_SECOND 100
CRGBPalette16 currentPalette;
TBlendType    currentBlending;
extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;


//AMBILIGHT
byte R[MAX_BAR_HEIGHT];
byte G[MAX_BAR_HEIGHT];
byte B[MAX_BAR_HEIGHT];
char recv[1];


//BOTÓN
const byte button = D6;
unsigned int operatingMode = 0;
unsigned long lastTime = 0;
const byte threshold = 100;


void setup() {
  // put your setup code here, to run once:
  delay(2000);
  Serial.begin(115200);

  //analogSetWidth(11);
  
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
  FastLED.setBrightness(MAX_BRIGHTNESS);
  currentPalette = RainbowColors_p;
  currentBlending = LINEARBLEND;
  pinMode(button, INPUT);
  attachInterrupt(digitalPinToInterrupt(button), operatingModeHandler, RISING);

  for(int i=0; i<NUM_LEDS; i++){
    leds[i] = 0xff0000;
  }
  FastLED.show();
  delay(2000);

}


void loop() {
  // put your main code here, to run repeatedly:
  switch(operatingMode){
    case 0:
      vumeter1();
      delayMicroseconds(2500);
      break;

    case 1:
      vumeter2();
      delayMicroseconds(2500);
      break;

    case 2:
      colorPalette();
      break;

    case 3:
      ambilight();
      break;
  }
  FastLED.show();
}


//FUNCIONES VÚMETRO
void vumeter1(){ 
  static uint16_t minLvl1=0, maxLvl1=0;
  static uint16_t minLvl2=0, maxLvl2=0;
  int      n, height;
  static int oldHeight = 0;

  n   = analogRead(AUDIO_IN_PIN);                  // Raw reading from mic
  n   = abs(n - 512 - DC_OFFSET); // Center on zero
  //n   = (n <= NOISE) ? 0 : (n - NOISE);             // Remove noise/hum

  if(maxLvl2 > minLvl2 && maxLvl2 > NOISE)
    //height = MAX_BAR_HEIGHT * (maxLvl1 - minLvl2) /(maxLvl2 - minLvl2);
    //height = MAX_BAR_HEIGHT * (maxLvl1-minLvl1)/(maxLvl2-minLvl2);
    height = MAX_BAR_HEIGHT * (1*maxLvl1-NOISE)/(1.1*maxLvl2-NOISE);
  else
    height = 0;

  height = height*0.7 + oldHeight*0.3;

  
  if(height < 0L)       height = 0;      // Clip output
  else if(height >= (MAX_BAR_HEIGHT-1)){
    height = MAX_BAR_HEIGHT - 1;
    peak = height;
  }
  else if(height > peak) peak = height + 1;
  
  //Actualizamos los leds
  for(int i=0; i<TOWER_NUM_LEDS; i++){
    if(i > height || height == 0){
      //Torre principal
      leds[i] = 0x000000;
      leds[2*TOWER_NUM_LEDS-1-i] = 0x000000;

      //Torre secundaria
      leds[2*TOWER_NUM_LEDS+i] = 0x000000;
      leds[4*TOWER_NUM_LEDS-1-i] = 0x000000;
    }
    else{
      //Torre principal
      leds[i] = CHSV(base_hue, 255, 255);
      leds[2*TOWER_NUM_LEDS-1-i] = CHSV(base_hue, 255, 255);

      //Torre secundaria
      leds[2*TOWER_NUM_LEDS+i] = CHSV(base_hue, 255, 255);
      leds[4*TOWER_NUM_LEDS-1-i] = CHSV(base_hue, 255, 255);
    }
  }
  drawPeak(255, 255, 255);
  
  //Bajamos la cima
  EVERY_N_MILLISECONDS(100){
    if(peak>0)
      peak -= 1;
  }

  EVERY_N_MILLISECONDS(200){
    if(base_hue>245) base_hue = 0;
    else base_hue += 10;
  }


  vol1[volCount1] = n;
  if(++volCount1 >= SAMPLES1) volCount1 = 0;
  vol2[volCount2] = n;
  if(++volCount2 >= SAMPLES2) volCount2 = 0;
  

  minLvl1 = 9999;
  maxLvl1 = 0;
  for(int i=0; i<SAMPLES1; i++){
    if(vol1[i] < minLvl1){
      minLvl1 = vol1[i];
    }
    else if(vol1[i] > maxLvl1) maxLvl1 = vol1[i];
  }

  minLvl2 = 9999;
  maxLvl2 = 0;
  for(int i=0; i<SAMPLES2; i++){
    if(vol2[i] < minLvl2)      minLvl2 = vol2[i];
    else if(vol2[i] > maxLvl2) maxLvl2 = vol2[i];
  }

  oldHeight = height;
}


void vumeter2(){
  static uint16_t minLvl21=0, maxLvl21=0;
  static uint16_t minLvl22=0, maxLvl22=0;
  int      n, height;  
  static int oldHeight2 = 0;
  static int peak1=0, peak2=0;

  n   = analogRead(AUDIO_IN_PIN);                  // Raw reading from mic
  n   = abs(n - 512 - DC_OFFSET); // Center on zero

  
  if(maxLvl22 > minLvl22 && maxLvl22 > NOISE){
    //height = MAX_BAR_HEIGHT * (maxLvl1 - minLvl2) /(maxLvl2 - minLvl2);
    //height = MAX_BAR_HEIGHT * (maxLvl1-minLvl1)/(maxLvl2-minLvl2);
    height = MAX_BAR_HEIGHT * (1*maxLvl21-NOISE)/(1.1*maxLvl22-NOISE);
    height = height*0.8 + oldHeight2*0.2;  
    height = height/2 + 1;
  }
  else
    height = 0;


  if(height < 0L)       height = 0;      // Clip output
  else if(height >= HALF_BAR_HEIGHT) height = HALF_BAR_HEIGHT - 1;
  if(height >= (HALF_BAR_HEIGHT-1))     peak1   = MAX_BAR_HEIGHT - 1, peak2=0;// Keep 'peak' dot at top 
  else if((height+HALF_BAR_HEIGHT) > peak1) peak1 = HALF_BAR_HEIGHT + height + 1, peak2 = HALF_BAR_HEIGHT - height - 1;


  
  //Actualizamos los leds
  for(int i=0; i<HALF_BAR_HEIGHT; i++){
    if(i > height || height == 0){
      //Torre principal
      leds[HALF_BAR_HEIGHT - i - 1] = 0x000000;
      leds[TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT + i + 1] = 0x000000;
      //Modificadas (arreglo)
      leds[HALF_BAR_HEIGHT + i + 1] = 0x000000;
      leds[TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT - i - 1] = 0x000000;

      //Torre secundaria
      leds[2*TOWER_NUM_LEDS+HALF_BAR_HEIGHT - i - 1] = 0x000000;
      leds[3*TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT + i + 1] = 0x000000;
      //Modificadas (arreglo)
      leds[2*TOWER_NUM_LEDS+HALF_BAR_HEIGHT + i + 1] = 0x000000;
      leds[3*TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT - i - 1] = 0x000000;
     
    }
    else{
      Wheel(map(i, 0, HALF_BAR_HEIGHT-1, 30, 150), i);
      //leds[HALF_BAR_HEIGHT-i-1] = 0xff0000;
      //leds[HALF_BAR_HEIGHT+i] = 0xff0000;
      //Serial.println(HALF_BAR_HEIGHT+i);
    }
  }

  
  leds[peak1] = 0xffffff;
  leds[2*TOWER_NUM_LEDS-1-peak1] = 0xffffff;
  leds[peak2] = 0xffffff;
  leds[2*TOWER_NUM_LEDS-1-peak2] = 0xffffff;

  leds[2*TOWER_NUM_LEDS+peak1] = 0xffffff;
  leds[4*TOWER_NUM_LEDS-1-peak1] = 0xffffff;
  leds[2*TOWER_NUM_LEDS+peak2] = 0xffffff;
  leds[4*TOWER_NUM_LEDS-1-peak2] = 0xffffff;
  

  //Bajamos la cima
  EVERY_N_MILLISECONDS(100){
    if(peak1 > HALF_BAR_HEIGHT){
      peak1 -= 1;
      peak2 += 1;
    }
  }

  vol1[volCount1] = n;
  if(++volCount1 >= SAMPLES1) volCount1 = 0;
  vol2[volCount2] = n;
  if(++volCount2 >= SAMPLES2) volCount2 = 0;
  
  minLvl21 = 9999;
  maxLvl21 = 0;
  for(int i=0; i<SAMPLES1; i++){
    if(vol1[i] < minLvl21){
      minLvl21 = vol1[i];
    }
    else if(vol1[i] > maxLvl21) maxLvl21 = vol1[i];
  }

  minLvl22 = 9999;
  maxLvl22 = 0;
  for(int i=0; i<SAMPLES2; i++){
    if(vol2[i] < minLvl22)      minLvl22 = vol2[i];
    else if(vol2[i] > maxLvl22) maxLvl22 = vol2[i];
  }

  oldHeight2 = height;
}


// Input a value 0 to 255 to get a color value.
// The colors are a transition r - g - b - back to r.
void Wheel(byte WheelPos, int i) {
  if(WheelPos < 85) {
    leds[HALF_BAR_HEIGHT+i].setRGB(WheelPos * 3, 255 - WheelPos * 3, 0);
    leds[TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT-i].setRGB(WheelPos * 3, 255 - WheelPos * 3, 0);
    leds[HALF_BAR_HEIGHT-i-1].setRGB(WheelPos * 3, 255 - WheelPos * 3, 0);
    leds[TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT+i+1].setRGB(WheelPos * 3, 255 - WheelPos * 3, 0);

    leds[2*TOWER_NUM_LEDS+HALF_BAR_HEIGHT+i].setRGB(WheelPos * 3, 255 - WheelPos * 3, 0);
    leds[3*TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT-i].setRGB(WheelPos * 3, 255 - WheelPos * 3, 0);
    leds[2*TOWER_NUM_LEDS+HALF_BAR_HEIGHT-i-1].setRGB(WheelPos * 3, 255 - WheelPos * 3, 0);
    leds[3*TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT+i+1].setRGB(WheelPos * 3, 255 - WheelPos * 3, 0);
  } 
  
  else if(WheelPos < 170) {
    WheelPos -= 85;
    leds[HALF_BAR_HEIGHT+i].setRGB(255 - WheelPos * 3, 0, WheelPos * 3);
    leds[TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT-i].setRGB(255 - WheelPos * 3, 0, WheelPos * 3);
    leds[HALF_BAR_HEIGHT-i-1].setRGB(255 - WheelPos * 3, 0, WheelPos * 3);
    leds[TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT+i+1].setRGB(255 - WheelPos * 3, 0, WheelPos * 3);

    leds[2*TOWER_NUM_LEDS+HALF_BAR_HEIGHT+i].setRGB(255 - WheelPos * 3, 0, WheelPos * 3);
    leds[3*TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT-i].setRGB(255 - WheelPos * 3, 0, WheelPos * 3);
    leds[2*TOWER_NUM_LEDS+HALF_BAR_HEIGHT-i-1].setRGB(255 - WheelPos * 3, 0, WheelPos * 3);
    leds[3*TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT+i+1].setRGB(255 - WheelPos * 3, 0, WheelPos * 3);
  } 
  
  else {
    WheelPos -= 170;
    leds[HALF_BAR_HEIGHT+i].setRGB(0, WheelPos * 3, 255 - WheelPos * 3);
    leds[TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT-i].setRGB(0, WheelPos * 3, 255 - WheelPos * 3);
    leds[HALF_BAR_HEIGHT-i-1].setRGB(0, WheelPos * 3, 255 - WheelPos * 3);
    leds[TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT+i+1].setRGB(0, WheelPos * 3, 255 - WheelPos * 3);

    leds[2*TOWER_NUM_LEDS+HALF_BAR_HEIGHT+i].setRGB(0, WheelPos * 3, 255 - WheelPos * 3);
    leds[3*TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT-i].setRGB(0, WheelPos * 3, 255 - WheelPos * 3);
    leds[2*TOWER_NUM_LEDS+HALF_BAR_HEIGHT-i-1].setRGB(0, WheelPos * 3, 255 - WheelPos * 3);
    leds[3*TOWER_NUM_LEDS-1+HALF_BAR_HEIGHT+i+1].setRGB(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}

void drawPeak(byte R, byte G, byte B){
  //Torre principal
  leds[peak].setRGB(R, G, B);
  leds[2*TOWER_NUM_LEDS-peak-1].setRGB(R, G, B);

  //Torre secundaria
  leds[2*TOWER_NUM_LEDS+peak].setRGB(R, G, B);
  leds[4*TOWER_NUM_LEDS-peak-1].setRGB(R, G, B);
}




//FUNCIONES COLORPALETTE
void colorPalette(){
  FastLED.delay(1000 / UPDATES_PER_SECOND);
  ChangePalettePeriodically();
    
  static uint8_t startIndex = 0;
  startIndex = startIndex + 1; /* motion speed */
  
  FillLEDsFromPaletteColors( startIndex);
}

void FillLEDsFromPaletteColors( uint8_t colorIndex){
    uint8_t brightness = 255;
    
    for( int i = 0; i < TOWER_NUM_LEDS; ++i) {
        //Torre principal
        leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
        leds[2*TOWER_NUM_LEDS-1-i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);

        //Torre secundaria
        leds[2*TOWER_NUM_LEDS+i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
        leds[4*TOWER_NUM_LEDS-1-i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
        colorIndex += 3;
    }
}

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; }
    }
}

void SetupTotallyRandomPalette(){
    for( int i = 0; i < 16; ++i) {
        currentPalette[i] = CHSV( random8(), 255, random8());
    }
}

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;
    
}

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 );
}

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
};


//FUNCIONES AMBILIGHT
void ambilight(){
  if(Serial.available()>0){
    if(Serial.read()== 'w'){
      for(int i = TOWER_NUM_LEDS-1; i>=0; i--){

        
        while(!Serial.available());
        String rl = Serial.readStringUntil('r');
        int RL = rl.toInt();
        
        while(!Serial.available());
        String gl = Serial.readStringUntil('g');
        int GL = gl.toInt();
        
        while(!Serial.available());
        String bl = Serial.readStringUntil('b');
        int BL = bl.toInt();


        
        while(!Serial.available());
        String pio = Serial.readStringUntil('p');
        int PIO = pio.toInt();
        
        while(!Serial.available());
        String rte = Serial.readStringUntil('l');
        int RTE = rte.toInt();
        
        while(!Serial.available());
        String klj = Serial.readStringUntil('k');
        int KLJ = klj.toInt();
        
        
        leds[i].r = RL;
        leds[i].g = GL;
        leds[i].b = BL;    
        leds[2*TOWER_NUM_LEDS-1-i].r = RL;
        leds[2*TOWER_NUM_LEDS-1-i].g = GL;
        leds[2*TOWER_NUM_LEDS-1-i].b = BL;
        

        
        leds[2*TOWER_NUM_LEDS+i].r = PIO;
        leds[2*TOWER_NUM_LEDS+i].g = RTE;
        leds[2*TOWER_NUM_LEDS+i].b = KLJ;
        leds[4*TOWER_NUM_LEDS-1-i].r = PIO;
        leds[4*TOWER_NUM_LEDS-1-i].g = RTE;
        leds[4*TOWER_NUM_LEDS-1-i].b = KLJ;    
      }
    }
  }
}



//INTERRUPCIONES
ICACHE_RAM_ATTR void operatingModeHandler(){
  if((millis()-lastTime) > threshold){
    if(operatingMode < 3) operatingMode++;
    else operatingMode = 0;
    lastTime = millis();
  }
}