In this episode, I'm combining the light organ and the servo control into one Arduino UNO..
It's also the first provisional attempt of mounting into the base of the turntable.
How the Light Organ and the Servo Lift Work Together
I want the light organ and the lift to just work together, without me having to worry about them.
In previous posts I have introduced the autonomous light organ and the autonomous lift.
They are now combined on a single Arduino, but still have a life of their own.
As soon as the music plays, the light organ will do its work and starts flashing its LEDs.
At the same time, it will tell the servo to move up and stay up for a while (its lifespan: 150 while loops).
Each time a LED is active, it renews the lifespan. But when there's no LED on, one point is taken from the lifespan.
If all the lifepoints are taken, the servo retreats and moves the LEDS back inside the case.
A video says more than 1000 words:
The code is simple. Search for the word decay to find out what's happening.
#include <Servo.h> #include <fix_fft.h> // servo declaration Servo servo; #define SERVO_PIN 9 #define SERVO_DELAY 25 #define SERVO_BASE 90 #define SERVO_TOP (SERVO_BASE + 61) int servoPos = SERVO_BASE; bool servoUp = false; // light organ declaration #define MUESTRAS 128 // Numero de muestras para el cálculo de la FFT #define LOGM 7 // Logaritmo en base 2 del número de muestras #define BAJOS_MEDIOS 7 // Nº de banda para el corte entre Bajos y Medios #define MEDIOS_AGUDOS 35 // Nº de banda para el corte entre Medios y Agudos #define BPIN 13 // Pin de salida Bajos #define MPIN 12 // Pin de salida Medios #define APIN 11 // Pin de salida Agudos #define TIMERPIN 8 #define MAX_PASADAS 10 // Nº de pasadas para el cálculo de los lÃmites char data[MUESTRAS]; // Array con los valores muestreados (parte real) char im[MUESTRAS]; // Array con los valores muestreados (parte imaginaria) unsigned char salida[MUESTRAS/2]; // Valores obtenidos de la FFT (espectro de 64 bandas) unsigned char bajos,medios,agudos; // Valores calculados para cada canal byte pasada, // nº de pasada para el cáculo de los lÃmites acumBajos,acumMedios,acumAgudos, // acumuladores de veces que se supera el lÃmite limBajos,limMedios,limAgudos; // lÃmites calculados para cada canal // both: // decay is a decreasing counter that tells how long the servo will stay up after the last led flashed. // reset to DECAY each time a led lights up // looses one live each time no leds are active #define DECAY 150U unsigned int uDecay = 0U; // servo functionality void servoInit() { servoUp = false; servo.attach(SERVO_PIN); servoPos = SERVO_BASE; servo.write(SERVO_BASE); delay(1000); } void servoGoUp() { if (servoPos < SERVO_BASE) { servoInit(); } if (servoPos < SERVO_TOP) { servoPos++; servo.write(servoPos); delay(SERVO_DELAY); } } void servoGoDown() { if (servoPos < SERVO_BASE) { servoInit(); } if (servoPos > SERVO_BASE) { servoPos--; servo.write(servoPos); delay(SERVO_DELAY); } } void servoGo() { if (servoUp) { servoGoUp(); } else { servoGoDown(); } } // light organ fuctions /* * Funcion que aplica una ventana de Hann a los datos muestreados para reducir el * efecto de las discontinuidades en los extremos */ void aplicaVentana (char *vData) { double muestrasMenosUno = (double(MUESTRAS) - 1.0); // Como la ventana es simétrica , se calcula para la mitad y se aplica el factor por los dos extremos for (uint8_t i = 0; i < MUESTRAS/2 ; i++) { double indiceMenosUno = double(i); double ratio = (indiceMenosUno / muestrasMenosUno); double factorPeso = 0.5 * (1.0 - cos(6.28 * ratio)); vData[i] *= factorPeso; vData[MUESTRAS - (i + 1)] *= factorPeso; } } void lightOrganSetup() { // Configuramos el prescaler a 32 -> 16Mhz/32 = 500 KHz // como cada conversion son 13 ciclos 500/13 ~ 38.4KHz // Es decir podemos medir en teoria hasta unos 19KHz, // que para este proyecto sobra. bitWrite(ADCSRA,ADPS2,1); bitWrite(ADCSRA,ADPS1,0); bitWrite(ADCSRA,ADPS0,1); // Como la señal es muy baja,utilizamos la referencia interna // de 1.1 V en vez de la de defecto de 5 V. analogReference(INTERNAL); // Salidas para los canales de Bajos,Medios y Agudos pinMode(BPIN,OUTPUT); pinMode(MPIN,OUTPUT); pinMode(APIN,OUTPUT); // debug pinMode(TIMERPIN,OUTPUT); Serial.begin(9600); // Variables para el cálculo de los lÃmites pasada = 0; acumBajos = acumMedios = acumAgudos = 0; limBajos = limMedios = limAgudos = 50; } void lightOrganGo() { // Realizamos el muestreo // Serial.println("Start sampling:"); digitalWrite(TIMERPIN, HIGH); for( int i=0; i < MUESTRAS; i++) { data[i] = analogRead(0)/4 -128; //Convertimos de 0..1024 a -128..127 im[i] = 0; // parte imaginaria = 0 } digitalWrite(TIMERPIN, LOW); /* { Serial.println(" samples\n"); for( int i=0; i < MUESTRAS; i++) { Serial.print(data[i], DEC); Serial.print(","); } Serial.println(" ---"); } */ // Aplicamos la ventana de Hann aplicaVentana (data); /* Serial.println(" window\n"); for( int i=0; i < MUESTRAS; i++) { Serial.print(data[i], DEC); Serial.print(","); } Serial.println(" ---"); */ // Calculamos la FFT fix_fft(data,im,LOGM,0); // Sólo nos interesan los valores absolutos, no las fases, asi que // calculamos el módulos de los vectores re*re + im*im. // Dado que los valores son pequeños utilizamos el cuadrado for (int i=0; i < MUESTRAS/2; i++){ salida[i] = data[i] * data[i] + im[i] * im[i]; } /* Serial.println(" result\n"); for( int i=0; i < MUESTRAS/2; i++) { Serial.print(salida[i], DEC); Serial.print(","); } Serial.println(" ---"); */ // Ahora repartimos las bandas entre las 3 salidas // En vez de sacar la media, utilizo sólo el valor máximo de // una banda bajos = 0; for (int i=2; i < BAJOS_MEDIOS; i++){ bajos += salida[i]; } bajos = bajos/2; medios = 0; for (int i=BAJOS_MEDIOS ; i < MEDIOS_AGUDOS; i++){ medios += salida[i]; } medios = medios/2; agudos = 0; for (int i=MEDIOS_AGUDOS; i < MUESTRAS/2; i++){ agudos += 2*(salida[i]); // jc 20150601 doubled the highs sesitivity } agudos = agudos/2; // Calculamos si el canal correspondiente // supera le lÃmite para encenderlo int siBajos = bajos > limBajos; int siMedios = medios > limMedios; int siAgudos = agudos > limAgudos; digitalWrite(BPIN,siBajos ? HIGH : LOW); digitalWrite(MPIN,siMedios? HIGH : LOW); digitalWrite(APIN,siAgudos? HIGH : LOW); // Utilizamos las veces que se supera para // recalcular el lÃmite y evitar que con los // cambios de volumen los canales se saturen // o no funcionen. acumBajos += siBajos; acumMedios += siMedios; acumAgudos += siAgudos; if ( ++pasada > MAX_PASADAS ) { pasada = 0; limBajos = 20 + acumBajos*5; limMedios = 20 + acumMedios*5; limAgudos = 20 + acumAgudos*5; acumBajos = 0; acumMedios = 0; acumAgudos = 0; } if (siBajos | siMedios /*| siAgudos*/) { // jc 20150601 renew lift up decay if a led is on, // ignore highs because I've made them more sensitive above uDecay = DECAY; } else if (uDecay > 0) { uDecay--; } } // arduino general void setup() { Serial.begin(9600); servoInit(); lightOrganSetup(); // Serial.println("Menu: Up: 1, Down: 0"); } void loop() { // lift debug while (Serial.available() > 0) { uDecay = (Serial.parseInt()); } servoUp = (uDecay > 0); servoGo(); lightOrganGo(); }
Next, I,ll move the code to a Yún and include MQTTing of the Highs, Mids and Lows info to the cloud.