Optimal ILI9488 Setup? What is the best hardware to use for a robust system?

AshPowers

Well-known member
Hi! I am currently using a T4.0 with a few different touchscreen displays and having some mixed performance results. I built this system originally with an ILI9341 2.8" screen and with the ILI9341_t3n library and double buffering, am getting amazing display quality with no flickering and a framerate right at 60FPS. Couldn't be happier with that setup.

However, wanting to upgrade this to a larger screen with more resolution and physical realestate, I've added both the 3.5" and 4.0" ili9488 480x320 touchscreens to this same system. The issue I am running into is when using buffering on these ili9488 screens, I am getting a dismal 8FPS which just isn't going to work for a turbocharger boost control system. The graphics are flicker-free with the buffering - same quality as with the ili9341_t3n but nowhere near the kind of framerate.

If I turn off the buffering on the ili9488 I get an amazing 70FPS, however, this results in a lot of flickering of the graphics and text on the screen. I am not performing a fillScreen with the ili9488 as I could with the 9341 when using the framebuffer. Rather, for the areas that are to be changed, I am using the fillRect/fillCircle/fillTriangle, etc, to black out the object from the previous frame and then re-drawing the new object immediately afterwards. Unfortunately this still results in a small amount of flickering of these objects.

One approach I've thought of is instead of drawing the previous object in black and redrawing the new object, if I could draw the new object first and then draw the inverse difference between the new and old in black, I believe this would get rid of the perceived flickering. I'm just not sure how exactly the code would be written to do this for things like text.

Is there already a function written in the ili9488 library that performs a task just like this?

OR, perhaps the problem I am experiencing is in how I am using the 9488 library? I've gone through the huge thread on this forum for the development of the 9488 library, which is some 28 pages long.... Trying to pick out the posts that are relative to where this library is at currently and how best to use it is nearly impossible.

Framebuffering would be the most desired method to use for this as I am also very familiar with that approach and my code is already setup that way. It is just baffling that there is an enormous difference going from 60FPS to only 8FPS with only an increase from 320X240 to 480X320 running identical code... Please help! :)

Here are some pics of these devices and a video of them in operation. The 2.8 display is rock solid but you can see the small flickering in the 3.5" and 4.0".

https://youtu.be/xtID1r2CaBc

All3-1.jpgall3-2.jpgall3-3.jpg
 
OK, since my query has appeared to go completely over the intellect of all the apparent geniuses of this forum, I'll provide my solution to this. (Or at the least, no one seems to GAF.. :-/)

Using the framebuffer is absolute fecal matter. It is SSSSLLLLLOOOOWWWWW in comparison to simply using standard draw* and print* functions with some not-so-clever coding either... SMH

For geometric objects, the solution is to draw the object in the new position for the current frame first. Calculate the difference in the area used for the current object position to the previous object position and then draw the shape of that difference in the same color as your background. This maintains the integrity of the object's body through the frame to frame transition. The eye will catch even the slightest moment when the object disappears into the background and comes back.

Code:
  value = mapFloat(BP, -6, 53, 0, 500);
  sdeg = mapFloat(value, 0, 500, -290, 0); // Map value to angle
  if (BP < 0){ sdeg = -261;}
  sx = cos(sdeg * 0.012);
  sy = sin(sdeg * 0.012);
  sx = (sx * 98 + 120);
  sy = (sy * 98 + 140);    // Calculate the triangle vertices for the new needle position
  int new_x1, new_y1, new_x2, new_y2;
  calculateTriangleVertices(240, 187, sdeg * 0.012, 20, &new_x1, &new_y1, &new_x2, &new_y2);

  // Draw the new red triangle
  tft.fillTriangle(new_x1, new_y1, new_x2, new_y2, (sx + 40)*xr, sy * yr, ILI9488_RED);

  // Erase the remaining pixels from the previous needle position by drawing a single black triangle
  if (old_x1 != 0) {
    int third_vertice_x, third_vertice_y;
    if (sdeg < old_sdeg) { // If the needle moved clockwise
      third_vertice_x = old_x1;
      third_vertice_y = old_y1;
      if (sdeg == -227) {
        tft.drawLine(old_x1, old_y1, 240, 187, ILI9488_YELLOW);
      }
    } else { // If the needle moved counterclockwise
      third_vertice_x = old_x2;
      third_vertice_y = old_y2;
      if (sdeg == -221) {
        tft.drawLine((sx + 40)*xr, sy * yr, old_x2, old_y2, ILI9488_BLACK);
      }
    }
    tft.fillTriangle((osx + 40)*xr, osy * yr, (sx + 40)*xr, sy * yr, third_vertice_x, third_vertice_y, ILI9488_BLACK);
  }
  tft.fillCircle(240, 187, 20, ILI9488_RED);
  tft.drawCircle(240, 187, 18, ILI9488_WHITE);
  // Update the previous angle, needle position, and triangle vertices
  old_sdeg = sdeg;
  osx = sx;
  osy = sy;
  old_x1 = new_x1;
  old_y1 = new_y1;
  old_x2 = new_x2;
  old_y2 = new_y2;
  BPOLD = BP;

If you are are dealing with text objects the solution is the same principle but handled a little differently.. Here is an example:

Code:
tft.setFont(Arial_96_Bold);

  int valueInt = int(BP);
  int oldValueInt = int(BPOLD);

  // Erase only the parts of the old value that have changed
  tft.setTextColor(ILI9488_BLACK);
  if (oldValueInt != valueInt) {
    tft.setCursor(330, 133);
    tft.print(oldValueInt);
  }

  // Draw the new value in yellow
  tft.setTextColor(ILI9488_YELLOW);
  tft.setCursor(330, 133);
  tft.print(valueInt);

Doing it this way rather than using the frame buffer has significantly decreased the program loop cycle time. I was getting, at best, with the ILI9341_t3n driver on the T4 running only 320x240 with the framebuffer, about 60 frames per second... With the ILI9488 at 480x320, non-buffered, and altering about a dozen different graphics and text on the screen at every loop of the program, I am pushing upwards of 110 FPS!!

If I may make a suggestion to the developers of the libraries for these displays... You guys, firstly are absolutely bad-ass and I've gone through countless threads/posts of the development of these TFT libraries and am truly humbled by your level of expertise....it is beyond my capabilities and I have nothing but respect. I am just an end user that is doing the best I can within my abilities to leverage the platform you have put hard work into in order to achieve my goal. So, again, my highest level of respect and appreciation to you!

To my suggestion... From what I gather re: frameBuffering, as the program proceeds through code and objects are being drawn to the tft object, a memory buffer that represents every pixel is being logically modified pixel by pixel until the updateScreen call is made... at which time it writes out the screen image data to the TFT in one shot. By looking at the amount of time it takes for this process to occur it does not appear to me that it is changing the pixel color data for ONLY the pixels that have a new color compared to the previous frame. If this were the case, this is a FAR more efficient way of updating the pixel data compared to my method of drawing the entire new object and using a logical NOT to create a shape to draw in the background color.... But still, my trebuchet is beating your frameBuffer's arse..

Seems to me that instead of using a memory-based buffering approach, a better methodology would be to look at each of the objects on the screen and how they are being created.. be it tft.print or tft.drawLine, etc.. And map out the change and using the same object creators, remove the difference...

Anyway..
 
Sorry I did not notice your thread earlier. Simply put, if it does not show up in the New posts, when I am looking for things, I might easly miss it.

Also should mention, that those of us that have done stuff on these drivers are also just end users, doing it for the fun of it. Many of us our retired, and just doing this for a hobby.

Yes, there is an interesting problem of switching from ILI9341 to ILI9488 speed wise. That is to update a whole frame you go from sending: 320*240*2 bytes (plus some simple overhead to start it), to 480*320*3 bytes, as the ILI9488 over SPI does not support 16 bit colors, but instead either 18 or 24... So you send 3 times the number of bytes.

So yes blindly updating the whole display is going to take longer...

However there are other simple tricks you can now play with current ILI9488_t3 (some not yet in build, but you can get from github\mjs513 (master version) or my github fork which has it.

First, you can now, set the clip rectangle around the area you are updating, like the text area in your display and then do updateScreen, and it will only draw the pixels in that rectangle to the screen. setClipRect(x, y, w, h);
You can set it before the graphic primitives are done and it will clip those primitives or you can set it just before the updateScreen to simply limit how much of the display draws.

There is now also an option you can turn on: void updateChangedAreasOnly(bool updateChangedOnly)
That when each graphic primitive is done, the bounding rectangles are ored to each other and then when you do the updateScreen that rectangle area potentially clipped by clip rectangle is updated... And the dirty area fields are cleared. This was in ILI9341_t3n, but some fixes recently went in plus added to a few of the libraries.

Note: updateScreenAsync does not currently support the clipping or the changed areas stuff. I have a WIP version of ILI9431_t3n where I have added it, so far only for T4.x and not continuous updates.

As I mentioned about the changed areas, it mostly just takes your input like from: drawLine(x1, y1, x2, y2); and it will add the whole rectangle bounds by those 2 coordinates and adds the whole area to the dirty rectangle. What it could do is to actually look at each pixel value it is storing into the frame buffer and only if that value changes does it then reflect that changed pixel location into the bounding changed area. So far the only place I have done that is with writeRect

Why writeRect? Because there is another graphics library TGX (https://github.com/vindar/tgx) whose code is setup to write to an external frame buffer (a memory array), and he also has a library https://github.com/vindar/ILI9341_T4 which is optimized to host the TGX code. Several threads on this recently. In those, there were requests about users wanting to use the tgx library on different displays (st7789 and ILI9488). So I made sure we could at least host the library with ILI9341_t3n and have it work reasonably. Still not as optimized for it as the ILI9888_t4, but... And then added the same support into those displays as well (plus a couple of others not part of Teensyduino release).

At some point, I may play more with adding in something like the _t4 version that has method like: updateScreen(uint16_t *fb)
that instead of doing the two step update (writeRect, updateScreen) it does it in one and maybe not update the whole bounding area of changes, like I do now, but instead potentially break this up into smaller changed rectangles, to hopefully more minimize the number of bytes needed to be sent. I have not analyzed his code for this to know what limitations he has put into deciding which rectangles to output and order for these... There are some interesting things to consider like if you have a bounding area with like a tall skinny area and a wider short area higher up on the display like needle area and text area for the numeric value, (in your case this is lower)... But do you try to update the display with rectangles that follow the scan lines or do you try to totally minimize output... Not sure yet if I am inspired enough to work on this. Obvsously someone else can and issue PR...

And of course, optimizing how you do graphic outputs can potentially always win. With or without frame buffers. For example, if your textual outputs and the like,
if there is no overlap, like the needle won't overlap the numbers. I have mentioned before that, you can either do it manually or at times I have create simple object to handle some of it.

This can be done as simple or complex as you want. For example do you wish for the numbers to be left justified, centered or right justified... And in some cases maybe fudged.

Left justified, and limited changes:
Simply draw the text opaque by setting both foreground and background color. And do the print. Now if this ends up not properly overwriting the end of previous text, a couple ways to handle. Could simply output 1 or more spaces... Or you could get the text cursor position after the text output, and compare it to the end position of previous write, and when necessary do a fillRect with background color between those two positions.

Right justified or centered. You ask the system how big the rectangle is for your next text. and if necessary do a fillRect to fill in the stuff to the left of your text, output your text, fill in on the right of the text if necessary... All of this can be done with out frame buffer and no flickering... Or can be done quick and dirty with frame buffer. Simply set the rectangle to the area for that field, do your updates, could simply fill the rectangle, draw the text and do updateScreen...

The use of frame buffer for the above makes it easier if for example maybe your background is not solid color, or something else like the needle might overlap...

Hope that helps.

Now back to playing... Or working in yard or???
 
Back
Top