WT32 SC01 Plus (#416)

This commit is contained in:
Shadowtrance 2025-11-29 02:40:24 +10:00 committed by GitHub
parent 8de88dd3fd
commit 62124541e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 596 additions and 2 deletions

View File

@ -66,7 +66,8 @@ jobs:
{ id: waveshare-s3-lcd-13, arch: esp32s3 },
{ id: waveshare-s3-touch-lcd-128, arch: esp32s3 },
{ id: waveshare-s3-touch-lcd-147, arch: esp32s3 },
{ id: waveshare-s3-touch-lcd-43, arch: esp32s3 }
{ id: waveshare-s3-touch-lcd-43, arch: esp32s3 },
{ id: wireless-tag-wt32-sc01-plus, arch: esp32s3 }
]
runs-on: ubuntu-latest
needs: [ BuildSdk ]
@ -114,4 +115,4 @@ jobs:
CDN_TOKEN_VALUE: ${{ secrets.CDN_TOKEN_VALUE }}
uses: ./.github/actions/publish-firmware
with:
cdn_version: stable
cdn_version: stable

View File

@ -0,0 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility ST7796-i8080 PwmBacklight FT6x36 driver vfs fatfs
)

View File

@ -0,0 +1,69 @@
#include "devices/Display.h"
#include "devices/SdCard.h"
#include <Tactility/hal/Configuration.h>
#include <Tactility/lvgl/LvglSync.h>
#include <PwmBacklight.h>
using namespace tt::hal;
static DeviceVector createDevices() {
return {
createDisplay(),
createSdCard()
};
}
static bool initBoot() {
return driver::pwmbacklight::init(GPIO_NUM_45);
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices,
.i2c = {
i2c::Configuration {
.name = "Internal",
.port = I2C_NUM_0,
.initMode = i2c::InitMode::ByTactility,
.isMutable = false,
.config = (i2c_config_t) {
.mode = I2C_MODE_MASTER,
.sda_io_num = GPIO_NUM_6,
.scl_io_num = GPIO_NUM_5,
.sda_pullup_en = true,
.scl_pullup_en = true,
.master = {
.clk_speed = 400000
},
.clk_flags = 0
}
}
},
.spi {
// SD card
spi::Configuration {
.device = SPI2_HOST,
.dma = SPI_DMA_CH_AUTO,
.config = {
.mosi_io_num = GPIO_NUM_40,
.miso_io_num = GPIO_NUM_38,
.sclk_io_num = GPIO_NUM_39,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.data4_io_num = GPIO_NUM_NC,
.data5_io_num = GPIO_NUM_NC,
.data6_io_num = GPIO_NUM_NC,
.data7_io_num = GPIO_NUM_NC,
.data_io_default_level = false,
.max_transfer_sz = 32768,
.flags = 0,
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
.intr_flags = 0
},
.initMode = spi::InitMode::ByTactility,
.isMutable = false,
.lock = nullptr // No custom lock needed
}
}
};

View File

@ -0,0 +1,41 @@
#include "Display.h"
#include <PwmBacklight.h>
#include <Ft6x36Touch.h>
#include <St7796i8080Display.h>
constexpr auto LCD_HORIZONTAL_RESOLUTION = 320;
constexpr auto LCD_VERTICAL_RESOLUTION = 480;
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Ft6x36Touch::Configuration>(
I2C_NUM_0,
GPIO_NUM_7,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION
);
auto touch = std::make_shared<Ft6x36Touch>(std::move(configuration));
return std::static_pointer_cast<tt::hal::touch::TouchDevice>(touch);
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto configuration = St7796i8080Display::Configuration(
GPIO_NUM_NC, //CS
GPIO_NUM_0, //RS
GPIO_NUM_47, //WR
{ GPIO_NUM_9, GPIO_NUM_46, GPIO_NUM_3, GPIO_NUM_8,
GPIO_NUM_18, GPIO_NUM_17, GPIO_NUM_16, GPIO_NUM_15 }, // D0..D7
GPIO_NUM_4, //RESET
GPIO_NUM_45 //BL
);
configuration.mirrorX = true;
configuration.horizontalResolution = LCD_HORIZONTAL_RESOLUTION;
configuration.verticalResolution = LCD_VERTICAL_RESOLUTION;
configuration.touch = createTouch();
configuration.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
auto display = std::make_shared<St7796i8080Display>(configuration);
return display;
}

View File

@ -0,0 +1,5 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -0,0 +1,23 @@
#include "SdCard.h"
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
#include <Tactility/lvgl/LvglSync.h>
using tt::hal::sdcard::SpiSdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard() {
auto configuration = std::make_unique<SpiSdCardDevice::Config>(
GPIO_NUM_41,
GPIO_NUM_NC,
GPIO_NUM_NC,
GPIO_NUM_NC,
SdCardDevice::MountBehaviour::AtBoot,
tt::lvgl::getSyncLock(),
std::vector<gpio_num_t>(),
SPI2_HOST
);
return std::make_shared<SpiSdCardDevice>(
std::move(configuration)
);
}

View File

@ -0,0 +1,7 @@
#pragma once
#include "Tactility/hal/sdcard/SdCardDevice.h"
using tt::hal::sdcard::SdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard();

View File

@ -0,0 +1,20 @@
[general]
vendor=Wireless Tag
name=WT32 SC01 Plus
[hardware]
target=ESP32S3
flashSize=16MB
spiRam=true
spiRamMode=QUAD
spiRamSpeed=80M
tinyUsb=true
esptoolFlashFreq=80M
[display]
size=3.5"
shape=rectangle
dpi=139
[lvgl]
colorDepth=16

View File

@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat esp_lcd_st7796 driver
)

View File

@ -0,0 +1,3 @@
# ST7796
A basic ESP32 LVGL driver for ST7796 parallel i8080 displays.

View File

@ -0,0 +1,275 @@
#include "St7796i8080Display.h"
#include <Tactility/Log.h>
#include <driver/gpio.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lvgl_port.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <lvgl.h>
constexpr auto TAG = "St7796i8080Display";
static St7796i8080Display* g_display_instance = nullptr;
St7796i8080Display::St7796i8080Display(const Configuration& config)
: configuration(config), lock(std::make_shared<std::mutex>()) {
// Validate configuration
if (!configuration.isValid()) {
TT_LOG_E(TAG, "Invalid configuration: resolution must be set");
return;
}
}
bool St7796i8080Display::createI80Bus() {
TT_LOG_I(TAG, "Creating I80 bus");
// Create I80 bus configuration
esp_lcd_i80_bus_config_t bus_cfg = {
.dc_gpio_num = configuration.dcPin,
.wr_gpio_num = configuration.wrPin,
.clk_src = LCD_CLK_SRC_PLL160M,
.data_gpio_nums = {
configuration.dataPins[0], configuration.dataPins[1],
configuration.dataPins[2], configuration.dataPins[3],
configuration.dataPins[4], configuration.dataPins[5],
configuration.dataPins[6], configuration.dataPins[7],
},
.bus_width = configuration.busWidth,
.max_transfer_bytes = configuration.bufferSize * sizeof(uint16_t),
.psram_trans_align = 64,
.sram_trans_align = 4
};
if (esp_lcd_new_i80_bus(&bus_cfg, &i80BusHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to create I80 bus");
return false;
}
return true;
}
bool St7796i8080Display::createPanelIO() {
TT_LOG_I(TAG, "Creating panel IO");
// Create panel IO
esp_lcd_panel_io_i80_config_t io_cfg = {
.cs_gpio_num = configuration.csPin,
.pclk_hz = configuration.pixelClockFrequency,
.trans_queue_depth = configuration.transactionQueueDepth,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = configuration.lcdCmdBits,
.lcd_param_bits = configuration.lcdParamBits,
.dc_levels = {
.dc_idle_level = configuration.dcLevels.dcIdleLevel,
.dc_cmd_level = configuration.dcLevels.dcCmdLevel,
.dc_dummy_level = configuration.dcLevels.dcDummyLevel,
.dc_data_level = configuration.dcLevels.dcDataLevel,
},
.flags = {
.cs_active_high = configuration.flags.csActiveHigh,
.reverse_color_bits = configuration.flags.reverseColorBits,
.swap_color_bytes = configuration.flags.swapColorBytes,
.pclk_active_neg = configuration.flags.pclkActiveNeg,
.pclk_idle_low = configuration.flags.pclkIdleLow
}
};
if (esp_lcd_new_panel_io_i80(i80BusHandle, &io_cfg, &ioHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to create panel");
return false;
}
return true;
}
bool St7796i8080Display::createPanel() {
TT_LOG_I(TAG, "Configuring panel");
// Create ST7796 panel
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = configuration.resetPin,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = 16,
.flags = {
.reset_active_high = false
},
.vendor_config = nullptr
};
if (esp_lcd_new_panel_st7796(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to create panel");
return false;
}
// Reset panel
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to reset panel");
return false;
}
// Initialize panel
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to init panel");
return false;
}
// Set swap XY
if (esp_lcd_panel_swap_xy(panelHandle, configuration.swapXY) != ESP_OK) {
TT_LOG_E(TAG, "Failed to swap XY ");
return false;
}
// Set mirror
if (esp_lcd_panel_mirror(panelHandle, configuration.mirrorX, configuration.mirrorY) != ESP_OK) {
TT_LOG_E(TAG, "Failed to set panel to mirror");
return false;
}
// Set inversion
if (esp_lcd_panel_invert_color(panelHandle, configuration.invertColor) != ESP_OK) {
TT_LOG_E(TAG, "Failed to set panel to invert");
return false;
}
// Turn on display
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
TT_LOG_E(TAG, "Failed to turn display on");
return false;
}
return true;
}
bool St7796i8080Display::start() {
TT_LOG_I(TAG, "Initializing I8080 ST7796 Display hardware...");
// Calculate buffer size if needed
configuration.calculateBufferSize();
// Allocate buffer
size_t buffer_size = configuration.bufferSize * LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565);
displayBuffer = (uint8_t*)heap_caps_malloc(buffer_size, MALLOC_CAP_DMA);
if (!displayBuffer) {
TT_LOG_E(TAG, "Failed to allocate display buffer");
return false;
}
// Create I80 bus
if (!createI80Bus()) {
stop();
return false;
}
// Create panel IO
if (!createPanelIO()) {
stop();
return false;
}
// Create panel
if (!createPanel()) {
stop();
return false;
}
TT_LOG_I(TAG, "Display hardware initialized");
return true;
}
bool St7796i8080Display::stop() {
// Turn off display
if (panelHandle) {
esp_lcd_panel_disp_on_off(panelHandle, false);
}
// Destroy in reverse order: panel, IO, bus
if (panelHandle) {
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
}
if (ioHandle) {
esp_lcd_panel_io_del(ioHandle);
ioHandle = nullptr;
}
if (i80BusHandle) {
esp_lcd_del_i80_bus(i80BusHandle);
i80BusHandle = nullptr;
}
// Free buffer 1
if (displayBuffer) {
heap_caps_free(displayBuffer);
displayBuffer = nullptr;
}
// Turn off backlight
if (configuration.backlightDutyFunction) {
configuration.backlightDutyFunction(0);
}
return true;
}
bool St7796i8080Display::startLvgl() {
TT_LOG_I(TAG, "Initializing LVGL for ST7796 display");
// Don't reinitialize hardware if it's already done
if (!ioHandle) {
TT_LOG_I(TAG, "Hardware not initialized, calling start()");
if (!start()) {
TT_LOG_E(TAG, "Hardware initialization failed");
return false;
}
} else {
TT_LOG_I(TAG, "Hardware already initialized, skipping");
}
// Create LVGL display using lvgl_port
lvgl_port_display_cfg_t display_cfg = {
.io_handle = ioHandle,
.panel_handle = panelHandle,
.control_handle = nullptr,
.buffer_size = configuration.bufferSize,
.double_buffer = false,
.trans_size = 0,
.hres = configuration.horizontalResolution,
.vres = configuration.verticalResolution,
.monochrome = false,
.rotation = {
.swap_xy = configuration.swapXY,
.mirror_x = configuration.mirrorX,
.mirror_y = configuration.mirrorY,
},
.color_format = LV_COLOR_FORMAT_RGB565,
.flags = {
.buff_dma = true,
.buff_spiram = false,
.sw_rotate = false,
.swap_bytes = false,
.full_refresh = false,
.direct_mode = false
}
};
// Create the LVGL display
lvglDisplay = lvgl_port_add_disp(&display_cfg);
if (!lvglDisplay) {
TT_LOG_E(TAG, "Failed to create LVGL display");
return false;
}
g_display_instance = this;
TT_LOG_I(TAG, "LVGL display created successfully");
return true;
}
bool St7796i8080Display::stopLvgl() {
if (lvglDisplay) {
lvgl_port_remove_disp(lvglDisplay);
lvglDisplay = nullptr;
}
return true;
}

View File

@ -0,0 +1,136 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_types.h>
#include <esp_lcd_st7796.h>
#include <driver/gpio.h>
#include <array>
#include <memory>
#include <functional>
#include <mutex>
#include <string>
class St7796i8080Display : public tt::hal::display::DisplayDevice {
public:
struct Configuration {
// Pin configuration
gpio_num_t csPin;
gpio_num_t dcPin;
gpio_num_t wrPin;
std::array<gpio_num_t, 8> dataPins;
gpio_num_t resetPin;
gpio_num_t backlightPin;
// Display resolution configuration
uint16_t horizontalResolution;
uint16_t verticalResolution;
// Bus configuration
unsigned int pixelClockFrequency = 20 * 1000 * 1000; // 20MHz default
size_t busWidth = 8; // 8-bit bus
size_t transactionQueueDepth = 10;
size_t bufferSize = 0; // Will be calculated if 0
// LCD command/parameter configuration
int lcdCmdBits = 8;
int lcdParamBits = 8;
// DC line level configuration
struct {
bool dcIdleLevel = 0;
bool dcCmdLevel = 0;
bool dcDummyLevel = 0;
bool dcDataLevel = 1;
} dcLevels;
// Bus flags
struct {
bool csActiveHigh = false;
bool reverseColorBits = false;
bool swapColorBytes = true;
bool pclkActiveNeg = false;
bool pclkIdleLow = false;
} flags;
// Display configuration
bool swapXY = false;
bool mirrorX = false;
bool mirrorY = false;
bool invertColor = true;
// Additional features
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr;
std::function<void(uint8_t)> backlightDutyFunction = nullptr;
// Basic constructor - requires resolution to be set separately
Configuration(gpio_num_t cs, gpio_num_t dc, gpio_num_t wr,
std::array<gpio_num_t, 8> data, gpio_num_t rst, gpio_num_t bl)
: csPin(cs), dcPin(dc), wrPin(wr), dataPins(data), resetPin(rst), backlightPin(bl),
horizontalResolution(0), verticalResolution(0) {} // Initialize to 0
// Method to calculate buffer size after resolution is set
void calculateBufferSize() {
if (bufferSize == 0 && horizontalResolution > 0 && verticalResolution > 0) {
bufferSize = horizontalResolution * verticalResolution / 10;
}
}
// Validation method
bool isValid() const {
return horizontalResolution > 0 && verticalResolution > 0;
}
};
private:
Configuration configuration;
esp_lcd_i80_bus_handle_t i80BusHandle = nullptr;
esp_lcd_panel_io_handle_t ioHandle = nullptr;
esp_lcd_panel_handle_t panelHandle = nullptr;
lv_display_t* lvglDisplay = nullptr;
std::shared_ptr<std::mutex> lock;
uint8_t* displayBuffer = nullptr;
// Internal initialization methods
bool createI80Bus();
bool createPanelIO();
bool createPanel();
public:
explicit St7796i8080Display(const Configuration& config);
lv_display_t* getLvglDisplay() const override { return lvglDisplay; }
std::string getName() const override { return "I8080 ST7796"; }
std::string getDescription() const override { return "I8080-based ST7796 display"; }
// Lifecycle
bool start() override;
bool stop() override;
bool startLvgl() override;
bool stopLvgl() override;
// Capabilities
bool supportsLvgl() const override { return true; }
bool supportsDisplayDriver() const override { return false; }
bool supportsBacklightDuty() const override { return configuration.backlightDutyFunction != nullptr; }
// Touch and backlight
std::shared_ptr<tt::hal::touch::TouchDevice> getTouchDevice() override { return configuration.touch; }
std::shared_ptr<tt::hal::display::DisplayDriver> getDisplayDriver() override { return nullptr; }
void setBacklightDuty(uint8_t backlightDuty) override {
if (configuration.backlightDutyFunction != nullptr) {
configuration.backlightDutyFunction(backlightDuty);
}
}
// Hardware access methods
esp_lcd_panel_io_handle_t getIoHandle() const { return ioHandle; }
esp_lcd_panel_handle_t getPanelHandle() const { return panelHandle; }
// Resolution access methods
uint16_t getHorizontalResolution() const { return configuration.horizontalResolution; }
uint16_t getVerticalResolution() const { return configuration.verticalResolution; }
};
// Factory function for registration
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -79,6 +79,8 @@ menu "Tactility App"
bool "Waveshare ESP32 S3 Touch LCD 1.28"
config TT_DEVICE_WAVESHARE_S3_LCD_13
bool "Waveshare ESP32 S3 LCD 1.3"
config TT_DEVICE_WIRELESS_TAG_WT32_SC01_PLUS
bool "Wireless Tag WT32-SC01 Plus"
help
Select a device.