Playing around with yet another version of ILI9341_t3 library (ILI9341_t3n)

In itself it would have probably 0% difference in possible speed.
As for Gauge drawing with flicker and the like. I added code to the ili9341_t3n to help speed things up. In particular the code with each graphic primitive in frame buffer mode, would keep a bounding rectangle of what parts of the image changed since the last updateScreen was called and when you call updateScreen it would only update that portion of the display, which in cases of a needle changing a little bit was only a small fraction of the full pixels of the display.

I'm curious as to how the automatic bounding box works. Does one need to do any more than just set useFrameBuffer(true)? Is it basically an auto "setClipRect()"? Or is it a more sophisticated routine that tightly bounds what's changed? Wouldn't a clipping rectangle get very large on a gauge when the needle is at 45°, negating the benefit?
 
I'm curious as to how the automatic bounding box works. Does one need to do any more than just set useFrameBuffer(true)? Is it basically an auto "setClipRect()"? Or is it a more sophisticated routine that tightly bounds what's changed? Wouldn't a clipping rectangle get very large on a gauge when the needle is at 45°, negating the benefit?

I wish it was automatic :D You need to define the X,Y pointers and the W,H of the clipping rectangle. One thing that confuses me is the position it starts at is the opposite of the other graphic primitives eg. drawing a char/string you pointer is at the top left corner, whereas ClipRect pointer starts at the bottom left corner.

Regarding the clipping rectangle at a 45° angle, the rectangle becomes quite big, but still its much faster than a full display refresh. If I'm not mistaking, Kurt has implemented a mechanism that updates only changed pixels within the clipping rectangle on the ILI9341_t3n library - this obviously contributes to performance improvement, no doubt.
 
Actually if you are using the frame buffer, and your updates are reasonably in the same area, in theory I have code in place that it remember the bounding rectangle of things that changed since you last did an updateScreen.

Again this does not work with the Async stuff, only the sync stuff (so far).

But I believe I have it such that you can enable this by a call like: tft.updateChangedAreasOnly();

It has been awhile since i played with it so...
 
I found this in the header file and set _updateChangedAreasOnly to true and saved the header file so the default for me is now "true". Maybe this can be called in the code, but I wasn't sure how. I just ran a test by removing all of my millions of setClipRect() commands . . . and everything is rendering great, so the automatic clipping rectangle seems to be working. Nice!! I think this should work with the dial needle.

Code:
#ifdef ENABLE_ILI9341_FRAMEBUFFER
    // Add support for optional frame buffer
    uint16_t	*_pfbtft;						// Optional Frame buffer 
    uint8_t		_use_fbtft;						// Are we in frame buffer mode?
    uint16_t	*_we_allocated_buffer;			// We allocated the buffer; 
	int16_t  	_changed_min_x, _changed_max_x, _changed_min_y, _changed_max_y;
	bool 		_updateChangedAreasOnly = false;	// current default off set to
	void		(*_frame_complete_callback)() = nullptr;
	bool 		_frame_callback_on_HalfDone = false;
 
Again as I mentioned there is a simple member function:
Code:
void updateChangedAreasOnly(bool updateChangedOnly) {
#ifdef ENABLE_ILI9341_FRAMEBUFFER
		_updateChangedAreasOnly = updateChangedOnly;
#endif
	}
That you can call that sets that variable true or false.

The interesting thing with needle is if that is all you are changing, then yes can do this automatic. But if you then are updating some text field somewhere else, you may be better off doing them as separate sets of updates to limit just how much of the screen redraws.
 
The interesting thing with needle is if that is all you are changing, then yes can do this automatic. But if you then are updating some text field somewhere else, you may be better off doing them as separate sets of updates to limit just how much of the screen redraws.

I've been thinking, to optimize performance even more, for any text fields that aren't changing, you can compare the current value to its old value and decide if it requires updating. On integers it would be simple enough. But how do you suggest one does this with floats? with a single decimal point, I'm using tft.print(float value,1) to print the values. Do you suggest converting to an integer and multiplying and then comparing?
 
I've been thinking, to optimize performance even more, for any text fields that aren't changing, you can compare the current value to its old value and decide if it requires updating. On integers it would be simple enough. But how do you suggest one does this with floats? with a single decimal point, I'm using tft.print(float value,1) to print the values. Do you suggest converting to an integer and multiplying and then comparing?

That sounds reasonable to try - math and multiply is faster (single op) than SPI rewrite of the number and background.

Wondering how much overhead is in the tft.print(float value,1)? Since doing the *10 anyhow:
Code:
// int32_t?  uint32_t?
static int32_t old_foo=0x7FFF;
int32_t foo = floatX*10;
if ( foo != old_foo ) {
  // prep cursor/field for overwrite
  tft.print( foo/10 );
  tft.print( '.' );
  tft.print( foo%10 );
  old_foo = foo;
}
 
That sounds reasonable to try - math and multiply is faster (single op) than SPI rewrite of the number and background.

Wondering how much overhead is in the tft.print(float value,1)? Since doing the *10 anyhow:
Code:
// int32_t?  uint32_t?
static int32_t old_foo=0x7FFF;
int32_t foo = floatX*10;
if ( foo != old_foo ) {
  // prep cursor/field for overwrite
  tft.print( foo/10 );
  tft.print( '.' );
  tft.print( foo%10 );
  old_foo = foo;
}

I think we need to convert everything back into a float if possible as you need to set the cursor for each print if I'm not mistaking - could be a hell of a headache to code in.
In my code I move the curser left<>right based on the size of value - that way I'm able to center the string to the decimal point and it doesn't jump back and forth.


EDIT:
this could work:
Code:
// int32_t?  uint32_t?
static int32_t old_foo=0x7FFF;
int32_t foo = floatX*10;
float floatX_n = 0; // new float for tft.print
if ( foo != old_foo ) {
  // prep cursor/field for overwrite
  floatX_n = foo * 0.1f;
  tft.print(floatX_n, 1);
  old_foo = foo;
}
 
Last edited:
Again if your goal is to output at 10ths of a unit and only update if it changes, you have multiple ways to do so.

In many cases I will use fixed point math...

But you can also do it quick and dirty with floating point...

Something as simple as:

Code:
float last_value = 999999.9999; // some value you won't have.

float new_value = ???

if ((abs(last_value - new_value) >= 0.1) {
    display the new value
    last_value = new_value;
}

Obviously there are probably faster ways...

NOTE: But side note: I just pushed up a minor change to the library, that when you call begin, it sets the pending_rx_count back to zero, in case someone calls
the begin again and for some reason, the count had not been cleared back to zero...
 
Again if your goal is to output at 10ths of a unit and only update if it changes, you have multiple ways to do so.

In many cases I will use fixed point math...

But you can also do it quick and dirty with floating point...

Something as simple as:

Code:
float last_value = 999999.9999; // some value you won't have.

float new_value = ???

if ((abs(last_value - new_value) >= 0.1) {
    display the new value
    last_value = new_value;
}

Obviously there are probably faster ways...

That's much cleaner. As mentioned before, I think the speed impact will still be a lot lower than rewriting and unchanged string to the display
 
I implemented the code above, calculating the absolute value and updating the relevant area of the display if its equal to or greater than 0.1, but it seems to have really slowed down the rate of change, as in, it wasn't updating nearly as fast as it does without the condition set

Here's an example trying to limit the printout of Boost on the display as it doesn't change much when the car is at idle but as soon as some throttle is applied it will jump up and down quite fast

Code:
float intakePressureFinal = 0; // Boost: range is -30 to +30
float intakePressureFinal_n = 0x7fff0;

if ((abs(intakePressureFinal_n - intakePressureFinal) >= 0.1)) {
      
        if (intakePressureFinal >= 0 && intakePressureFinal < 10){ //Boost is between 0-10
          tft.setCursor(221, 230);
        }
      
        else if (intakePressureFinal > -99 && intakePressureFinal < -10){ // Boost bigger than -10 and smaller than -99
          tft.setCursor(182, 230);
        }

        else if (intakePressureFinal >= 10 && intakePressureFinal < 99){ // Boost is bigger than 10 and smaller than 99
          tft.setCursor(193, 230); 
        }

        else { // Boost is between -10 and 0
          tft.setCursor(210, 230);
        }

        tft.print(intakePressureFinal, 1);
        tft.setClipRect(182, 220, 110, 35);
        tft.updateScreen();
        tft.setClipRect();
        intakePressureFinal_n = intakePressureFinal;
 }

Perhaps this method is too slow to execute or I ain't doing it right :confused:
 
Been up to some testing on an online C++ complier as I don't have access to a Teensy at the moment - will test the same code later on or tomorrow on a T4/4.1

Here's the script im using to test the function:
Code:
#include <iostream>
#include "math.h"

using namespace std;
float intakePressureFinal[5] = {-30.2, -15.2, 1.1, 14.9, 30.0}; // Boost: range is -30 to +30
float intakePressureFinal_n[5] = {-24.2, -16.2, 1.1, 14.8, 30.2};
float diff;


int main()
{
    
    for (int i = 0; i < 5 ; i++){
        cout<<"intakePressureFinal: " << intakePressureFinal[i];
        diff = intakePressureFinal_n[i] - intakePressureFinal[i];
        cout<< " || Diff: " << diff;
        if (fabs(diff) >= 0.1) {
            cout<<" || Output: " << intakePressureFinal[i] << "\n";
            intakePressureFinal_n[i] = intakePressureFinal[i];
        }
        else{
            cout<<" || No change \n";
            }
    }
    return 0;
}

Output:
Code:
intakePressureFinal: -30.2 || Diff: 6 || Output: -30.2                                                                                                                                          
intakePressureFinal: -15.2 || Diff: -1 || Output: -15.2                                                                                                                                         
intakePressureFinal: 1.1 || Diff: 0 || No change                                                                                                                                                
intakePressureFinal: 14.9 || Diff: -0.0999994 || No change                                                                                                                                      
intakePressureFinal: 30 || Diff: 0.200001 || Output: 30

If I change intakePressureFinal_n[3] (14.8) to 15.0 I get a different output
Code:
intakePressureFinal: -30.2 || Diff: 6 || Output: -30.2                                                                                                                                        
intakePressureFinal: -15.2 || Diff: -1 || Output: -15.2                                                                                                                                       
intakePressureFinal: 1.1 || Diff: 0 || No change                                                                                                                                              
intakePressureFinal: 14.9 || Diff: 0.1 || Output: 14.9                                                                                                                                        
intakePressureFinal: 30 || Diff: 0.200001 || Output: 30

But, if I change the value to compare abs to 0.09 from 0.1 it works much better:
Code:
if (fabs(intakePressureFinal_n[i]) >= 0.09)
as I'm not getting a rounded off 0.1 or higher if the old value is smaller than the new value. I believe this is the cause of the "slowness" in updating the numbers on the display - it's just not meeting the conditions when using a difference of 0.1 for a single decimal point of precision.
 
Has anyone using the Buydisplay 7" with capacitive touch screen. https://www.buydisplay.com/spi-7-inch-tft-lcd-dislay-module-1024x600-ra8876-optl-touch-screen-panel
I have connected one to a teensy 4.0 Works fine, however the touch screen in showing the touches with x 1024 and y 600 in the top left hand corner. Using the test sketch from https://github.com/mjs513/Ra8876LiteTeensy. When the screen is touched the large dot is opposite of where it is being touched. It is like the touch screen was put on backwards. Please help.
Tim
 
Probably should ask on ra8876 thread, but probably does not take logical rotation into account. I would probably have simple sketch that prints the touch positions, and use map functions to convert
 
I don't know if this is the right place to ask, please correct me if wrong! but this was the first ILI9341 library i got to work for me.

I was wondering if there is there a way to have a "fake frame buffer" that is a copy of whatever was sent to the screen,

I would like to retrieve the color of pixels that are already being displayed.

It is for a "live" music driven graphic display sort of like a simplified version of the winamp visualization called "milkdrop"

I am using a teensy 4 and the PJRC store ILI9341 touchscreen (not using touch currently) with this library. My sketch is based on the settings of the buddabrot example.

The performance pf the teensy and screen are much better then i was expecting, and i notice the LED (which is used by SPI) is always on, does that mean it is working as fast as it possibly can, with no room for tweaks?
 
The performance pf the teensy and screen are much better then i was expecting, and i notice the LED (which is used by SPI) is always on, does that mean it is working as fast as it possibly can, with no room for tweaks?

By default the SPI CLOCK speed = 30000000 Mhz (LED pin)
Code:
void setup()
{
  tft.begin(); will use the default SPI CLOCK @ 30000000 MHz.
}

I believe KurtE provide the possibility to override this behavior by doing this at run time:
Code:
void setup()
{
  tft.begin(100000000, 2000000); // ILI9341_SPICLOCK 100000000, ILI9341_SPICLOCK_READ 2000000
}

NOTE: this of course will work only with short wires no more than 1 inch at this speed.
 
yes in my ili9341_t3n library I did add the extra parameters to the begin to allow you to choose an upper speed for SPI. By upper speed I mean that the system will choose the fastest speed it can for your processor that does not exceed the speed you specify.

With this library we also have the construct built in of using a frame buffer. Where all of the drawing primitives draw into this memory and then you have a method updateScreen that draws those pixels to the screen.

This frame buffer is just memory that is just an array of pixel 16 bit colors (565), stored in memory in row order... So you can read from this or do what you like.

Also in most of these libraries, you do have the ability to read a pixel or a rectangle of pixels back. If you are not using the frame buffer, this will do a query over spi to the screen, which is a slower operation. Note, the 2nd speed parameter that @Chris O showed on the begin is the fastest SPI Read speed, and these displays require slower SPI for read operations... But if you are using frame buffer than I simply read the information from it...
The functions for this are:
Code:
  uint16_t readPixel(int16_t x, int16_t y);
  void readRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pcolors);
 
This worked well, I even increased the second "MISO" frequency to 30000000 and it works.

Just aside, I was reading about the imx1170 for some reason, and it's page shows it has "pxp pixel pipeline" and the 1060 also has it!
is there any way to use the iMx1060 "PxP pixel pipeline" with ILI9341, it seems to do hardware alpha blending and so on on a frame buffer, and supports 565 color. https://mcuxpresso.nxp.com/api_doc/dev/1406/a00062.html
 
Hi Kurt, I am using your t3n library on an HX8357 based display with great results! One thing I have not been able to find is a way to display a .bmp or .jpg file. I did see where you convert a file and include it in your program, but I wanted to get a .jpg or .bmp file directly off the SD card and out to the display. I was able to do this with an Arduino Due and the adafruit library, but have not been able to figure out how to do it with the Teensy 4.1. IMG_20200917_165248168_HDR.jpg
Thanks,
Len
 
Offtopic:
To upload bmp images to your HX8357, use the attached sketch. Allows uploading the image from the SDIO reader of the teensy 4.1, also use the SdFat Beta library instead of the SD library. Upload some photos to see how it works. This is the equivalent of the sketch on a 3.5 "ILI9488 TFT:
 

Attachments

  • spitftbitmap0.ino
    8 KB · Views: 66
  • T4_ILI9488_05.jpg
    T4_ILI9488_05.jpg
    195.5 KB · Views: 80
Last edited:
I have a question about how to use multiple setClipRect() functions. I have a simple sketch that has 4 moving squares across a black background. The only way I can get it to render correctly is to fill the screen, set the setClipRect for the particular square, draw the square, then update the screen *for every square*. Is this correct? It seems odd to have to fill the background for every square, but maybe that's just the way it is? I have included my code below and would be open to any optimizations or anything I'm misunderstanding. Thanks!

Code:
// Screen Libraries
#include "SPI.h"
#include "ILI9341_t3n.h"

// Hook Up Teensy
#define TFT_DC  9
#define TFT_CS  10
#define TFT_RST 8                                           

DMAMEM uint16_t screenBuffer[320 * 240];                    // Screen Buffer
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST);     // Create Screen Object

// Framerate Timer
int frameCounter = 0;
int frameTimer = 0;
int frameInterval = 1000;

int boxX1 = -50; int boxY1 = 50;
int boxX2 = 320; int boxY2 = 150;
int boxY3 = -50; int boxX3 = 50;
int boxY4 = 320; int boxX4 = 150;

int boxSpeed = 1;
int squareSize = 50;
int screenW = 320;
int screenH = 240;

// Initialize Screen
void setup(){
  Serial.begin(9600);
  tft.begin();                      // Connect to LCD Screen
  tft.setRotation(1);               // Rotate Screen 90 Degrees
  
  tft.setFrameBuffer(screenBuffer); // Initialize Frame Buffer
  tft.useFrameBuffer(1);            // Use Frame Buffer

  tft.fillScreen(ILI9341_BLACK);    // Clear Screen
  tft.updateScreen();
}

void loop() {
  int squareBufferStart = boxSpeed;
  int squareBuffer = boxSpeed * 2;

  // Draw the Boxes
  tft.fillScreen(ILI9341_BLACK);    // Clear Screen
  tft.setClipRect(boxX1 - squareBufferStart, boxY1 - squareBufferStart, squareSize + squareBuffer, squareSize + squareBuffer);
  tft.fillRect(boxX1, boxY1, squareSize, squareSize, ILI9341_RED);
  tft.updateScreen();

  tft.fillScreen(ILI9341_BLACK);    // Clear Screen
  tft.setClipRect(boxX2 - squareBufferStart, boxY2 - squareBufferStart, squareSize + squareBuffer, squareSize + squareBuffer);
  tft.fillRect(boxX2, boxY2, squareSize, squareSize, ILI9341_GREEN);
  tft.updateScreen();
  
  tft.fillScreen(ILI9341_BLACK);    // Clear Screen
  tft.setClipRect(boxX3 - squareBufferStart, boxY3 - squareBufferStart, squareSize + squareBuffer, squareSize + squareBuffer);
  tft.fillRect(boxX3, boxY3, squareSize, squareSize, ILI9341_BLUE);
  tft.updateScreen();
  
  tft.fillScreen(ILI9341_BLACK);    // Clear Screen
  tft.setClipRect(boxX4 - squareBufferStart, boxY4 - squareBufferStart, squareSize + squareBuffer, squareSize + squareBuffer);
  tft.fillRect(boxX4, boxY4, squareSize, squareSize, ILI9341_YELLOW);
  tft.updateScreen();

  // Move the Boxes
  if(boxX1 > screenW){boxX1 = -squareSize;} else{boxX1 += boxSpeed;}
  if(boxX2 < -squareSize){boxX2 = screenW;} else{boxX2 -= boxSpeed;}

  if(boxY3 > screenH){boxY3 = -squareSize;} else{boxY3 += boxSpeed;}
  if(boxY4 < -squareSize){boxY4 = screenH;} else{boxY4 -= boxSpeed;}

  // Frame Timer
  if(millis() - frameTimer > frameInterval){
    Serial.println(frameCounter); 
    frameCounter = 0;
    frameTimer = millis();
  }
  else{
    frameCounter++; // Increment Frame Counter
  }

}
 
@zrochran - The main issue I think you are running into is to remember the clip rectangle is a single setting, if you set a new one that is the current one. Also all graphic primitives are impacted by the fillScreen. Note: The fillScreen is nothing more than the fillRect with 0, 0, SCREEN WIDTH, SCREEN HEIGHT passed in...

So for example: If we look at your code:
Code:
void loop() {
  int squareBufferStart = boxSpeed;
  int squareBuffer = boxSpeed * 2;

  // Draw the Boxes
  tft.fillScreen(ILI9341_BLACK);    // Clear Screen
  tft.setClipRect(boxX1 - squareBufferStart, boxY1 - squareBufferStart, squareSize + squareBuffer, squareSize + squareBuffer);
  tft.fillRect(boxX1, boxY1, squareSize, squareSize, ILI9341_RED);
  tft.updateScreen();

  tft.fillScreen(ILI9341_BLACK);    // Clear Screen
  tft.setClipRect(boxX2 - squareBufferStart, boxY2 - squareBufferStart, squareSize + squareBuffer, squareSize + squareBuffer);
  tft.fillRect(boxX2, boxY2, squareSize, squareSize, ILI9341_GREEN);
  tft.updateScreen();
So the first time loop is called that first fillScreen will fill the whole screen with black.
Then set the clip rectangle and do your fillrect, and then do the update screen with the clip rectangle still turned on so it only actually updates the screen in that area.

You then call the second fill screen, while the first rectangles clip rectangle is set, so it clears the first rectangles area. You then set new one...

When loop is called the subsequent times, the last rectangle is still set so the first rectangle areas fillScreen will still be in effect.

Again I am not sure what your real goal here is, so hard to say what right solution is.

If you want primitives to update the whole screen, do tft.setClipRect();
And if no clip rectangle is in effect, the things like fillScreen will fill the entire screen. Also if no clip rectangle the tft.updateScreen() will update the whole screen.
 
This worked well, I even increased the second "MISO" frequency to 30000000 and it works.

Just aside, I was reading about the imx1170 for some reason, and it's page shows it has "pxp pixel pipeline" and the 1060 also has it!
is there any way to use the iMx1060 "PxP pixel pipeline" with ILI9341, it seems to do hardware alpha blending and so on on a frame buffer, and supports 565 color. https://mcuxpresso.nxp.com/api_doc/dev/1406/a00062.html

I have used the PXP for several functions to support the ILI9341:

1. Convert YUV422 data from an OV7670 camera to RGB565 for the ILI9341.
2. Rotate the camera data to match the ILI9341 rotation zero. (Not too useful for camera data as it is easier to set the ILI9341 to rotation 3.)
3. Convert VGA camera images in either YUV422 or RGB565 format to RGB565 QVGA for ILI9341 display while saving the full VGA image to SD card.
4. Convert VGA camera images in either YUV422 or RGB565 format to RGB88 for use as input to a JPEG compression function (which reduces the
614KB camera image to about 48KBytes.)

I haven't yet started playing with the alpha blending and other similar functions, but another poster (@vjmuzik) has done so. There is quite a lot on the forum that you can find with a search for "PXP".
 
Hi Kurt,

I'm looking at running 2x ILI9341 displays. One on SPI1 and SPI2 (SPI0 is used). Is there any issues with using the sdio pins for SPI2 or am I best to use the PSRAM Pins?
 
It seems to me that you can connect the two displays in SPI1, you just have to use different CS and generate two control instances in the header, for example tft_1 and tft_2

s8uh0f7lh83i51uzg.jpg

Teensy 3.2: ILI9341 2.8" and 2.4"

PD: I would recommend using an external source to supply the backlight power to the displays, to prevent some uninvited smoke.
 
Back
Top