Descriere proiect

Proiectul consta in implementarea unui sistem de lumini ce se aprind in moduri diferite in functie de sunetul captat de un microfon sau primit prin o mufa de 3.5mm. Semnalul de intrare de la microfon este amplificat de un LM386. Ambele semnale audio sunt conectate la ADC, prin care se colecteaza date, iar apoi se prelucreaza pentru a obtine informatii relevante pentru controlarea luminilor (Fast Fourier Transform).

Audio-LED-Strip-v2

Schema bloc

Lista componente

  • Microfon + amplificator + componente pasive filtrare
  • banda leduri individual adresabile
  • sursa tensiune 5V 3A+
  • jack 3.5mm

Schema electrica

Hardware design

Cea mai dificila parte a proiectului consta in implementarea unui amplificator suficient de bun pentru microfonul folosit, deoarece sistemul este in sine susceptibil la orice fel de zgomot. In implementarea curenta, spre exemplu, modul de operare bazat pe input de la microfon nu functioneaza extraordinar de bine ori datorita pieselor prost alese (LM386 sau microfonul cu electret) ori datorita zgomotului pe liniile de alimentare (5V pe USB).

Operarea prin jack de 3.5mm este mult mai stabila din punct de vedere a zgomotului, dar trebuie avut in minte faptul ca tensiunea semnalului audio este undeva intre 0.7-1.2V p2p. Cu offset DC la 1/2 din VCC se obtine o plaja de valori cuprinsa intre aproximativ 1.5 - 3.5V. In implementarea curenta asta impune o restrictie asupra iesirilor audio compatibile - functioneaza cel mai bine cu 'volumul' dispozitivului sursa setat la maxim.

Future work:

  • Amplificarea semnalului audio de pe jack-ul de 3.5mm inainte de sampling in ADC
  • Imbunatatirea microfonului/amplificatorului in materie de zgomot in semnal

Software design

Pentru inceput, tot software-ul a fost dezvoltat folosind Arduino IDE si, respectiv, un 'Core' ce permite integrarea chip-ului ATmega324PA in mediul Arduino, facand posibila utilizarea oricarei biblioteci dezvoltate pentru familia ATmega. Gasiti mai jos, in referinte, Core-ul si instructiunile de utilizare.

Librarii externe folosite (link-uri in referinte):

  • arduinoFFT
  • FastLED

Software-ul poate fi impartit in 7 componente distincte:

  • Compile-time configuration
  • State controller
  • Raw data sampling
  • Fast Fourier transform
  • Data processing
  • Color computation
  • LED animations

Compile-time configuration

Proiectul este destul de configurabil, are diversi parametri ce controleaza atat functionalitatea efectiva (numarul de led-uri, culorile), cat si parametri avansati, cum ar fi numarul de sample-uri pentru FFT, parametrii pentru functia de eliminare a zgomotului, etc.

#define SAMPLES 64 //FFT input samples from the ADC. Must be a power of 2
#define num_buckets 32 // Total number of  frequency buckets after FFT, must be <= SAMPLES/2
#define max_out 80 // FFT output is mapped from 0 to max_out
 
#define updateLEDS 6 // How many leds the animation moves at a time. Must be a multiple of 2 and >= 4
#define NUM_LEDS 100 // How many leds the strip has in total. Must be a multiple of 2
#define DATA_PIN PD5 // Led Strip DATA pin
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define BRIGHTNESS 32
#define MAXHUE 240 // purplish blue
 
//ADC input settings
#define ADC_MIC 0b11000100 // ADC setting to use the MIC
#define ADC_JACK 0b11000111 // ADC setting to use the 3.5mm JACK
 
//##### NOISE COMPENSATION
//The first bucket to start being relevant
#define START_JACK 2
#define START_MIC 8
 
// The maximum noise on the first bucket
#define NOISE_JACK 5
#define NOISE_MIC 80
 
// The last bucket on which noise needs to be removed
#define DENOISE_END_BUCKET 15
 
// Overall noise floor
#define OVERALL_NOISE 5
 
// Linear scaler (lowers the impact of the first buckets on the output color)
#define BASE_SCALER 5
//##### END NOISE COMPENSATION
 
 
#define ledPin 15 // pin the controls the output LED
#define buttonPin 2 // pin that controls the states
#define debounceDelay 50

State controller

Proiectul are (in momentul de fata) implementate 4 moduri de operare:

  1. Input de la jack-ul de 3.5mm
    • Animatie de wave-uri de culori controlate de frecventa dominanta (rosu → albastru), pornind din centrul benzii de LED-uri (0)
    • Animatie de wave-uri de culori controlate de frecventa dominanta (albastru → rosu), pornind din centrul benzii de LED-uri (1)
    • Aprinderea intregii benzi in culoarea frecventei dominante (2)
  2. Input de la microfon, prin amplificator
    • Animatie de wave-uri de culori controlate de frecventa dominanta (rosu → albastru), pornind din centrul benzii de LED-uri (3)

Starile sunt schimbate circular prin apasarea butonului BTN legat la PB2.

int reading = digitalRead(buttonPin);
if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay)
{
    state++;
    if (state == 4) state = 0;
 
    // 0-jack 1-jack_reverse 2-jack_solid 3-mic
    switch (state)
    {
        case 0:
            for (int i = 0; i < num_buckets; i++)
                bucket_color[i] = CHSV(i *(MAXHUE / num_buckets), 255, 255); //sets the buckets hues
            ADMUX = ADC_JACK;
            startBucket = START_JACK;
            noiseLevel = NOISE_JACK;
            ledState = HIGH;
            break;
        case 1:
            for (int i = 0; i < num_buckets; i++)
                bucket_color[i] = CHSV((MAXHUE - 30) - i *((MAXHUE - 30) / num_buckets), 255, 255); //sets the buckets hues in reverse (blue to red)
            ADMUX = ADC_JACK;
            startBucket = START_JACK;
            noiseLevel = NOISE_JACK;
            ledState = HIGH;
            break;
        case 2:
            for (int i = 0; i < num_buckets; i++)
                bucket_color[i] = CHSV(i *(MAXHUE / num_buckets), 255, 255); //sets the buckets hues
            ADMUX = ADC_JACK;
            startBucket = START_JACK;
            noiseLevel = NOISE_JACK;
            ledState = HIGH;
            break;
        case 3:
            for (int i = 0; i < num_buckets; i++)
                bucket_color[i] = CHSV(i *(MAXHUE / num_buckets), 255, 255); //sets the buckets hues
            ADMUX = ADC_MIC;
            startBucket = START_MIC;
            noiseLevel = NOISE_MIC;
            ledState = LOW;
            break;
    }
    digitalWrite(ledPin, ledState);
    lastDebounceTime = millis();
}
previousState = reading;

Raw data sampling

Datele sunt preluate de la ADC, cate SAMPLES odata, pentru a fi folosite in analiza Fourier.

// ADC readings
for (int i = 0; i < SAMPLES; i++)
{
    while (!(ADCSRA &0x10)); // wait for conversion
    ADCSRA = 0b11110101; // clear ADIF
    int value = ADC - 512; // read from ADC and remove the DC offset
    vReal[i] = value / 8; // compress the value
    vImag[i] = 0;
}

Fast Fourier transform

Folosind libraria arduinoFFT se aplica o analiza Fourier asupra sample-urilor culese mai inainte. Din cauza limitarilor procesorului (16MHz, memorie limitata), numarul maxim de SAMPLE-uri ce pot fi procesate este 64. 128 de sample-uri se incadreaza in memorie, dar timpul de procesare este prea lung, astfel luminile devin irelevante si decalate in raport cu semnalul audio.

// FFT
FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

Data processing

In caz ca numarul de bucket-uri de frecvente ales in configurare este mai mic decat 1/2 din numarul de sample-uri, atunci se va face o medie a valorilor pentru a reduce numarul de sample-uri.

// average the collected data if the number of samples is greater than the number of buckets (num_buckets)
int step = (SAMPLES / 2) / num_buckets;
 
#if num_buckets > SAMPLES / 2
int c = 0;
for (int i = 0; i < (SAMPLES / 2); i += step)
{
    data_avgs[c] = 0;
    for (int k = 0; k < step; k++)
    {
        data_avgs[c] = data_avgs[c] + vReal[i + k];
    }
    data_avgs[c] = data_avgs[c] / step;
    c++;
}
#endif

Color computation

Fiecare bucket (fiecare grup de frecvente) are asignata o culoare. Spre exemplu, frecventele joase sunt culori calde, spre rosu, iar cele inalte culori reci, spre albastru/violet. In functie de aceste culori asignate bucket-urilor, cat si a magnitudinii calculate prin analiza Fourier se calculeaza o culoare ce tine cont de toate bucket-urile, fiind predominante culorile cu magnitudine ridicata.

// black
CRGB nColor(0, 0, 0);
int r = 0, g = 0, b = 0;
 
// Compute the next color based on all the buckets and their values
for (int i = startBucket; i < num_buckets; i++)
{
    if (step == 1) data_avgs[i] = vReal[i];
    data_avgs[i] = constrain(data_avgs[i], 0, 80); // set max &min values for buckets
    data_avgs[i] = map(data_avgs[i], 0, 80, 0, max_out); // remap averaged values to max_out
 
    //get the mapped data and remove the noise
    outValue = data_avgs[i];
    outValue = denoise(data_avgs[i], i, noiseLevel);
 
    /*
      The new color is a mix of all the colors of the buckets (bucket_color[index]), but
      mixed depending on the values of the buckets so that the most dominant frequency
      has a priority and stands out
    */
    CRGB temp = bucket_color[i];
 
    nColor.r += temp.r *outValue / (max_out *bucket_scaler[i]);
    nColor.g += temp.g *outValue / (max_out *bucket_scaler[i]);
    nColor.b += temp.b *outValue / (max_out *bucket_scaler[i]);
}

Functia denoise

Aceasta functie primeste ca parametri valoarea de input originala, index-ul bucket-ului si marja de zgomot definita in configurare. Practic aceasta functie elimina zgomotul gradual pana la DENOISE_END_BUCKET folosind o functie de gradul 1.

Future work: folosirea unei functii de grad ridicat cu o precizie mai buna.

int denoise(int input, int bucket, int noiseLevel) {
  int compensation = (noiseLevel / DENOISE_END_BUCKET) * bucket - noiseLevel;
  if (compensation < 0) input += compensation;
  if (input < OVERALL_NOISE) return 0;
  if (input < 0) return 0;
 
  return input;
}

LED animations

In final, dupa ce a fost calculata viitoare culoare de adaugat pe banda de LED-uri, aceasta este asignata corespunzator si afisata folosind libraria FastLED.

//Do the animations
if (state != 2) // if the animation is not 'solid' (all leds the same)
{
    //Shift the left half to the left
    for (int i = 0; i <= NUM_LEDS / 2 - updateLEDS / 2; i++)
    {
        leds[i] = leds[i + updateLEDS / 2];
    }
 
    //Shift the right half to the right
    for (int i = NUM_LEDS - 1; i >= NUM_LEDS / 2 + updateLEDS / 2; i--)
    {
        leds[i] = leds[i - updateLEDS / 2];
    }
 
    //Add new middle leds using the new color
    for (int i = NUM_LEDS / 2 - updateLEDS / 2 + 2; i <= NUM_LEDS / 2 + updateLEDS / 2 - 2; i++)
    {
        leds[i] = nColor;
    }
 
    //Blend the new color with the old one on the borders
    CRGB oldColor = leds[NUM_LEDS / 2 - updateLEDS / 2 - 1];
    CRGB newColorLo = nColor, newColorMid = nColor;
    if (oldColor)
    {
        newColorLo = CRGB((oldColor.r * 7 + nColor.r * 3) / 10, (oldColor.g * 7 + nColor.g * 3) / 10, (oldColor.b * 7 + nColor.b * 3) / 10);
        newColorMid = CRGB((oldColor.r * 2 + nColor.r * 3) / 5, (oldColor.g * 2 + nColor.g * 3) / 5, (oldColor.b * 2 + nColor.b * 3) / 5);
    }
 
    //oldColor 100%
    leds[NUM_LEDS / 2 - updateLEDS / 2] = newColorLo; // more old less new
    leds[NUM_LEDS / 2 - updateLEDS / 2 + 1] = newColorMid; // less old more new
    //nColor 100%
    //MIDDLE OF THE STRIP
    //nColor 100%
    leds[NUM_LEDS / 2 + updateLEDS / 2 - 1] = newColorMid; // less old more new
    leds[NUM_LEDS / 2 + updateLEDS / 2] = newColorLo; // more old less new
    //oldColor 100%
 
}
else
{ // all the leds are set to the new color
    for (int i = 0; i < NUM_LEDS; i++)
        leds[i] = nColor;
}
 
//Send the data to the LED strip
FastLED.show();

Rezultate obtinute

Per total implementarea e completa, cu mentiunea ca modul de operare prin microfon este, in cel mai bun caz, dezamagitor in privinta aspectului luminilor.

In video-ul de mai jos se poate observa functionalitatea folosind input-ul prin jack-ul de 3.5mm si un numar de 200 de LED-uri.

Referinte

Inspiratia pentru acest proiect a venit din video-ul lui Devon Crawford: watch.

In implementare am folosit ca referinte urmatoarele proiecte:

Pentru dezvoltarea codului in Arduino IDE (folosind ATmega324PA) am folosit MightyCore: MightyCore. Gasiti in README pe github toate informatiile necesare instalarii.

Librariile folosite se gasesc la:

pm/prj2019/ostiru/biglights.txt · Last modified: 2021/04/14 17:07 (external edit)
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0