FFT spectrum visualiser wont scale on display

Hi all, I' currently attempting to produce a TFT based spectrum visualiser, and have run into a small problem. I'm using an Adafruit 480x320 TFT (3.5" TFT 320x480 + Touchscreen Breakout Board w/MicroSD Socket (HXD8357D) | The Pi Hut) and am attempting to use some code based on the fix_fft library that I have slightly modified (Project#20 OLED Spectrum Analyzer using Fixed-point FFT; FHT on free-running mode - myscratchbooks).

So far I have got the actual spectrum working and displaying on the TFT display, however I'm having trouble making it actually fit onto the screen. Seeing as the original guide is using a much smaller OLED display, I understand that is why the visualiser is only appearing as small in the top left corner of my own TFT display. While attempting to fix this, I have managed to get as far as expanding the visualiser along the X axis, in order to fill the screen horizontally, by adding gaps in between each of the frequency bars.

However, when I try and adjust the "ylim" variable to expand the visualiser vertically, I run into some problems. Initially, when doubling the original ylim value from 60 to 120, the visualiser does expand vertically. But when I change the ylim variable to 180/240/300 etc the visualiser simply completely disappears. I cannot for the life of me figure out why this is happening.

I've attached the code I am using, any help would be very appreciated :slight_smile:

#include "fix_fft.h"                                  // library to perfom the fixed-point in-place Fast Fourier Transform
//#include <SPI.h>                                    // SPI library
#include <Adafruit_GFX.h>                             // graphics library for OLED
#include <Adafruit_TFTLCD.h> // Hardware-specific library

#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4
Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

#define WHITE   0xFFFF
#define  BLACK   0x0000

// https://arduino.stackexchange.com/questions/699/how-do-i-know-the-sampling-frequency
// http://www.dellascolto.com/bitwise/2017/05/25/audio-spectrum-analyzer/
// For a 16 MHz Arduino the ADC clock , default (prescaler=128) fs=9615 Hz=> fmax =fs/2
// if FFT_N=128, (9615/2)/(128/2) = 75 hz per bin
// if FFT_N=256, (9615/2)/(256/2) = 37.6 hz per bin
const int FFT_N =256;                                 // The Arduino Uno does not have enough memory to support 256 samples.
                                                      // many samples you will get a larger resolution for the results.
                                                      // set FFT_N to 256 by using an Arduino Mega 2560.
char im[FFT_N], data[FFT_N];                          // variables for the FFT
char x = 0, ylim = 60;                                // variables for drawing the graphics
int i = 0, val,fall;                                       //counters
int dropMax = 0;
int curMax;

int dat2[FFT_N];  

void setup()
{
  Serial.begin(9600);                               // serial comms for debuging
  uint16_t identifier = tft.readID();
  tft.begin(identifier);
  tft.setRotation(1);
  tft.setTextSize(1);                             // set OLED text size to 1 (1-6)
  tft.setTextColor(WHITE);                        // set text color to white
  tft.fillScreen(BLACK);                             // clear display
  analogReference(DEFAULT);                           // Use default (5v) aref voltage.
};

void loop()
{
  int mini=1024, maxi=0;                               // set minumum & maximum ADC values
  for (i = 0; i < FFT_N; i++) {                       // take 128 or 256  point fft samples
    val = analogRead(A15);                             // get audio from Analog 0
    data[i] = val / 4 - 128;                          // the reason it is divided by 4 and subtracted by 128 is because the "data" is of type "char" which is an 8 bit variable (-128 to 127). 
                                                      // So because analogRead returns 0-1023, dividing it by 4 gives you 0-255, subtracting it by 128 gives you -128-127.
                                                      // scale from 10 bit (0-1023) to 8 bit (0-255)
    im[i] = 0;                                        //
    if(val>maxi) maxi=val;                              //capture maximum level
    if(val<mini) mini=val; 
    //if(fall<mini) mini=fall;                              //capture minimum level
  };
    
  fix_fft(data, im, 8, 0);                            // perform the FFT on data for log2 (256) = 8 
  //fix_fft(data, im, 7, 0);                            // perform the FFT on data for log2 (128) = 7

// /*  display graphic, un-comment here
                                              // clear display
      // result array is a bin. 
      // interessted in the absolute value of the transformation
      // The number of bins you get is half the amount of samples spanning the frequency range from zero to half the sampling rate.
      // the first half of array elements contain the real values after fix_fft 
      // In the end we get N/2 bins, each covering a frequency range of sampling_rate/N Hz. 
  for (i = 1; i < FFT_N/2; i++) {                                     // full spectrum;  here 4.8kHz bandwith, has 128 bins each 37.5Hz
                                                                      // 1st bin = 37.5Hz, 2nd bin = 37.5*2 Hz  etc.
                                                                      // change for loop values for specific range of interested frequency
                                                                      // ex:  for (i = 1; i < 64; i++)    // intersted in first 63 bins
    int dat = sqrt(data[i] * data[i] + im[i] * im[i]);                // filter out noise and hum 
    dat = (2-dat/60)*dat;                                             // multiplication factor makes it look better with the microphone board (runs from 2 to 1)
    if (dat>=dat2[i]) tft.drawLine(i*3 + x, ylim - dat2[i], i*3 + x, ylim - dat, WHITE);
    else tft.drawLine(i*3 + x, ylim - dat2[i], i*3 + x, ylim - dat, BLACK);
    dat2[i] = dat;  
    tft.drawLine(i*3 + x, ylim, i*3 + x, ylim - dat, WHITE);          // if using FFT_N=256; draw bar graphics for freqs
  };              

  tft.setCursor(0,0);                                           // set cursor to top of screen
  tft.print("->Spectrum Analyzer<-");                           // print title to buffer
  
//  */  display graphic, un-comment here

};  // end of loop()

FFT_v2.ino (8.1 KB)

Hi, please read the forum guide in the sticky post. This will tell you how to get the best out of this forum, including how to post code correctly.

what's your arduino?

if you do int based math on a small arduino (16 bits int) you might run into overflow issues

I'm running this on the Mega2560

double check all the lines where you have some maths, possibly change the type to long

Tried changing a few different int values to long, the visualiser still disappears when ylim is set above 120.

I have not looked at the logic at all but would suggest my random guess would make me try

try to change at the start

long x = 0, ylim = 60;                                // variables for drawing the graphics
long i = 0, val, fall;                                      //counters
long dropMax = 0;
long curMax;
long dat2[FFT_N];

and change the display loop to

  for (i = 1; i < FFT_N / 2; i++) {                                   // full spectrum;  here 4.8kHz bandwith, has 128 bins each 37.5Hz
    long dat = sqrt((long) data[i] * (long)  data[i] + (long)  im[i] * (long)  im[i]);                // filter out noise and hum
    dat = (2l - dat / 60.0) * dat;                                       // multiplication factor makes it look better with the microphone board (runs from 2 to 1)
    if (dat >= dat2[i]) tft.drawLine(i * 3 + x, ylim - dat2[i], i * 3 + x, ylim - dat, WHITE);
    else tft.drawLine(i * 3 + x, ylim - dat2[i], i * 3 + x, ylim - dat, BLACK);
    dat2[i] = dat;
    tft.drawLine(i * 3 + x, ylim, i * 3 + x, ylim - dat, WHITE);      // if using FFT_N=256; draw bar graphics for freqs
  }

Check out how the author decided to modify the FFT vector to fit the screen. It is not only ylim; also these lines control the size of the lines that define the height of each line of the spectrum:

int dat = sqrt(data[i] * data[i] + im[i] * im[i]);                // filter out noise and hum 
    dat = (2-dat/60)*dat;                                             // multiplication factor makes it look better with the microphone board (runs from 2 to 1)
    if (dat>=dat2[i]) tft.drawLine(i*3 + x, ylim - dat2[i], i*3 + x, ylim - dat, WHITE);
    else tft.drawLine(i*3 + x, ylim - dat2[i], i*3 + x, ylim - dat, BLACK);
    dat2[i] = dat;  
    tft.drawLine(i*3 + x, ylim, i*3 + x, ylim - dat, WHITE);

I suggest rethinking the area of ​​the code that plots the data.

It is convenient to add a conversion that allows scaling the data of the FFT vector, so that the maximums are equal to a predefined limit.

Part of my research with the arduinoFFT library

// (Heavily) adapted from https://github.com/G6EJD/ESP32-8266-Audio-Spectrum-Display/blob/master/ESP32_Spectrum_Display_02.ino
// Adapted to ILI9341_t3 by TFTLCDCyg

//#include <SPI.h>
//#include "ILI9341_t3.h"
//#define TFT_CS   10  // CS & DC can use pins 2, 6, 9, 10, 15, 20, 21, 22, 23
//#define TFT_DC    9  //  but certain pairs must NOT be used: 2+10, 6+9, 20+23, 21+22
//#define TFT_RST   8  // RST can use any pin
//ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST);

#include <ST7735_t3.h>    // Hardware-specific library

#define TFT_RST 8
#define TFT_DC  9
#define TFT_CS 10  //ftf
// MOSI=11, MISO=12, SCK=13
int Rotacion=1;
ST7735_t3 tft = ST7735_t3(TFT_CS, TFT_DC, TFT_RST);

#include <arduinoFFT.h>

#define SAMPLES         1024  // 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       100//150 250   //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    A2//16    // Signal in on this pin

#define NUM_BANDS       44//24    // To change this, you will need to change the bunch of if statements describing the mapping from bins to bands
#define NOISE           70   // Used as a crude noise filter, values below this are ignored
//#define TOP           150    //  escala máxima de referencia  0.625*240 = 150  ILI9341
#define TOP             110//80    //  escala máxima de referencia  0.625*128 = 80  ST7735
int PitchB = 2, PitchX = 1;   //ancho de cada barra, separación entre barras

int YhB = TOP;
//ILI9341
//int xbase = (320-(NUM_BANDS*PitchB + (NUM_BANDS-1)*PitchX))/2;
//int ybase = (240+(YhB))/2;

//ST7735
int xbase = (160-(NUM_BANDS*PitchB + (NUM_BANDS-1)*PitchX))/2;
int ybase = (128+(YhB))/2;

int Datos[48];   //NUM_BANDS
long previousMillis0 = 0; 

// Sampling and FFT stuff
unsigned int sampling_period_us;

int bandValues[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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);


// Tabla de colores
uint16_t  color;
#define  BLACK          0x0000
#define RED             0xF800
#define GREEN           0x07E0
//#define BLUE            0x001F
#define BLUE            0x102E
#define CYAN            0x07FF
#define MAGENTA         0xF81F
#define YELLOW          0xFFE0 
#define ORANGE          0xFD20
#define GREENYELLOW     0xAFE5 
#define DARKGREEN       0x03E0
#define WHITE           0xFFFF



// Setup - initialize ILI9341 display, wait for serial monitor, open SD card
void setup() {
  Serial.begin(115200);
//  tft.begin();
//  tft.setRotation(3);     //ILI9488
//  tft.fillScreen(BLACK);
//  tft.setTextColor(YELLOW);
//  tft.setTextSize(1);
//  tft.println("Waiting for Arduino Serial Monitor...");

  tft.initR(INITR_18BLACKTAB); // if you're using a 1.8" TFT 128x160 displays
  //tft.initR(INITR_18BLACKTAB_OFFSET); // if you're using a 1.8" TFT 128x160 displays
  tft.setRotation(Rotacion); 
 // color = GREEN; 
  tft.fillScreen(ST77XX_BLUE);
  tft.setTextSize(1);
  tft.setCursor(30, 50); tft.setTextColor(ST77XX_WHITE);  tft.println("Iniciando TFT SPI");
  tft.setCursor(40, 60); tft.setTextColor(ST77XX_WHITE);  tft.println("  ST7735 1.8''");
  delay(1500);
  tft.setTextSize(1);
  tft.fillScreen(ST77XX_BLACK);

  while (!Serial && millis() < 500); // wait up to 3 seconds for Arduino Serial Monitor

  //Serial.println("ILI9341 Slideshow");
  Serial.println("ST7735 Slideshow");
  tft.fillScreen(BLACK);

  tft.setCursor(0, 2);
  sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQ));

   MP();
}
void loop(){}


void BarrasTFT()
{
  for (int j = 0; j < NUM_BANDS; j++) 
     {
      tft.fillRect(xbase + j*PitchX + j*PitchB, ybase-Datos[j], PitchB, Datos[j], GREEN);
      tft.fillRect(xbase + j*PitchX + j*PitchB, ybase-Datos[j], PitchB, Datos[j], GREEN);
      tft.fillRect(xbase + j*PitchX + j*PitchB, ybase-Datos[j], PitchB, Datos[j], GREEN);
      tft.fillRect(xbase + j*PitchX + j*PitchB, ybase-Datos[j], PitchB, Datos[j], GREEN);      
      tft.fillRect(xbase + j*PitchX + j*PitchB, ybase-TOP, PitchB, TOP-Datos[j], BLACK);
     }
}

char TXP[50];

void MP()
{
  int barHeight = bandValues[0] / AMPLITUDE;
  sprintf(TXP,"altura = %d", SAMPLES);
  BarrasTFT();
  while(1)
  {

  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.Windowing(FFT_WIN_TYP_FLT_TOP, 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
      

      if (i<=2 )           bandValues[0]  += (int)vReal[i];
      if (i>=2  && i<=3 )  bandValues[1]  += (int)vReal[i];
      if (i>=3  && i<=4 )  bandValues[2]  += (int)vReal[i];
      if (i>=4  && i<=5 )  bandValues[3]  += (int)vReal[i];
      if (i>=5  && i<=6 )  bandValues[4]  += (int)vReal[i];
      if (i>=6  && i<=6 )  bandValues[5] += (int)vReal[i]; 
      if (i>=6  && i<=7 )  bandValues[6] += (int)vReal[i]; 
      if (i>=7  && i<=8 )  bandValues[7] += (int)vReal[i]; 
      if (i>=8  && i<=9 )  bandValues[8] += (int)vReal[i]; 
      if (i>=9  && i<=11 ) bandValues[9] += (int)vReal[i]; 
      if (i>=11  && i<=13 ) bandValues[10] += (int)vReal[i];
      if (i>=13  && i<=14 ) bandValues[11] += (int)vReal[i];
      if (i>=14 && i<=16 ) bandValues[12] += (int)vReal[i]; 
      if (i>=16 && i<=18 ) bandValues[13] += (int)vReal[i]; 
      if (i>=18 && i<=20 ) bandValues[14] += (int)vReal[i]; 
      if (i>=20 && i<=23 ) bandValues[15] += (int)vReal[i]; 
      if (i>=23 && i<=26 ) bandValues[16] += (int)vReal[i]; 
      if (i>=26 && i<=29 ) bandValues[17] += (int)vReal[i]; 
      if (i>=29 && i<=32 ) bandValues[18] += (int)vReal[i]; 
      if (i>=32  && i<=36 ) bandValues[19] += (int)vReal[i];
      if (i>=36  && i<=41 ) bandValues[20] += (int)vReal[i];
      if (i>=41  && i<=46 ) bandValues[21] += (int)vReal[i];
      if (i>=46 && i<=52 ) bandValues[22] += (int)vReal[i]; 
      if (i>=52 && i<=58 ) bandValues[23] += (int)vReal[i]; 
      if (i>=58 && i<=66 ) bandValues[24] += (int)vReal[i]; 
      if (i>=66 && i<=74 ) bandValues[25] += (int)vReal[i]; 
      if (i>=74 && i<=83 ) bandValues[26] += (int)vReal[i]; 
      if (i>=83 && i<=93 ) bandValues[27] += (int)vReal[i]; 
      if (i>=93 && i<=105 ) bandValues[28] += (int)vReal[i]; 
      if (i>=105  && i<=118 ) bandValues[29] += (int)vReal[i]; 
      if (i>=118  && i<=133 ) bandValues[30] += (int)vReal[i]; 
      if (i>=133  && i<=149 ) bandValues[31] += (int)vReal[i]; 
      if (i>=149 && i<=168 ) bandValues[32] += (int)vReal[i];  
      if (i>=168 && i<=189 ) bandValues[33] += (int)vReal[i]; 
      if (i>=189 && i<=213 ) bandValues[34] += (int)vReal[i]; 
      if (i>=213 && i<=239 ) bandValues[35] += (int)vReal[i]; 
      if (i>=239 && i<=269 ) bandValues[36] += (int)vReal[i]; 
      if (i>=269 && i<=302 ) bandValues[37] += (int)vReal[i]; 
      if (i>=302 && i<=340 ) bandValues[38] += (int)vReal[i]; 
      if (i>=340  && i<=382 ) bandValues[39] += (int)vReal[i];
      if (i>=382  && i<=430 ) bandValues[40] += (int)vReal[i];
      if (i>=430  && i<=484 ) bandValues[41] += (int)vReal[i];
      if (i>=484 && i<=544 ) bandValues[42] += (int)vReal[i]; 
      if (i>=544 && i<=612 ) bandValues[43] += (int)vReal[i]; 
      if (i>=612          ) bandValues[44] += (int)vReal[i];  
       
    }
  }

  // Process the FFT data into bar heights
  for (byte band = 0; band < NUM_BANDS; band++) 
  {
    int barHeight = bandValues[band] / AMPLITUDE;
    if (barHeight > TOP){barHeight = TOP;}
    Datos[band] = barHeight;
  }

  unsigned long currentMillis0 = millis();
  if(currentMillis0 - previousMillis0 > 18)
  {
    previousMillis0 = currentMillis0;
    BarrasTFT();
  }  
  }
}

MCU: teensy 4@450MHz
TFT: ST7735 ZJY180-TV2.0 1.8" 160x128 Touch

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.