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

[项目] 【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪

[复制链接]
这是一个非常简单,但在视觉上仍然非常有效的项目,可以用作桌面上的一个小工具。

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

但是,在特定情况下,我们不必开发测量仪器,而是开发一个简单的小工具,我们不需要极高的精度,我们只需要以音乐节奏打开几个 LED。

顺便说一句,该项目已由昵称为 januks 的用户提交给 Arduino 项目中心,实际上是 Shaajeb 出色的 Spectrum analajzer 项目的返工。我根据我的想法和要求对硬件和代码进行了最少的修改。

该设备的构建非常简单,并且仅包含几个组件:
Arduino Nano MCU 板,
8x64 色 LED 矩阵,带 WS2812B 个可寻址 LED
两 (3) 个触摸按钮
三个电阻器和一个电容器

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

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

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

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

对于这种情况,我在代码中进行了一些修改,但也希望在 input 上设置一个简单的 Low-pass filter。让我们在输入端使用在线音调生成器来测试范围。范围高达 10Khz。

现在,在本例中,矩阵已完全填充,视觉上看起来要好得多。至于设备的外观,我试图制作一个由 PVC 板和玻璃制成的简单但仍然实用的版本,厚度为 4 毫米。

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

【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪图3

【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪图1

【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪图2

【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪图4

【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪图5

【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪图6

【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪图7

驴友花雕  中级技神
 楼主|

发表于 4 小时前

【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪

项目代码

  1. /*
  2.   Copyright (c) 2020 Janux
  3.   Permission is hereby granted,   free of charge, to any person obtaining a copy
  4.   of this software and associated   documentation files (the "Software"), to deal
  5.   in the Software without restriction,   including without limitation the rights
  6.   to use, copy, modify, merge, publish,   distribute, sublicense, and/or sell
  7.   copies of the Software, and to permit persons   to whom the Software is
  8.   furnished to do so, subject to the following conditions:
  9.    The above copyright notice and this permission notice shall be included in all
  10.    copies or substantial portions of the Software.
  11.   THE SOFTWARE IS PROVIDED   "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12.   IMPLIED, INCLUDING BUT   NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  13.   FITNESS FOR A PARTICULAR   PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  14.   AUTHORS OR COPYRIGHT HOLDERS   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  15.   LIABILITY, WHETHER IN AN ACTION OF   CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  16.   OUT OF OR IN CONNECTION WITH THE   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  17.   SOFTWARE.
  18.   Based on an   original project for the MAX72xx LED matrix and FFT lib made from Shajeeb.
  19.   Configuration   settings section based on work of Ragnar Ranøyen Homb from Norvegian Creation.
  20. */
  21. #define   LIN_OUT 1                //FHT linear output magnitude
  22. #define FHT_N 128                //set   SAMPLES for FHT, Must be a power of 2
  23. #include <FHT.h>
  24. #define xres 32                  //Total number of columns in the display, must be <= SAMPLES/2
  25. #define   yres 8                  //Total number of rows in the display
  26. #define ledPIN   6                //out pint to control Leds
  27. #define NUM_LEDS (xres * yres)  //total   leds in Matrix
  28. #include <Adafruit_NeoPixel.h>
  29. #include <Adafruit_NeoMatrix.h>
  30. #define colorPIN 5        //pin   to change ledcolor
  31. #define brightnessPIN 10  //pin to change brightness
  32. byte   displaycolor = 0;    //default color value
  33. byte brightness = 1;      //default   brightness level
  34. #include <EEPROM.h>
  35. #define CONFIG_START 32         //Memory   start location
  36. #define CONFIG_VERSION "VER01"  //Config version configuration
  37. typedef   struct {
  38.   char version[6];
  39.   byte displaycolor;
  40.   byte brightness;
  41. }   configuration_type;
  42. configuration_type CONFIGURATION = {
  43.   CONFIG_VERSION,
  44.    displaycolor,
  45.   brightness
  46. };
  47. byte yvalue;
  48. int peaks[xres];
  49. byte   state = HIGH;                    // the current reading from the input pin
  50. byte   previousState = LOW;             // the previous reading from the input pin
  51. unsigned   long lastDebounceTime = 0;   // the last time the output pin was toggled
  52. unsigned   long debounceDelay = 100;    // the debounce time; increase if the output flickers
  53. byte   data_avgs[xres]; //Array for samplig
  54. // Parameter 1 = number of leds in matrix
  55. //   Parameter 2 = pin number
  56. // Parameter 3 = pixel type flags, add together as needed:
  57. //    NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
  58. //   NEO_KHZ400   400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
  59. //   NEO_GRB     Pixels   are wired for GRB bitstream (most NeoPixel products)
  60. //   NEO_RGB     Pixels   are wired for RGB bitstream (v1 FLORA pixels, not v2)
  61. Adafruit_NeoPixel pixel   = Adafruit_NeoPixel(NUM_LEDS, ledPIN, NEO_GRB + NEO_KHZ800);
  62. // EQ filter
  63. byte   eq[32] = {
  64.   60, 65, 70, 75, 80, 85, 90, 95,
  65.   100, 100, 100, 100, 100, 100,   100, 100,
  66.   100, 100, 100, 100, 100, 100, 100, 100,
  67.   115, 125, 140, 160,   185, 200, 200, 200
  68. };
  69. bool EQ_ON = true; // set to false to disable eq
  70. //Define   5 set of colors for leds, 0 for single custom color
  71. byte colors[][8] = {
  72.    {170, 160, 150, 140, 130, 120, 1, 1},
  73.   {1, 5, 10, 15, 20, 25, 90, 90},
  74.    {90, 85, 80, 75, 70, 65, 1, 1},
  75.   {90, 90, 90, 30, 30, 30, 1, 1},
  76.   {170,   160, 150, 140, 130, 120, 110, 0}
  77. };
  78. //Define chars for display settings
  79. byte   charBitmap[] = {
  80.   0x1C, 0x10, 0x10, 0x10, 0x10, 0x1C, 0x08, 0x18, 0x08, 0x08,   0x08, 0x1C,
  81.   0x0C, 0x12, 0x04, 0x08, 0x10, 0x1E, 0x0C, 0x12, 0x02, 0x06, 0x12,   0x0C,
  82.   0x10, 0x10, 0x10, 0x14, 0x1E, 0x04, 0x1E, 0x10, 0x1E, 0x02, 0x12, 0x0C,
  83.    0x1E, 0x10, 0x10, 0x1E, 0x12, 0x1E, 0x1E, 0x02, 0x04, 0x08, 0x08, 0x08,
  84.   0x0C,   0x12, 0x0C, 0x12, 0x12, 0x0C, 0x1C, 0x12, 0x1C, 0x12, 0x12, 0x1C
  85. };
  86. void   setup() {
  87.   pixel.begin();           //initialize Led Matrix
  88.   //Begin   FFT operations
  89.   ADCSRA = 0b11100101;    // set ADC to free running mode and   set pre-scaler to 32 (0xe5)
  90.   ADMUX =  0b00000000;    // use pin A0 and external   voltage reference
  91.   // Read config data from EEPROM
  92.   if (loadConfig())   {
  93.     displaycolor = CONFIGURATION.displaycolor;
  94.     brightness = CONFIGURATION.brightness;
  95.    }
  96.   //Set brightness loaded from EEPROM
  97.   pixel.setBrightness(brightness   * 24 + 8);
  98.   //Show current config on start
  99.   //change true to false if   you don't want this
  100.   showSettings(3, true);
  101. }
  102. void loop() {
  103.   while   (1) {            // reduces jitter
  104.     Sampling();          // FHT Library use   only one data array
  105.     RearrangeFHT();      // re-arrange FHT result to match   with no. of display columns
  106.     SendToDisplay();     // send to display according   measured value
  107.     colorChange();       // check if button pressed to change   color
  108.     brightnessChange();  // check if button pressed to change brightness
  109.      delay(10);           // delay to reduce flickering (FHT is too fast :D)
  110.    }
  111. }
  112. void Sampling() {
  113.   for (int i = 0; i < FHT_N; i++) {
  114.     while   (!(ADCSRA & 0x10));   // wait for ADC to complete current conversion ie ADIF bit   set
  115.     ADCSRA = 0b11110101 ;       // clear ADIF bit so that ADC can do next   operation (0xf5)
  116.     //ADLAR bit is 0, so the 10 bits of ADC Data registers are   right aligned
  117.     byte m = ADCL;              // fetch adc data
  118.     byte j   = ADCH;
  119.     int value = (j << 8) | m;   // form into an int
  120.     value -= 0x0200;             // form into a signed int
  121.     value <<= 6;                // form   into a 16b signed int
  122.     fht_input[i] = value / 8;   // copy to fht input array   after compressing
  123.   }
  124.   // ++ begin FHT data process -+-+--+-+--+-+--+-+--+-+--+-+--+-+-
  125.    fht_window();    // window the data for better frequency response
  126.   fht_reorder();    // reorder the data before doing the fht
  127.   fht_run();       // process the   data in the fht
  128.   fht_mag_lin();   // take the output of the fht
  129. }
  130. void   RearrangeFHT() {
  131.   // FHT return real value unsing only one array
  132.   // after   fht_mag_lin() calling the samples value are in
  133.   // the first FHT_N/2 position   of the array fht_lin_out[]
  134.   int step = (FHT_N / 2) / xres;
  135.   int c = 0;
  136.    for (int i = 0; i < (FHT_N / 2); i += step) {
  137.     data_avgs[c] = 0;
  138.     for   (int k = 0 ; k < step ; k++) {
  139.       data_avgs[c] = data_avgs[c] + fht_lin_out[i   + k];  // linear output magnitude
  140.     }
  141.     data_avgs[c] = data_avgs[c] /   step ; // save avgs value
  142.     c++;
  143.   }
  144. }
  145. void SendToDisplay() {
  146.    for (int i = 0; i < xres; i++) {
  147.     if (EQ_ON)
  148.       data_avgs[i] = data_avgs[i]   * (float)(eq[i]) / 100; // apply eq filter
  149.     data_avgs[i] = constrain(data_avgs[i],   0, 80);        // set max & min values for buckets to 0-80
  150.     data_avgs[i] =   map(data_avgs[i], 0, 80, 0, yres);     // remap averaged values to yres 0-8
  151.      yvalue = data_avgs[i];
  152.     peaks[i] = peaks[i] - 1;                              //   decay by one light
  153.     if (yvalue > peaks[i]) peaks[i] = yvalue;             //   save peak if > previuos peak
  154.     yvalue = peaks[i];                                    //   pick peak to display
  155.     setColumn(i, yvalue);                                 //   draw columns
  156.   }
  157.   pixel.show();                                           //   show column
  158. }
  159. // Light up leds of x column according to y value
  160. void   setColumn(byte x, byte y) {
  161.   int led, i;
  162.   for (i = 0; i < yres; i++)   {
  163.     led = GetLedFromMatrix(x, i); //retrieve current led by x,y coordinates
  164.      if (peaks[x] > i) {
  165.       switch (displaycolor) {
  166.         case 4:
  167.            if (colors[displaycolor][i] == 0) {
  168.             // show custom color   with zero value in array
  169.             pixel.setPixelColor(led, 255, 255, 255);   //withe
  170.           }
  171.           else {
  172.             // standard color defined   in colors array
  173.             pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
  174.            }
  175.           break;
  176.         case 5:
  177.           //change color   by column
  178.           pixel.setPixelColor(led, Wheel(x * 16));
  179.           break;
  180.          case 6:
  181.           //change color by row
  182.           pixel.setPixelColor(led,   Wheel(i * y * 3));
  183.           break;
  184.         case 7:
  185.           //change   color by... country :D
  186.           //Italy flagh
  187.           //if (x < 11)   pixel.setPixelColor(led, 0, 255, 0);
  188.           //if (x > 10 && x < 21) pixel.setPixelColor(led,   255, 255, 255);
  189.           //if (x > 20) pixel.setPixelColor(led, 255, 0, 0);
  190.            //stars and stripes
  191.           if (i < yres - 2) {
  192.             if   (x & 0x01) {
  193.               pixel.setPixelColor(led, 0, 0, 255);
  194.             }
  195.              else {
  196.               pixel.setPixelColor(led, 255, 0, 0);
  197.             }
  198.            }
  199.           else {
  200.             pixel.setPixelColor(led, 255, 255,   255);
  201.           }
  202.           break;
  203.         default:
  204.           //display   colors defined in color array
  205.           pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
  206.        }   //END SWITCH
  207.     }
  208.     else {
  209.       //Light off leds
  210.       pixel.setPixelColor(led,   pixel.Color(0, 0, 0));
  211.     }
  212.   }
  213. }
  214. //================================================================
  215. //   Calculate a led number by x,y coordinates
  216. // valid for WS2812B with serpentine   layout placed in horizzontal
  217. // and zero led at bottom right (DIN connector on   the right side)
  218. // input value: x= 0 to xres-1 , y= 0 to yres-1
  219. // return   a led number from 0 to NUM_LED
  220. //================================================================
  221. int   GetLedFromMatrix(byte x, byte y) {
  222.   int led;
  223.   x = xres - x - 1;
  224.   if   (x & 0x01) {
  225.     //Odd columns increase backwards
  226.     led = ((x + 1) * yres   - y - 1);
  227.   }
  228.   else {
  229.     //Even columns increase normally
  230.     led   = ((x + 1) * yres - yres + y);
  231.   }
  232.   return constrain(led, 0, NUM_LEDS);
  233. }
  234. //================================================================
  235. void   colorChange() {
  236.   int reading = digitalRead(colorPIN);
  237.   if (reading == HIGH   && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
  238.     displaycolor++;
  239.      if (displaycolor > 7) displaycolor = 0;
  240.     showSettings(1, true); //set   to false if you don't want this
  241.     saveConfig();
  242.     lastDebounceTime = millis();
  243.    }
  244.   previousState = reading;
  245. }
  246. void brightnessChange() {
  247.   int   reading = digitalRead(brightnessPIN);
  248.   if (reading == HIGH && previousState   == LOW && millis() - lastDebounceTime > debounceDelay) {
  249.     brightness++;
  250.      if (brightness > 7) brightness = 0;
  251.     pixel.setBrightness(brightness *   24 + 8);
  252.     showSettings(2, true); //set to false if you don't want this
  253.      saveConfig();
  254.     lastDebounceTime = millis();
  255.   }
  256.   previousState   = reading;
  257. }
  258. // Utility from Adafruit Neopixel demo sketch
  259. // Input   a value 0 to 255 to get a color value.
  260. // The colours are a transition R - G   - B - back to R.
  261. unsigned long Wheel(byte WheelPos) {
  262.   WheelPos = 255 - WheelPos;
  263.    if (WheelPos < 85) {
  264.     return pixel.Color(255 - WheelPos * 3, 0, WheelPos   * 3);
  265.   }
  266.   if (WheelPos < 170) {
  267.     WheelPos -= 85;
  268.     return pixel.Color(0,   WheelPos * 3, 255 - WheelPos * 3);
  269.   }
  270.   WheelPos -= 170;
  271.   return pixel.Color(WheelPos   * 3, 255 - WheelPos * 3, 0);
  272. }
  273. // load whats in EEPROM in to the local   CONFIGURATION if it is a valid setting
  274. int loadConfig() {
  275.   if (EEPROM.read(CONFIG_START   + 0) == CONFIG_VERSION[0] &&
  276.       EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1]   &&
  277.       EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2] &&
  278.       EEPROM.read(CONFIG_START   + 3) == CONFIG_VERSION[3] &&
  279.       EEPROM.read(CONFIG_START + 4) == CONFIG_VERSION[4])   {
  280.     // load (overwrite) the local configuration struct
  281.     for (unsigned   int i = 0; i < sizeof(CONFIGURATION); i++) {
  282.       *((char*)&CONFIGURATION +   i) = EEPROM.read(CONFIG_START + i);
  283.     }
  284.     return 1; // return 1 if config   loaded
  285.   }
  286.   return 0; // return 0 if config NOT loaded
  287. }
  288. // save   the CONFIGURATION in to EEPROM
  289. void saveConfig() {
  290.   CONFIGURATION.displaycolor   = displaycolor;
  291.   CONFIGURATION.brightness = brightness;
  292.   for (unsigned int   i = 0; i < sizeof(CONFIGURATION); i++)
  293.     EEPROM.write(CONFIG_START + i, *((char*)&CONFIGURATION   + i));
  294. }
  295. // 1 display color level, 2 display brightness level, 3 both
  296. void   showSettings(byte num, bool show) {
  297.   if (show) {
  298.     pixel.clear();
  299.     if   (num == 1 || num == 3) {
  300.       drawChar(0, 0);
  301.       drawChar(displaycolor   + 1, 5);
  302.     }
  303.     if (num == 2 || num == 3) {
  304.       drawChar(9, xres -   9);
  305.       drawChar(brightness + 1, xres - 4);
  306.     }
  307.     delay(1000);
  308.      pixel.clear();
  309.   }
  310. }
  311. // Draw custom chars
  312. void drawChar(byte   val, byte pos) {
  313.   for (int x = 4; x >= 0; x--) {
  314.     for (int y = 5; y >=   0; y--) {
  315.       if ((charBitmap[val * 6 + 5 - y] >> x) & 0x01) {
  316.         pixel.setPixelColor(GetLedFromMatrix(4   - x + pos, y + 1), Wheel((pos > 10) * 170));
  317.         pixel.show();
  318.       }
  319.      }
  320.   }
  321. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 4 小时前

【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪

【Arduino 动手做】8x32 彩色矩阵 WS2812B 上的 Arduino FFT 音频频谱分析仪
项目链接:https://www.hackster.io/mircemk/ ... r-matrix-ws2-e03947
项目作者:北马其顿 米尔塞姆克(Mirko Pavleski)

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

【Arduino 动手做】8x32 矩阵上的Arduino FFT音频频谱分析仪图1

回复

使用道具 举报

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

本版积分规则

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

硬件清单

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

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

mail