最近研究了下傅里叶变换,用它可以通过采集声音信号由时域转换到频域内,从而得到声音的频谱信息,可以做个频谱灯。
主要使用ESP32来实现了他,实现效果如下:
频谱灯
为了可以带出去露营,我把它做的很大,这样露营的时候效果会更好!
总体架子是木头设计的,高度60cm,如果用3d打印,这么大会挺费钱。
电路部分,主要使用Esp32板子,加上声音传感器模块Max4466,还有灯条ws2812B,用了128个灯,每列16个,共八列。
在Arduino里写的代码如下: 需要先加载两个库,一个是FFT,如下图第二个
还有个FastLed
频谱灯源码如下,4口输入声音电压信号,22口输出控制led信号
-
- #include
- #include
- #include "arduinoFFT.h"
-
-
- /********************FFT相关定义**********************************/
- #define CHANNEL 4 //选择音频输入IO口序号为4
-
- arduinoFFT FFT = arduinoFFT(); //创建FFT对象
-
- const uint16_t samples = 64; //采样点数,必须为2的整数次幂
- const double samplingFrequency = 4000; //Hz, 声音采样频率
-
- unsigned int sampling_period_us;
- unsigned long microseconds;
-
- double vReal[samples]; //FFT采样输入样本数组
- double vImag[samples]; //FFT运算输出数组
-
- int16_t vvalue; //fft后的值
-
- int16_t volume[8]; //保存下降数据
-
- static uint32_t t=0, dt = 70;
- static uint8_t flag=0;
-
- //FFT参数,保持默认即可
- #define SCL_INDEX 0x00
- #define SCL_TIME 0x01
- #define SCL_FREQUENCY 0x02
- #define SCL_PLOT 0x03
- /********************FFT相关定义*********************************/
-
-
- /*******************灯板参数定义*********************************/
- #define LED_PIN 22 //灯板输入IO口选择
- #define NUM_LEDS 128 //灯珠数量
- #define BRIGHTNESS 10 //默认背光亮度
- #define LED_TYPE WS2812 //灯珠类型
- #define COLOR_ORDER GRB //色彩顺序
-
- CRGB leds[NUM_LEDS]; //定义LED对象
- /*******************灯板参数定义*********************************/
-
-
- void drawBar(int idx, int16_t value, uint8_t *flag) //绘制函数,按序号和幅度值绘制条形块
- {
- //static
- constrain(value,0,16); //幅度限制在0-8范围内
-
- if(volume[idx] < value) //采集到的数据比之前大则更新,实现上冲效果
- volume[idx] = value;
-
- //if(idx%2){ //余2运算判断序号是否为奇数
- // for(int i = 0;i<16-volume[idx];i++) leds[idx*16+i] = CRGB::Black;
- //}else{
- for(int i = volume[idx];i<16;i++) leds[idx*16+i] = CRGB::Black;
- // }
-
- //for(int i = volume[idx];i<16;i++) leds[i]=CRGB::Black;
-
-
-
- if(*flag){
- volume[idx] -= 1; //达到时间则减小1,表示下落
- if(idx == 7) *flag = 0; //第0-7列均更新完毕则清除标记
- }
- }
-
- void setup(){
- sampling_period_us = round(1000000*(1.0/samplingFrequency)); //计算采样频率
- pinMode(CHANNEL,INPUT); //初始化麦克风接口为输入模式,表示读取麦克风数据
-
- FastLED.addLeds
(leds, NUM_LEDS);//初始化LED灯带 - FastLED.setBrightness( BRIGHTNESS ); //LED亮度设置,取值范围为0-255
- //Serial.begin(9600);
- }
-
- void loop()
- {
- /*采样*/
- microseconds = micros();
- for(int i=0; i
- {
- vReal[i] = analogRead(CHANNEL); //读取模拟值,信号采样
- vImag[i] = 0;
- while(micros() - microseconds < sampling_period_us){
- //empty loop
- }
- microseconds += sampling_period_us;
- }
-
- /*FFT运算*/
- FFT.Windowing(vReal, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD); /* Weigh data */
- FFT.Compute(vReal, vImag, samples, FFT_FORWARD); /* Compute FFT */
- FFT.ComplexToMagnitude(vReal, vImag, samples); /* Compute magnitudes */
-
- fill_rainbow((leds), 128/*数量*/, 0/*开始色值*/, 2/*递增值*/); //设置彩虹渐变,先填充满,然后根据取值大小填充黑色,表示熄灭灯
- for(int i = 0; i < 8; i++){ //循环遍历八列LED
- vvalue=(vReal[i*3+2]+vReal[i*3+3]+vReal[i*3+4])/3/100;
- //if(i==6)
- //{
- //Serial.println(vvalue);
- //}
- vvalue=vvalue-2;
- if(vvalue>16) vvalue=16;
- drawBar(i, vvalue, &flag); //选取频谱中取平均后的8个值,传递时间标志到绘制函数
- }
- FastLED.show(); //显示灯条
-
- if((millis()-t) > dt){ //读取时间,判断是否达到掉落时长
- flag = 1; //达到则标记为1
- t = millis(); //更新时间
- }
- }