From 317806feeae2a3867395e53d5ccca981227a1624 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Wed, 27 May 2026 17:03:45 -0500 Subject: [PATCH 1/7] feat(smartpanle-sc01-plus): Add `espp::SmartPanleeSc01Plus` BSP component for the Smart Panlee SC01 Plus dev board --- .github/workflows/build.yml | 2 + .github/workflows/upload_components.yml | 1 + components/display_drivers/idf_component.yml | 1 + components/display_drivers/include/st7796.hpp | 290 ++++++++++ .../smartpanle-sc01-plus/CMakeLists.txt | 6 + components/smartpanle-sc01-plus/Kconfig | 13 + components/smartpanle-sc01-plus/README.md | 37 ++ .../example/CMakeLists.txt | 21 + .../smartpanle-sc01-plus/example/README.md | 33 ++ .../example/main/CMakeLists.txt | 3 + .../main/smartpanle_sc01_plus_example.cpp | 240 ++++++++ .../example/partitions.csv | 5 + .../example/sdkconfig.defaults | 24 + .../smartpanle-sc01-plus/idf_component.yml | 30 + .../include/smartpanle-sc01-plus.hpp | 364 ++++++++++++ .../src/smartpanle-sc01-plus.cpp | 544 ++++++++++++++++++ doc/Doxyfile | 2 + doc/en/display/display_drivers.rst | 1 + doc/en/index.rst | 1 + doc/en/smartpanle_sc01_plus.rst | 23 + doc/en/smartpanle_sc01_plus_example.md | 2 + 21 files changed, 1643 insertions(+) mode change 100644 => 100755 components/display_drivers/idf_component.yml create mode 100644 components/display_drivers/include/st7796.hpp create mode 100755 components/smartpanle-sc01-plus/CMakeLists.txt create mode 100755 components/smartpanle-sc01-plus/Kconfig create mode 100755 components/smartpanle-sc01-plus/README.md create mode 100755 components/smartpanle-sc01-plus/example/CMakeLists.txt create mode 100755 components/smartpanle-sc01-plus/example/README.md create mode 100755 components/smartpanle-sc01-plus/example/main/CMakeLists.txt create mode 100644 components/smartpanle-sc01-plus/example/main/smartpanle_sc01_plus_example.cpp create mode 100755 components/smartpanle-sc01-plus/example/partitions.csv create mode 100755 components/smartpanle-sc01-plus/example/sdkconfig.defaults create mode 100755 components/smartpanle-sc01-plus/idf_component.yml create mode 100644 components/smartpanle-sc01-plus/include/smartpanle-sc01-plus.hpp create mode 100644 components/smartpanle-sc01-plus/src/smartpanle-sc01-plus.cpp mode change 100644 => 100755 doc/en/display/display_drivers.rst mode change 100644 => 100755 doc/en/index.rst create mode 100755 doc/en/smartpanle_sc01_plus.rst create mode 100755 doc/en/smartpanle_sc01_plus_example.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82595040e..054daabca 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -181,6 +181,8 @@ jobs: target: esp32s3 - path: 'components/serialization/example' target: esp32 + - path: 'components/smartpanle-sc01-plus/example' + target: esp32s3 - path: 'components/socket/example' target: esp32 - path: 'components/st25dv/example' diff --git a/.github/workflows/upload_components.yml b/.github/workflows/upload_components.yml index 7f2ef95a0..768787c4a 100755 --- a/.github/workflows/upload_components.yml +++ b/.github/workflows/upload_components.yml @@ -111,6 +111,7 @@ jobs: components/rx8130ce components/seeed-studio-round-display components/serialization + components/smartpanle-sc01-plus components/socket components/st25dv components/state_machine diff --git a/components/display_drivers/idf_component.yml b/components/display_drivers/idf_component.yml old mode 100644 new mode 100755 index 9c10035f3..8a0bf68b6 --- a/components/display_drivers/idf_component.yml +++ b/components/display_drivers/idf_component.yml @@ -17,6 +17,7 @@ tags: - ILI9341 - SH8601 - ST7789 + - ST7796 dependencies: idf: version: '>=5.0' diff --git a/components/display_drivers/include/st7796.hpp b/components/display_drivers/include/st7796.hpp new file mode 100644 index 000000000..9e54a073a --- /dev/null +++ b/components/display_drivers/include/st7796.hpp @@ -0,0 +1,290 @@ +#pragma once + +#include +#include +#include +#include + +#include "display_drivers.hpp" + +namespace espp { +/** + * @brief Display driver for the ST7796 display controller. + * + * This implementation follows the same lightweight callback-based structure as + * the other ESPP display drivers, with initialization values adapted from the + * Smart Panlee SC01 Plus board configuration. + */ +class St7796 { +public: + /// Supported command values used by this driver wrapper. + enum class Command : uint8_t { + slpout = 0x11, ///< Exit sleep mode. + invoff = 0x20, ///< Disable display inversion. + invon = 0x21, ///< Enable display inversion. + dispon = 0x29, ///< Turn the display on. + caset = 0x2a, ///< Set the column address window. + raset = 0x2b, ///< Set the row address window. + ramwr = 0x2c, ///< Write pixel data to RAM. + madctl = 0x36, ///< Configure memory addressing and rotation. + colmod = 0x3a, ///< Configure color depth / pixel format. + }; + + /// Initialize the display controller state and send the power-on sequence. + /// \param config Driver transport and orientation configuration. + static void initialize(const display_drivers::Config &config) { + write_command_ = config.write_command; + lcd_send_lines_ = config.lcd_send_lines; + reset_pin_ = config.reset_pin; + dc_pin_ = config.data_command_pin; + offset_x_ = config.offset_x; + offset_y_ = config.offset_y; + mirror_x_ = config.mirror_x; + mirror_y_ = config.mirror_y; + mirror_portrait_ = config.mirror_portrait; + swap_xy_ = config.swap_xy; + swap_color_order_ = config.swap_color_order; + + display_drivers::init_pins(reset_pin_, dc_pin_, config.reset_value); + + uint8_t madctl = 0; + if (swap_color_order_) { + madctl |= LCD_CMD_BGR_BIT; + } + if (mirror_x_) { + madctl |= LCD_CMD_MX_BIT; + } + if (mirror_y_) { + madctl |= LCD_CMD_MY_BIT; + } + if (swap_xy_) { + madctl |= LCD_CMD_MV_BIT; + } + + auto init_commands = std::to_array>({ + {(uint8_t)Command::slpout, {}, 120}, + {(uint8_t)Command::madctl, {madctl}}, + {(uint8_t)Command::colmod, {0x55}}, + {0xf0, {0xc3}}, + {0xf0, {0x96}}, + {0xb4, {0x01}}, + {0xb5, {0x1e}}, + {0xb6, {0x80, 0x22, 0x3b}}, + {0xb7, {0xc6}}, + {0xb9, {0x02, 0xe0}}, + {0xc0, {0x80, 0x16}}, + {0xc1, {0x19}}, + {0xc2, {0xa7}}, + {0xc5, {0x16}}, + {0xe8, {0x40, 0x8a, 0x00, 0x00, 0x29, 0x19, 0xa5, 0x33}}, + {0xe0, + {0xf0, 0x07, 0x0d, 0x04, 0x05, 0x14, 0x36, 0x54, 0x4c, 0x38, 0x13, 0x14, 0x2e, 0x34}}, + {0xe1, + {0xf0, 0x10, 0x14, 0x0e, 0x0c, 0x08, 0x35, 0x44, 0x4c, 0x26, 0x10, 0x12, 0x2c, 0x32}}, + {0xf0, {0x3c}}, + {0xf0, {0x69}, 120}, + {(uint8_t)Command::dispon}, + }); + + send_commands(init_commands); + + if (config.invert_colors) { + write_command_(static_cast(Command::invon), {}, 0); + } else { + write_command_(static_cast(Command::invoff), {}, 0); + } + } + + /// Update the controller addressing mode for a new display rotation. + /// \param rotation New display rotation. + static void rotate(const DisplayRotation &rotation) { + uint8_t data = 0; + if (swap_color_order_) { + data |= LCD_CMD_BGR_BIT; + } + if (mirror_x_) { + data |= LCD_CMD_MX_BIT; + } + if (mirror_y_) { + data |= LCD_CMD_MY_BIT; + } + if (swap_xy_) { + data |= LCD_CMD_MV_BIT; + } + switch (rotation) { + case DisplayRotation::LANDSCAPE: + break; + case DisplayRotation::PORTRAIT: + if (mirror_portrait_) { + data ^= (LCD_CMD_MX_BIT | LCD_CMD_MV_BIT); + } else { + data ^= (LCD_CMD_MY_BIT | LCD_CMD_MV_BIT); + } + break; + case DisplayRotation::LANDSCAPE_INVERTED: + data ^= (LCD_CMD_MY_BIT | LCD_CMD_MX_BIT); + break; + case DisplayRotation::PORTRAIT_INVERTED: + if (mirror_portrait_) { + data ^= (LCD_CMD_MY_BIT | LCD_CMD_MV_BIT); + } else { + data ^= (LCD_CMD_MX_BIT | LCD_CMD_MV_BIT); + } + break; + } + std::scoped_lock lock{spi_mutex_}; + write_command_(static_cast(Command::madctl), {&data, 1}, 0); + } + + /// Flush an LVGL drawing area to the display. + /// \param disp LVGL display pointer. + /// \param area Area to flush. + /// \param color_map RGB565 pixel data for the area. + static void flush(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map) { + fill(disp, area, color_map, (1 << (int)display_drivers::Flags::FLUSH_BIT)); + } + + /// Set the active drawing area using an LVGL area. + /// \param area Area to make active on the controller. + static void set_drawing_area(const lv_area_t *area) { + set_drawing_area(area->x1, area->y1, area->x2, area->y2); + } + + /// Set the active drawing area using raw coordinates. + /// \param xs Starting x coordinate. + /// \param ys Starting y coordinate. + /// \param xe Ending x coordinate. + /// \param ye Ending y coordinate. + static void set_drawing_area(size_t xs, size_t ys, size_t xe, size_t ye) { + std::array data; + + int offset_x = 0; + int offset_y = 0; + get_offset_rotated(offset_x, offset_y); + + uint16_t start_x = xs + offset_x; + uint16_t end_x = xe + offset_x; + uint16_t start_y = ys + offset_y; + uint16_t end_y = ye + offset_y; + + data[0] = (start_x >> 8) & 0xFF; + data[1] = start_x & 0xFF; + data[2] = (end_x >> 8) & 0xFF; + data[3] = end_x & 0xFF; + write_command_(static_cast(Command::caset), data, 0); + + data[0] = (start_y >> 8) & 0xFF; + data[1] = start_y & 0xFF; + data[2] = (end_y >> 8) & 0xFF; + data[3] = end_y & 0xFF; + write_command_(static_cast(Command::raset), data, 0); + } + + /// Fill an LVGL area using the supplied color buffer. + /// \param disp LVGL display pointer. + /// \param area Area to fill. + /// \param color_map RGB565 pixel data for the area. + /// \param flags Optional transport flags. + static void fill(lv_display_t *disp, const lv_area_t *area, uint8_t *color_map, + uint32_t flags = 0) { + std::scoped_lock lock{spi_mutex_}; + lv_draw_sw_rgb565_swap(color_map, lv_area_get_width(area) * lv_area_get_height(area)); + if (lcd_send_lines_) { + int offset_x = 0; + int offset_y = 0; + get_offset_rotated(offset_x, offset_y); + lcd_send_lines_(area->x1 + offset_x, area->y1 + offset_y, area->x2 + offset_x, + area->y2 + offset_y, color_map, flags); + } else { + set_drawing_area(area); + uint32_t size = lv_area_get_width(area) * lv_area_get_height(area); + write_command_(static_cast(Command::ramwr), {color_map, size * 2}, flags); + } + } + + /// Clear a rectangular region to a solid color. + /// \param x Starting x coordinate. + /// \param y Starting y coordinate. + /// \param width Width in pixels. + /// \param height Height in pixels. + /// \param color RGB565 fill color. + static void clear(size_t x, size_t y, size_t width, size_t height, uint16_t color = 0x0000) { + set_drawing_area(x, y, x + width, y + height); + + uint32_t size = width * height; + static constexpr int max_pixels_to_send = 1024; + uint16_t color_data[max_pixels_to_send]; + for (auto &pixel : color_data) { + pixel = color; + } + for (int i = 0; i < size; i += max_pixels_to_send) { + size_t num_pixels = std::min((int)(size - i), max_pixels_to_send); + write_command_(static_cast(Command::ramwr), + {reinterpret_cast(color_data), num_pixels * 2}, 0); + } + } + + /// Send a sequence of initialization commands to the controller. + /// \param commands Command list to send in order. + static void send_commands(std::span> commands) { + using namespace std::chrono_literals; + + for (const auto &[command, parameters, delay_ms] : commands) { + std::scoped_lock lock{spi_mutex_}; + write_command_(command, parameters, 0); + std::this_thread::sleep_for(delay_ms * 1ms); + } + } + + /// Set the controller coordinate offset used by this driver. + /// \param x X offset in pixels. + /// \param y Y offset in pixels. + static void set_offset(int x, int y) { + offset_x_ = x; + offset_y_ = y; + } + + /// Get the configured controller coordinate offset. + /// \param x Filled with the x offset. + /// \param y Filled with the y offset. + static void get_offset(int &x, int &y) { + x = offset_x_; + y = offset_y_; + } + + /// Get the coordinate offset after accounting for the current LVGL rotation. + /// \param x Filled with the rotated x offset. + /// \param y Filled with the rotated y offset. + static void get_offset_rotated(int &x, int &y) { + auto *display = lv_display_get_default(); + auto rotation = display ? lv_display_get_rotation(display) : LV_DISPLAY_ROTATION_0; + switch (rotation) { + case LV_DISPLAY_ROTATION_90: + case LV_DISPLAY_ROTATION_270: + x = offset_y_; + y = offset_x_; + break; + case LV_DISPLAY_ROTATION_0: + case LV_DISPLAY_ROTATION_180: + default: + x = offset_x_; + y = offset_y_; + break; + } + } + +protected: + static inline display_drivers::write_command_fn write_command_; + static inline display_drivers::send_lines_fn lcd_send_lines_; + static inline gpio_num_t reset_pin_; + static inline gpio_num_t dc_pin_; + static inline int offset_x_; + static inline int offset_y_; + static inline bool mirror_x_; + static inline bool mirror_y_; + static inline bool mirror_portrait_; + static inline bool swap_xy_; + static inline bool swap_color_order_; + static inline std::mutex spi_mutex_; +}; +} // namespace espp diff --git a/components/smartpanle-sc01-plus/CMakeLists.txt b/components/smartpanle-sc01-plus/CMakeLists.txt new file mode 100755 index 000000000..5dc307f54 --- /dev/null +++ b/components/smartpanle-sc01-plus/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + INCLUDE_DIRS "include" + SRC_DIRS "src" + REQUIRES driver esp_driver_i2s esp_lcd fatfs sdmmc base_component display display_drivers ft5x06 i2c input_drivers interrupt led task + REQUIRED_IDF_TARGETS "esp32s3" + ) diff --git a/components/smartpanle-sc01-plus/Kconfig b/components/smartpanle-sc01-plus/Kconfig new file mode 100755 index 000000000..877f14f56 --- /dev/null +++ b/components/smartpanle-sc01-plus/Kconfig @@ -0,0 +1,13 @@ +menu "Smart Panlee SC01 Plus Configuration" + config SMARTPANLEE_SC01_PLUS_INTERRUPT_STACK_SIZE + int "Interrupt stack size" + default 4096 + help + Size of the stack used for the touch interrupt handler task. + + config SMARTPANLEE_SC01_PLUS_AUDIO_TASK_STACK_SIZE + int "Audio task stack size" + default 4096 + help + Size of the stack used for the background audio playback task. +endmenu diff --git a/components/smartpanle-sc01-plus/README.md b/components/smartpanle-sc01-plus/README.md new file mode 100755 index 000000000..2b50c0c9b --- /dev/null +++ b/components/smartpanle-sc01-plus/README.md @@ -0,0 +1,37 @@ +# Smart Panlee SC01 Plus Board Support Package (BSP) Component + +[![Badge](https://components.espressif.com/components/espp/smartpanle-sc01-plus/badge.svg)](https://components.espressif.com/components/espp/smartpanle-sc01-plus) + +The Smart Panlee SC01 Plus (ZX3D50CE08S-USRC / WT32-SC01-PLUS class hardware) is +an ESP32-S3 touchscreen display module with a 3.5-inch 320x480 ST7796 LCD, an +FT5x06-family capacitive touch controller, onboard speaker wiring via I2S, SPI +microSD support, and RS-485 plus expansion GPIOs. + +The `espp::SmartPanleeSc01Plus` component provides a singleton hardware +abstraction for the board's display, touch controller, backlight, speaker audio +path, microSD card, and exposed peripheral pin mappings. + +## Supported Features + +- 3.5-inch 320x480 ST7796 display over 8080 8-bit parallel bus +- FT5x06 capacitive touch over I2C +- PWM backlight brightness control +- I2S speaker playback with software volume, mute, and sample-rate control +- SPI microSD card mounting helpers +- Exposed I2S, RS-485, and external GPIO pin maps for application use + +## Notes + +- This first BSP release focuses on the board features that are directly wired + and well documented in the published board references. +- The board's speaker path is driven directly over I2S using the published + `BCLK`, `LRCK`, and `DOUT` pins, without an external codec dependency in the + BSP. +- Touch handling follows the existing ESPP BSP pattern and uses the FT5x06 + controller plus the ESPP interrupt and LVGL touch-input helpers. + +## Example + +The [example](./example) shows how to initialize the display, register touch +input, play a click sound through the speaker path, adjust backlight +brightness, and optionally mount the microSD card. diff --git a/components/smartpanle-sc01-plus/example/CMakeLists.txt b/components/smartpanle-sc01-plus/example/CMakeLists.txt new file mode 100755 index 000000000..4eb25eb72 --- /dev/null +++ b/components/smartpanle-sc01-plus/example/CMakeLists.txt @@ -0,0 +1,21 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.20) + +set(ENV{IDF_COMPONENT_MANAGER} "0") +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +set(EXTRA_COMPONENT_DIRS + "../../../components/" +) + +set( + COMPONENTS + "main esptool_py smartpanle-sc01-plus display lvgl task" + CACHE STRING + "List of components to include" + ) + +project(smartpanle_sc01_plus_example) + +set(CMAKE_CXX_STANDARD 20) diff --git a/components/smartpanle-sc01-plus/example/README.md b/components/smartpanle-sc01-plus/example/README.md new file mode 100755 index 000000000..5da771406 --- /dev/null +++ b/components/smartpanle-sc01-plus/example/README.md @@ -0,0 +1,33 @@ +# Smart Panlee SC01 Plus BSP Example + +This example demonstrates the core functionality of the `espp::SmartPanleeSc01Plus` +BSP component on the Smart Panlee SC01 Plus touchscreen display module. + +## Features Demonstrated + +- ST7796 display initialization over the ESP32-S3 8-bit parallel LCD bus +- FT5x06 touch handling with LVGL integration +- I2S speaker playback with a bundled touch-click sound +- Backlight brightness control +- Optional microSD mounting and filesystem inspection +- Published peripheral pin maps for I2S, RS-485, and external GPIOs + +## Hardware Required + +- Smart Panlee SC01 Plus / WT32-SC01-PLUS compatible board +- USB-C cable for programming and power +- microSD card (optional) + +## Build and Flash + +Build the project and flash it to the board, then run monitor tool to view +serial output and verify that touching the screen draws circles and plays the +embedded click sound: + +``` +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) diff --git a/components/smartpanle-sc01-plus/example/main/CMakeLists.txt b/components/smartpanle-sc01-plus/example/main/CMakeLists.txt new file mode 100755 index 000000000..d5f4043b8 --- /dev/null +++ b/components/smartpanle-sc01-plus/example/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRC_DIRS "." + INCLUDE_DIRS "." + EMBED_TXTFILES "../../../t-deck/example/main/click.wav") diff --git a/components/smartpanle-sc01-plus/example/main/smartpanle_sc01_plus_example.cpp b/components/smartpanle-sc01-plus/example/main/smartpanle_sc01_plus_example.cpp new file mode 100644 index 000000000..a6d7b47d6 --- /dev/null +++ b/components/smartpanle-sc01-plus/example/main/smartpanle_sc01_plus_example.cpp @@ -0,0 +1,240 @@ +/** + * @file smartpanle_sc01_plus_example.cpp + * @brief Smart Panlee SC01 Plus BSP Example + */ + +#include +#include +#include + +#include "smartpanle-sc01-plus.hpp" + +#include "task.hpp" + +using namespace std::chrono_literals; + +static constexpr size_t MAX_CIRCLES = 100; +static std::vector circles; +static size_t next_circle_index = 0; +static std::vector audio_bytes; +static std::recursive_mutex lvgl_mutex; +static lv_obj_t *background = nullptr; +static void draw_circle(int x0, int y0, int radius); +static void clear_circles(); +static void initialize_circles(); +static void rotate_display(); +static bool load_audio(size_t &out_size, size_t &out_sample_rate); +static void play_click(espp::SmartPanleeSc01Plus &board); + +extern "C" void app_main(void) { + espp::Logger logger({.tag = "SC01 Plus Example", .level = espp::Logger::Verbosity::INFO}); + logger.info("Starting example!"); + circles.reserve(MAX_CIRCLES); + + //! [smartpanle sc01 plus 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 lock(lvgl_mutex); + draw_circle(touchpad_data.x, touchpad_data.y, 10); + } + } + }; + + if (!board.initialize_touch(touch_callback)) { + logger.warn("Touch initialization did not complete cleanly"); + } + + 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); + + lv_obj_t *label = lv_label_create(lv_screen_active()); + lv_label_set_text(label, "Smart Panlee SC01 Plus\n\nTouch the screen to draw and play a click.\n" + "Press refresh to rotate.\nCheck serial output for SD card, pin, and " + "audio info."); + lv_obj_align(label, LV_ALIGN_TOP_LEFT, 16, 16); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_LEFT, 0); + + 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; + initialize_circles(); + 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); + + espp::Task lv_task({.callback = [](std::mutex &m, std::condition_variable &cv) -> bool { + { + std::lock_guard lock(lvgl_mutex); + lv_task_handler(); + } + std::unique_lock lock(m); + cv.wait_for(lock, 16ms); + return false; + }, + .task_config = { + .name = "lv_task", + .stack_size_bytes = 6 * 1024, + }}); + lv_task.start(); + //! [smartpanle sc01 plus example] + + while (true) { + std::this_thread::sleep_for(1s); + if (board.is_sd_card_available()) { + uint32_t size_mb = 0; + uint32_t free_mb = 0; + if (board.get_sd_card_info(&size_mb, &free_mb)) { + logger.info("microSD: size={} MB free={} MB", size_mb, free_mb); + } + } + } +} + +static void draw_circle(int x0, int y0, int radius) { + if (circles.empty()) { + return; + } + + auto *circle = circles[next_circle_index]; + lv_obj_set_size(circle, radius * 2, radius * 2); + lv_obj_align(circle, LV_ALIGN_TOP_LEFT, x0 - radius, y0 - radius); + lv_obj_clear_flag(circle, LV_OBJ_FLAG_HIDDEN); + + next_circle_index = (next_circle_index + 1) % circles.size(); +} + +static void clear_circles() { + for (auto *circle : circles) { + lv_obj_add_flag(circle, LV_OBJ_FLAG_HIDDEN); + } + next_circle_index = 0; +} + +static void initialize_circles() { + if (!circles.empty()) { + return; + } + + circles.reserve(MAX_CIRCLES); + for (size_t i = 0; i < MAX_CIRCLES; ++i) { + auto *circle = lv_obj_create(lv_screen_active()); + lv_obj_remove_style_all(circle); + lv_obj_set_size(circle, 20, 20); + lv_obj_set_style_radius(circle, LV_RADIUS_CIRCLE, 0); + lv_obj_set_style_bg_color(circle, lv_palette_main(LV_PALETTE_CYAN), 0); + lv_obj_set_style_bg_opa(circle, LV_OPA_70, 0); + lv_obj_set_style_border_width(circle, 0, 0); + lv_obj_clear_flag(circle, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_flag(circle, LV_OBJ_FLAG_HIDDEN); + circles.push_back(circle); + } +} + +static void rotate_display() { + auto &board = espp::SmartPanleeSc01Plus::get(); + std::lock_guard lock(lvgl_mutex); + clear_circles(); + static auto rotation = LV_DISPLAY_ROTATION_0; + rotation = static_cast((static_cast(rotation) + 1) % 4); + auto *display = lv_display_get_default(); + lv_disp_set_rotation(display, rotation); + if (background) { + lv_obj_set_size(background, board.rotated_display_width(), board.rotated_display_height()); + } +} + +static bool load_audio(size_t &out_size, size_t &out_sample_rate) { + if (!audio_bytes.empty()) { + out_size = audio_bytes.size(); + return true; + } + + extern const uint8_t click_wav_start[] asm("_binary_click_wav_start"); + extern const uint8_t click_wav_end[] asm("_binary_click_wav_end"); + audio_bytes = std::vector(click_wav_start, click_wav_end); + if (audio_bytes.size() < 44) { + audio_bytes.clear(); + return false; + } + + auto sample_rate = *(reinterpret_cast(&audio_bytes[24])); + if (audio_bytes.size() > 44) { + audio_bytes.erase(audio_bytes.begin(), audio_bytes.begin() + 44); + } + out_size = audio_bytes.size(); + out_sample_rate = sample_rate; + return true; +} + +static void play_click(espp::SmartPanleeSc01Plus &board) { + if (audio_bytes.empty()) { + return; + } + + auto audio_buffer_size = board.audio_buffer_size(); + if (audio_buffer_size == 0) { + return; + } + + size_t offset = 0; + while (offset < audio_bytes.size()) { + auto bytes_to_play = std::min(audio_buffer_size, audio_bytes.size() - offset); + board.play_audio(audio_bytes.data() + offset, static_cast(bytes_to_play)); + offset += bytes_to_play; + } +} diff --git a/components/smartpanle-sc01-plus/example/partitions.csv b/components/smartpanle-sc01-plus/example/partitions.csv new file mode 100755 index 000000000..030cf611b --- /dev/null +++ b/components/smartpanle-sc01-plus/example/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size +nvs, data, nvs, 0xa000, 0x6000 +phy_init, data, phy, 0x10000, 0x1000 +factory, app, factory, 0x20000, 0x300000 +storage, data, spiffs, 0x320000, 0x4E0000 diff --git a/components/smartpanle-sc01-plus/example/sdkconfig.defaults b/components/smartpanle-sc01-plus/example/sdkconfig.defaults new file mode 100755 index 000000000..58f8b5d0f --- /dev/null +++ b/components/smartpanle-sc01-plus/example/sdkconfig.defaults @@ -0,0 +1,24 @@ +CONFIG_IDF_TARGET="esp32s3" + +CONFIG_FREERTOS_HZ=1000 +CONFIG_COMPILER_OPTIMIZATION_PERF=y + +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="8MB" +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y + +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=12288 +CONFIG_ESP_TIMER_TASK_STACK_SIZE=6144 + +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_OFFSET=0x9000 +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" + +CONFIG_SMARTPANLEE_SC01_PLUS_INTERRUPT_STACK_SIZE=4096 + +CONFIG_LV_DEF_REFR_PERIOD=16 +CONFIG_LV_USE_THEME_DEFAULT=y +CONFIG_LV_THEME_DEFAULT_DARK=y +CONFIG_LV_THEME_DEFAULT_GROW=y +CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=30 diff --git a/components/smartpanle-sc01-plus/idf_component.yml b/components/smartpanle-sc01-plus/idf_component.yml new file mode 100755 index 000000000..0a7a4f5ce --- /dev/null +++ b/components/smartpanle-sc01-plus/idf_component.yml @@ -0,0 +1,30 @@ +## IDF Component Manager Manifest File +license: "MIT" +description: "Smart Panlee SC01 Plus board support package (BSP) component for ESP-IDF" +url: "https://github.com/esp-cpp/espp/tree/main/components/smartpanle-sc01-plus" +repository: "git://github.com/esp-cpp/espp.git" +maintainers: + - William Emfinger +documentation: "https://esp-cpp.github.io/espp/smartpanle_sc01_plus.html" +examples: + - path: example +tags: + - cpp + - Component + - BSP + - Smart Panlee + - SC01 Plus +dependencies: + idf: + version: ">=5.0" + espp/base_component: ">=1.0" + espp/display: ">=1.0" + espp/display_drivers: ">=1.0" + espp/ft5x06: ">=1.0" + espp/i2c: ">=1.0" + espp/input_drivers: ">=1.0" + espp/interrupt: ">=1.0" + espp/led: ">=1.0" + espp/task: ">=1.0" +targets: + - esp32s3 diff --git a/components/smartpanle-sc01-plus/include/smartpanle-sc01-plus.hpp b/components/smartpanle-sc01-plus/include/smartpanle-sc01-plus.hpp new file mode 100644 index 000000000..1b2a7dbd9 --- /dev/null +++ b/components/smartpanle-sc01-plus/include/smartpanle-sc01-plus.hpp @@ -0,0 +1,364 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base_component.hpp" +#include "display.hpp" +#include "ft5x06.hpp" +#include "i2c.hpp" +#include "interrupt.hpp" +#include "led.hpp" +#include "st7796.hpp" +#include "task.hpp" +#include "touchpad_input.hpp" + +namespace espp { +/// 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. +/// +/// \section smartpanle_sc01_plus_example Example +/// \snippet smartpanle_sc01_plus_example.cpp smartpanle sc01 plus example +class SmartPanleeSc01Plus : public BaseComponent { +public: + /// Alias for the pixel type used by the display. + using Pixel = lv_color16_t; + /// Alias for the ST7796 display driver wrapper. + using DisplayDriver = espp::St7796; + /// Alias for the FT5x06-family capacitive touch controller. + using TouchDriver = espp::Ft5x06; + /// Alias for the touch data structure exposed to callers. + using TouchpadData = espp::TouchpadData; + /// Callback type invoked when fresh touch data is available. + using touch_callback_t = std::function; + + /// Published I2S pins for the SC01 Plus speaker interface. + struct I2sPins { + 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. + }; + + /// SPI microSD pin mapping published for the board. + struct SdCardPins { + 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. + }; + + /// RS-485 UART control and data pins published for the board. + struct Rs485Pins { + gpio_num_t rts; ///< RS-485 direction-control pin. + gpio_num_t rxd; ///< UART RX pin. + gpio_num_t txd; ///< UART TX pin. + }; + + /// External expansion GPIOs published for the board. + struct ExternalPins { + 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. + }; + + /// Configuration for mounting the optional microSD card. + struct SdCardConfig { + 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. + }; + + /// Mount point used when the optional microSD card is mounted. + static constexpr char mount_point[] = "/sdcard"; + + /// Access the singleton instance. + /// \return Reference to the singleton board-support instance. + static SmartPanleeSc01Plus &get() { + static SmartPanleeSc01Plus instance; + return instance; + } + + /// Deleted copy constructor. + SmartPanleeSc01Plus(const SmartPanleeSc01Plus &) = delete; + /// Deleted copy assignment operator. + SmartPanleeSc01Plus &operator=(const SmartPanleeSc01Plus &) = delete; + /// Deleted move constructor. + SmartPanleeSc01Plus(SmartPanleeSc01Plus &&) = delete; + /// Deleted move assignment operator. + SmartPanleeSc01Plus &operator=(SmartPanleeSc01Plus &&) = delete; + + /// Get the published I2S pin mapping for the board. + /// \return I2S pin mapping. + static constexpr I2sPins i2s_pins() { + return {GPIO_NUM_NC, GPIO_NUM_36, GPIO_NUM_35, GPIO_NUM_37, GPIO_NUM_NC}; + } + + /// Get the published SPI microSD pin mapping for the board. + /// \return MicroSD pin mapping. + static constexpr SdCardPins sd_card_pins() { + return {GPIO_NUM_39, GPIO_NUM_40, GPIO_NUM_38, GPIO_NUM_41}; + } + + /// Get the published RS-485 pin mapping for the board. + /// \return RS-485 pin mapping. + static constexpr Rs485Pins rs485_pins() { return {GPIO_NUM_2, GPIO_NUM_1, GPIO_NUM_42}; } + + /// Get the published external expansion GPIO mapping for the board. + /// \return External GPIO mapping. + static constexpr ExternalPins external_pins() { + return {GPIO_NUM_10, GPIO_NUM_11, GPIO_NUM_12, GPIO_NUM_13, GPIO_NUM_14, GPIO_NUM_21}; + } + + /// Get the internal I2C bus used for touch and board peripherals. + /// \return Reference to the internal I2C instance. + I2c &internal_i2c() { return internal_i2c_; } + /// Get the interrupt manager used by the BSP. + /// \return Reference to the interrupt manager. + espp::Interrupt &interrupts() { return interrupts_; } + + /// Initialize the touch controller and optional interrupt-driven callback. + /// \param callback Callback invoked when touch data changes. + /// \return True if touch support was initialized, false otherwise. + bool initialize_touch(const touch_callback_t &callback = nullptr); + /// Get the LVGL touch input wrapper created by initialize_display(). + /// \return Shared pointer to the touchpad input wrapper, or nullptr if not initialized. + std::shared_ptr touchpad_input() const { return touchpad_input_; } + /// Get the most recently cached touch data. + /// \return Latest cached touchpad data. + TouchpadData touchpad_data() const; + /// Read the cached touch data using the signature expected by TouchpadInput. + /// \param num_touch_points Filled with the number of active touch points. + /// \param x Filled with the x coordinate of the primary touch point. + /// \param y Filled with the y coordinate of the primary touch point. + /// \param btn_state Filled with the button state for LVGL compatibility. + void touchpad_read(uint8_t *num_touch_points, uint16_t *x, uint16_t *y, uint8_t *btn_state); + /// Convert raw touch data into display-oriented coordinates. + /// \param data Raw cached touch data. + /// \return Touch data transformed for the current display rotation and inversion settings. + TouchpadData touchpad_convert(const TouchpadData &data) const; + + /// Initialize the low-level LCD transport and backlight control. + /// \return True if the LCD interface was initialized, false otherwise. + bool initialize_lcd(); + /// Initialize the LVGL display wrapper. + /// \param pixel_buffer_size Display buffer size in pixels. + /// \return True if the display wrapper was initialized, false otherwise. + bool initialize_display(size_t pixel_buffer_size = 320 * 40); + /// Get the high-level LVGL display wrapper. + /// \return Shared pointer to the display wrapper, or nullptr if not initialized. + std::shared_ptr> display() const { return display_; } + /// Set the display backlight brightness. + /// \param brightness Brightness percentage in the range [0, 100]. + void brightness(float brightness); + /// Get the display backlight brightness. + /// \return Brightness percentage in the range [0, 100]. + float brightness() const; + + /// Get the number of bytes per pixel used by the panel. + /// \return Bytes per pixel. + static constexpr size_t bytes_per_pixel() { return sizeof(Pixel); } + /// Get the native display width. + /// \return Width in pixels. + static constexpr size_t display_width() { return lcd_width_; } + /// Get the native display height. + /// \return Height in pixels. + static constexpr size_t display_height() { return lcd_height_; } + /// Get the current display width after applying LVGL rotation. + /// \return Rotated width in pixels. + size_t rotated_display_width() const; + /// Get the current display height after applying LVGL rotation. + /// \return Rotated height in pixels. + size_t rotated_display_height() const; + + /// Initialize the speaker audio path using the board's published I2S pins. + /// \return True if audio playback support was initialized, false otherwise. + bool initialize_audio(); + /// Initialize the speaker audio path using the specified sample rate. + /// \param sample_rate Audio sample rate in Hz. + /// \return True if audio playback support was initialized, false otherwise. + bool initialize_audio(uint32_t sample_rate); + /// Initialize the speaker audio path using a custom task configuration. + /// \param sample_rate Audio sample rate in Hz. + /// \param task_config Task configuration for the background audio pump. + /// \return True if audio playback support was initialized, false otherwise. + bool initialize_audio(uint32_t sample_rate, const espp::Task::BaseConfig &task_config); + /// Set the audio sample rate. + /// \param sample_rate Audio sample rate in Hz. + void audio_sample_rate(uint32_t sample_rate); + /// Get the active audio sample rate. + /// \return Audio sample rate in Hz, or 0 if audio is not initialized. + uint32_t audio_sample_rate() const; + /// Get the internal playback buffer size. + /// \return Audio buffer size in bytes. + size_t audio_buffer_size() const; + /// Mute or unmute speaker playback. + /// \param mute True to mute playback, false to unmute it. + void mute(bool mute); + /// Query whether speaker playback is muted. + /// \return True if playback is muted. + bool is_muted() const; + /// Set the software playback volume. + /// \param volume Volume percentage in the range [0, 100]. + void volume(float volume); + /// Get the software playback volume. + /// \return Volume percentage in the range [0, 100]. + float volume() const; + /// Queue raw PCM audio bytes for playback. + /// \param data Audio payload to play. + void play_audio(std::span data); + /// Queue raw PCM audio bytes for playback. + /// \param data Pointer to PCM audio bytes. + /// \param num_bytes Number of bytes to play. + void play_audio(const uint8_t *data, uint32_t num_bytes); + + /// Initialize and mount the optional microSD card with default settings. + /// \return True if the card was mounted successfully, false otherwise. + bool initialize_sdcard(); + /// Initialize and mount the optional microSD card with custom settings. + /// \param config Mount configuration. + /// \return True if the card was mounted successfully, false otherwise. + bool initialize_sdcard(const SdCardConfig &config); + /// Check whether the microSD card is currently mounted. + /// \return True if the card is mounted and available. + bool is_sd_card_available() const; + /// Query mounted microSD capacity and free space. + /// \param size_mb Optional output for total capacity in megabytes. + /// \param free_mb Optional output for free space in megabytes. + /// \return True if card information was retrieved successfully, false otherwise. + bool get_sd_card_info(uint32_t *size_mb, uint32_t *free_mb) const; + +protected: + SmartPanleeSc01Plus(); + + static bool panel_io_color_trans_done(esp_lcd_panel_io_handle_t panel_io, + esp_lcd_panel_io_event_data_t *edata, void *user_ctx); + static espp::Task::BaseConfig default_audio_task_config(); + bool initialize_i2s(uint32_t default_audio_rate); + bool audio_task_callback(std::mutex &m, std::condition_variable &cv, bool &task_notified); + bool update_touch(); + void write_command(uint8_t command, std::span parameters, uint32_t user_data); + void write_lcd_lines(int xs, int ys, int xe, int ye, const uint8_t *data, uint32_t user_data); + + static constexpr auto internal_i2c_port = I2C_NUM_1; + static constexpr auto internal_i2c_clock_speed = 400 * 1000; + static constexpr gpio_num_t internal_i2c_sda = GPIO_NUM_6; + static constexpr gpio_num_t internal_i2c_scl = GPIO_NUM_5; + + static constexpr size_t lcd_width_ = 320; + static constexpr size_t lcd_height_ = 480; + static constexpr int lcd_clock_speed_hz = 40 * 1000 * 1000; + static constexpr size_t lcd_max_transfer_bytes = lcd_width_ * 40 * sizeof(Pixel); + static constexpr gpio_num_t lcd_reset_io = GPIO_NUM_4; + static constexpr gpio_num_t lcd_backlight_io = GPIO_NUM_45; + static constexpr gpio_num_t lcd_dc_io = GPIO_NUM_0; + static constexpr gpio_num_t lcd_wr_io = GPIO_NUM_47; + static constexpr gpio_num_t lcd_cs_io = GPIO_NUM_NC; + static constexpr gpio_num_t lcd_d0_io = GPIO_NUM_9; + static constexpr gpio_num_t lcd_d1_io = GPIO_NUM_46; + static constexpr gpio_num_t lcd_d2_io = GPIO_NUM_3; + static constexpr gpio_num_t lcd_d3_io = GPIO_NUM_8; + static constexpr gpio_num_t lcd_d4_io = GPIO_NUM_18; + static constexpr gpio_num_t lcd_d5_io = GPIO_NUM_17; + static constexpr gpio_num_t lcd_d6_io = GPIO_NUM_16; + static constexpr gpio_num_t lcd_d7_io = GPIO_NUM_15; + static constexpr bool backlight_value = true; + static constexpr bool invert_colors = true; + static constexpr auto rotation = espp::DisplayRotation::LANDSCAPE; + static constexpr bool mirror_x = false; + static constexpr bool mirror_y = false; + static constexpr bool swap_xy = false; + static constexpr bool swap_color_order = true; + + static constexpr bool touch_swap_xy = false; + static constexpr bool touch_invert_x = false; + static constexpr bool touch_invert_y = false; + static constexpr gpio_num_t touch_interrupt = GPIO_NUM_7; + + static constexpr auto i2s_port = I2S_NUM_0; + static constexpr int audio_num_channels = 2; + static constexpr int audio_num_bytes_per_channel = 2; + static constexpr int audio_update_frequency = 60; + static constexpr int calc_audio_buffer_size(int sample_rate) { + return sample_rate * audio_num_channels * audio_num_bytes_per_channel / audio_update_frequency; + } + + I2c internal_i2c_{I2c::Config{.port = internal_i2c_port, + .sda_io_num = internal_i2c_sda, + .scl_io_num = internal_i2c_scl, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .clk_speed = internal_i2c_clock_speed}}; + + espp::Interrupt::PinConfig touch_interrupt_pin_{.gpio_num = touch_interrupt, + .callback = + [this](const auto &event) { + if (!event.active) { + return; + } + if (update_touch() && touch_callback_) { + touch_callback_(touchpad_data()); + } + }, + .active_level = espp::Interrupt::ActiveLevel::LOW, + .interrupt_type = + espp::Interrupt::Type::FALLING_EDGE, + .pullup_enabled = true}; + + espp::Interrupt interrupts_{ + {.interrupts = {}, + .task_config = {.name = "sc01+ interrupts", + .stack_size_bytes = CONFIG_SMARTPANLEE_SC01_PLUS_INTERRUPT_STACK_SIZE}}}; + + std::shared_ptr touch_driver_; + std::shared_ptr touchpad_input_; + mutable std::recursive_mutex touchpad_data_mutex_; + TouchpadData touchpad_data_; + touch_callback_t touch_callback_{nullptr}; + + std::shared_ptr> display_; + std::shared_ptr backlight_; + std::vector backlight_channel_configs_; + esp_lcd_i80_bus_handle_t lcd_bus_{nullptr}; + esp_lcd_panel_io_handle_t panel_io_{nullptr}; + + std::atomic audio_initialized_{false}; + std::atomic volume_{50.0f}; + std::atomic mute_{false}; + std::unique_ptr audio_task_{nullptr}; + i2s_chan_handle_t audio_tx_handle_{nullptr}; + StreamBufferHandle_t audio_tx_stream_{nullptr}; + i2s_std_config_t audio_std_cfg_{}; + std::vector audio_tx_buffer_; + + bool sd_card_initialized_{false}; + sdmmc_card_t *sdcard_{nullptr}; +}; +} // namespace espp diff --git a/components/smartpanle-sc01-plus/src/smartpanle-sc01-plus.cpp b/components/smartpanle-sc01-plus/src/smartpanle-sc01-plus.cpp new file mode 100644 index 000000000..32e8fb833 --- /dev/null +++ b/components/smartpanle-sc01-plus/src/smartpanle-sc01-plus.cpp @@ -0,0 +1,544 @@ +#include "smartpanle-sc01-plus.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace espp { + +SmartPanleeSc01Plus::SmartPanleeSc01Plus() + : BaseComponent("SmartPanleeSc01Plus") {} + +bool SmartPanleeSc01Plus::initialize_touch(const SmartPanleeSc01Plus::touch_callback_t &callback) { + if (touch_driver_) { + logger_.warn("Touch already initialized, not initializing again!"); + return false; + } + + touch_driver_ = std::make_shared(TouchDriver::Config{ + .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3), + .read_register = + std::bind(&espp::I2c::read_at_register, &internal_i2c_, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), + .log_level = espp::Logger::Verbosity::WARN}); + + touch_callback_ = callback; + interrupts_.add_interrupt(touch_interrupt_pin_); + update_touch(); + return true; +} + +SmartPanleeSc01Plus::TouchpadData SmartPanleeSc01Plus::touchpad_data() const { + std::lock_guard lock(touchpad_data_mutex_); + return touchpad_data_; +} + +void SmartPanleeSc01Plus::touchpad_read(uint8_t *num_touch_points, uint16_t *x, uint16_t *y, + uint8_t *btn_state) { + std::lock_guard lock(touchpad_data_mutex_); + *num_touch_points = touchpad_data_.num_touch_points; + *x = touchpad_data_.x; + *y = touchpad_data_.y; + *btn_state = touchpad_data_.btn_state; +} + +SmartPanleeSc01Plus::TouchpadData +SmartPanleeSc01Plus::touchpad_convert(const SmartPanleeSc01Plus::TouchpadData &data) const { + TouchpadData temp_data; + temp_data.num_touch_points = data.num_touch_points; + temp_data.x = data.x; + temp_data.y = data.y; + temp_data.btn_state = data.btn_state; + if (temp_data.num_touch_points == 0) { + return temp_data; + } + if (touch_swap_xy) { + std::swap(temp_data.x, temp_data.y); + } + if (touch_invert_x) { + temp_data.x = lcd_width_ - (temp_data.x + 1); + } + if (touch_invert_y) { + temp_data.y = lcd_height_ - (temp_data.y + 1); + } + auto *display = lv_display_get_default(); + auto rotation = display ? lv_display_get_rotation(display) : LV_DISPLAY_ROTATION_0; + switch (rotation) { + case LV_DISPLAY_ROTATION_0: + break; + case LV_DISPLAY_ROTATION_90: + temp_data.y = lcd_height_ - (temp_data.y + 1); + std::swap(temp_data.x, temp_data.y); + break; + case LV_DISPLAY_ROTATION_180: + temp_data.x = lcd_width_ - (temp_data.x + 1); + temp_data.y = lcd_height_ - (temp_data.y + 1); + break; + case LV_DISPLAY_ROTATION_270: + temp_data.x = lcd_width_ - (temp_data.x + 1); + std::swap(temp_data.x, temp_data.y); + break; + default: + break; + } + return temp_data; +} + +bool SmartPanleeSc01Plus::panel_io_color_trans_done(esp_lcd_panel_io_handle_t panel_io, + esp_lcd_panel_io_event_data_t *edata, + void *user_ctx) { + (void)panel_io; + (void)edata; + (void)user_ctx; + auto *display = lv_display_get_default(); + if (display) { + lv_display_flush_ready(display); + } + return false; +} + +espp::Task::BaseConfig SmartPanleeSc01Plus::default_audio_task_config() { + return { + .name = "sc01+ audio", + .stack_size_bytes = CONFIG_SMARTPANLEE_SC01_PLUS_AUDIO_TASK_STACK_SIZE, + .priority = 20, + .core_id = 1, + }; +} + +bool SmartPanleeSc01Plus::initialize_i2s(uint32_t default_audio_rate) { + logger_.info("Initializing I2S audio output at {} Hz", default_audio_rate); + + i2s_chan_config_t chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port, I2S_ROLE_MASTER); + chan_config.auto_clear = true; + ESP_ERROR_CHECK(i2s_new_channel(&chan_config, &audio_tx_handle_, nullptr)); + + auto pins = i2s_pins(); + audio_std_cfg_ = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(default_audio_rate), + .slot_cfg = + I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = + { + .mclk = pins.mclk, + .bclk = pins.bclk, + .ws = pins.ws, + .dout = pins.dout, + .din = pins.din, + .invert_flags = + { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + audio_std_cfg_.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_256; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(audio_tx_handle_, &audio_std_cfg_)); + + auto buffer_size = calc_audio_buffer_size(default_audio_rate); + audio_tx_buffer_.resize(buffer_size); + audio_tx_stream_ = xStreamBufferCreate(buffer_size * 4, 0); + if (!audio_tx_stream_) { + logger_.error("Failed to create audio stream buffer"); + return false; + } + xStreamBufferReset(audio_tx_stream_); + + ESP_ERROR_CHECK(i2s_channel_enable(audio_tx_handle_)); + return true; +} + +bool SmartPanleeSc01Plus::initialize_lcd() { + if (panel_io_ || backlight_) { + logger_.warn("LCD already initialized, not initializing again!"); + return false; + } + + esp_lcd_i80_bus_config_t bus_config = { + .dc_gpio_num = lcd_dc_io, + .wr_gpio_num = lcd_wr_io, + .clk_src = LCD_CLK_SRC_DEFAULT, + .data_gpio_nums = {lcd_d0_io, lcd_d1_io, lcd_d2_io, lcd_d3_io, lcd_d4_io, lcd_d5_io, + lcd_d6_io, lcd_d7_io}, + .bus_width = 8, + .max_transfer_bytes = lcd_max_transfer_bytes, + .dma_burst_size = 64, + }; + ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &lcd_bus_)); + + esp_lcd_panel_io_i80_config_t io_config = { + .cs_gpio_num = lcd_cs_io, + .pclk_hz = lcd_clock_speed_hz, + .trans_queue_depth = 10, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .dc_levels = + { + .dc_idle_level = 0, + .dc_cmd_level = 0, + .dc_dummy_level = 0, + .dc_data_level = 1, + }, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(lcd_bus_, &io_config, &panel_io_)); + + backlight_channel_configs_.push_back({.gpio = static_cast(lcd_backlight_io), + .channel = LEDC_CHANNEL_0, + .timer = LEDC_TIMER_0, + .output_invert = !backlight_value}); + backlight_ = std::make_shared(Led::Config{.timer = LEDC_TIMER_0, + .frequency_hz = 5000, + .channels = backlight_channel_configs_, + .duty_resolution = LEDC_TIMER_10_BIT}); + + using namespace std::placeholders; + DisplayDriver::initialize(espp::display_drivers::Config{ + .write_command = std::bind(&SmartPanleeSc01Plus::write_command, this, _1, _2, _3), + .lcd_send_lines = + std::bind(&SmartPanleeSc01Plus::write_lcd_lines, this, _1, _2, _3, _4, _5, _6), + .reset_pin = lcd_reset_io, + .data_command_pin = GPIO_NUM_NC, + .reset_value = false, + .invert_colors = invert_colors, + .swap_color_order = swap_color_order, + .swap_xy = swap_xy, + .mirror_x = mirror_x, + .mirror_y = mirror_y}); + + brightness(100.0f); + return true; +} + +bool SmartPanleeSc01Plus::initialize_display(size_t pixel_buffer_size) { + if (!panel_io_) { + logger_.error( + "LCD not initialized, you must call initialize_lcd() before initialize_display()!"); + return false; + } + if (display_) { + logger_.warn("Display already initialized, not initializing again!"); + return false; + } + + display_ = std::make_shared>( + Display::LvglConfig{.width = lcd_width_, + .height = lcd_height_, + .flush_callback = DisplayDriver::flush, + .rotation_callback = DisplayDriver::rotate, + .rotation = rotation}, + Display::OledConfig{ + .set_brightness_callback = + [this](float brightness) { this->brightness(brightness * 100); }, + .get_brightness_callback = [this]() { return this->brightness() / 100.0f; }}, + Display::DynamicMemoryConfig{ + .pixel_buffer_size = pixel_buffer_size, + .double_buffered = true, + .allocation_flags = MALLOC_CAP_8BIT | MALLOC_CAP_DMA, + }); + + touchpad_input_ = std::make_shared(TouchpadInput::Config{ + .touchpad_read = + std::bind(&SmartPanleeSc01Plus::touchpad_read, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), + .swap_xy = touch_swap_xy, + .invert_x = touch_invert_x, + .invert_y = touch_invert_y, + .log_level = espp::Logger::Verbosity::WARN}); + + const esp_lcd_panel_io_callbacks_t callbacks = { + .on_color_trans_done = &SmartPanleeSc01Plus::panel_io_color_trans_done, + }; + ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(panel_io_, &callbacks, nullptr)); + + return true; +} + +bool SmartPanleeSc01Plus::initialize_audio() { + return initialize_audio(44100, default_audio_task_config()); +} + +bool SmartPanleeSc01Plus::initialize_audio(uint32_t sample_rate) { + return initialize_audio(sample_rate, default_audio_task_config()); +} + +bool SmartPanleeSc01Plus::initialize_audio(uint32_t sample_rate, + const espp::Task::BaseConfig &task_config) { + if (audio_initialized_) { + logger_.warn("Audio already initialized"); + return true; + } + if (!initialize_i2s(sample_rate)) { + logger_.error("Could not initialize I2S audio output"); + return false; + } + + using namespace std::placeholders; + audio_task_ = espp::Task::make_unique({ + .callback = std::bind(&SmartPanleeSc01Plus::audio_task_callback, this, _1, _2, _3), + .task_config = task_config, + }); + + audio_initialized_ = audio_task_ && audio_task_->start(); + return audio_initialized_; +} + +void SmartPanleeSc01Plus::brightness(float brightness) { + if (!backlight_) { + return; + } + backlight_->set_duty(backlight_channel_configs_.front().channel, + std::clamp(brightness, 0.0f, 100.0f)); +} + +float SmartPanleeSc01Plus::brightness() const { + if (!backlight_) { + return 0.0f; + } + auto duty = backlight_->get_duty(backlight_channel_configs_.front().channel); + return duty.value_or(0.0f); +} + +void SmartPanleeSc01Plus::audio_sample_rate(uint32_t sample_rate) { + if (!audio_initialized_ || !audio_tx_handle_) { + return; + } + + logger_.info("Setting audio sample rate to {} Hz", sample_rate); + i2s_channel_disable(audio_tx_handle_); + audio_std_cfg_.clk_cfg.sample_rate_hz = sample_rate; + i2s_channel_reconfig_std_clock(audio_tx_handle_, &audio_std_cfg_.clk_cfg); + xStreamBufferReset(audio_tx_stream_); + i2s_channel_enable(audio_tx_handle_); +} + +uint32_t SmartPanleeSc01Plus::audio_sample_rate() const { + if (!audio_initialized_) { + return 0; + } + return audio_std_cfg_.clk_cfg.sample_rate_hz; +} + +size_t SmartPanleeSc01Plus::audio_buffer_size() const { return audio_tx_buffer_.size(); } + +void SmartPanleeSc01Plus::mute(bool mute) { mute_ = mute; } + +bool SmartPanleeSc01Plus::is_muted() const { return mute_; } + +void SmartPanleeSc01Plus::volume(float volume) { volume_ = std::clamp(volume, 0.0f, 100.0f); } + +float SmartPanleeSc01Plus::volume() const { return volume_; } + +void SmartPanleeSc01Plus::play_audio(std::span data) { + play_audio(data.data(), static_cast(data.size())); +} + +void SmartPanleeSc01Plus::play_audio(const uint8_t *data, uint32_t num_bytes) { + if (!audio_initialized_ || !audio_tx_stream_ || !data || num_bytes == 0) { + return; + } + xStreamBufferSend(audio_tx_stream_, data, num_bytes, 0); +} + +size_t SmartPanleeSc01Plus::rotated_display_width() const { + auto *display = lv_display_get_default(); + auto rotation = display ? lv_display_get_rotation(display) : LV_DISPLAY_ROTATION_0; + switch (rotation) { + case LV_DISPLAY_ROTATION_90: + case LV_DISPLAY_ROTATION_270: + return lcd_height_; + case LV_DISPLAY_ROTATION_0: + case LV_DISPLAY_ROTATION_180: + default: + return lcd_width_; + } +} + +size_t SmartPanleeSc01Plus::rotated_display_height() const { + auto *display = lv_display_get_default(); + auto rotation = display ? lv_display_get_rotation(display) : LV_DISPLAY_ROTATION_0; + switch (rotation) { + case LV_DISPLAY_ROTATION_90: + case LV_DISPLAY_ROTATION_270: + return lcd_width_; + case LV_DISPLAY_ROTATION_0: + case LV_DISPLAY_ROTATION_180: + default: + return lcd_height_; + } +} + +bool SmartPanleeSc01Plus::initialize_sdcard(const SmartPanleeSc01Plus::SdCardConfig &config) { + if (sdcard_) { + logger_.error("SD card already initialized!"); + return false; + } + + esp_vfs_fat_sdmmc_mount_config_t mount_config = {}; + mount_config.format_if_mount_failed = config.format_if_mount_failed; + mount_config.max_files = config.max_files; + mount_config.allocation_unit_size = config.allocation_unit_size; + + sdmmc_host_t host = SDSPI_HOST_DEFAULT(); + host.slot = SPI2_HOST; + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + + spi_bus_config_t bus_config = {}; + bus_config.mosi_io_num = sd_card_pins().mosi; + bus_config.miso_io_num = sd_card_pins().miso; + bus_config.sclk_io_num = sd_card_pins().clk; + bus_config.quadwp_io_num = GPIO_NUM_NC; + bus_config.quadhd_io_num = GPIO_NUM_NC; + bus_config.max_transfer_sz = 4 * 1024; + auto ret = spi_bus_initialize((spi_host_device_t)host.slot, &bus_config, SDSPI_DEFAULT_DMA); + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + logger_.error("Failed to initialize SD SPI bus ({})", esp_err_to_name(ret)); + return false; + } + + sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT(); + slot_config.gpio_cs = sd_card_pins().cs; + slot_config.host_id = (spi_host_device_t)host.slot; + + ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &sdcard_); + if (ret != ESP_OK) { + logger_.error("Failed to initialize the card ({})", esp_err_to_name(ret)); + return false; + } + + sd_card_initialized_ = true; + return true; +} + +bool SmartPanleeSc01Plus::initialize_sdcard() { return initialize_sdcard(SdCardConfig{}); } + +bool SmartPanleeSc01Plus::is_sd_card_available() const { return sd_card_initialized_; } + +bool SmartPanleeSc01Plus::get_sd_card_info(uint32_t *size_mb, uint32_t *free_mb) const { + if (!sd_card_initialized_) { + return false; + } + + uint64_t total_bytes = 0; + uint64_t free_bytes = 0; + auto ret = esp_vfs_fat_info(mount_point, &total_bytes, &free_bytes); + if (ret != ESP_OK) { + logger_.error("Failed to get SD card information ({})", esp_err_to_name(ret)); + return false; + } + + if (size_mb) { + *size_mb = total_bytes / (1024 * 1024); + } + if (free_mb) { + *free_mb = free_bytes / (1024 * 1024); + } + return true; +} + +bool SmartPanleeSc01Plus::update_touch() { + if (!touch_driver_) { + return false; + } + + std::error_code ec; + TouchpadData temp_data; + touch_driver_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y, ec); + if (ec) { + logger_.error("Could not update touch driver: {}", ec.message()); + std::lock_guard lock(touchpad_data_mutex_); + touchpad_data_ = {}; + return false; + } + + std::lock_guard lock(touchpad_data_mutex_); + bool changed = temp_data != touchpad_data_; + touchpad_data_ = temp_data; + return changed; +} + +bool SmartPanleeSc01Plus::audio_task_callback(std::mutex &m, std::condition_variable &cv, + bool &task_notified) { + (void)m; + (void)cv; + (void)task_notified; + + auto available = xStreamBufferBytesAvailable(audio_tx_stream_); + auto buffer_size = audio_tx_buffer_.size(); + available = std::min(available, buffer_size); + + uint8_t *buffer = audio_tx_buffer_.data(); + std::memset(buffer, 0, buffer_size); + + size_t bytes_received = 0; + if (available > 0) { + bytes_received = xStreamBufferReceive(audio_tx_stream_, buffer, available, 0); + } + + if (mute_) { + std::memset(buffer, 0, bytes_received); + } else { + auto volume = volume_.load(); + if (volume < 100.0f) { + auto *samples = reinterpret_cast(buffer); + auto num_samples = bytes_received / sizeof(int16_t); + for (size_t i = 0; i < num_samples; ++i) { + auto scaled = static_cast((samples[i] * volume) / 100.0f); + samples[i] = static_cast(std::clamp( + scaled, std::numeric_limits::min(), std::numeric_limits::max())); + } + } + } + + i2s_channel_write(audio_tx_handle_, buffer, buffer_size, nullptr, portMAX_DELAY); + return false; +} + +void SmartPanleeSc01Plus::write_command(uint8_t command, std::span parameters, + uint32_t user_data) { + (void)user_data; + const uint8_t *data = parameters.empty() ? nullptr : parameters.data(); + ESP_ERROR_CHECK(esp_lcd_panel_io_tx_param(panel_io_, command, data, parameters.size())); +} + +void SmartPanleeSc01Plus::write_lcd_lines(int xs, int ys, int xe, int ye, const uint8_t *data, + uint32_t user_data) { + (void)user_data; + std::array window = { + static_cast((xs >> 8) & 0xFF), + static_cast(xs & 0xFF), + static_cast((xe >> 8) & 0xFF), + static_cast(xe & 0xFF), + }; + ESP_ERROR_CHECK(esp_lcd_panel_io_tx_param(panel_io_, + static_cast(DisplayDriver::Command::caset), + window.data(), window.size())); + window = { + static_cast((ys >> 8) & 0xFF), + static_cast(ys & 0xFF), + static_cast((ye >> 8) & 0xFF), + static_cast(ye & 0xFF), + }; + ESP_ERROR_CHECK(esp_lcd_panel_io_tx_param(panel_io_, + static_cast(DisplayDriver::Command::raset), + window.data(), window.size())); + size_t num_bytes = + static_cast(xe - xs + 1) * static_cast(ye - ys + 1) * sizeof(Pixel); + ESP_ERROR_CHECK(esp_lcd_panel_io_tx_color( + panel_io_, static_cast(DisplayDriver::Command::ramwr), data, num_bytes)); +} + +} // namespace espp diff --git a/doc/Doxyfile b/doc/Doxyfile index 404591115..1206cc6a1 100755 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -144,6 +144,7 @@ EXAMPLE_PATH = \ $(PROJECT_PATH)/components/rx8130ce/example/main/rx8130ce_example.cpp \ $(PROJECT_PATH)/components/serialization/example/main/serialization_example.cpp \ $(PROJECT_PATH)/components/seeed-studio-round-display/example/main/seeed_studio_round_display_example.cpp \ + $(PROJECT_PATH)/components/smartpanle-sc01-plus/example/main/smartpanle_sc01_plus_example.cpp \ $(PROJECT_PATH)/components/socket/example/main/socket_example.cpp \ $(PROJECT_PATH)/components/st25dv/example/main/st25dv_example.cpp \ $(PROJECT_PATH)/components/state_machine/example/main/hfsm_example.cpp \ @@ -319,6 +320,7 @@ INPUT = \ $(PROJECT_PATH)/components/rx8130ce/include/rx8130ce.hpp \ $(PROJECT_PATH)/components/serialization/include/serialization.hpp \ $(PROJECT_PATH)/components/seeed-studio-round-display/include/seeed-studio-round-display.hpp \ + $(PROJECT_PATH)/components/smartpanle-sc01-plus/include/smartpanle-sc01-plus.hpp \ $(PROJECT_PATH)/components/socket/include/socket.hpp \ $(PROJECT_PATH)/components/socket/include/udp_socket.hpp \ $(PROJECT_PATH)/components/socket/include/tcp_socket.hpp \ diff --git a/doc/en/display/display_drivers.rst b/doc/en/display/display_drivers.rst old mode 100644 new mode 100755 index 1ee395282..2252d71de --- a/doc/en/display/display_drivers.rst +++ b/doc/en/display/display_drivers.rst @@ -20,4 +20,5 @@ API Reference .. include-build-file:: inc/ili9341.inc .. include-build-file:: inc/ssd1351.inc .. include-build-file:: inc/st7789.inc +.. include-build-file:: inc/st7796.inc .. include-build-file:: inc/sh8601.inc diff --git a/doc/en/index.rst b/doc/en/index.rst old mode 100644 new mode 100755 index 5ffd2bb43..908ca54f0 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -66,6 +66,7 @@ This is the documentation for esp-idf c++ components, ESPP (`espp Date: Wed, 27 May 2026 21:47:48 -0500 Subject: [PATCH 2/7] fix doc --- doc/Doxyfile | 4 ++-- doc/en/display/display_drivers.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 1206cc6a1..82506ff73 100755 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -214,9 +214,10 @@ INPUT = \ $(PROJECT_PATH)/components/display/include/display.hpp \ $(PROJECT_PATH)/components/display_drivers/include/gc9a01.hpp \ $(PROJECT_PATH)/components/display_drivers/include/ili9341.hpp \ + $(PROJECT_PATH)/components/display_drivers/include/sh8601.hpp \ $(PROJECT_PATH)/components/display_drivers/include/ssd1351.hpp \ $(PROJECT_PATH)/components/display_drivers/include/st7789.hpp \ - $(PROJECT_PATH)/components/display_drivers/include/sh8601.hpp \ + $(PROJECT_PATH)/components/display_drivers/include/st7796.hpp \ $(PROJECT_PATH)/components/dns_server/include/dns_server.hpp \ $(PROJECT_PATH)/components/drv2605/include/drv2605.hpp \ $(PROJECT_PATH)/components/drv2605/include/drv2605_menu.hpp \ @@ -380,4 +381,3 @@ HAVE_DOT = NO GENERATE_LATEX = YES GENERATE_MAN = NO GENERATE_RTF = NO - diff --git a/doc/en/display/display_drivers.rst b/doc/en/display/display_drivers.rst index 2252d71de..b7311753a 100755 --- a/doc/en/display/display_drivers.rst +++ b/doc/en/display/display_drivers.rst @@ -18,7 +18,7 @@ API Reference .. include-build-file:: inc/gc9a01.inc .. include-build-file:: inc/ili9341.inc +.. include-build-file:: inc/sh8601.inc .. include-build-file:: inc/ssd1351.inc .. include-build-file:: inc/st7789.inc .. include-build-file:: inc/st7796.inc -.. include-build-file:: inc/sh8601.inc From a3ee35314a71e41176dfd12fb8c1481bd74f4402 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Wed, 27 May 2026 21:49:23 -0500 Subject: [PATCH 3/7] copy click.wav into example --- .../example/main/CMakeLists.txt | 2 +- .../smartpanle-sc01-plus/example/main/click.wav | Bin 0 -> 35918 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 components/smartpanle-sc01-plus/example/main/click.wav diff --git a/components/smartpanle-sc01-plus/example/main/CMakeLists.txt b/components/smartpanle-sc01-plus/example/main/CMakeLists.txt index d5f4043b8..a3e606e36 100755 --- a/components/smartpanle-sc01-plus/example/main/CMakeLists.txt +++ b/components/smartpanle-sc01-plus/example/main/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register(SRC_DIRS "." INCLUDE_DIRS "." - EMBED_TXTFILES "../../../t-deck/example/main/click.wav") + EMBED_TXTFILES click.wav) diff --git a/components/smartpanle-sc01-plus/example/main/click.wav b/components/smartpanle-sc01-plus/example/main/click.wav new file mode 100644 index 0000000000000000000000000000000000000000..2183344a236219ea90c0b6d27b792c93542eda0f GIT binary patch literal 35918 zcmeHw3AjyV`}ebkJ)C(obI37!WgZhrlu(GGSrUl`Aw?-tNh%3NNQRPGk*P9;kjU&H z(=nZSpL6!O)_T6*eb&3awb$PJ9Nzc;eb@DU*VR7nbDrtGfA{=6&pvha_UYEOYuAq$ zd!*CDU7i~;szxGXOkg6rX9Z)4YegorME2~!Q3FR!VNB&Juj75Hc!u{mw@|#!d%r)O z?TPIiyRFK(a!V`o^4}(WpSVvx>G1mpvpYTg)l-_x+uJ(v7X$UY+io<>X>jqK%w87{ zUTS)luH%^E}N ztV!En`&xQh-KT4xt6i|C*a)%BQ{}e(t!E~F+Xp9+<(R>?hf@~w0I4qrX;IgZn(U;tNGt;-)zg;4sDy(q_EjrHTTzz zPW`rWH^+jwgGy&%vg=4uAoHuMlTPkCJ?mif;gS1i9wgI`8sb_|D)yul~-1+ob+&t zU-H|(C?4(Wc)eTB(--<(ZgVd8+-GMSp1JL8?&&6HyJz_S9(Jzr)y|m{U9<9j5FHwGnQYHE%zRhzq{1^!sRP#E(^9@IcP9E++;yS5t**a`>Mx9S@5$YjmwUa_jh`~l zWDdUc?4>%F=Ur-gZOxTYx!$ZH#e-Z~fr`GX!tdPgm|=f8zJJ{0)HhQ0RV=D-du3kf zql(W~+MT+%eC@P~pCUUEU_1cllOzp?#6OGxkz+e&X7M1}W#0E2Msx+A_6Wxo1))rN$+- zPu?5bH2%1KnX@H(Mr`2kro3Ft-8TwWx|-zOpO=zzAiF4QV%B@v7qU9$HOxI(*ueF$ zua#$=_K7l9+A8dI%(tJ7X&kdKp+&;jFrkX$R}%YiateMi#&5e!bucS5n>=dGF@FlKV;CZ@Hfp+>>9UXo0J)XKQhmf06Hf-dX!x zZZEZpy6X5Qc1g@9@pa>yB)pX1ieH@|#(Uzs#NHh@G-`D8Fxz7L4%SR;uWVG`^=|i_ zD*nQqSaeU(S=Vw`2iNI>_gxNGwW8+>1&>m^-T$I*t=3+>NA$4Xwt;f7W4NP7baZs> z*qB&nT*bJ#v4ye6qd$wO=xiG0l{?upg{o4qmdlfr-<3Oj2Yo-gTY4PDeT!c$8d(%o z{B_ZI_nP9N-se2C{WE>3Y9qzPYw|>?yBKTlV;kW->NpbhUR2BIzR@S6`bGPltD+V; z20J&|#@ky+nsg_tD6~h9#(=kDp<;i=^x>szG! z5g5WpYh0)()R*6ouGrqO^>Ea8^mjh(%y5cP^PE?l;~jp-eA@u~!%`br5;BDo+HC%} z@`4&27#0}rTj6W(eagGdv&mE5d&pDWH{1J_|BA1_@>!szHePMYKIh%VUBaXCB`L+e z*4D`3wSVOJ!qL?+(=o~Ja16EC?20r;ZX+HR>#-e-Xb3;uv_bD(yhr@BP( zYyGv=tQH$277ASYPI}aqX6tOPZ~wx+-2RAto_&@r+CEN>u~n7UOOu5P;tqa4D^N#h z9hL8u%l;+-*`MxT?)%*LyKkber~k6ASzx_?jFJ~f(k7~xd2jxU&{TL%$`{wmL*=h* zZ`-Qdo7vmiN7^%O?d;`k@7j(?kH{^=ed16i3yZauJVBkPZVJfCO8=jJ#pm(;`}w_rmje^j%SwIzv38u@&t4Ikm??cORkdZw`L^@6F80Um3HILh0k&PXj_}3b z;wb4s;i9mJH(h$yJkZ|X-+$J(->3Mt`abm6_P-YJ`!_0$l@8ikwU9s0 z*Fxv+(s^;V{DAzH?P;6aX15E_dV$Sr`%CU*n=e(D%ZrP}rKUrT?Muh2i$pQg+U%u$`{U3|Hg#hzhri3#FGX@(@(j?2H$sZ_K>_6;Y7ig|jRGSGKwR!T3?0~(Z zSQIr|+7N$O){Pu?i>Mdw`g4a|SYpIX>7u&j8P^0V){I)#76)9hV@aj_T0u1Q_wjw!utMalWL z*2$IZ)06huya}>^$j8!eO~a%D1+D;@*^J zIsYXcm*xxocmdz;@1k8Q-m2u}zYxgIUgK+;mEjh%d%54rEpo3aSm3KuY}fL)NA$Fnoe$J7;)$A3}vdzX2? zc5f=0Uf9}oG5l z(J9;G+9gj+_&mX%P(F5X{1?uzW4Qd8V;bu&l?yz}hZJ8AEY5%5yE>a1g#BVSCl*|h57&%VQ2U-z)U zPlb98_D_}Q1hU-4{Ha*F5pae${99Attly5Yg>P|$Ns4> z+p$z!;`l-;@8~1fu&Q&!Y z%2fY7%7Q>QrH(pNVLVQ~#ulqzh)-!(rB3`$`EEAIc9-z2t-Sc5?OQQd&KLiX`b)ov zhx{q-*InImUoHNnFe7T6?MU^C6<4LZt4>RKF?oobWy|L4we=o%k^AzenTL;6J>CDL zcxJ}6_1B&%8c~$y^#-02vh6#P&z9q9ORIOTdc0a%(qE}O=HB>Wu^&WNvcDiU^Br;5 za&^pq*yAjGfvxv-7B4E*S#7PRJW%>G=Dz3=u~TE>rTc9^ct2Lh7U%fZ6)o~y$nEIr zeJwVp>5YN8?xJb#J$wSsm#f+ykE#{zOnxe9W~Ec*->CR}xy=a~aXlRCY~P5p_)p3@ z?=P-(c~@^Vz1}hFi|Z2#9?DbPON$%%D+ku|w&HirwlS#*E0dRdCOARu>B`q)(g`qY`54#QqXDhg^8XEg?+ux7u)>_-pT4D<*GPE+~`PmZjE{&W@yxd zu@5?T#=KxZ9<^4q+uu-kvP!=5fd-xf-in?V@uIrZGuJo7+g0r!s4NcQ`L-N!f#Zz4 z+cCn{z!7h|)Aow=rtlQIq|8^k`{w%I^HlT3cs!mNo+#g9-}JyF^&?(UjFPw6);aPW zH=-&>9gglAH8}c^qpR~X`2%?!tIFO|8z{X3clt7XPkNs49d-}(7kMryGyR+RNi|3K zi#;kek($UEa$|Xt?RRmr+={Id-%(qzw*q#ptAD7n!#6t6+dnbTTlquj&z*dvI9@1} zXGnYP)okY-Ikp{+?`^g0mF1_US;8~yg0?|@TNxi%<-h1#-E!HbL4T)Rx_NpS&pjDprtUgz92*eu|AzCv#bOL~9!ON*x$jp^jBXX_d6qxP{_{ zeZrIC%hF)!9r=v3Uj9fbFHaKBi(YoWP?LYmd#YLLuYqmKG5=%AVgDk$D;`h-3Rl-^ zH?$Ag@4SonE&D`TCNz^rijCy);!J71FhQKc(uAS>Xa2XgPJ4zoN9HxqS_@;e*93vj z6V~#B!ei`+u$N5}PO&R&Ci{XXvG3IWd|P0$w!(i-UFct?J{u@jzg8A&esv~)gfC}q zwnxYk*NfZbyQK5B?oyIHLwwFAi?Q-K_PqEWS6N5x3h%9+)Q&4Zs|S^XN)z<~#ic%@ zcr{H)V!x?&;T~;+Fi1-gU(_m#-)nyfRrr0v&-@$q4*uS8yYMYvA-u|4im!4me#;8Z|YWUw9-cF7RXUY`SaAqxXV}jZ)pC&A-+dF z3VRL+)r4tcA5q22Dq4z@*NPKmzc5v1LY$n7u(3YvwMW8Y_WKnZI)^Z z6Xl*lK)z3?U~>xVAG<@i`OKs%)EQpc;W;Jh5EmQxO>R|BtUJn$GFt~|n8 ztKEdoTAbLAzbCe4OT|Cf3u3x(MR-EEU05g#WCg-}_(b4+#jlWKW3&e1N17%a(Dn-H z{AFPrKg_oCi~I`TiSJn7s~P-H)$Whu>tU)3jtby{y>6>ldj zX4Qqag&VBCIGRln7w}o)SnVlspel&lmAS$OrGrorH_#^abM}VTp7rM!_)s>LuNLm$ z`C_(qM4G8pky~i{q?2l@G+%8eeyt7`vec>Ub!{eZ!>4PBY@M3N>Z-d1r~0;dw<=5X z)Rt1FDoItfk>YS|u8^lKVRQM9ydOKS`B*D$EcCl9e4suq&QvepuI?-i(t1iBEmNF? zldg~_3SIG6yLVZU_9MPEWiXH8W3`nyVO5}tur1I~7^K`OyojiOqjeJg*V*sNE>>RUY`t1jcvq_`TV^c{Y+W;i^{5 zMr(UnEv-8%Qje+U)hvIZ`ntb^_PMrS`%#|G?{dDxK6KnAT(Mmi=Gq<<8_REt-|^SQ zC;TnNYo4{j7O$KA=&Qy)^mXT}{0p^VS_|z}QP3n=Rr^Rav?OVWc3e8Fb(0?A3)v}D zb$50^oy%hR0XBu-U=6uV2=FxFUa<+7nhMjU%7P{ov2#e!9j_RGfsXB+h ztcs}D=hz5s6H8@9?7C1{C>C!Q>PWW>@lrYAnD{r-gs<77Y?tq{%E+dPPVQW5o@8oY+&HE`F*+i$hgW=%W?#Gupp+HMT(OBevDz z3`@aZSDxV?3bE>8VRB%zunhVBvy#p>tBv__?IrC1JENva6V);D z47D$K-;}EIC1NTYDCDwMe5G(vxkFqKct%_taEi_GUcRV&fqUy5@5XNDBgCcJ9BHh! zOZripBsJ!mxRL)VbYR=~uk4&!O}L=cgSStzJxX8JT;0Q;)b8T{V!vqzh1a!p;yW6X z{?_c$i~JighP^L*&x(0R{N-Vh;8A}NHmk#hjcPti*B*l&oB4cJ4LQ9+Yb(B|^~XKc z5U1~(!WiBff203Z`;^UBHKr(yU|SX8ymFS!Q{Q2QS^|3=_s$lf6aP|7!?&N?c{k~4 zepp<=_X#n$V~4R1aYAm?wzHbrQkJbgkNYx(acvbp&L7~DSu!fi$p;Ak;=OPS9%0A1 z9e?d;$v3l)wOFB&))c2!U7@z-Vn?(u*)Cp}{m9nvcZA-&hFFo05=Fe&WxhkG#jmm_ z@y`ra@^swlBeXG0Kqd*=PBv7VgDU9DcJp{Pf#vYqgd#p!5ZMQU#G0dD!BSW(qYTh1vKk`plQ!WUHwI0F` z+92VacAs!JDs3NMg};CF$Lm32gK@*p5PI;@f}K|pGPN(53+I12e~J&}=d~I9D{T{> zrDfo|yufPk+H5xO%F44R@j4iaznKli-veG^o7oiTzJfJkzp)d17yFVgX7BP5Y!+__ zyRPyWwva#0`tqgB$^T;ev}0^J{?fHkdlnXP{t#YNTkwLY#;(CHCvXnAv>2foZ!Jvc zcf$jbgK;=J79n5%=B-#di)YWVvp9n`@MBozY%#3f%#o`&87s04tPdN&CNVeg=faBK z{9{&yKgIl7WtOY$=c+cDH{gBvi@Y^na;>=oR&~V9HW>HoI93^cc$7_NAF@vvJiwN~ zH`9POh~0}5_YP)f&G1&bjqTwQYUC8(%fIKR`CG8z3H-h0KJH{4xsBb!-TX0rmVdyv z^Zl@`3j2x=XY=`LSbT<^sr=@ z{RCaV1L8FJWf+^oy0Yd=4!Ci7((M_!qng zErxx=E3%(p+fnphp1^7_2kVJ{DmNDT&1L1_g}P|%f&Kt{71`Ypb~zyX5UO%M{QDX| z#GizQ{qcQr2(+DyELp|hKpf`7AAj;1h)-KYsT&)@dLw#$*vG6po6I_~5v(;n9>Ru|5zsr{m?G0sUt1I&1(mX~SEx7Q6+!57jUj-g^i3&gYqY2dX3+ zKCj5u@(yT^K>Bm+Ab*uz6&Zvnl$n93FK9JqKEqIS;AM$qiyYl0# zCSDxTe3OvOM+t3s2Vo>n6*lnE@YNu^Cnsv}vIn(a*<|esn~#%nwRWD>;~SX9C&G%J z(7X!zEadlAoEU!k@Kfo#UAp03HXu^*0O^A$57>+u>4<~sm{u65tSp&F; z7syEdD^G)za=a#MfOF+e_+bQp1LyrjWa&6$?(@jpUZ{(~%hcJSQHegh%*j9zbTKv1f4qO~Q%1 z8@EFu^1eGNbtI~58uEA+kR~94_v6J9hfK*p4QzxL=ObHZ@qK(b>gyz`EEeZrE4G9` zgj?e|d~X?o^RXu~sS3`AOYp@HxSc*mZj1)S%Q#2h=j~93^;j-XMyC0AZ=7jk*gklB z3;g*5ABc0R6;z{7@0nfKfzYuK72-Nfb4Rzt=c6n z@|}D-V(<`Kf!FO%+|T~tC)f={AqwYmJywC;$v)zpSr>jMdmN|Pr@R8F6z)OYy}FoqO0`uHY6p!Y=X! z@Je6Yzh_Wy10W}ccLCqs$j+AResIh}oyP+8U7Y6oVcTiM?+@Gx%Tap+5vKxtpLid4 zL{DCcb%5M1z!?O&%OE!abvX>*Wago*#=oJk5^Xx#1Mr=jN3)g4_76Ze4*djR%;$NC z_i0#q09mvLb-j!;cGqJ(br-OG8a3;#3#llLF+$1wIU?@q6r7J`Eloi;U`t%&G+aGa&~NMfN7* z-Ik8Cw*_L?2^n=Ci^1*H9I@?%tRH~feE=)XamFO!RL$X8{15PMuM0t2jgZDFG$wA!d+jt>fBqY58&bm76 zI&TWzR`5hCc%&(7gWIMZBsarJa2s@vgCrj^;|6?j0TDZg{vuu-H}KnyGgoB!(17k) z4NU@<1DfEzLDX}33b-plqw3JHCcKr7>Z*Y^LKXZ@g=eDhR#5Pw%Y`-PkzvR1#yJRS ze_(VNKd1OnXnqztXF-yJD8<4HmB3jOH%x8RX*zzY;wK4sR4poA8^y>i7b4>VUolQz zA97m*GDB7gxIsAd_Va7V!s}SghAl3Pd>DziHDh2&IarVeJ!?bHdN{}Hz$-N%sXSyy zBV$yY1O?!wH_16<$mx7*76}Q06aUQ7>`#+GJdB(!z7Gi(HzJJ32O{9@|A!l z!xs+FMx!EV#Q|#t=7F9rNXmwO*ATrch`|-~*P(MBZ1aMKWW*q1Dewt-rYiKTg5MP( zDY*pA2dX^ia~1x$48BZgejR-tklpZt3LNsT4cd^-He`f^Ucj6h<(3Vjs1mL1Ky|{~ z(U2Gm*|E?y8XmF3cLMS$03C}FvjTV{7u7@_&ODv#`B8l`;t_>fjlzA6(*}AkLR$gsHF)#vz{z$U zd#RvlfL0%^F0^d`8)~CE;;`!~$>!70=QMOZ2kSF&{^g<`=*`5kz@wL%u(&aR@a@=&8*SS?~T24`V1 zT6vrdl~9w_P`S6k-dcDa)IzlIPdea(n(#;(Je>^dRK)5U^gDx_JOl5!bNqKi?l8P^ z7GBE1d80xjIytMt#s)xXj(E3*cbY?T1DuU%sQ+Y~%y#qvSeTDUUxjDR!_KqVJqr!~ zMqOUO{1VQOE7;G1Ocy_o7@Ps$8PxM7oO=a$@rv+b0z6X*tvbfHm7GP1(3P^Kuq3zg z!F2<5dyW4Eq@#%98C2X=M5GXP%VB*K&a`A?O+}2VmehR#e0mK#7eMhh{B#kTWFs10 z)ToSjC!m%|1G*2=P`MQmzeI4@(JRQSBIuh7`?BGcyb|gPCAboDEBfF06%XH1&C2kt z3J$U}AKuJHo>HT+2lc>_v5pd%Qb~4~M+Q*U#-Z8KG)Q*iRHO4Q2f3C5nFY|*4J+y2 z^P}w6&%JzTl7o983shO4$i{9yYRnCvXwbl3g5yJcRGg3^q!4}-=vQc3vol>?@ZA7D&&wcR!jlKphAG=aP(=o`YW z>adf})B?z$nmq;yN1^9Qc#_U156-7}aHqjvb>O#p$dp>(p**!C{zdTQHE47Z{YAv} z3gYTQyy=WghCWrGR~q!I3O!R16&a20pNnXx(2k*RPD6+pfX^yML=CJ^dF0-aRp zIC&`J$O2!<-5G=0ap1)u!ta!~K18D!k_$_8O@cKE7&}UCJ0Ik!s6GbWB=Fe4W5xH@2j?u;WE^ION)a8;el_sNzdjJ;tJ}xgS>hQCk7@l#^8b z_#;sXzXWY;knBJsO4@an_(gzClrs{vp!=4AQUfyiB~a36?8h42{TeXoJ{F;+geGGw zm;6u!6lmfDiXJ7s_kb%cpaH!Jb6^v_+ls+UXD@jx3YZi>I(4ETm0m}Bl*!Yi4}(5>BRgn63VP8?$p(4! zZ-eX6q6jgZ8uT*NWq3<+iu^*d0;nGq8AVk*NI6Ls*x@xB@>)U@kmJ0hYA6yM zST^YC0KXHI4(Lg-qg*D>QmhTnlh^4bNYN&bQ}sL1bbSSkDQe_lLiWM~o`3()JI0SW zMV-7)^-tPS?a=El7JShqXp}(#;Olu!IZY=?F*tRt#S%+&-;!mdFaEJEcn}pokYiYj=s}eymdd5GGkELqCeV-srVWmg_l6 z8A_H>_Ry=HY^SQ^XyiSzMz1yUCS{?9eTqHRFr9=HJtyXrCDHIAdDQX0^`#mjDzPN* z-N2>qC*%{VdXh&!9GIjpWi&;G?A06TNhgP1({wKAc}LYtXD6K&ln0dmdR z5Hew_C3Qs+wxf~f9gs&EOFq&gK$S-)9NA3SLb*w`px3@0f695XT>~XG@-bmhUg#A` z`K8whl*T?a`fX@#RJE?NQQ^9tI<~GW<%8aIIdq=r$Rw4d)2@CNlFnqmeo`2op?uZP z5&g6wY8^{Qpc>IXWk#isYrJMqi0s$zA-anw_sLU~S;S4g)0+`j+M$?Hd?}~(8rE+SiVN96H9-EegO~0f z(uDM+A5)gDH(5ZM5I*@u&wX8Q!lUz%e68mp$tEQIL%UWTsV+&D9#^u3=uIB-upU=K z4%I5j)n(}?fnNV~2I&4E9}y<`gz)vdK)*K$k3L2{5*3Z9(MOl3+o#u?zD^d99fp3S zE6JfBl4aPY%hLHtM?xgYx+ePRrE5lrhGc86@94in^)qzTk#&6|*`%LRx_?MR`WTk# z8j)6J)2%cpjGnlNThCis)gwq*qBp%7DPw4t=!}z?P9yz9qFr5I`fWr*&v)G~6e(R2 zNuZCRj~NleV%jlm3AU2Px@UC?Ga{4$dei-{OCVax)ZnJaQTM0m9aBeLPhC@8V_l}9 zuc0Glj*&e&ny#}I$v`7+UGv~PA^AF2FmE^oSxXe*`s)0;wT1*8%g|TnwPulyX2>*b z)id45cEf62Ch^mcVYS}tnPON$S{NP+j+zlM>UB*GtAj0~v8kml&G4&Xk&drts*$mJ zPjU^54V+M24eyy+nED3mXdN3GhwB`SZ^$&fP1Hud2kUFB>DuZV8@!>tVYS{{XF9$a z@nBpNyLA5s>uJR@d=hSVxCXlZ23BxSJHaj3r~0~%Z>7@pwXW%?!C0k_OWz5`B+2A2 zBQ7*2-_zSc|1P6fHc2ME>7Fy*apt|H>uR2KW-Sn&9uG4XrtnzdoosV|Hn?Yg7&~1vOqaFucih*Rr%=AU+vUJXH4NO@EwzW4j4DSss zsMpV_P|jeA;BjeM=I{w#I6BZ=!@_1XG)M!K**Kirx)QFfu^+x;$kOjnV@AEf6W*7GV)dv=6^yEnP0Da|bHzk3 z=uISJB~l;Gr&F28!6Tj8M7EA1;TvnE$)R=YC!EqM#h6*Sf@k3fh86}@WC{b>+Lz@I z9ZyHIa?#9`8a$4?YET#&M(Pcka7p@#71=~E`G_Kv)}%8=CcQyz?M*obhqW)A-%3X_ zlh)LYq=oM2bCW+Zg{fyazsXycTtjwQNQP`3(U^tzI!3rACZ#zFr?ToBKGS(kJ%dLk zT4{VHMKB+YgLllaiBlQ{tw;WprKur3GH39rK^dtJri(N#UFWiP3`wRJf~l=?f_L<> z!5!Y4cvg(?nSo?-gv+5l{ZlrTi5?ll;5N|A-q?xUo47P88(ZJkIc|kgr@3{?ThqX_ zEOcb*uj^RW?{KX`(e$~AX>gQ<8oU=7FMKuFtHJ!n*pRFDrD+ws8!Dr0308c=-pH%8 z8~!nP42>iAH`g#+rd5_HhbT+`Sh<2{Rs<6zT(XrivMik|7(aAuqL>I}$ujA~sjS#m zN)tVN6pp0xS`kdDTa#nbmyJfd`loDp)?KR=R$11Wm5=tqS4@twG%;m{(i;{Sy-689 zGH4?8x@O@##!6Xo%d(30tRE}2fp7Mvq;OsXNyjjG!besVt4u-(U9rw}yt1gQ^d_aD zp}AtE4A<1UV)B_t!6OsR%11Mk*BA$LhVL2(k#>UD3_86J-qFWaDKszW# zBXnJ#o7`5e$kdTF2<0q|H`2PUU8J4JYo@;8mIiYeR-1kBt~s{i8M9D}^tp*u)=0-T zkjm=ILJ8ixbwpiSxc0$l=GgQ?Fo#v+;GOWXiAAHxc;>n}GAXUn%$b$;*5p`uZtgWJ zsyQ>Y3r8%A%G5r1q+^(T){#|KFrRfNSQ9IiF|*=Enwf|OkJ$&~n`0ftxftkC|F9kzO-A~$CMd~&)mH^ziExhbu+um(kxh_ju=_P z(A8kRvd5vErO(U8F?UVNgS}A}ZYYO|6*>x~jD#92wd}Eu9*!A_OP3UBCv@#r@Ivjn znI@6!EKN>0b~t5ZJY&_gC>+bWVnr_7M+SN%Dl4ivGx5vfE{%$KZf=W7W0hg$HmMBl zf>%Pv22G?sT#CM8YHFbA+{P?YZ{kPVjU-8zV)B`z$Ow_>!zsfh-JGv%t4S}boNyVm z5=o9>lfG;6g(8OX8fc+>27-Ybxi_dH^`VkN5hC#zYoW9>kEChnsyWvYtUSi9Atmx| zuszJN>294m*e-n>S=Oz1E|~Mb#|@Qa zSQL&Jc_oy;wE3;$2HT~NBV$JPdSoqW^?!y-+}2MxUL+aBQ#Pe(ok?kxWX`NeCMS(d zZtK`erBj-S;iJ;HOT(viE02{jT!Oh`@>#XCBA8Uf89WOfo2bUf+_j2zIbzYs{N*}CEq#b?D@Na1px>o<~Zme`qFyj zkCh`_*RrG$qRAi5YuFlDvaxC+1f%I=ou(`bk`c}qymoU8LzA2L!MKs^4cDY}OcN^- zlDTGLmX1~Wj)_zHDA?DfBk4P)*2Goz$J9KSE3$-O3hUUE5gE^*4#zciZly09TSvGx zoM1g}g@f$773smak;W#9>9Np}DJK#_=vpL16T#f2HS-fr5sGWfO@y+ziAw*N^tUd9 zIIVnT$@;gTFdi$0NkyYb=%K4tj^LS<+sYSAr;n}Fx+PW~q6q)!e78n#l|ks%k5yi{ zEOR9sN#`{wXk=1Z<yt)O(Oz#ce@ZKuVI=dB&P>yg3p(~*r#ypg+thvDx z+Ly&+-3#Wl?$9iFH*_3MUHXd7YvvTq|7V|IEE-#N{ZFt%d98En>}FXMEH@Ho=vpxS z&Bmd;rOmB4p;XpgV-`vkIydP4(>@d@bpC%5??1%~r4QB3n1|Bc%G}@$?QezGp!rXD zLurkhDC_#FTLSR*9QsIBb#yXODfUlVsS_B8zZAjO5TRy)SY Date: Wed, 27 May 2026 21:52:53 -0500 Subject: [PATCH 4/7] fix sa --- components/display_drivers/include/st7796.hpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) mode change 100644 => 100755 components/display_drivers/include/st7796.hpp diff --git a/components/display_drivers/include/st7796.hpp b/components/display_drivers/include/st7796.hpp old mode 100644 new mode 100755 index 9e54a073a..883fa4502 --- a/components/display_drivers/include/st7796.hpp +++ b/components/display_drivers/include/st7796.hpp @@ -213,14 +213,12 @@ class St7796 { uint32_t size = width * height; static constexpr int max_pixels_to_send = 1024; - uint16_t color_data[max_pixels_to_send]; - for (auto &pixel : color_data) { - pixel = color; - } + std::array color_data; + std::fill(color_data.begin(), color_data.end(), color); for (int i = 0; i < size; i += max_pixels_to_send) { size_t num_pixels = std::min((int)(size - i), max_pixels_to_send); write_command_(static_cast(Command::ramwr), - {reinterpret_cast(color_data), num_pixels * 2}, 0); + {reinterpret_cast(color_data.data()), num_pixels * 2}, 0); } } From 13b76825c4ac8c4b8f9727cfa6c26d9dccaa3fef Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Wed, 27 May 2026 22:05:38 -0500 Subject: [PATCH 5/7] rename appropriately --- .github/workflows/build.yml | 2 +- .github/workflows/upload_components.yml | 2 +- .../CMakeLists.txt | 0 .../Kconfig | 0 .../README.md | 2 +- .../example/CMakeLists.txt | 4 ++-- .../example/README.md | 0 .../example/main/CMakeLists.txt | 0 .../example/main/click.wav | Bin .../example/main/smartpanlee_sc01_plus_example.cpp} | 8 ++++---- .../example/partitions.csv | 0 .../example/sdkconfig.defaults | 0 .../idf_component.yml | 4 ++-- .../include/smartpanlee-sc01-plus.hpp} | 4 ++-- .../src/smartpanlee-sc01-plus.cpp} | 2 +- doc/Doxyfile | 4 ++-- doc/en/index.rst | 2 +- doc/en/smartpanle_sc01_plus_example.md | 2 -- ...anle_sc01_plus.rst => smartpanlee_sc01_plus.rst} | 4 ++-- doc/en/smartpanlee_sc01_plus_example.md | 2 ++ 20 files changed, 21 insertions(+), 21 deletions(-) rename components/{smartpanle-sc01-plus => smartpanlee-sc01-plus}/CMakeLists.txt (100%) rename components/{smartpanle-sc01-plus => smartpanlee-sc01-plus}/Kconfig (100%) rename components/{smartpanle-sc01-plus => smartpanlee-sc01-plus}/README.md (94%) rename components/{smartpanle-sc01-plus => smartpanlee-sc01-plus}/example/CMakeLists.txt (80%) rename components/{smartpanle-sc01-plus => smartpanlee-sc01-plus}/example/README.md (100%) rename components/{smartpanle-sc01-plus => smartpanlee-sc01-plus}/example/main/CMakeLists.txt (100%) rename components/{smartpanle-sc01-plus => smartpanlee-sc01-plus}/example/main/click.wav (100%) rename components/{smartpanle-sc01-plus/example/main/smartpanle_sc01_plus_example.cpp => smartpanlee-sc01-plus/example/main/smartpanlee_sc01_plus_example.cpp} (97%) mode change 100644 => 100755 rename components/{smartpanle-sc01-plus => smartpanlee-sc01-plus}/example/partitions.csv (100%) rename components/{smartpanle-sc01-plus => smartpanlee-sc01-plus}/example/sdkconfig.defaults (100%) rename components/{smartpanle-sc01-plus => smartpanlee-sc01-plus}/idf_component.yml (88%) rename components/{smartpanle-sc01-plus/include/smartpanle-sc01-plus.hpp => smartpanlee-sc01-plus/include/smartpanlee-sc01-plus.hpp} (99%) mode change 100644 => 100755 rename components/{smartpanle-sc01-plus/src/smartpanle-sc01-plus.cpp => smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp} (99%) mode change 100644 => 100755 delete mode 100755 doc/en/smartpanle_sc01_plus_example.md rename doc/en/{smartpanle_sc01_plus.rst => smartpanlee_sc01_plus.rst} (87%) create mode 100755 doc/en/smartpanlee_sc01_plus_example.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 054daabca..22e79a491 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -181,7 +181,7 @@ jobs: target: esp32s3 - path: 'components/serialization/example' target: esp32 - - path: 'components/smartpanle-sc01-plus/example' + - path: 'components/smartpanlee-sc01-plus/example' target: esp32s3 - path: 'components/socket/example' target: esp32 diff --git a/.github/workflows/upload_components.yml b/.github/workflows/upload_components.yml index 768787c4a..8ed892d8d 100755 --- a/.github/workflows/upload_components.yml +++ b/.github/workflows/upload_components.yml @@ -111,7 +111,7 @@ jobs: components/rx8130ce components/seeed-studio-round-display components/serialization - components/smartpanle-sc01-plus + components/smartpanlee-sc01-plus components/socket components/st25dv components/state_machine diff --git a/components/smartpanle-sc01-plus/CMakeLists.txt b/components/smartpanlee-sc01-plus/CMakeLists.txt similarity index 100% rename from components/smartpanle-sc01-plus/CMakeLists.txt rename to components/smartpanlee-sc01-plus/CMakeLists.txt diff --git a/components/smartpanle-sc01-plus/Kconfig b/components/smartpanlee-sc01-plus/Kconfig similarity index 100% rename from components/smartpanle-sc01-plus/Kconfig rename to components/smartpanlee-sc01-plus/Kconfig diff --git a/components/smartpanle-sc01-plus/README.md b/components/smartpanlee-sc01-plus/README.md similarity index 94% rename from components/smartpanle-sc01-plus/README.md rename to components/smartpanlee-sc01-plus/README.md index 2b50c0c9b..d3cd90859 100755 --- a/components/smartpanle-sc01-plus/README.md +++ b/components/smartpanlee-sc01-plus/README.md @@ -1,6 +1,6 @@ # Smart Panlee SC01 Plus Board Support Package (BSP) Component -[![Badge](https://components.espressif.com/components/espp/smartpanle-sc01-plus/badge.svg)](https://components.espressif.com/components/espp/smartpanle-sc01-plus) +[![Badge](https://components.espressif.com/components/espp/smartpanlee-sc01-plus/badge.svg)](https://components.espressif.com/components/espp/smartpanlee-sc01-plus) The Smart Panlee SC01 Plus (ZX3D50CE08S-USRC / WT32-SC01-PLUS class hardware) is an ESP32-S3 touchscreen display module with a 3.5-inch 320x480 ST7796 LCD, an diff --git a/components/smartpanle-sc01-plus/example/CMakeLists.txt b/components/smartpanlee-sc01-plus/example/CMakeLists.txt similarity index 80% rename from components/smartpanle-sc01-plus/example/CMakeLists.txt rename to components/smartpanlee-sc01-plus/example/CMakeLists.txt index 4eb25eb72..2f705b136 100755 --- a/components/smartpanle-sc01-plus/example/CMakeLists.txt +++ b/components/smartpanlee-sc01-plus/example/CMakeLists.txt @@ -11,11 +11,11 @@ set(EXTRA_COMPONENT_DIRS set( COMPONENTS - "main esptool_py smartpanle-sc01-plus display lvgl task" + "main esptool_py smartpanlee-sc01-plus display lvgl task" CACHE STRING "List of components to include" ) -project(smartpanle_sc01_plus_example) +project(smartpanlee_sc01_plus_example) set(CMAKE_CXX_STANDARD 20) diff --git a/components/smartpanle-sc01-plus/example/README.md b/components/smartpanlee-sc01-plus/example/README.md similarity index 100% rename from components/smartpanle-sc01-plus/example/README.md rename to components/smartpanlee-sc01-plus/example/README.md diff --git a/components/smartpanle-sc01-plus/example/main/CMakeLists.txt b/components/smartpanlee-sc01-plus/example/main/CMakeLists.txt similarity index 100% rename from components/smartpanle-sc01-plus/example/main/CMakeLists.txt rename to components/smartpanlee-sc01-plus/example/main/CMakeLists.txt diff --git a/components/smartpanle-sc01-plus/example/main/click.wav b/components/smartpanlee-sc01-plus/example/main/click.wav similarity index 100% rename from components/smartpanle-sc01-plus/example/main/click.wav rename to components/smartpanlee-sc01-plus/example/main/click.wav diff --git a/components/smartpanle-sc01-plus/example/main/smartpanle_sc01_plus_example.cpp b/components/smartpanlee-sc01-plus/example/main/smartpanlee_sc01_plus_example.cpp old mode 100644 new mode 100755 similarity index 97% rename from components/smartpanle-sc01-plus/example/main/smartpanle_sc01_plus_example.cpp rename to components/smartpanlee-sc01-plus/example/main/smartpanlee_sc01_plus_example.cpp index a6d7b47d6..ef1e4fe14 --- a/components/smartpanle-sc01-plus/example/main/smartpanle_sc01_plus_example.cpp +++ b/components/smartpanlee-sc01-plus/example/main/smartpanlee_sc01_plus_example.cpp @@ -1,5 +1,5 @@ /** - * @file smartpanle_sc01_plus_example.cpp + * @file smartpanlee_sc01_plus_example.cpp * @brief Smart Panlee SC01 Plus BSP Example */ @@ -7,7 +7,7 @@ #include #include -#include "smartpanle-sc01-plus.hpp" +#include "smartpanlee-sc01-plus.hpp" #include "task.hpp" @@ -31,7 +31,7 @@ extern "C" void app_main(void) { logger.info("Starting example!"); circles.reserve(MAX_CIRCLES); - //! [smartpanle sc01 plus example] + //! [smartpanlee sc01 plus example] auto &board = espp::SmartPanleeSc01Plus::get(); if (!board.initialize_lcd()) { @@ -131,7 +131,7 @@ extern "C" void app_main(void) { .stack_size_bytes = 6 * 1024, }}); lv_task.start(); - //! [smartpanle sc01 plus example] + //! [smartpanlee sc01 plus example] while (true) { std::this_thread::sleep_for(1s); diff --git a/components/smartpanle-sc01-plus/example/partitions.csv b/components/smartpanlee-sc01-plus/example/partitions.csv similarity index 100% rename from components/smartpanle-sc01-plus/example/partitions.csv rename to components/smartpanlee-sc01-plus/example/partitions.csv diff --git a/components/smartpanle-sc01-plus/example/sdkconfig.defaults b/components/smartpanlee-sc01-plus/example/sdkconfig.defaults similarity index 100% rename from components/smartpanle-sc01-plus/example/sdkconfig.defaults rename to components/smartpanlee-sc01-plus/example/sdkconfig.defaults diff --git a/components/smartpanle-sc01-plus/idf_component.yml b/components/smartpanlee-sc01-plus/idf_component.yml similarity index 88% rename from components/smartpanle-sc01-plus/idf_component.yml rename to components/smartpanlee-sc01-plus/idf_component.yml index 0a7a4f5ce..7b39110f3 100755 --- a/components/smartpanle-sc01-plus/idf_component.yml +++ b/components/smartpanlee-sc01-plus/idf_component.yml @@ -1,11 +1,11 @@ ## IDF Component Manager Manifest File license: "MIT" description: "Smart Panlee SC01 Plus board support package (BSP) component for ESP-IDF" -url: "https://github.com/esp-cpp/espp/tree/main/components/smartpanle-sc01-plus" +url: "https://github.com/esp-cpp/espp/tree/main/components/smartpanlee-sc01-plus" repository: "git://github.com/esp-cpp/espp.git" maintainers: - William Emfinger -documentation: "https://esp-cpp.github.io/espp/smartpanle_sc01_plus.html" +documentation: "https://esp-cpp.github.io/espp/smartpanlee_sc01_plus.html" examples: - path: example tags: diff --git a/components/smartpanle-sc01-plus/include/smartpanle-sc01-plus.hpp b/components/smartpanlee-sc01-plus/include/smartpanlee-sc01-plus.hpp old mode 100644 new mode 100755 similarity index 99% rename from components/smartpanle-sc01-plus/include/smartpanle-sc01-plus.hpp rename to components/smartpanlee-sc01-plus/include/smartpanlee-sc01-plus.hpp index 1b2a7dbd9..9d9bb0928 --- a/components/smartpanle-sc01-plus/include/smartpanle-sc01-plus.hpp +++ b/components/smartpanlee-sc01-plus/include/smartpanlee-sc01-plus.hpp @@ -40,8 +40,8 @@ namespace espp { /// /// The class is a singleton and can be accessed using the get() method. /// -/// \section smartpanle_sc01_plus_example Example -/// \snippet smartpanle_sc01_plus_example.cpp smartpanle sc01 plus example +/// \section smartpanlee_sc01_plus_example Example +/// \snippet smartpanlee_sc01_plus_example.cpp smartpanlee sc01 plus example class SmartPanleeSc01Plus : public BaseComponent { public: /// Alias for the pixel type used by the display. diff --git a/components/smartpanle-sc01-plus/src/smartpanle-sc01-plus.cpp b/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp old mode 100644 new mode 100755 similarity index 99% rename from components/smartpanle-sc01-plus/src/smartpanle-sc01-plus.cpp rename to components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp index 32e8fb833..82b446c8b --- a/components/smartpanle-sc01-plus/src/smartpanle-sc01-plus.cpp +++ b/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp @@ -1,4 +1,4 @@ -#include "smartpanle-sc01-plus.hpp" +#include "smartpanlee-sc01-plus.hpp" #include #include diff --git a/doc/Doxyfile b/doc/Doxyfile index 82506ff73..3593046c4 100755 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -144,7 +144,7 @@ EXAMPLE_PATH = \ $(PROJECT_PATH)/components/rx8130ce/example/main/rx8130ce_example.cpp \ $(PROJECT_PATH)/components/serialization/example/main/serialization_example.cpp \ $(PROJECT_PATH)/components/seeed-studio-round-display/example/main/seeed_studio_round_display_example.cpp \ - $(PROJECT_PATH)/components/smartpanle-sc01-plus/example/main/smartpanle_sc01_plus_example.cpp \ + $(PROJECT_PATH)/components/smartpanlee-sc01-plus/example/main/smartpanlee_sc01_plus_example.cpp \ $(PROJECT_PATH)/components/socket/example/main/socket_example.cpp \ $(PROJECT_PATH)/components/st25dv/example/main/st25dv_example.cpp \ $(PROJECT_PATH)/components/state_machine/example/main/hfsm_example.cpp \ @@ -321,7 +321,7 @@ INPUT = \ $(PROJECT_PATH)/components/rx8130ce/include/rx8130ce.hpp \ $(PROJECT_PATH)/components/serialization/include/serialization.hpp \ $(PROJECT_PATH)/components/seeed-studio-round-display/include/seeed-studio-round-display.hpp \ - $(PROJECT_PATH)/components/smartpanle-sc01-plus/include/smartpanle-sc01-plus.hpp \ + $(PROJECT_PATH)/components/smartpanlee-sc01-plus/include/smartpanlee-sc01-plus.hpp \ $(PROJECT_PATH)/components/socket/include/socket.hpp \ $(PROJECT_PATH)/components/socket/include/udp_socket.hpp \ $(PROJECT_PATH)/components/socket/include/tcp_socket.hpp \ diff --git a/doc/en/index.rst b/doc/en/index.rst index 908ca54f0..6a2d4fca4 100755 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -66,7 +66,7 @@ This is the documentation for esp-idf c++ components, ESPP (`espp Date: Wed, 27 May 2026 22:06:49 -0500 Subject: [PATCH 6/7] Potential fix for pull request finding ensure clear command sends correct x/y sizing Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- components/display_drivers/include/st7796.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/display_drivers/include/st7796.hpp b/components/display_drivers/include/st7796.hpp index 883fa4502..d6c0cc9c3 100755 --- a/components/display_drivers/include/st7796.hpp +++ b/components/display_drivers/include/st7796.hpp @@ -209,7 +209,11 @@ class St7796 { /// \param height Height in pixels. /// \param color RGB565 fill color. static void clear(size_t x, size_t y, size_t width, size_t height, uint16_t color = 0x0000) { - set_drawing_area(x, y, x + width, y + height); + if (width == 0 || height == 0) { + return; + } + + set_drawing_area(x, y, x + width - 1, y + height - 1); uint32_t size = width * height; static constexpr int max_pixels_to_send = 1024; From c83cf59434e5bcfd2089cf4e61eab98e40431751 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Wed, 27 May 2026 22:22:08 -0500 Subject: [PATCH 7/7] add image to readme --- components/smartpanlee-sc01-plus/example/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/smartpanlee-sc01-plus/example/README.md b/components/smartpanlee-sc01-plus/example/README.md index 5da771406..6338ffe00 100755 --- a/components/smartpanlee-sc01-plus/example/README.md +++ b/components/smartpanlee-sc01-plus/example/README.md @@ -3,6 +3,8 @@ This example demonstrates the core functionality of the `espp::SmartPanleeSc01Plus` BSP component on the Smart Panlee SC01 Plus touchscreen display module. +image + ## Features Demonstrated - ST7796 display initialization over the ESP32-S3 8-bit parallel LCD bus