Bodmer's JPEGDecoder on a DUE with ILI9486 (9488?) WaveShare TFTs

Hi there,

I started with an Arduino DUE board that I've added WiFi and SD breakout modules to. I have a 2.8" TFT display that works fine using this GitHub - marekburiak/ILI9341_due: Arduino Due and AVR (Uno, Mega, Nano, Pro Mini,...) library for interfacing with ILI9341 SPI TFTs and JPEGDecoder. Unfortunately, this display has bad color distortion at various viewing angles so I'd like to replace it with slightly larger 3.5" 320x480 displays.

I have a WaveShare 3.5" that supposedly uses ILI9486 and appears a bit better at various viewing angles, but I've only gotten it working using ILI9488 from GitHub - jaretburkett/ILI9488: Arduino Library for the ILI9488 TFT controller for 4 wire SPI - mostly because I can't find a ILI9486_DUE type of library...I believe that HX8357 can work, but I ran into trouble using it. While I can use Bodmer's TFT_HX8357_Due version, it's missing the ILI9486 driver that got merged into the regular TFT_HX8357 version. I can't use the regular TFT_HX8357 since I'm on a due. (Should I be trying to merge these two?) The sample code provided with the display isn't for due either (https://www.waveshare.com/wiki/3.5inch_TFT_Touch_Shield) but I'd rather work with something closer to what I'm used to if I can also.

The WaveShare mostly works for me; I am able to draw background colors, write text, and so on using the ILI9488 driver. This is the code that's specifically giving me trouble with ILI9488 and the JPEGDecoder library:

tft.pushColor(*pImg++); // Send to TFT 16 bits at a time 
//tft.pushColors(pImg, 0, mcu_pixels); // Send the whole buffer, this is faster

I've tried both of the methods above and the first line (pushColor) produces distorted grey blocks with a slight resemblance of the original JPG, whereas the latter (pushColors) simply produces no output to the TFT for me.

This is all probably pretty vague, but I'm hoping someone a bit more familiar with this sort of thing can help steer me in the right direction... Should I be trying to modify the pushColor/pushColors functionality of the ILI9488 driver, or should I be using a different driver? Should I try merging the ILI9486 driver from TFT_HX8357 into TFT_HX8357_DUE? Should I be trying to use the ILI9341_DUE driver that I had working with the 2.8" display but adapting it to these WaveShare 3.5" displays?

Any advice would be appreciated!

I would stick with the library you have working.

The library you are using converts 16 bit colours to 24 bit, so that is not the problem.

I suspect the bytes need swapping in the colour feed to the TFT library, so change:

while (JpegDec.read()) {

to:

while (JpegDec.readSwappedBytes()) {

in your sketch. and try the pushColor option again.

Bodmer! Much appreciated. I wish I could test now, but I'll have to respond in the next day or two once I get home.

OK, thanks again. I've made it home and got to try .readSwappedBytes() but things actually look pretty similar this way. I've attached a photo of the result in case this helps, but I can only seem to attach one.

Here's the code I'm using:

//while (JpegDec.read()) {
while (JpegDec.readSwappedBytes()) {
   ...
   tft.setAddrWindow(mcu_x, mcu_y, mcu_x + win_w - 1, mcu_y + win_h - 1);
      // Write all MCU pixels to the TFT window
      while (mcu_pixels--) {
        tft.pushColor(*pImg++); // Send to TFT 16 bits at a time
      }
   ...
}

I am gobsmacked by the cavalier attitude to which controller you have.

  1. look at the Ebay sale site where you bought your display.
  2. do the photos match the item on your des?
  3. does the text make any sense? any obvious anomolies?
  4. post a link to the sale item.
  5. describe any differences.

If you can find an accurate schematic, it would be very useful.
A clear photo of the actual pcb can provide a lot of clues.

David.

@bjornenki

I read your post again. The library you are using expects the display to be 24 bit colour. If you draw red, green and blue rectangles on the screen, are they the right colour? If not then you have a 16 bit colour display and the ILI9488 library you have selected is not suitable.

Post back with the results of this test.

@david_prentice

Sorry for the confusion. The display in question is from https://www.waveshare.com/3.5inch-tft-touch-shield.htm. They also have a schematic, demo code, etc. at https://www.waveshare.com/wiki/3.5inch_TFT_Touch_Shield. To me, everything here makes sense and seems correct so far.

Their demo code works on my Uno (I can render BMP files), but I'd need to adapt it to work on my Due. This is why I was wondering what I should be adapting in my original post, since I could either find libraries that work with the ILI9486 or on Dues, but not both. I tend to find people saying to use something like ILI9488, HX8357, etc. when looking for ILI9486 libraries. Perhaps this confusion appears cavalier, or perhaps I'm a bit more confused than I realize.

@bodmer

Using jaretburkett's ILI9488 library seems to work well on this display. The "grahicstest" example works great. All colours appear as expected. The only thing I've tried that doesn't work as expected is introducing JPEGDecoder, but I'm still trying to figure out why.

In case it helps, for comparison here's how the sample code WaveShare provides draws a BMP:

#define RGB24TORGB16(R,G,B) (( (R) >> 3 ) << 11 ) | (( (G) >> 2 ) << 5) | ( (B) >> 3)
LCD.LCD_SetWindows( X_Point * BuffPixel , Y_Point, ( X_Point + 1 ) * BuffPixel , Y_Point);
LCD_DC_1;
LCD_CS_0;
for (uint16_t j = 0; j < BuffPixel; j++) {
   k = j * 3;
   SPI_Write_Byte( (RGB24TORGB16( ReadBuff[k + 2], ReadBuff[k + 1], ReadBuff[k])) >> 8);
   SPI_Write_Byte( (RGB24TORGB16(ReadBuff[k + 2], ReadBuff[k + 1], ReadBuff[k]))  & 0XFF);
}
LCD_CS_1;

bjornenki:
@david_prentice

Sorry for the confusion. The display in question is from https://www.waveshare.com/3.5inch-tft-touch-shield.htm. They also have a schematic, demo code, etc. at https://www.waveshare.com/wiki/3.5inch_TFT_Touch_Shield. To me, everything here makes sense and seems correct so far.

The description and demo code indicate the the display accepts 16 bit colours.

bjornenki:
Their demo code works on my Uno (I can render BMP files), but I'd need to adapt it to work on my Due. This is why I was wondering what I should be adapting in my original post, since I could either find libraries that work with the ILI9486 or on Dues, but not both. I tend to find people saying to use something like ILI9488, HX8357, etc. when looking for ILI9486 libraries. Perhaps this confusion appears cavalier, or perhaps I'm a bit more confused than I realize.

The demo code works, so the display is indeed 16 bit.

bjornenki:
Using jaretburkett's ILI9488 library seems to work well on this display. The "grahicstest" example works great. All colours appear as expected. The only thing I've tried that doesn't work as expected is introducing JPEGDecoder, but I'm still trying to figure out why.

This cannot be an entirely correct statement as Jaret's library sends 24 bit colour values to the display. I can only assume you are guessing that the output colours are correct... If you draw filled red, green and blue rectangles on the screen using Jaret's library then this will demonstrate the problem.

bjornenki:
In case it helps, for comparison here's how the sample code WaveShare provides draws a BMP:

#define RGB24TORGB16(R,G,B) (( (R) >> 3 ) << 11 ) | (( (G) >> 2 ) << 5) | ( (B) >> 3)

LCD.LCD_SetWindows( X_Point * BuffPixel , Y_Point, ( X_Point + 1 ) * BuffPixel , Y_Point);
LCD_DC_1;
LCD_CS_0;
for (uint16_t j = 0; j < BuffPixel; j++) {
  k = j * 3;
  SPI_Write_Byte( (RGB24TORGB16( ReadBuff[k + 2], ReadBuff[k + 1], ReadBuff[k])) >> 8);
  SPI_Write_Byte( (RGB24TORGB16(ReadBuff[k + 2], ReadBuff[k + 1], ReadBuff[k]))  & 0XFF);
}
LCD_CS_1;

This code converts 24 bit colours from the bitmap to 16 bit colours for the display, if this truly does work then this again confirms you have a display that requires 16 bit colours to be written to it.

My conclusion therefore is that Jaret's library will not display the correct colours and that is why the JPEGDecoder code is not working.

The schematic from bjornenki's #6 seems to be an 8080-16 parallel display that is driven by an SPI shift register.

Since most TFT controllers can use a native SPI interface, I don't see why anyone would kludge the shift register. Except that surplus 8080-16 displays appear on the AliExpress market and these do not have access to the IM# pins. So you can't configure for native SPI.

I have never used one of these quasi-SPI arrangements. As far as I can see, they are inherently write-only. The shift registers are considerably slower than the native SPI.

I have a regular SPI ILI9488 e.g. for Jaret's library. This hardware will read registers, GRAM etc. I have extended Jaret's library.

David.

bodmer:
This cannot be an entirely correct statement as Jaret's library sends 24 bit colour values to the display. I can only assume you are guessing that the output colours are correct... If you draw filled red, green and blue rectangles on the screen using Jaret's library then this will demonstrate the problem.

Sorry for the confusion. I was using things like fillScreen and testFilledRects to test the colors. Using these, the colors do appear as expected using Jaret's library. However, if I set up a quick loop and use pushColor to test the colors, they appear incorrect as you suspected; Green appears teal or light blue, blue appears black, etc. I assumed the all of the library functions would handle colors the same (pushColor vs fillScreen, etc.)

Perhaps this is why it took so long for me to understand...I'm still confused about why fillScreen(ILI9488_GREEN) looks green but pushColor(ILI9488_GREEN) looks teal/blue using this library, but I guess it's not important and I should just try finding a better suited one(?)

Thanks again for the help.

Go on. It is pretty straightforward. Your 8080-16 controller expects two 8-bit SPI writes per pixel.
Jaret's library is designed for native 4-wire SPI which requires three 8-bit SPI writes per pixel.

In parallel mode you can select different pixel formats e.g. 565. In native SPI mode the ILI9488 only uses 666 format.

ILI9488 and ILI9486 have similar registers. You have to compare datashèets for the subtle differences.

If Bodmer's library drives an ILI9486 with Waveshare's quasi-SPI shift register circuitry ok, you just need to change a couple of the "subtly different" registers.

Where did you obtain a Waveshare with ILI9488?

David.

david_prentice:
Where did you obtain a Waveshare with ILI9488?

I'm not sure what you mean, but I ordered from the link https://www.waveshare.com/3.5inch-tft-touch-shield.htm.

I'm wondering if the following code combined with the attached photo make sense? Specifically, why is the middle pushColor section getting distorted from green to teal? What's most confusing to me is that fillRect and drawFastHLine both use setAddrWindow and write16BitColor (same as pushColor), but look green as expected. I'm guessing it's not related to my JPEGDecoder issues, but I'm still curious.

#include <Adafruit_GFX.h>
#include <ILI9488.h>
#define TFT_RST 22  // Reset for TFT
#define TFT_DC 26 // Command/Data for TFT
#define TFT_CS 24 // Chip Selecst for TFT
ILI9488 tft = ILI9488(TFT_CS, TFT_DC, TFT_RST);

void setup() {
int x = 0;
  tft.begin();
  for(int y=1; y<=50;y++) {
    // 50 lines of actual green
     tft.drawFastHLine(0,y,340,ILI9488_GREEN);
  }
  while(x < 320) {
    for(int y=50; y<=200;y++) {
      // 150 lines of teal/blue (why teal?)
      tft.setAddrWindow(x,y,x,y+1);
      tft.pushColor(ILI9488_GREEN);
    }
    x++;
  }      

  // the rest is actual green too
  tft.fillRect(0,201,340,280,ILI9488_GREEN);
}

whyteal.png

Bodmer's TFT_HX8357 library can do parallel interface on HX8357, ILI9481, ILI9486.
Bodmer's TFT_eSPI library can do SPI interface on ILI9163, ST7735, S6D02A1, ILI9341 and RPI_ILI9486.

I am fairly certain that your Waveshare ILI9486 will work like RPI_ILI9486.

Quite honestly, I would expect your Waveshare to work with any SPI ILI9341 library with a few small mods.

  1. change geometry from 240x320 to 320x480.
  2. change ILI9341 init sequence to ILI9486 sequence

Note that your Waveshare is write-only. So you can never read ID, registers or GRAM memory.

Waveshare seems to have some example code but it looks very inconvenient to use.
I have a Waveshare HX8347D which also came with un-intuitive example code. (it is also write-only but could have been designed for read-write). I have written an Adafruit_GFX style library for SPI HX8347D

I have little interest in hardware that is write-only. Ask if you want help with init sequence.

The (ILI9488) in your title is confusing. Waveshare clearly document their item as ILI9486.

David.

Thanks David.

Unfortunately, Bodmer's TFT_eSPI and TFT_HX8357 libraries don't work on my Due. I've tried his TFT_HX8357_DUE, but it doesn't have the ILI9486 integration that TFT_HX8357 has.

Now I'm hoping to either add ILI9486 to his TFT_HX8357_DUE, or modify marekburiak's ILI9341_due library work with these displays.

I have access to a few init sequences I can try, but on the ILI9341_due library at least, it seems to be in a progmem format (?) that's throwing me off.

Hopefully write-only won't be a problem. I may want to register touch events to simply cycle jpegs, but as long as I can display a jpeg I'll be happy.

bjornenki:
Thanks David.

Unfortunately, Bodmer's TFT_eSPI and TFT_HX8357 libraries don't work on my Due. I've tried his TFT_HX8357_DUE, but it doesn't have the ILI9486 integration that TFT_HX8357 has.

Now I'm hoping to either add ILI9486 to his , or modify marekburiak's ILI9341_due library work with these displays.

I have access to a few init sequences I can try, but on the ILI9341_due library at least, it seems to be in a progmem format (?) that's throwing me off.

Hopefully write-only won't be a problem. I may want to register touch events to simply cycle jpegs, but as long as I can display a jpeg I'll be happy.

TFT_HX8357_DUE is for parallel, not serial, interface displays and is therefore unsuitable for modification.

If you modify an ILI9341 library bear in mind that all 8 bit control register access codes have to be written as a 16 bit value to get the write strobe counter to trigger. Example here and here.

david_prentice:
I am fairly certain that your Waveshare ILI9486 will work like RPI_ILI9486[...]Ask if you want help with init sequence.

Thanks David. I started with the ILI9341_due library by marekburiak and changed the screen resolution to 320x480. Then I tried adopting the init sequences from the code provided by WaveShare, from the ILI9488 library I was using previously, from bodmer's HX8357 library, and from his TFT_eSPI library.

So far, I've been able to get the screen to be something other than just white, so that feels good. At this point I feel pretty lost though and would love some help with those init sequences. The first two libraries that I mentioned trying the sequences from produce only a white screen. I've gotten farthest with the ones from bodmer's TFT_eSPI and TFT_HX8357. The sequences from bodmer's 2 libraries both produce what ends up like the attached photo (when running the graphicstestWithStats example in ILI9341_DUE). After about 20 seconds of running and filling up, it flickers a bit, but the screen doesn't change really from what's in the photo.

bodmer:
If you modify an ILI9341 library bear in mind that all 8 bit control register access codes have to be written as a 16 bit value to get the write strobe counter to trigger. Example here and here.

Thanks Bodmer. I've also tried changing all 8 bit control register access codes to be written as a 16 bit values in my ILI9341_DUE library, though I'm not sure I managed successfully. I'm basically calling spiwrite16 instead of spiwrite in ILI9341_due.h, similar to your 2nd example provided.

Hopefully I'm on the right track now!

Bodmer has written for RPI_ILI9486. I have never even seen one.

At a guess, regular native SPI code uses
spi8(command), spi8(argument_data) and spi16(pixel_data).

The RPI_abortion would be:
spi16(command), spi16(argument_data) and spi16(pixel_data).

On a Due you can flip between spi8() and spi16() at will.
On a Uno you only have spi8() in hardware. So you implement spi16() with spi8(hi), spi8(lo).

All register arguments are always 8-bits. Even if you have a 8080-18 or 8080-16 parallel interface, the argument data is only in bits 0..7
The native SPI interface is a little different. It reads one argument in 8-bits. So you can place a pair of arguments in a spi16() call.

This is difficult to describe in words. Look at the timing diagrams in the datasheet. Just remember that you are actually talking to an 8080-16 interface as far as the ILI9486 is concerned.

I suggest that you start again with a clean clone of Bodmer or Marek's code.
And just change the appropriate spi() calls to spi16() as provided by Due hardware.

The real mystery is WHY?
Adafruit and EastRising sell 320x480 displays with native SPI interface.
You should be able to drive them at their rated SPI speed. And most importantly, you can read ID and GRAM.

David.

Thanks for all the tips.

david_prentice:
The real mystery is WHY?
Adafruit and EastRising sell 320x480 displays with native SPI interface.
You should be able to drive them at their rated SPI speed. And most importantly, you can read ID and GRAM.

Why is a good question. At this point it's just sunk cost. I could try returning these WaveShares and ordering new displays, but I guess I was hoping to push through if possible. These WaveShares do look awfully nice when on and at the angles I want to view them at, and every display I've tried so far seems to have at least one drawback. Ultimately I want 5, so while I'd love to buy the Adafruit ones, they would quickly add up in price. Looking back, I wish I just started with 5 of those from the beginning. While I'm having fun with all of this, it's getting pretty low-level for my experience.

I don't know what it means to me that I can't read ID or GRAM. Would this affect drawing a JPG or reading a touch event (to potentially change to a new JPG)? I'd hate to get these working only to find out I can't really use them.

My comment was purely my personal interest.

You don't need to read ID or GRAM. At least you already know what controller is mounted.
Waveshare publish schematics and provide fairly accurate data e.g. what controller is mounted.

Most Ebay vendors have no idea what they are selling.

If you start again with a clear plan, you should be able to get your screen working.

I have not looked closely at either Bodmer's or Marek's code. But you should be able to identify where they are writing register arguments and where they are writing pixels.

David.

Wonderful.

I actually think I'm getting pretty close now. When I run the graphicstestWithStats example, tft.fillScreen(color) fills the screen perfectly and the colors also look as expected.

The rest of the tests seem to be running OK and without their previous distortion, but they are mostly collapsed at the top single row of the screen. All of the graohicstest examples except for the yellow testFilledRects (which is missing its magenta components) and the greenish (I believe Roundedrectsfilled) are collapsed this way.

The colors look correct for everything, but it seems as though setAddrWindow isn't working right. I'm having trouble figuring out why. I think this is why text isn't showing up either...it also looks collapsed onto a single row of pixels as well and only a few pixels wide.

At this point can I assume my init sequences are working fine at least, or might those explain some of these problems?