Help Mirroring TFT LCD with MCUFRIEND library

Hey there,

I have been struggling to solve an issue that I think is both unique and not unique at the same time. I have a Mega 2560 with an ELEGOO 2.8" TFT LCD (linked below), and it works perfectly with the MCUFRIEND library. However, I would like to horizontally mirror the display, as if you were looking at it through a mirror.

I want to accomplish this because I am trying to build a HUD for my car, but I want to use this LCD instead of 7 segment HUD display. I have seen many forum posts of other people trying to get their display to be unmirrored, but I had no luck trying to follow the same steps in reverse.
I also created a custom GFX font with every character backwards, and that worked great until I needed text with background color, (WHITE,BLACK) for example. I found out the long way that GFX fonts do not display the background color, only the system font :confused:

I was hoping someone who is more knowledgeable than me could chime in with a solution to help me get my display mirrored. I am also willing to buy a new display if it's not possible with this one, although I feel it should be possible with some change in the library code.

I ran the LCD_ID_readreg example sketch and it returned these results;

Read Registers on MCUFRIEND UNO shield
controllers either read as single 16-bit
e.g. the ID is at readReg(0)
or as a sequence of 8-bit values
in special locations (first is dummy)

reg(0x0000) 00 00	ID: ILI9320, ILI9325, ILI9335, ...
reg(0x0004) 00 00 00 00	Manufacturer ID
reg(0x0009) 00 00 61 00 00	Status Register
reg(0x000A) 00 08	Get Power Mode
reg(0x000C) 00 06	Get Pixel Format
reg(0x0061) 00 00	RDID1 HX8347-G
reg(0x0062) 00 00	RDID2 HX8347-G
reg(0x0063) 00 00	RDID3 HX8347-G
reg(0x0064) 00 00	RDID1 HX8347-A
reg(0x0065) 00 00	RDID2 HX8347-A
reg(0x0066) 00 00	RDID3 HX8347-A
reg(0x0067) 00 00	RDID Himax HX8347-A
reg(0x0070) 00 00	Panel Himax HX8347-A
reg(0x00A1) 00 00 00 00 00	RD_DDB SSD1963
reg(0x00B0) 00 00	RGB Interface Signal Control
reg(0x00B4) 00 02	Inversion Control
reg(0x00B6) 00 0A 82 27 04	Display Control
reg(0x00B7) 00 06	Entry Mode Set
reg(0x00BF) 00 00 00 00 00 00	ILI9481, HX8357-B
reg(0x00C0) 00 21 00 00 00 00 00 00 00	Panel Control
reg(0x00C8) 00 00 00 00 00 00 00 00 00 00 00 00 00	GAMMA
reg(0x00CC) 00 70	Panel Control
reg(0x00D0) 00 00 00	Power Control
reg(0x00D2) 00 00 00 02 02	NVM Read
reg(0x00D3) 00 00 93 41	ILI9341, ILI9488
reg(0x00D4) 00 00 00 00	Novatek ID
reg(0x00DA) 00 00	RDID1
reg(0x00DB) 00 00	RDID2
reg(0x00DC) 00 00	RDID3
reg(0x00E0) 00 0F 16 14 0A 0D 06 43 75 33 06 0E 00 0C 09 08	GAMMA-P
reg(0x00E1) 00 08 2B 2D 04 10 04 3E 24 4E 04 0F 0E 35 38 0F	GAMMA-N
reg(0x00EF) 00 03 80 02 02 02	ILI9327
reg(0x00F2) 00 02 02 02 02 02 02 02 02 02 02 02	Adjust Control 2
reg(0x00F6) 00 01 00 00	Interface Control

I'm not sure why the ID comes back as all 0's, as for the graphicstest_kbv sketch displays the ID as ILI9341 (0x9341).

This is the screen I am using, it is an ELEGOO 2.8" TFT LCD;

If anyone could help me out it would be much appreciated!!

That is not quite true, adafruit_GFX.h does the font display, and does show background color, but only for the default font to prevent spaced fonts from causing issues, you can modify adafruit_GFX to do that for any font (basically you remove the condition that tests for it)

again a modified version of adafruit_GFX will probably do the trick the easiest.

Start out with making a copy of adafruit_GFX.h & .cpp with a different name (and in a different folder) , this means also to modify the .cpp file to include the new .h file, and the #IFDEF at the top of the .h file to make sure it is only included once.
then first start looking for the font printing function

void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color,
                uint16_t bg, uint8_t size);
  void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color,
                uint16_t bg, uint8_t size_x, uint8_t size_y);

these do the work (from .h)

void Adafruit_GFX::drawChar(int16_t x, int16_t y, unsigned char c,
                            uint16_t color, uint16_t bg, uint8_t size_x,
                            uint8_t size_y) {

  if (!gfxFont) { // 'Classic' built-in font

    if ((x >= _width) ||              // Clip right
        (y >= _height) ||             // Clip bottom
        ((x + 6 * size_x - 1) < 0) || // Clip left
        ((y + 8 * size_y - 1) < 0))   // Clip top
      return;

    if (!_cp437 && (c >= 176))
      c++; // Handle 'classic' charset behavior

    startWrite();
    for (int8_t i = 0; i < 5; i++) { // Char bitmap = 5 columns
      uint8_t line = pgm_read_byte(&font[c * 5 + i]);
      for (int8_t j = 0; j < 8; j++, line >>= 1) {
        if (line & 1) {
          if (size_x == 1 && size_y == 1)
            writePixel(x + i, y + j, color);
          else
            writeFillRect(x + i * size_x, y + j * size_y, size_x, size_y,
                          color);
        } else if (bg != color) {
          if (size_x == 1 && size_y == 1)
            writePixel(x + i, y + j, bg);
          else
            writeFillRect(x + i * size_x, y + j * size_y, size_x, size_y, bg);
        }
      }
    }
    if (bg != color) { // If opaque, draw vertical line for last column
      if (size_x == 1 && size_y == 1)
        writeFastVLine(x + 5, y, 8, bg);
      else
        writeFillRect(x + 5 * size_x, y, size_x, 8 * size_y, bg);
    }
    endWrite();

  } else { // Custom font

    // Character is assumed previously filtered by write() to eliminate
    // newlines, returns, non-printable characters, etc.  Calling
    // drawChar() directly with 'bad' characters of font may cause mayhem!

    c -= (uint8_t)pgm_read_byte(&gfxFont->first);
    GFXglyph *glyph = pgm_read_glyph_ptr(gfxFont, c);
    uint8_t *bitmap = pgm_read_bitmap_ptr(gfxFont);

    uint16_t bo = pgm_read_word(&glyph->bitmapOffset);
    uint8_t w = pgm_read_byte(&glyph->width), h = pgm_read_byte(&glyph->height);
    int8_t xo = pgm_read_byte(&glyph->xOffset),
           yo = pgm_read_byte(&glyph->yOffset);
    uint8_t xx, yy, bits = 0, bit = 0;
    int16_t xo16 = 0, yo16 = 0;

    if (size_x > 1 || size_y > 1) {
      xo16 = xo;
      yo16 = yo;
    }

    // Todo: Add character clipping here

    // NOTE: THERE IS NO 'BACKGROUND' COLOR OPTION ON CUSTOM FONTS.
    // THIS IS ON PURPOSE AND BY DESIGN.  The background color feature
    // has typically been used with the 'classic' font to overwrite old
    // screen contents with new data.  This ONLY works because the
    // characters are a uniform size; it's not a sensible thing to do with
    // proportionally-spaced fonts with glyphs of varying sizes (and that
    // may overlap).  To replace previously-drawn text when using a custom
    // font, use the getTextBounds() function to determine the smallest
    // rectangle encompassing a string, erase the area with fillRect(),
    // then draw new text.  This WILL infortunately 'blink' the text, but
    // is unavoidable.  Drawing 'background' pixels will NOT fix this,
    // only creates a new set of problems.  Have an idea to work around
    // this (a canvas object type for MCUs that can afford the RAM and
    // displays supporting setAddrWindow() and pushColors()), but haven't
    // implemented this yet.

    startWrite();
    for (yy = 0; yy < h; yy++) {
      for (xx = 0; xx < w; xx++) {
        if (!(bit++ & 7)) {
          bits = pgm_read_byte(&bitmap[bo++]);
        }
        if (bits & 0x80) {
          if (size_x == 1 && size_y == 1) {
            writePixel(x + xo + xx, y + yo + yy, color);
          } else {
            writeFillRect(x + (xo16 + xx) * size_x, y + (yo16 + yy) * size_y,
                          size_x, size_y, color);
          }
        }
        bits <<= 1;
      }
    }
    endWrite();

  } // End classic vs custom font
}

this is the section that is omitted from the custom font section

 else if (bg != color) {
          if (size_x == 1 && size_y == 1)
            writePixel(x + i, y + j, bg);
          else
            writeFillRect(x + i * size_x, y + j * size_y, size_x, size_y, bg);
        }

(there may be a few more things like that, i am just having a quick look)
as for the inversion, all functions from inside adafruit_GFX call this internal function to wirte a pixel

void Adafruit_GFX::writePixel(int16_t x, int16_t y, uint16_t color) {
  drawPixel(x, y, color);
}

which in turn call the virtual void drawPixel(), i think it is obvious what you can do, and you won't need the custom font anymore.

void Adafruit_GFX::writePixel(int16_t x, int16_t y, uint16_t color) {
  drawPixel(_width - x - 1, y, color);
}

should do the trick.

Hey Deva_Rishi,

Thanks for the input. I probably shouldn't have included the part about the custom font, because in reality the default font is what I really want to use.
That being said, I tried changing the drawPixel function in the .cpp file as you said, and I got some interesting results running a test sketch;

Any text that is not size 1 was unaffected by the change.

Text size 1 was mirrored, but the spaces in the black background were not:

Do you have an idea of how to make the change apply to all text sizes and the full text background?

Well having a look at it, rather than calling writePixel() it calls writeFillRect()

for (int8_t i = 0; i < 5; i++) { // Char bitmap = 5 columns
      uint8_t line = pgm_read_byte(&font[c * 5 + i]);
      for (int8_t j = 0; j < 8; j++, line >>= 1) {
        if (line & 1) {
          if (size_x == 1 && size_y == 1)
            writePixel(x + i, y + j, color);
          else
            writeFillRect(x + i * size_x, y + j * size_y, size_x, size_y,
                          color);
        } else if (bg != color) {
          if (size_x == 1 && size_y == 1)
            writePixel(x + i, y + j, bg);
          else
            writeFillRect(x + i * size_x, y + j * size_y, size_x, size_y, bg);
        }
      }
    }

filling a small block like that, now writeFillRect() looks like this

void Adafruit_GFX::writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h,
                                 uint16_t color) {
  // Overwrite in subclasses if desired!
  fillRect(x, y, w, h, color);
}

so

void Adafruit_GFX::fillRect(int16_t x, int16_t y, int16_t w, int16_t h,
                            uint16_t color) {
  startWrite();
  for (int16_t i = x; i < x + w; i++) {
    writeFastVLine(i, y, h, color);
  }
  endWrite();
}

and

void Adafruit_GFX::writeFastVLine(int16_t x, int16_t y, int16_t h,
                                  uint16_t color) {
  // Overwrite in subclasses if startWrite is defined!
  // Can be just writeLine(x, y, x, y+h-1, color);
  // or writeFillRect(x, y, 1, h, color);
  drawFastVLine(x, y, h, color);
}

well these referrals are getting annoying, but i am thinking that the reason may be that the shield specific functions have faster functions for some of these.
anyway

void Adafruit_GFX::drawFastVLine(int16_t x, int16_t y, int16_t h,
                                 uint16_t color) {
  startWrite();
  writeLine(x, y, x, y + h - 1, color);
  endWrite();
}

and since

void Adafruit_GFX::writeLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
                             uint16_t color) {
#if defined(ESP8266)
  yield();
#endif
  int16_t steep = abs(y1 - y0) > abs(x1 - x0);
  if (steep) {
    _swap_int16_t(x0, y0);
    _swap_int16_t(x1, y1);
  }

  if (x0 > x1) {
    _swap_int16_t(x0, x1);
    _swap_int16_t(y0, y1);
  }

  int16_t dx, dy;
  dx = x1 - x0;
  dy = abs(y1 - y0);

  int16_t err = dx / 2;
  int16_t ystep;

  if (y0 < y1) {
    ystep = 1;
  } else {
    ystep = -1;
  }

  for (; x0 <= x1; x0++) {
    if (steep) {
      writePixel(y0, x0, color);
    } else {
      writePixel(x0, y0, color);
    }
    err -= dy;
    if (err < 0) {
      y0 += ystep;
      err += dx;
    }
  }
}

actually calls writePixel() there must be a deviation somewhere, the display may have a faster function for writing a line built in somewhere along the way, or the class running the display does.
The easiest way to circumvent this would be to cut in in the drawChar function yourself, by replacing

writeFillRect(x + i * size_x, y + j * size_y, size_x, size_y,
                          color);

with a simple function that just runs a double loop and calling writePixel(), size(square) times.
I am wondering if the lines in between actually are printed in the correct spot in that case, regardless of the size, in the end this also calls writeLine()

if (bg != color) { // If opaque, draw vertical line for last column
      if (size_x == 1 && size_y == 1)
        writeFastVLine(x + 5, y, 8, bg);
      else
        writeFillRect(x + 5 * size_x, y, size_x, 8 * size_y, bg);
    }

Anyway, there are parts in the library which allow creators of libraries to use faster functions for certain things.
Basically quicker methods to draw a line. If you know which function is overwritten that may help in getting consistent behavior.

You should probably consider leaving wirtePixel(0 in it's original form and just modifying the x coordinate in drawChar(), which would result in consistent behavior, with just the characters being mirrored but any lines and other shaped not.
What i suggested with writePixel() may not result in consistent behavior (actually for now it doesn't ) unless you find out what function is overridden in the shield specific part of the code.


Ah I have got it working, thank you so much Deva_Rishi!

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.