最近研究了下傅里叶变换,用它可以通过采集声音信号由时域转换到频域内,从而得到声音的频谱信息,可以做个频谱灯。
主要使用ESP32来实现了他,实现效果如下:
频谱灯
为了可以带出去露营,我把它做的很大,这样露营的时候效果会更好!
总体架子是木头设计的,高度60cm,如果用3d打印,这么大会挺费钱。
电路部分,主要使用Esp32板子,加上声音传感器模块Max4466,还有灯条ws2812B,用了128个灯,每列16个,共八列。
在Arduino里写的代码如下: 需要先加载两个库,一个是FFT,如下图第二个
还有个FastLed
频谱灯源码如下,4口输入声音电压信号,22口输出控制led信号
#include <TimeLib.h>
#include <FastLED.h>
#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<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);//初始化LED灯带
FastLED.setBrightness( BRIGHTNESS ); //LED亮度设置,取值范围为0-255
//Serial.begin(9600);
}
void loop()
{
/*采样*/
microseconds = micros();
for(int i=0; i<samples; 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(); //更新时间
}
}