Highly optimized ILI9341 (320x240 TFT color display) library

New PR made for the rather niche case (?) of using multiple displays on a common SPI bus (one /CS per display). The displays don't have to be of the same type, but will all need similar changes - I've "done" GC9A01A and ST77xx.
 
New PR made for the rather niche case (?) of using multiple displays on a common SPI bus (one /CS per display). The displays don't have to be of the same type, but will all need similar changes - I've "done" GC9A01A and ST77xx.
Sorry, I have not had time (or energy) to fully review the PRs yet.

Also the PRs mention, that you built for T3.x but did not test them. So probably will need to try them out on at least some of the
different T3.x boards. As all three (T3.2, 3.5 and 3.6) DMAs act differently, so easy to introduce issues.

Note: The PR titles and message above are sort of misleading, in that I believe without any of these changes you should
be able to have multiple displays on the same SPI buss with only CS pins. That is the normal behavior for SPI.
There are some obvious caveats to that, in that if we added some functions that have the same name and link error.

And some displays have issue with their MISO line that they don't work with other devices...

Will try to take a more in depth review of the changes, but having more fun on some other things.
 
I believe without any of these changes you should be able to have multiple displays on the same SPI buss with only CS pins. That is the normal behavior for SPI.
I’d love for that to be true, but I believe that because of the speed optimisation stuff there are issues. Specifically, retaining TCR state and pending RX count - each instance thinks it knows the state, but another one may have messed with it…

In addition, previously every instance claimed its own DMA channel, which is pointless and wasteful. No longer the case.

I aimed not to touch T3.x stuff at all (so they also don’t get the “improvements”), but as noted can’t test them beyond compilation.

Of course there’s nothing to be done about badly-behaved displays, apart from don’t use them if you need multiple MISOs to work.

It‘d be great if you or someone else could do a review and / or test, especially with T3.x, but no hurry on this niche case … keep on with the “more fun” stuff 🥳
 
It‘d be great if you or someone else could do a review and / or test, especially with T3.x, but no hurry on this niche case … keep on with the “more fun” stuff 🥳
The "more fun" stuff is trying to improve the use of some of the cameras supported on Zephyr on board like Arduino Giga or Portenta H7 or currently with Arduino Nicla Vision.

Sometimes it feels more like: hitting (y)with 🔨 as @mjs513 can attest to as well 😆. Will also try it at some point, hopefully soon to try some of these cameras with the Teensy board. But so far there are no Video subsystems like CSI, or FlexIO supported for the Teensy boards...
Although looks like IMXRT may have some support now in the HAL layer...

Back to reviewing the requested changes. I am still wondering about some of the need for some of these.

For example things like the TCR state and pending RX count. In most cases the DMA operations are sort of transactional.
That is I believe in most (hopefully all) cases that matter, the code initializes the rx_count to zero. There are cases where
we then increment the rx and decrement the rx, but hopefully all of these transactions end with the calls to
waitTransmitComplete or waitTransmitCompleteReturnLast.

And these wait for that count to go back to zero:
Code:
void ILI9341_t3n::waitTransmitComplete(void) {
  uint32_t tmp __attribute__((unused));
  //    digitalWriteFast(2, HIGH);


  while (pending_rx_count) {
    if ((_pimxrt_spi->RSR & LPSPI_RSR_RXEMPTY) == 0) {
      tmp = _pimxrt_spi->RDR; // Read any pending RX bytes in
      pending_rx_count--;     // decrement count of bytes still levt
    }
  }
  _pimxrt_spi->CR = LPSPI_CR_MEN | LPSPI_CR_RRF; // Clear RX FIFO
  //    digitalWriteFast(2, LOW);
}

We need to that, as we need to make sure the SPI output has been fully output and like, before we can change the state of the
DC or CS pin.

DMA-State - I am wondering if all of the libraries are using this in the same way? I know at one point there was other information
encoded into this state, but it has been a long time since I checked. Things like, have I initialized my DMA Settings chain yet?
Now if this state is shared, do they all still call off to initialize their specific configuration of data...

Will check more tomorrow.
 
Yes, I wasn't at all sure that it's necessary to have a shared copy of pending_rx_count, but there are so many possible paths that it just seemed easier to do that than "hope" all paths end in a waitTransmitComplete(). As it's just a few memory accesses any loss of efficiency that aren't dealt with by the compiler optimisation should be very minor.

The dma_state flags did need a bit of cleaning up for consistency between ILI9341, GC9A01A and ST77xx drivers, but nothing too major. As you say, there's actually very little point in knowing if DMA initialisation has happened, because another driver (specifically my re-working of the ST77xx one) will almost certainly trample on your setup at some point. You can still rely on any values set up in the _dmasettings[] array, as that is not shared. So any asynchronous updates just need to copy the required settings to the DMA registers, which will add only a few µs. Also, only a single DMA channel is allocated for all displays on each SPI bus, and then only if asynchronous updates are used. Previously, one was allocated per display; as there are only 16 available with current Teensyduino, and 32 in all, and each SPI bus grabs two (even if they're unused...), a multi-display system could quite easily run low very quickly.

All in all I'm not sure how relevant these changes are to most users, though I believe they do no harm. I am convinced that any multi-display system which shares a single SPI bus for the displays does require some changes, and that these do the job. My test rig has ILI9341, GC9A01A, ST7735, ST7789 and ST7796 displays sharing everything but their /CS pins - all displays can be used from a single sketch.

EDIT: looking again at the SPI code, I think it only grabs two DMA channels if async transfers are used. My testing was showing two unexpected channels in use - I need to figure out where those came from!
 
Last edited:
EDIT: looking again at the SPI code, I think it only grabs two DMA channels if async transfers are used. My testing was showing two unexpected channels in use - I need to figure out where those came from!
Probably the audio library? I forget which object it is, but one of the sources has a couple of DMAChannels outside of any class so they don't get discarded despite never being used.

Edit: Found it, AudioOutputPWM has 2 global static DMAChannels.
 
Last edited:
I have a problem with running ScrollTest on 240x320 TFT ILI9341 display with Teensy 3.1.
When text line 10 printed at the bottom, the area above it is filled with white and stays empty, bottom line of text keeps changing from 'text line 11' to 'text line 19'. Both in full screen scroll area and in smaller one. 'graphicstest.ino' from 'Examples' runs without issues. Arduino IDE 1.8.19.
Code from ILI9341_t3n-master\examples\scrollTest\scrollTest.ino (comments removed):
C++:
#include <SPI.h>
#include <ILI9341_t3n.h>
#include <ili9341_t3n_font_ComicSansMS.h>
#define ILI9341_RST 8
#define ILI9341_DC 9
#define ILI9341_CS 10
ILI9341_t3n tft = ILI9341_t3n(ILI9341_CS, ILI9341_DC, ILI9341_RST);
void setup() {
  Serial.begin(9600);
  tft.begin();
  tft.setRotation(3);
  tft.fillScreen(ILI9341_BLACK);
  while (!Serial) ;
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
  tft.enableScroll();
  tft.setScrollTextArea(0,0,120,240);
  tft.setScrollBackgroundColor(ILI9341_GREEN);
  tft.setCursor(180, 100);
  tft.setFont(ComicSansMS_12);
  tft.print("Fixed text");
  tft.setCursor(0, 0);
  tft.setTextColor(ILI9341_BLACK);
  for(int i=0;i<20;i++){
    tft.print("  this is line ");
    tft.println(i);
    delay(100);
  }

  tft.fillScreen(ILI9341_BLACK);
  tft.setScrollTextArea(40,50,120,120);
  tft.setScrollBackgroundColor(ILI9341_GREEN);
  tft.setFont(ComicSansMS_10);
  tft.setTextSize(1);
  tft.setCursor(40, 50);
  for(int i=0;i<20;i++){
    tft.print("  this is line ");
    tft.println(i);
    delay(500);
  }
}
void loop(void) {
}

Screenshots in attachment.
I opened an issue on GitHub:
 

Attachments

  • DSCN3162s.jpg
    DSCN3162s.jpg
    475.3 KB · Views: 45
  • DSCN3165s.jpg
    DSCN3165s.jpg
    428.7 KB · Views: 47
Wow! Connected MISO and it works now. I was always sure MISO is not used by displays. And it says everywhere 'is not required if the SPI bus is used only for the display'.
Thank you!
 
Last edited:
Pretty sure it’s needed for scrolling text to work. Some displays may not implement it [correctly], there’s a lot of variants out there…
 
The code that does the scrolling does:
Code:
void ILI9341_t3n::scrollTextArea(uint8_t scrollSize) {
  uint16_t awColors[scroll_width];
  for (int y = scroll_y + scrollSize; y < (scroll_y + scroll_height); y++) {
    readRect(scroll_x, y, scroll_width, 1, awColors);
    writeRect(scroll_x, y - scrollSize, scroll_width, 1, awColors);
  }
  fillRect(scroll_x, (scroll_y + scroll_height) - scrollSize, scroll_width,
           scrollSize, scrollbgcolor);
}

This works one of two ways:
a) If you are setup and using a frame buffer, the readRect simply copies out of the frame buffer memory.

b) It actually reads the pixels back from the display. Which requires MISO pin.

I now see that Issue was resolved...

Edit: Should mention that the example worked on my MMOD on my Shield on the ATP board...
 
I wonder, is there a way to change scroll direction? Like, scroll up or down?
Also, it is worth mentioning, maybe, that with Teensy scrollTest (and many others) does not proceed if serial monitor window is not opened in Arduino IDE.
Simply comment out
Code:
while (!Serial) ;
 
Last edited:
It would be a fairly easy change to the scrollText method to enable scrolling down as well as up, but of course you’d have to call it yourself, because text wrapping or newlines only cause upwards scrolling.

The while(!Serial) ; feature is indeed in many example sketches - they’re examples, not useful standalone code. Although not useful in the ScrollTest case, many PCs don’t enumerate USB fast enough to catch the first lines of output, so it’s very useful during development, but then confuses many people when their code ”doesn’t run” when connected to a simple power supply, or they don’t start the serial monitor. There are dozens of posts on this forum where folk have been caught out that way. There is no “right” answer, except to be aware of the possibility in others’ code.
 
It would be a fairly easy change to the scrollText method to enable scrolling down as well as up, but of course you’d have to call it yourself, because text wrapping or newlines only cause upwards scrolling.
If you think this is 'fairly easy' - can you provide a modified code to scroll forward and backward?
It does not seem obvious to me.
 
Wow! Connected MISO and it works now. I was always sure MISO is not used by displays. And it says everywhere 'is not required if the SPI bus is used only for the display'.
Thank you!
There is no hard rule on this... It really depends on which display control chip is used and in some cases whose display...

Some displays don't support reading anything on the display and as such not needed. I think the ST77xx chips are like this
Some won't work without them. I think RA8875/76 are like this.

In this case with the ILI9341 - it is sort of up to you. If you wish to read the registers or read pixels back in, then you need it
else you don't

Note some displays like many of the ILI9488 boards Again I believe you can read pixels back with it (would have to double check)
BUT: their MISO pins don't play nicely and they drive the IO pin High and Low (no tri-stating) So if you use the MISO pin directly
it does not share... Some of these displays add their own buffer chip or the like to handle this, with others you would have
to do this yourself if you wish for multiple things on the SPI buss.
I wonder, is there a way to change scroll direction? Like, scroll up or down?
Also, it is worth mentioning, maybe, that with Teensy scrollTest (and many others) does not proceed if serial monitor window is not opened in Arduino IDE.
Simply comment out
Code:
while (!Serial) ;
I typically add the above line with a timeout... Like: while (!Serial && (millis() < 5000)) {}
will wait for up to 5 seconds for the Serial to connect... Note the above assumes at start of sketch so just uses millis() as it
is good enough. If wanted in more general areas you could use something like elapsedMillis object for the timing...

If you think this is 'fairly easy' - can you provide a modified code to scroll forward and backward?
It does not seem obvious to me.
It should not be too difficult, but personally I am busy playing on other stuff...
But try making a copy of the scrollTextArea area code I included earlier (give it a different name) like:
scrollTextAreaDown.

Then instead of copying lines up starting at the top and reading the line: at the top + scroll_size and then writing
it to the top line, and then increment to do the next lines...

You start copying from the bottom. That is you read the line at the logical bottom line - scroll_size and copy it down to
the bottom line and decrement and walk your way up the buffers...

And obviously change the rectangle that you clear at the end...
 
If you think this is 'fairly easy' - can you provide a modified code to scroll forward and backward?
It does not seem obvious to me.
Well, it's "fairly easy" for those who are happy digging around in the guts of the libraries!

An updated library can be found in my repo, which includes a version of the ScrollTest example which shows how to scroll both up and down, and also checks that MISO is connected and working. I've done a pull request, but I know @KurtE has other stuff on his plate, so it may take a little while before he can take a look at it.

EDIT: cross post!
 
Back
Top