Firebeetle FFT VGA 显示器
之前基于 FireBeetle ESP32 和全向MEMS麦克风模块(SEN0487)制作过一个在OLED屏幕上显示当前环境声音频谱的装置【参考1】。这次制作的是能够输出 VGA 信号的频谱装置,这样,用户能够在显示器或者电视机上看到实时频谱输出。具体的VGA 显示原理,可以在之前的介绍中看到【参考2】,这次的设计硬件部分与之类似。电路图如下:其中主控和VGA部分如下:VGA本质上还是模拟信号,这里使用电阻能够输出不同电平的模拟信号,三根GPIO能够实现2^3=16种组合,因此也意味着能够实现16种颜色.
下面是用于连接全向MEMS麦克风模块的接口:
板子上带有一个 USB 公头用于取电,另外还有一个 USB母头,如果你的显示设备没有 VGA接口只有HDMI接口,那么需要一个VGA转HDMI线,而这种线通常使用USB公头取电,这种情况可以直接将它连接到这个 USB母头取电。
同样的,为了便于从充电宝取电,还设计了一个负载消耗电路。
PCB设计如下:
3D预览如下:
焊接后的实物如下:
接下来就可以进行软件的设计了,。基本原理是:首先通过ADC进行采样,然后将采样结果进行 FFT ,最终得到的是采样期间每个频率的能量。我们将这个数值显示在 VGA上就得到了期望的结果:
#include <arduinoFFT.h>
#include "fabgl.h"
// VGA 显示
fabgl::VGA16Controller DisplayController;
Canvas cv(&DisplayController);
//ZivDebug #define SAMPLES 1024 // Must be a power of 2
#define SAMPLES 256 // Must be a power of 2
#define SAMPLING_FREQ 40000 // Hz, must be 40000 or less due to ADC conversion time. Determines maximum frequency that can be analysed by the FFT Fmax=sampleF/2.
#define AMPLITUDE 1000 // Depending on your audio source level, you may need to alter this value. Can be used as a 'sensitivity' control.
#define AUDIO_IN_PIN A0 // Signal in on this pin
#define NOISE 500 // Used as a crude noise filter, values below this are ignored
const uint8_t kMatrixWidth = 16; // Matrix width
const uint8_t kMatrixHeight = 16; // Matrix height
#define NUM_BANDS 16 // To change this, you will need to change the bunch of if statements describing the mapping from bins to bands
#define BAR_WIDTH (kMatrixWidth/ (NUM_BANDS - 1))// If width >= 8 light 1 LED width per bar, >= 16 light 2 LEDs width bar etc
#define TOP (kMatrixHeight - 0) // Don't allow the bars to go offscreen
// Sampling and FFT stuff
unsigned int sampling_period_us;
byte peak[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // The length of these arrays must be >= NUM_BANDS
int oldBarHeights[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int bandValues[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
double vReal;
double vImag;
unsigned long newTime;
arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);
unsigned long int Elsp1;
int h;
int Height,Width;
void setup() {
Serial.begin(115200);
sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQ));
DisplayController.begin();
// 设定分辨率
DisplayController.setResolution(VGA_640x400_60Hz);
Height=cv.getHeight();
Width=cv.getWidth();
cv.setBrushColor(Color::Red );
// get a font for about 40x14 text screen
cv.selectFont(&fabgl::FONT_8x8);
cv.setGlyphOptions(GlyphOptions().FillBackground(true));
}
void loop() {
static int64_t stime= esp_timer_get_time();
static int FPS = 0;
static int FPSCounter = 0;
// Reset bandValues[]
for (int i = 0; i < NUM_BANDS; i++) {
bandValues = 0;
}
// Sample the audio pin
for (int i = 0; i < SAMPLES; i++) {
newTime = micros();
vReal = analogRead(AUDIO_IN_PIN); // A conversion takes about 9.7uS on an ESP32
vImag = 0;
while ((micros() - newTime) < sampling_period_us) {
/* chill */
}
}
// Compute FFT
FFT.DCRemoval();
FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.Compute(FFT_FORWARD);
FFT.ComplexToMagnitude();
// Analyse FFT results
for (int i = 2; i < (SAMPLES / 2); i++) { // Don't use sample 0 and only first SAMPLES/2 are usable. Each array element represents a frequency bin and its value the amplitude.
if (vReal > NOISE) { // Add a crude noise filter
//16 bands, 12kHz top band
if (i <= 2 ) bandValues+= (int)vReal;
if (i > 2 && i <= 3) bandValues+= (int)vReal;
if (i > 3 && i <= 5) bandValues+= (int)vReal;
if (i > 5 && i <= 7) bandValues+= (int)vReal;
if (i > 7 && i <= 9) bandValues+= (int)vReal;
if (i > 9 && i <= 13 ) bandValues+= (int)vReal;
if (i > 13&& i <= 18 ) bandValues+= (int)vReal;
if (i > 18&& i <= 25 ) bandValues+= (int)vReal;
if (i > 25&& i <= 36 ) bandValues+= (int)vReal;
if (i > 36&& i <= 50 ) bandValues+= (int)vReal;
if (i > 50&& i <= 69 ) bandValues += (int)vReal;
if (i > 69&& i <= 97 ) bandValues += (int)vReal;
if (i > 97&& i <= 135) bandValues += (int)vReal;
if (i > 135 && i <= 189) bandValues += (int)vReal;
if (i > 189 && i <= 264) bandValues += (int)vReal;
if (i > 264 ) bandValues += (int)vReal;
}
}
// Process the FFT data into bar heights
for (byte band = 0; band < NUM_BANDS; band++) {
// Scale the bars for the display
int barHeight = bandValues / AMPLITUDE;
if (barHeight > TOP) barHeight = TOP;
// Small amount of averaging between frames
barHeight = ((oldBarHeights * 1) + barHeight) / 2;
// Move peak up
if (barHeight > peak) {
peak = min(TOP, barHeight);
}
h = barHeight;
// Save oldBarHeights for averaging later
oldBarHeights = barHeight;
}
if (millis() - Elsp1 > 10) {
for (byte band = 0; band < NUM_BANDS; band++)
if (peak > 0) peak -= 1;
cv.setBrushColor(Color::Black );
cv.clear();
cv.setBrushColor(Color::Red );
for (int i = 0; i < 16; i++) {
if (h != 0) {
//cv.fillRectangle(cv.getWidth()*i / 16, 0, cv.getWidth() * (i + 1) / 16, cv.getHeight() *h / 16);
cv.fillRectangle(Width*i / 16, Height -1 , Width * (i + 1) / 16-1, (Height-1) *(16-h) / 16);
}
Serial.print(h, HEX);
Serial.print("");
}
Serial.println("");
Elsp1 = millis();
}
if (esp_timer_get_time() - stime > 1000000) {
// calculate FPS
FPS = FPSCounter;
stime = esp_timer_get_time();
FPSCounter = 0;
}
++FPSCounter;
// display test state and FPS
cv.setPenColor(Color::Blue);
cv.setBrushColor(Color::Yellow);
cv.drawTextFmt(80, 5, "%d FPS ",FPS);
}
这个项目焊接难度较低,演示效果非常好,有兴趣的朋友不妨尝试亲自实验,同时还可以修改代码来实现更多的效果。
参考:1. https://mc.dfrobot.com.cn/thread-314320-1-1.html2. https://mc.dfrobot.com.cn/thread-311156-1-1.html?fromuid=70205
https://www.bilibili.com/video/BV1F14y1f7Yh/?share_source=copy_web&vd_source=5ca375392c3dd819bfc37d4672cb6d54
工作的视频可以在B站看到 【ESP32 VGA 显示音频频谱】 https://www.bilibili.com/video/BV1F14y1f7Yh/?share_source=copy_web&vd_source=5ca375392c3dd819bfc37d4672cb6d54
本文提到的电路图和 PCB 下载
本文提到的代码下载:
页:
[1]