GDSTx - Teensy4.1 - Struggling to display correctly

Maveryck.G

New member
Hi everyone !
Thanks you to take the time to read me.
This is my first post, I came here in hope to get some help..

As the title suggests, I'm having some pain to get a working display, using the GDSTx lib.
I'm using the Teensy4.1 board, with a 4.3" Gen4-FT813-43CTP-CLB from 4DSystems. See the datasheet linked below :
Datasheet 4DSystem GEN4-FT813-43CTP-CLB

So here is the problem :
First time I'm calling a "draw loop/draw call" (I don't know how to call this) in the setup(), everything works fine.
But as soon as I want to display anything else from the loop() (like code from the examples), I can only get "tearing" or "blurry" image, displaying badly, or even don't displaying at all, and after a time, i get a "nuke" screen : Invalid Operation...

I know this specific screen is currently not fully supported by the library (its not in the "SizeEVE" list of display, in the config.h file), but I manage to get a "Hello World" working by using the "SizeEVE == 43" setting. Witch should correspond to the NHD FT813 4.3" if I recall correctly.
So,I'm thinking of a timing issue, or something like this. Unfortunaly, the manufacturer does not provide a "Timing table" nor the specifics values for the timing registers REG_HSYNC, REG_HOFFSET, REG_VSYNC, etc...
Timing information are provided.. But I'm unable to understand how to use them.. According to FTDI Documentation, i've understood that register value are related to wich is shown in the datasheet, But I don't understand precisly how to set them up.. Like why is there "minium/maximum/typical".. I'm kind of lost...

Anyway, if someone has some clue, I'll be glad to read !!

So, Let me show you the details :
Here is the situation where everything is ok :
C++:
void setup() {
    Serial.begin(115200);
    delay(100);
    D_LOGLN("*** Entering Setup Sequence ***")
   
    // Configure Pinmode
        // Digital Outputs
    pinMode(FT813_CS_PIN,     OUTPUT);
    pinMode(FT813_PD_PIN,     OUTPUT);
    pinMode(FT813_INT_PIN,     INPUT);
    digitalWrite(FT813_PD_PIN, HIGH);
    digitalWrite(FT813_INT_PIN, HIGH);

    // Init SPI
    SPI.begin();

    // Init Display
    initUi();
    delay(1000);
}

// Copy/pasted from example file
void initUi()
{
    GD.begin();
   
    GD.ClearColorRGB(0,0,0);
    GD.Clear();
   
    GD.cmd_text(GD.w/2, GD.h/2, 30, OPT_CENTER, "Hello world");

    // Added my owm rect.
    GD.Rect_Empty(
        16, 16,
        GD.w-32, GD.h-32,
        UI_COLOR_3
    );

    GD.Begin(LINES);
    GD.ColorRGB(UI_COLOR_1);
    GD.Vertex2f(0*16, 0*16);   
    GD.Vertex2f((GD.w)*16, 0*16);  //Superior
    GD.Vertex2f(0*16, (GD.h-1)*16);
    GD.Vertex2f((GD.w)*16, (GD.h-1)*16); //inferior
    GD.Vertex2f(0*16, 0*16);
    GD.Vertex2f(0*16, (GD.h-1)*16); //izquierda
    GD.Vertex2f((GD.w-1)*16, 0*16);
    GD.Vertex2f((GD.w-1)*16, (GD.h-1)*16); //derecha 

    GD.swap();
}

Here is the result : So far everything is fine.
1000057303.jpg


But here is the problem when I call something from the loop.


C++:
void setup() {
    Serial.begin(115200);
    delay(100);
    D_LOGLN("*** Entering Setup Sequence ***")
   
    // Configure Pinmode
        // Digital Outputs
    pinMode(FT813_CS_PIN,     OUTPUT);
    pinMode(FT813_PD_PIN,     OUTPUT);
    pinMode(FT813_INT_PIN,     INPUT);
    digitalWrite(FT813_PD_PIN, HIGH);
    digitalWrite(FT813_INT_PIN, HIGH);

    // Init SPI
    SPI.begin();

    // Init Display
    initUi();
    delay(1000);
}

void loop()
{
    drawTest();  
    delay(1000);
}

// Copy/pasted from example file
void initUi()
{
    GD.begin();
   
    GD.ClearColorRGB(0,0,0);
    GD.Clear();
   
    GD.cmd_text(GD.w/2, GD.h/2, 30, OPT_CENTER, "Hello world");

    // Added my owm rect.
    GD.Rect_Empty(
        16, 16,
        GD.w-32, GD.h-32,
        UI_COLOR_3
    );

    GD.Begin(LINES);
    GD.ColorRGB(UI_COLOR_1);
    GD.Vertex2f(0*16, 0*16);   
    GD.Vertex2f((GD.w)*16, 0*16);  //Superior
    GD.Vertex2f(0*16, (GD.h-1)*16);
    GD.Vertex2f((GD.w)*16, (GD.h-1)*16); //inferior
    GD.Vertex2f(0*16, 0*16);
    GD.Vertex2f(0*16, (GD.h-1)*16); //izquierda
    GD.Vertex2f((GD.w-1)*16, 0*16);
    GD.Vertex2f((GD.w-1)*16, (GD.h-1)*16); //derecha 

    GD.swap();
}

void drawTest()
{
    GD.ClearColorRGB(0x000055);
      GD.Clear();
      GD.finish();

      GD.SaveContext();
    GD.cmd_text(16, 16, 22, OPT_CENTER, "Test");
    GD.RestoreContext();
   
    GD.swap();
}

The "HelloWorld" screen is still showing fine, but as soon as drawTest() is called here is what i get :
1000057322.jpg

This kind of "tearing" made me think of a timing issue.. But 'im really not sure.. As I manage to have a first screen looking fine, It make me confuse..
Maybe i'm just dumb and I don't understand how the lib is working ?...

If someone can land some help, I'll be very very happy !
Btw, @TFTLCDCyg, I think i've read on the forum that you are involved in the lib maintenance.. If i can help by adding support for this display, or something, i'll be glad too !

Thank you all in advance for you help !!
 
That 4.3" 4D Systems FT813 TFT is very good. Congratulations.

The timing table is on page 25 of the link you provided. It seems to me that the GDSTx values are compatible with those of your TFT. However, there may be two parameters that need fine-tuning, as long as you see any kind of artifact or RGB color glitches. It's already a big step that it works and responds to the GDSTx library.

These types of displays (FT80x, FT81x, or BT8xx) don't work like typical SPI displays: ILI9341, ST7735, ILI9488, etc. On all of them, the previous display screen must be cleared before the next one can be displayed.

It's actually a graphics chip that works with a 60 Hz refresh rate, so it's not necessary to clear what's displayed on the TFT every time a primitive is updated or a new one is loaded. Presentation.

Based on this approach, it's not necessary to use the delay instruction or constantly clear the screen; programming is simpler.

Code:
#include <GDSTx.h>

void setup()
{
  GD.begin();
}

void loop()
{
    drawTest();
}

void drawTest()
{
    GD.ClearColorRGB(0x000055);
    GD.Clear();

    GD.SaveContext();
      GD.cmd_text(16, 16, 22, OPT_CENTER, "Test");
    GD.RestoreContext();
 
    GD.swap();
}

Try the sketch. If it doesn't work, it seems to me that, like the Riverdi BT817 TFT, the 4Dsystems TFTs require a software reset via the PD pin. GDTSx manages this via pin 24 of the Teensy 4.1. For this, we could define a specific SizeEVE constructor, perhaps called 432, which is only for the 4DSystems 4.3"/FT813 display.

NHD43_FT813.jpg

NHD 4.3" FT813, Black F407VG, GDSTx library


Color test
Code:
#include <GDSTx.h>
void setup()
{
  GD.begin();
}

void loop()
{
GD.ClearColorRGB(0x000000);
GD.Clear();
GD.get_inputs();

  GD.cmd_gradient(0, 0, 0x0000ff, 480, 0, 0xff0000);    //vertical
  GD.cmd_text(GD.w / 2, GD.h / 2, 31, OPT_CENTER, "Hello world");
 
Parametros();
 
GD.swap();
}

char TXP[50];
void Parametros()
{
  GD.SaveContext();
  GD.cmd_text(4, 2, 20, 0, GD23ZUTX_VERSION); 

  GD.ColorRGB(0x00ff00);
  sprintf(TXP,"F_CPU: %d MHz", (F_CPU/1000000)); 
 
  GD.cmd_text(3, (GD.h)-18, 20, 0, TXP);
      
  GD.RestoreContext();
}
 
Last edited:
That 4.3" 4D Systems FT813 TFT is very good. Congratulations.

The timing table is on page 25 of the link you provided. It seems to me that the GDSTx values are compatible with those of your TFT. However, there may be two parameters that need fine-tuning, as long as you see any kind of artifact or RGB color glitches. It's already a big step that it works and responds to the GDSTx library.

These types of displays (FT80x, FT81x, or BT8xx) don't work like typical SPI displays: ILI9341, ST7735, ILI9488, etc. On all of them, the previous display screen must be cleared before the next one can be displayed.

It's actually a graphics chip that works with a 60 Hz refresh rate, so it's not necessary to clear what's displayed on the TFT every time a primitive is updated or a new one is loaded. Presentation.

Based on this approach, it's not necessary to use the delay instruction or constantly clear the screen; programming is simpler.

Code:
#include <GDSTx.h>

void setup()
{
  GD.begin();
}

void loop()
{
    drawTest();
}

void drawTest()
{
    GD.ClearColorRGB(0x000055);
    GD.Clear();

    GD.SaveContext();
      GD.cmd_text(16, 16, 22, OPT_CENTER, "Test");
    GD.RestoreContext();
 
    GD.swap();
}

Try the sketch. If it doesn't work, it seems to me that, like the Riverdi BT817 TFT, the 4Dsystems TFTs require a software reset via the PD pin. GDTSx manages this via pin 24 of the Teensy 4.1. For this, we could define a specific SizeEVE constructor, perhaps called 432, which is only for the 4DSystems 4.3"/FT813 display.

View attachment 38252
NHD 4.3" FT813, Black F407VG, GDSTx library


Color test
Code:
#include <GDSTx.h>
void setup()
{
  GD.begin();
}

void loop()
{
GD.ClearColorRGB(0x000000);
GD.Clear();
GD.get_inputs();

  GD.cmd_gradient(0, 0, 0x0000ff, 480, 0, 0xff0000);    //vertical
  GD.cmd_text(GD.w / 2, GD.h / 2, 31, OPT_CENTER, "Hello world");
 
Parametros();
 
GD.swap();
}

char TXP[50];
void Parametros()
{
  GD.SaveContext();
  GD.cmd_text(4, 2, 20, 0, GD23ZUTX_VERSION);

  GD.ColorRGB(0x00ff00);
  sprintf(TXP,"F_CPU: %d MHz", (F_CPU/1000000));
 
  GD.cmd_text(3, (GD.h)-18, 20, 0, TXP);
     
  GD.RestoreContext();
}

Hi @TFTLCDCyg !

First of all, thanks you A LOT for your detailed answer ! It's really precious, and I appreciate it a lot !
Btw, sorry for my delay to answer back, I was traveling, so I answer now.

So ! I've tried your two sketches, and there is progress !! I'm still using the "SizeEVE=43", and the gradient screen (with "Hello World" and various info and properties) is showing fine ! (See attached Files)
WhatsApp Image 2025-10-05 à 15.00.30_7a104e47.jpg

So, I guess you're right, I have to just call some kind of an "update()" / "refresh()" function in the loop directly. If i put a delay between the calls in anyways, nothing works, and I get a "tearing" display again.

So now, the only problem that persist is that the text shown is flickering... I assume every time the function is called ?.. maybe a bit slower !
So, it's already a lot better ! But there is still this little issue.. And I don't what causes this...

I also see a little "gap" at the bottom right of the screen, and i don't know if it's just some "hardware" particularity or if it's the symptom of a configuration problem.
See :
WhatsApp Image 2025-10-05 à 15.00.31_bd081612.jpg


But at the top I didn't see this "gap"
WhatsApp Image 2025-10-05 à 15.00.31_2fbf5b9e.jpg


I've also tried to set SizeEVE to 432, but i was unable to display anything.. So I guess 43 is fine, maybe with a fine tuning, like you said !
Also, you said there's two parameter that required fine tuning.. but which one ?
Btw, I'm using a dedicated PCB/Motherboard for my project, and (obviously), i've wired the PD_PIN on the pin 23 of the teensy... Sooooo, I hope the pin is configurable in the lib if it's needed ?
If it's not (or hardly), I could rewire this on the Teensy's pin 24. It's still a prototype, so, I anticipate this !

Again, thanks you A LOT for your knowledge, your time and you enlightments ! It's really really appreciated !!
 
I'm glad to see that the library works even on hardware I've never been able to test before.

The timing table is in the link to the datasheet you attached. This is the content of constructor 43, the two parameters that are somewhat different are: Thfp and Tvfp, for the Newhaven TFT of 4.3", they work very well

Code:
    GD.wr32(REG_HSIZE,  480);//480       Th
    GD.wr32(REG_VSIZE,  272);//272       Tv
  
    GD.wr32(REG_HCYCLE, 548);//548       Th         4DSystems-43      485    531    598
    GD.wr32(REG_HOFFSET, 43);//43        Thb                            3     43     43
    GD.wr32(REG_HSYNC0,   0);//0         Thfp                           2      8     75
    GD.wr32(REG_HSYNC1,  41);//41        Thpw/Thw                       2      4     43

    GD.wr32(REG_VCYCLE, 292);//292       Tv                           276    292    321
    GD.wr32(REG_VOFFSET, 12);//12        Tvbp                           2     12     12
    GD.wr32(REG_VSYNC0,   0);//0         Tvfp                           2      8     37
    GD.wr32(REG_VSYNC1,  10);//10        Tvpw                           2      4     12

    GD.wr32(REG_PCLK,     5);//5
    if (GameduinoX==0)
     {
         GD.wr32(REG_SWIZZLE,  0);//0  NHD43    //3 for GD3
     }
    if (GameduinoX==1)
     {
         GD.wr32(REG_SWIZZLE,  3);//0  NHD43    //3 for GD3
     }   
  
    GD.wr32(REG_PCLK_POL, 1);//1
    GD.wr32(REG_CSPREAD,  1);//1
    GD.wr32(REG_DITHER,   1);//1

You could check the value according to the range in your display's datasheet and see if there's any change. The range is opposite, from minimum to maximum, and the typical value suggested by 4DSystems is somewhere in the middle of the two.

Since there are no books to consult, it will be a matter of trial and error, this is how I found the values for the 4.3" NHD. Taking these values as a base, I can happily add a new constructor to GDSTx with the parameters that correspond to your 4DSystems TFT. It could be called 431 for example.

Upload the example that gives you some kind of visual problem, just as you have it. Don't change anything, so we can reproduce what you see on your TFT. By the way, you don't need to add anything to load images; the library is designed for Teensy 4.1. You just need to add the multimedia content inside the GDSTx files to a microSD card, preferably with a high read/write speed. With that, we can also see if anything else needs to be adjusted.

PD:

In the next few days I'm due to receive a 4.3" NHD TFT with 800x480 px resolution. I'm going to have to fix some parts of the library to get it working. I'll take the opportunity to modify the call to the PD pin as you suggest, so that it can be configured as SizeEVE.
 
Last edited:
Thanks you @TFTLCDCyg a lot again for your response !!tftlc
Again it's helping a lot !

Okay, I think I understand better which value on the datasheet correspond to which registers ! I'll give it a try tomorrow and I'll keep you informed :)
I've didn't understood that a range of values can be tolerated by the display !

So the code I'm currently running is pretty much the same you've suggested :

C++:
#include <GDSTx.h>

void setup()
{
    GD.begin()
}

void loop()
{
    uiUpdate();
}

void uiUpdate()
{
    GD.ClearColorRGB(0x000000);
GD.Clear();
GD.get_inputs();

  GD.cmd_gradient(0, 0, 0x0000ff, 480, 0, 0xff0000);    //vertical
  GD.cmd_text(GD.w / 2, GD.h / 2, 31, OPT_CENTER, "Hello world");
 
Parametros();
 
GD.swap();
}

char TXP[50];
void Parametros()
{
  GD.SaveContext();
  GD.cmd_text(4, 2, 20, 0, GD23ZUTX_VERSION);

  GD.ColorRGB(0x00ff00);
  sprintf(TXP,"F_CPU: %d MHz", (F_CPU/1000000));
 
  GD.cmd_text(3, (GD.h)-18, 20, 0, TXP);
     
  GD.RestoreContext();
}

I'm just running the uiUpdate() and Parametros() in a separate file for readability reasons.
If its relevant I can share the entire project, among its pretty specific ^^

So with this code and the "43" constructor, the texts is constantly flickering (but not the background gradient, for some reason)... not very very fast, but fast enough to be obnoxious..
I'll try to upload a video of this.

Btw, I've seen that when the Teensy reboot (like, when code is uploaded) the flickering temporarily stops... so I guess its somehow linked to the uiUpdate()..

I'll try to tune a bit the Registers like you've suggested and see !

Thanks again for your enlightenment about the image ! Currently I'm not planning to display images, but it's interesting! I'll give it a try when I have time, in the next week !

Also, thank for the PD Pin ! It'll be awesome! If I can help in anyway, I'll be glad ! :)
 
I forgot to mention something important. Try the example included in the library called sprites. This will help us determine if there are any fine-tuning needs to be done on the timing table. If you see any distortion in the image, take a photo so we can try to interpret the possible source of the error, if it still exists.

By the way, were you able to configure the TFT touch calibration?

To build a project with multiple menus, I recommend not using the void loop function. It's preferable to use independent functions with while loops to replace it and separate tabs for better control. Here's an example with two submenus (M1 and M2) and a main menu (MP).

I've included the base menu with which you can build the menus you need. In this case, the delay (75) instruction is used to ensure a delay in the menu jump, since the touch panel response is too fast and can cause coprocessor errors.
 

Attachments

  • Menu.zip
    1.9 KB · Views: 40
Last edited:
I forgot to mention something important. Try the example included in the library called sprites. This will help us determine if there are any fine-tuning needs to be done on the timing table. If you see any distortion in the image, take a photo so we can try to interpret the possible source of the error, if it still exists.

By the way, were you able to configure the TFT touch calibration?

To build a project with multiple menus, I recommend not using the void loop function. It's preferable to use independent functions with while loops to replace it and separate tabs for better control. Here's an example with two submenus (M1 and M2) and a main menu (MP).

I've included the base menu with which you can build the menus you need. In this case, the delay (75) instruction is used to ensure a delay in the menu jump, since the touch panel response is too fast and can cause coprocessor errors.
Okay ! So, I found the problem !!

Just to recap : I've tested and tweaked the timing setting, but it was just the same... the screen was still flickering.
So, I got the intuition that I should do something wrong...
And I was right that I was wrong ! (Sorry for that pun..)

So, like I was saying, I'm working on a larger project, and over displaying information, I have to perform some computing and regulations...
So in the loop(), I'd implemented a little "software timer". (That's just an object with a bool timeout() function. Every x millisec, it return true)

So my loop was like :
C++:
#include <Arduino.h>
#include <GDSTx.h>

Void setup
{
  GD.begin();
}

void loop
{
  uiUpdate();

  if (softwareTimer.timeout() )
  {
    lotOfProcess();
    anotherComputation();
    etc();
  }

}

( I'll share the full code tomorrow, with my work computer ! )

Because this approche didn't freeze the loop, I thought at first that it didn't interact with the display... but.. It does 😅

The timer is "timeout" every 100ms (10Hz).. and the screen was flickering at... 10Hz !
I've tried to change the timer period, and it directly change the flickering speed..
And the flickering completely stopped if I commented the "processing" section.

So, good news : everything is fine with the lib and the display !!
Bad news : I'm dumb ! And I should have shared the full code, and think about this issue sooner 😅

So, I guess there is a way to solve this, because its seems to me that's its a relatively basic concurrency/coprocessing problem.

I was thinking about calling the uiUpdate() with a timer interrupt/ISR. Like all the processing stay in the loop(), and the uiUpdate is called concurrently by an ISR, every 16ms (60Hz) approximately? Maybe faster ?

I don't know if there is another way to solve this ?


So, as you asked, I still don't look after the touch screen calibration.
But I'm interested ! I've taken a look at the menu example you've sent, and I have to admit than I didn't understand very clearly how it work 😅
It's on the road map but not a priority for now ^^
But if you some advise, I'm interested !!

Thanks you again for your time, an all your precious advises !
 
Back
Top