Урок 19. LED панель + Микрофон. Светомузыка



  • Цель урока

    Привет! Сегодня мы научимся зажигать многоцветные светодиодные панели (только в M5 FIRE), полагаясь на составляющие частоты звукового сигнала, полученные со встроенного (только в M5 FIRE) микрофона.

    Рисунок 1

    Этот урок научит: подключать и использовать на практике сторонние библиотеки для работы с быстрым преобразованием Фурье (FFT) и светодиодами SK6818.

    Краткая справка

    Цветомузыка - вид искусства, основанный на способности человека ассоциировать звуковые ощущения со световыми восприятиями; такое явление в неврологии получило название синестезия. Светомузыка как искусство, представляет собой производную от музыки и является её неотъемлемой частью. Её назначение — раскрытие сущности музыки посредством зрительных восприятий. Основной целью светомузыки как искусства — это изучение способности человека испытывать ощущения, навязываемые световыми образами при сопровождении музыки.

    Любители музыки уже давно подметили, что музыкальные инструменты звучат намного громче и отчетливее в хорошо освещённом помещении, чем в затемнённом. Поэтому при исполнении серьёзной музыки свет в зале обычно не гасят.

    Впервые связь между слухом и зрением очень убедительно показал русский физик и русский физиолог академик П. П. Лазарев.

    Подробно на Wiki: https://ru.wikipedia.org/wiki/Светомузыка

    Перечень компонентов для урока

    • PC/MAC;
    • M5STACK FIRE;
    • кабель USB-C из стандартного набора.

    Начнём!

    Шаг 1. Установка библиотеки для работы с LED BAR

    Переходим по ссылке Библиотека LED BAR в разделе Downloads и скачиваем пример с файлами библиотек (рис. 2).

    Рисунок 2

    Далее извлекаем архив в новую папку sketch и удаляем файл demo1.ino (рис. 3).

    Рисунок 3

    Шаг 2. Установка библиотеки FFT

    Переходим по ссылке Библиотека Arduino FFT в разделе Downloads и скачиваем пример с файлами библиотек (рис. 4).

    Рисунок 4

    Далее извлекаем содержимое и копируем папку в C:\Users\USER_NAME\Documents\Arduino\libraries с новым именем arduinoFFT вместо arduinoFFT-master (рис. 5).

    https://pp.userapi.com/c851016/v851016266/3d841/sySeWj6WcW4.jpg

    Рисунок 5

    Отлично! С библиотеками всё :)

    Шаг 3. Пишем скетч

    Создадим в Arduino IDE новый скетч и сохраним его в той папке, где лежат файлы библиотеки из шага 1 (рис. 6).

    Рисунок 6

    Сразу подключим необходимые библиотеки и создадим необходимые переменные:

    #include <M5Stack.h>
    #include "arduinoFFT.h"
    #include "esp32_digital_led_lib.h"
    arduinoFFT FFT = arduinoFFT();
    
    #define SAMPLES 256 //Must be a power of 2
    #define SAMPLING_FREQUENCY 10000 //Hz, must be 10000 or less due to ADC conversion time. Determines maximum frequency that can be analysed by the FFT.
    #define amplitude 50
    unsigned int sampling_period_us;
    unsigned long microseconds;
    byte peak[] = {0,0,0,0,0,0,0};
    double vReal[SAMPLES];
    double vImag[SAMPLES];
    unsigned long newTime, oldTime;
    strand_t m_sLeds = {.rmtChannel = 0, .gpioNum = 15, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 10, .pixels = nullptr, ._stateVars = nullptr};
    

    Для упрощенного обращения к LED BAR предлагаю использовать следующую функцию void ledBar(int R, int G, int B, int M). Где int R, int G, int B - яркость КРАСНОГО, ЗЕЛЕНОГО и СИНЕГО - соответственно (от 0 до 255); int M - режим: от 0 до 9 - номер светодиода, 10 - все светодиоды из левой панели, 11 - все светодиоды из правой панели, 12 - все светодиоды.

    Светодиоды расположены по часовой стрелке (рис. 7).

    Рисунок 7

    Рисунок 7.1

    void ledBar(int R, int G, int B, int M) {
      if ((M < 0) || (M > 13)) return;
      if (M == 11) // right
      {
        for (int i = 0; i < 5; i++)
        {
           m_sLeds.pixels[i] = pixelFromRGBW(R, G, B, 0);
        }
      }
      else if (M == 10) // left
      {
        for (int i = 5; i < 10; i++)
        {
           m_sLeds.pixels[i] = pixelFromRGBW(R, G, B, 0);
        }
      }
      else if (M == 12) // all
      {
        for (int i = 0; i < 10; i++)
        {
           m_sLeds.pixels[i] = pixelFromRGBW(R, G, B, 0);
        }
      }
      else
      {
         m_sLeds.pixels[M] = pixelFromRGBW(R, G, B, 0);
      }
      digitalLeds_updatePixels(&m_sLeds);
    }
    

    Основная часть. Не забывайте про void dacWrite(25, 0); чтобы динамик не издавал странных звуков и треска.

    void setup(){
      M5.begin();
      pinMode(25, OUTPUT);
      pinMode(34, INPUT);
      sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQUENCY));
      pinMode(15, OUTPUT);
      digitalWrite (15, LOW);
      if (digitalLeds_initStrands(&m_sLeds, 1)) {
        Serial.println("Can't init LED driver().");
      }
      digitalLeds_resetPixels(&m_sLeds);
    }
    

    Обратите внимание на pinMode(34, INPUT) - это аналоговый вход, к которому подключён встроенный микрофон (рис. 8) через усилитель, поэтому настроим его на INPUT.

    Рисунок 8

    void loop() {
      for (int i = 0; i < SAMPLES; i++) {
        newTime = micros() - oldTime;
        oldTime = newTime;
        vReal[i] = analogRead(34); // A conversion takes about 1mS on an ESP8266
        vImag[i] = 0;
        while (micros() < (newTime + sampling_period_us));  // do nothing to wait
      }
      
      FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
      FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
      FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
      dacWrite(25, 0);
      
      for (int i = 2; i < (SAMPLES/2); i++){ // Don't use sample 0 and only first SAMPLES/2 are usable. Each array eleement represents a frequency and its value the amplitude.
        if (vReal[i] > 200) { // Add a crude noise filter, 4 x amplitude or more
          if (i<=5 )             displayBand(0,(int)vReal[i]/amplitude); // 125Hz
          if (i >5   && i<=12 )  displayBand(1,(int)vReal[i]/amplitude); // 250Hz
          if (i >12  && i<=32 )  displayBand(2,(int)vReal[i]/amplitude); // 500Hz
          if (i >32  && i<=62 )  displayBand(3,(int)vReal[i]/amplitude); // 1000Hz
          if (i >62  && i<=105 ) displayBand(4,(int)vReal[i]/amplitude); // 2000Hz
          if (i >105 && i<=120 ) displayBand(5,(int)vReal[i]/amplitude); // 4000Hz
          if (i >120 && i<=146 ) displayBand(6,(int)vReal[i]/amplitude); // 8000Hz
        }
      }
    }
    

    Зажигать светодиоды будем с помощью функции void displayBand(int band, int dsize). Где band - частота в сигнале; dsize - количество частоты в сигнале. Можете смело тут экспериментировать и добиваться лучших вспышек.

    void displayBand(int band, int dsize){
      if (band > 1) dsize += 100;
      if (dsize >= 150)
      {
        ledBar(0, 0, 0, 12);
        if (band == 0)
        {
          if (dsize >= 300)
          {
            ledBar(255, 0, 0, 0);
            ledBar(255, 0, 0, 1);
          }
        }
        else if (band == 1)
        {
          if (dsize >= 180)
          {
            ledBar(255, 255, 0, 3);
            ledBar(255, 255, 0, 4);
          }
        }
        else if (band == 2)
        {
          if (dsize >= 170)
          {
            ledBar(0, 255, 0, 5);
            ledBar(0, 255, 0, 6);
          }
        }
        else
        {
          ledBar(0, 0, 255, 8);
          ledBar(0, 0, 255, 9);
        }
      }
    }
    

    Завершающий шаг

    На этом всё :)

    Downloads