Bug when using the TFT_eSPI Library with ST7796

I absolutely love this library and I also really like this display. Now I have come to a problem that cost me a lot of sleep last night. I followed through today and this is what I have found so far:

Symptom 1: When I save a portion of graphics, overwrite it and then restore it with: tft.readRect(x, y, 20, 20, img); and tft.pushRect(x, y, 20, 20, img); I have noticed slight distortions. If you do it often enough, the original img is destroyed. I don’t use this THAT much in the project and eventually the screen is refreshed anyway, so I ignored it.

Symptom 2: I wrote a routine to save images to 16/24bpp BMP files and noticed that when I view those images in Windows, the colors are wrong. For example, yellow and orange are visually exactly the same.

Upon investigating, I have found that even readPixel() doesn’t read what you think you wrote on this display. Here a short demonstration

#include <TFT_eSPI.h>                           // Graphics library for display
TFT_eSPI tft = TFT_eSPI();                      // Invoke TFT_eSPI library with default width and height (SPI)
void setup(){
  Serial.begin(115200);
  while((!Serial) & millis() < 5000) delay(1);
  delay(5000);                                  // Anti-bricking delay! Prevent rogue Serial activity from blocking uploads!
  Serial.printf("\r\n\nFirmware:%s %s\r\n\n",   // Add some blank space and the firmware date/time in the Serial monitor
    __DATE__, __TIME__);
  tft.begin();
  tft.fillScreen(TFT_BLACK);
  Serial.printf("Name                 fill read Result\n");
  test("TFT_BLACK",       TFT_BLACK);       // 0x0000
  test("TFT_WHITE",       TFT_WHITE);       // 0xFFFF
  test("TFT_NAVY",        TFT_NAVY);        // 0x000F
  test("TFT_DARKGREEN",   TFT_DARKGREEN);   // 0x03E0
  test("TFT_DARKCYAN",    TFT_DARKCYAN);    // 0x03EF
  test("TFT_MAROON",      TFT_MAROON);      // 0x7800
  test("TFT_PURPLE",      TFT_PURPLE);      // 0x780F
  test("TFT_OLIVE",       TFT_OLIVE);       // 0x7BE0
  test("TFT_LIGHTGREY",   TFT_LIGHTGREY);   // 0xD69A
  test("TFT_DARKGREY",    TFT_DARKGREY);    // 0x7BEF
  test("TFT_BLUE",        TFT_BLUE);        // 0x001F
  test("TFT_GREEN",       TFT_GREEN);       // 0x07E0
  test("TFT_CYAN",        TFT_CYAN);        // 0x07FF
  test("TFT_RED",         TFT_RED);         // 0xF800
  test("TFT_MAGENTA",     TFT_MAGENTA);     // 0xF81F
  test("TFT_YELLOW",      TFT_YELLOW);      // 0xFFE0
  test("TFT_ORANGE",      TFT_ORANGE);      // 0xFDA0
  test("TFT_GREENYELLOW", TFT_GREENYELLOW); // 0xB7E0
  test("TFT_PINK",        TFT_PINK);        // 0xFE19
  test("TFT_BROWN",       TFT_BROWN);       // 0x9A60
  test("TFT_GOLD",        TFT_GOLD);        // 0xFEA0
  test("TFT_SILVER",      TFT_SILVER);      // 0xC618
  test("TFT_SKYBLUE",     TFT_SKYBLUE);     // 0x867D
  test("TFT_VIOLET",      TFT_VIOLET);      // 0x915C
}
void test(char *nam, uint16_t clr){
  tft.fillRect(0,0,5,5,clr);
  uint16_t readpixel = tft.readPixel(2, 2);
  Serial.printf("%-*s %04x %04x",20,nam,clr,readpixel);
  if(!(clr == readpixel)){
    char binClr[20];
    char binPix[20];
    binary(binClr,clr);
    binary(binPix,readpixel);
    Serial.printf(" %s -> %s\n",binClr,binPix);
  } else {
    Serial.printf("\n");
  }
}
void binary(char buffer[], uint16_t val){
  uint16_t mask = 0x8000;
  for(int i = 0; i < 16; i++){
    buffer[i] = mask & val ? '1' : '0';
    mask = mask >> 1;
  }
  buffer[16] = 0;
}

void loop(){}

Which gives the following output:

Name                 fill read Result
TFT_BLACK            0000 0000
TFT_WHITE            ffff ffff
TFT_NAVY             000f 001f 0000000000001111 -> 0000000000011111
TFT_DARKGREEN        03e0 07e0 0000001111100000 -> 0000011111100000
TFT_DARKCYAN         03ef 07ff 0000001111101111 -> 0000011111111111
TFT_MAROON           7800 f800 0111100000000000 -> 1111100000000000
TFT_PURPLE           780f f81f 0111100000001111 -> 1111100000011111
TFT_OLIVE            7be0 ffe0 0111101111100000 -> 1111111111100000
TFT_LIGHTGREY        d69a ffbf 1101011010011010 -> 1111111110111111
TFT_DARKGREY         7bef ffff 0111101111101111 -> 1111111111111111
TFT_BLUE             001f 003f 0000000000011111 -> 0000000000111111
TFT_GREEN            07e0 0fe0 0000011111100000 -> 0000111111100000
TFT_CYAN             07ff 0fff 0000011111111111 -> 0000111111111111
TFT_RED              f800 f801 1111100000000000 -> 1111100000000001
TFT_MAGENTA          f81f f83f 1111100000011111 -> 1111100000111111
TFT_YELLOW           ffe0 ffe1 1111111111100000 -> 1111111111100001
TFT_ORANGE           fda0 ffe1 1111110110100000 -> 1111111111100001
TFT_GREENYELLOW      b7e0 ffe1 1011011111100000 -> 1111111111100001
TFT_PINK             fe19 fe3b 1111111000011001 -> 1111111000111011
TFT_BROWN            9a60 bee1 1001101001100000 -> 1011111011100001
TFT_GOLD             fea0 ffe1 1111111010100000 -> 1111111111100001
TFT_SILVER           c618 ce39 1100011000011000 -> 1100111000111001
TFT_SKYBLUE          867d 8eff 1000011001111101 -> 1000111011111111
TFT_VIOLET           915c b3fd 1001000101011100 -> 1011001111111101

As you can see, when I fill an area with TFT_NAVY (0x000f), what readPixel() returns has an extra 1 (0x001f). The values appear to be shifted one bit. In some cases, the difference is radical. (TFT_GREENYELLOW, b7e0, ffe1)

I’ve poked in the library but haven’t found a bug yet. For the ST7796, it reads two bytes and returns that value. Can’t much go wrong there. Then it occurred to me that there may be something in the hardware that’s askew so I grabbed the datasheet and have come up with a suspicion:

The ST7796 supports multiple bit modes (page 153). Is it possible that this library THINKS it should be reading 16 bits but the hardware is actually in 18 or 24 bit mode - even though the 16bit writes produce the right colors?

I don’t think I can take this any further. Can anyone confirm the problem and maybe suggest how I can get it fixed?

If everything has a pre-fixed "1", perhaps the shifting in the library "read" is not correct (or the presentation).

Does Adafruit's GFX and ST7796 libraries have a similar readArea() or readPixel()? I have been reading (but do not know of) shortcomings of TFT_eSPI.

After digging a lot deeper, this is what I have learned. The datasheet says (page 7) the chip uses 18bits per pixel internally. The datasheet shows a command (3Ah, page 190) Interface Pixel Format and it is used from within ST7796_Init.h to set 16 bit mode.

There is also a command (0Ch) Read Display Pixel Format (page 152-153) that would appear to set the format for reading pixels. 16 bit is an option. It is referenced in ST7796_Defines.h to create a #define ST7796_RDPIXFMT but then that define is apparently never used. I tried issuing this command myself but it didn’t make any difference and I may not be doing it right at all.

However, I’m not sure my theory of using one resolution to write but reading in another even holds up because the bits overlap when reading pixels. The same pixels are sometimes 1 when getting a blue pixel but also when getting red. Or blue and green. Or green and red. If the pixels were just in the wrong place, I would expect them to at least stay in that wrong place and not move around.

I will see if I can try a different library and see if the results are the same but that will take time because I am in the middle of this project (and bogged down!).

Thanks for showing interest!

Find the pattern of color-vs-prefixbit, correct the bits, re-write-to-display()

What?

The pattern of bits is already known and the display is correct. It’s the results from readPixel() that are corked. For example, readPixel() gives exactly the same results regardless if the pixel is orange or yellow.

I have spent hours analyzing what I get vs. what I should be getting and there is no way to correct the bits unless you know what they were supposed to be in the first place but a screenshot for instance has no knowledge of what the screen is supposed to be displaying which is why it uses readPixel() in the first place.

Yes, you show/know the pattern of inaccuracy. When reading and re-writing... read, correct, re-write.

This is, I believe unsolvable so I will close the issue.

The data reported by readPixel here is corrupt and cannot be fixed. Each bit of what should be a rrrrrggggggbbbbb pattern bleeds to it’s neighbor to the left. If you should get 00000000 0000001, you will get 00000000 00000011. If you should get 10000000 00000000, you will get 10000000 00000001 (the bleed rolls over).

This type of corruption is most often the result of timing issues but I reduced the setting #define SPI_READ_FREQUENCY all the way to 1MHz and nothing changed.