LilyGo T-Dongle S3

T-Dongle S3

The LilyGo T-Dongle S3 is a development board for the ESP32-S3 module. It features a USB-A connector which doubles as a micro-SD card reader, a color LCD, an RGB LED, and a button.

The espp::TDongleS3 component provides a singleton hardware abstraction for initializing the display and LED subsystems.

API Reference

Header File

Classes

class TDongleS3 : public espp::BaseComponent

The TDongleS3 class provides an interface to the LilyGo T-Dongle-S3 ESP32-S3 development board.

The class provides access to the following features:

The class is a singleton and can be accessed using the get() method.

Example

  espp::TDongleS3 &tdongle = espp::TDongleS3::get();
  tdongle.set_log_level(espp::Logger::Verbosity::INFO);

  // initialize the LED
  if (!tdongle.initialize_led()) {
    logger.error("Failed to initialize led!");
    return;
  }
  // initialize the LCD
  if (!tdongle.initialize_lcd()) {
    logger.error("Failed to initialize LCD!");
    return;
  }
  // set the pixel buffer to be a full screen buffer
  static constexpr size_t pixel_buffer_size = tdongle.lcd_width() * tdongle.lcd_height();
  // initialize the LVGL display for the T-Dongle-S3
  if (!tdongle.initialize_display(pixel_buffer_size)) {
    logger.error("Failed to initialize display!");
    return;
  }

  // initialize the uSD card
  using SdCardConfig = espp::TDongleS3::SdCardConfig;
  SdCardConfig sdcard_config{};
  if (!tdongle.initialize_sdcard(sdcard_config)) {
    logger.warn("Failed to initialize SD card, continuing without it.");
  }

  // initialize the button, which we'll use to cycle the rotation of the display
  logger.info("Initializing the button");
  lv_obj_t *bg = nullptr;
  lv_obj_t *label = nullptr;
  static auto update_layout = [&]() {
    int width = tdongle.rotated_display_width();
    int height = tdongle.rotated_display_height();
    lv_obj_set_size(bg, width, height);
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
    if (circle_layer) {
      lv_obj_set_size(circle_layer, width, height);
      lv_obj_align(circle_layer, LV_ALIGN_CENTER, 0, 0);
      lv_obj_move_foreground(circle_layer);
      lv_obj_invalidate(circle_layer);
    }
  };
  auto on_button_pressed = [&](const auto &event) {
    if (event.active) {
      // lock the display mutex
      std::lock_guard<std::mutex> lock(lvgl_mutex);
      static auto rotation = LV_DISPLAY_ROTATION_0;
      rotation = static_cast<lv_display_rotation_t>((static_cast<int>(rotation) + 1) % 4);
      fmt::print("Setting rotation to {}\n", (int)rotation);
      lv_display_t *disp = lv_display_get_default();
      lv_disp_set_rotation(disp, rotation);
      update_layout();
    }
  };
  tdongle.initialize_button(on_button_pressed);

  // set the LED to be red
  espp::Hsv hsv(150.0f, 1.0f, 1.0f);
  float brightness = 5.0f; // 5% brightness
  tdongle.led(hsv, brightness);

  // set the background color to black
  bg = lv_obj_create(lv_screen_active());
  lv_obj_set_size(bg, tdongle.rotated_display_width(), tdongle.rotated_display_height());
  lv_obj_set_style_bg_color(bg, lv_color_make(0, 0, 0), 0);
  if (!initialize_circle_layer(tdongle.rotated_display_width(), tdongle.rotated_display_height())) {
    logger.error("Failed to initialize circle layer!");
    return;
  }

  // add text in the center of the screen
  label = lv_label_create(lv_screen_active());
  lv_label_set_text(label, "Drawing circles\nto the screen.");
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
  lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
  update_layout();

  lv_obj_move_foreground(circle_layer);

  // start a simple thread to do the lv_task_handler every 16ms
  espp::Task lv_task({.callback = [](std::mutex &m, std::condition_variable &cv) -> bool {
                        {
                          // lock the display mutex
                          std::lock_guard<std::mutex> lock(lvgl_mutex);
                          lv_task_handler();
                        }
                        std::unique_lock<std::mutex> lock(m);
                        cv.wait_for(lock, 16ms);
                        return false;
                      },
                      .task_config = {
                          .name = "lv_task",
                          .core_id = 1,
                      }});
  lv_task.start();

  // set the display brightness to be 75%
  tdongle.brightness(75.0f);

  // make a task to constantly shift the hue of the LED
  espp::Task led_task({.callback = [&](std::mutex &m, std::condition_variable &cv) -> bool {
                         static float hue = 0.0f;
                         hue += 1.0f;
                         if (hue >= 360.0f) {
                           hue = 0.0f;
                         }
                         espp::Hsv hsv(hue, 1.0f, 1.0f);
                         espp::TDongleS3::get().led(hsv, brightness);
                         std::unique_lock<std::mutex> lock(m);
                         cv.wait_for(lock, 25ms);
                         return false;
                       },
                       .task_config = {
                           .name = "led_task",
                       }});
  led_task.start();

  while (true) {
    auto start = esp_timer_get_time();
    // if there are 10 circles on the screen, clear them
    if (visible_circle_count >= MAX_CIRCLES) {
      // lock the lvgl mutex
      std::lock_guard<std::mutex> lock(lvgl_mutex);
      clear_circles();
    } else {
      // draw a circle of circles on the screen (just draw the next circle)
      int middle_x = tdongle.rotated_display_width() / 2;
      int middle_y = tdongle.rotated_display_height() / 2;
      static constexpr int radius = 30;
      float angle = visible_circle_count * 2.0f * M_PI / MAX_CIRCLES;
      int x = middle_x + radius * cos(angle);
      int y = middle_y + radius * sin(angle);
      // lock the lvgl mutex
      std::lock_guard<std::mutex> lock(lvgl_mutex);
      draw_circle(x, y, 5);
    }
    auto end = esp_timer_get_time();
    auto elapsed = end - start;
    std::this_thread::sleep_for(100ms - std::chrono::microseconds(elapsed));
  }

Public Types

using button_callback_t = espp::Interrupt::event_callback_fn

Alias for the button callback function.

using Pixel = lv_color16_t

Alias for the pixel type used by the T-Dongle-S3 display.

Public Functions

espp::Interrupt &interrupts()

Get a reference to the interrupts

Returns:

A reference to the interrupts

bool initialize_button(const button_callback_t &callback = nullptr)

Initialize the button

Parameters:

callback – The callback function to call when the button is pressed

Returns:

true if the button was successfully initialized, false otherwise

bool button_state() const

Get the button state

Returns:

The button state (true = button pressed, false = button released)

bool initialize_led()

Initialize the RGB LED

Returns:

true if the RGB LED was successfully initialized, false otherwise

std::shared_ptr<LedStrip> led() const

Get a shared pointer to the RGB LED

Returns:

A shared pointer to the RGB LED

bool led(const Hsv &hsv, float brightness = 100.0f)

Set the color of the LED

Parameters:
  • hsv – The color of the LED in HSV format

  • brightness – The brightness of the LED as a percentage (0 - 100)

Returns:

true if the color was successfully set, false otherwise

bool led(const Rgb &rgb, float brightness = 100.0f)

Set the color of the LED

Parameters:
  • rgb – The color of the LED in RGB format

  • brightness – The brightness of the LED as a percentage (0 - 100)

Returns:

true if the color was successfully set, false otherwise

bool initialize_lcd()

Initialize the LCD (low level display driver)

Returns:

true if the LCD was successfully initialized, false otherwise

bool initialize_display(size_t pixel_buffer_size)

Initialize the display (lvgl display driver)

Note

This will also allocate two full frame buffers in the SPIRAM

Parameters:

pixel_buffer_size – The size of the pixel buffer

Returns:

true if the display was successfully initialized, false otherwise

size_t rotated_display_width() const

Get the display width in pixels, according to the current orientation

Returns:

The display width in pixels, according to the current orientation

size_t rotated_display_height() const

Get the display height in pixels, according to the current orientation

Returns:

The display height in pixels, according to the current orientation

std::shared_ptr<Display<Pixel>> display() const

Get a shared pointer to the display

Returns:

A shared pointer to the display

void brightness(float brightness)

Set the brightness of the backlight

Note

This function will only work after initialize_lcd() has been called

Parameters:

brightness – The brightness of the backlight as a percentage (0 - 100)

float brightness() const

Get the brightness of the backlight

Note

This function will only work after initialize_lcd() has been called

Returns:

The brightness of the backlight as a percentage (0 - 100)

Pixel *vram0() const

Get the VRAM 0 pointer (DMA memory used by LVGL)

Note

This is the memory used by LVGL for rendering

Note

This is null unless initialize_display() has been called

Returns:

The VRAM 0 pointer

Pixel *vram1() const

Get the VRAM 1 pointer (DMA memory used by LVGL)

Note

This is the memory used by LVGL for rendering

Note

This is null unless initialize_display() has been called

Returns:

The VRAM 1 pointer

uint8_t *frame_buffer0() const

Get the frame buffer 0 pointer

Note

This memory is designed to be used by the application developer and is provided as a convenience. It is not used by the display driver.

Note

This is null unless initialize_display() has been called

Returns:

The frame buffer 0 pointer

uint8_t *frame_buffer1() const

Get the frame buffer 1 pointer

Note

This memory is designed to be used by the application developer and is provided as a convenience. It is not used by the display driver.

Note

This is null unless initialize_display() has been called

Returns:

The frame buffer 1 pointer

void write_lcd_frame(const uint16_t x, const uint16_t y, const uint16_t width, const uint16_t height, uint8_t *data)

Write a frame to the LCD

Note

This method queues the data to be written to the LCD, only blocking if there is an ongoing SPI transaction

Parameters:
  • x – The x coordinate

  • y – The y coordinate

  • width – The width of the frame, in pixels

  • height – The height of the frame, in pixels

  • data – The data to write

bool initialize_sdcard(const SdCardConfig &config)

Initialize the uSD card

Parameters:

config – The configuration for the uSD card

Returns:

True if the uSD card was initialized properly.

inline sdmmc_card_t *sdcard() const

Get the uSD card

Note

The uSD card is only available if it was successfully initialized and the mount point is valid

Returns:

A pointer to the uSD card

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

Public Static Functions

static inline TDongleS3 &get()

Access the singleton instance of the TDongleS3 class.

Returns:

Reference to the singleton instance of the TDongleS3 class

static inline constexpr size_t num_leds()

Get the number of LEDs in the strip

Returns:

The number of LEDs in the strip

static inline constexpr size_t lcd_width()

Get the width of the LCD in pixels

Returns:

The width of the LCD in pixels

static inline constexpr size_t lcd_height()

Get the height of the LCD in pixels

Returns:

The height of the LCD in pixels

static inline constexpr auto get_lcd_dc_gpio()

Get the GPIO pin for the LCD data/command signal

Returns:

The GPIO pin for the LCD data/command signal

Public Static Attributes

static constexpr size_t SPI_MAX_TRANSFER_BYTES = SPI_LL_DMA_MAX_BIT_LEN / 8

Maximum number of bytes that can be transferred in a single SPI transaction to the Display. 32k on the ESP32-S3.

static constexpr char mount_point[] = "/sdcard"

Mount point for the uSD card on the TDeck.

struct SdCardConfig

Configuration for the uSD card.

Public Members

bool format_if_mount_failed = false

Format the uSD card if mount failed.

int max_files = 5

The maximum number of files to open at once.

size_t allocation_unit_size = 2 * 1024

The allocation unit size in bytes.