Skip to content

T-Deck Quick Start

This guide covers Arduino, PlatformIO, LVGL, and peripheral usage for the T-Deck. For hardware specs and pin mapping, see the T-Deck product page.

Pin Definitions

Copy this block into every sketch - all examples below reference these constants.

cpp
#define BOARD_POWERON       10   // Peripheral power rail; set HIGH when on battery
#define BOARD_I2C_SDA       18
#define BOARD_I2C_SCL        8
#define BOARD_BAT_ADC        4
#define BOARD_TOUCH_INT     16
#define BOARD_KEYBOARD_INT  46
#define BOARD_SDCARD_CS     39
#define BOARD_TFT_CS        12
#define BOARD_TFT_DC        11
#define BOARD_TFT_BACKLIGHT 42
#define BOARD_SPI_MOSI      41
#define BOARD_SPI_MISO      38
#define BOARD_SPI_SCK       40
#define BOARD_TBOX_G01       3
#define BOARD_TBOX_G02       2
#define BOARD_TBOX_G03      15
#define BOARD_TBOX_G04       1
#define BOARD_ES7210_MCLK   48
#define BOARD_ES7210_LRCK   21
#define BOARD_ES7210_SCK    47
#define BOARD_ES7210_DIN    14
#define RADIO_CS_PIN         9
#define RADIO_BUSY_PIN      13
#define RADIO_RST_PIN       17
#define RADIO_DIO1_PIN      45
#define BOARD_GPS_TX_PIN    43
#define BOARD_GPS_RX_PIN    44
#define BOARD_BOOT_PIN       0

SPI bus sharing: T-Deck's TFT, SD card, and LoRa module share one SPI bus. Before any SPI transaction, pull the other two CS lines HIGH:

cpp
digitalWrite(BOARD_SDCARD_CS, HIGH);
digitalWrite(BOARD_TFT_CS,    HIGH);
digitalWrite(RADIO_CS_PIN,    HIGH);

Arduino

Arduino IDE

Install Board Support

  1. Open Arduino IDE -> File -> Preferences
  2. Add this URL to Additional boards manager URLs:
    https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  3. Open Tools -> Board -> Boards Manager, search esp32, install esp32 by Espressif Systems

Version lock: The upstream T-Deck examples are tested against arduino-esp32 2.0.14. Check repository dependency compatibility before upgrading arduino-esp32.

Install Libraries

Copy all folders from the project lib directory into your Arduino Sketchbook libraries/ folder, or install them manually from Library Manager:

LibraryVersionSource
LovyanGFXlatestGitHub
LilyGo-display-librarylatestXinyuan-LilyGO/LilyGo-display-library
RadioLiblatestGitHub
LVGL8.4.0GitHub
TinyGPSPluslatestGitHub
TouchLiblatestGitHub
AceButtonlatestGitHub
ESP32-audioI2SlatestGitHub
SensorsLiblatestGitHub

Do not upgrade libraries when Arduino IDE prompts for newer versions in lib/ are tested together.

LovyanGFX Configuration:

For display examples, use LilyGo_LovyanGFX. The library wraps T-Deck's ST7789, backlight, peripheral power, and shared SPI CS setup, so sketches can create a LilyGo_T_Deck object directly.

Basic Example

ExampleDescription
LilyGo_LovyanGFX_Board_TestUnified LilyGo_LovyanGFX board display test

Board Settings

Arduino IDE SettingValue
BoardESP32S3 Dev Module
Upload Speed921600
USB ModeHardware CDC and JTAG
USB CDC On BootEnabled
USB Firmware MSC On BootDisabled
USB DFU On BootDisabled
CPU Frequency240 MHz (WiFi)
Flash ModeQIO 80 MHz
Flash Size16MB (128Mb)
Core Debug LevelNone
Partition Scheme16M Flash (3MB APP/9.9MB FATFS)
PSRAMOPI PSRAM
Arduino Runs OnCore 1
Events Run OnCore 1

Upload

Select the correct COM port, then click Upload.

If upload fails: hold the trackball center button (BOOT = GPIO0), insert USB, then click Upload. After flashing, press RST to exit download mode.

The ESP32-C3 keyboard MCU is programmed separately via the 6-pin header near the RST button (top to bottom: 3V3, GND, RST, BOOT, RX, TX).


PlatformIO

platformio.ini

ini
[wifi]
ssid     = ${sysenv.PIO_WIFI_SSID}
password = ${sysenv.PIO_WIFI_PASSWORD}

[platformio]
default_envs = T-Deck
; Uncomment the example you want to build, comment out the rest:
src_dir = examples/UnitTest
; src_dir = examples/Microphone
; src_dir = examples/Touchpad
; src_dir = examples/lvgl_example
; src_dir = examples/Keyboard_T_Deck_Master
; src_dir = examples/GPSShield
; src_dir = examples/LoRaWAN_Starter
; src_dir = examples/I2SPlay
; src_dir = examples/LvglArduinoVNC_VGA
boards_dir = boards

[env:T-Deck]
platform      = espressif32@6.3.0
board         = T-Deck
framework     = arduino
upload_speed  = 921600
monitor_speed = 115200
lib_deps =
    lovyan03/LovyanGFX
    jgromes/RadioLib
    mikalhart/TinyGPSPlus
    lvgl/lvgl@^8.4.0
    AceButton
    lewisxhe/SensorsLib
build_flags =
    -DBOARD_HAS_PSRAM=1
    -DCORE_DEBUG_LEVEL=1
    -DARDUINO_USB_CDC_ON_BOOT=1
    '-DWIFI_SSID="${wifi.ssid}"'
    '-DWIFI_PASSWORD="${wifi.password}"'
    -DDISABLE_ALL_LIBRARY_WARNINGS
    -DRADIOLIB_EXCLUDE_CC1101
    -DRADIOLIB_EXCLUDE_NRF24
    -DRADIOLIB_EXCLUDE_RF69
    -DRADIOLIB_EXCLUDE_SX1231
    -DRADIOLIB_EXCLUDE_SI443X
    -DRADIOLIB_EXCLUDE_RFM2X
    -DRADIOLIB_EXCLUDE_SX127X
    -DRADIOLIB_EXCLUDE_AFSK
    -DRADIOLIB_EXCLUDE_AX25
    -DRADIOLIB_EXCLUDE_HELLSCHREIBER
    -DRADIOLIB_EXCLUDE_MORSE
    -DRADIOLIB_EXCLUDE_RTTY
    -DRADIOLIB_EXCLUDE_SSTV
    -DRADIOLIB_EXCLUDE_DIRECT_RECEIVE
    -DRADIOLIB_EXCLUDE_APRS
    -DRADIOLIB_EXCLUDE_BELL

Steps

  1. Install Visual Studio Code and Python
  2. Install the PlatformIO IDE extension in VS Code
  3. Clone the repository and open the T-Deck folder in VS Code
  4. PlatformIO auto-installs dependencies on first build (may take a few minutes)
  5. In platformio.ini, uncomment the src_dir line for the example you want
  6. Click Build to compile, then Upload to upload

LVGL

T-Deck uses LVGL v8.4.0 with display flushing handled by the LilyGo_T_Deck object from LilyGo_LovyanGFX. The display is 320 × 240, no touch; interaction uses the trackball.

lv_conf.h

Place lv_conf.h in your Arduino libraries folder (same level as lvgl/). Minimum settings:

c
#define LV_COLOR_DEPTH     16
#define LV_HOR_RES_MAX    320
#define LV_VER_RES_MAX    240
#define LV_TICK_CUSTOM      1
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h"
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis())

Hello World

cpp
#define LILYGO_LGFX_USE_T_DECK
#include <LilyGo_LovyanGFX.h>
#include <lvgl.h>

LilyGo_T_Deck display;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[320 * 10];

void disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
    uint32_t w = area->x2 - area->x1 + 1;
    uint32_t h = area->y2 - area->y1 + 1;
    if (display.getStartCount() == 0) display.startWrite();
    display.pushImageDMA(area->x1, area->y1, w, h, (lgfx::swap565_t *)&color_p->full);
    lv_disp_flush_ready(disp);
}

void setup() {
    display.begin(1);

    lv_init();
    lv_disp_draw_buf_init(&draw_buf, buf, NULL, 320 * 10);

    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res   = 320;
    disp_drv.ver_res   = 240;
    disp_drv.flush_cb  = disp_flush;
    disp_drv.draw_buf  = &draw_buf;
    lv_disp_drv_register(&disp_drv);

    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Hello T-Deck!");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}

void loop() {
    lv_timer_handler();
    delay(5);
}

Trackball as LVGL Input

cpp
#include <lvgl.h>

static int16_t enc_diff = 0;
static lv_indev_state_t enc_state = LV_INDEV_STATE_REL;

// Call from ISR or polling increment/decrement enc_diff based on G01/G02
void trackball_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
    data->enc_diff = enc_diff;
    data->state    = enc_state;
    enc_diff = 0;
}

void setup() {
    // ... LVGL display init as above ...

    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type    = LV_INDEV_TYPE_ENCODER;
    indev_drv.read_cb = trackball_read;
    lv_indev_drv_register(&indev_drv);
}

Peripheral Examples

Power Enable

cpp
// Required when running on battery; safe to call on USB too.
pinMode(BOARD_POWERON, OUTPUT);
digitalWrite(BOARD_POWERON, HIGH);

Display (LovyanGFX)

cpp
#define LILYGO_LGFX_USE_T_DECK
#include <LilyGo_LovyanGFX.h>

LilyGo_T_Deck display;

void setup() {
    display.begin(1);   // landscape
    display.fillScreen(TFT_BLACK);
    display.setTextColor(TFT_WHITE, TFT_BLACK);
    display.drawString("T-Deck", 120, 110, &fonts::Font4);
}

LoRa (SX1262 秒RadioLib)

cpp
#include <RadioLib.h>

SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);

void setup() {
    pinMode(BOARD_SDCARD_CS, OUTPUT); digitalWrite(BOARD_SDCARD_CS, HIGH);
    pinMode(BOARD_TFT_CS,    OUTPUT); digitalWrite(BOARD_TFT_CS,    HIGH);
    SPI.begin(BOARD_SPI_SCK, BOARD_SPI_MISO, BOARD_SPI_MOSI);

    // 915 MHz, 125 kHz BW, SF7, CR4/5, private sync, 22 dBm
    int state = radio.begin(915.0, 125.0, 7, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
    if (state != RADIOLIB_ERR_NONE) {
        Serial.printf("LoRa init failed: %d\n", state);
    }
}

void loop() {
    // Transmit
    int state = radio.transmit("Hello LoRa");
    Serial.printf("TX: %d\n", state);
    delay(2000);
}

Keyboard (I²C)

cpp
#include <Wire.h>

#define KEYBOARD_ADDR 0x55

void setup() {
    Wire.begin(BOARD_I2C_SDA, BOARD_I2C_SCL);
    pinMode(BOARD_KEYBOARD_INT, INPUT_PULLUP);
}

void loop() {
    if (digitalRead(BOARD_KEYBOARD_INT) == LOW) {
        Wire.requestFrom(KEYBOARD_ADDR, 1);
        if (Wire.available()) {
            char key = Wire.read();
            Serial.printf("Key: %c\n", key);
        }
    }
}

Trackball

cpp
volatile int trackX = 0, trackY = 0;

void IRAM_ATTR onG01() { trackX++; }
void IRAM_ATTR onG02() { trackX--; }
void IRAM_ATTR onG03() { trackY++; }
void IRAM_ATTR onG04() { trackY--; }

void setup() {
    pinMode(BOARD_TBOX_G01, INPUT);
    pinMode(BOARD_TBOX_G02, INPUT);
    pinMode(BOARD_TBOX_G03, INPUT);
    pinMode(BOARD_TBOX_G04, INPUT);
    attachInterrupt(BOARD_TBOX_G01, onG01, CHANGE);
    attachInterrupt(BOARD_TBOX_G02, onG02, CHANGE);
    attachInterrupt(BOARD_TBOX_G03, onG03, CHANGE);
    attachInterrupt(BOARD_TBOX_G04, onG04, CHANGE);
}

void loop() {
    Serial.printf("Trackball X=%d Y=%d\n", trackX, trackY);
    delay(100);
}

Microphone (ES7210 I²S)

When the microphone is active, GPIO0 (BOOT / trackball center) is not available.

cpp
#include <driver/i2s.h>

void setup() {
    i2s_config_t cfg = {
        .mode                 = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
        .sample_rate          = 16000,
        .bits_per_sample      = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format       = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1,
        .dma_buf_count        = 4,
        .dma_buf_len          = 256,
        .use_apll             = false,
    };
    i2s_pin_config_t pins = {
        .mck_io_num   = BOARD_ES7210_MCLK,
        .bck_io_num   = BOARD_ES7210_SCK,
        .ws_io_num    = BOARD_ES7210_LRCK,
        .data_in_num  = BOARD_ES7210_DIN,
        .data_out_num = I2S_PIN_NO_CHANGE,
    };
    i2s_driver_install(I2S_NUM_0, &cfg, 0, NULL);
    i2s_set_pin(I2S_NUM_0, &pins);
}

GPS (MIA-M10Q TinyGPSPlus)

cpp
#include <TinyGPSPlus.h>

TinyGPSPlus gps;
HardwareSerial gpsSerial(1);

void setup() {
    Serial.begin(115200);
    gpsSerial.begin(9600, SERIAL_8N1, BOARD_GPS_RX_PIN, BOARD_GPS_TX_PIN);
}

void loop() {
    while (gpsSerial.available()) {
        gps.encode(gpsSerial.read());
    }
    if (gps.location.isUpdated()) {
        Serial.printf("Lat: %.6f  Lon: %.6f\n",
            gps.location.lat(), gps.location.lng());
    }
}

SD Card (SPI)

cpp
#include <SD.h>
#include <SPI.h>

void setup() {
    SPI.begin(BOARD_SPI_SCK, BOARD_SPI_MISO, BOARD_SPI_MOSI);
    if (!SD.begin(BOARD_SDCARD_CS)) {
        Serial.println("SD init failed");
        return;
    }
    Serial.printf("SD size: %llu MB\n", SD.cardSize() / (1024 * 1024));
}

ESP-IDF

The T-Deck repository does not include an official ESP-IDF project template. If you need IDF support:

  1. Create a new IDF project: idf.py create-project t-deck
  2. Set target: idf.py set-target esp32s3
  3. Use the pin definitions from the Pin Definitions section above
  4. Configure PSRAM in menuconfig: Component config -> ESP PSRAM -> Enable
  5. Refer to the Espressif ESP-IDF Programming Guide for driver APIs

MicroPython