Proyecto: Dimmer con Arduino para tiras de LED Digitales

Dimmer con Arduino para tiras de LED Digitales (controladas por un 1903 IC)

(English version HERE)

Objetivo

En este proyecto usaremos un arduino, conectado via USB a un ordenador y recibiendo comandos de un sketch de processing, para controlar 5 segmentos de tiras de LED. Es el sketch de Processing quien controla los destellos de cada segmento mediante la detección de «beats» en la linea de entrada de micro del ordenador.

ArchitectureArduinoDimmer

Materiales y Software:

–       5 segmentos de 1 metro cada uno de tira de LED digital (controlada por un 1903 IC o compatible). Ejemplo.
–       1 Arduino UNO. Referencia.
–       1 fuente de alimentación de 12 V. Ejemplo.
–       1 cable USB. Ejemplo.
–       1 ordenador con Processing y Arduino IDE instalados.
–       Librerías MINIM y Serial de Processing..
–       Librería FastSPI_LED de Arduino.

Tiras de LED «digitales» o «addressable».

Digital LED Strip
Tira de LED digital de tres contactos. En este caso, cada microcontrolador controla un solo LED. 

Las tiras de LED digitales son físicamente muy parecidas a las tiras normales RGB, pero las primeras permiten controlar el color e iluminación de cada uno de sus LEDs de forma independiente (o en grupos de N LEDs). La ventaja clara de esto es el enorme gasto de cableado que esto representa frente a las tiras RGB, donde cada elemento independiente requiere un cableado distinto. En este ejemplo vamos a controlar 75 grupos de LED (3 LED cada grupo) con solo cinco cableados y salidas de Arduino.

Pero a cambio, las tiras de LED digitales presentan un retardo mayor para actualizar cada elemento, ya que la información pasa secuencialmente del primer elemento hasta el último. En aquellas instalaciones en las que se cubran grandes distancias con una tira de LED el retardo en la actualización puede ser apreciable. En estos casos recomendamos cubrir la distancia total con varios segmentos más pequeños para optimizar el ratio retardo/cableado.

Las tiras digitales de LED incorporan un microcontrolador (un 1903 IC en este caso) que se encarga de decodificar la información que le llega a través de la linea de datos y genera las 3 señales PWM que alimentan los pines R, G y B de los LEDs conectados.

Captura de pantalla 2013-11-18 a las 17.26.00
Esquema de la conexión interna de los diferentes microcontroladores en la tira digital. Cada controlador lee todo el conjunto de comandos, se queda con el suyo, y pasa los restantes al resto de microcontroladores de la tira. En los casos en los que se usen tiras largas, este comportamiento puede causar retardos considerables.

DATASHEET (Japonés) del microcontrolador: UCS1903. (La ayuda de Google Translator puede venir bien para descifrar su contenido).

Librería de Arduino Fast_SPI2

Web: https://code.google.com/p/fastspi/
Comunidad en Google: https://plus.google.com/communities/109127054924227823508
Documentación: https://code.google.com/p/fastspi/wiki/Documentation

Esta librería, publicada bajo una licencia del MIT, ofrece una manera sencilla para controlar tiras de LED digitales controladas por distintos chipsets. La lista de los distintos microcontroladores que pueden manejarse con esta librería está disponible aquí. En nuestro caso, el chipset 1903IC es compatible con el WS2811.

Para seleccionar el chipset correcto, deberemos crear un objeto LED de la clase que ofrece la librería de la siguiente forma:

LEDS.addLeds<MICROCONTROLLER, PIN>(COLOR_DATA, NUM_LEDS);

Ejemplo:

LEDS.addLeds<WS2811, 13>(leds, 15);

La funcionalidad que ofrece la librería es bastante básica y ofrece algunos ejemplos que nos permiten comenzar a jugar casi instantáneamente con las tiras de LED.

El ciclo de trabajo con las tiras de LED digitales es siempre el mismo:

Actualiza valores color LEDs -> Enciende LEDs -> espera N microsegundos -> Apaga LEDs

Un ejemplo:

for(int i = 0; i < 3; i++) {
for(int iLed = 0; iLed < NUM_LEDS; iLed++) {
memset(leds, 0,  NUM_LEDS * sizeof(struct CRGB));
 
switch(i) {
// You can access the rgb values by field r, g, b
case 0: leds[iLed].r = 128; break;
 
// or by indexing into the led (r==0, g==1, b==2)
case 1: leds[iLed][i] = 128; break;
 
// or by setting the rgb values for the pixel all at once
case 2: leds[iLed] = CRGB(0, 0, 128); break;
}
 
// and now, show your led array!
LEDS.show();
delay(10);
}

Nuestro sistema:

Como veréis, el esquema de conexionado no puede ser más sencillo. Usaremos la fuente de alimentación para alimentar las tiras de LED que necesitan potencias mayores. El Arduino lo alimentaremos directamente desde el ordenador via USB.

El el sketch de Arduino (que podéis descargar abjo) definimos los pines 9 a 13 como las salidas a las que irán conectadas directamente las líneas de datos de cada tira de LED.

Esquema circuito ArduinoDimmer
En este esquema podéis ver como los pines de salida del Arduino (del 9 al 13) se conectan directametne con el pin de datos de cada tira de LED. (Haz click en la imagen para agrandarla)

Arduino Sketch

Podéis descargar el archivo aquí: vjspain_ArduinoLedDimmer.ino.

/*
Kike Ramirez - vjspain.com, november, 2013
Arduino based Dimmer controlling 5 segments of digital LED Strips with microcontroller WS2811.
Each segment contains 15 (NUM_LEDS) groups of 3 independents LEDs.
CC Creative Commons v3.0 - http://creativecommons.org/licenses/by/3.0/deed.es_ES
*/
#include "FastSPI_LED2.h"
// Number of independents LEDs in each strip segment
#define NUM_LEDS 15
// Output PIN (Data LED strip connection) definition
#define DATA_PIN1 13
#define DATA_PIN2 12
#define DATA_PIN3 11
#define DATA_PIN4 10
#define DATA_PIN5 9
// This is an array of leds. One item for each led in your strip. 5 segments in this case.
CRGB leds1[NUM_LEDS];
CRGB leds2[NUM_LEDS];
CRGB leds3[NUM_LEDS];
CRGB leds4[NUM_LEDS];
CRGB leds5[NUM_LEDS];
// Data reception via serial counter.
int incomingByte = 0;
// String buffer
char cadena[4];
byte contador=0;
int valor=0;
// Flags
boolean sb1, sb2, sb3, sb4;
void setup() {
// Opens serial oprt and set 9600bps
 Serial.begin(9600); 

 // Initialize each segment
 FastLED.addLeds<WS2811, DATA_PIN1, RGB>(leds1, NUM_LEDS);
 FastLED.addLeds<WS2811, DATA_PIN2, RGB>(leds2, NUM_LEDS);
 FastLED.addLeds<WS2811, DATA_PIN3, RGB>(leds3, NUM_LEDS);
 FastLED.addLeds<WS2811, DATA_PIN4, RGB>(leds4, NUM_LEDS);
 FastLED.addLeds<WS2811, DATA_PIN5, RGB>(leds5, NUM_LEDS);
}
void loop() {

 // Initialize flags
 sb1 = false;
 sb2 = false;
 sb3 = false;
 sb4 = false;
 if (Serial.available() >= 4) // this was == 4, more than one command may be waiting
// Memory allocation
 memset(cadena, 0, sizeof(cadena));
{ 
 for (int i=0; i < 4; i++) 
 { 
 cadena[i] = Serial.read(); 
 } 

 if (strcmp(cadena, " B1") == 0) // If B1 is received, update in red.
 { 

 sb1 = true;
 destello(255,0,0);

 } 

 else if (strcmp(cadena, " B2") == 0) // If B2 is received, update in green
 { 

 sb2 = true;
 destello(0,255,0);

 } 

 else if (strcmp(cadena, " B3") == 0) // If B3 is received, update in blue
 { 

 sb3 = true;
 destello(0,0,255);

 } 

 else if (strcmp(cadena, " B4") == 0) // If B4 is received, update in white
 { 

 sb4 = true;
 destello(255,255,255);

 }
 }

 // Once updated, flash it!
 pulsoLeds();
}
 
// Funcion to turn all LEDs off
void apagarLeds() {

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

 leds1[i].r = 0;
 leds1[i].g = 0;
 leds1[i].b = 0;

 }
for (int i = 0; i < NUM_LEDS; i++) {

 leds2[i].r = 0;
 leds2[i].g = 0;
 leds2[i].b = 0;

 }

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

 leds3[i].r = 0;
 leds3[i].g = 0;
 leds3[i].b = 0;

 }
for (int i = 0; i < NUM_LEDS; i++) {

 leds4[i].r = 0;
 leds4[i].g = 0;
 leds4[i].b = 0;

 }

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

 leds5[i].r = 0;
 leds5[i].g = 0;
 leds5[i].b = 0;

 }
}
// Function to flash all LEDs
void pulsoLeds() {

 FastLED.show();
 delay(10);
 apagarLeds();
 FastLED.show();

}
// Function to update LEDs with a colour
void destello(int r, int g, int b) {

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

 leds1[i].r = r;
 leds1[i].g = g;
 leds1[i].b = b;

 }
for (int i = 0; i < NUM_LEDS; i++) {

 leds2[i].r = r;
 leds2[i].g = g;
 leds2[i].b = b;

 }
for (int i = 0; i < NUM_LEDS; i++) {

 leds3[i].r = r;
 leds3[i].g = g;
 leds3[i].b = b;

 }
for (int i = 0; i < NUM_LEDS; i++) {

 leds4[i].r = r;
 leds4[i].g = g;
 leds4[i].b = b;

 }
for (int i = 0; i < NUM_LEDS; i++) {

 leds5[i].r = r;
 leds5[i].g = g;
 leds5[i].b = b;

 } 

}

El sketch es bastante simple. Va «oyendo» continuamente lo que le llega a través del puerto serial y analiza las cadenas de texto recibidas. Según el comando que reciba (las posibilidades son B1, B2, B3 y B4), hará un «flash» con los LEDs en rojo, verde, azul o blanco.

Implementa las funciones:

  • apagarLeds(), apaga todos los LEDs.
  • destello() actualiza los valores de color en memoria para cada LED según el color que se le pase como entrada.
  • pulsoLeds() enciende todos los LEDs (según su color en memoria), espera un tiempo y vuelve a apagarlos. Hace un pulso de luz.

Processing Sketch

Podéis descargar el archivo aquí: vjspain_ArduinoLEDSoundAnalysis.pde

/*
Kike Ramirez - vjspain.com, november, 2013
Sound Analysis Skech. Beat Detector used to send serial data to an Arduino connected via USB to control digital LED Strips.
CC Creative Commons v3.0 - http://creativecommons.org/licenses/by/3.0/deed.es_ES
*/
import ddf.minim.*;
import ddf.minim.analysis.*;
import processing.serial.*;
// Declarations
Minim minim;
AudioInput in;
BeatDetect beat;
Serial myPort;
void setup()
{
 size(512, 200, P3D);

 // Serial port opening at 9600bps
 myPort = new Serial(this, Serial.list()[0], 9600);

 // Initialize Minim object
 minim = new Minim(this);

 // use the getLineIn method of the Minim object to get an AudioInput
 in = minim.getLineIn();

 // Initialize beat detector to 1024 samples and 44100 samples per second
 beat = new BeatDetect(1024,44100);

 // uncomment this line to *hear* what is being monitored
 // in.enableMonitoring();
}
void draw()
{
 background(0);
 stroke(255);

 // draw the waveforms so we can see what we are monitoring
 for(int i = 0; i < in.bufferSize() - 1; i++)
 {
 line( i, 50 + in.left.get(i)*50, i+1, 50 + in.left.get(i+1)*50 );
 line( i, 150 + in.right.get(i)*50, i+1, 150 + in.right.get(i+1)*50 );
 }

 // Draw beat detection as a rectangle
 beat.detect(in.left.toArray());

 // If "Hat" is detected sent B1
 if (beat.isHat()) {

 fill(255,0,0);
 rect(0,0,20,20);
 myPort.write(" B1");

 }
// If "Kick" is detected sent B2
 if (beat.isKick()) {

 fill(0,255,0);
 rect(0,30,20,20);
 myPort.write(" B2");

 }
// If "Onset" is detected sent B3
 if (beat.isOnset()) {

 fill(0,0,255);
 rect(0,60,20,20);
 myPort.write(" B3");

 }
// If "Snare" is detected sent B4
 if (beat.isSnare()) {

 fill(255);
 rect(0,90,20,20);
 myPort.write(" B4");
}

}

El archivo de processing también es realmente básico. Enumeremos lo que hace:

  • Monitoriza la entrada de micro de ordenador continuamente.
  • Representa la onda capturada gráficamente (en amplitud).
  • Usa la clase BeatDetect de la librería Minim, la cual detecta distintos tipos de beats, según la frecuencia/potencia del sonido. Para saber más de este análisis, mira aquí.
  • Para cada tipo de beat, dibuja un rectángulo en la ventana del sketch con el color correspondiente.
  • Envía el comando adecuado al Arduino via puerto serial.

¿Por qué esta arquitectura?

¿Por qué no usar un Arduino en configuración stand-alone con un micro incorporado? Todos los análisis de sonido podrían hacerse en el propio Arduino y podríamos prescindir del ordenador y del sketch de processing.

Sabemos que esta puede no ser la configuración óptima para controlar tiras de LED digitales, pero se trata de un primer paso en busca de nuestro objetivo final.

¿Para qué deberíamos ocuparnos de crear todo el software de análisis y generar todas las opciones de representación, los efectos, la interfaz de usuario, etc. si podemos encontrar múltiples softwares como MadMapper o cientos de controladores DMX que ya lo hacen?

Aquí tan solo estamos creando un dimmer para tiras de LED digitales. Y el objetivo final es crear un dimmer DMX para tiras digitales. Nuestro dimmer, una vez finalizado, podrá conectarse a una ENTTEC para ser manejado directamente usando la funcionalidad de Madmapper o de cualquier controlador DMX, tal como se hace normalmente con las tiras RGB.

Esa es la razón por la que elegimos hacer un dimmer que no incorporara el análisis de sonido y se controlara externamente, via Serial en un principio. Este ha sido el primer paso.

Os mantendremos informados de la evolución del proyecto. Vuestros comentarios y sugerencias son, como siempre, bienvenidos!!!