LED maticový displej s ESP32 a MAX7219 - Audio vizualizér

06.04.2023 Arduino #esp32 #max7219 #display

Ukázka dalšího zajímavého využití maticového displeje ve spojení s mikrokontrolérem ESP32 v podobě hudebního vizualizéru.


Hudebních koncerty bývají v současnosti doprovázeny efektní světelnou show. Proč si nepostavit svůj vlastní hudební vizualizér, který reaguje na hudbu nebo zvuk? Zde je jednoduchý projekt, jak vytvořit svůj vlastní zvukový/hudební vizualizér jehož základem je ESP32 (LaskaKit), 32x8 dot Matrix Display Modul a Audio Sensor. LED diody na 32x8 maticovém displeji budou reagovat podle signálů, které ESP32 přijímá přes mikrofon, který je připojen k jeho analogovému pinu.

Princip a funkce maticového displeje bylo ukázáno v předchozím článku, proto se zde budeme zabývat pouze samotným audio vizualizérem.

Schéma zapojení ESP32, maticového displeje a mikrofonu

ESP32 32x8 LED Matrix
3.3V VCC
GND GND
23 DIN
5 CS
18 CLK
ESP32 Modul mikrofonu
3.3V VCC
GND GND
36 OUT

Programování

Níže je uveden celý kód a následně vysvětlení jednotlivých klíčových fragmentů.

#include <arduinoFFT.h>
#include <MD_MAX72xx.h>
#include <SPI.h>


#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 8  //Maximum single module connected
#define CS_PIN 5

#define READ_MICRO 36

MD_MAX72XX disp = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
arduinoFFT FFT = arduinoFFT();

double realComponent[64];
double imagComponent[64];

int spectralHeight[] = {0b00000000, 0b10000000, 0b11000000,
                        0b11100000, 0b11110000, 0b11111000,
                        0b11111100, 0b11111110, 0b11111111
                       };

int c;
int value;
int ind; 

int columnDisp=64;
int samples=64;


void setup(){
  disp.begin();
  Serial.begin(115200);
}

void loop(){

  int sensitivity = map(analogRead(A6), 0, 1023, 50, 100);
  Serial.println (analogRead(A6));
  for (int i = 0; i < samples; i++){

    realComponent[i] = analogRead(READ_MICRO) / sensitivity;
    imagComponent[i] = 0;

  }

  FFT.Windowing(realComponent, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(realComponent, imagComponent, samples, FFT_FORWARD);
  FFT.ComplexToMagnitude(realComponent, imagComponent, samples);

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

    realComponent[i] = constrain(realComponent[i], 0, 80);
    realComponent[i] = map(realComponent[i], 0, 80, 0, 8);
    ind = realComponent[i];
    value = spectralHeight[ind];

    c = columnDisp - i;
    disp.setColumn(c, value);

  }

}

V první řadě musí být ve vývojovém prostředí dostupné knihovny ArduinoFFT, MD_MAX72xx a knihovna SPI.

#include <arduinoFFT.h>
#include <MD_MAX72xx.h>
#include <SPI.h>

Knihovna ArduinoFFT je určena k převodu vstupního analogového signálu do frekvenčního spektra. K tomu je vhodné se seznámit s principem rychlé Fourierovy transformace v Arduinu.

Krátký audiosignál lze považovat za součet různých oscilací. Každá oscilace má svou vlastní frekvenci (=výška) a amplitudu (=úroveň).

Algoritmus Fourierovy transformace lze použít ke zpětnému sledování různých frekvencí a amplitud krátkého audiosignálu. (více o tomto tématu zde). Rychlá Fourierova transformace je vylepšená verze diskrétní Fourierovy transformace. Oba alogritmy používají maticové násobení k výpočtu frekvenčních pásem z daného audiosignálu.

Dále vytvoříme instanci pro MD_MAX72XX pomocí funkce MD_MAX72XX(). Tato funkce vyžaduje tři parametry, první je typ hardwaru, druhý pin CS a třetí maximální počet připojených zařízení. Vytvořte také instanci pro arduinoFFT pomocí funkce arduinoFFT().

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 8  //Maximum single module connected
#define CS_PIN 5

MD_MAX72XX disp = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
arduinoFFT FFT = arduinoFFT();

Následně definujte dvě nová pole pro uložení 64bitových spektrálních složek pro skutečnou a imaginární část spektra. 

double realComponent[64];
double imagComponent[64];

Uvedené řádky vytvoří datovou sadu 64 vzorků a uložena do vReal. Tato data jsou krátkým zvukovým signálem, který bude analyzován pomocí FFT v dalších řádcích kódu. Výsledkem jsou dvě pole: realComponent a imagComponent. Čísla určitého indexu jsou součástí komplexního čísla. Ale co je komplexní číslo? Komplexní číslo má dvě různé složky - reálnou část a imaginární část, která má jednotku i. Můžete je zobrazit v grafu s imaginární částí na vodorovné ose a imaginární částí na svislé ose 3 + 2i bude vypadat takto:

Absolutní hodnotu lze vypočítat pomocí pythagorovy věty. 

Tato hodnota je úrovní určité frekvence. Abychom vypočítali průměr určitých frekvenčních pásem, musíme je sečíst. Ale který index se podobá které frekvenci? To závisí na počtu vzorků n a na jaké frekvenci f je Arduino schopno získat datovou sadu. i je index v polích realComponent a imagComponent.

Arduino má takt 16MHz, vizualizér si nejlépe vedl s 64 vzorky a Arduino potřebuje 13 cyklů na jedno měření. Frekvence, při které může Arduino zachytit novou datovou sadu, je tedy:

V důsledku toho je frekvence určitého indexu i:

Protože první index neposkytuje užitečná data, druhý a třetí index (600 Hz a 900 Hz) jsou hodnoty pro basy, mezi 4 a 39 jsou středy a vše nahoře jsou výšky.

Pojďme dál. Nyní ve funkci setup() inicializujte Serial Monitor na přenosovou rychlost 115200 pro účely ladění. Také inicializujte tečkový displej pomocí funkce disp.begin().

void setup(){
  disp.begin();
  Serial.begin(115200);
}

V rámci funkce Loop, vezmeme 64 vzorků odečtů mikrofonu pomocí smyčky for přes pin36 na ESP32 a uložíme je do pole „realComponent“, které jsme definovali dříve.

  int sensitivity = map(analogRead(A6), 0, 1023, 50, 100);
  Serial.println (analogRead(A6));
  for (int i = 0; i < samples; i++){

    realComponent[i] = analogRead(READ_MICRO) / sensitivity;
    imagComponent[i] = 0;

  }

Po získání 64 spektrálních hodnot z mikrofonu se prošli algoritmem FFT, abychom vypočítali 64 spektrálních složek pro reálné a imaginární signály. Pak se v dalších řádcích předali tyto funkce čtení FFT.ComplexToMagnitude() pro výpočet velikosti 64 spektrálních složek.

  FFT.Windowing(realComponent, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(realComponent, imagComponent, samples, FFT_FORWARD);
  FFT.ComplexToMagnitude(realComponent, imagComponent, samples);

V další části kódu, se použila smyčku for k ovládání všech 64 sloupců matice LED. Tato hodnota je uložena v proměnné columnDisp. Uvnitř této smyčky for se nejdříve převedli naměřené hodnoty na známý rozsah a poté pomocí funkce map namapovali tyto hodnoty na rozsah od 0 do 8. Nula znamená, že všechny LED v tomto konkrétním sloupci budou nízké a naopak.

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

    realComponent[i] = constrain(realComponent[i], 0, 80);
    realComponent[i] = map(realComponent[i], 0, 80, 0, 8);
    ind = realComponent[i];
    value = spectralHeight[ind];

    c = columnDisp - i;
    disp.setColumn(c, value);

  }

Literatura:

[1] DIY Music/Audio Visualizer using 32x8 Dot Matrix Display and Arduino Circuit Digest [online]. USA: 2023 [cit. 2023-04-06]. Dostupné z: https://circuitdigest.com/microcontroller-projects/diy-music-audio-visualizer-using-dot-matrix-display-and-arduino-nano

[2] Arduino FFT lightorgan with fading lights The Techblog [online]. USA: 2023 [cit. 2023-04-06]. Dostupné z: https://the-techblog.com/en/lightorgan/