ESP32, ST7735 and TFT_eSPI / TFT libraries

Hello,

I am still trying out my new ESP32 which I would like to hook up to an ST7735 1.8'' TFT.

I have just installed the latest TFT_eSPI and TFT libraries from Bodmer on Github, but I can't get the code to compile.

Everything else compiles fine in my code, but when I put tft.initR(INITR_18GREENTAB); in my setup() section, I get this error:

D:\Arduino\HeadUnitESP32\HeadUnitESP32.ino: In function 'void setup()':

HeadUnitESP32:53: error: 'INITR_18GREENTAB' was not declared in this scope

   tft.initR(INITR_18GREENTAB);

             ^

exit status 1
'INITR_18GREENTAB' was not declared in this scope

How do I initialize the display using these libraries?

I've attached my code (with which I was simply going to test out the ESP32 together with the TFT a little bit) to this post in a zip file.

HeadUnitESP32.zip (11.7 KB)

tft.initR(INITR_18GREENTAB);

Where did you get the idea to use this code with those libraries? My search indicates INITR_18GREENTAB is from a different library:

carguy:
How do I initialize the display using these libraries?

Did you check the example sketches included with the libraries (find them in the File > Examples menu).

Configure User_setup.h in the TFT_eSPI library for your ESP32 and your ST7735

ok I have updated my sketch to correctly initialize the screen with these libraries. And I have changed the User_Setup.h in the TFT library.

That has somehow made everything much worse, and these are the errors I get now:

C:\Program Files\Arduino\libraries\TFT\src\utility\Adafruit_ST7735.cpp: In member function 'void Adafruit_ST7735::commonInit(const uint8_t*)':

C:\Program Files\Arduino\libraries\TFT\src\utility\Adafruit_ST7735.cpp:349:13: error: cannot convert 'volatile uint32_t* {aka volatile unsigned int*}' to 'volatile uint8_t* {aka volatile unsigned char*}' in assignment

   csport    = portOutputRegister(digitalPinToPort(_cs));

             ^

C:\Program Files\Arduino\libraries\TFT\src\utility\Adafruit_ST7735.cpp:351:13: error: cannot convert 'volatile uint32_t* {aka volatile unsigned int*}' to 'volatile uint8_t* {aka volatile unsigned char*}' in assignment

   rsport    = portOutputRegister(digitalPinToPort(_rs));

             ^

C:\Program Files\Arduino\libraries\TFT\src\utility\Adafruit_ST7735.cpp:370:17: error: cannot convert 'volatile uint32_t* {aka volatile unsigned int*}' to 'volatile uint8_t* {aka volatile unsigned char*}' in assignment

     clkport     = portOutputRegister(digitalPinToPort(_sclk));

                 ^

C:\Program Files\Arduino\libraries\TFT\src\utility\Adafruit_ST7735.cpp:372:17: error: cannot convert 'volatile uint32_t* {aka volatile unsigned int*}' to 'volatile uint8_t* {aka volatile unsigned char*}' in assignment

     dataport    = portOutputRegister(digitalPinToPort(_sid));

                 ^

exit status 1

I have attached the changed User_Setup.h and my updated sketch to this post.

User_Setup.h (8.4 KB)

HeadUnitESP32.zip (11.7 KB)

It looks as if you have configured User_Setup.h for your specific hardware.
I would test it with the library examples e.g. TFT_flash_jpg to check the picture colours, quality etc.

Running library examples show how to use a particular library. e.g. what header files to include, what constructors to use, ...

Then I tried to build your sketch.
Why have you included TFT.h ? That is an AVR specific library that is nothing to do with TFT_eSPI.h

When I removed the TFT.h line, your sketch showed some icons that did not look very good on my display.

David.

@carguy

The TFT_eSPI library does not need the TFT library so that is probably the source of the error.

The TFT_eSPI library now has a function to draw FLASH bitmaps to the screen which will probably draw 8x faster than the sketch function, so you can also use that.

Here is a trimmed down version of your sketch (excluding your image header files) that works OK on my display:

#include <TFT_eSPI.h>

#include "header.h"
#include "frost.h"
#include "car.h"

TFT_eSPI tft = TFT_eSPI();

#define dimmerPin 33

void setup() {

  Serial.begin(115200);
  Serial.println("Serial connection OK.");

  tft.init();
  tft.setRotation(1);
  tft.fillScreen(0x20c0);

  ledcSetup(0, 5000, 8);
  ledcAttachPin(dimmerPin, 0);
  ledcWrite(0, 255);

  tft.pushImage(0,  0, 128, 33, header);
  tft.pushImage(0, 34, 128, 24, frost);
  tft.pushImage(0, 35, 128, 70, car);

  tft.fillRect(8, 130, 112, 2, 0xfa28);
}

void loop() {
  // put your main code here, to run repeatedly:

}

ok, I can confirm that the flash images now display lightning fast. :slight_smile: This will also help with my bespoke font libraries which I am creating at the moment. My first experiment with that is included in the attachments at the bottom of this post.

David: I've adjusted the User_Setup.h for my display, which turns out to be a "GREENTAB2" display. Now the colors are displayed correctly, except the rows of the flash images aren't right, and there is a margin of garbled pixels to the left:

This problem persists whether it's in "0" or "2" rotation mode.

In landscape mode (rotation set to "1"), the icons are displayed correctly, for whatever reason, but I've still got a garbled margin:

I would like the display to be in rotation mode "2", because the display of this trip computer will be mounted in the top of the center console, and the usual angle from which I will be looking at it (in an LHD car) will be from the top left, more or less. And on this display, the colors appear the most stable at such an angle when the rotation is set to "2".

I've made these icons with LCD Image Converter, and I'm not aware that I would have got that part wrong. I've included the three icons in a zip folder at the bottom of this post with everything else.

I'm not sure yet how to use my custom font libraries; the general idea will be to have a function like this:

void printMyText(String myText, String fontName, byte x_start, byte y_start)

Within this function, the text string will then be broken up into its individual characters and the characters will then be matched against the elements in const charData_SF19PtRed SF19PtRed[][] at the bottom there, and then the correct character pixel information will be retrieved from the const uint16_t SF19PtRed[][] above and pushed onto the TFT character by character.

I'm not sure yet how to search for the correct character; do I have to loop through const charData_SF19PtRed[][] each time for each character? I don't know...

But I guess it's a good idea to first get the "garbled pixels" issues sorted.

HeadUnitESP32.zip (29.1 KB)

SF19PtRed.h (72.4 KB)

I think that Bodmer offers you several choices for the initialisation. e.g. ST7735_BLACKTAB, ST7735_GREENTAB, ...

I suggest that you try all the permutations.
One of them will give correct colours, margins etc.

Bodmer also provides a diagnosis skectch to read the ID or at least determine whether you have a ST7735S.

The ESP32 (and ESP8266) work very nicely with SPI displays.

Rendering an anti-aliased Font should be pretty straightforward. It just means using a 4-colour or 16-colour bitmap instead of the regular 2-colour monochrome Font bitmaps.

David.

ok I just figured out that my GREENTAB is actually a BLACKTAB :slight_smile:

It works perfectly now.

The next thing I will try tonight is to get my experimental font to display something on the screen.

So wth the initial niggles out of the way, I have now moved on to a font library and a function that puts writing into 16-bit RGB565 bitmaps.

This is not doing what it should at the moment and produces a number of compile errors, it will need a fair bit more work. But maybe you guys will have a few ideas:

void printMyText(String myText, String fontName, byte x_start, byte y_start) {

  int textLength = myText.length();
  char textCopy[50];
  myText.toCharArray(textCopy, 50);

  uint16_t imageData;

  byte startX = x_start;
  byte startY = y_start;
  byte x_width;
  byte y_height;


  boolean matchFound = false;

  for (int i = 0; i <= textLength; i++) {

    int characterTableLength =  sizeof(NG20ptLightRed_coords) / sizeof(NG20ptLightRed_coords[0]);

    for (int j = 0; j <= characterTableLength; j++) {

      //Looking up matching character in character table "NG20ptLightRed_coords[][]

      char currentChar[1];
      currentChar[0] =  NG20ptLightRed_coords[j][0];

      if (textCopy[i] == currentChar[0] ) {

        // if match found...

        matchFound = true;
        imageData = NG20ptLightRed[i];

        x_width = NG20ptLightRed_coords[j][1];
        y_height = NG20ptLightRed_coords[j][2];

        // ... print the character out on the screen at the desired pixel coordinates

        tft.setAddrWindow(startX, startY, startX + y_width - 1, startY + y_height - 1);

        int count = 0;

        for (uint16_t k = 0; k < height; k++) {
          for (uint16_t l = 0; l < width; l++) {

            tft.pushColor(pgm_read_word(&NG20ptLightRed[j][count++]));
          }
        }

        startX += (x_width + 1);
        startY += (y_height + 1);
      }

      // If a character is not found in coordinates table, print a blank character

      if (textCopy[i] && j == characterTableLength && !matchFound) {

        tft.fillRect(startX, startY, x_width, y_height, 0x20c0);
        matchFound = false;
      }
    }
  }
}

This is a rudimentary function for now; later when it is finished, I want it to be able to use a number of different fonts.

The font library .h file that is used in this sketch is included in the zip file below.

I take it I can't just use tft.pushImage() in this function, as I would have no array name to reference for a particular character in my function arguments the way my font library is set up at the moment.

But see for yourselves in the attachment.

HeadUnitESP32.zip (18 KB)

Please use consistent names for your ZIP file(s).

Where did you get your "Font" from?
Where did you get your "converter" program from?

As a general rule, fonts in embedded systems consist of a table that lists number of letters and general flags and tables for width, height, glyph_width, glyph_height, glyph_offset, bitmap_position for each letter.

Several schemes are used for minimising the storage size of the bitmaps. e.g just storing the glyph rather than the whole rectangle reserved for the letter.

To render a letter, you look up in the tables to find the address of the bitmap, dimensions etc.
And draw the bitmap in the required place on your screen.

It is pretty straightforward but gets a little fiddly if some tables are in SRAM, others in Flash. e.g. on an AVR.

Provide some links. Then we can show you what to do.

But the general strategy is the same. i.e. look up the letter. Find the memory address of the bitmap. Drawing a monochrome bitmap or a 16-colour bitmap is trivial.

David.

As it says in the header information at the top of the file "NG20ptLightRed", the font was created using LCD Image Converter, which is a software project by "riuson", who is also a regular on this forum.

http://www.riuson.com/lcd-image-converter

It allows you to create your own templates to format the information in your .h output file, and that's what I did.

Instead of having one single continuous pane of RGB565 pixels and then accessing the characters by their offset position, I have tried out the approach of letting the array with all the pixel information be a two-dimensional array, with every character occupying one sub-array. That's what you see at the beginning of the .h file in the array const uint16_t NG20ptLightRed[69][323] .

And then there's a struct/array at the very bottom of that file that lists all the characters in my character set (I will be working with a "reduced" character set, which will only contain the characters +,-./0123456789:;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz). This array also contains the height and width of each character as the second and third respective sub-array element.

The basic idea is to go through the string that I want to print out as bitmap characters by matching every letter of my string against the characters as found in the subarrays of const charData_NG20ptLightRed NG20ptLightRed_coords[69][3] .

My problems still with this approach are that I don't quite understand how I can match single letters of the text that I would like to print out against that array.

Here's my printMyText() function in its current form:

void printMyText(String myText, String fontName, byte x_start, byte y_start) {

  int textLength = myText.length();
  byte startX = x_start;
  byte startY = y_start;
  byte x_width;
  byte y_height;
  boolean matchFound = false;

  for (int i = 0; i <= textLength; i++) {

    int characterTableLength =  sizeof(NG20ptLightRed_coords) / sizeof(NG20ptLightRed_coords[0]);

    for (int j = 0; j <= characterTableLength; j++) {

      //Looking up matching character in character table "NG20ptLightRed_coords[][]
      if (myText.charAt(i) == NG20ptLightRed_coords[j][0]) {

        // if match found...
        matchFound = true;
        x_width = NG20ptLightRed_coords[j][1];
        y_height = NG20ptLightRed_coords[j][2];

        // ... print the character out on the screen at the desired pixel coordinates
        tft.setAddrWindow(startX, startY, startX + x_width - 1, startY + y_height - 1);

        int count = 0;

        for (uint16_t k = 0; k < y_height; k++) {
          for (uint16_t l = 0; l < x_width; l++) {

            tft.pushColor(pgm_read_word(&NG20ptLightRed[j][count++]));
          }
        }

        startX += (x_width + 1);
        startY += (y_height + 1);
      }

      // If a character is not found in coordinates table, print a blank character
      if (j == characterTableLength && !matchFound) {

        tft.fillRect(startX, startY, x_width, y_height, 0x20c0);
      }
      matchFound = false;
    }
  }
}

And these are the error messages I am getting:

D:\Arduino\HeadUnitESP32\HeadUnitESP32.ino: In function 'void printMyText(String, String, byte, byte)':

HeadUnitESP32:29: error: no match for 'operator==' (operand types are 'char' and 'const charData_NG20ptLightRed')

       if (myText.charAt(i) == NG20ptLightRed_coords[j][0]) {

                            ^

HeadUnitESP32:33: error: cannot convert 'const charData_NG20ptLightRed' to 'byte {aka unsigned char}' in assignment

         x_width = NG20ptLightRed_coords[j][1];

                 ^

HeadUnitESP32:34: error: cannot convert 'const charData_NG20ptLightRed' to 'byte {aka unsigned char}' in assignment

         y_height = NG20ptLightRed_coords[j][2];

                  ^

D:\Arduino\HeadUnitESP32\HeadUnitESP32.ino: In function 'void setup()':

HeadUnitESP32:92: error: variable or field 'printMyText' declared void

   void printMyText("Frost", "NG20ptLightRed", 40, 28);

                   ^

exit status 1
no match for 'operator==' (operand types are 'char' and 'const charData_NG20ptLightRed')

I'm not entirely sure how to convert the variable types so that I can actually compare and match them.

I've included all the files in a (consistently named) zip file again.

HeadUnitESP32-v1.1.zip (18.1 KB)

I looked at your "Font" and changed some weirdos. e.g.

typedef struct {

    char* character_NG20ptLightRed;
//    uint16_t charData_NG20ptLightRed;   //your inits do not have this field
    byte height_NG20ptLightRed;
    byte width_NG20ptLightRed;
} charData_NG20ptLightRed;

// you had a multi-dimensional array of structs
const charData_NG20ptLightRed NG20ptLightRed_coords[69] = {
    {"+", 19, 17}, {",", 19, 4}, {"-", 19, 7}, {".", 19, 4},
    ...

And then I amended your sketch to :

#include <TFT_eSPI.h>

#include "NG20ptLightRed.h"

#include "header.h"
#include "frost.h"
#include "car.h"

TFT_eSPI tft = TFT_eSPI();

byte printLetter(char c, byte startX, byte startY)
{
    byte idx;
    // look up the character.  Find index of the bitmap etc.
    // most fonts have sequences e.g. ascii ' ' to ascii '}'
    // this means you can calculate the index immediately
    for (idx = 0; idx < 69; idx++) {
        if (NG20ptLightRed_coords[idx].character_NG20ptLightRed[0] == c)
            break;
    }
    byte x_width = NG20ptLightRed_coords[idx].width_NG20ptLightRed;
    byte y_height = NG20ptLightRed_coords[idx].height_NG20ptLightRed;
    uint16_t *imageData = (uint16_t*)NG20ptLightRed[idx];
    tft.setAddrWindow(startX, startY, startX + x_width - 1, startY + y_height - 1);
    tft.pushColors(imageData, x_width * y_height);
    return x_width;
}

byte printString(char *p, byte x_start, byte y_start)
{
    char c;
    while (c = *p++) {
        x_start += printLetter(c, x_start, y_start);
    }
}

#define dimmerPin 33
uint16_t buf[128 * 70];   //so we can build bitmaps in the correct endian

void pushImage(uint32_t x0, uint32_t y0, uint32_t w, uint32_t h, uint16_t *data)
{
    for (uint32_t i = 0; i < w * h; i++) {
        uint16_t x = data[i];
        buf[i] = (x >> 8) | (x << 8);
    }
    tft.pushRect(x0, y0, w, h, buf);

}

void setup() 
{
    Serial.begin(115200);
    Serial.println("Serial connection OK.");

    tft.init();
    tft.setRotation(2);
    tft.fillScreen(0x20c0);

    ledcSetup(0, 5000, 8);
    ledcAttachPin(dimmerPin, 0);
    ledcWrite(0, 255);

    delay(2000);
    // your bitmaps are in the wrong endian
    tft.pushRect(0,  0, 128, 33, (uint16_t*)header);
    //    tft.pushRect(0, 34, 128, 24, (uint16_t*)frost);
    tft.pushRect(0, 58, 128, 70, (uint16_t*)car);

    tft.fillRect(8, 130, 112, 2, 0xfa28);

    printString("Frost", 40, 28);
    delay(5000);

    // call helper function to change endian
    pushImage(0,  0, 128, 33, (uint16_t*)header);
    pushImage(0, 58, 128, 70, (uint16_t*)car);

}

void loop() 
{
}

I know that the ESP32 has got oodles of Flash. But your "Font" is unbelievably inefficient for storage.
Oh, and the text rendering is not that impressive.

Yes, I could tidy up the Font access and make it understand AVR PROGMEM.

David.

Mmm... well there were a lot of logical, flow and syntax errors but I have probably fixed most of them!

You can use pushImage, see modded code, as it just needs a pointer to the FLASH, an array name is an address pointer. To point to an array with [index] it is a case of converting it to an address e.g. &name[25]

carguy.zip (18.2 KB)

Thanks everybody so far,

I will have a more detailed look at your posts and your code as soon as I get home (I'm at another computer right now where I don't have access to my Arduino stuff).

I will also contact riuson again, the guy who made the LCD Image Converter, and ask him if there is a way I can put offset information for all the characters in my struct array.

Technically, it's not difficult at all using a custom template file within LCD Image Converter to have the RGB565 pixel information put out any way you like, meaning it's also possible to put it in one continuous pane within a one-dimensional array.

What I wasn't quite able to figure out is how to then put the pixel offset information for the individual characters/glyphs in the second array. So I am going to have to ask riuson what I have to state in my template file to achieve that.

This would probably shrink down the effective memory size of my font arrays, won't it?

Also, LCD Image Converter lets you choose freely between big-endian and little-endian, so I would just have to toggle that setting and then generate a new file with the correct-endian pixel definitions.

I could not make head nor tail of the Image_Converter program.
It says that it can do Grayscale and Colour. But all that happens is that it creates a 4-bit color bitmap with monochrome entries or a 16-bit color bitmap with monochrome entries.

A massive waste of memory.

I don't see how to produce an anti-aliased font that actually means anything.

If you can create something worthwhile, I would be very interested in rendering it.
After all, your anti-aliased graphics look very impressive. Sometimes anti-aliased fonts look good and sometimes not so good.

Oh, I can probably process whatever the Image Converter spits out. But it would be nicer to let it do the work.

David.

Again, I'm not at my computer at home right now, but LCD Image Converter allows you to create custom color profiles, one of them being RGB565 where the pixel data is put out straight into "code-useable" uint16_t color information.

The software does not appear to come with a built-in RGB565 profile, I believe riuson once made me a color profile template on this forum for this purpose. When I'm back home, maybe I will post that template here.

All my other icons I made for this test sketch were also converted into RGB565 using LCD Image Converter (the graphics themselves were done with GIMP). LCD Image Converter is really a nifty little program once you get the hang of it, I haven't found anything like it from other creators. And it's very variable, as I said, besides the mentioned color profile templates, you can create your own output templates where you can specify what you want your .h file to look like.

You can do a search and replace of "}, {" with "," on the font bitmap array ONLY to make it one dimension. Also delette the first and last braces {}, see attached. This will save about 22kbytes of FLASH which considering the resources available is not a lot but there may be performance advantages for larger fonts as the FLASH is cached in RAM and comes from an SPI FLASH device.

As you need to scan for the character you can move a pointer, to calculate on-the-fly where the next bitmap is by adding "width x height" for each character. As you have lots of computing horsepower that makes a negligible performance change and I doubt you will update screen text often. Attached uses this approach and thus does not need a pointer lookup table.

carguy2.zip (18.2 KB)

back home...

@David: I've attached the color profile template that riuson made for me. You can load it into LCD Image Converter via File ---> Open...

In the Options ---> Conversion dialogue, you can then select "carguy-r5g6b5" in the Preset drop-down, or if it doesn't show up there, click on "Import..." in that same dialogue and import the profile that way.

Even if you're doing a font, the "Image" tab will still apply to your font, and you can make adjustments and fine tune the color profile and the eventual output.

Within the "template" tab, you can define how you want your output code in your .h file formatted. You can specify a text file of your choosing. The font.tmpl and image.tmpl files in the program folder are one template you can choose.

@bodmer & David: I will have thorough look at the code you guys posted later tonight. I have some urgent non-Arduino stuff to do right now... ergh...

preset-carguy-r5g6b5.zip (775 Bytes)

I am going to have to postpone a thorough go-through of your code until tomorrow, as it's 12:56 am here now.

One thing I did do briefly tonight was fiddle with LCD Image Converter some more. I was able to modify the .h output template so that it now gives all the desired characters in one continuous one-dimensional pane. And in the charData array at the bottom, I was able to at least print out the total block size for each character (although that could probably also have been had by simply multiplying the known width by height for each character).

Maybe there is a way to add up the block sizes continuously within the font.tmpl file so that they will show up in the charData array as actual offset reference values, I don't know. I am going to have to contact riuson to find out. It would also be nice to be able to state the total number of array elements. Not sure if there are commands within the software that can do that.

I have included both a new .h font output file generated with LCD Image converter, as well as the font.tmpl file that I have used to generate this font file. As I said, the font.tmpl file can be imported into LCD Image Converter via Options ---> Conversion... ---> Templates.

carguy-font-files.zip (6.5 KB)