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[SAMPLES];
double vImag[SAMPLES];
unsigned long newTime;

arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);

unsigned long int Elsp1;
int h[16];
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[i] = 0;
  }

  // Sample the audio pin
  for (int i = 0; i < SAMPLES; i++) {
    newTime = micros();
    vReal[i] = analogRead(AUDIO_IN_PIN); // A conversion takes about 9.7uS on an ESP32
    vImag[i] = 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[i] > NOISE) {                    // Add a crude noise filter

      //16 bands, 12kHz top band
      if (i <= 2 )           bandValues[0]  += (int)vReal[i];
      if (i > 2   && i <= 3  ) bandValues[1]  += (int)vReal[i];
      if (i > 3   && i <= 5  ) bandValues[2]  += (int)vReal[i];
      if (i > 5   && i <= 7  ) bandValues[3]  += (int)vReal[i];
      if (i > 7   && i <= 9  ) bandValues[4]  += (int)vReal[i];
      if (i > 9   && i <= 13 ) bandValues[5]  += (int)vReal[i];
      if (i > 13  && i <= 18 ) bandValues[6]  += (int)vReal[i];
      if (i > 18  && i <= 25 ) bandValues[7]  += (int)vReal[i];
      if (i > 25  && i <= 36 ) bandValues[8]  += (int)vReal[i];
      if (i > 36  && i <= 50 ) bandValues[9]  += (int)vReal[i];
      if (i > 50  && i <= 69 ) bandValues[10] += (int)vReal[i];
      if (i > 69  && i <= 97 ) bandValues[11] += (int)vReal[i];
      if (i > 97  && i <= 135) bandValues[12] += (int)vReal[i];
      if (i > 135 && i <= 189) bandValues[13] += (int)vReal[i];
      if (i > 189 && i <= 264) bandValues[14] += (int)vReal[i];
      if (i > 264          ) bandValues[15] += (int)vReal[i];
    }
  }

  // Process the FFT data into bar heights
  for (byte band = 0; band < NUM_BANDS; band++) {

    // Scale the bars for the display
    int barHeight = bandValues[band] / AMPLITUDE;
    if (barHeight > TOP) barHeight = TOP;

    // Small amount of averaging between frames
    barHeight = ((oldBarHeights[band] * 1) + barHeight) / 2;

    // Move peak up
    if (barHeight > peak[band]) {
      peak[band] = min(TOP, barHeight);
    }
    h[band] = barHeight;

    // Save oldBarHeights for averaging later
    oldBarHeights[band] = barHeight;

  }

  if (millis() - Elsp1 > 10) {
    for (byte band = 0; band < NUM_BANDS; band++)
      if (peak[band] > 0) peak[band] -= 1;


    cv.setBrushColor(Color::Black    );
    cv.clear();
    cv.setBrushColor(Color::Red    );
    for (int i = 0; i < 16; i++) {
      if (h[i] != 0) {
        //cv.fillRectangle(cv.getWidth()*i / 16, 0, cv.getWidth() * (i + 1) / 16, cv.getHeight() *h[i] / 16);
        cv.fillRectangle(Width*i / 16, Height -1 , Width * (i + 1) / 16-1, (Height-1) *(16-h[i]) / 16);
        
      }
      Serial.print(h[i], 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.html
  2. https://mc.dfrobot.com.cn/thread-311156-1-1.html?fromuid=70205

本文提到的电路图下载:

本文提到的完整代码:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注