Wednesday, September 12, 2012

Arduino light organ (II)


<< Arduino light organ (I) - Introduction             Arduino light organ (III) - Power stage >>

Obtaining the signal

As I mentioned in the previous post, in my case I am going to use a microphone as a signal source, this eliminates the need to connect directly and provides more freedom of movement.

I've used a Dealextreme module, the SKU: 135533, a sound detector module with digital output and analog. Here we use the latter.
As the level of the signal delivery is very small, I used an ancient u741 (but functional) that had a drawer, to adjust the input signal to the Arduino.
It is also possible to directly use an electret microphone, but we have to amplify it properly (you can find plenty of schemes on the internet), or use a module, and amplified as this


The scheme: (click images to enlarge):

Arduino light organ scheme

And the assembly:

Arduino light organ assembly



Some details:

  • The resistor of 1k to power the module greatly reduces the noise produced by feeding Arduino, which then moves to the output.. 
  • The u714 is a basic configuration of the amplifier, the output signal is obtained approximately 1 Vpp with an offset of 5V / 2 ~ 2.5V.
  • As the Arduino analog input signals need 0 to 5V, the voltage divider formed by R1/R2 creates an offset of approximately 0.5V so that the signal has a range of 0-1 V approx.
  • To measure more accurately, we program the Arduino analog reference as "INTERNAL", which has a value of 1.1 V instead of the default 5V.
The code (you can download it here):

/*
*  Organo de luces psicodelicas 
*  Organo de luces de tres canales , utilizando la FFT   
*  Autor: Jose Daniel Herrera
*  Fecha: 05/09/2012
*  http://arduino-guay.blogspot.com.es
*/

#include "fix_fft.h"

#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  9                // Pin de salida Bajos
#define MPIN  10               // Pin de salida Medios 
#define APIN  11               // Pin de salida Agudos

#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


/*
* 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 setup() {                   
    // 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);
    
    // Variables para el cálculo de los límites
    pasada = 0;
    acumBajos = acumMedios = acumAgudos = 0;
    limBajos = limMedios = limAgudos = 50;
}

void loop() {

    // Realizamos el muestreo 
    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                          
    }
    
    // Aplicamos la ventana de Hann
    aplicaVentana (data);
 
    // 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];
    }
   
    // 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 += salida[i];
    }
    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;
   }
 }

You need the fix_fft library that you can download from here:   fix_fft

The code

The code is commented so that it can easily follow, but basically see its operation.
This part contains a dose of mathematics that can be a little boring for some, if not interested simply passes it :-).

Setup

The analog reference is set to 'INTERNAL' to measure voltages in the range of 1V. Also the prescaler is set to 32 (the prescaler divides the clock frequency of the micro to perform 'A/D' conversions among other things), to achieve a sampling frequency of about 38 KHz, which is more than sufficient for this project. Then configure the output pins.

Loop

Is sampled 128 samples, then it passed to a window function (Hann) to minimize the effects of discontinuities at the beginning and end of the sample and then applies the fast Fourier transform with fix_fft call.

After this in 'data' have the values ​​of the 64 bands (Nmuestras / 2). As we are interested in absolute value and not the real and imaginary parts separately, we calculate the module, and since the value is something small we leave the square

Below is just group the values ​​in three bands (bass, mid and treble) and depending on if they exceed a limit enable outputs.

The threshold varies depending on the number of times that each light channel is activated, which provides some feedback and prevents volume changes with a channel is saturated or not light.
The grouping of bands and the limits I obtained by testing, you can perform other tests to get your results.

If you liked not miss the next post to see how to connect it to true bulbs:
Arduino light organ (III) - Power stage



If you liked it, remember to share it on your favorite social network. Thanks

24 comments:

  1. There is a mistake in the
    OrganoLuces.ino code.
    REef #include "fix_fft.h" in the actual ino file has brackets, won't compile.
    removing them and adding the "" will fix the problem.

    ReplyDelete
  2. you must change in: #include ...
    I made this montage with small changes and I'm very satisfied. I changed the sketch and put another channel (first time as negative channel, then I modified in subwoofer channel, see http://nicuflorica.blogspot.ro/2014/01/miniorga-de-lumini-cu-arduino.html

    ReplyDelete
  3. @Nicu FLORICA
    A pleasure that you have served as inspiration!

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. i keep getting this error message and I don't know what to do!
    Arduino: 1.5.8 (Mac OS X), Board: "Arduino Uno"

    /Users/Kerem/Documents/Arduino/libraries/Fix_fft/fix_fft.cpp:50:7: error: 'prog_int8_t' does not name a type
    const prog_int8_t Sinewave[N_WAVE-N_WAVE/4] PROGMEM = {
    ^
    In file included from /Users/Kerem/Documents/Arduino/libraries/Fix_fft/fix_fft.cpp:1:0:
    /Users/Kerem/Documents/Arduino/libraries/Fix_fft/fix_fft.cpp: In function 'int fix_fft(char*, char*, int, int)':
    /Users/Kerem/Documents/Arduino/libraries/Fix_fft/fix_fft.cpp:199:28: error: 'Sinewave' was not declared in this scope
    wr = pgm_read_word_near(Sinewave + j+N_WAVE/4);
    ^
    /Users/Kerem/Documents/Arduino/libraries/Fix_fft/fix_fft.cpp:209:28: error: 'Sinewave' was not declared in this scope
    wi = -pgm_read_word_near(Sinewave + j);
    ^
    Error compiling.

    This report would have more information with
    "Show verbose output during compilation"
    enabled in File > Preferences.

    ReplyDelete
  6. i've gotten my code work, i've run a "blink" to see if the output LEDs are working and they are, but for some reason i can not get an input signal in. i'm using this sound sensor: http://www.seeedstudio.com/wiki/Grove_-_Sound_Sensor
    should this pose a problem? if so, what modifications would you advise?

    ReplyDelete
  7. hola, es posible cambiar la sensibilidad de las luces? no se como puedo cambiarlas usando el "prescalar"...o es posible que hay otro método para hacer esto? gracias!

    ReplyDelete
  8. @Kerem
    How did you fix your code? I keep getting the same error message as you did.

    ReplyDelete
  9. @Kerem
    The 330 Ohm resistor is T1, and the 360 Ohm is T2.

    ReplyDelete
  10. Hello and thanks for the code

    but i get an error:

    undefined reference to `fix_fft(char*, char*, int, int)'

    what can i do to solve the problem. I know its maybe something with pointers.

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. Thanks for sharing this! Finally it compiles without errors, I will try connecting my arduino to a microphone tomorrow..

    ReplyDelete
  13. I've got error message:
    "
    C:\Program Files (x86)\Arduino\libraries\Fix_fft\fix_fft.cpp:50:7: error: 'prog_int8_t' does not name a type
    const prog_int8_t Sinewave[N_WAVE-N_WAVE/4] PROGMEM = {
    ^
    In file included from C:\Program Files (x86)\Arduino\libraries\Fix_fft\fix_fft.cpp:1:0:
    C:\Program Files (x86)\Arduino\libraries\Fix_fft\fix_fft.cpp: In function 'int fix_fft(char*, char*, int, int)':
    C:\Program Files (x86)\Arduino\libraries\Fix_fft\fix_fft.cpp:199:28: error: 'Sinewave' was not declared in this scope
    wr = pgm_read_word_near(Sinewave + j+N_WAVE/4);
    ^
    C:\Program Files (x86)\Arduino\libraries\Fix_fft\fix_fft.cpp:209:28: error: 'Sinewave' was not declared in this scope
    wi = -pgm_read_word_near(Sinewave + j);"

    What a Sinewave? How can I fix this?

    ReplyDelete
  14. I also have this error. Did someone fix it? Thanks

    Arduino: 1.6.4 (Windows 7), Board: "Arduino Nano, ATmega328"

    C:\Users\Joao\Documents\Arduino\libraries\Fix_fft\fix_fft.cpp:50:7: error: 'prog_int8_t' does not name a type
    const prog_int8_t Sinewave[N_WAVE-N_WAVE/4] PROGMEM = {
    ^
    In file included from C:\Users\Joao\Documents\Arduino\libraries\Fix_fft\fix_fft.cpp:1:0:
    C:\Users\Joao\Documents\Arduino\libraries\Fix_fft\fix_fft.cpp: In function 'int fix_fft(char*, char*, int, int)':
    C:\Users\Joao\Documents\Arduino\libraries\Fix_fft\fix_fft.cpp:199:28: error: 'Sinewave' was not declared in this scope
    wr = pgm_read_word_near(Sinewave + j+N_WAVE/4);
    ^
    C:\Users\Joao\Documents\Arduino\libraries\Fix_fft\fix_fft.cpp:209:28: error: 'Sinewave' was not declared in this scope
    wi = -pgm_read_word_near(Sinewave + j);
    ^
    Error compiling.

    This report would have more information with
    "Show verbose output during compilation"
    enabled in File > Preferences.

    ReplyDelete
  15. @Anonymous
    In version 1.6.0, the "prog_int8_t" is no longer used. Replace it with "int8_t". (In your fix_fft.cpp)

    Replace
    const prog_int8_t Sinewave[N_WAVE-N_WAVE/4] PROGMEM = {

    For
    const int8_t Sinewave[N_WAVE-N_WAVE/4] PROGMEM = {

    ReplyDelete
  16. @Anonymous
    In version 1.6.0, the "prog_int8_t" is no longer used. Replace it with "int8_t". (IN your fix_fft.cpp)

    Replace
    const prog_int8_t Sinewave[N_WAVE-N_WAVE/4] PROGMEM = {

    For
    const int8_t Sinewave[N_WAVE-N_WAVE/4] PROGMEM = {

    ReplyDelete
  17. hi, quite new here. could you teach me how to add fix_fft.h in the library? vincent_lim89@live.com

    ReplyDelete
  18. Hello!
    This is really a nice software. I "ported" it to Digispark, I only had to change the analog input to P4 (from reasons I do not know analogRead(0) defines P5, which is Reset, so analogRead(2) reads from P4 and P5 is left open. The Leds are on P0, 1 and 2. Took some time and https://digistump.com/wiki/digispark/tutorials/basics but now it works ;-)

    ReplyDelete
  19. In addition to my last post:
    https://www.youtube.com/watch?v=E-_1xuJtCcw&feature=youtu.be&ab_channel=hansgruber

    ReplyDelete
  20. What are you using to drive the large light bulbs from the breadboard?

    ReplyDelete

Note: Only a member of this blog may post a comment.

Arduino Chromatic Clock, with Steampunk style

Arduino Chromatic Clock, with Steampunk  style Need a unique gift for Christmas? ... This watch unlike normal, uses colors instead of...