ILI9488_t3 - Support for the ILI9488 on T3.x and beyond...

Thanks Mike,

Not sure at times how much these change help or not... Often times it depends on how much time the code overhead is versus how many times it avoids with reducing the bytes sent out SPI. Example did same code change for the ST7735/89_t3 code base and tried it on T4 with ST7789 2" version... Code change:

I tried it with and without the change:
Code:
#if 1
  uint16_t _previous_addr_x0 = 0xffff; 
  uint16_t _previous_addr_x1 = 0xffff; 
  uint16_t _previous_addr_y0 = 0xffff; 
  uint16_t _previous_addr_y1 = 0xffff; 

  void setAddr(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
    __attribute__((always_inline)) {
      if ((x0 != _previous_addr_x0) || (x1 != _previous_addr_x1)) {
        writecommand(ST7735_CASET); // Column addr set
        writedata16(x0+_xstart);   // XSTART 
        writedata16(x1+_xstart);   // XEND
        _previous_addr_x0 = x0;
        _previous_addr_x1 = x1;
      }
      if ((y0 != _previous_addr_y0) || (y1 != _previous_addr_y1)) {
        writecommand(ST7735_RASET); // Row addr set
        writedata16(y0+_ystart);   // YSTART
        writedata16(y1+_ystart);   // YEND
        _previous_addr_y0 = y0;
        _previous_addr_y1 = y1;
    }
  }
#else
  void setAddr(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
    __attribute__((always_inline)) {
        writecommand(ST7735_CASET); // Column addr set
        writedata16(x0+_xstart);   // XSTART 
        writedata16(x1+_xstart);   // XEND
        writecommand(ST7735_RASET); // Row addr set
        writedata16(y0+_ystart);   // YSTART
        writedata16(y1+_ystart);   // YEND
  }
#endif
Using a version of the graphic test that works in all 4 orientations and prints out timings...
And not much difference:

Code:
	BEFORE			AFTER	
Rotations	0	1		0	1
tftPrintTest: 	1611	1611		1610	1610
testlines: 	536	535		535	535
tftPrintTest: 	73	73		73	74
testdrawrects: 	65	71		65	70
tftPrintTest: 	593	1199		592	1199
testfill/drawcircles: 	204	205	191	190
testroundrects: 	124	123		121	122
testtriangles: 	73	80		74	79
mediabuttons: 	1115	1114		1115	1115
Totals:	4394	5012		4376	4995

I show two of the orientations. Find that the landscape versus portrait at least on one of the tests is a lot different in timing...
But the before versus the later is not a big difference.

Doesn't x change permanently and y only rarely when using the frame buffer?
Maybe only check if x has changed if framebuffer is not active.
 
Hello everybody,
I have a suggestion to further reduce SPI transfer when using framebuffer. It is based on "updateChangedRange", but more efficient when used correctly. Unfortunately, more memory is required, namely an eighth of the resolution of the display. However, most people should have enough memory from current Teensy models. The CPU is also more demanding, but it is faster than SPI.
I just put it in an example code:

Code:
const unsigned int _width = 320; //for debug only
const unsigned int _height = 480; //for debug only

uint8_t changesMask[_width * _height / 8]; //A mask of the changed pixels with an eighth of the resolution because we use the bits as Boolean.

void setup() {
  Serial.begin(9600);
  while (!Serial) ; //wait for Arduino Serial Monitor for debug only

  //Change arbitrary pixels
  updateChangedPixel(1, 0);
  updateChangedPixel(7, 0);
  updateChangedPixel(3, 0);
  updateChangedPixel(36, 0);
  updateChangedPixel(41, 0);
  updateChangedPixel(47, 0);

  updateScreen();

  //feedback for debug
  Serial.println("Screen updated.");

  //Change pixel area
  updateChangedPixelArea(5,5,10,10);

  updateScreen();

  //feedback for debug
  Serial.println("Screen updated.");
}

void updateChangedPixelArea(unsigned int x, unsigned int y, unsigned int w, unsigned int h) {
  for(int _y = y; _y < y + h; _y++){
    for(int _x = x; _x < x + w; _x++){
      updateChangedPixel(_x, _y);
    }
  }
}

void updateChangedPixel(unsigned int x, unsigned int y) {
  if (x >= _width || y >= _height) return; //Out of bounds? We are done.

  int maskIndex = (y * _width + x) / 8; //An eighth of the resolution because we use the bits as Boolean.
  int bitIndex = x % 8;
  
  bitSet(changesMask[maskIndex], bitIndex);
}

void updateScreen(){
  for (int i = 0; i < _width * _height; i++){
    if (bitRead(changesMask[i/8], i % 8)){
      //Transfer the changed pixels via SPI
      //TODO in lib
      
      //Print the changed pixels for debug only
      Serial.print("bitmap byte no. ");
      Serial.print(i);
      Serial.println(" is changed");
    }
  }
  
  memset(changesMask,0,_width * _height / 8); //reset the Mask
}

void loop() {

}

Useful?
 
Hello everybody,
I have a suggestion to further reduce SPI transfer when using framebuffer. It is based on "updateChangedRange", but more efficient when used correctly. Unfortunately, more memory is required, namely an eighth of the resolution of the display. However, most people should have enough memory from current Teensy models. The CPU is also more demanding, but it is faster than SPI.
I just put it in an example code:

Code:
const unsigned int _width = 320; //for debug only
const unsigned int _height = 480; //for debug only

uint8_t changesMask[_width * _height / 8]; //A mask of the changed pixels with an eighth of the resolution because we use the bits as Boolean.

void setup() {
  Serial.begin(9600);
  while (!Serial) ; //wait for Arduino Serial Monitor for debug only

  //Change arbitrary pixels
  updateChangedPixel(1, 0);
  updateChangedPixel(7, 0);
  updateChangedPixel(3, 0);
  updateChangedPixel(36, 0);
  updateChangedPixel(41, 0);
  updateChangedPixel(47, 0);

  updateScreen();

  //feedback for debug
  Serial.println("Screen updated.");

  //Change pixel area
  updateChangedPixelArea(5,5,10,10);

  updateScreen();

  //feedback for debug
  Serial.println("Screen updated.");
}

void updateChangedPixelArea(unsigned int x, unsigned int y, unsigned int w, unsigned int h) {
  for(int _y = y; _y < y + h; _y++){
    for(int _x = x; _x < x + w; _x++){
      updateChangedPixel(_x, _y);
    }
  }
}

void updateChangedPixel(unsigned int x, unsigned int y) {
  if (x >= _width || y >= _height) return; //Out of bounds? We are done.

  int maskIndex = (y * _width + x) / 8; //An eighth of the resolution because we use the bits as Boolean.
  int bitIndex = x % 8;
  
  bitSet(changesMask[maskIndex], bitIndex);
}

void updateScreen(){
  for (int i = 0; i < _width * _height; i++){
    if (bitRead(changesMask[i/8], i % 8)){
      //Transfer the changed pixels via SPI
      //TODO in lib
      
      //Print the changed pixels for debug only
      Serial.print("bitmap byte no. ");
      Serial.print(i);
      Serial.println(" is changed");
    }
  }
  
  memset(changesMask,0,_width * _height / 8); //reset the Mask
}

void loop() {

}

Useful?

A "dirty rectangle" scheme would help with perf, but your implementation (1 pixel at a time) would be self defeating because of the overhead of writing/skipping individual pixels. It's not about the total data sent over SPI, but the transitions from command to data and the total sent. Please see my blog post for more info about this concept:

https://bitbanksoftware.blogspot.com/2019/08/optimizing-access-to-serial-i2cspi.html

A better approach would be to find a minimum rectangle of change or divide the display into tiles (e.g. 16x16 pixels). The CPU has enough spare cycles to make checking for changes worthwhile and square tiles would allow contiguous writes of a decent sized area.
 
A "dirty rectangle" scheme would help with perf, but your implementation (1 pixel at a time) would be self defeating because of the overhead of writing/skipping individual pixels. It's not about the total data sent over SPI, but the transitions from command to data and the total sent. Please see my blog post for more info about this concept:

https://bitbanksoftware.blogspot.com/2019/08/optimizing-access-to-serial-i2cspi.html

A better approach would be to find a minimum rectangle of change or divide the display into tiles (e.g. 16x16 pixels). The CPU has enough spare cycles to make checking for changes worthwhile and square tiles would allow contiguous writes of a decent sized area.

Yes, of course, unfortunately I didn't think that far. For each pixel a "setAddr" has to be executed and a RAMWR command has to be sent ... Stupid of me.
That with the tiles is a good idea. One could write an algorithm that calculates the optimal number of tiles. I probably cannot leave it and will try shortly.
 
I hate to say the obvious, but you need to organize your output code as to not flicker... ;)

If you are not, I would be using the frame buffer, so usually easy to just draw the screen and then do updateScreen to put the fully updated pixels on the screen.

In cases like this, I often end up setting up to single step through the display process.

I end up adding a simple function (or in line code that looks like:
Code:
void waitUserInput()
{
  Serial.println("Press anykey to continue");
  while (Serial.read() == -1) ;
  while (Serial.read() != -1) ;
}
That for example I put in a call right after each call to updateScreen, and then step through my outputs and see if everything progresses from each display to the next. If not you have localized down to where the issue might be...
Note: sometimes I add an optional string parameter to wait function that prints out, the string, so if things jump around you know which one... Again something like:

extern void waitUserInput(const char *title=nullptr);
...

Code:
void waitUserInput(const char *title)
{
  if (title) Serial.print(title);
  Serial.println("Press anykey to continue");
  while (Serial.read() == -1) ;
  while (Serial.read() != -1) ;
}


But again unless we see your actual code, we can only guess.
Could be you are not use Frame buffer and have code that does: tft.fillScreen(...)
followed by drawing of the display, which almost for sure will flicker.

Or you are using Frame buffer and you are using clipping. Then maybe you did a lot of your updates with clip rectangle on, which implies rest of display was not updated, and then did an update without any clip rectangle and it had whatever garbage is in the buffer...
 
Thank you for your prompt reply! Did you watch my video? The code initially runs smoothly. Only when the display gets warm, with high outside temperatures, does it start to look like in the video. I think it is due to some parameters in the initiation of the display, wrongly configured voltages? You can find the init comands at github in my repository Ili9486 if you want to see it.
 
Thank you for your prompt reply! Did you watch my video? The code initially runs smoothly. Only when the display gets warm, with high outside temperatures, does it start to look like in the video. I think it is due to some parameters in the initiation of the display, wrongly configured voltages? You can find the init comands at github in my repository Ili9486 if you want to see it.

So now I had time to take care of the problem. The solution was to adjust the init_commands for Power control 1 (C0h). The standard parameters are obviously not good. I have set both parameters from 0Eh to 17h. Now it runs without flickering.
 
In ILI9488_t3.cpp, lines 84 and 85 are commented out which prevent code compiling on Teensy 4.1 when trying to use DMA (updateScreenAsync):
84: //DMASetting ILI9488_t3::_dmasettings[2];
85: //DMAChannel ILI9488_t3::_dmatx;

Uncommenting these makes the code compile and it seems to work (though with a screen tear midway through which I have not looked into yet). Why are the lines commented out? Thanks.
 
Which version do you have?

That is there was a change PR that went to @mjs513 and then I believe was pulled into the recently released Teensyduino...

The change was: DMASettings and DMAChannel were no longer static objects, but instead were members of the main tft class. So no longer needed to be defined here.

Why? Because on T4.1 with possibility of external memory, you can have more than one of these displays and as such having it static would not work properly.
 
I have an app on a Teensy4.1 that I am developing that has problems which seem to be related to the font drawing routine.

// Tft.setTextColor(ILI9488_CYAN, ILI9488_BLACK);
Tft.setTextColor(ILI9488_CYAN);
Tft.setFont(ComicSansMS_20);

If I use the commented out text color command I seem to be getting Stack corruption as my program starts doing random things and often crashes the display all together.
I can replace the use of non opaque font over writing by just using a fillrect for that part of the display but then I get flickering.
Has anybody else had this issue when using background colours for fonts?
I use the Mono font elsewhere in the app and this doesn't appear to have the same issue.
 
I have an app on a Teensy4.1 that I am developing that has problems which seem to be related to the font drawing routine.

// Tft.setTextColor(ILI9488_CYAN, ILI9488_BLACK);
Tft.setTextColor(ILI9488_CYAN);
Tft.setFont(ComicSansMS_20);

If I use the commented out text color command I seem to be getting Stack corruption as my program starts doing random things and often crashes the display all together.
I can replace the use of non opaque font over writing by just using a fillrect for that part of the display but then I get flickering.
Has anybody else had this issue when using background colours for fonts?
I use the Mono font elsewhere in the app and this doesn't appear to have the same issue.

If you post a (reasonably) simple and complete sketch that reproduces this, will take a look. Without the test case it may be hard to do, but will still try to take look.
 
If you post a (reasonably) simple and complete sketch that reproduces this, will take a look. Without the test case it may be hard to do, but will still try to take look.
Thanks for the reply. I was planning on doing this but as the corruption was so random it may have been hard to reproduce. It was corrupting the CRC32 routine before I added the touch input check, then it was corrupting the Z value so my presumption was it was corrupting the stack.
Anyway, I have found the issue, don't actually understand the code or interface to the TFT fully, but certainly in my case the problem has now gone. There appear to be several versions of the code on GitHub, presumably forks, but don't have the time to chase them down at the moment.

Anyway, the fix is that I added a 'writecommand_last(ILI9488_NOP);' line in before the 'endSPITransaction' at the end of the 'drawFontChar()' function.
So:
3147 write16BitColor(textbgcolor);
3148 writecommand_last(ILI9488_NOP); // Inserted Line
3149 endSPITransaction();

Can you explain why this would be needed? Or has the fault just moved so I am not seeing it!
 
I think I know what is going on...

If you would not mind, could you try a quick test for me:

Remove your write command.
Instead change the line above it:
Code:
write16BitColor(textbgcolor, true);

What was happening is we were not telling the write code that this was the last item to be output and as such it probably had the SPI continue bit set in the TCR register.
Which is probably what you were seeing.
 
If you would not mind, could you try a quick test for me:
Remove your write command.
Instead change the line above it:
Code:
write16BitColor(textbgcolor, true);
Yes, that still works perfectly. Thanks for taking the time to reply and for the speed of your response. I have used Teensy's since the original and must say I am loving the speed of the 4.1 and the USB host just wish it had another 8 I/O pins ;).
 
@Cotman - helps to have good information... You led me right too the issue.

Issued PR back to @mjs513 https://github.com/mjs513/ILI9488_t3/pull/22

May have to check a few other libraries to see if same problem.

As for 8 other IO pins...
It may depend on how and how hard you need them...
Example the bottom memory footprints, you can get 7 IO pins there.

SDCard: you can get 6 IO pins there, by using an SD card breakout... I have played around with one from Sparfun and I have done my own. It was sort of fun with T3.5/6 to use this to be able to sometimes hook up a tft display..
 
As for 8 other IO pins...
It may depend on how and how hard you need them...
Example the bottom memory footprints, you can get 7 IO pins there.
SDCard: you can get 6 IO pins there, by using an SD card breakout... I have played around with one from Sparfun and I have done my own. It was sort of fun with T3.5/6 to use this to be able to sometimes hook up a tft display..
Am currently using the 7 memory lines as I still want SD card access, it's just fiddly having to add the 7 wires and not so neat ;) The project is an IC tester for up to 42 pin DIPS of all types, for some I need speed to test and others I need to simulate clocks etc which is why I haven't gone the SPI DIO route. Though I still had to add 48 DIO SPI lines for power control to combinations of the 42 pins.
 
Am currently using the 7 memory lines as I still want SD card access, it's just fiddly having to add the 7 wires and not so neat ;) The project is an IC tester for up to 42 pin DIPS of all types, for some I need speed to test and others I need to simulate clocks etc which is why I haven't gone the SPI DIO route. Though I still had to add 48 DIO SPI lines for power control to combinations of the 42 pins.

As for the bottom 7 pins, I did a quick and dirty castellated add on board to solder to those pins, plus the 5 internal pins: program... and brought those out to be breadboard compatible...
I built one of them to be able to do testing using those pins.

IMG_1121.jpg

Here is showing the actual small board with other T4.1 that has chips...
IMG_1119.jpg

If I were to do more of these, I probably would have made the center ones line on/off with pads cut in half like the others, so that they can solder in castellated as well.
But for my test setup works OK.
 
I FINALLY got to testing the buydisplay ILI9488 on the T4.1 with external PSRAM.

Unfortunately, it does not work well at all using external PSRAM. It seem to update extremely slow when uncommenting #define ENABLE_EXT_DMA_UPDATES in the header file


My current implementation uses tft.useFrameBuffer(true) and then using the following methods to draw onto the screen:
Code:
tft.setClipRect(a, b, c, d);
tft.updateScreen();
tft.setClipRect();


I have ran the memory test found the PJRC website to confirm that the PSRAM is working as it should.

Any suggestions?
 
It is always an interesting trade off. The PSRAM is a lot slower memory than the others... But in many cases the cache can hide the differences in speed.

In the UpdateScreen case it will likely be slower... As you are just reading and writing to the buffer and to the SPI registers... And slower memory access... Although I can not imaging that it should be that much slower as you are still dealing with SPI speeds.

What the ENABLE_EXT_DMA_UPDATES does is to allow the code to store all of the pixels using twice as much memory per pixel (32 bits instead of 16), which implies you are touching more memory. But what it also allows is for the DMA code to work directly. That is without this, the code for doing the updateScreenAsync, uses two secondary buffers, which it uses round robin, and it would convert the 16 bit colors into 24 bit colors (in 32 bits) and then output that using DMA. When the first buffer completes outputting, it automatically goes on to start outputting the second buffer, and gives an interrupt, which the code then refills the first buffer with the next set of pixels... So there is lots of overhead going on in the background.

With the PSRAM 32 bit buffer, you can simply startup a DMA operation to directly output from this buffer to the SPI, so you only get one interrupt per screen... And no data conversion ... But each memory access is slower...

As for speeding it up... If you are knowing that you are going to output setClipRect(a, b, c, d) if you do this before your code actually updates the screen buffer than only those pixels (and as such 32 bit words) in memory that are within that rectangle will be touched... Which depending on how big a region you are updating, could be good win.
 
I have a Teensy 4.1 and I am using 3.5inch Waveshare LCD (A) display (for RPi) with @sepp89117 ILI9486_t3n library.
https://github.com/sepp89117/ILI9486_RPi-t4

Everything works for me:
Code:
tft.setRotation (3);
tft.fillScreen (ILI9486_BLACK);
tft.setTextColor(ILI9486_YELLOW); 
tft.setTextSize (2);
tft.println ("Hello World");

The only thing that fails me is to visualize the "picture" with the right colors.

If I try to visualize a picture with "writeRect", it is displayed with wrong colors.

Code:
tft.writeRect(32, 33, 256, 174, (uint16_t*)picture);

Picture_writerect.jpg

The colors in the image should be:
Picture_OK.jpg

Any guide to solve this problem?
 
Sometimes hard to know.. As I mentioned on the issue up on github. Sometimes need to find the exact display you are using. When some of us were playing with ILI9488 like displays earlier, I ended up with a few different displays that were often unclear on what actual driver they had, especially with ones that run on RPI...

Some of the boards I looked at, adds had something on them, like it may have, X, or Y or Z display chip on board.

On this thread mostly talking about ILI9488 displays, like you can get from Ebay: or Amazon: https://www.amazon.com/Naliovker-480x320-Serial-Display-ILI9488/dp/B088JWM8ZR
Which the ones of these I have have the same pins on them as the ILI9341 that is sold by PJRC.


A few of the Waveshare ones I for example see up on Amazon are also ILI9486. When I look at their website it mentions three different 3.5" displays A, B, C...
And I could not see anywhere it mentions which display. But one hint is it mentions that A and B are hardware compatible, but use a different driver... So ??

When I see issues like this, I often add in at startup something like:
Code:
  tft.fillScreen(TFT_RED);
  delay(500);
  tft.fillScreen(TFT_GREEN);
  delay(500);
  tft.fillScreen(TFT_BLUE);
  delay(500);
  tft.fillScreen(TFT_BLACK);

As recently did for some Camera and MICROMOD stuff we are playing with. And I watch to make sure RED is RED... If not know something is wrong.

As I mentioned on the Library Issue for the library you mentioned. I am unsure if it is an ILI9486 supports 16 bit colors by SPI. I know that the ILI9488 does not. So this driver sends out 3 bytes per pixel...

Again first test I would do would be to test the right solid colors are output. Otherwise maybe bytes reversed or???
 
Thank you very much @KurtE for all your observations.

The screen that I am using Waveshare LCD (A), when I use it with the RPi and the command "dmesg" shows the ILI9486 controller.

I already did the test with only solid colors and everything is OK, RED is RED, GREEN is GREEN...

I keep analyzing the library code and doing more tests.
 
I managed to get it to work with LCD (A),
basically there are two changes:

1. Change the speed from 80e (LCD (C) is fast) to 30e (LCDC (A) is slower)

2. The startup sequence (init_commands) for the LCD (A) is a bit different and I used the one from @palmerr23
https://github.com/palmerr23/ILI9486_Teensy_Library

I have created a fork, where I add:

// Uncomment to use LCD (A), by default it works with LCD (C)
// #define IS_VER_A
To continue working by default with LCD (C)

Changes:
https://github.com/rcla/ILI9486_RPi-t4/commit/6527639f99fb579ffa88dcc3dfab17f0d8d627fa
 
Back
Top