13浏览
查看: 13|回复: 3

[项目] 【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵

[复制链接]
这次,我将向您展示如何制作一个视觉上美观的频谱分析仪,它也可以用作音频 VU 表和瀑布分析仪。该设备由 16x16 LED 矩阵制成,二极管上带有 WS2812 芯片,成本约为 12 美元。当我们使用 Line input(线路输入)时,输入的立体声信号首先使用两个 10K 电阻器转换为单声道,然后通过一个 100nF 电容器来阻断直流。然后,信号由两个 100k 电阻器偏置到 3.3V / 2 = 1.65V,以便 ADC 读取。使用 Microfon 板比线路输入方法简单得多,但您将受限于麦克风灵敏度可以检测到的频率。矩阵通过单个按钮进行控制。有五种作模式,其中三种是频谱分析仪、一种音频 VU 表和一种瀑布分析仪。

按钮功能包括:
• 单击:更改模式
• 长按:更改亮度
• 2 秒内按下 3 个按钮:设置为自动更改模式
• 2 秒内按下 5 个按钮:关闭显示屏
原始代码由 Scott Marley 在 GitHub 上编写,正如作者所说,它与 G6EJD 最初编写的示例进行了大量修改。我在现成的 16x16 矩阵上制作了这个项目,这简化了制作方法,我还针对这种情况对代码进行了非常小的调整,您可以在下面下载它。

该设备制作简单,包含多个组件:
• ESP32 微控制器
• 16x16 WS2812 LED 矩阵
• 包含前置放大器芯片的小型麦克风板
• 5 个电阻
• 1 个电容器
• 和 One button

如果您希望设备看起来与此相同,那么您还需要一台 3D 打印机。为了获得更好的视觉效果,矩阵二极管应用隔板分隔。为此,在 3D 打印机上打印网格。我创建了一个自定义网格,它需要更少的材料和打印时间。您可以通过下面的链接下载 STL 文件。应在网格上放置描图纸以散射光线。
在设备描述期间,设备在麦克风模式下运行。要通过线路输入运行 Analyzer,我们需要插入音频信号。我们可以根据源信号的强度来更改代码中的灵敏度。只需按一下按钮,我们就可以改变模式。长按用于更改亮度。亮度分为三个级别。在 2 秒内按下 3 个按钮,我们将设备设置为自动更改模式。在 5 秒内按下 2 个按钮将关闭显示。一个有趣的情况是,当我们输入具有特定频率的正弦信号时。为此,我使用了 Tone Generator。可以看出,这是一款用于频域分析的频谱分析仪仪器。通过这种方式,该设备可以用作实验室频率分析的简单工具,主要用于学习。
最后,将设备内置在一个合适的 PVC 板盒子中,并涂有自粘彩色标签。

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图1

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图3

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图2

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图4

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图5

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图6

驴友花雕  中级技神
 楼主|

发表于 4 小时前

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵

项目代码

  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       300          // 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_RIGHT +
  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 = 40000;        // 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. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 4 小时前

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图3

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图1

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图2

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 4 小时前

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图2

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图1

【Arduino 动手做】DIY 简单 FFT 频谱分析仪 16x16 LED 矩阵图3
回复

使用道具 举报

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

本版积分规则

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

硬件清单

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

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

mail