驴友花雕 发表于 4 天前

【Arduino 动手做】FastLED矩阵的ESP32频谱分析仪VU表

频谱分析仪 VU 表在 ESP32 上运行 40kHz FFT 并输出到 16 x 16 FastLED 矩阵。该代码与最初由 G6EJD 编写的此示例进行了大量修改。
演示

如果您打算使用此代码,建议您观看下面的 YouTube 视频,了解其工作原理。请注意,由于视频的制作,代码已更改为使用 FastLED Neomatrix,这要归功于 VonHirsch。请参阅下面的“安装和代码使用”。

设置电路
矩阵将从线路输入或麦克风运行,因此请选择适合您的设置。为按钮选择图钉时,请小心。ESP32 上的一些引脚没有上拉电阻,如果您选择其中一个,按钮将不起作用!当我测试时,我发现 D2 不起作用,但 D4 有效,所以我选择了那个。

线路输入
输入的立体声信号首先使用两个 10K 电阻器转换为单声道,然后通过一个 100nF 电容器来阻断直流。然后,信号由两个 100k 电阻器偏置到 3.3V / 2 = 1.65V,以便 ADC 读取。这些值都不是关键的。电阻器应为 10k 或更高,并且每对应匹配。我尝试了从100nF 到10uF 的电容器,但没有注意到结果有任何差异。引脚 D5 是 LED 数据引脚,连接到矩阵中的第一个 LED。引脚 D2 连接到一个瞬时按钮,用于更改显示模式。

麦克风
这比 line in 方法简单得多,但您将受限于麦克风的灵敏度可以检测到的频率。您需要一个带有板载放大功能的麦克风,通常类似于 MAX4466 的东西,可以从 eBay 或 alixepress 便宜地购买。使用 I2S 麦克风会获得更好的结果,但这超出了本项目的范围。调高您拥有的任何麦克风的增益。麦克风应连接到 GND、3V3,输出引脚应连接到 D35。引脚 D5 是 LED 数据引脚,连接到矩阵中的第一个 LED。引脚 D2 连接到一个瞬时按钮,用于更改显示模式。

安装和代码使用
1. 下载此存储库并打开 ESP32_FFT_VU.ino。
2. 您将需要 Arduino 库管理器中的 FastLED、Neomatrix 和 EasyButton 库。您还需要 arduinoFFT 库。在撰写本文时,库管理器版本 arduinoFFT 存在一个阻止工作的错误,因此请从 GitHub 存储库下载它并从 zip 文件安装它。DCRemoval()
3. 观看视频以了解如何使用它。
4. 要将其自定义为您自己的矩阵布局,请阅读 Adafruit 上的 Neomatrix 布局。

控制
矩阵通过单个按钮进行控制。这些功能包括:
• 单击:更改模式
• 长按:更改亮度
• 2 秒内按下 3 个按钮:设置为自动更改模式
• 2 秒内按下 5 个按钮:关闭显示屏











驴友花雕 发表于 4 天前

【Arduino 动手做】FastLED矩阵的ESP32频谱分析仪VU表

项目代码

// (Heavily) adapted from https://github.com/G6EJD/ESP32-8266-Audio-Spectrum-Display/blob/master/ESP32_Spectrum_Display_02.ino
// Adjusted to allow brightness changes on press+hold, Auto-cycle for 3 button presses within 2 seconds
// Edited to add Neomatrix support for easier compatibility with different layouts.

#include <FastLED_NeoMatrix.h>
#include <arduinoFFT.h>
#include <EasyButton.h>

#define SAMPLES         1024          // Must be a power of 2
#define SAMPLING_FREQ   40000         // Hz, must be 40000 or less due to ADC conversion time. Determines maximum frequency that can be analysed by the FFT Fmax=sampleF/2.
#define AMPLITUDE       1000          // Depending on your audio source level, you may need to alter this value. Can be used as a 'sensitivity' control.
#define AUDIO_IN_PIN    35            // Signal in on this pin
#define LED_PIN         5             // LED strip data
#define BTN_PIN         4             // Connect a push button to this pin to change patterns
#define LONG_PRESS_MS   200         // Number of ms to count as a long press
#define COLOR_ORDER   GRB         // If colours look wrong, play with this
#define CHIPSET         WS2812B       // LED strip type
#define MAX_MILLIAMPS   2000          // Careful with the amount of power here if running off USB port
const int BRIGHTNESS_SETTINGS = {5, 70, 200};// 3 Integer array for 3 brightness settings (based on pressing+holding BTN_PIN)
#define LED_VOLTS       5             // Usually 5 or 12
#define NUM_BANDS       16            // To change this, you will need to change the bunch of if statements describing the mapping from bins to bands
#define NOISE         500         // Used as a crude noise filter, values below this are ignored
const uint8_t kMatrixWidth = 16;                        // Matrix width
const uint8_t kMatrixHeight = 16;                         // Matrix height
#define NUM_LEDS       (kMatrixWidth * kMatrixHeight)   // Total number of LEDs
#define BAR_WIDTH      (kMatrixWidth/ (NUM_BANDS - 1))// If width >= 8 light 1 LED width per bar, >= 16 light 2 LEDs width bar etc
#define TOP            (kMatrixHeight - 0)                // Don't allow the bars to go offscreen
#define SERPENTINE   true                               // Set to false if you're LEDS are connected end to end, true if serpentine

// Sampling and FFT stuff
unsigned int sampling_period_us;
byte peak[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};            // The length of these arrays must be >= NUM_BANDS
int oldBarHeights[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int bandValues[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
double vReal;
double vImag;
unsigned long newTime;
arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);

// Button stuff
int buttonPushCounter = 0;
bool autoChangePatterns = false;
EasyButton modeBtn(BTN_PIN);

// FastLED stuff
CRGB leds;
DEFINE_GRADIENT_PALETTE( purple_gp ) {
0,   0, 212, 255,   //blue
255, 179,   0, 255 }; //purple
DEFINE_GRADIENT_PALETTE( outrun_gp ) {
0, 141,   0, 100,   //purple
127, 255, 192,   0,   //yellow
255,   0,   5, 255 };//blue
DEFINE_GRADIENT_PALETTE( greenblue_gp ) {
0,   0, 255,60,   //green
64,   0, 236, 255,   //cyan
128,   0,   5, 255,   //blue
192,   0, 236, 255,   //cyan
255,   0, 255,60 }; //green
DEFINE_GRADIENT_PALETTE( redyellow_gp ) {
0,   200, 200,200,   //white
64,   255, 218,    0,   //yellow
128,   231,   0,    0,   //red
192,   255, 218,    0,   //yellow
255,   200, 200,200 }; //white
CRGBPalette16 purplePal = purple_gp;
CRGBPalette16 outrunPal = outrun_gp;
CRGBPalette16 greenbluePal = greenblue_gp;
CRGBPalette16 heatPal = redyellow_gp;
uint8_t colorTimer = 0;

// FastLED_NeoMaxtrix - see https://github.com/marcmerlin/FastLED_NeoMatrix for Tiled Matrixes, Zig-Zag and so forth
FastLED_NeoMatrix *matrix = new FastLED_NeoMatrix(leds, kMatrixWidth, kMatrixHeight,
NEO_MATRIX_TOP      + NEO_MATRIX_LEFT +
NEO_MATRIX_ROWS       + NEO_MATRIX_ZIGZAG +
NEO_TILE_TOP + NEO_TILE_LEFT + NEO_TILE_ROWS);

void setup() {
Serial.begin(115200);
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalSMD5050);
FastLED.setMaxPowerInVoltsAndMilliamps(LED_VOLTS, MAX_MILLIAMPS);
FastLED.setBrightness(BRIGHTNESS_SETTINGS);
FastLED.clear();

modeBtn.begin();
modeBtn.onPressed(changeMode);
modeBtn.onPressedFor(LONG_PRESS_MS, brightnessButton);
modeBtn.onSequence(3, 2000, startAutoMode);
modeBtn.onSequence(5, 2000, brightnessOff);
sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQ));
}

void changeMode() {
Serial.println("Button pressed");
if (FastLED.getBrightness() == 0) FastLED.setBrightness(BRIGHTNESS_SETTINGS);//Re-enable if lights are "off"
autoChangePatterns = false;
buttonPushCounter = (buttonPushCounter + 1) % 6;
}

void startAutoMode() {
autoChangePatterns = true;
}

void brightnessButton() {
if (FastLED.getBrightness() == BRIGHTNESS_SETTINGS)FastLED.setBrightness(BRIGHTNESS_SETTINGS);
else if (FastLED.getBrightness() == BRIGHTNESS_SETTINGS) FastLED.setBrightness(BRIGHTNESS_SETTINGS);
else if (FastLED.getBrightness() == BRIGHTNESS_SETTINGS) FastLED.setBrightness(BRIGHTNESS_SETTINGS);
else if (FastLED.getBrightness() == 0) FastLED.setBrightness(BRIGHTNESS_SETTINGS); //Re-enable if lights are "off"
}

void brightnessOff(){
FastLED.setBrightness(0);//Lights out
}

void loop() {

// Don't clear screen if waterfall pattern, be sure to change this is you change the patterns / order
if (buttonPushCounter != 5) FastLED.clear();

modeBtn.read();

// Reset bandValues[]
for (int i = 0; i<NUM_BANDS; i++){
    bandValues = 0;
}

// Sample the audio pin
for (int i = 0; i < SAMPLES; i++) {
    newTime = micros();
    vReal = analogRead(AUDIO_IN_PIN); // A conversion takes about 9.7uS on an ESP32
    vImag = 0;
    while ((micros() - newTime) < sampling_period_us) { /* chill */ }
}

// Compute FFT
FFT.DCRemoval();
FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.Compute(FFT_FORWARD);
FFT.ComplexToMagnitude();

// Analyse FFT results
for (int i = 2; i < (SAMPLES/2); i++){       // Don't use sample 0 and only first SAMPLES/2 are usable. Each array element represents a frequency bin and its value the amplitude.
    if (vReal > NOISE) {                  // Add a crude noise filter

    /*8 bands, 12kHz top band
      if (i<=3 )         bandValues+= (int)vReal;
      if (i>3   && i<=6) bandValues+= (int)vReal;
      if (i>6   && i<=13 ) bandValues+= (int)vReal;
      if (i>13&& i<=27 ) bandValues+= (int)vReal;
      if (i>27&& i<=55 ) bandValues+= (int)vReal;
      if (i>55&& i<=112) bandValues+= (int)vReal;
      if (i>112 && i<=229) bandValues+= (int)vReal;
      if (i>229          ) bandValues+= (int)vReal;*/

    //16 bands, 12kHz top band
      if (i<=2 )         bandValues+= (int)vReal;
      if (i>2   && i<=3) bandValues+= (int)vReal;
      if (i>3   && i<=5) bandValues+= (int)vReal;
      if (i>5   && i<=7) bandValues+= (int)vReal;
      if (i>7   && i<=9) bandValues+= (int)vReal;
      if (i>9   && i<=13 ) bandValues+= (int)vReal;
      if (i>13&& i<=18 ) bandValues+= (int)vReal;
      if (i>18&& i<=25 ) bandValues+= (int)vReal;
      if (i>25&& i<=36 ) bandValues+= (int)vReal;
      if (i>36&& i<=50 ) bandValues+= (int)vReal;
      if (i>50&& i<=69 ) bandValues += (int)vReal;
      if (i>69&& i<=97 ) bandValues += (int)vReal;
      if (i>97&& i<=135) bandValues += (int)vReal;
      if (i>135 && i<=189) bandValues += (int)vReal;
      if (i>189 && i<=264) bandValues += (int)vReal;
      if (i>264          ) bandValues += (int)vReal;
    }
}

// Process the FFT data into bar heights
for (byte band = 0; band < NUM_BANDS; band++) {

    // Scale the bars for the display
    int barHeight = bandValues / AMPLITUDE;
    if (barHeight > TOP) barHeight = TOP;

    // Small amount of averaging between frames
    barHeight = ((oldBarHeights * 1) + barHeight) / 2;

    // Move peak up
    if (barHeight > peak) {
      peak = min(TOP, barHeight);
    }

    // Draw bars
    switch (buttonPushCounter) {
      case 0:
      rainbowBars(band, barHeight);
      break;
      case 1:
      // No bars on this one
      break;
      case 2:
      purpleBars(band, barHeight);
      break;
      case 3:
      centerBars(band, barHeight);
      break;
      case 4:
      changingBars(band, barHeight);
      break;
      case 5:
      waterfall(band);
      break;
    }

    // Draw peaks
    switch (buttonPushCounter) {
      case 0:
      whitePeak(band);
      break;
      case 1:
      outrunPeak(band);
      break;
      case 2:
      whitePeak(band);
      break;
      case 3:
      // No peaks
      break;
      case 4:
      // No peaks
      break;
      case 5:
      // No peaks
      break;
    }

    // Save oldBarHeights for averaging later
    oldBarHeights = barHeight;
}

// Decay peak
EVERY_N_MILLISECONDS(60) {
    for (byte band = 0; band < NUM_BANDS; band++)
      if (peak > 0) peak -= 1;
    colorTimer++;
}

// Used in some of the patterns
EVERY_N_MILLISECONDS(10) {
    colorTimer++;
}

EVERY_N_SECONDS(10) {
    if (autoChangePatterns) buttonPushCounter = (buttonPushCounter + 1) % 6;
}

FastLED.show();
}

// PATTERNS BELOW //

void rainbowBars(int band, int barHeight) {
int xStart = BAR_WIDTH * band;
for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    for (int y = TOP; y >= TOP - barHeight; y--) {
      matrix->drawPixel(x, y, CHSV((x / BAR_WIDTH) * (255 / NUM_BANDS), 255, 255));
    }
}
}

void purpleBars(int band, int barHeight) {
int xStart = BAR_WIDTH * band;
for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    for (int y = TOP; y >= TOP - barHeight; y--) {
      matrix->drawPixel(x, y, ColorFromPalette(purplePal, y * (255 / (barHeight + 1))));
    }
}
}

void changingBars(int band, int barHeight) {
int xStart = BAR_WIDTH * band;
for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    for (int y = TOP; y >= TOP - barHeight; y--) {
      matrix->drawPixel(x, y, CHSV(y * (255 / kMatrixHeight) + colorTimer, 255, 255));
    }
}
}

void centerBars(int band, int barHeight) {
int xStart = BAR_WIDTH * band;
for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    if (barHeight % 2 == 0) barHeight--;
    int yStart = ((kMatrixHeight - barHeight) / 2 );
    for (int y = yStart; y <= (yStart + barHeight); y++) {
      int colorIndex = constrain((y - yStart) * (255 / barHeight), 0, 255);
      matrix->drawPixel(x, y, ColorFromPalette(heatPal, colorIndex));
    }
}
}

void whitePeak(int band) {
int xStart = BAR_WIDTH * band;
int peakHeight = TOP - peak - 1;
for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    matrix->drawPixel(x, peakHeight, CHSV(0,0,255));
}
}

void outrunPeak(int band) {
int xStart = BAR_WIDTH * band;
int peakHeight = TOP - peak - 1;
for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    matrix->drawPixel(x, peakHeight, ColorFromPalette(outrunPal, peakHeight * (255 / kMatrixHeight)));
}
}

void waterfall(int band) {
int xStart = BAR_WIDTH * band;
double highestBandValue = 60000;      // Set this to calibrate your waterfall

// Draw bottom line
for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
    matrix->drawPixel(x, 0, CHSV(constrain(map(bandValues,0,highestBandValue,160,0),0,160), 255, 255));
}

// Move screen up starting at 2nd row from top
if (band == NUM_BANDS - 1){
    for (int y = kMatrixHeight - 2; y >= 0; y--) {
      for (int x = 0; x < kMatrixWidth; x++) {
      int pixelIndexY = matrix->XY(x, y + 1);
      int pixelIndex = matrix->XY(x, y);
      leds = leds;
      }
    }
}
}

驴友花雕 发表于 4 天前

【Arduino 动手做】FastLED矩阵的ESP32频谱分析仪VU表

【Arduino 动手做】FastLED矩阵的ESP32频谱分析仪VU表
项目链接:https://www.youtube.com/watch?v=Mgh2WblO5_c
项目作者:斯科特·马利,ALTech DIY

项目视频 :https://www.youtube.com/watch?v=26SWWaKUnCg&t=551s
项目代码:https://github.com/s-marley/ESP32_FFT_VU
arduinoFFT库:https://github.com/kosme/arduinoFFT





页: [1]
查看完整版本: 【Arduino 动手做】FastLED矩阵的ESP32频谱分析仪VU表