Skocz do zawartości

Oświetlenie RGB - ESP32 - Home Assistant - Filtry aktywne - FFT


Gieneq

Pomocna odpowiedź

Ostatnio pod namową znajomych zwróciłem uwagę na lampę, która stoi w mieszkaniu i zabrałem się za jej ulepszenie. Lampka z papieru z diodką, rezystorem, przełącznikiem i koszykiem na 3 baterie – nic szczególnego.

IMG_6775.thumb.jpg.90620b9adcb05b0c8c72da86eb035612.jpg

Wyrzuciłem to co było w środku i zabrałem się za złożenie czworokąta z diod WS2812B i 5 białych diod FLUX. Metody alternatywne dla trawienia płytek PCB też działają 🙂 

IMG_6778.thumb.jpg.ef867f3b2be30dfffed4e515828c14e2.jpg

Połączyłem im masy, 3 kabelki diod programowalnych poszły do ESP8266, wspólne anody diod FLUX poszły na dren klucza tranzystorowego składającego się z połączenia NPN + MOSFET-P kolejno: BC847 i FDT458P. MOS choć w obudowie SMD SOT223 jest w stanie przetrwać 3,4 A. LEDy przy zasilaniu 5,1 V pobierają około 1,1 A więc jest zapas mocy.

IMG_6779.thumb.jpg.291a30e8af06a771d3aa1ef37dd0f1f9.jpg

Tam gdzie kiedyś były baterie znalazł się moduł ESP8266. Dodatkowo dorzuciłem termometr DS18B20, dlatego że wymarzyłem sobie wpiąć lampkę w automatykę domową Home Assistant i mieć coś tam co robi wykresy. Można by dodać tryb, gdzie barwa świecenia lampy reprezentowałaby temperaturę... ale to może później.

IMG_6784.thumb.jpg.372f3712916d7bf4cffcdad08eb9b5a7.jpg

Pięknie nie wygląda, ale działa. Zasilanie jest pociągnięte cienkim kabelkiem z dołączonym gniazdem micro USB. Elektronikę udało się zamknąć klapką i ulepszona lampka gotowa:

IMG_6789.thumb.jpg.af0ce0befa21b43ae99e74fd8ecf51d5.jpg

Dodałem węzeł do HA. Znalazły się tu 3 encje:

  • termometr,
  • białe LED,
  • kolorowe LED.

Dla LED dodałem jeszcze efekty dla testu - jest to bardzo proste. W ESP Home ustawiamy plik konfiguracji .yaml:

  - platform: neopixelbus
    method: BIT_BANG
    type: GRB
    pin: GPIO4
    num_leds: 12
    name: "Light ball RGB"

    effects:
      # Customize parameters
      - random:
          name: "Slow Random Effect"
          transition_length: 30s
          update_interval: 30s
          
      - random:
          name: "Fast Random Effect"
          transition_length: 4s
          update_interval: 5s
          
      - pulse:
          name: "Fast Pulse"
          transition_length: 0.5s
          update_interval: 0.5s
          
      - strobe:
          name: Strobe RGB
          colors:
            - state: true
              brightness: 100%
              red: 100%
              green: 0%
              blue: 0%
              duration: 70ms
            - state: true
              brightness: 100%
              red: 0%
              green: 100%
              blue: 0%
              duration: 70ms
            - state: true
              brightness: 100%
              red: 0%
              green: 0%
              blue: 100%
              duration: 70ms
              
      - flicker:
          name: Flicker Effect
          alpha: 95%
          intensity: 1.5%

      - addressable_rainbow:
          name: Rainbow Effect
          speed: 10
          width: 50
          
      - addressable_random_twinkle:
          name: Random Twinkle Effect
          twinkle_probability: 5%
          progress_interval: 32ms
          
      - addressable_fireworks:
          name: Fireworks Effect
          update_interval: 32ms
          spark_probability: 10%
          use_random_color: false
          fade_out_rate: 120

Dla światła monochromatycznego (białego) efektów jest nieco mniej ale też można zrobić coś ciekawego – np. stroboskop:

output:
  - platform: esp8266_pwm
    pin: GPIO5
    frequency: 1000 Hz
    id: pwm_output

light:
  - platform: monochromatic
    output: pwm_output
    name: "Light Ball White"
    effects:
      # Customize parameters
      - random:
          name: "Slow Random Efdfect"
          transition_length: 30s
          update_interval: 30s
          
      - strobe:
          name: "Strobe Quite Fast"
          colors:
            - state: true
              brightness: 100%
              duration: 55ms
            - state: false
              brightness: 0%
              duration: 550ms
              
      - strobe:
          name: "Strobe Fast"
          colors:
            - state: true
              brightness: 100%
              duration: 40ms
            - state: false
              brightness: 0%
              duration: 40ms
    
      - strobe:
          name: "Strobe Ultra Fast"
          colors:
            - state: true
              brightness: 100%
              duration: 25ms
            - state: false
              brightness: 0%
              duration: 25ms

Po wgraniu wygenerowanego kodu (najpierw przez USB, później przy aktualizacjach przez OTA) w panelu pojawiły się kontrolki:

IMG_6791.thumb.PNG.3939ad6031a6772fbc7cbe4ab5bfbac7.PNGIMG_6788.thumb.PNG.87b765a3e7ad8a61fea21bce6c032789.PNG

Przykładowy efekt losowej zmiany kolorów:

Ale to nie koniec. Oglądając swoje dzieło, jak piękne miga sterowane z różnych urządzeń, wpadłem na pomysł, żeby zachęcić urządzenie do reagowania na muzykę. Tej lampce jednak dam spokój, bo ani nie chcę jej rozgrzebywać, ani nie ma tam miejsca na mikrofon itp.

Zakładam więc worklog, żeby spróbować swoich sił w przetwarzaniu dźwięku na ESP32 – pomoc członków forum mile widziana. Jak na razie wiem, że pośrednim celem jest detekcja BPM - liczby uderzeń na sekundę utożsamianej z tempem, do której wykorzystuje się FFT. Stąd przejrzałem obecne rozwiązania i niestety nie ma tego zbyt wiele 😞 

Trafiłem na stronę gdzie można wystukać sobie BPM i działa całkiem ładnie: 126-128. Następnie nagrałem kawałek piosenki i wrzuciłem na stronę, która wyciąga z mp3 BPM :

image.thumb.png.a4976883d4d97d0d010d4480395ee678.png

Również efekt dobry. Będę tu katował kawałek piosenki "Walking on a dream" Empire of the sun i faktycznie BPM jest 128.

To teraz pytanie... jak to zautomatyzować? Zacząłem szukać implementacji czegoś typu "ESP32 detect tempo bpm" ale nie ma tego wiele. Trafiłem na jedna pracę naukową, gdzie przedstawiono algorytm, który podobno ma ponad 85% skuteczność nawet przy muzyce klasycznej. Wyniki wyglądają obiecująco, ale jednak odbiłem się od tego w poszukiwaniu gotowców.

image.thumb.png.1d81a956fff4b31bb5224cf27f647d7f.png

Jako krok pośredni zacząłem szukać tego co znam i wiem że działa - FFT, co w świecie audio nazywa się VU meter czy analizą widma, wizualizacją częstotliwości itp. Tu jest już ciekawie, bo są implementacje dla Arduino np. EasyFFT. Biblioteka ta nie przekonała mnie za bardzo, bo jest bardzo ograniczona – w wyniku otrzymujemy "TOP 5" częstotliwości.

Kolejny traf był już dużo lepszy, tytuł artykułu nie pozostawia wątpliwości, że dobrze trafiłem "How to Perform FFT Onboard ESP32, and Get Both Frequency and Amplitude".

Przykładowy kod bazuje na odczycie czujnika, więc nieco zmodyfikowałem kod, żeby odczytywał sample muzyki zarejestrowanej mikrofonem. Tu przy okazji wtrącenie dot. hardwaru - na razie sprawa jest dość prosta - mikrofon elektretowy z przedwzmacniaczem nieodwracającym na op-ampie AS358P-E1.

IMG_6794.thumb.jpg.7dce5e27f51bf3703b57098c76be81f0.jpg

Dostroiłem wzmocnienie mikrofonu, żeby coś było widać i przeszedłem do kodu FFT. Ustawiłem liczbę próbek na 4096, max częstotliwość na 4 kHz – z tw. Nyquista wiemy że uda się zarejestrować max 2 kHz i jest to jak mi się wydaje dostateczne, bo BPM nie przekracza powiedzmy 200/60 Hz. Może tu robię błąd i powinienem badać cały przedział, albo tylko bardzo niskie częstotliwości - wydaje mi się, że prędzej powinienem skupić się na powiedzmy przedziale do 100 Hz, albo i mniej, ale nie wiem 😞 nie znam się, się zobaczy.

Ustawiłem 32 kubełki do rysowania wykresu, normalizację żeby mieć ładną 100 w piku:

image.thumb.png.d3851dcc50cc724f91fc557d9b147115.png

Coś robi, ale rozrzut wartości jest ogromny i nie są to planowane ułamki Hz. Domyślam się, że częstotliwość bazowa - ta o największym wzmocnieniu niekoniecznie musi oznaczać BPM (patrząc na to co jest opisane we wspomnianej pracy, to nie FFT wnosi informację o tempie, tylko analiza pochodnej, która informuje o jakiejś energii? ... doczytam). Nie mam za bardzo pewności czy wynik w ogóle jest poprawny, bo nazwijmy to system pozostawiony w ciszy wyświetla 50 Hz. Zmienię na pewno mikrofon, bo tej jest podejrzanie mały.

#include <Arduino.h>
#include "FFT.h"
// #include "FFT_signal.h"

#define FFT_N 4096 // Must be a power of 2
float sampling_freq = 4000.0; // max captured freq /= 2
long sampling_period_usl = long(1000000.0/sampling_freq);
float total_time = FFT_N/sampling_freq;

// #define TOTAL_TIME 0.0512 //The time in which data was captured. This is equal to FFT_N/sampling_freq

float fft_input[FFT_N];
float fft_output[FFT_N];

float max_magnitude = 0;
float fundamental_freq = 0;

int sampling_index = 0;
fft_config_t *real_fft_plan = fft_init(FFT_N, FFT_REAL, FFT_FORWARD, fft_input, fft_output);

long ttime = 0;
long samling_time = 0;

#define BINS_COUNT 32
#define BINS_K 100.0
float bins[BINS_COUNT];
// (FFT_N / 2) / 8 - samples per bin = 128
int vals_per_bins = (FFT_N/2) / BINS_COUNT;

void setup() {
  Serial.begin(115200);
    ttime = micros();
    samling_time = micros();
}

void loop() {
  if(sampling_index >= FFT_N) {
    ttime = micros() - ttime;
    // float fs = 1.0 * FFT_N / ttime;
    // Serial.print(fs * 1000000);
    // Serial.println(" Hz");
    /* START PROCESSING */
    max_magnitude = 0;
    fundamental_freq = 0;
    fft_execute(real_fft_plan);

    for (int k = 1 ; k < real_fft_plan->size / 2 ; k++)
    {
      float mag = sqrt(pow(real_fft_plan->output[2*k],2) + pow(real_fft_plan->output[2*k+1],2))/1;
      float freq = k*1.0/total_time;

      if(mag > max_magnitude)
      {
        max_magnitude = mag;
        fundamental_freq = freq;
      }

      int selected_bin = k / vals_per_bins;
      if(selected_bin < BINS_COUNT)
      bins[selected_bin] += mag;
    }

    //norm bins
    float max_bin_val = 0;
    for(int i = 0;i < BINS_COUNT; i++){
      if(bins[i] > max_bin_val)
      max_bin_val = bins[i];
    }
    
    for(int i = 0;i < BINS_COUNT; i++){
      bins[i] = bins[i] * BINS_K / max_bin_val;    
    }

    
    // Serial.print(ttime);
    // Serial.print(" us ");
    Serial.print(fundamental_freq);
    Serial.print(" Hz ");
    Serial.print(max_magnitude);
    Serial.print(" (like: ");
    Serial.print(analogRead(A0));
    Serial.println(")");

    for(int i = 0;i < BINS_COUNT; i++){
      int bin_cast = int(bins[i]);
      for(int j = 0;j < bin_cast; j++){
        Serial.print("#");
      }
      Serial.print(" ");
      Serial.print(bin_cast);
      Serial.println();
    }
      Serial.println();

    /* END PROCESSING */
    ttime = micros();

    sampling_index = 0;
    samling_time = micros();
  }

  if (micros() - samling_time > sampling_period_usl){
    samling_time += sampling_period_usl;
    real_fft_plan->input[sampling_index++] = analogRead(A0);
  }

}

Samo wyznaczanie FFT jest bardzo szybkie, dla 2048 punktów zajęło około 6,1 s. Dużo więcej czasu zajmuje blokujące zbieranie próbek z zadaną częstotliwością, ale kiedyś zastąpię to przerwaniami i podwójnym buforowaniem 🙂 więc jak na razie zapas mocy jest, są materiały, są perspektywy że coś z tego będzie.

Tak jeszcze w ramach planów na przyszłość. Jak uda mi się poprawnie wykryć BPM przyjdzie czas na integrację z Home Assistantem. Mam plan, żeby dało się sterować światłami by jedna kontrolka w panelu HA umożliwiała odpalenie animacji, ale też aby dało się wysłać dane z takiego "czujnika" do systemu i przekazanie do innego urządzenia. Wektor kubełków do analizatora widma też byłby mile widziany.

Edytowano przez Gieneq
  • Lubię! 2
Link do komentarza
Share on other sites

Czekaj moment. Po co Ci BPM? To jest określenie tempa, a nie rytmu.

Raczej w latach 70-tych na dyskoteki nie chadzałeś, ale pewnie starsi pamiętają tajemnicze urządzenia o nazwie "iluminofonia", które bardzo ładnie migało w tak muzyki. A było to nic innego jak trzy żarówki z filtrami podłączone do trzech filtrów; czerwona (dolnoprzepustowy) bardzo ładnie łapała centralę 🙂

Spróbuj na początek czegoś takiego (czerwony - dół, zielony - środek, niebieski - góra), bez żadnego liczenia BPM-ów.

Zainteresuj się też kodem wtyczek do jakichś playerów typu "synaesthesia" (nie podam linka, dawno nie używałem), może z tego coś wyciągniesz.

Edytowano przez ethanak
Link do komentarza
Share on other sites

@ethanak faktycznie popełniłem błąd, doczytałem: rytm to nie tempo, tempo to BPM, które zapisuje się na początku utworu. Interesuje mnie BPM, bo chciałbym zrobić wizualizację podobną do tego co jest na pierścieniach JBL partybox, tak by animacja przyspieszała lub zwalniała pod wpływem tempa utworu.

image.thumb.png.df26832f79b1dcaf374b4add123b442b.png

Iluminofony faktycznie niewiele mi mówią, poczytam, ale kojarzę filtry i crossover. Mój pierwszy plan polegał na odcięciu częstotliwości od powiedzmy 200/60 Hz ale to chyba nie tędy droga. Właściwie do końca nie rozumiem w czym szukać tempa utworu. Domyślam się, że da się rozpoznać po częstości zmian dźwięków, które w literaturze angielskiej nazywa się "onsetami", po polsku nie znalazłem odpowiednika.

image.thumb.png.443bca2237e6185241f52e6341c6fcb2.png

Przeglądając jakiś stack trafiłem na pakiet Pythona librosa, wygląda na to że to będzie dobry start żeby zapoznać się z tematem.

Link do komentarza
Share on other sites

36 minut temu, Gieneq napisał:

Interesuje mnie BPM

Nie, nie interesuje Ciebie BPM. Pamiętaj, że BPM to (upraszczając) nic innego jak ilość taktów w jednostce czasu, przy czym ta jednostka to raczej nie sekunda (w końcu PM to "per minute"). A nigdzie nie jest powiedziane, że takty mają być identyczne. Tempo może przyspieszać, zwalniać, a nawet pojedyncze takty mogą mieć różną długość (fermata). Poza tym co powiesz na nietypowe metrum (Pink Floyd, "Money")?

Ponieważ nie chcesz robić animacji w stylu "Animusic" (gdzie faktycznie musiałbyś znać położenie w czasie każdej nuty), a sterować kolorową lampką, zasugerowałbym rozpoczęcie od czego takiego:

Załóż, że centrala uderza raz na początku taktu (nie musi, ale wybierz sobie utwory w których jest to zachowane, potem możesz to modyfikować). W ten sposób dwa uderzenia centrali dają czas trwania taktu. Co prawda tego, który już minął, ale można założyć, że następny będzie trwał mniej więcej tyle samo. W ten sposób możesz w pewnym stopniu przewidzieć czas trwania bieżącego taktu i do tego dostosować tempo animacji.

Na Pink Floydów z ich metrum 3+4 to nie pomoże, ale na typowe na początek kawałki - szczególnie muzyki tanecznej, gdzie tempo nie może się za mocno zmieniać żeby się ludziom na parkiecie nogi na supełki nie pozawiązywały, stałe metrum obowiązuje przez cały utwór a centrala wali z grubej rury żeby nawet największy antytalent wiedział kiedy ma nogą ruszyć) - powinno.

 

 

 

  • Lubię! 2
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Wrzucę drobną aktualizację projektu. Poszedłem za radą @ethanak i przeszukałem schematy kolorofonów. W sumie najłatwiej byłoby kupić taki, bo na OLX można dorwać zabytki już po 50-300zł.

Na początek próbowałem swoich sił montując własne filtry ze wzmacniaczy operacyjnych i wyszło całkiem znośnie, ale audio na płytce stykowej to taki sobie pomysł. W końcu kupiłem kit składający się z bloku wzmacniacza i 3 filtrów pasmowoprzepustowych, działa naprawdę świetnie 🙂 Różnicowe zasilanie +-15V swoje robi. Najpewniej jest to ten sam kit co pokazał młody na filmie z poprzedniego wpisu:

IMG_6859.thumb.jpg.3a2c6b86096fcaf3aa55a24f2f325a94.jpgIMG_6889.thumb.PNG.5dfb4ef02b1f26a49741718fb3ae5ba9.PNGIMG_6887.thumb.jpg.ad868d71f0a8874cb5091674d8c88e6b.jpg

Zmontowałem, pomigałem LEDami, fajnie działa. Puściłem sinusoidę 50-20k Hz i widać było separację na odpowiednie kanały. Z muzyką też sobie ładnie radzą, na pierwszym zdjęciu widać odsiane górki z utworu Walking on a dream. Na drugim widać crossover niskich i wysokich tonów z utworu z Piratów z Karaibów.

IMG_6888.thumb.PNG.bbf1a3ab614de0a123c5b8cabd169c9e.PNGIMG_6890.thumb.PNG.10d1718237348dfa6c455f968f4c9450.PNG

W sumie o to mi chodziło, ale jak zobaczyłem jakie cudeńka ludzie robią z FFT to wróciłem do ESP32. Znalazłem ciekawą bibliotekę, która wyznacza FFT i poradnik jak użyć tego do wyświetlenia widma sygnału. Polecam przejrzeć githuba. Można tam znaleźć excela, w którym autor biblioteki konwertuje kubełki FFT na słupki wykresu - albo raczej na kolumnę else-ifów 😞 Napisałem jednak swój kod w Pythonie generujący LUT:

def list_bonds(min_freq, max_freq, sampling_rate, samples_count, bands_count):
    bands = []

    bin_width = sampling_rate / samples_count
    print(f"Bin width: {round(bin_width, 3)} Hz")

    usable_bins = samples_count//2 - 1
    print(f"Usable bins: {usable_bins}")

    freq_multiplier = (max_freq/min_freq)**(1.0/(bands_count - 1))

    print(f"Frequency multiplier: {round(freq_multiplier, 3)}")

    center_bins = []
    for i_band in range(bands_count+1):
        band_freq = min_freq * freq_multiplier ** i_band
        center_bin = (band_freq / bin_width)
        center_bins.append({"i_band": i_band, "band_freq":band_freq, "center_bin":center_bin})
    # print(center_bins)

    min_max_bonds = []

    for i_band in range(bands_count):
        sel_bin = center_bins[i_band]["center_bin"]
        next_bin = center_bins[i_band+1]["center_bin"]
        low_bin = 0
        if i_band > 0:
            low_bin = max(int(round(min_max_bonds[i_band - 1]["high"])), 0)
        high_bin = min(int(round((next_bin-sel_bin)//2 + sel_bin)), usable_bins)
        if i_band == bands_count - 1:
            high_bin = usable_bins
        range_list = list(range(low_bin, high_bin))
        min_max_bonds.append({"low":low_bin, "high":high_bin, "range":range_list})

        print(i_band, range_list)

    check_sum = 0
    for i, min_max_bond in enumerate(min_max_bonds):
        items_count = len(min_max_bond["range"])
        check_sum += items_count
        bands.extend([i]*items_count)

    print(f"Usable bins count ({check_sum}) correct: {check_sum == usable_bins}")


    return bands


bands = list_bonds(80, 12000, 40000, 1024, 19)

str_output = f"int bands[{len(bands)}]" + " = {\n"
file = open("bonds_from_bins.txt", 'w')

for i, b in enumerate(bands):
    str_output += str(b) + ", "
    if i % 32 == 31:
        str_output += "\n"
str_output = str_output[0:-2]
str_output += "};\n"

file.write(str_output)

Wynikiem jest:

int bands[511] = {
0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 
10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 
12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18};

Jak widać niskie tony mają większy wpływ na wysokość słupka w widmie i niekiedy tylko jeden kubełek odpowiada za jeden słupek, w przypadku wysokich tonów jest inaczej:

image.thumb.png.4f5d22fc6eb7b6b4916d96b895aeaf2b.png

Wyznaczenie 1024 punktowego FFT zajmuje bardzo mało czasu tylko 1,6 ms. Sampling rate też jest spory nawet bez modyfikacji parametrów ADC i 40 kHz uzyska się bez problemu.

#include <Arduino.h>
#include <FastLED.h>

#include "arduinoFFT.h"
#include "bands.h"

constexpr int AUDIO_PIN = A0;

constexpr uint16_t SAMPLES_COUNT = 1024;
constexpr uint16_t USABLE_SAMPLES_COUNT = SAMPLES_COUNT/2 - 1;
constexpr double AMPLITUDE = 1024.0;

constexpr int COLORS = 7;
constexpr int WSIZE= 8;

constexpr char COLOR_SELECTOR[COLORS][WSIZE] = {{"\033[0;46m"}, {"\033[0;46m"}, {"\033[0;44m"}, {"\033[0;42m"}, {"\033[0;43m"}, {"\033[0;44m"}, {"\033[0;41m"}};


constexpr double SAMPLING_FREQUENCY = 40000.0;
constexpr unsigned long SAMPLING_PERIOD_US = static_cast<unsigned long>(1000000.0 / SAMPLING_FREQUENCY);

constexpr double NOISE_LIMIT = 500.0;

unsigned long passed_time_us;

double vReal[SAMPLES_COUNT];
double vImag[SAMPLES_COUNT];

arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES_COUNT, SAMPLING_FREQUENCY);

constexpr int MATRIX_W = 19;
constexpr int MATRIX_H = 21;
constexpr int MATRIX_BRIGHTNESS = 50;
constexpr int BASE_BRIGHTNESS = 80;
constexpr int BANDS_COUNT = MATRIX_W;
constexpr int MAX_BAR_HEIGHT = MATRIX_H;

extern const int bands[];

double magnitude_bands[BANDS_COUNT];
int bar_heights[BANDS_COUNT];

void sample();

void eval();


void setup() {
    Serial.begin(115200);
    Serial.println(F("FFT Spectrum Display"));
    passed_time_us = micros();
}

void loop() {
    sample();
    eval();
    // delay(5000);
}

void sample() {
    for (int i = 0; i < SAMPLES_COUNT; i++) {
        vReal[i] = analogRead(AUDIO_PIN);
        vImag[i] = 0;
        while (micros() - passed_time_us < SAMPLING_PERIOD_US) {
        }
        passed_time_us += SAMPLING_PERIOD_US;
    }
}

void eval() {
    FFT.DCRemoval();
    FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);

    FFT.Compute(FFT_FORWARD);

    FFT.ComplexToMagnitude();  // wynik w vReal
  
    for (int i = 0; i < BANDS_COUNT; i++)
      magnitude_bands[i] = 0.0;

    for (int i = 0; i < USABLE_SAMPLES_COUNT; i++) {
      if (vReal[i] > NOISE_LIMIT)
        magnitude_bands[bands[i]] += vReal[i];
    }
    //jezeli przekroczy max to skaluj

      // Serial.println("\033[0;42m bold green text\033[0m plain text\n");
    Serial.print("\033[0m");
    for (int i = 0; i < BANDS_COUNT; i++){
      bar_heights[i] = min((int)(1.0 * magnitude_bands[i] / AMPLITUDE), MAX_BAR_HEIGHT-1);
      int color = (int)(bar_heights[i] * (COLORS-1) / (MAX_BAR_HEIGHT-1));
      Serial.print(COLOR_SELECTOR[color]);
      Serial.print("__");
      // Serial.print("\t");
    }
    Serial.print("\033[0m\t");

    for (int i = 0; i < BANDS_COUNT; i++){
      bar_heights[i] = min((int)(1.0 * magnitude_bands[i] / AMPLITUDE), MAX_BAR_HEIGHT-1);
      Serial.print(bar_heights[i]);
      Serial.print("\t");
    }
    Serial.println("\033[0m");
}

Problem w tym że nie mam wyświetlacza... skorzystałem więc z Tera Term i kodów do kolorowania tła znaków 🙂 

752301976_UntitledProject(Time0_01_2916).thumb.png.24f32942f98984b905420873bf17956d.png

Efekt jest bardzo ciekawy, odświeżanie uzależnione jest prędkością transmisji ale i tak robi wrażenie. Nagrałem test generatorem sinusa i kilka utworów, niestety nie znajdziecie tu popularnych kawałków, bo jest ryzyko wyciszenia ścieżki dźwiękowej przez YT 😞 

 

Wygrzebałem z tak zwanej "szuflady" 500 diod WS2812B, myślę że coś z tego powstanie 🔥

 

  • Lubię! 2
Link do komentarza
Share on other sites

Dnia 5.11.2021 o 14:44, Gieneq napisał:

W sumie o to mi chodziło, ale jak zobaczyłem jakie cudeńka ludzie robią z FFT

Budzą się wspomnienia... Na studiach na zaliczenie wszyscy robili projekt w Javie, a ja na ATMega'dze zrobiłem FFT. Niestety tylko na 4.5, bo źle dobrałem parametry dla okna czasowego Bartletta-Hanna 😕

Fajne zestawienie Tobie wyszło @Gieneq analog kontra cyfrówka... 

Link do komentarza
Share on other sites

@pmochocki uczymy się cały czas. Pamiętam jak chodziłem do szkoły to zbudowałem na układzie LM3916 VU meter. Układ zmontowany bardzo po omacku, ale w końcu działał. Dopiero po latach zrozumiałem co ja właściwie składałem 😅

W sumie wyświetlacz widma sygnału można bez problemu zrobić analogowo - składamy kaskadę filtrów i jakieś układy np wspomniany LM3916 do sterowania LEDami i zrobione. Tylko problem w tym że to jest okropnie drogie 😞 Sprawdzałem właśnie że ten scalak w AVT kosztuje 11 zł i pamiętam że zawsze był drogi, jak składałem VU meter to bałem się że spalę układ bo zastępczego nie kupiłem bo był za drogi.

Złożenie czegoś takiego analogowo to koszt sterownika ponad 200zł za same scalaki. Więc jak już to chyba lepiej pokusić się o układ FPGA i to też w ramach akceleracji FFT, np. zsyntezować układ FFT i wysłać wyniki jakimś interfejsem szeregowym do mikrokontrolera, który już sobie to zinterpretuje i zrobi ładne animacje itp.

W sumie... mam FPGA, ale nie jestem dobry w te "klocki". Tu może pytanie do kogoś zorientowanego w temacie FPGA, czy jest to do zrobienia? Pozwolę sobie zawołać @FlyingDutch wiem, że znasz się na tym. Czy z zestawem Elbert V2 da się zrobić minimum 1024 punktowe FFT i wysłać to do mikrokontrolera np przez I2C? Jak bardzo jest to skomplikowane? 

Link do komentarza
Share on other sites

1 godzinę temu, Gieneq napisał:

@pmochocki uczymy się cały czas.

Najchętniej jak już nie musimy. 🙂

3 godziny temu, Gieneq napisał:

W sumie wyświetlacz widma sygnału można bez problemu zrobić analogowo - składamy kaskadę filtrów i jakieś układy np wspomniany LM3916 do sterowania LEDami i zrobione. Tylko problem w tym że to jest okropnie drogie 😞 

3 godziny temu, Gieneq napisał:

Złożenie czegoś takiego analogowo to koszt sterownika ponad 200zł za same scalaki.

Tak niestety jest. Zacząłem zbierać materiały na barierę IR aby pokazać coś swojemu dziecku i tu brami do generatora, tu licznik, aby opóźnić wywołanie alarmu, tu znów generator do buzzera... A wszystko można zrobić na dwóch ATTiny za 2.5zł.
Z drugiej strony w planach też prosty line follower - dwa silniczki, dwa tranzystory jeden czujnik odbiciowy z wzmacniaczem operacyjnym. Są nadal fajne rzeczy, które można, a czasami trzeba robić w analogu. Z drugiej strony trzeba sobie czasami powiedzieć, że tu nie ma co iść tą drogą...

5 godzin temu, Gieneq napisał:

Więc jak już to chyba lepiej pokusić się o układ FPGA i to też w ramach akceleracji FFT, np. zsyntezować układ FFT i wysłać wyniki jakimś interfejsem szeregowym do mikrokontrolera, który już sobie to zinterpretuje i zrobi ładne animacje itp.

Albo czasami pójść zupełnie inną drogą - super pomysł z użyciem FPGA.

Link do komentarza
Share on other sites

6 godzin temu, Gieneq napisał:

@pmochocki uczymy się cały czas. Pamiętam jak chodziłem do szkoły to zbudowałem na układzie LM3916 VU meter. Układ zmontowany bardzo po omacku, ale w końcu działał. Dopiero po latach zrozumiałem co ja właściwie składałem 😅

...

W sumie... mam FPGA, ale nie jestem dobry w te "klocki". Tu może pytanie do kogoś zorientowanego w temacie FPGA, czy jest to do zrobienia? Pozwolę sobie zawołać @FlyingDutch wiem, że znasz się na tym. Czy z zestawem Elbert V2 da się zrobić minimum 1024 punktowe FFT i wysłać to do mikrokontrolera np przez I2C? Jak bardzo jest to skomplikowane? 

Czesć,

obawiam się, że będzie to  trudne (z powodów wydajności liczenia FFT). O ile implementacja protokołu I2C jest do zrobienia na tym zestawie (bez zajmowania jakiejś dużej liczby zasobów FPGA), to już 1024 punktowe FFT nie bardzo. Dlaczego - ponieważ Spartan3A z Elbert'a nie ma pełnoprawnych bloków DSP, posiada tylko trzy sprzętowe układy mnożące. Wszystko zależy więc od czasu liczenia FFT w układzie FPGA (teoretycznie IPCore "FFT" Xilinx'a jest dostepny (wersja 7.1) dla FPGA z Elbert'a, ale nie wiem jak z wydajnością. Lepiej byłoby to zrobić z magistralą AXI (Stream) wprowadzanie próbek z ADC/Mikrokontrolera, przejście przez FFT i wyprowadzanie danych także przez AXI, ale tych wersji IPCore FFT nie uruchomisz na Elbercie. Dużo bardziej realistyczne jest zrobienie tego na zestawie FPGA z ARTIX7 i w VIVADO.

Dużo bardziej wykonalne byłoby to na tej płytce FPGA:

 - "Sipeed Lichee Tang Nano 4K". Patrz link na Aliexpress.com:

https://www.aliexpress.com/item/1005003152104886.html?spm=a2g0s.9042311.0.0.38c2b90aOCx3I2

Ta nowa wersja płytki ma sprzętowy procesor ARM Cortex-M3 (hard CPU), około 4 razy większe zasoby FPGA (np. 4608 LUT, 3456 rejestrów, 16 sprzętowych układów mnożących). Sama płytka ma teraz złącze do kamery (np. taniej kamery OV2640 i wyjście HDMI). .

Pozdrawiam

Edytowano przez FlyingDutch
  • Lubię! 1
  • Pomogłeś! 1
Link do komentarza
Share on other sites

@FlyingDutch super, bardzo dziękuję za poradę, skorzystam. FPGA jest mi naprawdę obce, ale kiedyś trzeba się przełamać. Temat oświetlenia RGB zainspirował mnie, żeby zrobić coś większego i traf chciał że trafiłem na ten film:

Co do mechaniki to nic lepszego nie wymyśliłem, więc projekt już poszedł do realizacji, ale polem do popisu może być elektronika i program i tu chciałem zrobić coś więcej niż wgrać gotowca z sieci na Arduino. Może to trochę za duże kombinowanie, bo skoro coś działa to po co kombinować? – Żeby się czegoś nauczyć 🙂 Jak skończę mechanikę, podłączę matrycę diod i będzie działać, to wrócę do tematu, czyli pewnie dopiero w grudniu.

W ramach aktualizacji worklogu:

Zamknąłem temat analogowych światełek, filtry na op-ampach działają rewelacyjnie. Brakuje mi w tym wszystkich aktywnej regulacji wzmocnienia, ale już trudno - jest super. Na wyjściu op-ampów zrobiłem wzmacniacz z tranzystorów NPN BD139 - bo takie akurat miałem, do tego LEDy mocy:

image.thumb.png.3d19f880fb171eb02d6d880ee23b4063.png

Efekt na nagraniu, mam nadzieję że YT nie wytnie dźwięku.

Mi się podoba, co prawda front jest taki sobie - wziąłem grubą blachę aluminiową z dawnego projektu, a otwór zainspirował mnie, że może w przyszłości umieszczę tam laser, ale takich obiecywanek każdy majsterkowicz składa wiele więc uznaję, że na tym koniec.

Wracając do najważniejszej części moich eksperymentów z wizualizacją audio - wyświetlacz spektrum.

Zamówiłem już wszystko co potrzeba:

  • plexi,
  • profile,
  • 100W zasilacz,
  • płytki PCB pod sterownik i diody programowalne.

image.thumb.png.6d4ef8e336ca5cca835471b56ed6b468.png

Ktoś mógłby się przyczepić po co tyle PCB - 75 sztuk, przecież są taśmy LED – pewnie że są, ale jak ktoś ma szpulę 500 diod w szufladzie to warto je do czegoś użyć. Na razie PCB bardzo mi się podobają, ciekawe czy po pierwszej 100 przylutowanych LEDów dalej będą mi się podobać 🙄

Płytki jak będą to będą, różnie z tym bywa w tych czasach. W międzyczasie zamówiłem plexi według mojego projektu z PDFów - projekt wzorowany na tym co na w filmiku. Ciekawe że autor udostępnił pliki do produkcji i nawet pliki źródłowe Corela, więc jak zawsze miałem miłe zaskoczenie, że na świecie jest więcej użytkowników Corela, którzy tworzą w tym programie takie cudeńka - sam używam Corela od lat naprawdę do wszystkiego... od tego do czego służy: reklama, ulotki, banerki, przez projekt elementów pod CNC a skończywszy na wizualizacji układu mebli...

Wracając. Dowiedziałem się nieco o plexi, okazuje się, że w zasadzie nic o niej nie wiedziałem 😞 

Pierwsza sprawa - plexi cięte laserowo ma wewnętrzne naprężenia głównie w okolicach krawędzi i kontakt z silnym alkoholem, np. izopropanolem lub klejenie powodują, że materiał pęka. Jak gość z firmy mi o tym napisał niechciało mi się wierzyć, ale faktycznie:

image.thumb.png.5e6384a076be4a74251b4b0e9dfcf2d8.png

Plexa tak wygląda po polaniu jej izopropanolem i potrzymaniu przez chwilę - nikt raczej nie będzie trzymał plexy w alkoholu, ale zanim klej na bazie rozpuszczalnika wyschnie to może trochę potrwać. W testach z klejem nie było tak drastycznych scen ale jakieś małe pęknięcie powstało.

image.thumb.png.1b6d588f895b99080e1d33fa897f7352.png

Radą na to jest relaksacja - zobaczymy ile w tym prawdy, ale wygrzewając wycięte elementy w piecu podobno naprężenia puszczają.

Druga informacja o której nie wiedziałem, to że plexi cięta laserowo nie nadaje się do rozprowadzania światła. Niestety trzeba wybrać droższą opcję frezowania/szlifowania żeby uzyskać matową krawędź. 

 

  • Lubię! 2
Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.