Slow ST7735 on Teensy 3.2

Status
Not open for further replies.

bcnx

Active member
Hi all,

I am using a Teensy 3.2 with a Adafruit ST7735 1.8" TFT screen. I am currently using it as a audio peakmeter and outputting the audio input values to the TFT screen.
The result is however quite slow. I use the ST7735 over SPI. The code is on another machine (clipboard does not seem to work with VNC on OS X), but I do a simple tft.fillRect using the peak value as height and then a delay of 150 ms and then a tft.fillScreen to reset the screen.
I did some research and apparently one option is to use the ST7735 library optimized for Teensy 3.1 (I am guessing it will work for Teensy 3.2 as well).

Are there other possibilities to enhance speed? If the Teensy ST7735 library does not help, should I consider Teensy 3.5 and 3.6?

Cheers,


BC
 
OK, so I switched to the specialised Teensy ST7735 library, but it does not speed up things, the image is still jerking. Is this the symptom for having to use a more powerful Teensy?

Cheers!

BC
 
Maybe the problem is something else in your code?

That optimized lib was nearly as fast as ILI9341_t3, so it really should be very quick, if the library and display were the only factors.
 
Yes, you're right, the code is essential. The VNC clipboard is still not working but I moved to the machine connected to the Teensy. Don't mind the spaghetti code, but this is what I have. If you like I can also post a video of the result.

Code:
/* Mono Peak Meter

   Scrolling peak audio level meter in the Arduino Serial Monitor

   Audio input needs to connect to pin 16 (A2).  The signal range is 0 to 1.2V.
   See the documentation in the Audio System Design Tool for the recommended
   circuit to connect an analog signal.

   This example code is in the public domain
*/

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputAnalog         adc1;           //xy=164,95
AudioAnalyzePeak         peak1;          //xy=317,123
AudioConnection          patchCord1(adc1, peak1);
// GUItool: end automatically generated code

#define TFT_SCLK 13  // SCLK can also use pin 14
#define TFT_MOSI 11  // MOSI can also use pin 7
#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
#define SD_CS 4   // CS for SD card, can use any pin

#include <Adafruit_GFX.h>    // Core graphics library
#include <ST7735_t3.h> // Hardware-specific library
#include <SPI.h>

#if defined(__SAM3X8E__)
    #undef __FlashStringHelper::F(string_literal)
    #define F(string_literal) string_literal
#endif

// Option 1: use any pins but a little slower
ST7735_t3 tft = ST7735_t3(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

// Option 2: must use the hardware SPI pins
// (for UNO thats sclk = 13 and sid = 11) and pin 10 must be
// an output. This is much faster - also required if you want
// to use the microSD card (see the image drawing example)
//Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst);
float p = 3.1415926;

void setup() {
  pinMode(SD_CS, INPUT_PULLUP);  // don't touch the SD card
  AudioMemory(4);
  Serial.begin(9600);

  //tft.initR(INITR_GREENTAB); 
  tft.initR(INITR_BLACKTAB);

  uint16_t time = millis();
  tft.fillScreen(ST7735_BLACK);
  time = millis() - time;

  tft.fillScreen(ST7735_BLACK);
  tft.setRotation(-2);
}

// for best effect make your terminal/monitor a minimum of 31 chars wide and as high as you can.

elapsedMillis fps;

void loop() {
  //tft.invertDisplay(true);
  //delay(500);
  //tft.invertDisplay(false);
  tft.fillScreen(ST7735_BLACK);
  
  //delay(500);
  int Peak1 = peak1.read() * 200.0;
  Serial.print(Peak1);
  Serial.println();
  if (Peak1 < 130) {
    tft.fillRect(38,0,50,Peak1,0x07E0);
  }
  else {
    int PeakRed = Peak1 - 130;
    tft.fillRect(38,0,50,130,0x07E0);
    tft.fillRect(38,130,50,PeakRed,0xF800);
  }
  delay(41.66);
  //tft.fillRect(38,0,50,1,0x07E0);
}


Cheers guys,

BC
 
You're always going to get flicker if you do this in loop().

Code:
  tft.fillScreen(ST7735_BLACK);

Filling the entire display with black is going to take time!

As a quick test, I added a couple lines to print the elapsed microseconds.

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputAnalog         adc1;           //xy=164,95
AudioAnalyzePeak         peak1;          //xy=317,123
AudioConnection          patchCord1(adc1, peak1);
// GUItool: end automatically generated code

#define TFT_SCLK 13  // SCLK can also use pin 14
#define TFT_MOSI 11  // MOSI can also use pin 7
#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
#define SD_CS 4   // CS for SD card, can use any pin

#include <Adafruit_GFX.h>    // Core graphics library
#include <ST7735_t3.h> // Hardware-specific library
#include <SPI.h>

#if defined(__SAM3X8E__)
    #undef __FlashStringHelper::F(string_literal)
    #define F(string_literal) string_literal
#endif

// Option 1: use any pins but a little slower
ST7735_t3 tft = ST7735_t3(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

// Option 2: must use the hardware SPI pins
// (for UNO thats sclk = 13 and sid = 11) and pin 10 must be
// an output. This is much faster - also required if you want
// to use the microSD card (see the image drawing example)
//Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst);
float p = 3.1415926;

void setup() {
  pinMode(SD_CS, INPUT_PULLUP);  // don't touch the SD card
  AudioMemory(4);
  Serial.begin(9600);

  //tft.initR(INITR_GREENTAB); 
  tft.initR(INITR_BLACKTAB);

  uint16_t time = millis();
  tft.fillScreen(ST7735_BLACK);
  time = millis() - time;

  tft.fillScreen(ST7735_BLACK);
  tft.setRotation(-2);
}

// for best effect make your terminal/monitor a minimum of 31 chars wide and as high as you can.

elapsedMillis fps;

void loop() {
  //tft.invertDisplay(true);
  //delay(500);
  //tft.invertDisplay(false);
  elapsedMicros usec = 0;
  tft.fillScreen(ST7735_BLACK);
  //usec = 0;  // uncomment this to see only the time drawing the rectangles
  
  //delay(500);
  int Peak1 = peak1.read() * 200.0;
  Serial.print(Peak1);
  
  if (Peak1 < 130) {
    tft.fillRect(38,0,50,Peak1,0x07E0);
  }
  else {
    int PeakRed = Peak1 - 130;
    tft.fillRect(38,0,50,130,0x07E0);
    tft.fillRect(38,130,50,PeakRed,0xF800);
  }
  Serial.print(",  usec=");
  Serial.print(usec);
  Serial.println();
  delay(41.66);
  //tft.fillRect(38,0,50,1,0x07E0);
}

It's taking about 37 ms.

If you uncomment that line for only the rectangles, the time drops to about 7 ms.
 
To make this run well, you're going to need to do things in a smarter way than filling the entire screen with black. Even with the optimized library, the serial interface to the display just isn't fast enough!

Look at File > Examples > Audio > Tutorial > Part_3_03_TFT_Display for an example. Even this isn't the smartest possible way, as you can see by the comment in the code. But it does work pretty well. Each bar is drawn without the time taken for the rest of the screen, which really cuts down on the flicker.

Someday I hope we will get double buffered displays. But the reality of these cheap displays is only single buffering. You really do need to use a strategy where you quick draw just the portion of the screen you're updating, so there's minimal time for a wrong image (like all black) to appear as flickering.
 
Hi Paul,

thanks I will try that. I thought filling the fillscreen was necessary to reset the screen and see the new rectangle. I noticed that just drawing a new rectangle did not replace the old one. I will check the example code for the smarter way of doing things.
Do you think this could run adequately on a Teensy LC?

cheers,

BC
 
Last edited:
It will run much, much slower on Teensy LC.

The SPI port on Teensy LC lacks the advanced features you get on Teensy 3.x. The optimized libraries aren't supported.
 
OK Paul, cheers for that.
I used the LC in a project where I show MIDI in real time. This is text based, which might be a bit faster than the drawing functions. But at least now I know that if things get too slow, I better upgrade to 3.2 or higher,

Take care,

BC
 
Hi again,

so I tested as soon as I got home, but removing the fillscreen line, results into the first rectangle that was drawn on the TFT, just staying there. Newer rectangles drawn after the first one, are not shown, the first one seems to "hug" the TFT screen. I looked at the tutorial code, but I did not find a way to "reset" the screen. Next, I tried to draw a black rectangle over the green (and red) ones I'm using, but then the flickering occurs again, probably because too much real estate on the screen is changed at one time.
If someone has any clue on how to mitigate this, that would be awesome.
I will further test by limiting the black "resetting" rectangle to a minimal size and by playing around with delays, but any input is much appreciated,

cheers,

BC

EDIT: so I thought a bit about the problem and it is not true that only the first rectangle is displayed, subsequent rectangles are also shown, but on top of the existing ones. I think I will have to device some clever algorithm that draws or removes lines intelligently so the flickering is reduced to the minimum. I will keep you posted.
 
Last edited:
If you look at that example from the tutorial, it draws all the pixels of each bar. First it draws the green part on the bottom. Then it draws the rest black on the top. This isn't very efficient, but it does minimize the time where the display shows anything other than the old or the new.

A smarter approach would be to remember what was previously draw. Then if the value does up, just the new green could be drawn. If the value goes down, just the "new" black could be drawn.
 
Hi Paul,

exactly what I was thinking: put subsequent values in different variables and move relative to them: if we go down, we paint some line black with a lower Y value (the TFT screen's orientation is set to -2), if we go up, we add the necessary green lines. Adds quite some complexity, but I'm up for it ;-)

cheers,

BC
 
So, for posterity: my code runs fine now, without flickering. The trick was to indeed do some "smart" drawing, where I avoid to refill the complete screen, but just draw the differences in between changes. If the signal goes up, I draw extra lines (instead of rectangles) and when the signal goes down, I paint some black lines from top to bottom, to simulate a smaller bar. A 3.2 Teensy can drive the TFT perfectly this way!
 
Status
Not open for further replies.
Back
Top