2.8 inch spi tft lcd ili9341 not working factory

Think is, before I try to modify any library to bend it for the STM32L0 I wanted simply to check my SPI configuration by issuing a simpliest thing, reading LCD ID registers. But that"s the thing, I do not receive any data from ILI9341. I guess all registers are accessible right after the proper reset of the ILI9341, right? So, here is my "init" procedure:

please, see attached screen from my logic analyzer. You can see, CS is brrought low followed by eight clock pulses (500 kHz period) on SCK and 0x04 MSB first on the MOSI and with the DC set low. Afterwards, there is sequence of 4 dummy 0xFF bytes to push out the desired data out from the ILI9341 SPI buffer with the DC set high. But as you can see, there are only zeros on MISO line. Of course, the CS is brought up at the end. I"ve checked the wiring 10 times and I don"t think it is the problem. Maybe, are really all register accessible right after the start of the ILI9341? Or do I have to wirte some data somewhere first?

2.8 inch spi tft lcd ili9341 not working factory

The model is "TJCTM24028-SPI". The board looks like the boards on the smaller displays, but with the additional touch-ic (currently unused and not connected).

Paul - that is just a more pin exposed version of the "purple test board (https://oshpark.com/shared_projects/IWylNfzD)" from the ILI9341 page? And some added components?

Ok interested, ordered boards from oshpark and some TJCTM24028-SPI displays. Seems like I spend half my development time trying to come up with a display option that does not require "time spent fiddling".

The added components are two 10K resistors, as pullups on the two CS signals. I really should have put one on the original test board"s CS signal. These bigger displays have 5 extra pins for the touch controller chip. I just routed the SPI signals and 2 pins for CS and INT. If the board works, I"ll do to the trouble of updating that OSH Park page with good documentation.

The added components are two 10K resistors, as pullups on the two CS signals. I really should have put one on the original test board"s CS signal. These bigger displays have 5 extra pins for the touch controller chip. I just routed the SPI signals and 2 pins for CS and INT. If the board works, I"ll do to the trouble of updating that OSH Park page with good documentation.

That looks like a nice upgrade to the ILI9341 in the store now - and only minimally higher cost? Supported touch on known good units and larger screen area ++good++. Found "some" on aliexpress not noted - assume like FrankB"s it is a Resistive touch unit like the $35 Adafruit - not Capacitive like the $45 unit?

Also ++good++ any other board that makes prototyping/connecting easier - like those from your ILI9341 YouTube Demo (https://www.youtube.com/watch?v=rL_2_D3cgFg) I asked about -

Bad news Frank - if you get medieval on the Teensy and can save the pins and the underboards you"d cut your losses - as long as you can cut just the Teensy with the close contact. My limited experience at cleaning pins only works about 1 at a time - then they go cleaner and not too much heat - especially with the other boards and solder so close.

Paul: The PJRC MicroSD reader - I just got two and the first powered up and works just fine! IIRC You noted somewhere of a new version with a second pull-up, is that important for reliable use?

The rapidly switching signals for the TFT do capacitively couple to the resistive touchscreen signals. Occasionally they cause a significant error in the touch reading, and frequently they add a small amount of noise.

Are samples polled or interrupt supplied? How much response time is lost with a 2 or 4 oversample that you could sum, then shift away? That would minimize but not ignore errant readings, but also make dragging lag.

New 240x320 2.8" SPI TFT LCD Touch Panel Serial Port Module +PCB ILI9341 5V/3.3V (http://www.ebay.com/itm/New-240x320-2-8-SPI-TFT-LCD-Touch-Panel-Serial-Port-Module-PCB-ILI9341-5V-3-3V-/191531963190?hash=item2c9831d336#shpCntId)

At this moment I"m pretty happy with the performance of version 1.1, so I"m not personally planning to spend much more time on this. But if you or anyone else comes up with a much better way, I"d be happy to merge a good pull request.

I am excited about this. I am confused though regarding choosing a ili9341 with xpt2046 touchscreen that is compatible with the OSH board pointed to by Paul.

Post #30 above has this link to a "one" row supported unit in the PJRC Store. (http://www.pjrc.com/store/display_ili9341_touch.html) It is a 2.8" board though.

The 2 row types usually have 40 pins (20 per row). Those are meant for parallel connection. They can be used with the UTFT library. The parallel interface is very fast, but it requires a lot of wires that use up nearly all the pins on Teensy.

But you"ll probably do fine to get one from China. Just make sure it has 1 row of 14 pins. Take a moment to count the number of pins in the photos. If there"s a photo of the back side where signal names are printed, compare the names to the "ILI9341 Pin" column in the connections table. There"s always some small risk when you build stuff directly from Chinese merchants, but if the photo has 14 pins and you can see the same signal names in the same order, odds are very good it"ll work.

But it looks like Paul switched to through holes (for now?) on the test board that"s out on OSH Park (https://644db4de3505c40a0444-327723bce298e3ff5813fb42baeefbaa.ssl.cf1.rackcdn.c om/d00240b93fba420f650eb4e49f426273.png). I have drawers full of through hole caps, so didn"t order any of those.

Thanks, I saw the update to the board as you noted so knew the SMD was out of date. Being new to this - my desk doesn"t have those kind of drawers. I may have picked up a suitable cap assortment. Searching on Digikey is daunting knowing a few functional words can take you to the wrong list - or even the right one with a few thousand parts on many pages to differentiate - and caps having so many material types . . . and then when you find a suitable through hole - there is the the leg distance. It looks like the OSH turn around is longer than I thought so I have time it seems for Paul to update. Though looking now at the OSH images it seems the cap legs are .10"

Has anyone tried the edits to use the SD_card (https://forum.pjrc.com/threads/28106-Display_ili9341?p=72889&viewfull=1#post72889)slot as was done on the non touch ILI9341?

PJRC touch display package came today - great delivery time! - not powered up yet. I sent a note to Robin for future QC testing - If you were lucky enough to get one from the first batch - make sure glass front is secured to PCB or you could get a rude surprise. Metal frame loops may not catch the display side ears allowing it to fall out as you rotate it. Worse yet - the metal frame on one of mine was not secured to the PCB board meaning upside down the screen would drop and swing/tug on the attaching cable. Double sided tape seems to be a fix.

As noted I added double-stick tape and expect it to be fine - but it shipped adhesive free, here"s pic copy of the pic I sent to Robin. The left shows no factory tape - the right shows a jumper wire I placed under the display free of the metal loops. The invoice shows a friendly smiley face that was free! And no I didn"t get the display for $8 I checked my CC. 5292

Wired and working per the Connections and 100% success! (http://www.pjrc.com/store/display_ili9341_touch.html). Much nicer than when I did the ILI9341 unit - I wired to Teensy header rather than jumper wires - which worked but once apart seemed like too much effort to restore.

One thing I noticed is you never get the same X,Y twice (on the test with delay(100) between samples). I used a pointer and did my best to hold it still. I suppose that is the variability Paul saw and wanted to average out in some fashion.

opps:: (~4 hours later sitting on desk) Seeing the screen go WHITE after running for some time sitting IDLE? USB output shows touch data updates but display is nothing but white screen? Just saw it a 3rd time - reverted back to original example/ILI9341Test - happened in under 10 minutes? And again in like 6 mins and then very quickly a time or two. Ran from USB charger battery - same thing. Swapped to backup display and re-powered - same Teensy and same loaded code . . . will update . . . so far so good @35+ mins.

ODD: Loaded "graphicstest" and d#1 is running that? And now back running ILI9341Test ??? Seems flakey - went white again and a reset from TYQT brought it back up - 25 minutes and counting . . . came back after an hour and it was white - restored again on TYQT reset. White again at 33 mins - came back on power cycle. I"ve seen this display go WHITE now 6 times in the last 2 hours 20 minutes. It will come back but not long. As before the Touch SPI is still active - LED blinks - and the USB gives the readings. This is the display that came unstuck from the PCB. I didn"t think much of it but when first attached I did a double upload - I thought maybe there needed to be a delay in setup - there isn"t and the other doesn"t need it and it doesn"t when it starts up working.

That case is with fixed offset images adjusting for the parallax offset. The touch problem is noisy readings - compounded by user movement - intended or not.

I don"t know what return values are expected - but empirically I noticed that the dummy X and next X read are always zero when there is no touch. So after the dummy comes back zero, the next five SPI commands could be aborted as ( !touched ) - then after the SPI.endTransaction() all processing can be terminated after zraw is set zero. This cuts SPI data flow in half when there is no touch, if this is supported behavior, and then the math component goes to zero as now it is done on each update.

And perhaps in update() the assignment of msraw = now; should not be done up front, but only if there is a touch as it will delay getting back in to find the next touch.

So far seeing the raw X,Y data only shows it has a great deal of variability. But it is scaled over 10::1 to the screen pixels, so a value near +/- 15 on either axis will map to the same pixel. Over time the simple average (on the X axis) of the three points groups matches the current scheme +/-1, that time can be 6 total readings, or many more. usually the worst case difference is under 10, sometimes greater than 50, I haven"t looked at which is right more often yet, though that would be easy - but it is late. Paul"s code is right the initial X read is often not in the ball park, other times it is. There may be a simpler way to toss out errant readings, perhaps I can check if given X,Y pairs seem correlated as the current code assumes - it may be that any given X or Y is independently wild?

The cool thing is the Teensy is fast and edits and recompiles are quick! Also cool I didn"t get just one display, #2 has not gone white even once, though touch worked on the white screen and I"m now needing the big screen fed by USB to scan copious data streaming - and thinking of more compares as I wrote this.

This type of measurement is probably always going to be pretty noisy. It"s a fairly high source impedance, and there"s a matrix of switching signals to drive the TFT nearby.

The 12 bit numbers can look pretty terrible. But remember it"s nearly 3000 to 3500 across the entire width of the display. Fluctuations of 30 are only 1% of the display width. Sure it"d be nice if all 12 bit were perfect, but you have to keep some perspective about how accurately a finger can even touch a 2.8 inch display. Even 2% or 3% accuracy (of the full scale) is still better than anyone can probably touch.

Still, if anyone wants to really work on this, RANSAC or some other algorithm would be awesome on a purely technical level, even if it"s not of much practical value for the actual measurement on these small displays.

This type of measurement is probably always going to be pretty noisy. It"s a fairly high source impedance, and there"s a matrix of switching signals to drive the TFT nearby.

The 12 bit numbers can look pretty terrible. But remember it"s nearly 3000 to 3500 across the entire width of the display. Fluctuations of 30 are only 1% of the display width. Sure it"d be nice if all 12 bit were perfect, but you have to keep some perspective about how accurately a finger can even touch a 2.8 inch display. Even 2% or 3% accuracy (of the full scale) is still better than anyone can probably touch.

Still, if anyone wants to really work on this, RANSAC or some other algorithm would be awesome on a purely technical level, even if it"s not of much practical value for the actual measurement on these small displays.

My reason for asking was I have an Adafruit 2.8 ILI9341 without Resistive Touch IC and was wondering if it would be beneficial to add a dedicated IC to it.

The change I made to returned X,Y is that Paul did a distance test as a paired point, which makes sense until looking at the data source and acquisition time. The three reads are under 96 SPI xfer bits apart start to end - alternated xyxyxy. We know the data is jittery and out of 6 points at least one probably wild, but calling it "paired" with one seems artificial, and one wild coordinate would get the pair tossed, even if the other coordinate better equaled a second. So I implemented diff of the 3 values as X and Y and averaged the two closest ones. In my instrumented build I was seeing about 30% of the time my pair average was unique from the initial library.

For either of the above noted calls the same internal update() is done. For touched() it still does all the math and calculations after completing the 10 word SPI data cycle in all cases. In watching the data I saw that the first X value returned after the 4th transfer is ZERO when there is no touch, the first reads are for the Pressure to detect a touch. This calc was done after the SPI cycle, but if there is no X then there is no touch [empirical not from data sheet?], that means the next 4 SPI words can be skipped as the data won"t be used in any case and we can determine that there was no touch. Not reading 40% of the data and skipping all the math means the touched() call is less expensive in SPI bandwidth and cycles.

1> I wondered if reading "xxxyyy" instead of "xyxyxy" would give a better reading not swapping the multiplexor and analog converter around, and the results got more jitter as I saw them. Perhaps the Read x then y cycle allows the alternate resistive plane to recharge?

2> I saw the code noted the first x read was always noisy, and it is, I suppose that is from "powering down" the touch panel with the final read and getting it repowered with the first read.

Considering the number of errors I may end up giving the ADC Library a try, not sure how it will handle pins being reassigned from analog to digital thou.

Managed to slap together a mostly working chunk of code using the ADC library. The xpos seems to work fine, the ypos is all over the place when not being touched. Sometimes its 0 sometimes its 3000-3400, sometimes its around the midpoint. Its way to erratic for averaging to fix. Will put a scope on it tomorrow.

Donziboy2 - to match your findings - I read this last night but didn"t post - but it relates: http://www.unclelarry.com/?p=101 in that he discovered the 5th plane hoping he could read light touches - but until the touch is hard enough there is no circuit completed. Also describes the pressure sense. Again he shows a formula different than in the code for pressure, as in the PDF doc I saw. The code there works for touch detect - but is not linear with the math used.

I updated my PULL request to Paul with a question about the time between SPI.transfer - doing actual z detect after the data from the first two reads comes back is better than assuming the x=0 on the third read is defined behavior on all units, it also allows exit one SPI word sooner when not touched. Based on the above I think in the end the z=pressure calc should be changed when touch is detected.

My SPI question would be - the transfers are blocking as we assign a return value. If we execute "a few" instructions in code as the master before we execute the next SPI transfer is that controlled or gated by master "clocking" the transfer and not by a steady UART like clock?

I tried doing the math with both float and uint32_t. It was more accurate with float but as long as you set a decent threshold to trigger on you should do fine with either. I also had some issues with float overflowing when using 12bit adc values. Makes sense since the Adafruit code uses only 10bit values they would not see issues.

Working with that I see similar or greater variability to the pressure # as in the initial PJRC lib code that is wholly different, but relies on z1 and z2. Removed the x-term as it just adds another random term to what didn"t look good.

Just looking at (z2/z-1) and expecting it to reflect on the x position I don"t see that. It changes on my display at the lightest touch seen or a firm touch - finger or spring stylus. Not only at the same point by a factor of 2 - but across the display both x and y.

I should be right on x.vs.y - as they are bits in the SPI commands - and printed in the debug spew. But it varies across both axis as well. Lowest x,y has highest z tendency on the lightest touch and it is lowest z at the highest x,y.

This is with my working #2 display - up for days. As long as it has been on #1 was off - did a power off swap and it came up white on repowering. Did a reprogram and the color is back [for 10 minutes] - but the touch works in either case - and #1 maybe better than #2,

Working with that I see similar or greater variability to the pressure # as in the initial PJRC lib code that is wholly different, but relies on z1 and z2. Removed the x-term as it just adds another random term to what didn"t look good.

Just looking at (z2/z-1) and expecting it to reflect on the x position I don"t see that. It changes on my display at the lightest touch seen or a firm touch - finger or spring stylus. Not only at the same point by a factor of 2 - but across the display both x and y.

If you don"t move your finger it is fairly accurate, the Adafruit code which i copy/pasted basically uses that formula in a different order, the outcome is the same. For my testing its very apparent when your touching the screen regardless of where you touch it, the touch values are vastly different. Based on my screwing around using uint32_t shows 0-10000(very light touch) values are only seen if the screen is being touched, the touch values from noise are 30K - 500K values. Im not using any averaging at the moment and only using 1 read per output.

They do list a second formula that uses Y also. So you could make it more accurate. On my screen the Y resistance is 524 and R resistance is 288. Really accuracy with this part is not really needed, if you can determine the screen is being touched then its good enough. For me I just need to know when to ignore the x/y pos values since Y seems to be all over the place due to noise from the LCD.

I should be right on x.vs.y - as they are bits in the SPI commands - and printed in the debug spew. But it varies across both axis as well. Lowest x,y has highest z tendency on the lightest touch and it is lowest z at the highest x,y.

This is with my working #2 display - up for days. As long as it has been on #1 was off - did a power off swap and it came up white on repowering. Did a reprogram and the color is back [for 10 minutes] - but the touch works in either case - and #1 maybe better than #2,

Indeed - no problem detecting touch - I was expecting the system would provide a more stable value - even re-reading it after x,y when looking good as noted showed no real improvement worth extra cpu cycles from what PJRC started with that I could see over just reading the z values up front as is.

If you want to try my updated "I:\Teensy165\hardware\teensy\avr\libraries\XPT2046 _Touchscreen\XPT2046_Touchscreen.cpp" on your 1.26 Beta 1 install file I"ll attach it. I think what I changed is noted in a prior post. Turns out one of the nits I picked made things work better for Frank in a specific case and otherwise perfectly well.

Then I"m hoping to do an interrupt LIB version to stop SPI polling for Touch. But I"ve been wondering how you attachInterrupt(TS_Pin, onTouch, FALLING); then onTouch(){ PIN___disable_irq()(TS_PIN) ... PIN___enable_irq(TS_PIN) }

At a bare minimum, an approach like Paul"s Encoder library (https://www.pjrc.com/teensy/td_libs_Encoder.html), which gives users the option to #define a flag to use or not use interrupts, is a very elegant way to do what you"re wanting to do.

Then I"m hoping to do an interrupt LIB version to stop SPI polling for Touch. But I"ve been wondering how you attachInterrupt(TS_Pin, onTouch, FALLING); then onTouch(){ PIN___disable_irq()(TS_PIN) ... PIN___enable_irq(TS_PIN) }

@FrankB: - there is a note about the touch hardware interrupt (https://github.com/Defragster/XPT2046_Touchscreen) that says interrupts are not that straightforward for the touch display:

However, inside your interrupt function, if the display is no longer being touched, any attempt to read the touch position will cause the interrupt pin to create another falling edge. This can lead to an infinite loop of falsely triggered interrupts. Special care is needed to avoid triggering more interrupts on the low signal due to reading the touch position.

As I read this:: This is a problem in that on TOUCH the isr triggers - then the ISR gets control to query the device it will then "read the touch position". If at any point it does that - if the TOUCH is removed it triggers yet another interrupt while still within the prior interrupt. I suppose while contact it held the INT PIN is not reset - but at the point touch is lost and it does reset - any ISR call to update() will itself trigger yet another INT - which would then call update() ... repeat until touched.

Here is an update - almost cool - whenever you touch "off" it rotates the screen and redraws. This just allows your code to pick or change orientation on the fly and properly maps the touch data to the relevant x,y pixels. It won"t clip or re-arrange things outside the pixels that are in the common 0,239 area that is in the 4 rotations. Rotations 1,3 and 2,4 work on the same constraints so that adds yet another variable to my pondering:: if buttons were tagged as such they could be duplicated for use in alternate views at updated locations, and hidden as appropriate.

In case this makes it to a SAMPLE I fixed the setup() wait for serial. I also changed the debounce to 1. It does get spurious hits - something you don"t see when not painting each hit. One thing might be to add a Z threshold in the GetMap - another might be to slow down the polling rate with a 30ms delay between calls.

Paul uses a different mapping so they are not compatible. So you can"t use the Touchpaint-example from the examples-menu for "our" display without changing it.

@frank - my stuff not yet on githib - I did have that thought a post or two ago when I had to re-upload to forum - but not finding the time to complete my first pass so it shows what I want. Though the GetMap() turned out to be quickly useful for the update above! You could port that to T3TRIS and invert your screen for two players taking turns head to head!

Somehow I created a branch so it is under Beta3. This is in my fork of the PJRC code so my working XPT2046_Touchscreen LIB code is there too. Hoping that makes it into the next Beta of 1.26, but Arduino 1.6.6 dropped today and not sure where it will fall out if Paul finds it a worthy update.

I modified the TouchPaint to use Button Definitions from an Array of Structs and moved the action code to functions that loop through the array to do the work. Not a general purpose solution, but drops lines of code and uncluttered the loop for sure of repeated drawRect and fillRect. You can initialize it to start on any tft.setRotation() though 2 or 4 makes sense with how the buttons are laid out.

Hm, this will take some time, if ever.... It has little problems, like PWM (Display-Backlight) not working and if used together with Audioshield+"Connectorboard" the pins are blocking the sd-card-slot so it is not easy to replace the sd-card ...

Picture a good start to see what it was - as you know I"m looking at another ESP8266 unit - I just need to finish where I am and learn Eagle and put on it what I need. I just need to glue the power rx/tx to a raw ESP-12 layout. No audio needed and the video is a debug option - the PJRC touch OSH board would be a good start - though smaller power with SPX3819 or similar. I got the starter eagle I probably need from Constantine, but distracted by the TOUCH just now.

T_3.2 or T_3.1 gives a power choice - if 250ma is a safe average max the T_3.2 is here now. I started assuming the T_3.1 and outside LDO needed - haven"t seen my ESP board yet so not sure how high it will peak.

Changes in fours lines looks like about enough to do it to turn TOGGLE into SLIDE - but to add TOGGLE .vs. SLIDE would take a bit more. Not quite working - may need to edit a 5th line?

I"m just running from USB powered Teensy: I did not have the power supply so I just ran one wire from the VCC CAP pin hole over to the 3.3v Pin and did not need all the edits you did to bypass the power.

Paul - also this is a pretty cool post #123 is a cool TOUCH demo of windowing buttons if you want to take a 5 minute break to see what you"ve allowed us to create. Also BTW - if you have an extra minute I have that pull request on the : https://github.com/PaulStoffregen/XPT2046_Touchscreen/pull/1. I have not reverted to your B1 code to say it saves the world - but it is working for Frank and I.

Infact with three lines swapped the hardware version works intermittently where the Timer version works well - the initial touch may take 50ms to be caught - but then input is read every 10ms during the touch. So that is polling 20 times/sec and sometimes a touch is missed. Any touch is recorded then if the user app asks or not. From there as often as the user requests touch data it will be provided during a touch - on the adjustable 10ms boundary.

@defragster do you have the latest and greatest on gethub ( looked but did not see anything that looked like timer interrupts )? Been trying to use the Adafruit_Gfx_Button functions, my main frustration is trying to change the button background color when it is being pressed and back to normal on release but justReleased() just does not work. Think it might be time to abandon the Adafruit _Gfx_Button and give your code a try.

Tried running the isr touch paint demo, runs until I try to draw on the screen, get a short line then the teensy hangs. I am on arduino 1.6.5 and teensyduino 1.26 beta-1 using your touch driver. Getting late ( not as young as some ) and will update to current release tomorrow.

I have a known good SD card (works in another project using a micro SD socket) - and that same card is fitted into a functional micro > full size SD card adapter.

One of the displays I got turned out to be defective (noted above) and is being replaced - Thanks Robin! When I found I didn"t need to return it - I could HACK IT! - I was going to use it as a test bed for SDCARD access - I didn"t wire it up yet, but I found when I closed the J1 jumper the screen came back! That "component" on VCC is bypassed and it must have been the problem on my display. I suspected that might be that case as the WHITE SCREEN is what you get when you don"t power the VCC pin.

I will run my wires to the SDCARD connect soon and post my results as I also put shorts over where the three resistors were when I soldered the J1 closed. And I re-taped the display down and it is still working after running the Interrupt code [ PIN and TIMER ] overnight from a USB battery pack.

So on two "systems" with and without Pin interrupt they run from a USB pack - overnight and still working - I"m not sure why the post above showed failure without USB connect?

The Timer in the sample is running very fast and a well lit SPI LED - but the Interrupt device has no Touch SPI until it is touched, but the app does clear the Paint area every two seconds when not touched. This was handy for testing so I could keep testing paint on a black screen.

It appears like maybe some form of interaction between the SD card SPI communications and the Touch SPI communications? Im not sure and will get out of my depth debugging the SPI code very quickly!

This does make me start wondering if there is a reason that the SD card is on its own SPI bus (i.e not connected in with either of the other buses present on the screen) but that could be for other reasons.

This is the display that was not getting VCC until soldered over the J1 - it is now working in full for the last 24 hours. The SD card I picked has dozens of files and it did a good directory.

NOTE: When the TOUCH triggers I queue a read for the next user code query. It does not complete a read during the interrupt. It is up to the user code to request data when the code is ready to read it. As it exists the Interrupt code prevents the need for user polling until a Touch happens - but without implementing a user callback I can"t force the user code to act on it until it sees the flag by asking for a point. And my debounce code requires two reads about the same pixel to call it good to return. Unless I make it read and buffer a few points to return - until the user code calls the touch is lost.

I will start afresh again today, remake the SPI wiring links ( those ended up isolated back during my investigations). I will load your code and try and replicate what you are dong that works !

I have "#define TFT_CS 10" on mine - and the one sample I loaded seemed to suggest "CS 4" so I went with that as I had used it on stand alone micro-SD.

It shows that I implemented interrupts about halfway. Touch is detected - but not acted on until user code asks. I may look to fix that - but I had issues and what went stable works like this - because of how I do the Touch reading and worked around the interrupt during interrupt re-entrancy prevention.

@Twinscroll , Great news - glad I could offer you encouragement and a sample. I went on blind faith and dumb luck. As you can see my board isn"t pretty - but it works. The PJRC OSH Display board puts on 10K pullups - not sure how that affects - will build one and see.

Good news is at least two of the new Touch ILI9341 screens have been edited to work per the directions that were suggested for the old Non-touch: this hardware modification may be able to get it working (https://forum.pjrc.com/threads/28106-Display_ili9341?p=72889&viewfull=1#post72889)

This color touchscreen work is so excellent: thanks to Paul, and everyone else contributing, and Adafruit for the GFX library. This has become my "go to" display for quick prototypes: with a Teensy 3.2, the purple board of Paul"s, and the 2.8" TFT.

According to the best data we could find, it is OK to run the backlight at 60 mA per LED. There are four in parallel so that means 240 mA! There is a 4.9 ohm series resistor with the ganged anodes already on the TFT board. I replaced the 100 ohm purple board resistor with 5 ohms (the smallest TH part I had) and the backlight pulls 160 mA, plenty safe I would think, and the display is SO much better with a brighter backlight. We"ll run it at 160 mA and then drop to 20 mA if there is no touch for say 15 seconds, just like tablets and cell phones dim the display.

We have spun our own board for a medical device prototype, and put a PFET at the top of the backlight anodes so we can PWM the backlight since we will run on batteries. I"ll post more about all that once I get the board up this weekend. It"s a rush project. We also have our laser driver on board, with both programmable current (capable of 700 mA or more, we are using 400 mA now) and PWM, with a closed loop servo to make the current stable and accurate. We put on 128 MBit serial flash (we can"t use the huge SD card on the display in our package), a speaker, I2C temp sensor near the FET, and a few other things. It is running off a 3S R/C model battery pack with something like 14 watt hours, so it has plenty of juice. We have switcher modules for 5V and 3.3V, and use the low cell in the pack to keep the Teensy RTC running when the power switch shuts off both regulators. Our board stacks under the LCD, with standoffs in the four corners.

OK thanks...got the latest Teensyduino release and now the skitch comples and runs. I see a black outline square with the word No in the upper left corner and next to it a filled in red square. I touch them and the screen but see no changes...I"m not sure what I"m supposed to see but I guess this means my touch wiring isn"t correct?

I can run that sketch but when I touch nothing happens...I must not have my TFT wired up correctly for touch...I will try to find an example showing how to wire it for touch and double check my wiring...thanks...

An additional 4 pins are required for the touchscreen. For the two analog pins, we"ll use A2 and A3. For the other two connections, you can pin any two digital pins but we"ll be using D9 (shared with D/C) and D8 since they are available. We can save the one pin by sharing with D/Cbut you can"t share any other SPI pins. So basically you can get away with using only three additional pins.

I can run that sketch but when I touch nothing happens...I must not have my TFT wired up correctly for touch...I will try to find an example showing how to wire it for touch and double check my wiring...thanks...

The wiring works per the link in this thread that goes to the PJRC page: https://forum.pjrc.com/threads/30559-2-8-ILI9341-restive-touch-miso-not-connected?p=85727&viewfull=1#post85727

If you have the PJRC style board - the SPI pins are shared - the only unique pin per device is the Chip Select. Not sure if this snippet from the code will help see the wiring if you have the PJRC XPY2046 controlled touch.

If you have the AdaFruit hardware with real Analog values from the touch - it isn"t using the same touch controller - it would be like was used by another poster on this thread. In that case find his posts - it required directly reading and interpreting the analog values.

It takes the HIT testing for buttons out of the user code, but introduces a complex data structure for buttons to feed an engine that identifies buttons. in you case the samples might be interesting but not useful for just page forward and backward.

For your purposes adding 10 on screen buttons could let you jump directly to any screen. in landscape mode the buttons would be large enough to hit and label in two rows of 5 and leave a large area to display whatever you wanted in another screen area. I diverted from the button work to get a non polling version using the interrupt pin that went pretty well, but has some anomaly.

I just moved all my examples to the new GitHub listed above as having it inside the touch screen fork of PJRC makes GitHub painful. I haven"t looked at them for some time, but left them working.

I just created this TOUCH thread for my work in progress - to minimize the pollution of this thread and the repetitious info on this and another thread: ILI9341-and-XPT2046-for-Teensy-Touchscreen-320x240-display (https://forum.pjrc.com/threads/31634-ILI9341-and-XPT2046-for-Teensy-Touchscreen-320x240-display)

Oh, the topic/issue of this thread "..miso not connected.." is solved :) (It was bad soldering of the connector between the display and pcb. Works now.)

Thanks Frank - I put the same note on both threads - the other was created after this since the title fit your problem. Hopefully my new thread will make a single spot that is findable. Even better I hope I can get back to where I was and make some improvements.

Question regarding the Color 320x240 TFT Display and the ILI9341 Controller Chip, do we have any capability to change the screen brightness? I"m running mine on a Teensy 3.2 using 3.3v and the display is "OK" but I was just wondering if we have any tweaking capabilities?

@rfresh737 - what resistor did you put inline with the power? I"m not sure about 3.3v - but on 5v it recommends 100ohm. I saw a note the other day from 5v users they went as low as 10 ohm. I"m not an EE and didn"t even try the math - but as long as the current limit works out - that should put more current to the LEDS for more brightness

@rfresh737 - what resistor did you put inline with the power? I"m not sure about 3.3v - but on 5v it recommends 100ohm. I saw a note the other day from 5v users they went as low as 10 ohm. I"m not an EE and didn"t even try the math - but as long as the current limit works out - that should put more current to the LEDS for more brightness

EDIT: I"m using a 100 ohm resistor. Those are what was used in some of the tutorials I looked at...however, I"m not sure that"s appropriate for a 3.3v system. But I"m a newbie so I have no idea what would be "correct" for 3.3v?

EDIT: I dropped it down to a 47 ohm resistor and that made the LCD a little brighter and now the brightness looks "better" than with the 100 ohm resistor. In fact, I"d say the screen brightness is "excellent" now. So, maybe 47 ohms on a 3.3v board with this screen.

Paul - If you get to update the OSH Display board setup - don"t forget the FUSE link rating - I did - I just realized I adjusted the other needed parts - but left the low 0.14A PTC on my order list - so it won"t feed the T_3.2 enough current to use the new hardware - given that 5v feeds the display LED and the LDO output is 5v not 6v not sure if you would recommend .3A or .35A maybe?

@rfresh737 - what resistor did you put inline with the power? I"m not sure about 3.3v - but on 5v it recommends 100ohm. I saw a note the other day from 5v users they went as low as 10 ohm. I"m not an EE and didn"t even try the math - but as long as the current limit works out - that should put more current to the LEDS for more brightness

Thanks, I"ve got the board and all the parts now. One question though - which way does the Shottky diode go? I could find any posted photos that show it and the PCB is not marked. Is a schematic for this board available? Thanks again.

Now, to reduce the pin usage, I connected the XPT2046 to the SPI and everything works fine, but if I connect the pin SDO_MISO of the display, the SDO_MISO data become crazy shooting continuously and autonomously incorrect values.

2.8 inch spi tft lcd ili9341 not working factory

In these videos, the SPI (GPIO) bus is referred to being the bottleneck. SPI based displays update over a serial data bus, transmitting one bit per clock cycle on the bus. A 320x240x16bpp display hence requires a SPI bus clock rate of 73.728MHz to achieve a full 60fps refresh frequency. Not many SPI LCD controllers can communicate this fast in practice, but are constrained to e.g. a 16-50MHz SPI bus clock speed, capping the maximum update rate significantly. Can we do anything about this?

The fbcp-ili9341 project started out as a display driver for the Adafruit 2.8" 320x240 TFT w/ Touch screen for Raspberry Pi display that utilizes the ILI9341 controller. On that display, fbcp-ili9341 can achieve a 60fps update rate, depending on the content that is being displayed. Check out these videos for examples of the driver in action:

Given that the SPI bus can be so constrained on bandwidth, how come fbcp-ili9341 seems to be able to update at up to 60fps? The way this is achieved is by what could be called adaptive display stream updates. Instead of uploading each pixel at each display refresh cycle, only the actually changed pixels on screen are submitted to the display. This is doable because the ILI9341 controller, as many other popular controllers, have communication interface functions that allow specifying partial screen updates, down to subrectangles or even individual pixel levels. This allows beating the bandwidth limit: for example in Quake, even though it is a fast pacing game, on average only about 46% of all pixels on screen change each rendered frame. Some parts, such as the UI stay practically constant across multiple frames.

A hybrid of both Polled Mode SPI and DMA based transfers are utilized. Long sequential transfer bursts are performed using DMA, and when DMA would have too much latency, Polled Mode SPI is applied instead.

Undocumented BCM2835 features are used to squeeze out maximum bandwidth: SPI CDIV is driven at even numbers (and not just powers of two), and the SPI DLEN register is forced in non-DMA mode to avoid an idle 9th clock cycle for each transferred byte.

Good old interlacing is added into the mix: if the amount of pixels that needs updating is detected to be too much that the SPI bus cannot handle it, the driver adaptively resorts to doing an interlaced update, uploading even and odd scanlines at subsequent frames. Once the number of pending pixels to write returns to manageable amounts, progressive updating is resumed. This effectively doubles the maximum display update rate. (If you do not like the visual appearance that interlacing causes, it is easy to disable this by uncommenting the line #define NO_INTERLACING in file config.h)

The result is that the SPI bus can be kept close to 100% saturation, ~94-97% usual, to maximize the utilization rate of the bus, while only transmitting practically the minimum number of bytes needed to describe each new frame.

although not all boards are actively tested on, so ymmv especially on older boards. (Bug fixes welcome, use https://elinux.org/RPi_HardwareHistory to identify which board you are running on)

This driver does not utilize the notro/fbtft framebuffer driver, so that needs to be disabled if active. That is, if your /boot/config.txt file has lines that look something like dtoverlay=pitft28r, ..., dtoverlay=waveshare32b, ... or dtoverlay=flexfb, ..., those should be removed.

This program neither utilizes the default SPI driver, so a line such as dtparam=spi=on in /boot/config.txt should also be removed so that it will not cause conflicts.

Likewise, if you have any touch controller related dtoverlays active, such as dtoverlay=ads7846,... or anything that has a penirq= directive, those should be removed as well to avoid conflicts. It would be possible to add touch support to fbcp-ili9341 if someone wants to take a stab at it.

If you have been running existing fbcp driver, make sure to remove that e.g. via a sudo pkill fbcp first (while running in SSH prompt or connected to a HDMI display), these two cannot run at the same time. If /etc/rc.local or /etc/init.d contains an entry to start up fbcp at boot, that directive should be deleted.

When using one of the displays that stack on top of the Pi that are already recognized by fbcp-ili9341, you don"t need to specify the GPIO pin assignments, but fbcp-ili9341 code already has those. Pass one of the following CMake directives for the hats:

-DFREEPLAYTECH_WAVESHARE32B=ON: If you are running on the Freeplay CM3 or Zero device, pass this flag. (this is not a hat, but still a preconfigured pin assignment)

-DPIRATE_AUDIO_ST7789_HAT=ON: If specified, targets a Pirate Audio 240x240, 1.3inch IPS LCD display HAT for Raspberry Pi with ST7789 display controller

-DKEDEI_V63_MPI3501=ON: If specified, targets a KeDei 3.5 inch SPI TFTLCD 480*320 16bit/18bit version 6.3 2018/4/9 display with MPI3501 display controller.

If you connected wires directly on the Pi instead of using a Hat from the above list, you will need to use the configuration directives below. In addition to specifying the display, you will also need to tell fbcp-ili9341 which GPIO pins you wired the connections to. To configure the display controller, pass one of:

-DILI9341=ON: If you are running on any other generic ILI9341 display, or on Waveshare32b display that is standalone and not on the FreeplayTech CM3/Zero device, pass this flag.

-DILI9340=ON: If you have a ILI9340 display, pass this directive. ILI9340 and ILI9341 chipsets are very similar, but ILI9340 doesn"t support all of the features on ILI9341 and they will be disabled or downgraded.

-DILI9486L=ON: If you have a ILI9486L display, pass this directive. Note that ILI9486 and ILI9486L are quite different, mutually incompatible controller chips, so be careful here identifying which one you have. (or just try both, should not break if you misidentified)

-DGPIO_TFT_DATA_CONTROL=number: Specifies/overrides which GPIO pin to use for the Data/Control (DC) line on the 4-wire SPI communication. This pin number is specified in BCM pin numbers. If you have a 3-wire SPI display that does not have a Data/Control line, set this value to -1, i.e. -DGPIO_TFT_DATA_CONTROL=-1 to tell fbcp-ili9341 to target 3-wire ("9-bit") SPI communication.

-DGPIO_TFT_RESET_PIN=number: Specifies/overrides which GPIO pin to use for the display Reset line. This pin number is specified in BCM pin numbers. If omitted, it is assumed that the display does not have a Reset pin, and is always on.

-DGPIO_TFT_BACKLIGHT=number: Specifies/overrides which GPIO pin to use for the display backlight line. This pin number is specified in BCM pin numbers. If omitted, it is assumed that the display does not have a GPIO-controlled backlight pin, and is always on. If setting this, also see the #define BACKLIGHT_CONTROL option in config.h.

fbcp-ili9341 always uses the hardware SPI0 port, so the MISO, MOSI, CLK and CE0 pins are always the same and cannot be changed. The MISO pin is actually not used (at the moment at least), so you can just skip connecting that one. If your display is a rogue one that ignores the chip enable line, you can omit connecting that as well, or might also be able to get away by connecting that to ground if you are hard pressed to simplify wiring (depending on the display).

To get good performance out of the displays, you will drive the displays far out above the rated speed specs (the rated specs yield about ~10fps depending on display). Due to this, you will need to explicitly configure the target speed you want to drive the display at, because due to manufacturing variances each display copy reaches a different maximum speed. There is no "default speed" that fbcp-ili9341 would use. Setting the speed is done via the option

-DSPI_BUS_CLOCK_DIVISOR=even_number: Sets the clock divisor number which along with the Pi core_freq= option in /boot/config.txt specifies the overall speed that the display SPI communication bus is driven at. SPI_frequency = core_freq/divisor. SPI_BUS_CLOCK_DIVISOR must be an even number. Default Pi 3B and Zero W core_freq is 400MHz, and generally a value -DSPI_BUS_CLOCK_DIVISOR=6 seems to be the best that a ILI9341 display can do. Try a larger value if the display shows corrupt output, or a smaller value to get higher bandwidth. See ili9341.h and waveshare35b.h for data points on tuning the maximum SPI performance. Safe initial value could be something like -DSPI_BUS_CLOCK_DIVISOR=30.

There are a couple of options to explicitly say which Pi board you want to target. These should be autodetected for you and generally are not needed, but e.g. if you are cross compiling for another Pi board from another system, or want to be explicit, you can try:

-DSINGLE_CORE_BOARD=ON: Pass this option if you are running on a Pi that has only one hardware thread (Pi Model A, Pi Model B, Compute Module 1, Pi Zero/Zero W). If not present, autodetected.

-DARMV8A=ON: Pass this option to specifically optimize for ARMv8-A instruction set (Pi 2B >= rev. 1.2, 3B, 3B+, CM3, CM3 lite, 4B, CM4, Pi400). If not present, autodetected.

-DBACKLIGHT_CONTROL=ON: If set, enables fbcp-ili9341 to control the display backlight in the given backlight pin. The display will go to sleep after a period of inactivity on the screen. If not, backlight is not touched.

-DDISPLAY_CROPPED_INSTEAD_OF_SCALING=ON: If set, and source video frame is larger than the SPI display video resolution, the source video is presented on the SPI display by cropping out parts of it in all directions, instead of scaling to fit.

-DDISPLAY_BREAK_ASPECT_RATIO_WHEN_SCALING=ON: When scaling source video to SPI display, scaling is performed by default following aspect ratio, adding letterboxes/pillarboxes as needed. If this is set, the stretching is performed breaking aspect ratio.

-DUSE_DMA_TRANSFERS=OFF: If specified, disables using DMA transfers (at great expense of lost CPU usage). Pass this directive if DMA is giving some issues, e.g. as a troubleshooting step if something is not looking right.

-DDISPLAY_SWAP_BGR=ON: If this option is passed, red and blue color channels are reversed (RGB<->BGR) swap. Some displays have an opposite color panel subpixel layout that the display controller does not automatically account for, so define this if blue and red are mixed up.

Here is a full example of what to type to build and run, if you have the Adafruit 2.8" 320x240 TFT w/ Touch screen for Raspberry Pi with ILI9341 controller:

If the above does not work, try specifying -DSPI_BUS_CLOCK_DIVISOR=8 or =10 to make the display run a little slower, or try with -DUSE_DMA_TRANSFERS=OFF to troubleshoot if DMA might be the issue. If you are using another display controller than ILI9341, using a much higher value, like 30 or 40 may be needed. When changing CMake options, you can reissue the CMake directive line without having to reclone or recreate the build directory. However you may need to manually delete file CMakeCache.txt between changing options to avoid CMake remembering old settings.

If you want to do a full rebuild from scratch, you can rm -rf build to delete the build directory and recreate it for a clean rebuild from scratch. There is nothing special about the name or location of this directory, it is just my usual convention. You can also do the build in some other directory relative to the fbcp-ili9341 directory if you please.

If the size of the default HDMI output /dev/fb0 framebuffer differs from the resolution of the display, the source video size will by default be rescaled to fit to the size of the SPI display. fbcp-ili9341 will manage setting up this rescaling if needed, and it will be done by the GPU, so performance should not be impacted too much. However if the resolutions do not match, small text will probably appear illegible. The resizing will be done in aspect ratio preserving manner, so if the aspect ratios do not match, either horizontal or vertical black borders will appear on the display. If you do not use the HDMI output at all, it is probably best to configure the HDMI output to match the SPI display size so that rescaling will not be needed. This can be done by setting the following lines in /boot/config.txt:

These lines hint native applications about the default display mode, and let them render to the native resolution of the TFT display. This can however prevent the use of the HDMI connector, if the HDMI connected display does not support such a small resolution. As a compromise, if both HDMI and SPI displays want to be used at the same time, some other compatible resolution such as 640x480 can be used. See Raspberry Pi HDMI documentation for the available options to do this.

The refresh speed of the display is dictated by the clock speed of the SPI bus that the display is connected to. Due to the way the BCM2835 chip on Raspberry Pi works, there does not exist a simple speed=xxx Mhz option that could be set to define the bus speed. Instead, the SPI bus speed is derived from two separate parameters: the core frequency of the BCM2835 SoC in general (core_freq in /boot/config.txt), and the SPI peripheral CDIV (Clock DIVider) setting. Together, the resulting SPI bus speed is then calculated with the formula SPI_speed=core_freq/CDIV.

Adjust the CDIV value by passing the directive -DSPI_BUS_CLOCK_DIVISOR=number in CMake command line. Possible values are even numbers 2, 4, 6, 8, .... Note that since CDIV appears in the denominator in the formula for SPI_speed, smaller values result in higher bus speeds, whereas higher values make the display go slower. Initially when you don"t know how fast your display can run, try starting with a safe high setting, such as -DSPI_BUS_CLOCK_DIVISOR=30, and work your way to smaller numbers to find the maximum speed the display can cope with. See the table at the end of the README for specific observed maximum bus speeds for different displays.

Ensure turbo speed. This is critical for good frame rates. On the Raspberry Pi 3 Model B, the BCM2835 core runs by default at 400MHz (resulting in 400/CDIV MHz SPI speed) if there is enough power provided to the Pi, and if the CPU temperature does not exceed thermal limits. If the CPU is idle, or voltage is low, the BCM2835 core will instead revert to non-turbo 250MHz state, resulting in 250/CDIV MHz SPI speed. This effect of turbo speed on performance is significant, since 400MHz vs non-turbo 250MHz comes out to +60% of more bandwidth. Getting 60fps in Quake, Sonic or Tyrian often requires this turbo frequency, but e.g. NES and C64 emulated games can often reach 60fps even with the stock 250MHz. If for some reason under-voltage protection is kicking in even when enough power should be fed, you can force-enable turbo when low voltage is present by setting the value avoid_warnings=2 in the file /boot/config.txt.

Perhaps a bit counterintuitively, underclock the core. Setting a smaller core frequency than the default turbo 400MHz can enable using a smaller clock divider to get a higher resulting SPI bus speed. For example, if with default core_freq=400 SPI CDIV=8 works (resulting in SPI bus speed 400MHz/8=50MHz), but CDIV=6 does not (400MHz/6=66.67MHz was too much), you can try lowering core_freq=360 and set CDIV=6 to get an effective SPI bus speed of 360MHz/6=60MHz, a middle ground between the two that might perhaps work. Balancing core_freq= and CDIV options allows one to find the maximum SPI bus speed up to the last few kHz that the display controller can tolerate. One can also try the opposite direction and overclock, but that does then of course have all the issues that come along when overclocking. Underclocking does have the drawback that it makes the Pi run slower overall, so this is certainly a tradeoff.

On the other hand, it is desirable to control how much CPU time fbcp-ili9341 is allowed to use. The default build settings are tuned to maximize the display refresh rate at the expense of power consumption on Pi 3B. On Pi Zero, the opposite is done, i.e. by default the driver optimizes for battery saving instead of maximal display update speed. The following options can be controlled to balance between these two:

The main option to control CPU usage vs performance aspect is the option #define ALL_TASKS_SHOULD_DMA in config.h. Enabling this option will greatly reduce CPU usage. If this option is disabled, SPI bus utilization is maximized but CPU usage can be up to 80%-120%. When this option is enabled, CPU usage is generally up to around 15%-30%. Maximal CPU usage occurs when watching a video, or playing a fast moving game. If nothing is changing on the screen, CPU consumption of the driver should go down very close to 0-5%. By default #define ALL_TASKS_SHOULD_DMA is enabled for Pi Zero, but disabled for Pi 3B.

The CMake option -DUSE_DMA_TRANSFERS=ON should always be enabled for good low CPU usage. If DMA transfers are disabled, the driver will run in Polled SPI mode, which generally utilizes a full dedicated single core of CPU time. If DMA transfers are causing issues, try adjusting the DMA send and receive channels to use for SPI communication with -DDMA_TX_CHANNEL= and -DDMA_RX_CHANNEL= CMake options.

The option #define SELF_SYNCHRONIZE_TO_GPU_VSYNC_PRODUCED_NEW_FRAMES can be used in conjunction with #define USE_GPU_VSYNC to try to find a middle ground between raspberrypi/userland#440 issues - moderate to little stuttering while not trying to consume too much CPU. Try experimenting with enabling or disabling this setting.

If your SPI display bus is able to run really fast in comparison to the size of the display and the amount of content changing on the screen, you can try enabling #define UPDATE_FRAMES_IN_SINGLE_RECTANGULAR_DIFF option in config.h to reduce CPU usage at the expense of increasing the number of bytes sent over the bus. This has been observed to have a big effect on Pi Zero, so is worth checking out especially there.

If the SPI display bus is able to run really really really fast (or you don"t care about frame rate, but just about low CPU usage), you can try enabling #define UPDATE_FRAMES_WITHOUT_DIFFING option in config.h to forgo the adaptive delta diffing option altogether. This will revert to naive full frame updates for absolutely minimum overall CPU usage.

A pleasing aspect of fbcp-ili9341 is that it introduces very little latency overhead: on a 119Hz refreshing ILI9341 display, fbcp-ili9341 gets pixels as response from GPIO input to screen in well less than 16.66 msecs time. I only have a 120fps recording camera, so can"t easily measure delays shorter than that, but rough statistical estimate of slow motion video footage suggests this delay could be as low as 2-3 msecs, dominated by the ~8.4msecs panel refresh rate of the ILI9341.

This does not mean that overall input to display latency in games would be so immediate. Briefly testing a NES emulated game in Retropie suggests a total latency of about 60-80 msecs. This latency is caused by the NES game emulator overhead and extra latency added by Linux, DispmanX and GPU rendering, and GPU framebuffer snapshotting. (If you ran fbcp-ili9341 as a static library bypassing DispmanX and the GPU stack, directly linking your GPIO input and application logic into fbcp-ili9341, you would be able to get down to this few msecs of overall latency, like shown in the above GPIO input video)

Interestingly, fbcp-ili9341 is about ~33msecs faster than a cheap 3.5" KeDei HDMI display. I do not know if this is a result of the KeDei HDMI display specifically introducing extra latency, or if all HDMI displays connected to the Pi would have similar latency overhead. An interesting question is also how SPI would compare with DPI connected displays on the Pi.

Unfortunately a limitation of SPI connected displays is that the VSYNC line signal is not available on the display controllers when they are running in SPI mode, so it is not possible to do vsync locked updates even if the SPI bus bandwidth on the display was fast enough. For example, the 4 ILI9341 displays I have can all be run faster than 75MHz so SPI bus bandwidth-wise all of them would be able to update a full frame in less than a vsync interval, but it is not possible to synchronize the updates to vsync since the display controllers do not report it. (If you do know of a display that does actually expose a vsync clock signal even in SPI mode, you can try implementing support to locking on to it)

You can however choose between two distinct types of tearing artifacts: straight line tearing and diagonal tearing. Whichever looks better is a bit subjective, which is why both options exist. I prefer the straight line tearing artifact, it seems to be less intrusive than the diagonal tearing one. To toggle this, edit the option #define DISPLAY_FLIP_ORIENTATION_IN_SOFTWARE in config.h. When this option is enabled, fbcp-ili9341 produces straight line tearing, and consumes a tiny few % more CPU power. By default Pi 3B builds with straight line tearing, and Pi Zero with the faster diagonal tearing. Check out the video Latency and tearing test #2: GPIO input to display latency in fbcp-ili9341 and tearing modes to see in slow motion videos how these two tearing modes look like.

Another option that is known to affect how the tearing artifact looks like is the internal panel refresh rate. For ILI9341 displays this refresh rate can be adjusted in ili9341.h, and this can be set to range between ILI9341_FRAMERATE_61_HZ and ILI9341_FRAMERATE_119_HZ (default). Slower refresh rates produce less tearing, but have higher input-to-display latency, whereas higher refresh rates will result in the opposite. Again visually the resulting effect is a bit subjective.

Having no vsync is not all bad though, since with the lack of vsync, SPI displays have the opportunity to obtain smoother animation on content that is not updating at 60Hz. It is possible that content on the SPI display will stutter even less than what DPI or HDMI displays on the Pi can currently provide (although I have not been able to test this in detail, except for the KeDei case above).

The main option that affects smoothness of display updates is the #define USE_GPU_VSYNC line in config.h. If this is enabled, then the internal Pi GPU HDMI vsync clock is used to drive frames onto the display. The Pi GPU clock runs at a fixed rate that is independent of the content. This rate can be discovered by running tvservice -s on the Pi console, and is usually 59Hz or 60Hz. If your application renders at this rate, animation will look smooth, but if not, there will be stuttering. For example playing a PAL NES game that updates at 50Hz with HDMI clock set at 60Hz will cause bad microstuttering in video output if #define USE_GPU_VSYNC is enabled.

If USE_GPU_VSYNC is disabled, then a busy spinning GPU frame snapshotting thread is used to drive the updates. This will produce smoother animation in content that does not maintain a fixed 60Hz rate. Especially in OpenTyrian, a game that renders at a fixed 36fps and has slowly scrolling scenery, the stuttering caused by USE_GPU_VSYNC is particularly visible. Running on Pi 3B without USE_GPU_VSYNC enabled produces visually smoother looking scrolling on an Adafruit 2.8" ILI9341 PiTFT set to update at 119Hz, compared to enabling USE_GPU_VSYNC on the same setup. Without USE_GPU_VSYNC, the dedicated frame polling loop thread "finds" the 36Hz update rate of the game, and then pushes pixels to the display at this exact rate. This works nicely since SPI displays disregard vsync - the result is that frames are pushed out to the SPI display immediately as they become available, instead of pulling them at a fixed 60Hz rate like HDMI does.

A drawback is that this kind of polling consumes more CPU time than the vsync option. The extra overhead is around +34% of CPU usage compared to the vsync method. It also requires using a background thread, and because of this, it is not feasible to be used on a single core Pi Zero. If this polling was unnecessary, this mode would also work on a Pi Zero, and without the added +34% CPU overhead on Pi 3B. See the Known Issues section below for more details.

There are two other main options that affect frame delivery timings, #define SELF_SYNCHRONIZE_TO_GPU_VSYNC_PRODUCED_NEW_FRAMES and #define SAVE_BATTERY_BY_PREDICTING_FRAME_ARRIVAL_TIMES. Check out the video fbcp-ili9341 frame delivery smoothness test on Pi 3B and Adafruit ILI9341 at 119Hz for a detailed side by side comparison of these different modes. The conclusions drawn from the four tested scenarios in the video are:

The codebase captures screen framebuffers by snapshotting via the VideoCore vc_dispmanx_snapshot() API, and the obtained pixels are then routed on to the SPI-based display. This kind of polling is performed, since there does not exist an event-based mechanism to get new frames from the GPU as they are produced. The result is inefficient and can easily cause stuttering, since different applications p