Using TFT Library with Teensy 4.0: How do I update only changed pixels?

Slabshaft

Member
Caveat: I'm not software or electrical engineer, so my knowledge is very basic.

The setup:
Teensy 4.0, 2.8" Touch TFT from PJRC, ILI9341_t3n library

The problem:
Updating the entire screen upon every change results in flicker, rendering the GUI almost unusable. I think this is obvious to others and well known. However, I haven't found any solutions other than to just write tons of code to re-draw what was changed. That's where the code blows up in complexity and is really hard for me to manage since my GUI has many elements.

My main question before the details:
Is there an easy-to-implement method to only write what was changed in the frame but also be able to use existing library methods like tft.drawRect()? I think the Teensy 4.0 has the ram to deal with brute force ways of doing it like reading the entire pixel array after creating the primitive shapes/text, then only write what's changed based on a comparison to another array.

I can't find much info (or at least info written in terms I understand). I assume it's because the microcontrollers until recently haven't had the RAM to do this? I'm using Kurt's ILI9341_t3n library which has "frame buffer" features, but I don't understand what they do just by reading the code and I can't follow the forums posts (too in the weeds/technical and/or abbreviated). I don't know how frame buffering or direct memory access works, so maybe I'm close to an answer with the work people are doing here?

If this is already doable, would someone be willing to write up a quick guide on how to implement something basic, like moving a rectangle across the screen?

Insignificant Details:
My latest idea was to see if I could just store the entire new frame in a 320x240 array of RGB565 color values and compare it to an array of the previous frame's values. Then, just draw what changed. This takes >380kb of the RAM, but it does work smoothly. However, I'm only actually moving a single pixel around with this because I don't know how to translate any other primitives (rectangles, circles, text...) into the array of pixels. Is there some way I can intercept the tft.drawRect(...); methods to put those into an array so I can manually write the pixels to the screen instead? There must be some simple way to do this that I'm just not seeing.

I'm hoping Kurt's library already can do this, but I'm just not understanding how to implement it.

Here's my hacky array comparison method that's a resource hog but works smoothly with a single-pixel animation:
Code:
 void refresh_tft() {

    for (int i = 0; i < 240; i++) {
        for (int j = 0; j < 320; j++) {
            
            if(old_pixel_arr[j][i] != new_pixel_arr[j][i]){

                tft.drawPixel(j,i,new_pixel_arr[j][i]);

            }
        }
    }

    for (int i = 0; i < 240; i++) {
        for (int j = 0; j < 320; j++) {

            old_pixel_arr[j][i] = new_pixel_arr[j][i];

        }
    } 
}
 
There are lots of ways to do update the screen without flicker...

Probably the simplest approach my library is to simply enable the use of the logical frame buffer. There is a few sentences up on github: https://github.com/KurtE/ILI9341_t3n
in the readme on frame buffer. Probably not enough, but I am mainly doing this stuff for the fun of it.

You can enable it using a call like: tft.useFrameBuffer(true);
You can either provide the memory for it or let it internally allocate the frame buffer.
You set the frame buffer by calling something like tft.setFrameBuffer(myBuffer);

When the frame buffer and when you wish to update something on the screen, you can call all of the basic graphic primitives, but instead of writing to the screen, it simply updates the memory buffer.
And when you are done with your updates, you can call tft.updateScreen(),
which will write everything from the frame buffer to the screen. There are some shortcuts this code does (I think), like keep a bounding rectangle of everything that changed since the last update screen and then only update that portion of the screen. There is also concepts of setting a logical clip buffer, to restrict things.

There is also a version of the updateScreen (updateScreenAsync) which uses DMA do do the update and allows your code to something else while the screen is updated. Note: the DMA code does not restrict the updates to only the updated region....

Other approaches: If you are simply doing text updates, you can use the ability of using Opaque text output, such that the output of a new character will overwrite fully what was underneath it. You might also need to remember how far the previous text extended and maybe clear out the portion of the rectangle from where the new text ended and the previous text ended. Or you can cheat if your logical fields have a fixed width, and simply blank from current position to end of field...

Other logical primitives often have similar ways where you have to keep track of what state it was in before and where we are now. Things like logical bar charts, where you may need to clear out or rewrite partial regions depending on previous state.

Not sure if this helps or not?
 
Thanks for the response :cool: Ok, so it looks like the clipping rectangle is the answer for me at this point. Here is the basic program flow, explained slightly differently for anyone who tries this in the future (in a gui using the default primitives). I'm getting perfectly smooth animations this way with zero flicker. Thank you for this library Kurt!

Start with the setup:
Code:
    tft.begin();

    tft.setRotation(1);     //Or whatever

    tft.useFrameBuffer(true);

    draw_background_elements();

Then, any time a primitive needs to move, re-appear or disappear, a clipping rectangle needs to be calculated to encompass both the OLD and the NEW primitive bounds. This is super easy since all primitives have some start or start/finish points to borrow as the clipping rectangle bounds. I expand the clipping rectangle by a number of pixels for faster movements in case the shape must jump more than a pixel at a time. So my own simplified flow looks like this for any graphic change:

rebuild entire gui with new primitive location > set new clipping rectangle > refresh screen

For a 20x20px white square with smooth motion (does not teleport), it might look like this. BTW, my test indicates it doesn't matter that the clipping rectangle goes off screen.
Code:
void update_square(int position_x, int position_y, int width, int height){

    draw_background_elements(); //some other function to draw whatever goes "underneath" the moving square

    tft.fillRect(position_x, position_y, width ,height, 0xFFFF);  //Draw the new square over the background elements

    tft.setClipRect(position_x - 2, position_y - 2, width + 4, height +4);
This method will break down if the clipping rectangle gets large, so I am designing the gui to keep the clipping rectangle minimized. It will still flicker if the entire screen is set as a clipping rectangle. For changing pages in a gui, it's fine. Otherwise, the Teensy 4 is so fast, it can easily just re-calculate the whole gui with no noticeable delay/flicker when using the clipping rectangle. I noticed that if I need to change the entire screen, the clipping rectangle must be resized to the entire screen because it uses the last used clipping rectangle. Anyway, hopefully this is useful for another newbie like myself.
 
Back
Top