CYD – Cheap Yellow Display – dissected

They seem to be everywhere right now, the ubiquitous CYD or better known as the Cheap Yellow Display! aptly named after the Yellow PCB colour I’m sure. They resolve many things for a builder such as myself and resolve one neatly designed PCB…. but there is a but and its rather a big but!! the documentation and implementation is totally and utterly awful. SSooooo bad…..

  First and foremost. I am acutely aware that these boards are used for hacking WiFi, Bluetooth and NFC. This is a get you going help page, to aid with programming your CYD and I WILL NOT ENTERTAIN ANY HELP with those or other hacking uses, ethical or not. Simple, DO NOT ASK….. 

OK, rant over. The main issue many will face with the CYD is that it’s pin assignments are far from standardised. The pins are not driven in the main .ino file but in extended headers. For example User_setup.h, lv_conf.h, etc.  Unless these headers are correctly configured then you are cast iron, guaranteed to get nowt, zip, nil, zero on the display. Nothing will work.! So lets first go through the Pin outs for my particular strain of CYD. The 2 USB version, Micro USB and USB-C. Known as the CYB2USB or v3. We must be weary of strapping pins that must be held high or low during boot. 

TFT Pins as follows:

TFT MOSI : GPIO 13

TFT SCK : GPIO 14

TFT DC : 2 

Touch Pins as follows:

Touch CLK :                 GPIO 25       –   XPT2046 SCK

Touch CS :                    GPIO 33       –   XPT2046 CS

Touch DIN (MOSI) :  GPIO 32       –   XPT2046 MOSI

Touch OUT (MISO) : GPIO 39       –   XPT2046 MISO

Touch IRQ :                 GPIO 36       –   Optional Interrupt Pin

SD Card reader as follows:

SD CLK (SCK) :           GPIO 18       –   SD Card SPI Clock

SD CMD :                    GPIO 23       –   SD Card MOSI

SD DATA0 :                    GPIO 19       –   SD Card MISO

SD CS :                    GPIO 5         –   SD Card Chip Select

Audio Stage as follows:

Audio Ouput : GPIO 26

Back Light : GPIO 21

I2C Pins as follows :

SCL : GPIO 22

SDA : GPIO 21 (Shared)

 OK lets try some code and it is important we get the following files correct and in their correct places. Please observe the naming above the code and its location!! Important.

**********************************************************************************************************************************************

/* Main Sketch        CYD_Checker_V1.1.0.ino
   Version: 1.1.0
   Purpose: Single-file LVGL + ST7789 + XPT2046 example with runtime colour/order toggles
   Pins (adjust if needed):
     MOSI = 13, SCLK = 14, DC = 2, CS = 15, RST = 12, BL = 21
     TOUCH_CS = 33, TOUCH_IRQ = 36
   Runtime Serial commands:
     c -> run color test (ble, green, red, white, black)
     s -> toggle tft.setSwapBytes(true/false)
     p -> toggle pushSwap flag used in flush
     o -> print current settings
     r -> rotate display (cycles 0..3)
*/
#include <Arduino.h>
#include <TFT_eSPI.h>
#include <lvgl.h>
#include <XPT2046_Touchscreen.h>
#include <SPI.h>
#include <esp_heap_caps.h>
#define SKETCH_VERSION “1.1.0”
// Hardware pins (match your User_Setup.h)
#define TFT_BL_PIN   21
#define TFT_RST_PIN  12
#define TOUCH_CS_PIN 33
#define TOUCH_IRQ_PIN 36
// Global single TFT instance (only one definition)
TFT_eSPI tft = TFT_eSPI(); // uses TFT_eSPI/User_Setup.h
// Touch controller
XPT2046_Touchscreen ts(TOUCH_CS_PIN, TOUCH_IRQ_PIN);
// LVGL buffers and drivers
static lv_disp_draw_buf_t draw_buf;
static lv_color_t *buf1 = nullptr;
static lv_disp_drv_t disp_drv;
static lv_indev_drv_t indev_drv;
// Runtime flags (can be toggled via Serial)
static bool runtime_setSwapBytes = true; // applied after tft.init()
static bool runtime_pushSwap = false;    // passed to pushColors in flush
static uint8_t runtime_rotation = 1;     // 0..3
// Forward declarations
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p);
void my_touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data);
void create_ui();
void run_color_test();
void print_settings();
void create_ui() {
  lv_obj_t *scr = lv_scr_act();
  lv_obj_t *label = lv_label_create(scr);
  lv_label_set_text_fmt(label, “ST7789 LVGL v%s”, SKETCH_VERSION);
  lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 8);
  lv_obj_t *spinner = lv_spinner_create(scr, 1000, 60);
  lv_obj_set_size(spinner, 80, 80);
  lv_obj_align(spinner, LV_ALIGN_CENTER, 0, 10);
}
void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println();
  Serial.printf(“Starting LVGL_ST7789_v%s\n”, SKETCH_VERSION);
  // Backlight
  pinMode(TFT_BL_PIN, OUTPUT);
  digitalWrite(TFT_BL_PIN, HIGH);
  // Manual reset
  pinMode(TFT_RST_PIN, OUTPUT);
  digitalWrite(TFT_RST_PIN, LOW); delay(50);
  digitalWrite(TFT_RST_PIN, HIGH); delay(50);
  // TFT init
  tft.init();
  tft.invertDisplay(false); // try false first; if colours are still wrong try true eg. Cyan, Magenta and Yellow – typical in v3 displays
  tft.setRotation(runtime_rotation);
  tft.setSwapBytes(runtime_setSwapBytes);
  Serial.printf(“TFT init w=%d h=%d\n”, tft.width(), tft.height());
  tft.fillScreen(TFT_BLACK);
  // LVGL init
  lv_init();
  // Allocate DMA-capable buffer if possible
  const uint32_t buf_pixels = 240 * 40;
  buf1 = (lv_color_t*)heap_caps_malloc(buf_pixels * sizeof(lv_color_t), MALLOC_CAP_DMA);
  if (!buf1) {
    Serial.println(“DMA alloc failed, falling back to malloc”);
    buf1 = (lv_color_t*)malloc(buf_pixels * sizeof(lv_color_t));
    if (!buf1) {
      Serial.println(“Buffer allocation failed – halting”);
      while (1) delay(1000);
    }
  } else {
    Serial.println(“DMA buffer allocated”);
  }
  lv_disp_draw_buf_init(&draw_buf, buf1, NULL, buf_pixels);
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = tft.width();
  disp_drv.ver_res = tft.height();
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);
  // Touch init (optional)
  if (ts.begin()) {
    Serial.println(“XPT2046 init OK”);
  } else {
    Serial.println(“XPT2046 init FAILED – disconnect for TFT-only testing”);
  }
  lv_indev_drv_init(&indev_drv);
  indev_drv.type = LV_INDEV_TYPE_POINTER;
  indev_drv.read_cb = my_touch_read;
  lv_indev_drv_register(&indev_drv);
  create_ui();
  print_settings();
  Serial.println(“Setup complete. Use Serial commands: c s p o r”);
}
void loop() {
  // Handle LVGL timersc
  lv_timer_handler();
  delay(5);
  // Serial command handling (non-blocking)
  if (Serial.available()) {
    char c = Serial.read();
    switch (c) {
      case ‘c’: run_color_test(); break;
      case ‘s’:
        runtime_setSwapBytes = !runtime_setSwapBytes;
        tft.setSwapBytes(runtime_setSwapBytes);
        Serial.printf(“setSwapBytes -> %s\n”, runtime_setSwapBytes ? “true” : “false”);
        break;
      case ‘p’:
        runtime_pushSwap = !runtime_pushSwap;
        Serial.printf(“pushSwap -> %s\n”, runtime_pushSwap ? “true” : “false”);
        break;
      case ‘o’:
        print_settings();
        break;
      case ‘r’:
        runtime_rotation = (runtime_rotation + 1) & 3;
        tft.setRotation(runtime_rotation);
        Serial.printf(“rotation -> %u\n”, runtime_rotation);
        break;
      default:
        // ignore other bytes
        break;
    }
  }
}
/* Display flush callback */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
  int32_t x1 = area->x1;
  int32_t y1 = area->y1;
  int32_t x2 = area->x2;
  int32_t y2 = area->y2;
  uint32_t w = (x2 – x1 + 1);
  uint32_t h = (y2 – y1 + 1);
  tft.startWrite();
  tft.setAddrWindow(x1, y1, w, h);
  // Use runtime_pushSwap to flip bytes at push time if needed
  tft.pushColors((uint16_t*)colour_p, w * h, runtime_pushSwap);
  tft.endWrite();
  lv_disp_flush_ready(disp);
}
/* Touch read callback */
void my_touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
  if (!ts.touched()) {
    data->state = LV_INDEV_STATE_REL;
    return;
  }
  TS_Point p = ts.getPoint();
  // Default raw ranges; calibrate if needed
  const int rawMinX = 200;
  const int rawMaxX = 3900;
  const int rawMinY = 200;
  const int rawMaxY = 3900;
  int sx = map(p.x, rawMinX, rawMaxX, 0, tft.width());
  int sy = map(p.y, rawMinY, rawMaxY, 0, tft.height());
  sx = constrain(sx, 0, tft.width() – 1);
  sy = constrain(sy, 0, tft.height() – 1);
  data->point.x = sx;
  data->point.y = sy;
  data->state = (p.z > 0) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
}
/* This utility will run a quick colour test on the TFT */
void run_color_test() {
  Serial.println(“Running colour test: BLUE, GREEN, RED, WHITE, BLACK”);
  tft.fillScreen(TFT_RED);   delay(500);
  tft.fillScreen(TFT_GREEN); delay(500);
  tft.fillScreen(TFT_BLUE);  delay(500);
  tft.fillScreen(TFT_WHITE); delay(500);
  tft.fillScreen(TFT_BLACK); delay(500);
  // restore LVGL screen
  lv_refr_now(NULL);
  Serial.println(“Colour test complete”);
}
/* Utility to print the users current runtime settings */
void print_settings() {
  Serial.printf(“Version: %s\n”, SKETCH_VERSION);
  Serial.printf(“setSwapBytes: %s\n”, runtime_setSwapBytes ? “true” : “false”);
  Serial.printf(“pushSwap (flush): %s\n”, runtime_pushSwap ? “true” : “false”);
  Serial.printf(“rotation: %u\n”, runtime_rotation);
  Serial.printf(“TFT size: %d x %d\n”, tft.width(), tft.height());
}
****************************************************************************************************************************
Contents of    –  User_setup.h in location C:\Users\** your user name ** \Documents\Arduino\libraries\TFT_eSPI\User_setup.h
*****************************************************************************************************************************
// User_Setup.h
#define ST7789_DRIVER
#define TFT_WIDTH 240
#define TFT_HEIGHT 320

// SPI pins for your module
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 12
#define TFT_BL 21

// Touch pins (optional)
#define TOUCH_CS 33
#define TOUCH_IRQ 36

// Try BGR first; if colors wrong, change to TFT_RGB
#define TFT_RGB_ORDER TFT_RGB

#define ST7789_240X320

// Lower frequency while debugging; raise later if stable
#define SPI_FREQUENCY 20000000
#define SPI_READ_FREQUENCY 20000000

***************************************************************************************************************************

Alternative User_setup.h below

***************************************************************************************************************************

// User_Setup_ST7789_Custom.h
#define ST7789_DRIVER
#define TFT_WIDTH  240
#define TFT_HEIGHT 320
// SPI pins for your module
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS   15    // change if your module uses a different CS 
#define TFT_DC   2
#define TFT_RST  12    // change if wired to a different pin
#define TFT_BL   21    // change if your backlight pin differs
#define TFT_RGB_ORDER TFT_BGR
#define ST7789_240X320
// Lower frequency while debugging
#define SPI_FREQUENCY  20000000
#define SPI_READ_FREQUENCY  20000000