【Arduino 动手做】Arduino FFT 音频频谱分析仪,基于 8x32 颜色矩阵 WS2

在这里插入图片描述
《Arduino 手册(思路与案例)》栏目介绍:
在电子制作与智能控制的应用领域,本栏目涵盖了丰富的内容,包括但不限于以下主题:Arduino BLDC、Arduino CNC、Arduino E-Ink、Arduino ESP32 SPP、Arduino FreeRTOS、Arduino FOC、Arduino GRBL、Arduino HTTP、Arduino HUB75、Arduino IoT Cloud、Arduino JSON、Arduino LCD、Arduino OLED、Arduino LVGL、Arduino PID、Arduino TFT,以及Arduino智能家居、智慧交通、月球基地、智慧校园和智慧农业等多个方面与领域。不仅探讨了这些技术的基础知识和应用领域,还提供了众多具体的参考案例,帮助读者更好地理解和运用Arduino平台进行创新项目。目前,本栏目已有近4000篇相关博客,旨在为广大电子爱好者和开发者提供全面的学习资源与实践指导。通过这些丰富的案例和思路,读者可以获取灵感,推动自己的创作与开发进程。
https://blog.csdn.net/weixin_41659040/category_12422453.html

在这里插入图片描述

频谱分析仪将信号幅度显示为频率的函数,使工程师和技术人员能够可视化和分析信号特性。特别是,音频分析仪在频域中对声信号进行视觉呈现,其中信号的频率显示在 x 轴上,而某个频率的幅度显示在 y 轴上。在我之前的几个视频中,我介绍了几种不同类型的此类设备,但这次是第一次使用 FHT Arduino 库。该库比常用的 FFT 库快几倍,但以牺牲速度为代价,音频范围的两端会出现一定的分辨率和精度损失。

现在让我们看看该设备在实际条件下是如何工作的。

考虑到它非常简单,该设备无需任何先前设置即可立即运行。一个按钮可分 7 个步骤调节 LED 光强度。使用另一个按钮,我们浏览具有特定颜色集的 6 个不同的模组,我们还可以添加更多模组,只需对代码进行非常小的修改。

接下来,让我们测试一下该分析仪覆盖的频率范围。为此,我们将使用一个简单的在线音调生成器。如您所见,该设备覆盖了从 20 赫兹到 20 千赫兹的整个听力范围。这种大范围设备在用于视觉 FFT 分析时非常出色,但在呈现音乐材料时有一个实际缺点。

也就是说,这种音乐信号的很大一部分(也许是 90%)在高达 10Khz 的范围内,只有一小部分属于更高的频率。这实际上意味着在音乐信号发出的整个过程中,分析仪的最右侧部分将处于非活动状态。让我们看看它在实践中的样子(这是一个带有语音信号的示例,因此我们也将尝试使用音乐材料)。由于,正如我在开头提到的,这是一种视觉装饰性添加,而不是精确的测量仪器,因此最好将带宽减少一半,实际上为 10 kHz。

对于这种情况,我对代码进行了一些修改,但也希望在输入上设置一个简单的低通滤波器。让我们在输入端使用在线音调发生器测试范围。范围可达 10Khz。

现在,在这种情况下,矩阵被完全填充,在视觉上看起来要好得多。

至于设备的外观,我尝试制作一个简单但仍然实用的版本,由厚度为 4 毫米的 PVC 板和玻璃制成。

最后是一个简短的结论。

这是一个极其简单的项目,面向初学者,但在视觉上仍然非常有效,可以作为桌面上的小工具,或作为音频设备的补充。它也可以用作用于教育目的的简单学校 FFT 频谱分析仪仪器。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
项目代码

/*
  Copyright (c) 2020 Janux

  Permission is hereby granted,   free of charge, to any person obtaining a copy
  of this software and associated   documentation files (the "Software"), to deal
  in the Software without restriction,   including without limitation the rights
  to use, copy, modify, merge, publish,   distribute, sublicense, and/or sell
  copies of the Software, and to permit persons   to whom the Software is
  furnished to do so, subject to the following conditions:
   The above copyright notice and this permission notice shall be included in all
   copies or substantial portions of the Software.
  THE SOFTWARE IS PROVIDED   "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT   NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR   PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF   CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.

  Based on an   original project for the MAX72xx LED matrix and FFT lib made from Shajeeb.
  Configuration   settings section based on work of Ragnar Ranøyen Homb from Norvegian Creation.
*/

#define   LIN_OUT 1                //FHT linear output magnitude
#define FHT_N 128                //set   SAMPLES for FHT, Must be a power of 2
#include <FHT.h>

#define xres 32                  //Total number of columns in the display, must be <= SAMPLES/2
#define   yres 8                  //Total number of rows in the display
#define ledPIN   6                //out pint to control Leds
#define NUM_LEDS (xres * yres)  //total   leds in Matrix
#include <Adafruit_NeoPixel.h>
#include <Adafruit_NeoMatrix.h>

#define colorPIN 5        //pin   to change ledcolor
#define brightnessPIN 10  //pin to change brightness

byte   displaycolor = 0;    //default color value
byte brightness = 1;      //default   brightness level

#include <EEPROM.h>
#define CONFIG_START 32         //Memory   start location
#define CONFIG_VERSION "VER01"  //Config version configuration

typedef   struct {
  char version[6];
  byte displaycolor;
  byte brightness;
}   configuration_type;

configuration_type CONFIGURATION = {
  CONFIG_VERSION,
   displaycolor,
  brightness
};

byte yvalue;
int peaks[xres];
byte   state = HIGH;                    // the current reading from the input pin
byte   previousState = LOW;             // the previous reading from the input pin
unsigned   long lastDebounceTime = 0;   // the last time the output pin was toggled
unsigned   long debounceDelay = 100;    // the debounce time; increase if the output flickers

byte   data_avgs[xres]; //Array for samplig

// Parameter 1 = number of leds in matrix
//   Parameter 2 = pin number
// Parameter 3 = pixel type flags, add together as needed:
//    NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400   400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels   are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels   are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel pixel   = Adafruit_NeoPixel(NUM_LEDS, ledPIN, NEO_GRB + NEO_KHZ800);

// EQ filter
byte   eq[32] = {
  60, 65, 70, 75, 80, 85, 90, 95,
  100, 100, 100, 100, 100, 100,   100, 100,
  100, 100, 100, 100, 100, 100, 100, 100,
  115, 125, 140, 160,   185, 200, 200, 200
};

bool EQ_ON = true; // set to false to disable eq

//Define   5 set of colors for leds, 0 for single custom color
byte colors[][8] = {
   {170, 160, 150, 140, 130, 120, 1, 1},
  {1, 5, 10, 15, 20, 25, 90, 90},
   {90, 85, 80, 75, 70, 65, 1, 1},
  {90, 90, 90, 30, 30, 30, 1, 1},
  {170,   160, 150, 140, 130, 120, 110, 0}
};

//Define chars for display settings
byte   charBitmap[] = {
  0x1C, 0x10, 0x10, 0x10, 0x10, 0x1C, 0x08, 0x18, 0x08, 0x08,   0x08, 0x1C,
  0x0C, 0x12, 0x04, 0x08, 0x10, 0x1E, 0x0C, 0x12, 0x02, 0x06, 0x12,   0x0C,
  0x10, 0x10, 0x10, 0x14, 0x1E, 0x04, 0x1E, 0x10, 0x1E, 0x02, 0x12, 0x0C,
   0x1E, 0x10, 0x10, 0x1E, 0x12, 0x1E, 0x1E, 0x02, 0x04, 0x08, 0x08, 0x08,
  0x0C,   0x12, 0x0C, 0x12, 0x12, 0x0C, 0x1C, 0x12, 0x1C, 0x12, 0x12, 0x1C
};

void   setup() {

  pixel.begin();           //initialize Led Matrix

  //Begin   FFT operations
  ADCSRA = 0b11100101;    // set ADC to free running mode and   set pre-scaler to 32 (0xe5)
  ADMUX =  0b00000000;    // use pin A0 and external   voltage reference

  // Read config data from EEPROM
  if (loadConfig())   {
    displaycolor = CONFIGURATION.displaycolor;
    brightness = CONFIGURATION.brightness;
   }

  //Set brightness loaded from EEPROM
  pixel.setBrightness(brightness   * 24 + 8);

  //Show current config on start
  //change true to false if   you don't want this
  showSettings(3, true);
}

void loop() {
  while   (1) {            // reduces jitter
    Sampling();          // FHT Library use   only one data array
    RearrangeFHT();      // re-arrange FHT result to match   with no. of display columns
    SendToDisplay();     // send to display according   measured value
    colorChange();       // check if button pressed to change   color
    brightnessChange();  // check if button pressed to change brightness
     delay(10);           // delay to reduce flickering (FHT is too fast :D)
   }
}

void Sampling() {
  for (int i = 0; i < FHT_N; i++) {
    while   (!(ADCSRA & 0x10));   // wait for ADC to complete current conversion ie ADIF bit   set
    ADCSRA = 0b11110101 ;       // clear ADIF bit so that ADC can do next   operation (0xf5)
    //ADLAR bit is 0, so the 10 bits of ADC Data registers are   right aligned
    byte m = ADCL;              // fetch adc data
    byte j   = ADCH;
    int value = (j << 8) | m;   // form into an int
    value -= 0x0200;             // form into a signed int
    value <<= 6;                // form   into a 16b signed int
    fht_input[i] = value / 8;   // copy to fht input array   after compressing
  }
  // ++ begin FHT data process -+-+--+-+--+-+--+-+--+-+--+-+--+-+-
   fht_window();    // window the data for better frequency response
  fht_reorder();    // reorder the data before doing the fht
  fht_run();       // process the   data in the fht
  fht_mag_lin();   // take the output of the fht
}

void   RearrangeFHT() {
  // FHT return real value unsing only one array
  // after   fht_mag_lin() calling the samples value are in
  // the first FHT_N/2 position   of the array fht_lin_out[]
  int step = (FHT_N / 2) / xres;
  int c = 0;
   for (int i = 0; i < (FHT_N / 2); i += step) {
    data_avgs[c] = 0;
    for   (int k = 0 ; k < step ; k++) {
      data_avgs[c] = data_avgs[c] + fht_lin_out[i   + k];  // linear output magnitude
    }
    data_avgs[c] = data_avgs[c] /   step ; // save avgs value
    c++;
  }
}

void SendToDisplay() {
   for (int i = 0; i < xres; i++) {
    if (EQ_ON)
      data_avgs[i] = data_avgs[i]   * (float)(eq[i]) / 100; // apply eq filter
    data_avgs[i] = constrain(data_avgs[i],   0, 80);        // set max & min values for buckets to 0-80
    data_avgs[i] =   map(data_avgs[i], 0, 80, 0, yres);     // remap averaged values to yres 0-8
     yvalue = data_avgs[i];
    peaks[i] = peaks[i] - 1;                              //   decay by one light
    if (yvalue > peaks[i]) peaks[i] = yvalue;             //   save peak if > previuos peak
    yvalue = peaks[i];                                    //   pick peak to display
    setColumn(i, yvalue);                                 //   draw columns
  }
  pixel.show();                                           //   show column
}

// Light up leds of x column according to y value
void   setColumn(byte x, byte y) {
  int led, i;

  for (i = 0; i < yres; i++)   {
    led = GetLedFromMatrix(x, i); //retrieve current led by x,y coordinates
     if (peaks[x] > i) {

      switch (displaycolor) {
        case 4:
           if (colors[displaycolor][i] == 0) {
            // show custom color   with zero value in array
            pixel.setPixelColor(led, 255, 255, 255);   //withe
          }
          else {
            // standard color defined   in colors array
            pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
           }
          break;

        case 5:
          //change color   by column
          pixel.setPixelColor(led, Wheel(x * 16));
          break;

         case 6:
          //change color by row
          pixel.setPixelColor(led,   Wheel(i * y * 3));
          break;

        case 7:
          //change   color by... country :D

          //Italy flagh
          //if (x < 11)   pixel.setPixelColor(led, 0, 255, 0);
          //if (x > 10 && x < 21) pixel.setPixelColor(led,   255, 255, 255);
          //if (x > 20) pixel.setPixelColor(led, 255, 0, 0);

           //stars and stripes
          if (i < yres - 2) {
            if   (x & 0x01) {
              pixel.setPixelColor(led, 0, 0, 255);
            }
             else {
              pixel.setPixelColor(led, 255, 0, 0);
            }
           }
          else {
            pixel.setPixelColor(led, 255, 255,   255);
          }

          break;

        default:
          //display   colors defined in color array
          pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
       }   //END SWITCH
    }
    else {
      //Light off leds
      pixel.setPixelColor(led,   pixel.Color(0, 0, 0));
    }
  }
}

//================================================================
//   Calculate a led number by x,y coordinates
// valid for WS2812B with serpentine   layout placed in horizzontal
// and zero led at bottom right (DIN connector on   the right side)
// input value: x= 0 to xres-1 , y= 0 to yres-1
// return   a led number from 0 to NUM_LED
//================================================================
int   GetLedFromMatrix(byte x, byte y) {
  int led;
  x = xres - x - 1;
  if   (x & 0x01) {
    //Odd columns increase backwards
    led = ((x + 1) * yres   - y - 1);
  }
  else {
    //Even columns increase normally
    led   = ((x + 1) * yres - yres + y);
  }
  return constrain(led, 0, NUM_LEDS);
}
//================================================================

void   colorChange() {
  int reading = digitalRead(colorPIN);
  if (reading == HIGH   && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
    displaycolor++;
     if (displaycolor > 7) displaycolor = 0;
    showSettings(1, true); //set   to false if you don't want this
    saveConfig();
    lastDebounceTime = millis();
   }
  previousState = reading;
}

void brightnessChange() {
  int   reading = digitalRead(brightnessPIN);
  if (reading == HIGH && previousState   == LOW && millis() - lastDebounceTime > debounceDelay) {
    brightness++;
     if (brightness > 7) brightness = 0;
    pixel.setBrightness(brightness *   24 + 8);
    showSettings(2, true); //set to false if you don't want this
     saveConfig();
    lastDebounceTime = millis();
  }
  previousState   = reading;
}

// Utility from Adafruit Neopixel demo sketch
// Input   a value 0 to 255 to get a color value.
// The colours are a transition R - G   - B - back to R.
unsigned long Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
   if (WheelPos < 85) {
    return pixel.Color(255 - WheelPos * 3, 0, WheelPos   * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return pixel.Color(0,   WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return pixel.Color(WheelPos   * 3, 255 - WheelPos * 3, 0);
}

// load whats in EEPROM in to the local   CONFIGURATION if it is a valid setting
int loadConfig() {
  if (EEPROM.read(CONFIG_START   + 0) == CONFIG_VERSION[0] &&
      EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1]   &&
      EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2] &&
      EEPROM.read(CONFIG_START   + 3) == CONFIG_VERSION[3] &&
      EEPROM.read(CONFIG_START + 4) == CONFIG_VERSION[4])   {

    // load (overwrite) the local configuration struct
    for (unsigned   int i = 0; i < sizeof(CONFIGURATION); i++) {
      *((char*)&CONFIGURATION +   i) = EEPROM.read(CONFIG_START + i);
    }
    return 1; // return 1 if config   loaded
  }
  return 0; // return 0 if config NOT loaded
}

// save   the CONFIGURATION in to EEPROM
void saveConfig() {
  CONFIGURATION.displaycolor   = displaycolor;
  CONFIGURATION.brightness = brightness;
  for (unsigned int   i = 0; i < sizeof(CONFIGURATION); i++)
    EEPROM.write(CONFIG_START + i, *((char*)&CONFIGURATION   + i));
}

// 1 display color level, 2 display brightness level, 3 both
void   showSettings(byte num, bool show) {
  if (show) {
    pixel.clear();
    if   (num == 1 || num == 3) {
      drawChar(0, 0);
      drawChar(displaycolor   + 1, 5);
    }
    if (num == 2 || num == 3) {
      drawChar(9, xres -   9);
      drawChar(brightness + 1, xres - 4);
    }
    delay(1000);
     pixel.clear();
  }
}

// Draw custom chars
void drawChar(byte   val, byte pos) {
  for (int x = 4; x >= 0; x--) {
    for (int y = 5; y >=   0; y--) {
      if ((charBitmap[val * 6 + 5 - y] >> x) & 0x01) {
        pixel.setPixelColor(GetLedFromMatrix(4   - x + pos, y + 1), Wheel((pos > 10) * 170));
        pixel.show();
      }
     }
  }
}
//by Janux®, Last version on 28/06/2020.

【Arduino 动手做】Arduino FFT 音频频谱分析仪,基于 8x32 颜色矩阵 WS2
项目链接:https://www.hackster.io/mircemk/arduino-fft-audio-spectrum-analyzer-on-8x32-color-matrix-ws2-e03947
项目作者:米尔科·帕夫莱斯基

项目视频:https://www.youtube.com/watch?v=KmDTCp0HBVQ
项目代码:
https://www.hackster.io/code_files/652986/download
https://www.hackster.io/code_files/652987/download

在这里插入图片描述
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

驴友花雕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值