Can't make EVE4 display work - just pale blue screen

sw_hunt

Well-known member
I'm using a Teensy 4.0 with the following pin connections:
Teensy pin 2 - EVE4 pin 7 - IRQ
3 - 8 - Reset
10 - 6 - CS
11 - 5 - MOSI
12 - 4 - MISO
13 - 3 - CLK
Teensy 3V3 - EVE4 pin 1
12V PSU - EVE4 pins 17, 18 - BLVDD
Teensy GND - EVE4 pins 2,19,20

I can make the backlight come on and the screen is light blue but can't get even simple colour or text. Comms seems good and I can change registers and read them back as having been changed ok, but nothing goes to the display itself.

I've tried GDTX.h (config.h doesn't seem to have a 12" option to set), Platform.h from Riverdi and a 'bare metal' sketch (below) but no luck with any.

The display is a Riverdi RVT121HVBFWCA0-B

Any help gratefully received :) TIA.

Some code that successfully does nothing apart from make the backlight come on:

Code:
#include <SPI.h>

/* Pins */
#define EVE_CS   10
#define EVE_PDN   3

/* EVE base */
#define RAM_REG  0x302000UL
#define RAM_DL   0x300000UL
#define RAM_CMD  0x308000UL

/* REGs */
#define REG_ID          (RAM_REG + 0x000)
#define REG_PWM_HZ      (RAM_REG + 0x0D0)
#define REG_PWM_DUTY    (RAM_REG + 0x0D4)
#define REG_GPIOX_DIR   (RAM_REG + 0x090)   // bit7
#define REG_GPIOX       (RAM_REG + 0x094)   // bit7

#define REG_HCYCLE      (RAM_REG + 0x02C)
#define REG_HOFFSET     (RAM_REG + 0x030)
#define REG_HSYNC0      (RAM_REG + 0x034)
#define REG_HSYNC1      (RAM_REG + 0x038)
#define REG_VCYCLE      (RAM_REG + 0x03C)
#define REG_VOFFSET     (RAM_REG + 0x040)
#define REG_VSYNC0      (RAM_REG + 0x044)
#define REG_VSYNC1      (RAM_REG + 0x048)
#define REG_HSIZE       (RAM_REG + 0x04C)
#define REG_VSIZE       (RAM_REG + 0x050)
#define REG_OUTBITS     (RAM_REG + 0x05C)
#define REG_DITHER      (RAM_REG + 0x060)
#define REG_SWIZZLE     (RAM_REG + 0x064)
#define REG_CSPREAD     (RAM_REG + 0x068)
#define REG_PCLK_POL    (RAM_REG + 0x06C)
#define REG_PCLK        (RAM_REG + 0x070)

#define REG_CMDB_WRITE  (RAM_REG + 0x0FC)
#define REG_CMDB_SPACE  (RAM_REG + 0x574)

/* Host commands */
#define HCMD_ACTIVE 0x00
#define HCMD_CLKEXT 0x44
#define HCMD_CLKSEL 0x61
#define HCMD_PCLK   0x70

/* Co-proc */
#define CMD_DLSTART  0xFFFFFF00UL
#define CMD_SWAP     0xFFFFFF01UL

/* DL macros */
#define DL_CLEAR_COLOR_RGB(r,g,b) (0x02000000UL | ((uint32_t)(r)<<16) | ((uint32_t)(g)<<8) | (b))
#define DL_CLEAR(c,s,t)           (0x26000000UL | ((c?1:0) | ((s?1:0)<<1) | ((t?1:0)<<2)))
#define DL_DISPLAY()              (0x00000000UL)

SPISettings eveSPI(1000000, MSBFIRST, SPI_MODE0); // 1 MHz for bring-up


/* SPI helpers */
static inline void eveBegin(){ SPI.beginTransaction(eveSPI); digitalWrite(EVE_CS, LOW); }
static inline void eveEnd(){   digitalWrite(EVE_CS, HIGH); SPI.endTransaction(); }
void eveHostCmd(uint8_t cmd, uint16_t data=0){
  eveBegin(); SPI.transfer(cmd); SPI.transfer(lowByte(data)); SPI.transfer(highByte(data)); eveEnd();
}
void eveWrite8 (uint32_t a, uint8_t  v){
  eveBegin(); SPI.transfer(((a>>16)&0x3F)|0x80); SPI.transfer((a>>8)&0xFF); SPI.transfer(a&0xFF);
  SPI.transfer(v); eveEnd();
}
void eveWrite16(uint32_t a, uint16_t v){
  eveBegin(); SPI.transfer(((a>>16)&0x3F)|0x80); SPI.transfer((a>>8)&0xFF); SPI.transfer(a&0xFF);
  SPI.transfer(lowByte(v)); SPI.transfer(highByte(v)); eveEnd();
}
uint8_t eveRead8(uint32_t a){
  eveBegin(); SPI.transfer((a>>16)&0x3F); SPI.transfer((a>>8)&0xFF); SPI.transfer(a&0xFF);
  SPI.transfer(0x00); uint8_t v=SPI.transfer(0x00); eveEnd(); return v;
}
uint32_t eveRead32(uint32_t a){
  eveBegin(); SPI.transfer((a>>16)&0x3F); SPI.transfer((a>>8)&0xFF); SPI.transfer(a&0xFF);
  SPI.transfer(0x00);
  uint32_t b0=SPI.transfer(0x00), b1=SPI.transfer(0x00), b2=SPI.transfer(0x00), b3=SPI.transfer(0x00);
  eveEnd(); return (b0 | (b1<<8) | (b2<<16) | (b3<<24));
}

/* CMDB */
static uint16_t cmdb_wptr=0;
void cmdb_reset(){ cmdb_wptr=0; }
void cmdb_write32(uint32_t v){ eveWrite32(RAM_CMD+cmdb_wptr, v); cmdb_wptr=(cmdb_wptr+4)&0x0FFF; }
void cmdb_flush(){ eveWrite32(REG_CMDB_WRITE, cmdb_wptr); }
void eveWrite32(uint32_t a,uint32_t v){
  eveBegin(); SPI.transfer(((a>>16)&0x3F)|0x80); SPI.transfer((a>>8)&0xFF); SPI.transfer(a&0xFF);
  SPI.transfer(v&0xFF); SPI.transfer((v>>8)&0xFF); SPI.transfer((v>>16)&0xFF); SPI.transfer((v>>24)&0xFF); eveEnd();
}

void dumpKey(const char* tag){
  Serial.println(tag);
  Serial.printf("HCYCLE=%u HSIZE=%u HOFF=%u HSYNC0/1=%u/%u  VCYCLE=%u VSIZE=%u VOFF=%u VSYNC0/1=%u/%u\n",
    (unsigned)eveRead32(REG_HCYCLE), (unsigned)eveRead32(REG_HSIZE),  (unsigned)eveRead32(REG_HOFFSET),
    (unsigned)eveRead32(REG_HSYNC0), (unsigned)eveRead32(REG_HSYNC1),
    (unsigned)eveRead32(REG_VCYCLE), (unsigned)eveRead32(REG_VSIZE),  (unsigned)eveRead32(REG_VOFFSET),
    (unsigned)eveRead32(REG_VSYNC0), (unsigned)eveRead32(REG_VSYNC1));
  Serial.printf("SWIZZLE=%lu CSPREAD=%lu OUTBITS=%lu DITHER=%lu  PCLK_POL=%lu PCLK=%lu  GPIOX_DIR=0x%02lX GPIOX=0x%02lX  CMDW=%lu\n",
    eveRead32(REG_SWIZZLE), eveRead32(REG_CSPREAD), eveRead32(REG_OUTBITS), eveRead32(REG_DITHER),
    eveRead32(REG_PCLK_POL), eveRead32(REG_PCLK),
    (unsigned long)eveRead32(REG_GPIOX_DIR), (unsigned long)eveRead32(REG_GPIOX),
    eveRead32(REG_CMDB_WRITE));
}

void setup(){
  pinMode(EVE_CS, OUTPUT); digitalWrite(EVE_CS, HIGH);
  pinMode(EVE_PDN, OUTPUT);
  Serial.begin(115200); delay(300);
  SPI.begin();

  Serial.println("\nBT81x: Riverdi RED via CMDB");

  // Reset
  digitalWrite(EVE_PDN, LOW);  delay(40);
  digitalWrite(EVE_PDN, HIGH); delay(120);

  // Wake, keep PCLK OFF
  eveHostCmd(HCMD_ACTIVE); delay(20);
  eveHostCmd(HCMD_CLKEXT); delay(20);
  eveHostCmd(HCMD_CLKSEL, 1); delay(20);
  eveHostCmd(HCMD_PCLK,   0); delay(20);

  // ID must be 0x7C
  uint8_t id = eveRead8(REG_ID);
  Serial.printf("REG_ID=0x%02X (expect 0x7C)\n", id);
  if (id != 0x7C) { Serial.println("No BT81x ID; check wiring."); while(1){} }

  // Gate panel/backlight ON via GPIOX
  eveWrite8 (REG_GPIOX_DIR, 0x80);  // bit7 dir
  eveWrite8 (REG_GPIOX,     0x80);  // bit7 high
  eveWrite16(REG_PWM_HZ,    1000);
  eveWrite8 (REG_PWM_DUTY,  96);

  // Timings (1650/828, offsets 150/33), SWIZZLE=0, POL=1
  eveWrite16(REG_HCYCLE,  1650);
  eveWrite16(REG_HSIZE,   1280);
  eveWrite16(REG_HOFFSET, 150);
  eveWrite16(REG_HSYNC0,  0);
  eveWrite16(REG_HSYNC1,  40);

  eveWrite16(REG_VCYCLE,  828);
  eveWrite16(REG_VSIZE,   800);
  eveWrite16(REG_VOFFSET, 33);
  eveWrite16(REG_VSYNC0,  0);
  eveWrite16(REG_VSYNC1,  10);

  eveWrite8 (REG_SWIZZLE,  0);
  eveWrite8 (REG_CSPREAD,  1);
  eveWrite8 (REG_OUTBITS,  0);
  eveWrite8 (REG_DITHER,   1);
  eveWrite8 (REG_PCLK_POL, 1);

  // Pixel clock ON (div=2), then build DL via CMDB
  eveWrite8 (REG_PCLK, 2);
  delay(50);

  cmdb_reset();
  cmdb_write32(CMD_DLSTART);
  cmdb_write32(DL_CLEAR_COLOR_RGB(255,0,0));
  cmdb_write32(DL_CLEAR(1,1,1));
  cmdb_write32(DL_DISPLAY());
  cmdb_write32(CMD_SWAP);
  cmdb_flush();

  dumpKey("After program (expect RED):");
}

void loop(){ delay(1000); }
Some more code that does the blue screen thing as well:
Code:
#include <GDSTx.h>

int MCUID;

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

void loop()
{
GD.ClearColorRGB(0x100020);
GD.Clear();
  GD.cmd_text(GD.w / 2, GD.h / 2, 31, OPT_CENTER, "Hello world");

GD.Begin(LINES);
  GD.ColorRGB(255,255,255);
  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 
 
Parametros();
 
GD.swap();
}
 
A while ago, I was configuring the library to connect a 10.1" EVE4 display. However, due to time and resource constraints, it wasn't possible to get the display for testing. Since you already have a 12.1" display, if you'd allow me, "you'll be my eyes for the experiment."

The timing tables between your 12.1" EVE4 display and the 10.1" display aren't very different. I adjusted some parts of the GDSTx library to configure both the timing table and the constructor for 12.1"/10.1".

Download GDSTx again and try it as configured to see what results you get.

The main constructor is SizeEVE 100.

By the way, these displays require a software reset on the pin PD, please connect it to pin 24 of your teensy 4. GDSTx already takes into account this setting on the PD pin connected to pin 24 of your Teensy 4.

Please share a photo of the result with the settings, it will help me see possible later adjustments, for the moment everything is theoretical, based on the datasheet of your EVE4 TFT
 
Awesome, thanks TFTLCDCyg!! I had to change the reset pin in your .h file to 3 as the Teensy doesn't have an (easily accessible) pin 24. Your Hello World example now shows Hello World. There is a white border along the left edge but no other borders. It shows 2.2 TX4... at the top left and Teensy 4 at the bottom left. Hello World is approximately central. Presumably there should be a border at the other edges and something needs a tweak?

I've done some semi-random messing with the .cpp file and changing to this brings on the right hand side margin:
GD.wr32(REG_HSIZE, 1279); // Thd visible horizontal line length

So I thought this (next line) might show a top or bottom line but all it does is lose the RHS margin again! I'll leave the fixing to the professionals!
GD.wr32(REG_VSIZE, 799); // Tvd number of visible lines
 
Last edited:
Great. Please, share a photo of the result so I can get an idea of the corrections. They should be minimal in both hoffset and voffset.
 
IMG_8275.JPG
 
Last edited:
The displacement in x+, y-, and y+ is interesting. Please upload the sketch called "sprites" and again upload a photo with the result.
 
Before reaching the 2001 counter, did you notice any artifacts?
I've adjusted some parts of the library so that the BT817 chip's line pattern and color test can be used for the 12.1" display.

Please download the GDSTx library from the Github repository again and upload the example.

Code:
#include <GDSTx.h>

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

void loop()
{
  GD.Clear();
  GD.cmd_testcard();
}

This is what will be displayed in the TFT

Test.jpg
 
IMG_8365.JPG

Testcard with updated library works well - see attached.

If by artifacts, you mean unexpected flashes outside the area where the sprites are, then no. The Teensy gets to the 2001 counter very fast so I put a delay in the main loop to slow it down, but didn't see anything unexpected.

I've found I can print text anywhere on the screen but not rectangles or lines beyond x = 1023 where rectangles wrap around and lines are ignored, probably because we run out of 10 bit numbers. There is a way around this using GD.VertexTranslateY() to add an offset, but this gets messy when drawing across the 1023/4 boundary. Hopefully you can make the library handle larger numbers without tricks. Thanks.
 
This maximum resolution of the BT817 is very recent; Riverdi pushed the chip's capabilities to the limit.

I see that the GDSTx library is responding well with the latest tweaks; it's just a matter of applying the correct TFT timing values in the startup section of GDSTx.ccp; the display datasheet is the key. But I only suggest it if artifacts appear. At the moment, it doesn't seem to be the case.

It's not necessary to add delays in the sprite test sketch; I've noticed that the artifacts occur at maximum speed. Based on the resulting testcard image, I see that all the patterns are correct and show no color shifts. If you noticed, you can see that the border is drawn correctly.

The maximum communication speed of the BT817 chip is 72 MHz, but I've noticed that at this limit the display fails. The library sets a value of 48 MHz.

If you notice any color errors, it may indicate that some adjustment to the TFT timing table is needed. Based on the tests you've completed, it seems stable.

That 12.1" TFT looks great. I hope you'll share your progress later.

PD: I wonder what the example of playing an AVI video with audio or loading JPG images will look like?

Teensy 4.1@150MHz (underclock), NHD 3.5" FT813, Canvas Go Plus 256 Gb microSD

Use this programming guide to familiarize yourself with the graphical instructions.

https://excamera.com/files/gd2book_v0.pdf

It was designed for a 480x272 px TFT. From the tests you've done, GDSTx can handle anything beyond that.I don't believe it!
 
Last edited:
On 1280×800 panels the X coordinate wraps at 1023 for lines and rectangles. GDSTx currently requires manual use of VertexTranslateX() to draw past x=1023. Would you consider adding an optional ‘BT81x extended’ mode or helper so that GD.Vertex2f() automatically applies the necessary translation when x≥1024? This would let users draw across the full width without extra code.

In case it is useful to you or other users, below is code that does this:

Code:
  // Draw a 100‑px grid
  Jumbo_BeginLines(1, 0x004080);
  for (uint16_t x = 0; x <= 1279; x += 100) {
    Jumbo_VLine(x, 0, 799);
  }
  for (uint16_t y = 0; y <= 799; y += 100) {
    Jumbo_HLine(0, 1279, y);
  }
  GD.End();

  // Draw a control panel rectangle crossing the boundary
  Jumbo_Rect(950, 100, 1200, 300, 0x3333FF);
  GD.swap();


Code:
#ifndef JUMBO_GFX_H
#define JUMBO_GFX_H

#include <stdint.h>

// Helpers to draw primitives across the 1023-pixel boundary on BT817/BT81x
void Jumbo_BeginLines(uint16_t width, uint32_t color);
void Jumbo_VLine(uint16_t x, uint16_t y0, uint16_t y1);
void Jumbo_HLine(uint16_t x0, uint16_t x1, uint16_t y);
void Jumbo_Rect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t color);

#endif // JUMBO_GFX_H


Code:
#include "JumboGfx.h"
#include <GDSTx.h>

// Internal helper to apply or clear X translation
static void Jumbo_SetTranslateX(int32_t pixels16) {
  // GD.VertexTranslateX() expects the offset in 1/16 pixel units
  GD.VertexTranslateX((uint32_t)pixels16 & 0x1FFFF);
}

// Begin drawing lines with a given width and colour
void Jumbo_BeginLines(uint16_t width, uint32_t color) {
  GD.LineWidth((uint16_t)(width * 16U)); // width in pixels ×16
  GD.ColorRGB(color);
  GD.Begin(LINES);
}

// Draw a vertical line across the height; handles x > 1023
void Jumbo_VLine(uint16_t x, uint16_t y0, uint16_t y1) {
  if (x <= 1023) {
    GD.Vertex2f((int32_t)x * 16, (int32_t)y0 * 16);
    GD.Vertex2f((int32_t)x * 16, (int32_t)y1 * 16);
  } else {
    // Draw in translated space
    Jumbo_SetTranslateX(1024 * 16);
    uint16_t lx = x - 1024;
    GD.Vertex2f((int32_t)lx * 16, (int32_t)y0 * 16);
    GD.Vertex2f((int32_t)lx * 16, (int32_t)y1 * 16);
    Jumbo_SetTranslateX(0);
  }
}

// Draw a horizontal line; splits if it crosses 1023
void Jumbo_HLine(uint16_t x0, uint16_t x1, uint16_t y) {
  if (x0 > x1) { uint16_t t = x0; x0 = x1; x1 = t; }
  // Segment entirely in the first region
  if (x1 <= 1023) {
    GD.Vertex2f((int32_t)x0 * 16, (int32_t)y * 16);
    GD.Vertex2f((int32_t)x1 * 16, (int32_t)y * 16);
    return;
  }
  // Segment entirely in the second region
  if (x0 >= 1024) {
    Jumbo_SetTranslateX(1024 * 16);
    uint16_t lx0 = x0 - 1024;
    uint16_t lx1 = x1 - 1024;
    GD.Vertex2f((int32_t)lx0 * 16, (int32_t)y * 16);
    GD.Vertex2f((int32_t)lx1 * 16, (int32_t)y * 16);
    Jumbo_SetTranslateX(0);
    return;
  }
  // Crosses the boundary: split at 1023/1024
  GD.Vertex2f((int32_t)x0 * 16, (int32_t)y * 16);
  GD.Vertex2f((int32_t)1023 * 16, (int32_t)y * 16);
  Jumbo_SetTranslateX(1024 * 16);
  uint16_t lx1 = x1 - 1024;
  GD.Vertex2f(0, (int32_t)y * 16);
  GD.Vertex2f((int32_t)lx1 * 16, (int32_t)y * 16);
  Jumbo_SetTranslateX(0);
}

// Draw a filled rectangle; splits on the 1023/1024 boundary if needed
void Jumbo_Rect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t color) {
  if (x0 > x1) { uint16_t t = x0; x0 = x1; x1 = t; }
  if (y0 > y1) { uint16_t t = y0; y0 = y1; y1 = t; }

  GD.ColorRGB(color);
  GD.Begin(RECTS);

  // Entirely left of the boundary
  if (x1 <= 1023) {
    GD.Vertex2f((int32_t)x0 * 16, (int32_t)y0 * 16);
    GD.Vertex2f((int32_t)x1 * 16, (int32_t)y1 * 16);
    GD.End();
    return;
  }

  // Entirely right of the boundary
  if (x0 >= 1024) {
    Jumbo_SetTranslateX(1024 * 16);
    uint16_t lx0 = x0 - 1024;
    uint16_t lx1 = x1 - 1024;
    GD.Vertex2f((int32_t)lx0 * 16, (int32_t)y0 * 16);
    GD.Vertex2f((int32_t)lx1 * 16, (int32_t)y1 * 16);
    Jumbo_SetTranslateX(0);
    GD.End();
    return;
  }

  // Spans the boundary: draw two rectangles
  GD.Vertex2f((int32_t)x0 * 16, (int32_t)y0 * 16);
  GD.Vertex2f((int32_t)1023 * 16, (int32_t)y1 * 16);
  Jumbo_SetTranslateX(1024 * 16);
  uint16_t lx1 = x1 - 1024;
  GD.Vertex2f(0, (int32_t)y0 * 16);
  GD.Vertex2f((int32_t)lx1 * 16, (int32_t)y1 * 16);
  Jumbo_SetTranslateX(0);
  GD.End();
}

#ifdef UNIT_TEST
// Simple self‑test: draws a full grid and a rectangle spanning the boundary.
void drawTest() {
  GD.ClearColorRGB(0x002040);
  GD.Clear();

  // Draw grid lines
  Jumbo_BeginLines(1, 0x00FFFF);
  for (uint16_t x = 0; x <= 1279; x += 100) {
    Jumbo_VLine(x, 0, 799);
  }
  for (uint16_t y = 0; y <= 799; y += 100) {
    Jumbo_HLine(0, 1279, y);
  }
  GD.End();

  // Draw a spanning rectangle
  Jumbo_Rect(950, 350, 1100, 500, 0xFF8000);

  GD.swap();
}
#endif
 
Try this sketch again
Code:
#include <GDSTx.h>

int MCUID;

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

void loop()
{
GD.ClearColorRGB(0x100020);
GD.Clear();
  GD.cmd_text(GD.w / 2, GD.h / 2, 31, OPT_CENTER, "Hello world");

GD.Begin(LINES);
  GD.ColorRGB(255,255,255);
  GD.SaveContext();
  GD.VertexFormat(3);
  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.RestoreContext();
 
Parametros();
 
GD.swap();
}

What is shown on the TFT?
 
My mystake, sorry!

Try this:

Code:
#include <GDSTx.h>

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

void loop()
{
GD.ClearColorRGB(0x100020);
GD.Clear();
  GD.cmd_text(GD.w / 2, GD.h / 2, 31, OPT_CENTER, "Hello world");

GD.Begin(LINES);
  GD.ColorRGB(255,255,255);
  GD.SaveContext();
  GD.VertexFormat(3);
  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.RestoreContext();
 
GD.swap();
}
 
Apparently, the general parameters are correct. Perhaps it's a matter of fine-tuning some of the general timings; it's difficult to know which ones are correct at a distance. However, the datasheet should be your guide. I think this is the first approximation and the way to go. Don't change anything else if it doesn't give you any problems at the moment.

By the way, did the touchscreen respond during testing?
 
Hi again

TFTLCDCyg

,


Thanks for the updated hello‑world example and your guidance on timing. I’m still running into one consistent limitation, though: on the 1280×800 BT817 panels, anything drawn with Vertex2f() beyond x = 1023 is either ignored (lines) or wraps around to the left (rectangles). The text routines (e.g. cmd_text()) draw cleanly across the full width, which suggests the timing values are correct. However, to get graphics primitives across the full 1280‑pixel width I currently have to manually call GD.VertexTranslateX(1024*16) and split primitives at x = 1023, as per the BT817 programming guide. This works, but it’s tricky to handle in application code.


Would you consider adding an “extended coordinates” option to GDSTx (or a helper) that automatically applies the necessary VertexTranslateX calls when x ≥ 1024? It would let users call GD.Vertex2f() or GD.Begin(LINES) with full 0–1279 coordinates without worrying about the 1023 boundary. If there’s a better solution you’d recommend for drawing on panels wider than 1024 px, I’d really appreciate your guidance. I included code that does this successfully above.

For reference, the touch controller works fine across the whole screen once calibrated.

Thanks for your continued help.
 
Wow, I thought the touchpad was going to give problems, it's good to know that it's valid for resolutions greater than 800x480 px.

According to the EVE3/EVE4 chip programming guide, the workspace is defined by the VERTEX_FORMAT instruction, which can take the values 0, 1, 2, 3, and 4. (Page 24, Table 21).

I understand that for the value 4, the working space is from -1024 to 1023 pixels, giving a resolution of 1/16 per pixel. But I don't know if it has to do directly with the screen resolution, such as 1280x800 px.

By default, the value 4 is used for 800x480 TFT´s. Could a value of 3 solve the problem that occurs beyond 1024 px?
 
Thank you for looking into this further. I reviewed the BT81x programming guide and it clarifies how VERTEX_FORMAT affects coordinate range. With VERTEX_FORMAT set to 4 (the default), only 10 bits are used for the integer part of each coordinate, giving a range of –1024..+1023 in 1/16‑pixel units. That’s why our primitives clip at x≈1023 on the 1280×800 panel.


Switching to VERTEX_FORMAT = 3 would indeed expand the range to –2048..+2047, but it reduces the precision to 1/8‑pixel units. Because GDSTx multiplies coordinates by 16 internally, you’d have to modify all drawing code to use ×8 instead of ×16. There are also trade‑offs for anti‑aliased lines and small elements. The translation commands (VERTEX_TRANSLATE_X/Y) are designed to address this issue—by shifting the coordinate origin, you keep 1/16‑pixel precision and still reach pixels beyond 1023.


In other words, the clipping isn’t a timing issue; it’s inherent to how the BT817 interprets coordinates. Could GDSTx offer an extended‑coordinate option that automatically handles this—either by exposing the fractional‑bit choice (VertexFormat) or by integrating the translation internally—so that end‑users can draw across the full width without manually splitting primitives? That way those of us who need the full 1280‑pixel range can choose it, while others can stay with the default high‑precision mode.


Thanks again for your help.
 
Indeed, the value is natively 4 in GDSTx.

My question is whether this setting to 3 has a real effect on your screen, allowing you to use the full 1280 pixels of width, meaning you can draw any element without color shifts or artifacts.

I believe reducing the resolution from 16 to 8 is simply a way to extend the capacity of the BT81X chip to support larger screens at the expense of individual pixel resolution, without reducing the processing speed of the graphics controller.

Another question I have is whether setting it to 3 could cause a failure in the touch calibration.

I have an idea for how I could get it to detect the TFT chip type from the start and consequently define whether to use the value 4 or 3, without having to do it manually each time, considering the SizeEVE value as the key.

Once again, I'm asking you to be my eyes on the experiment, as I don't have a TFT of that size to test the changes. I'll work on the adjustments, and it'll be a GDSTx beta again.

PD: The change to control VertexFormat depending on the TFT size from driver startup in GDSTx is now available.

This setting in VertexFormat to 3 seems to affect the graphics functions they call "primitives" and the touch matrix.

Please run graphics and touch tests within the surface larger than 1024 pixels. This will help us see if the changes in GDSTx have worked for extended TFTs. Share some photos to see the changes.
 
Last edited:
I tested both modes extensively on the 1280×800 BT817, drawing sinewaves, lines, text and borders. The sine curves and borders look virtually identical whether I use VERTEX_FORMAT=4 with translation or VERTEX_FORMAT=3. Given this, I’m happy to run in the extended‑range mode for my project. However, it’s important to preserve a user‑selectable option: for some applications (e.g. antialiased graphics) the full 1/16‑pixel precision can matter. Would you consider adding a parameter or macro so that when the library detects a wide panel it can default to format 3, but let the user override it to format 4 with automatic translation? That would give the best of both worlds—no manual translation for typical use, but precision when needed. I’m still holding onto the current version in case I need to fork it, but I’d much rather continue testing and improving GDSTx with you.
 
So selection 3 worked correctly for you at 1280 pixels?

There's no problem with the automatic selection; support for 4 is still native. Extended mode 3 applies only to the 12.1" TFT. I decided to use SizeEVE as the parameter to select the normal or extended mode. Now, in GDSTx, everything is relative: for the 12.1" BT817 TFT (1280x800 px), the normal resolution is the extended resolution with a value of 3.

For a 5" BT817 TFT (800x480 px), the normal resolution is the one with a value of 4.

To change from normal to extended mode in real time, we can use the SaveContext()/RestoreContext() instructions.

SaveContext() saves all previously set graphic parameters. From this instruction, we can apply the normal resolution mode and work with everything displayed on the screen at this resolution.

To return to the previous graphic parameters, we achieve this with RestoreContext().

Whatever is drawn within both instructions will have its own graphic environment. This means we can modify the normal or extended mode within the same sketch without needing to create an additional macro, and the mode set when the TFT was started will be preserved.

PD: Could you share a photo of your Teensy 4 setup with the 12.1" TFT working? I find it incredible that such a small board has the capacity to control a relatively large TFT.
 
Last edited:
Back
Top