Electronics > Microcontrollers

ESP32 and waterfall graph

(1/1)

gcrcien:
Hello everyone,
Im using a esp32 wroom to make a simple vfo/panadapter for radios that i make.
My goal is to create an opensource pcb for this purpose, however im running into a speed problem.
I'm runing a simple code for a waterfall graph which is done by reading an adc channel and computing the fft of the read values, then the esp32 displays the values on a tft display (spi ili9486 320x480) running at 80mhz but the update speed seems to be really slow like 7-10hz and that's just for the waterfall graph, i did something similar but without the waterfall for a previous project involving a stm32 and that ran a bit faster, https://github.com/gcrcien/STM32_VFO, im coding all this in the arduino IDE.
Has any of you guys done a similar project that can give hints on how to accelerate this project? I'm new to the esp32 but i have read that dma is a way to accelerate the spi lcd refresh rate, but im lost with this.


--- Code: ---#include "arduinoFFT.h"
#include "SPI.h"
#include "TFT_eSPI.h"

TFT_eSPI tft = TFT_eSPI();


#define BLACK   0x0000
#define GREEN   0x07E0

arduinoFFT FFT;

const uint16_t samples = 128;
const double samplingFrequency = 10000;
double vReal[samples];
double vImag[samples];

#define NUM_WATERFALL_ROWS 90
float waterfallData[NUM_WATERFALL_ROWS][128] = {0};

void performFFTAndDrawGraph(int xOffset, int yOffset);

void setup() {
  //SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0));
  tft.init();
  tft.invertDisplay(0);
  tft.fillScreen(BLACK);
  tft.setRotation(1);
}

void loop() {
  performFFTAndDrawGraph(0, 0);
}

void performFFTAndDrawGraph(int xOffset, int yOffset) {
  // Capturar datos de entrada
  for (int i = 0; i < samples; i++) {

    vReal[i] = analogRead(34);
    vImag[i] = 0;
  }

  // Realizar FFT
   FFT = arduinoFFT(vReal, vImag, samples, samplingFrequency);
   FFT.Windowing(FFT_WIN_TYP_RECTANGLE, FFT_FORWARD);
   FFT.Compute(FFT_FORWARD);
   FFT.ComplexToMagnitude();

  // Solo mapear los datos de entrada antes de almacenarlos en el array waterfall
  for (int col = 0; col < 128; ++col) {
    int mappedValue = map(vReal[col], 0, 5000, 0, 128);  // Solo mapear a un rango de 0 a 255
    waterfallData[NUM_WATERFALL_ROWS - 1][col] = mappedValue;
   
  }

  // Actualizar datos de waterfall
  for (int row = 0; row < NUM_WATERFALL_ROWS - 1; ++row) {
    for (int col = 0; col < 128; ++col) {
       waterfallData[row][col] = waterfallData[row + 1][col];
    }
  }

  // Dibujar el waterfall en la pantalla
  for (int row = 0; row < NUM_WATERFALL_ROWS; ++row) {
    for (int col = 0; col < 128; ++col) {
      int intensity = waterfallData[row][col];
      int color = intensity;  // El color ahora está en el rango de 0 a 255

       tft.drawPixel(col + xOffset, TFT_HEIGHT - 1 - row + yOffset + 80, color);
    }
  }
}
--- End code ---

globoy:
As you suspect, you are doing the graphics about as slowly as possible, even using Bodmer's library.  Some things to think about:

1. Easiest but not most optimized: Instead of drawing each pixel, have a small frame buffer and render to that.  It need only be a few lines long.  Then draw the whole frame buffer to the display using the write pixels function.  That way, at least, you're efficiently moving pixel data to the display even if your overall system is still single threaded.

2. Slightly more complex: Use two small frame buffers organized in a ping-pong fashion.  Draw to one while you're rendering the other one.  You can enable DMA in TFT_eSPI and then trigger a write pixels of one frame buffer and continue your code to render in the other one, only waiting for a transfer to finish when you're ready to initiate the next one.  This way you are processing the FFT at the same time as you are rendering.

3. Optimized: Use the dual cores.  Do your FFT on one core and rendering on the other with a pair of buffers (could either be FFT data or render data depending which ends up making the overall system the fastest) and mutexes to protect access.  You can use the underlying FreeRTOS primitives in the Arduino ESP32 environment.

It should definitely be possible to get much higher than 7-10 Hz refresh rate.  I've done various video processing things on ESP32 and gotten 30 FPS out of the system.

I realize you may have been looking for someone to suggest actual code but it's good for you to dive into the TFT_eSPI internals and documentation as well as head off and look at the ESP32 IDF API.  It'll greatly increase your project's capabilities.  Start by looking at Bodmer's header file and the API for DMA managed writes (these really only make sense when you want to use double-buffered rendering buffers, otherwise his direct rendering code is blazing fast on its own).  Then in the IDF look at the FreeRTOS stuff about starting tasks on specific CPUs (For example you could start the task for the PRO CPU, #0, in Setup() and then let your other task execute in the Loop() since it will be running on the APP CPU, #1).  And also look at creating mutexes to protect data structures when accessing them from multiple asynchronous processes or tasks.  Mutexes may seem scary at first but actually they're really simple and powerful.

hans:
Good suggestions.
Not all ESP32s are dual core though. But IIRC all of them can run at 240MHz, so that would theoretically triple the update speed assuming the code is not I/O limited (such as the SPI bus to the display).

To reduce overhead from the SPI bus, indeed use whole line or frame (double) buffering. Draw into 1 buffer while sending the other via DMA.
However, you could start out by sending on a line basis. Sending pixel by pixel may result in a random write for each pixel written, which needs several bytes of X/Y data to write just 1-3 bytes of pixel data. If the LCD driver has to send register commands, it could be even slower.
You're already drawing the waterfall with a column inner loop (X-axis), so that should be easy to write 1 line at a time. Instead of using tft.drawPixel(), write the intended color to a local array, and then after the col inner loop, send the whole line to the display (you'll have to look up which function to use).

ralphrmartin:
Rather than needlessly copying data from row to row of the waterfall array at  each time step, use an integer to say which row of the data corresponds to the top of the waterfall, and update it cyclically at each time step so you only overwrite the oldest row of the waterfall with the new data. The same integer can be added (cylically) to the row numbers when drawing to select the right row of data.

rhb:
FWIW Developing a proper waterfall/spectrum analyzer has been on my list for some time.  Setting the center frequency and span are rather different from my historical use of the FFT.

This is so I can come back to this thread.

Have Fun!
Reg

Navigation

[0] Message Index

There was an error while thanking
Thanking...
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod