Highly optimized ILI9341 (320x240 TFT color display) library

What I did was to compare the previous value with the updated values and if they needed to change I would. The flicker is very noticeable if you update characters that don't change. It's very strange since the off time is fairly short.

I also attempted to break up the values and only blank characters that changed but my limited skill at c caused a few issues and I gave up, I had some success but it seemed hit or miss at times.

When I get home I can dig it up if anyone wants to play with it, I did 4 char values with a movable decimal, which I think the decimal part broke it sometimes.

This is what I'm doing currently. Unfortunately I still have very noticeable flickering. Most likely because I'm using the _48 font.

Redrawing a single character good idea and I planned on implementing some version of this feature into my "label" object that I created last night. It shouldn't be hard to find the width's of previous characters and offset the drawing cursor. Unfortunately it still doesn't change the fact that we are blanking the area of the framebuffer entirely BEFORE the character is drawn. It seems like I'm going to have to dig through the font code and custom implement a new character drawing function.
 
This is what I'm doing currently. Unfortunately I still have very noticeable flickering. Most likely because I'm using the _48 font.

Redrawing a single character good idea and I planned on implementing some version of this feature into my "label" object that I created last night. It shouldn't be hard to find the width's of previous characters and offset the drawing cursor. Unfortunately it still doesn't change the fact that we are blanking the area of the framebuffer entirely BEFORE the character is drawn. It seems like I'm going to have to dig through the font code and custom implement a new character drawing function.

I actually set the starting position of each number after breaking the value into its individual parts. Its a lot of if statements and it seemed to choke at times, I will post it when I get home, maybe it will give someone ideas.

I also found it easier to redraw the character in black then draw the new char in the color you want. Instead of blacking out a box which is slower. Also works pretty good for more then one char, blacking out a box takes longer then blacking out a specific area.
 
I had some luck just dividing the screen into zones and replacing text that changed. I used both techniques clear rectangle (fill) and re-write the string in the background color. definitely worth the effort if your going to be reading the display much. I am working on a project to time RC cars using a Lidar to detect crossing the start / finish line and a hand held unit for showing team number and time. Wireless via XBee comms between the two units. the flicker was very tolerable even using fill rect on small portions of the screen.


Cheers Kb
 
I had some luck just dividing the screen into zones and replacing text that changed. I used both techniques clear rectangle (fill) and re-write the string in the background color. definitely worth the effort if your going to be reading the display much. I am working on a project to time RC cars using a Lidar to detect crossing the start / finish line and a hand held unit for showing team number and time. Wireless via XBee comms between the two units. the flicker was very tolerable even using fill rect on small portions of the screen.
Cheers Kb

Currently I'm using the screen as a display for a temperature controller. Most of it is static aside from the main temperature display. I've been trying to make it look as "nice" as possible, mostly by abusing the living crap out of my DMA controller for just about everything. I actually revisted the tearing line on my screen and it in fact does throw interrupts for vsync and hsync (if you have mode 2 turned on). I managed to clear 8 40x40 rects (measured by seeing what color rect appears on screen) before the blanking period would end. It honestly seems like my best bet would be to take a combination of your approach and my DMA abuse. Draw the characters into memory modifying the "DrawHLine" out of the drawFontBits function and DMA them over during the vblank.


Fortunately I have my interface laid out in a way that doesn't require me to ever read back from the screen. It will always be black behind the text.

Unfortunately, I think I'm moving beyond the realm of the library itself in this discussion. I feel bad hijacking the thread into a debate on the best way to draw font characters, especially on a completely different microcontroller with different peripherals than the library was intended for.

Update: I converted the DrawHLine code to use DMA as well as optimized some of the sending of data around the SetWindow and the flicker is nearly gone. I'm starting to think that I may have inadvertently created more performance issues with my port than I thought.
 
Last edited:
If anyone is curious, I went ahead and combined every suggestion given. I created a label object that would only update characters if they had changed. Otherwise it would feed through a similar version of the "DrawFontChar" function which ended immediately after the cursor would be updated. Then I would draw a black character in it's place and redraw the new character. This was much better than redrawing the entire string (pretty obvious I think) but I still was not completely satisfied with this.

I went ahead and modified the drawFontBits function to take a buffer and the width of the buffer. I also redefined the #define that prevented the function from drawing individual pixels. Instead it would write to individual locations of memory within the buffer. After the drawFontBits was done filling the buffer (this could be as slow as it wants to for all I care) I sent the entire character over in one DMA. Using this the only "flicker" I notice is the flicker of the character itself changing. No more period of a completely black background.

Because I feel bad rubbing my DMA peripheral in everyones face I went ahead and switched it to using the FIFO to see how the performance was. As expected it was slower, though not by much. I think it would be perfectly acceptable to use this solution without a DMA controller. Although this solution is also using a 6.4k RAM buffer (80*80*2)

Here are changes I made to the code to facilitate this. As I said before I'm working on a pure C architecture so the code is slightly different from the official C++ source. Though the relevant lines should be completely agnostic, as well as easily portable.

The math below looks largely..well wrong. But it works perfectly fine for my applications. I'd be very interested to see if someone can come up with a "proper" form.

I also have yet to figure out the exact issue but this only works up on fonts up to 40 pt on my controller. It may be I'm running out of memory, or it's possible I'm running into a DMA limitation on the amount of bytes being sent.

https://gist.github.com/gregslomin/2c29d3e4ee6b65ad744d

If you wish to make the modifications yourself - I believe these are the steps I took


1. Add the buffer and buffer width parameter to "DrawFontBits"
Code:
void drawFontBits(Adafruit_ILI9340 *display, uint32_t bits, uint32_t numbits,
		uint32_t x, uint32_t y, uint32_t repeat, uint16_t *buffer, uint16_t width)

2. Change the #defines in "DrawFontBits" so the first loop is defined and the second is not
3. Replace the "drawPixel" call with a write to the buffer passed into the function
Code:
#if 1 (Step 2)
		do {
			n--;
			if (bits & (1 << n)) {
				buffer[y*width+x1] = fontColor; (Step 3)
				//drawPixel(display, x1, y, fontColor);
				//Serial.printf("        pixel at %d,%d\n", x1, y);
			}
			x1++;
		}while (n > 0);
#endif
#if 0 (Step 2)

4. Modify the "DrawFontChar" function where it calls "DrawFontBits" replacing the uint32_t y = origin_y with the line below.
5. Modify the "DrawFontBits" function call - removing "origin_x" as well as adding the buffer and width parameters
6. Set the drawing window as shown below
7. Send all bytes over SPI using your preferred method
Code:
int32_t y = 5+font->cap_height - height - yoffset;;//origin_y; (Step 4)
	while (linecount) {
              ...snip...
		
				uint32_t bits = fetchbits_unsigned(data, bitoffset, xsize);
				if((x+xoffset) >= 0)
					drawFontBits(display, bits, xsize, x+xoffset, y-1, n, (uint16_t*)buffer, display->cursor_x-oldX+1); (Step 5)
	      ... more snip ... 
		} else {
               ...snip...
				uint32_t bits = fetchbits_unsigned(data, bitoffset, xsize);
				if((x+xoffset) >= 0)
					drawFontBits(display, bits, xsize, x+xoffset, y-1, n, (uint16_t*)buffer, display->cursor_x-oldX+1); (Step 5)
	      ... more snip ... 
			} while (x < width);
			y += n;
			linecount -= n;
		}
}
                // Set Display window to equal size of font character
                setAddrWindow(display, oldX, oldY-5, display->cursor_x, origin_y+font->cap_height);
                // enable DC
		SET_BIT(display->dcPort, display->dc); (Step 6)
                // Send all bytes over SPI
		sspSend(LPC_SSP0, buffer, (delta+1)*(font->cap_height+6)*2; (Step 7)
                // Disable DC
		CLEAR_BIT(display->dcPort, display->dc);

Missed a step
Step 8. Add the following to the top of the "DrawFontChar" function

Code:
int oldX = display->cursor_x;
int oldY = display->cursor_y;
or more likely
int oldX = this->cursor_x;
int oldY = this->cursor_y;

Edit: Kerning fixed
 
Last edited:
Thanks Frank B!

IMG_9437.jpg
 
I also have yet to figure out the exact issue but this only works up on fonts up to 40 pt on my controller.

Hi Reoze, Have you looked into this any further?

I am going to give your method a try, as the flickering screen is a real let down on the overall "look" of the thing I am working on.

I will let you know how I get on.

Thanks
Keith

Update :: I think I've hit a skills brick wall implementing these changes.
 
Last edited:
What I did was to compare the previous value with the updated values and if they needed to change I would. The flicker is very noticeable if you update characters that don't change. It's very strange since the off time is fairly short.

I also attempted to break up the values and only blank characters that changed but my limited skill at c caused a few issues and I gave up, I had some success but it seemed hit or miss at times.

When I get home I can dig it up if anyone wants to play with it, I did 4 char values with a movable decimal, which I think the decimal part broke it sometimes.

Hi Donziboy,

Would you happen to have this code available ? I would very much like to have a play with this if possible. I am also fairy limited at C.
I have a display layout that looks great, but really need to get the least flickering as possible!

Thanks
 
The best way to prevent flickering is to *somehow* sync with the display-refresh.
But i must admit, that i don't know how to do it. At the moment.

Does anybody know more ?

Another approach to minimize it is to draw changed pixels only. I did this with my tetris-game.
With the big fonts, this is a bit complicated..
The ILI9341 is *really* lacking double-buffering - which is the usual way to prevent such effects.
 
Last edited:
Thanks for the input Frank B.
For my application, where I am updating some numeric text at a rate of about 10 to 20 Hz (though I simulated much faster) - The method outlined above of only updating fields that have changed would work OK / be acceptable for me.
I have simulated this method by a basic up counter increasing some values etc and looping over.
However, I am not quite good enough with C in order to write my own function to perform the end to end task of automated checking of changed values passed over / positioning of the cursor etc :( or at least, I am giving it a go...... but it it might be a long road.
 
You could try to "print" into a buffer and compare that buffer with the buffer from the "print" before.
Then, you only write the characters that are changed.
But this still deletes/rewrites by far more pixels than necessary. But it could be done without changes to the library.

Th next step would be to "draw" the characters to a ram-buffer, and compare the pixels in that buffer with the state before - and write only the changed PIXELS to the display.
But that means writing many lines of code and using much ram.
 
If you don't need all the wonderful new fonts have you tried using the original Adafruit font and something like

tft.setTextColor (ILI9341_RED, ILI9341_BLACK);

By selecting both a foreground and background color and using a font that supports background color it will just work with no fancy work on your part.
 
Hi Donziboy,

Would you happen to have this code available ? I would very much like to have a play with this if possible. I am also fairy limited at C.
I have a display layout that looks great, but really need to get the least flickering as possible!

Thanks


At some point I broke it so im not sure how much of it still works. Kept having issues when the decimal got moved sometime and it was not properly clear the previous value.
newaverages is the data i was getting from a second controller. it would then break each value up and then display it. I had planed on making a clean function but gave up and moved on to the rest of my project. I also have issues with C programming ;)

Code:
 /*                                                               //1234 as example
  byte a = (newaverages[i] / 1000) % 10;                                  //1
  byte b = (newaverages[i] / 100) % 10;                                   //2  
  byte c = (newaverages[i] / 10) % 10;                                    //3
  byte d = newaverages[i]  % 10;                                          //4
  
  

  tft.setTextSize(2);
  if(a != aold){
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(50, 5 + (i * 16));
  tft.print(aold, DEC);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(50, 5 + (i * 16));  
  tft.print(a, DEC);
  aold = a; 
  }

  if(b != bold){
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(62, 5 + (i * 16));
  tft.print(bold, DEC);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(62, 5 + (i * 16));  
  tft.print(b, DEC);
  bold = b;
  }

////decimal is printed once with rest of boxes
  if(c != cold){ 
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(86, 5 + (i * 16));
  tft.print(cold, DEC);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(86, 5 + (i * 16));  
  tft.print(c, DEC);
  cold = c;
  }

  if(d != dold){
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(98, 5 + (i * 16));
  tft.print(dold, DEC);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(98, 5 + (i * 16));  
  tft.print(d, DEC);
  dold = d; 
  }
*/
 
Thanks for the input everyone. (Donziboy2 for your code example work in progress).
I would like to use one of the Nicer Fonts, so I will see if I can get this working with the adafruit font as the fallback plan.

I can see that reoze has implemented what sounds like a nice solution (Post number #305) - I have tried to make the same modifications from the information provided, but it all got a bit messy :eek:
I will crack on with the compare/update method and let you know how I get on!

-- If anyone successfully implements the changes in post # 305, please let me know! It could save my hair :)
 
By the way, PJRC now has one of the 320x240 ILI9341 TFT displays in stock. :)

http://www.pjrc.com/store/display_ili9341.html

I'm trying to get the ILI9341 TFT + Teensy 3.2 + Audio Card to work based on the suggested alternate pin routing and the Highly Optimized Library in the documentation, but get a blank screen using USB power to Teensy.

Has anyone got this to work using the Audio Card ? Does the SD card have to be in place in the Audi Card too for this to work ? I'm not using the SD card on the TFT display.
 
I'm trying to get the ILI9341 TFT + Teensy 3.2 + Audio Card to work based on the suggested alternate pin routing and the Highly Optimized Library in the documentation, but get a blank screen using USB power to Teensy.

Has anyone got this to work using the Audio Card ? Does the SD card have to be in place in the Audi Card too for this to work ? I'm not using the SD card on the TFT display.

Duh..Ok decided to try without the Audio card, default 3.1 settings and it works for me fine after a whole day of futzing - but only if I connect a resistor (used 940 ohm) between LED pin and vIN.

No resistor = blank screen. I presume LED really meant backlight here..
 
Yes the Display works with the audiocard. Thats the only way i use it. Use 100 ohm as resistor, it is much brighter.
 
Indeed it does. I'll add a 1K micropot with 100 ohm in series (for safe minimum val to 1k max) for adjustable brightness.
 
Last edited:
If you have OPEN CS pins on Teensy you can just hardwire them. IIRC some months back somebody had the need for 4 - so they did that. Frank found three open pins and put them through a mux on his memory card to get 6 SPI FLASH/RAM chips.
 
Thanks Paul and Frank (and all the others) for the hard work on making this display something really useful for us.

I just purchased this display (received it the other day) and have read through most of this long thread...is there a getting started tutorial somewhere with some code examples that new folks like me can start exploring with?

Thanks...
 
Thanks Paul and Frank (and all the others) for the hard work on making this display something really useful for us.

I just purchased this display (received it the other day) and have read through most of this long thread...is there a getting started tutorial somewhere with some code examples that new folks like me can start exploring with?

Thanks...

You'll find them in the "Examples" menu :)
 
Back
Top