驴友花雕 发表于 2025-8-2 09:50:39

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

这与 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 架构的人现在会明白我的意思。
更改频段数 运行时间 与现在不同,它是在编译之前完成的,并且只能通过上传具有更改参数的固件来更改。
详细化和自动化校准模式。















驴友花雕 发表于 2025-8-2 09:52:54

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

项目代码

/********************************************************************************************************************************************************
*                                                                                                                                                       *
*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 -> 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 up1
#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;



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);
      
      sprintf(LCDPrintBuf,"Cal Filter: %s",Filtername);
      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 = offset-samples;
   vImag = 0.0; //Imaginary part must be zeroed in case of looping to avoid wrong calculations and overflows
   #if PrintADCRAW
    Serial.printf("%7d,",samples);
   #endif

   #if VisualizeAudio
    Serial.printf("%d\n",samples);
   #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 = 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;
   if (vReal > NoiseTresshold){
   int freq = BucketFrequency(i);
   int iBand = 0;
   while (iBand < numBands){
       if (freq < BandCutoffTable)break;
       iBand++;
   }
   if (iBand > numBands)iBand = numBands;
   FreqBins+= vReal;
   //float scaledValue = vReal;
   //if (scaledValue > peak)
   //    peak = scaledValue;
   }
}

// bufmd=FreqBins;
#if PrintRAWBins
for ( int y=0; y<numBands;y++){
    Serial.printf("%7.1f,",FreqBins);
}
Serial.printf("\n");
#endif

//############ Step 5: Determine the VU valueand 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*= BandCalibration_Pink;
   else if (CalibrationType==2)FreqBins*= BandCalibration_White;
    else if (CalibrationType==3)FreqBins*= BandCalibration_Brown;

}



//*
//############ 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);
    //FreqBins = powf(FreqBins, gLogScale); // in case we want log scale..i leave it in here as reminder
   //Serial.printf( " - log: %d \n",(int)FreqBins);
// }
static float lastAllBandsPeak = 0.0f;
float allBandsPeak = 0;
//bufmd=FreqBins;
//bufmd=FreqBins;
for (int i = 0; i < numBands; i++){
   //allBandsPeak = max (allBandsPeak, FreqBins);
   if (FreqBins> allBandsPeak){
   allBandsPeak = FreqBins;
   }
}   
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 /= (allBandsPeak * 1.0f);
}

// Process the FFT data into bar heights
for (int band = 0; band < numBands; band++) {
   int barHeight = FreqBins*kMatrixHeight-1;//(AMPLITUDE);
   if (barHeight > TOP-2) barHeight = TOP-2;

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

   // Move peak up
   if (barHeight > peak) {
   peak = min(TOP, barHeight);
   PeakFlag=1;
   }
    bndcounter+=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 = barHeight;
}
// for calibration
//bndcounter+=barHeight;
if (loopcounter==256){
loopcounter=0;
#if CalibratieLog
   Calibration();
   for(int g=0;g<numBands;g++)bndcounter=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==1){
       PeakTimer++;
       if (PeakTimer> Peakdelay){PeakTimer=0;PeakFlag=0;}
   }
   else if ((peak > 0) &&(PeakDirection==up)){
       peak += 1;
       if (peak>(kMatrixHeight+10))peak=0;
       } // when to far off screen then reset peak height
   else if ((peak > 0)&&(PeakDirection==down)){ peak -= 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>Totalbnd)Totalbnd=bndcounter;
}


for (int g=0;g<numBands;g++){
    bndcounter=Totalbnd/bndcounter;
    Serial.printf(" %2.2f",bndcounter);
    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; // 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 > 0)
      n = pix - 1;
      pix = n;
    }
}

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

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

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




void drawLogo(void){
#ifdef HUB75
// logo is 46 width and 54 high
int i=0;
int xyStart={(kMatrixWidth/2)-23,2};
CRGB pix;
for (int y=0;y<50;y++){
   for (int x=0;x<46;x++){
   pix=logo;   
   i++;
   dma_display->drawPixelRGB888(x+xyStart,y+xyStart,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
}

驴友花雕 发表于 2025-8-2 09:54:09

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

【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/



页: [1]
查看完整版本: 【Arduino 动手做】8-64通道FFT频谱分析仪