回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

【Arduino 动手做】8-64通道FFT频谱分析仪 简单

头像 驴友花雕 2025.08.02 8 0

这与 FFT 频谱分析仪有关。它有 8、16、24、32 甚至 64 个频率箱(通道),如果您修改固件,您也许可以将该频率箱数量增加一倍。
PCB 能够驱动像素矩阵(WS2812 或 LED 矩阵),或者您可以连接一个或多个 HUB75E 显示器,但您必须选择使用哪一个并相应地调整设置中的参数。
您可以使用音频输入连接音频信号,也可以使用麦克风输入连接小型电容式麦克风。虽然使用麦克风会因为它的局限性而限制频率响应。
输入灵敏度是自动的,它将调整到输入的电平。您可以调整亮度和峰值保持时间。当它没有收到任何输入信号时,一段时间后,它将进入点火模式,在这种模式下,一些 led/显示器会像火一样亮起。
我为我的原型使用了两块 HUB75E 面板,因为它们安装在木架上。PCB 安装在显示器的背面。我为电子产品设计了一个 PCB。PCB 可以在我的 Tindie 网上商店购买。固件 (Arduino Sketch ) 是开源的,您可以根据需要对其进行修改。
以下是规格:
音频中的线路
音频中的麦克风
8、16、24、32 或 64 个通道
不同的图案和颜色
可调峰值延迟时间
可选输入滤波器
每隔几秒钟自动更改模式(可设置为开/关)
基于ESP32
PCB 提供板载前置放大器和连接器或 HUB75 显示器
可驱动 WS2812 等灯带
可驱动系列HUB75€面板
可调节整体亮度
PCB 提供预组装的 SMD 元件。您只需要添加 ESP32 板以及连接器/运算放大器和插座。
提供原理图和PCB布局以及gerber文件(PCB生产文件)
正在进行的工作:
未来的固件更新将包括:
平滑的 LED 过渡,因为 LED 速度非常快,它会跟随数据流。我想在每个 LED 转动时实现一种余辉。这样移动的酒吧看起来就不那么忙碌了。
Web 界面,我想在 wifi 上输出数据,以便在 Web 浏览器上实时可视化频谱。尽管从技术上讲,这已经完成,但挑战将是同时使用 ADC。那些了解 ESP32 架构的人现在会明白我的意思。
更改频段数 运行时间 与现在不同,它是在编译之前完成的,并且只能通过上传具有更改参数的固件来更改。
详细化和自动化校准模式。

我设计了一个 PCB,您可以在我的 Tindie 商店购买:
https://www.tindie.com/products/markdonners/pcb-8-...
所有 SMD 组件均已预组装,您只需添加一些插座、连接器和 e ESP32 板。
我使用了 DOIT 的 ESP32 DEVKIT V1
您还需要一个像样的电源,仅使用 USB 为此 PCB 和显示器或 ledmatrix 供电会损坏您的 ESP32,因为板载稳压器无法处理那么大的功率。
您还必须决定要使用哪种可视化效果。您可以使用一个或多个 HUB75 显示器,也可以使用基于像素的矩阵。您可以购买一个矩阵,也可以制作一个带有多个 LED 灯条的矩阵。
您需要相应地调整 arduino 草图中的设置。

我使用了 Arduino IDE。它可以在线免费获得,并且可以完成这项工作。然而,我最近偶然发现了一种叫做 Sloeber Beryllium 的东西,这是一个很棒的工具,提供了更好的编译器接口。然而,它有一点学习曲线,但我保证,这是值得的!你为什么不去看看呢?您还可以使用 Visual Studio 或其他一些出色的 IDE。但是,重要的是 正确的库 最好不要安装不需要的内容,因为它可能会在编译时给您带来错误。确保您的 Arduino IDE 设置为使用 ESP32。如果您不知道如何作,请谷歌或查看 youtube 视频。有一些非常明确的说明,设置 IDE 并不难。你可以的!

8-64 通道 FFT 频谱分析仪

音频中的线路
音频中的麦克风
8、16、24、32 或 64 个通道
不同的图案和颜色
可调峰值延迟时间
可选输入滤波器
每隔几秒钟自动更改模式(可设置为开/关)
基于ESP32
PCB 提供板载前置放大器和连接器或 HUB75 显示器
可驱动 WS2812 等灯带
可驱动系列HUB75€面板
可调节整体亮度 PCB 提供预组装的 SMD 元件。您只需要添加 ESP32 板以及连接器/运算放大器和插座。 提供原理图和PCB布局以及gerber文件(PCB生产文件) 正在进行的工作: 未来的固件更新将包括:
平滑的 LED 过渡,因为 LED 速度非常快,它会跟随数据流。我想在每个 LED 转动时实现一种余辉。这样移动的酒吧看起来就不那么忙碌了。
Web 界面,我想在 wifi 上输出数据,以便在 Web 浏览器上实时可视化频谱。尽管从技术上讲,这已经完成,但挑战将是同时使用 ADC。那些了解 ESP32 架构的人现在会明白我的意思。
更改频段数 运行时间 与现在不同,它是在编译之前完成的,并且只能通过上传具有更改参数的固件来更改。
详细化和自动化校准模式。

00.jpg
02.jpg
03.jpg
04.jpg
05.jpg
06.jpg
07.jpg

项目代码

代码
 /********************************************************************************************************************************************************
 *                                                                                                                                                       *
 *  Project:         FFT Spectrum Analyzer                                                                                                               *
 *  Target Platform: ESP32                                                                                                                               *
 *                                                                                                                                                       * 
 *  Version: 1.0                                                                                                                                         *
 *  Hardware setup: See github                                                                                                                           *
 *  Spectrum analyses done with analog chips MSGEQ7                                                                                                      *
 *                                                                                                                                                       * 
 *  Mark Donners                                                                                                                                         *
 *  The Electronic Engineer                                                                                                                              *
 *  Website:   www.theelectronicengineer.nl                                                                                                              *
 *  facebook:  https://www.facebook.com/TheelectronicEngineer                                                                                            *
 *  youtube:   https://www.youtube.com/channel/UCm5wy-2RoXGjG2F9wpDFF3w                                                                                  *
 *  github:    https://github.com/donnersm                                                                                                               *
 *                                                                                                                                                       *  
 ********************************************************************************************************************************************************* 
 * Version History                                                                                                                                       *
 *  1.0 First release, code extraced from 14 band spectrum analyzer 3.00 and modified to by used with FFT on a ESP32. No need for frequency board or     *
 *      MCGEQ7 chips.                                                                                                                                    *
 *      - HUB75 interface or                                                                                                                             *
 *      - WS2812 leds ( matrix/ledstrips)                                                                                                                *
 *      - 8/16/32 or 64 channel analyzer                                                                                                                 *
 *      - calibration for White noise, pink noise, brown noise sensitivity included and selectable                                                         *
 *      - Fire screensaver                                                                                                                               *
 *      - Display of logo and interface text when used with HUB75                                                                                        *
 *                                                                                                                                                       *
 *********************************************************************************************************************************************************
 * Version FFT 1.0 release July 2021                                                                                                                     *
 *********************************************************************************************************************************************************
 *  Status   | Description                                                                                                                               *
 *  Open     | Some Hub75 displays use a combination of chipsets of are from a different productions batch which will not work with this libary          *
 *  Open     | Sometime the long press for activating/de-activating the autoChange Pattern mode doesn't work                                             *
 *  Solved   | When using 64 bands, band 0 is always at max value. This was caused by the array dize [64]-> solved by chnaging it to 65                  *
 * Not a bug | Different types of HUB75 displays require different libary settings.It is what it is and it all depends on what the distributer sends you.*
 *           | For into on the libary settings, see the library documentation on Github: https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA  *
 * Wish      | Web interface. not possible without some heavy workaround cant use WIFI and ADC at same time                                              *
 * *******************************************************************************************************************************************************          
 * People who inspired me to do this build and figure out how stuff works:
 * Dave Plummer         https://www.youtube.com/channel/UCNzszbnvQeFzObW0ghk0Ckw
 * Mrfaptastic          https://github.com/mrfaptastic
 * Scott Marley         https://www.youtube.com/user/scottmarley85
 * Brian Lough          https://www.youtube.com/user/witnessmenow
 * atomic14             https://www.youtube.com/channel/UC4Otk-uDioJN0tg6s1QO9lw
 * 
 * Make sure your arduino IDE settings: Compiler warnings is set to default to make sure the code will compile                                           */





#define VERSION     "V1.0"

#include <FastLED_NeoMatrix.h>
#include <arduinoFFT.h>
#include "I2SPLUGIN.h"
#include <math.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include "FFT.h"
#include "LEDDRIVER.H"
#include "Settings.h"
#include "PatternsHUB75.h"
#include "PatternsLedstrip.h"                            
#include "fire.h"
#include "logos.h"
int skip=true;
int ButtonOnTimer=0;
int ButtonStarttime=0;
int ButtonSequenceCounter=0;
int ButtonOffTimer=0;
int ButtonStoptime=0;

int ButtonPressedCounter=0;
int ButtonReleasedCounter=0;
int ShortPressFlag=0;
int LongPressFlag=0;
int LongerPressFlag=0;
boolean Next_is_new_pressed= true;
boolean Next_is_new_release = true;
int PreviousPressTime=0;
#define up  1
#define down 0
int PeakDirection=0;

long LastDoNothingTime = 0;                       // only needed for screensaver
int DemoModeMem=0;                                   // to remember what mode we are in when going to demo, in order to restore it after wake up
bool AutoModeMem=false;                                // same story
bool DemoFlag=false;                               // we need to know if demo mode was manually selected or auto engadged. 

char LCDPrintBuf[30];



void setup() {
 Serial.begin(115200);
 Serial.println("Setting up Audio Input I2S");
 setupI2S();
 Serial.println("Audio input setup completed");
 delay(1000);
 #ifdef Ledstrip
  SetupLEDSTRIP();
 #endif  

 #ifdef HUB75
  SetupHUB75();
  if (kMatrixHeight>60){
  dma_display->setBrightness8(100);
  #if LogoBoot 
   drawLogo();
   delay(2500);
  #endif 
  }
 #endif
 
 
}

void loop() {
 size_t bytesRead = 0;
 int TempADC=0;
 if (skip==false)i2s_adc_disable(I2S_NUM_0);
 skip=false; // we only want to skip this the very first loop run.

 //Handle Userinterface
 {
  // set brightness and test if button is pressed
  TempADC = analogRead(BRIGHTNESSPOT);

  if (TempADC<10){ // ADC value < 10 so button is pressed
    ButtonOffTimer=0;
    ButtonOnTimer=millis()-ButtonStarttime;
    ButtonStoptime=millis();
  }
  else { // no Button pressed so ADC value is not related to button and can be proccessed
  ButtonOnTimer=0;
  ButtonOffTimer=millis()-ButtonStoptime;
  ButtonStarttime = millis();
  
  // read potmeters and process
  Peakdelay=map(analogRead(PEAKDELAYPOT),0,4095,1,100);
  BRIGHTNESSMARK=map(TempADC,100,2100,BRIGHTNESSMIN,BRIGHTNESSMAX);
// dbgprint("potm:%d",BRIGHTNESSMARK);
  #ifdef Ledstrip
    FastLED.setBrightness(BRIGHTNESSMARK);
  #endif
  #ifdef HUB75
    dma_display->setBrightness8(map(TempADC,100,2100,BRIGHTNESSMIN,BRIGHTNESSMAX));

   #endif
  }
 
 
  if(ButtonOffTimer>ButtonTimeout){
    ButtonStoptime=millis(); // time that no switch was presset will reset the counter.
    ButtonSequenceCounter=0; // reset the sequencecounter
    if (ShortPressFlag==1){
     // Serial.printf("Short press detected\n");
      buttonPushCounter = (buttonPushCounter + 1) % 13;
      #ifdef HUB75
      dma_display->clearScreen();
      #endif
      Serial.printf("Pattern Mode changed to: %d\n",buttonPushCounter);
      ShortPressFlag=0;
    }
  }
  if ((ButtonOnTimer>LongerPress)&&(ButtonOnTimer<(LongerPress+ShortPress))){LongerPressFlag=1;}
  else if  ((ButtonOnTimer>LongPress)&&(ButtonOnTimer<(LongPress+ShortPress))){LongPressFlag=1;}
  else if ((ButtonOnTimer>ShortPress)&&(ButtonOnTimer<(2*ShortPress))){
    ShortPressFlag=1;
    if((millis()-PreviousPressTime)<ButtonSequenceRepeatTime){
      ButtonSequenceCounter++;
      dbgprint("Multible press counter: %d\n",ButtonSequenceCounter);
      ShortPressFlag=0;
      CalibrationType= (CalibrationType +1)% 4;
      Serial.printf("Calibration table changed to: %s\n",Filtername[CalibrationType]);
      
      sprintf(LCDPrintBuf,"Cal Filter: %s",Filtername[CalibrationType]);
      DisplayPrint(LCDPrintBuf);
      
    }
    PreviousPressTime=millis();
  }
  if (LongerPressFlag==1){ 
    dbgprint( "Longer press detected\n");
    autoChangePatterns = !autoChangePatterns;
    if(autoChangePatterns == true){ 
      Serial.print("Patterns wil now change every few seconds\n");
      DisplayPrint((char*) "Autochange ON");
    }
    else {
      Serial.print("Automatically changing of pattern is now disabled\n");
      DisplayPrint((char*) "Autochange OFF");
    }
    LongerPressFlag=0;
    ShortPressFlag=0;
  } 
  else if (LongPressFlag==1){
    dbgprint("long press detected\n");
    autoChangePatterns = !autoChangePatterns;
    if(autoChangePatterns == true){ 
      Serial.print("Patterns wil now change every few seconds\n");
      DisplayPrint((char*) "Autochange ON");
    }
    else {
      Serial.print("Automatically changing of pattern is now disabled\n");
      DisplayPrint((char*)"Autochange OFF");
    }
    LongPressFlag=0;
    ShortPressFlag=0;
  }
 } // end user interface


  //############ Step 1: read samples from the I2S Buffer ##################
 i2s_adc_enable(I2S_NUM_0);

 i2s_read(I2S_PORT, 
         (void*)samples, 
          sizeof(samples),  
          &bytesRead,   // workaround This is the actual buffer size last half will be empty but why?
          portMAX_DELAY); // no timeout

 if (bytesRead != sizeof(samples)){
   Serial.printf("Could only read %u bytes of %u in FillBufferI2S()\n", bytesRead, sizeof(samples));
   // return;
 }

 //############ Step 2: compensate for Channel number and offset, safe all to vReal Array   ############
 for (uint16_t i = 0; i < ARRAYSIZE(samples); i++) {
   vReal[i] = offset-samples[i];
   vImag[i] = 0.0; //Imaginary part must be zeroed in case of looping to avoid wrong calculations and overflows
   #if PrintADCRAW
    Serial.printf("%7d,",samples[i]); 
   #endif

   #if VisualizeAudio
    Serial.printf("%d\n",samples[i]);
   #endif
  }
 
   #if PrintADCRAW
    Serial.printf("\n"); 
   #endif

 //############ Step 3: Do FFT on the VReal array  ############
  // compute FFT
  FFT.DCRemoval();
  FFT.Windowing(vReal, SAMPLEBLOCK, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(vReal, vImag, SAMPLEBLOCK, FFT_FORWARD);
  FFT.ComplexToMagnitude(vReal, vImag, SAMPLEBLOCK);
  FFT.MajorPeak(vReal, SAMPLEBLOCK, samplingFrequency); 
  for (int i = 0; i < numBands; i++) {
    FreqBins[i] = 0;
  }
 //############ Step 4: Fill the frequency bins with the FFT Samples ############
 float averageSum = 0.0f;
 for (int i = 2; i < SAMPLEBLOCK / 2; i++){ 
   averageSum+=vReal[i];
   if (vReal[i] > NoiseTresshold){
     int freq = BucketFrequency(i);
     int iBand = 0;
     while (iBand < numBands){
       if (freq < BandCutoffTable[iBand])break;
       iBand++;
     }
     if (iBand > numBands)iBand = numBands;
     FreqBins[iBand]+= vReal[i]; 
     //  float scaledValue = vReal[i];
     //  if (scaledValue > peak[iBand])
     //    peak[iBand] = scaledValue;
   }
 }

// bufmd[0]=FreqBins[12];
 #if PrintRAWBins
  for ( int y=0; y<numBands;y++){
    Serial.printf("%7.1f,",FreqBins[y]);
  }
  Serial.printf("\n");
 #endif
  
 //############ Step 5: Determine the VU value  and mingle in the readout...( cheating the bands ) ############ Step 
 float t=averageSum / (SAMPLEBLOCK / 2);
 gVU = max(t, (oldVU * 3 + t) / 4);
 oldVU = gVU; 
 if(gVU>DemoTreshold)LastDoNothingTime = millis(); // if there is signal in any off the bands[>2] then no demo mode

  // Serial.printf("gVu: %d\n",(int) gVU);

 for(int j=0;j<numBands;j++){
   if (CalibrationType==1)FreqBins[j]*= BandCalibration_Pink[j];
   else if (CalibrationType==2)FreqBins[j]*= BandCalibration_White[j];
    else if (CalibrationType==3)FreqBins[j]*= BandCalibration_Brown[j];

 }



 //*
 //############ Step 6: Averaging and making it all fit on screen 
 //for (int i = 0; i < numBands; i++) {
   //Serial.printf ("Chan[%d]:%d",i,(int)FreqBins[i]);
    //FreqBins[i] = powf(FreqBins[i], gLogScale); // in case we want log scale..i leave it in here as reminder
   //  Serial.printf( " - log: %d \n",(int)FreqBins[i]);
// }
 static float lastAllBandsPeak = 0.0f;
 float allBandsPeak = 0;
 //bufmd[1]=FreqBins[13];
 //bufmd[2]=FreqBins[1];
 for (int i = 0; i < numBands; i++){
   //allBandsPeak = max (allBandsPeak, FreqBins[i]);
   if (FreqBins[i]> allBandsPeak){
     allBandsPeak = FreqBins[i];
   }
 }   
 if (allBandsPeak < 1)allBandsPeak = 1;
 //  The followinf picks allBandsPeak if it's gone up.  If it's gone down, it "averages" it by faking a running average of GAIN_DAMPEN past peaks
 allBandsPeak = max(allBandsPeak, ((lastAllBandsPeak * (GAIN_DAMPEN-1)) + allBandsPeak) / GAIN_DAMPEN);  // Dampen rate of change a little bit on way down
 lastAllBandsPeak = allBandsPeak;


 if (allBandsPeak < 80000)allBandsPeak = 80000;
 for (int i = 0; i < numBands; i++){ 
   FreqBins[i] /= (allBandsPeak * 1.0f);
 }

 // Process the FFT data into bar heights
 for (int band = 0; band < numBands; band++) {
   int barHeight = FreqBins[band]*kMatrixHeight-1;  //(AMPLITUDE);
   if (barHeight > TOP-2) barHeight = TOP-2;
  
   // Small amount of averaging between frames
   barHeight = ((oldBarHeights[band] * 1) + barHeight) / 2;

   // Move peak up
   if (barHeight > peak[band]) {
     peak[band] = min(TOP, barHeight);
     PeakFlag[band]=1;
   }
    bndcounter[band]+=barHeight; // ten behoeve calibratie

    // if there hasn't been much of a input signal for a longer time ( see settings ) go to demo mode
   if ((millis() - LastDoNothingTime) > DemoAfterSec && DemoFlag==false)
   { dbgprint("In loop 1:  %d", millis() - LastDoNothingTime);
    DemoFlag=true;
    // first store current mode so we can go back to it after wake up
    DemoModeMem=buttonPushCounter;
    AutoModeMem=autoChangePatterns;
    autoChangePatterns=false;
    buttonPushCounter=12;
    #ifdef HUB75
    dma_display->clearScreen();
    #endif
    dbgprint("Automode is turned of because of demo");
   } 
   // Wait,signal is back? then wakeup!     
    else if (DemoFlag==true &&   (millis() - LastDoNothingTime) < DemoAfterSec   )   
    { //("In loop 2:  %d", millis() - LastDoNothingTime);
      // while in demo the democounter was reset due to signal on one of the bars.
      // So we need to exit demo mode.
      #ifdef HUB75
      dma_display->clearScreen();
      #endif
      buttonPushCounter=DemoModeMem; // restore settings
      dbgprint ("automode setting restored to: %d",AutoModeMem);
      autoChangePatterns=AutoModeMem;// restore settings
      DemoFlag=false;  
    }
    #if BottomRowAlwaysOn
      if (barHeight==0)barHeight=1; // make sure there is always one bar that lights up
    #endif
  // Now visualize those bar heights
  switch (buttonPushCounter) {
    case 0:
     #ifdef HUB75
      PeakDirection=down;
      BoxedBars(band, barHeight);
      BluePeak(band);
     #endif
     #ifdef Ledstrip
      changingBarsLS(band, barHeight);
     #endif
     break;
     
    case 1: 
     #ifdef HUB75
      PeakDirection=down;
      BoxedBars2(band, barHeight);
      BluePeak(band);
     #endif 
     #ifdef Ledstrip
     TriBarLS(band, barHeight);
     TriPeakLS(band);
     #endif
     break;
    case 2:
     #ifdef HUB75
      PeakDirection=down;
      BoxedBars3(band, barHeight);
      RedPeak(band);
     #endif
     #ifdef Ledstrip
      rainbowBarsLS(band, barHeight);
      NormalPeakLS(band, PeakColor1);
     #endif
      break;
    case 3:
     #ifdef HUB75
      PeakDirection=down;
      RedBars(band, barHeight); 
      BluePeak(band);
     #endif
     #ifdef Ledstrip
      purpleBarsLS(band, barHeight);
      NormalPeakLS(band, PeakColor2);
     #endif
      break;
    case 4:
     #ifdef HUB75
      PeakDirection=down;
      ColorBars(band, barHeight);
     #endif
     #ifdef Ledstrip
      SameBarLS(band, barHeight); 
      NormalPeakLS(band, PeakColor3);
     #endif
      break;
    case 5:
     #ifdef HUB75
      PeakDirection=down;
      Twins(band, barHeight);
      WhitePeak(band);
     #endif
     #ifdef Ledstrip
      SameBar2LS(band, barHeight); 
      NormalPeakLS(band, PeakColor3);
     #endif
     break;
    case 6:
     #ifdef HUB75
      PeakDirection=down;
      Twins2(band, barHeight);
      WhitePeak(band);
     #endif
     #ifdef Ledstrip
      centerBarsLS(band, barHeight);
     #endif
      break;
    case 7:
     #ifdef HUB75
       PeakDirection=down;
      TriBars(band, barHeight);
      TriPeak(band);
     #endif
     #ifdef Ledstrip
      centerBars2LS(band, barHeight);
     #endif
      break;
    case 8:
     #ifdef HUB75 
      PeakDirection=up;
      TriBars(band, barHeight);
      TriPeak(band);
     #endif
     #ifdef Ledstrip
      centerBars3LS(band, barHeight);
     #endif
      break;
    case 9:
     #ifdef HUB75
     PeakDirection=down;
     centerBars(band, barHeight);
     #endif
     #ifdef Ledstrip
       BlackBarLS(band, barHeight);
       outrunPeakLS(band);
     #endif
     break;
    case 10:
     #ifdef HUB75
     PeakDirection=down;
     centerBars2(band, barHeight);
     #endif
     #ifdef Ledstrip
      BlackBarLS(band, barHeight);
      NormalPeakLS(band, PeakColor5);
     #endif
      break;
    case 11:
     #ifdef HUB75
      PeakDirection=down;
      BlackBars(band, barHeight);
      DoublePeak(band);     
     #endif
     #ifdef Ledstrip
      BlackBarLS(band, barHeight);
      TriPeak2LS(band);
     #endif
      break;
    case 12:
     #ifdef HUB75
      make_fire(); // go to demo mode
     #endif
     #ifdef Ledstrip
      matrix->fillRect(0, 0, matrix->width(), 1, 0x0000); // delete the VU meter
      make_fire();
     #endif
     break;
  } 
  
  // Save oldBarHeights for averaging later
  oldBarHeights[band] = barHeight;
 }
// for calibration
 //bndcounter[h]+=barHeight;
 if (loopcounter==256){
  loopcounter=0;
 #if CalibratieLog 
   Calibration();
   for(int g=0;g<numBands;g++)bndcounter[g]=0;
 #endif
 
 }
 loopcounter++;

 if (buttonPushCounter!=12) DrawVUMeter(0); // Draw it when not in screensaver mode
 
 #if PrintRAWBins
  Serial.printf("\n");
  //delay(10);
 #endif


  
   // Decay peak
 EVERY_N_MILLISECONDS(Fallingspeed){
   for (byte band = 0; band < numBands; band++){
     if(PeakFlag[band]==1){
       PeakTimer[band]++;
       if (PeakTimer[band]> Peakdelay){PeakTimer[band]=0;PeakFlag[band]=0;}
     }
     else if ((peak[band] > 0) &&(PeakDirection==up)){ 
       peak[band] += 1;
       if (peak[band]>(kMatrixHeight+10))peak[band]=0;
       } // when to far off screen then reset peak height
     else if ((peak[band] > 0)&&(PeakDirection==down)){ peak[band] -= 1;}
   }   
     colorTimer++;
 }

 
 EVERY_N_MILLISECONDS(10)colorTimer++; // Used in some of the patterns
 
 EVERY_N_SECONDS(SecToChangePattern) {
   // if (FastLED.getBrightness() == 0) FastLED.setBrightness(BRIGHTNESSMARK);  //Re-enable if lights are "off"
   if (autoChangePatterns){ 
    buttonPushCounter = (buttonPushCounter + 1) % 12;
    #ifdef HUB75
    dma_display->clearScreen();
    #endif
   }
 }

 
 #ifdef Ledstrip
  delay(1); // needed to give fastled a minimum recovery time
  FastLED.show();
 #endif
  
} // loop end






  // BucketFrequency
  //
  // Return the frequency corresponding to the Nth sample bucket.  Skips the first two 
  // buckets which are overall amplitude and something else.

int BucketFrequency(int iBucket){
 if (iBucket <= 1)return 0;
 int iOffset = iBucket - 2;
 return iOffset * (samplingFrequency / 2) / (SAMPLEBLOCK / 2);
}




void DrawVUPixels(int i, int yVU, int fadeBy = 0){ 
 CRGB VUC;
 if (i>(PANE_WIDTH/3)){
   VUC.r=255;
   VUC.g=0;
   VUC.b=0 ;
 }
 else if (i>(PANE_WIDTH/5)){
   VUC.r=255;
   VUC.g=255;
   VUC.b=0;
 }
 else{ // green
   VUC.r=0;
   VUC.g=255;
   VUC.b=0;
 }
 
 #ifdef Ledstrip
  int xHalf = matrix->width()/2;
//  matrix->drawPixel(xHalf-i-1, yVU, CRGB(0,100,0).fadeToBlackBy(fadeBy));
//  matrix->drawPixel(xHalf+i,   yVU, CRGB(0,100,0).fadeToBlackBy(fadeBy));
  matrix->drawPixel(xHalf-i-1, yVU, CRGB(VUC.r,VUC.g,VUC.b).fadeToBlackBy(fadeBy));
  matrix->drawPixel(xHalf+i,   yVU, CRGB(VUC.r,VUC.g,VUC.b).fadeToBlackBy(fadeBy));
  
 #endif

 #ifdef HUB75
  int xHalf = PANE_WIDTH/2;
  dma_display->drawPixelRGB888(xHalf-i-2,yVU,VUC.r,VUC.g,VUC.b); //left side of screen line 0
  dma_display->drawPixelRGB888(xHalf-i-2,yVU+1,VUC.r,VUC.g,VUC.b); //left side of screen line 1
  dma_display->drawPixelRGB888(xHalf+i+1,yVU,VUC.r,VUC.g,VUC.b); // right side of screen line 0
  dma_display->drawPixelRGB888(xHalf+i+1,yVU+1,VUC.r,VUC.g,VUC.b);// right side of screen line 1
 #endif
}



void DrawVUMeter(int yVU){
 static int iPeakVUy = 0;        // size (in LED pixels) of the VU peak
 static unsigned long msPeakVU = 0;       // timestamp in ms when that peak happened so we know how old it is
 const int MAX_FADE = 256;
 #ifdef HUB75
  for(int x=0; x<PANE_WIDTH;x++){
    dma_display->drawPixelRGB888(x,yVU,0,0,0);
    dma_display->drawPixelRGB888(x,yVU+1,0,0,0);
  }
 #endif
 #ifdef Ledstrip
  matrix->fillRect(0, yVU, matrix->width(), 1, 0x0000);
 #endif
 if (iPeakVUy > 1){
   int fade = MAX_FADE * (millis() - msPeakVU) / (float) 1000;
   DrawVUPixels(iPeakVUy,   yVU, fade);
 }
 int xHalf = (PANE_WIDTH/2)-1;
 int bars  = map(gVU, 0, MAX_VU, 1, xHalf);
 bars = min(bars, xHalf);
 if(bars > iPeakVUy){
   msPeakVU = millis();
   iPeakVUy = bars;
 }
 else if (millis() - msPeakVU > 1000)iPeakVUy = 0;
 for (int i = 0; i < bars; i++)DrawVUPixels(i, yVU);
      
}

void Calibration(void){
  Serial.printf("BandCalibration_XXXX[%1d]=\n{",numBands);
  long Totalbnd=0;
  
  for (int g=0;g<numBands;g++){
    if (bndcounter[g]>Totalbnd)Totalbnd=bndcounter[g];
  }
  
  
  for (int g=0;g<numBands;g++){
    bndcounter[g]=Totalbnd/bndcounter[g];
    Serial.printf(" %2.2f",bndcounter[g]);
    if(g<numBands-1)Serial.printf(",");
    else Serial.print(" };\n");
  }
}

//**************************************************************************************************
//                                          D B G P R I N T                                        *
//**************************************************************************************************
// Send a line of info to serial output.  Works like vsprintf(), but checks the DEBUG flag.        *
// Print only if DEBUG flag is true.  Always returns the formatted string.                         *
// Usage dbgprint("this is the text you want: %d", variable);
//**************************************************************************************************
void dbgprint(const char * format, ...) {
  if (DEBUG) {
    static char sbuf[DEBUG_BUFFER_SIZE]; // For debug lines
    va_list varArgs; // For variable number of params
    va_start(varArgs, format); // Prepare parameters
    vsnprintf(sbuf, sizeof(sbuf), format, varArgs); // Format the message
    va_end(varArgs); // End of using parameters
    if (DEBUG) // DEBUG on?
    {
      Serial.print("Debug: "); // Yes, print prefix
      Serial.println(sbuf); // and the info
    }
   // return sbuf; // Return stored string
  }
}

void make_fire() {
  uint16_t i, j;
  
  if (t > millis()) return;
  t = millis() + (1000 / FPS);

  // First, move all existing heat points up the display and fade
 
  for (i = rows - 1; i > 0; --i) {
    for (j = 0; j < cols; ++j) {
      uint8_t n = 0;
      if (pix[i - 1][j] > 0)
        n = pix[i - 1][j] - 1;
      pix[i][j] = n;
    }
  }

  // Heat the bottom row
  for (j = 0; j < cols; ++j) {
    i = pix[0][j];
    if (i > 0) {
      pix[0][j] = random(NCOLORS - 6, NCOLORS - 2);
    }
  }

  // flare
  for (i = 0; i < nflare; ++i) {
    int x = flare[i] & 0xff;
    int y = (flare[i] >> 8) & 0xff;
    int z = (flare[i] >> 16) & 0xff;
    glow(x, y, z);
    if (z > 1) {
      flare[i] = (flare[i] & 0xffff) | ((z - 1) << 16);
    } else {
      // This flare is out
      for (int j = i + 1; j < nflare; ++j) {
        flare[j - 1] = flare[j];
      }
      --nflare;
    }
  }
  newflare();

  // Set and draw
  for (i = 0; i < rows; ++i) {
    for (j = 0; j < cols; ++j) {
     // matrix -> drawPixel(j, rows - i, colors[pix[i][j]]);
     CRGB COlsplit=colors[pix[i][j]];
     #ifdef HUB75
      dma_display->drawPixelRGB888(j,rows - i,COlsplit.r,COlsplit.g,COlsplit.b);
     #endif
     #ifdef Ledstrip
      matrix -> drawPixel(j, rows - i, colors[pix[i][j]]);
     #endif
    }
  }
}




void drawLogo(void){
#ifdef HUB75  
 // logo is 46 width and 54 high
 int i=0;
 int xyStart[2]={(kMatrixWidth/2)-23,2};
 CRGB pix;
 for (int y=0;y<50;y++){
   for (int x=0;x<46;x++){
     pix=logo[i];   
     i++;
     dma_display->drawPixelRGB888(x+xyStart[0],y+xyStart[1],pix.r,pix.g,pix.b);    
   }
 }
 dma_display->fillRect(5, 53, kMatrixWidth-10, 11, dma_display->color444(0, 0, 0));
 delay(1000);
 dma_display->setTextSize(1);
 dma_display->setTextWrap(false);
 dma_display->setCursor(10,55);
 dma_display->print("Spectrum FFT  ");  
 dma_display->print(VERSION);
 delay(2000);
 dma_display->fillRect(5, 53, kMatrixWidth-10, 11, dma_display->color444(0, 0, 0));
 dma_display->setTextColor(dma_display->color444(15,15,0));
 dma_display->setCursor(10,55);
 dma_display->print("   Mark Donners   ");
 delay(2000);
#endif
}

void DisplayPrint(char * text){
 #ifdef HUB75
   dma_display->fillRect(8, 8, kMatrixWidth-16, 11, dma_display->color444(0,0 , 0));
   dma_display->setTextSize(1);
   dma_display->setTextWrap(false);
   dma_display->setCursor(10,10);
   dma_display->print(text);
   delay(1000);
   dma_display->fillRect(8, 8, kMatrixWidth-16, 11, dma_display->color444(0,0 , 0));   
 #endif 
}

【Arduino 动手做】8-64通道FFT频谱分析仪
项目链接:https://www.instructables.com/8-64-Channel-FFT-Spectrum-Analyzer/
项目作者:emdee401

项目视频:https://www.youtube.com/watch?v=bQ7c9Vlhyp0
项目代码:https://github.com/donnersm/FFT_ESP32_Analyzer
https://github.com/donnersm/FFT_ESP32_Analyzer/blob/main/Main%20Sketch/V1.0/V1.0.ino
PCB文件:https://github.com/donnersm/FFT_ESP32_Analyzer/tree/main/Hardware
https://www.tindie.com/products/markdonners/pcb-8-64-channel-fft-spectrum-analyzer/

00193-.gif

评论

user-avatar
icon 他的勋章
    展开更多