87浏览
查看: 87|回复: 2

[项目] 【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 个按钮:关闭显示屏

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

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

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

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

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

驴友花雕  中级技神
 楼主|

发表于 2025-6-24 20:37:08

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

项目代码

  1. // (Heavily) adapted from https://github.com/G6EJD/ESP32-8266-Audio-Spectrum-Display/blob/master/ESP32_Spectrum_Display_02.ino
  2. // Adjusted to allow brightness changes on press+hold, Auto-cycle for 3 button presses within 2 seconds
  3. // Edited to add Neomatrix support for easier compatibility with different layouts.
  4. #include <FastLED_NeoMatrix.h>
  5. #include <arduinoFFT.h>
  6. #include <EasyButton.h>
  7. #define SAMPLES         1024          // Must be a power of 2
  8. #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.
  9. #define AMPLITUDE       1000          // Depending on your audio source level, you may need to alter this value. Can be used as a 'sensitivity' control.
  10. #define AUDIO_IN_PIN    35            // Signal in on this pin
  11. #define LED_PIN         5             // LED strip data
  12. #define BTN_PIN         4             // Connect a push button to this pin to change patterns
  13. #define LONG_PRESS_MS   200           // Number of ms to count as a long press
  14. #define COLOR_ORDER     GRB           // If colours look wrong, play with this
  15. #define CHIPSET         WS2812B       // LED strip type
  16. #define MAX_MILLIAMPS   2000          // Careful with the amount of power here if running off USB port
  17. const int BRIGHTNESS_SETTINGS[3] = {5, 70, 200};  // 3 Integer array for 3 brightness settings (based on pressing+holding BTN_PIN)
  18. #define LED_VOLTS       5             // Usually 5 or 12
  19. #define NUM_BANDS       16            // To change this, you will need to change the bunch of if statements describing the mapping from bins to bands
  20. #define NOISE           500           // Used as a crude noise filter, values below this are ignored
  21. const uint8_t kMatrixWidth = 16;                          // Matrix width
  22. const uint8_t kMatrixHeight = 16;                         // Matrix height
  23. #define NUM_LEDS       (kMatrixWidth * kMatrixHeight)     // Total number of LEDs
  24. #define BAR_WIDTH      (kMatrixWidth  / (NUM_BANDS - 1))  // If width >= 8 light 1 LED width per bar, >= 16 light 2 LEDs width bar etc
  25. #define TOP            (kMatrixHeight - 0)                // Don't allow the bars to go offscreen
  26. #define SERPENTINE     true                               // Set to false if you're LEDS are connected end to end, true if serpentine
  27. // Sampling and FFT stuff
  28. unsigned int sampling_period_us;
  29. 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
  30. int oldBarHeights[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  31. int bandValues[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  32. double vReal[SAMPLES];
  33. double vImag[SAMPLES];
  34. unsigned long newTime;
  35. arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);
  36. // Button stuff
  37. int buttonPushCounter = 0;
  38. bool autoChangePatterns = false;
  39. EasyButton modeBtn(BTN_PIN);
  40. // FastLED stuff
  41. CRGB leds[NUM_LEDS];
  42. DEFINE_GRADIENT_PALETTE( purple_gp ) {
  43.   0,   0, 212, 255,   //blue
  44. 255, 179,   0, 255 }; //purple
  45. DEFINE_GRADIENT_PALETTE( outrun_gp ) {
  46.   0, 141,   0, 100,   //purple
  47. 127, 255, 192,   0,   //yellow
  48. 255,   0,   5, 255 };  //blue
  49. DEFINE_GRADIENT_PALETTE( greenblue_gp ) {
  50.   0,   0, 255,  60,   //green
  51. 64,   0, 236, 255,   //cyan
  52. 128,   0,   5, 255,   //blue
  53. 192,   0, 236, 255,   //cyan
  54. 255,   0, 255,  60 }; //green
  55. DEFINE_GRADIENT_PALETTE( redyellow_gp ) {
  56.   0,   200, 200,  200,   //white
  57. 64,   255, 218,    0,   //yellow
  58. 128,   231,   0,    0,   //red
  59. 192,   255, 218,    0,   //yellow
  60. 255,   200, 200,  200 }; //white
  61. CRGBPalette16 purplePal = purple_gp;
  62. CRGBPalette16 outrunPal = outrun_gp;
  63. CRGBPalette16 greenbluePal = greenblue_gp;
  64. CRGBPalette16 heatPal = redyellow_gp;
  65. uint8_t colorTimer = 0;
  66. // FastLED_NeoMaxtrix - see https://github.com/marcmerlin/FastLED_NeoMatrix for Tiled Matrixes, Zig-Zag and so forth
  67. FastLED_NeoMatrix *matrix = new FastLED_NeoMatrix(leds, kMatrixWidth, kMatrixHeight,
  68.   NEO_MATRIX_TOP        + NEO_MATRIX_LEFT +
  69.   NEO_MATRIX_ROWS       + NEO_MATRIX_ZIGZAG +
  70.   NEO_TILE_TOP + NEO_TILE_LEFT + NEO_TILE_ROWS);
  71. void setup() {
  72.   Serial.begin(115200);
  73.   FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalSMD5050);
  74.   FastLED.setMaxPowerInVoltsAndMilliamps(LED_VOLTS, MAX_MILLIAMPS);
  75.   FastLED.setBrightness(BRIGHTNESS_SETTINGS[1]);
  76.   FastLED.clear();
  77.   modeBtn.begin();
  78.   modeBtn.onPressed(changeMode);
  79.   modeBtn.onPressedFor(LONG_PRESS_MS, brightnessButton);
  80.   modeBtn.onSequence(3, 2000, startAutoMode);
  81.   modeBtn.onSequence(5, 2000, brightnessOff);
  82.   sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQ));
  83. }
  84. void changeMode() {
  85.   Serial.println("Button pressed");
  86.   if (FastLED.getBrightness() == 0) FastLED.setBrightness(BRIGHTNESS_SETTINGS[0]);  //Re-enable if lights are "off"
  87.   autoChangePatterns = false;
  88.   buttonPushCounter = (buttonPushCounter + 1) % 6;
  89. }
  90. void startAutoMode() {
  91.   autoChangePatterns = true;
  92. }
  93. void brightnessButton() {
  94.   if (FastLED.getBrightness() == BRIGHTNESS_SETTINGS[2])  FastLED.setBrightness(BRIGHTNESS_SETTINGS[0]);
  95.   else if (FastLED.getBrightness() == BRIGHTNESS_SETTINGS[0]) FastLED.setBrightness(BRIGHTNESS_SETTINGS[1]);
  96.   else if (FastLED.getBrightness() == BRIGHTNESS_SETTINGS[1]) FastLED.setBrightness(BRIGHTNESS_SETTINGS[2]);
  97.   else if (FastLED.getBrightness() == 0) FastLED.setBrightness(BRIGHTNESS_SETTINGS[0]); //Re-enable if lights are "off"
  98. }
  99. void brightnessOff(){
  100.   FastLED.setBrightness(0);  //Lights out
  101. }
  102. void loop() {
  103.   // Don't clear screen if waterfall pattern, be sure to change this is you change the patterns / order
  104.   if (buttonPushCounter != 5) FastLED.clear();
  105.   modeBtn.read();
  106.   // Reset bandValues[]
  107.   for (int i = 0; i<NUM_BANDS; i++){
  108.     bandValues[i] = 0;
  109.   }
  110.   // Sample the audio pin
  111.   for (int i = 0; i < SAMPLES; i++) {
  112.     newTime = micros();
  113.     vReal[i] = analogRead(AUDIO_IN_PIN); // A conversion takes about 9.7uS on an ESP32
  114.     vImag[i] = 0;
  115.     while ((micros() - newTime) < sampling_period_us) { /* chill */ }
  116.   }
  117.   // Compute FFT
  118.   FFT.DCRemoval();
  119.   FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  120.   FFT.Compute(FFT_FORWARD);
  121.   FFT.ComplexToMagnitude();
  122.   // Analyse FFT results
  123.   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.
  124.     if (vReal[i] > NOISE) {                    // Add a crude noise filter
  125.     /*8 bands, 12kHz top band
  126.       if (i<=3 )           bandValues[0]  += (int)vReal[i];
  127.       if (i>3   && i<=6  ) bandValues[1]  += (int)vReal[i];
  128.       if (i>6   && i<=13 ) bandValues[2]  += (int)vReal[i];
  129.       if (i>13  && i<=27 ) bandValues[3]  += (int)vReal[i];
  130.       if (i>27  && i<=55 ) bandValues[4]  += (int)vReal[i];
  131.       if (i>55  && i<=112) bandValues[5]  += (int)vReal[i];
  132.       if (i>112 && i<=229) bandValues[6]  += (int)vReal[i];
  133.       if (i>229          ) bandValues[7]  += (int)vReal[i];*/
  134.     //16 bands, 12kHz top band
  135.       if (i<=2 )           bandValues[0]  += (int)vReal[i];
  136.       if (i>2   && i<=3  ) bandValues[1]  += (int)vReal[i];
  137.       if (i>3   && i<=5  ) bandValues[2]  += (int)vReal[i];
  138.       if (i>5   && i<=7  ) bandValues[3]  += (int)vReal[i];
  139.       if (i>7   && i<=9  ) bandValues[4]  += (int)vReal[i];
  140.       if (i>9   && i<=13 ) bandValues[5]  += (int)vReal[i];
  141.       if (i>13  && i<=18 ) bandValues[6]  += (int)vReal[i];
  142.       if (i>18  && i<=25 ) bandValues[7]  += (int)vReal[i];
  143.       if (i>25  && i<=36 ) bandValues[8]  += (int)vReal[i];
  144.       if (i>36  && i<=50 ) bandValues[9]  += (int)vReal[i];
  145.       if (i>50  && i<=69 ) bandValues[10] += (int)vReal[i];
  146.       if (i>69  && i<=97 ) bandValues[11] += (int)vReal[i];
  147.       if (i>97  && i<=135) bandValues[12] += (int)vReal[i];
  148.       if (i>135 && i<=189) bandValues[13] += (int)vReal[i];
  149.       if (i>189 && i<=264) bandValues[14] += (int)vReal[i];
  150.       if (i>264          ) bandValues[15] += (int)vReal[i];
  151.     }
  152.   }
  153.   // Process the FFT data into bar heights
  154.   for (byte band = 0; band < NUM_BANDS; band++) {
  155.     // Scale the bars for the display
  156.     int barHeight = bandValues[band] / AMPLITUDE;
  157.     if (barHeight > TOP) barHeight = TOP;
  158.     // Small amount of averaging between frames
  159.     barHeight = ((oldBarHeights[band] * 1) + barHeight) / 2;
  160.     // Move peak up
  161.     if (barHeight > peak[band]) {
  162.       peak[band] = min(TOP, barHeight);
  163.     }
  164.     // Draw bars
  165.     switch (buttonPushCounter) {
  166.       case 0:
  167.         rainbowBars(band, barHeight);
  168.         break;
  169.       case 1:
  170.         // No bars on this one
  171.         break;
  172.       case 2:
  173.         purpleBars(band, barHeight);
  174.         break;
  175.       case 3:
  176.         centerBars(band, barHeight);
  177.         break;
  178.       case 4:
  179.         changingBars(band, barHeight);
  180.         break;
  181.       case 5:
  182.         waterfall(band);
  183.         break;
  184.     }
  185.     // Draw peaks
  186.     switch (buttonPushCounter) {
  187.       case 0:
  188.         whitePeak(band);
  189.         break;
  190.       case 1:
  191.         outrunPeak(band);
  192.         break;
  193.       case 2:
  194.         whitePeak(band);
  195.         break;
  196.       case 3:
  197.         // No peaks
  198.         break;
  199.       case 4:
  200.         // No peaks
  201.         break;
  202.       case 5:
  203.         // No peaks
  204.         break;
  205.     }
  206.     // Save oldBarHeights for averaging later
  207.     oldBarHeights[band] = barHeight;
  208.   }
  209.   // Decay peak
  210.   EVERY_N_MILLISECONDS(60) {
  211.     for (byte band = 0; band < NUM_BANDS; band++)
  212.       if (peak[band] > 0) peak[band] -= 1;
  213.     colorTimer++;
  214.   }
  215.   // Used in some of the patterns
  216.   EVERY_N_MILLISECONDS(10) {
  217.     colorTimer++;
  218.   }
  219.   EVERY_N_SECONDS(10) {
  220.     if (autoChangePatterns) buttonPushCounter = (buttonPushCounter + 1) % 6;
  221.   }
  222.   FastLED.show();
  223. }
  224. // PATTERNS BELOW //
  225. void rainbowBars(int band, int barHeight) {
  226.   int xStart = BAR_WIDTH * band;
  227.   for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
  228.     for (int y = TOP; y >= TOP - barHeight; y--) {
  229.       matrix->drawPixel(x, y, CHSV((x / BAR_WIDTH) * (255 / NUM_BANDS), 255, 255));
  230.     }
  231.   }
  232. }
  233. void purpleBars(int band, int barHeight) {
  234.   int xStart = BAR_WIDTH * band;
  235.   for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
  236.     for (int y = TOP; y >= TOP - barHeight; y--) {
  237.       matrix->drawPixel(x, y, ColorFromPalette(purplePal, y * (255 / (barHeight + 1))));
  238.     }
  239.   }
  240. }
  241. void changingBars(int band, int barHeight) {
  242.   int xStart = BAR_WIDTH * band;
  243.   for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
  244.     for (int y = TOP; y >= TOP - barHeight; y--) {
  245.       matrix->drawPixel(x, y, CHSV(y * (255 / kMatrixHeight) + colorTimer, 255, 255));
  246.     }
  247.   }
  248. }
  249. void centerBars(int band, int barHeight) {
  250.   int xStart = BAR_WIDTH * band;
  251.   for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
  252.     if (barHeight % 2 == 0) barHeight--;
  253.     int yStart = ((kMatrixHeight - barHeight) / 2 );
  254.     for (int y = yStart; y <= (yStart + barHeight); y++) {
  255.       int colorIndex = constrain((y - yStart) * (255 / barHeight), 0, 255);
  256.       matrix->drawPixel(x, y, ColorFromPalette(heatPal, colorIndex));
  257.     }
  258.   }
  259. }
  260. void whitePeak(int band) {
  261.   int xStart = BAR_WIDTH * band;
  262.   int peakHeight = TOP - peak[band] - 1;
  263.   for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
  264.     matrix->drawPixel(x, peakHeight, CHSV(0,0,255));
  265.   }
  266. }
  267. void outrunPeak(int band) {
  268.   int xStart = BAR_WIDTH * band;
  269.   int peakHeight = TOP - peak[band] - 1;
  270.   for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
  271.     matrix->drawPixel(x, peakHeight, ColorFromPalette(outrunPal, peakHeight * (255 / kMatrixHeight)));
  272.   }
  273. }
  274. void waterfall(int band) {
  275.   int xStart = BAR_WIDTH * band;
  276.   double highestBandValue = 60000;        // Set this to calibrate your waterfall
  277.   // Draw bottom line
  278.   for (int x = xStart; x < xStart + BAR_WIDTH; x++) {
  279.     matrix->drawPixel(x, 0, CHSV(constrain(map(bandValues[band],0,highestBandValue,160,0),0,160), 255, 255));
  280.   }
  281.   // Move screen up starting at 2nd row from top
  282.   if (band == NUM_BANDS - 1){
  283.     for (int y = kMatrixHeight - 2; y >= 0; y--) {
  284.       for (int x = 0; x < kMatrixWidth; x++) {
  285.         int pixelIndexY = matrix->XY(x, y + 1);
  286.         int pixelIndex = matrix->XY(x, y);
  287.         leds[pixelIndexY] = leds[pixelIndex];
  288.       }
  289.     }
  290.   }
  291. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2025-6-24 20:39:50

【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

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

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

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail