Smart Panlee SC01 Plus

Smart Panlee SC01 Plus

The Smart Panlee SC01 Plus is an ESP32-S3 touchscreen display module with a 3.5-inch 320x480 ST7796 LCD, FT5x06 capacitive touch, direct I2S speaker playback, SPI microSD support, and published I2S / RS-485 / expansion GPIO pin maps.

The espp::SmartPanleeSc01Plus component provides a singleton hardware abstraction for the display, touch, backlight, audio output, and microSD card, while also exposing the board’s documented peripheral pins for application use. Its LCD path uses the object-style espp::St7796 controller integration.

API Reference

Header File

Classes

class SmartPanleeSc01Plus : public espp::BaseComponent

The SmartPanleeSc01Plus class provides an interface to the Smart Panlee SC01 Plus development board.

The class provides access to the following features:

  • ST7796 3.5-inch 320x480 display over an 8-bit Intel 8080 style bus

  • FT5x06 capacitive touch controller

  • PWM backlight control

  • I2S audio playback for the onboard speaker path

  • SPI microSD card mounting helpers

  • Published I2S, RS-485, and expansion pin maps

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

Example

  auto &board = espp::SmartPanleeSc01Plus::get();

  if (!board.initialize_lcd()) {
    logger.error("Failed to initialize LCD!");
    return;
  }

  if (!board.initialize_display(board.display_width() * 40)) {
    logger.error("Failed to initialize display!");
    return;
  }

  auto touch_callback = [&](const auto &touch) {
    static auto previous_touchpad_data = board.touchpad_convert(touch);
    auto touchpad_data = board.touchpad_convert(touch);
    if (touchpad_data != previous_touchpad_data) {
      previous_touchpad_data = touchpad_data;
      if (touchpad_data.num_touch_points > 0) {
        play_click(board);
        std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
        draw_circle(touchpad_data.x, touchpad_data.y, 10);
      }
    }
  };

  if (!board.initialize_audio()) {
    logger.warn("Audio initialization did not complete cleanly");
  } else {
    size_t wav_size = 0;
    size_t wav_sample_rate = 0;
    if (load_audio(wav_size, wav_sample_rate)) {
      logger.info("Loaded {} bytes of audio at {} Hz", wav_size, wav_sample_rate);
      board.audio_sample_rate(wav_sample_rate);
      board.volume(30.0f);
      board.mute(false);
    } else {
      logger.warn("Could not load the embedded click sound");
    }
  }

  board.brightness(80.0f);

  if (!board.initialize_sdcard()) {
    logger.info("No microSD card mounted");
  }

  auto i2s = board.i2s_pins();
  auto rs485 = board.rs485_pins();
  logger.info("I2S pins: bclk={}, ws={}, dout={}", i2s.bclk, i2s.ws, i2s.dout);
  logger.info("RS485 pins: rts={}, rxd={}, txd={}", rs485.rts, rs485.rxd, rs485.txd);

  lv_obj_t *bg = lv_obj_create(lv_screen_active());
  lv_obj_set_size(bg, board.display_width(), board.display_height());
  lv_obj_set_style_bg_color(bg, lv_color_make(8, 12, 24), 0);

  info_label = lv_label_create(lv_screen_active());
  lv_label_set_text(info_label, "Smart Panlee SC01 Plus\n\nTouch the screen to draw and play a "
                                "click.\nPress refresh to rotate.\nCheck serial output for SD "
                                "card, pin, and audio info.");
  lv_label_set_long_mode(info_label, LV_LABEL_LONG_WRAP);
  lv_obj_set_style_text_align(info_label, LV_TEXT_ALIGN_LEFT, 0);
  update_label_layout(static_cast<int>(board.display_width()));

  lv_obj_t *btn = lv_btn_create(lv_screen_active());
  lv_obj_set_size(btn, 56, 56);
  lv_obj_align(btn, LV_ALIGN_TOP_RIGHT, -12, 12);
  lv_obj_t *btn_label = lv_label_create(btn);
  lv_label_set_text(btn_label, LV_SYMBOL_REFRESH);
  lv_obj_align(btn_label, LV_ALIGN_CENTER, 0, 0);
  background = bg;
  if (!initialize_circle_layer(board.display_width(), board.display_height())) {
    logger.error("Failed to initialize circle layer!");
    return;
  }
  lv_obj_add_event_cb(
      btn,
      [](lv_event_t *event) {
        (void)event;
        rotate_display();
      },
      LV_EVENT_PRESSED, nullptr);

  lv_obj_set_scrollbar_mode(lv_screen_active(), LV_SCROLLBAR_MODE_OFF);
  lv_obj_clear_flag(lv_screen_active(), LV_OBJ_FLAG_SCROLLABLE);
  lv_obj_move_foreground(circle_layer);

  if (!board.initialize_touch(touch_callback)) {
    logger.warn("Touch initialization did not complete cleanly");
  }

  espp::Task lv_task({.callback = [](std::mutex &m, std::condition_variable &cv) -> bool {
                        {
                          std::lock_guard<std::recursive_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",
                          .stack_size_bytes = 6 * 1024,
                      }});
  lv_task.start();

Public Types

using Pixel = lv_color16_t

Alias for the pixel type used by the display.

using DisplayDriver = espp::St7796

Alias for the ST7796 display driver wrapper.

using TouchDriver = espp::Ft5x06

Alias for the FT5x06-family capacitive touch controller.

using TouchpadData = espp::TouchpadData

Alias for the touch data structure exposed to callers.

using touch_callback_t = std::function<void(const TouchpadData&)>

Callback type invoked when fresh touch data is available.

Public Functions

SmartPanleeSc01Plus(const SmartPanleeSc01Plus&) = delete

Deleted copy constructor.

SmartPanleeSc01Plus &operator=(const SmartPanleeSc01Plus&) = delete

Deleted copy assignment operator.

SmartPanleeSc01Plus(SmartPanleeSc01Plus&&) = delete

Deleted move constructor.

SmartPanleeSc01Plus &operator=(SmartPanleeSc01Plus&&) = delete

Deleted move assignment operator.

inline I2c &internal_i2c()

Get the internal I2C bus used for touch and board peripherals.

Returns:

Reference to the internal I2C instance.

inline espp::Interrupt &interrupts()

Get the interrupt manager used by the BSP.

Returns:

Reference to the interrupt manager.

bool initialize_touch(const touch_callback_t &callback = nullptr)

Initialize the touch controller and optional interrupt-driven callback.

Parameters:

callback – Callback invoked when touch data changes.

Returns:

True if touch support was initialized, false otherwise.

inline std::shared_ptr<TouchpadInput> touchpad_input() const

Get the LVGL touch input wrapper created by initialize_display().

Returns:

Shared pointer to the touchpad input wrapper, or nullptr if not initialized.

TouchpadData touchpad_data() const

Get the most recently cached touch data.

Returns:

Latest cached touchpad data.

void touchpad_read(uint8_t *num_touch_points, uint16_t *x, uint16_t *y, uint8_t *btn_state)

Read the cached touch data using the signature expected by TouchpadInput.

Parameters:
  • num_touch_points – Filled with the number of active touch points.

  • x – Filled with the x coordinate of the primary touch point.

  • y – Filled with the y coordinate of the primary touch point.

  • btn_state – Filled with the button state for LVGL compatibility.

TouchpadData touchpad_convert(const TouchpadData &data) const

Convert raw touch data into display-oriented coordinates.

Parameters:

data – Raw cached touch data.

Returns:

Touch data transformed for the current display rotation and inversion settings.

bool initialize_lcd()

Initialize the low-level LCD transport and backlight control.

Returns:

True if the LCD interface was initialized, false otherwise.

bool initialize_display(size_t pixel_buffer_size = 320 * 40)

Initialize the LVGL display wrapper.

Parameters:

pixel_buffer_sizeDisplay buffer size in pixels.

Returns:

True if the display wrapper was initialized, false otherwise.

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

Get the high-level LVGL display wrapper.

Returns:

Shared pointer to the display wrapper, or nullptr if not initialized.

inline const std::shared_ptr<DisplayDriver> &display_driver() const

Get a shared pointer to the low-level display driver.

Returns:

Shared pointer to the display driver, or a typed-null handle if not initialized.

void brightness(float brightness)

Set the display backlight brightness.

Parameters:

brightness – Brightness percentage in the range [0, 100].

float brightness() const

Get the display backlight brightness.

Returns:

Brightness percentage in the range [0, 100].

size_t rotated_display_width() const

Get the current display width after applying LVGL rotation.

Returns:

Rotated width in pixels.

size_t rotated_display_height() const

Get the current display height after applying LVGL rotation.

Returns:

Rotated height in pixels.

void write_lcd_lines(int xs, int ys, int xe, int ye, const uint8_t *data, uint32_t user_data)

Write lines to the LCD using the legacy scanline-oriented transport signature.

Note

This method queues the panel transfer asynchronously and may return before the write has completed.

Parameters:
  • xs – Inclusive start X coordinate.

  • ys – Inclusive start Y coordinate.

  • xe – Inclusive end X coordinate.

  • ye – Inclusive end Y coordinate.

  • data – Pointer to pixel payload data.

  • user_data – Opaque user data passed by the caller.

bool initialize_audio()

Initialize the speaker audio path using the board’s published I2S pins.

Returns:

True if audio playback support was initialized, false otherwise.

bool initialize_audio(uint32_t sample_rate)

Initialize the speaker audio path using the specified sample rate.

Parameters:

sample_rate – Audio sample rate in Hz.

Returns:

True if audio playback support was initialized, false otherwise.

bool initialize_audio(uint32_t sample_rate, const espp::Task::BaseConfig &task_config)

Initialize the speaker audio path using a custom task configuration.

Parameters:
  • sample_rate – Audio sample rate in Hz.

  • task_configTask configuration for the background audio pump.

Returns:

True if audio playback support was initialized, false otherwise.

void audio_sample_rate(uint32_t sample_rate)

Set the audio sample rate.

Parameters:

sample_rate – Audio sample rate in Hz.

uint32_t audio_sample_rate() const

Get the active audio sample rate.

Returns:

Audio sample rate in Hz, or 0 if audio is not initialized.

size_t audio_buffer_size() const

Get the internal playback buffer size.

Returns:

Audio buffer size in bytes.

void mute(bool mute)

Mute or unmute speaker playback.

Parameters:

mute – True to mute playback, false to unmute it.

bool is_muted() const

Query whether speaker playback is muted.

Returns:

True if playback is muted.

void volume(float volume)

Set the software playback volume.

Parameters:

volume – Volume percentage in the range [0, 100].

float volume() const

Get the software playback volume.

Returns:

Volume percentage in the range [0, 100].

void play_audio(std::span<const uint8_t> data)

Queue raw PCM audio bytes for playback.

Parameters:

data – Audio payload to play.

void play_audio(const uint8_t *data, uint32_t num_bytes)

Queue raw PCM audio bytes for playback.

Parameters:
  • data – Pointer to PCM audio bytes.

  • num_bytes – Number of bytes to play.

bool initialize_sdcard()

Initialize and mount the optional microSD card with default settings.

Returns:

True if the card was mounted successfully, false otherwise.

bool initialize_sdcard(const SdCardConfig &config)

Initialize and mount the optional microSD card with custom settings.

Parameters:

config – Mount configuration.

Returns:

True if the card was mounted successfully, false otherwise.

bool is_sd_card_available() const

Check whether the microSD card is currently mounted.

Returns:

True if the card is mounted and available.

bool get_sd_card_info(uint32_t *size_mb, uint32_t *free_mb) const

Query mounted microSD capacity and free space.

Parameters:
  • size_mb – Optional output for total capacity in megabytes.

  • free_mb – Optional output for free space in megabytes.

Returns:

True if card information was retrieved successfully, false otherwise.

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 SmartPanleeSc01Plus &get()

Access the singleton instance.

Returns:

Reference to the singleton board-support instance.

static inline constexpr I2sPins i2s_pins()

Get the published I2S pin mapping for the board.

Returns:

I2S pin mapping.

static inline constexpr SdCardPins sd_card_pins()

Get the published SPI microSD pin mapping for the board.

Returns:

MicroSD pin mapping.

static inline constexpr Rs485Pins rs485_pins()

Get the published RS-485 pin mapping for the board.

Returns:

RS-485 pin mapping.

static inline constexpr ExternalPins external_pins()

Get the published external expansion GPIO mapping for the board.

Returns:

External GPIO mapping.

static inline constexpr size_t bytes_per_pixel()

Get the number of bytes per pixel used by the panel.

Returns:

Bytes per pixel.

static inline constexpr size_t display_width()

Get the native display width.

Returns:

Width in pixels.

static inline constexpr size_t display_height()

Get the native display height.

Returns:

Height in pixels.

Public Static Attributes

static constexpr char mount_point[] = "/sdcard"

Mount point used when the optional microSD card is mounted.

struct ExternalPins

External expansion GPIOs published for the board.

Public Members

gpio_num_t io1

External GPIO 1.

gpio_num_t io2

External GPIO 2.

gpio_num_t io3

External GPIO 3.

gpio_num_t io4

External GPIO 4.

gpio_num_t io5

External GPIO 5.

gpio_num_t io6

External GPIO 6.

struct I2sPins

Published I2S pins for the SC01 Plus speaker interface.

Public Members

gpio_num_t mclk

Optional master clock pin.

gpio_num_t bclk

I2S bit clock pin.

gpio_num_t ws

I2S word-select / LRCLK pin.

gpio_num_t dout

I2S data-out pin.

gpio_num_t din

Optional I2S data-in pin.

struct Rs485Pins

RS-485 UART control and data pins published for the board.

Public Members

gpio_num_t rts

RS-485 direction-control pin.

gpio_num_t rxd

UART RX pin.

gpio_num_t txd

UART TX pin.

struct SdCardConfig

Configuration for mounting the optional microSD card.

Public Members

bool format_if_mount_failed = {false}

Format the card if mounting fails.

int max_files = {5}

Maximum number of simultaneously open files.

size_t allocation_unit_size = {16 * 1024}

FAT allocation unit size in bytes.

struct SdCardPins

SPI microSD pin mapping published for the board.

Public Members

gpio_num_t clk

SPI clock pin.

gpio_num_t mosi

SPI MOSI pin.

gpio_num_t miso

SPI MISO pin.

gpio_num_t cs

SPI chip-select pin.