Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 10 of 10

Thread: A little FFT demo for 480x320 TFT (HX8357D)

  1. #1
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,231

    A little FFT demo for 480x320 TFT (HX8357D)

    Click image for larger version. 

Name:	teensy_fft_display.jpg 
Views:	109 
Size:	312.2 KB 
ID:	20917

    Just a little hacked together example of AudioAnalyzeFFT1024, using the Adafruit HX8357D 480x320 display.
    the grid is 23 x 16 pixels, and as the FFT bins are spaced approx 43Hz apart, each vertical div is 1kHz, and the
    vertical scale is set to 5dB/div.

    Source is an electret mic soldered to Audio shield rev D, hiding underneath by stacking header breakout under
    the Teensy 4.0 (@ 150MHz)

    The function at the end, display_fft(), does the work of pulling the FFT values and plotting them.

    Code:
    #include <Audio.h>
    #include <SPI.h>
    #include "Adafruit_GFX.h"
    #include "Adafruit_HX8357.h"
    
    
    
    // Audio setup using audio shield ver D (which uses SPI pins 10-13 and also 6-8, 15, 18-21, 23
    
    AudioAnalyzeFFT1024  fft ;
    AudioInputI2S        i2s1 ;
    AudioOutputI2S       i2s2 ;
    AudioControlSGTL5000 sgtl5000 ;
    
    AudioConnection patchcord1 (i2s1, 0, fft, 0) ;
    
    
    // Display pin assignment:
    
    // The audio shield uses pin 10 as SPI CS for uSD, so we use CS = 14 for display SPI
    // 16 for display reset
    
    #define TFT_DC  9
    #define TFT_CS  14
    #define TFT_RST 16
    
    // Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
    Adafruit_HX8357 tft = Adafruit_HX8357 (TFT_CS, TFT_DC, TFT_RST);
    
    
    void setup() 
    {
      tft.begin () ;
      tft.setRotation(3);  // orient as landscape, 1 or 3 depending on which way up
      draw_grid (true) ;
    
      start_audio () ;
    }
    
    void loop(void) 
    { 
      if (fft.available())
        display_fft() ;
    }
    
    void start_audio ()
    {
      AudioMemory (12) ;
      fft.windowFunction (AudioWindowHanning1024) ;
      
      sgtl5000.enable ();
      sgtl5000.inputSelect (AUDIO_INPUT_MIC);  // microphone needed on Audio shield
      sgtl5000.micGain (55); // microphone gain 0 .. 63
      sgtl5000.volume (0.5); // for headphone socket
    }
    
    
    // drawing graph
    
    #define PIXELS_PER_KHZ 23  // approximately,  1000 / (44100 / 1024)
    #define PIXELS_PER_DIV 16  // pixels per vertical division
    
    #define gw (20 * PIXELS_PER_KHZ)  // graph width
    #define gh 19 * PIXELS_PER_DIV    // graph height
    
    #define DB_PER_DIV 5
      
    uint16_t text_colour = tft.color565 (128, 255, 128) ;
    uint16_t grid_colour = tft.color565 (128, 48, 0) ;
    uint16_t grid2_colour = tft.color565 (192, 100, 0) ;
    
    // draw graticule for display of FFT
    void draw_grid (bool clear)
    {
      int w = tft.width();
      int h = tft.height();
      Serial.print (w) ; Serial.print (", ") ; Serial.println (h) ;
    
      if (clear)
        tft.fillScreen(HX8357_BLACK);
    
      tft.setTextColor (text_colour);  
      tft.setTextSize (1);
      int kHz = 0 ;
      for (int i = 0 ; i <= gw ; i += PIXELS_PER_KHZ)
      {
        if ((i/PIXELS_PER_KHZ % 5) == 0)
        {
          tft.drawLine (i, 0, i, gh+7, grid2_colour) ;
          tft.setCursor (i-2, gh+7) ;
          tft.printf ("%i", kHz) ;
        }
        else
          tft.drawLine (i, 0, i, gh, grid_colour) ;
        kHz += 1 ;
      }
    
      int dB = 0 ;
      for (int i = 0 ; i <= gh ; i += PIXELS_PER_DIV)
      {
        tft.drawLine (0, i, gw, i, grid_colour) ;
        tft.setCursor(gw-14, i-2);
        tft.printf("%4i", dB);
        dB -= DB_PER_DIV ;
      }
    }
    
    // redraw a 16 bit wide column of the graticule
    void draw_part_grid (int x)
    {
      int h = tft.height();
      for (int i = x ; i < x+16 ; i ++)
        if ((i % PIXELS_PER_KHZ) == 0)
          tft.drawLine (i, 0, i, gh, (i/PIXELS_PER_KHZ % 5) == 0 ? grid2_colour : grid_colour) ;
    
      for (int i = 0 ; i <= gh ; i += PIXELS_PER_DIV)
        tft.drawLine (x, i, x+15, i, grid_colour) ;
    }
    
    
    #define MIN_DB (-85)   // clip dB values to this for 0 samples.
    
    // convert FFT amplitude values to dB scale, 0dB = full scale
    int dB_mag (float ampl)
    {
      ampl *= 1.41421 ;  // convert to rms.
      int res = ampl == 0.0 ?
        round (MIN_DB / DB_PER_DIV * 16) : 
        round (16 * log10 (ampl) * 20 / DB_PER_DIV) ;  // 16 pixels / div vertical scale
      return constrain (res, -gh, 0) ;
    }
    
    
    void display_fft()
    {
      static int prev_trace[gw+1] ;  // record previous trace to erase it next time round
      
      int i = 1 ;
      tft.drawLine (i-1, prev_trace[i-1], i, prev_trace[i], HX8357_BLACK) ;
      int pp = 0 - dB_mag (fft.read (0)) ;
      prev_trace[0] = pp ;
      while (i <= gw-16)
      {
        // redraw in columns, 16 pixels wide, for flicker-free refresh
        // erase last plot
        for (int j = 1 ; j < 17 ; j ++)
          tft.drawLine (i+j-1, prev_trace[i+j-1], i+j, prev_trace[i+j], HX8357_BLACK) ;
    
        // redraw grid for this column
        draw_part_grid (i-1);
    
        // plot new fft data
        for (int j = 0 ; j < 16 ; j ++)
        {
          int p = 0 - dB_mag (fft.read (i+j)) ;
          prev_trace[i+j] = p ;
          tft.drawLine (i+j-1, pp, i+j, p, HX8357_GREEN) ;
          pp = p ;  
        }
        i += 16 ;
      }
    }

  2. #2
    Thanks for posting this.

    It's a nice little demo and just what I needed to show someone. Porting to my LCD was trivial and saved me heaps of time. :-)

  3. #3
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,231
    I guess I should port it to this recent graphics library: https://forum.pjrc.com/threads/61774...41_t3-projects

  4. #4
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,231
    I've enhanced the example to include a higher resolution FFT (uses 32 bit fixed point internally), included as .cpp and .h files (including
    a generic window function implementation). (attached as a zip)

    The example now toggles between Audio library FFT1024 (green) and higher res FFT (32 bit, 4096 bins) in yellow, every 4 seconds,
    to show the difference in noise floor you get.

    Click image for larger version. 

Name:	P1030268.JPG 
Views:	76 
Size:	268.0 KB 
ID:	21141

    And details of the difference (the little peak is a spur the audio shield rev D seems to generate for the microphone input at about 17.5kHz)
    Name:  P1030265.JPG
Views: 379
Size:  88.3 KBName:  P1030257.JPG
Views: 386
Size:  83.5 KB


    I should clarify the issue with the existing FFT is that is uses internal 16 bit fixed point to work with a 16 bit input
    signal - but rounding errors accumulate across the passes of the FFT, lowering the bit resolution significantly below
    16 bits, and introducing the blocky artifacts visible in the green trace. Using 32 bit internal values prevents this
    lose of accuracy and allows full details of the signal to be revealed.
    Attached Files Attached Files

  5. #5
    Senior Member
    Join Date
    Dec 2015
    Location
    LA
    Posts
    223
    Mark, nice work, thanks
    I added to your code a little and messaged to the 320 by 240 display Paul sells.
    I'm trying to understand why the gfft's/second seem to top out at 150MHz cpu while the fft/second keep going higher but still not close to linear.

    Click image for larger version. 

Name:	fft speed.png 
Views:	29 
Size:	41.5 KB 
ID:	24756Click image for larger version. 

Name:	gfft4096.png 
Views:	21 
Size:	3.2 KB 
ID:	24758

    Any thoughts?

  6. #6
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,231
    gfft?

    Is this just the FFT's or including screen updates?

  7. #7
    Senior Member
    Join Date
    Dec 2015
    Location
    LA
    Posts
    223
    The gfft are your AudioAnalyzeFFT objects with the size appended.
    Added globals and a few additions to the main loop:


    Code:
    int fft_count;
    bool is_gfft=true;
    
    void loop(void) { 
      if (millis() & 0x1000){  // switch between the two FFTs every 4 seconds.
        if(is_gfft==true){
          Serial.print("gfft's = ");Serial.println(fft_count);
          fft_count=0;
          is_gfft=false;
        }
        if (fft.available()){
          display_fft(false) ;
          fft_count++;
        }
      }else{
        if(is_gfft==false){
          Serial.print("fft's = ");Serial.println(fft_count);
          fft_count=0;
          is_gfft=true;
        }
        if (gfft.available()){
          display_fft (true) ;
          fft_count++;
        }
      }
    }

  8. #8
    Senior Member
    Join Date
    Dec 2015
    Location
    LA
    Posts
    223
    Just ran again with the display_FFT(xxx) lines commented out and got the same number 45 for the gfft(4096) and a even higher 353 for the audio library fft.

    Just realized I posted the wrong graph. The graph shows the results per 4 seconds not 1 second in all cases.

  9. #9
    Junior Member
    Join Date
    Apr 2021
    Posts
    5
    Does this apply to LCDs as well?

  10. #10
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,231
    Just had a thought about this - I think I use new/delete for the variable sized FFT to get its data vectors, so
    its not using tightly coupled memory. I suspect its limited by memory latency/bandwidth?

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •