diff --git a/.gitignore b/.gitignore index f35bc868..496b3a09 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ sdkconfig.old managed_components/ dependencies.lock +.vscode/ + diff --git a/Boards/CYD-2432S028R/CMakeLists.txt b/Boards/CYD-2432S028R/CMakeLists.txt index 1e6393dd..d691e455 100644 --- a/Boards/CYD-2432S028R/CMakeLists.txt +++ b/Boards/CYD-2432S028R/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ILI934x XPT2046 PwmBacklight driver vfs fatfs + REQUIRES Tactility esp_lvgl_port ILI934x XPT2046SoftSPI PwmBacklight driver vfs fatfs ) diff --git a/Boards/CYD-2432S028R/Source/CYD2432S028R.cpp b/Boards/CYD-2432S028R/Source/CYD2432S028R.cpp index a9358552..e1a461b1 100644 --- a/Boards/CYD-2432S028R/Source/CYD2432S028R.cpp +++ b/Boards/CYD-2432S028R/Source/CYD2432S028R.cpp @@ -1,6 +1,7 @@ #include "CYD2432S028R.h" #include "hal/YellowDisplay.h" #include "hal/YellowConstants.h" +#include "hal/YellowSdCard.h" #include #include #include @@ -24,7 +25,7 @@ bool initBoot() { const Configuration cyd_2432s028r_config = { .initBoot = initBoot, .createDisplay = createDisplay, - .sdcard = nullptr, + .sdcard = createYellowSdCard(), .power = nullptr, .i2c = {}, .spi { @@ -53,14 +54,14 @@ const Configuration cyd_2432s028r_config = { .lock = tt::lvgl::getSyncLock() }, - // Touch + // SDCard spi::Configuration { - .device = CYD2432S028R_TOUCH_SPI_HOST, + .device = CYD2432S028R_SDCARD_SPI_HOST, .dma = SPI_DMA_CH_AUTO, .config = { - .mosi_io_num = GPIO_NUM_32, - .miso_io_num = GPIO_NUM_39, - .sclk_io_num = GPIO_NUM_25, + .mosi_io_num = GPIO_NUM_23, + .miso_io_num = GPIO_NUM_19, + .sclk_io_num = GPIO_NUM_18, .quadwp_io_num = GPIO_NUM_NC, .quadhd_io_num = GPIO_NUM_NC, .data4_io_num = GPIO_NUM_NC, diff --git a/Boards/CYD-2432S028R/Source/hal/YellowConstants.h b/Boards/CYD-2432S028R/Source/hal/YellowConstants.h index 29134fd9..96aa39d1 100644 --- a/Boards/CYD-2432S028R/Source/hal/YellowConstants.h +++ b/Boards/CYD-2432S028R/Source/hal/YellowConstants.h @@ -16,9 +16,16 @@ #define CYD2432S028R_TOUCH_SPI_HOST SPI3_HOST #define CYD2432S028R_TOUCH_PIN_CS GPIO_NUM_33 +// Touch (Software SPI) +#define CYD_TOUCH_MISO_PIN GPIO_NUM_39 +#define CYD_TOUCH_MOSI_PIN GPIO_NUM_32 +#define CYD_TOUCH_SCK_PIN GPIO_NUM_25 +#define CYD_TOUCH_CS_PIN GPIO_NUM_33 +#define CYD_TOUCH_IRQ_PIN GPIO_NUM_36 + // SDCard -#define SDCARD_SPI_HOST SPI3_HOST -#define SDCARD_PIN_CS GPIO_NUM_5 +#define CYD2432S028R_SDCARD_SPI_HOST SPI3_HOST +#define CYD2432S028R_SDCARD_PIN_CS GPIO_NUM_5 // SPI Transfer #define CYD_SPI_TRANSFER_SIZE_LIMIT (CYD2432S028R_LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8) diff --git a/Boards/CYD-2432S028R/Source/hal/YellowDisplay.cpp b/Boards/CYD-2432S028R/Source/hal/YellowDisplay.cpp index 01007353..82534fa5 100644 --- a/Boards/CYD-2432S028R/Source/hal/YellowDisplay.cpp +++ b/Boards/CYD-2432S028R/Source/hal/YellowDisplay.cpp @@ -1,21 +1,39 @@ #include "YellowDisplay.h" -#include "Xpt2046Touch.h" +#include "Xpt2046SoftSpi.h" #include "YellowConstants.h" #include #include +static const char* TAG = "YellowDisplay"; + +// Global to hold reference (only needed if calling stop() later) +static std::unique_ptr touch; + static std::shared_ptr createTouch() { - auto configuration = std::make_unique( - CYD2432S028R_TOUCH_SPI_HOST, - CYD2432S028R_TOUCH_PIN_CS, - 240, - 320, - false, - true, - false + auto configuration = std::make_unique( + CYD_TOUCH_MOSI_PIN, + CYD_TOUCH_MISO_PIN, + CYD_TOUCH_SCK_PIN, + CYD_TOUCH_CS_PIN, + CYD2432S028R_LCD_HORIZONTAL_RESOLUTION, // 240 + CYD2432S028R_LCD_VERTICAL_RESOLUTION, // 320 + false, // swapXY + true, // mirrorX + false // mirrorY ); - return std::make_shared(std::move(configuration)); + // Allocate the driver + touch = std::make_unique(std::move(configuration)); + + // Start the driver + if (!touch->start()) { + ESP_LOGE(TAG, "Touch driver start failed"); + return nullptr; + } + + return std::shared_ptr(touch.get(), [](tt::hal::touch::TouchDevice*) { + // No delete needed; `touch` is managed above + }); } std::shared_ptr createDisplay() { @@ -28,9 +46,9 @@ std::shared_ptr createDisplay() { CYD2432S028R_LCD_HORIZONTAL_RESOLUTION, CYD2432S028R_LCD_VERTICAL_RESOLUTION, touch, - false, - true, - false, + false, // swapXY + true, // mirrorX + false, // mirrorY false, CYD2432S028R_LCD_DRAW_BUFFER_SIZE ); diff --git a/Boards/CYD-2432S028R/Source/hal/YellowSdCard.cpp b/Boards/CYD-2432S028R/Source/hal/YellowSdCard.cpp index 3c8322ea..49c81c3a 100644 --- a/Boards/CYD-2432S028R/Source/hal/YellowSdCard.cpp +++ b/Boards/CYD-2432S028R/Source/hal/YellowSdCard.cpp @@ -7,14 +7,14 @@ using tt::hal::sdcard::SpiSdCardDevice; std::shared_ptr createYellowSdCard() { auto* configuration = new SpiSdCardDevice::Config( - SDCARD_PIN_CS, + CYD2432S028R_SDCARD_PIN_CS, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, SdCardDevice::MountBehaviour::AtBoot, std::make_shared(), std::vector(), - SDCARD_SPI_HOST + CYD2432S028R_SDCARD_SPI_HOST ); auto* sdcard = (SdCardDevice*) new SpiSdCardDevice( diff --git a/Drivers/XPT2046SoftSPI/CMakeLists.txt b/Drivers/XPT2046SoftSPI/CMakeLists.txt new file mode 100644 index 00000000..8c472f93 --- /dev/null +++ b/Drivers/XPT2046SoftSPI/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "Source" + INCLUDE_DIRS "Source" + REQUIRES Tactility driver esp_lvgl_port +) \ No newline at end of file diff --git a/Drivers/XPT2046SoftSPI/README.md b/Drivers/XPT2046SoftSPI/README.md new file mode 100644 index 00000000..ec7ff827 --- /dev/null +++ b/Drivers/XPT2046SoftSPI/README.md @@ -0,0 +1,4 @@ +# XPT2046_SoftSPI Driver + +XPT2046_SoftSPI is a driver for the XPT2046 resistive touchscreen controller that uses SoftSPI. +Inspiration from: https://github.com/ddxfish/XPT2046_Bitbang_Arduino_Library/ diff --git a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp new file mode 100644 index 00000000..1ff5cc96 --- /dev/null +++ b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp @@ -0,0 +1,371 @@ +#include "Xpt2046SoftSpi.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "Xpt2046SoftSpi" + +#define RERUN_CALIBRATE false +#define CMD_READ_Y 0x90 // Try different commands if these don't work +#define CMD_READ_X 0xD0 // Alternative: 0x98 for Y, 0xD8 for X + +struct Calibration { + int xMin; + int xMax; + int yMin; + int yMax; +}; + +Calibration cal = { + .xMin = 100, + .xMax = 1900, + .yMin = 100, + .yMax = 1900 +}; + +Xpt2046SoftSpi* Xpt2046SoftSpi::instance = nullptr; + +Xpt2046SoftSpi::Xpt2046SoftSpi(std::unique_ptr inConfiguration) + : configuration(std::move(inConfiguration)) { + assert(configuration != nullptr); +} + +// Defensive check for NVS, put here just in case NVS is init after touch setup. +static void ensureNvsInitialized() { + static bool initialized = false; + if (initialized) return; + + esp_err_t result = nvs_flash_init(); + if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) { + nvs_flash_erase(); // ignore error for safety + result = nvs_flash_init(); + } + + initialized = (result == ESP_OK); +} + +bool Xpt2046SoftSpi::start(lv_display_t* display) { + ensureNvsInitialized(); + + TT_LOG_I(TAG, "Starting Xpt2046SoftSpi touch driver"); + + // Configure GPIO pins + gpio_config_t io_conf = {}; + + // Configure MOSI, CLK, CS as outputs + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << configuration->mosiPin) | + (1ULL << configuration->clkPin) | + (1ULL << configuration->csPin); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + + if (gpio_config(&io_conf) != ESP_OK) { + TT_LOG_E(TAG, "Failed to configure output pins"); + return false; + } + + // Configure MISO as input + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << configuration->misoPin); + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + + if (gpio_config(&io_conf) != ESP_OK) { + TT_LOG_E(TAG, "Failed to configure input pin"); + return false; + } + + // Initialize pin states + gpio_set_level(configuration->csPin, 1); // CS high + gpio_set_level(configuration->clkPin, 0); // CLK low + gpio_set_level(configuration->mosiPin, 0); // MOSI low + + TT_LOG_I(TAG, "GPIO configured: MOSI=%d, MISO=%d, CLK=%d, CS=%d", configuration->mosiPin, configuration->misoPin, configuration->clkPin, configuration->csPin); + + // Load or perform calibration + bool calibrationValid = true; //loadCalibration() && !RERUN_CALIBRATE; + if (calibrationValid) { + // Check if calibration values are valid (xMin != xMax, yMin != yMax) + if (cal.xMin == cal.xMax || cal.yMin == cal.yMax) { + TT_LOG_W(TAG, "Invalid calibration detected: xMin=%d, xMax=%d, yMin=%d, yMax=%d", cal.xMin, cal.xMax, cal.yMin, cal.yMax); + calibrationValid = false; + } + } + + if (!calibrationValid) { + TT_LOG_W(TAG, "Calibration data not found, invalid, or forced recalibration"); + calibrate(); + saveCalibration(); + } else { + TT_LOG_I(TAG, "Loaded calibration: xMin=%d, yMin=%d, xMax=%d, yMax=%d", cal.xMin, cal.yMin, cal.xMax, cal.yMax); + } + + // Create LVGL input device + deviceHandle = lv_indev_create(); + if (!deviceHandle) { + TT_LOG_E(TAG, "Failed to create LVGL input device"); + return false; + } + lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(deviceHandle, touchReadCallback); + lv_indev_set_user_data(deviceHandle, this); + + instance = this; + TT_LOG_I(TAG, "Xpt2046SoftSpi touch driver started successfully"); + return true; +} + +bool Xpt2046SoftSpi::stop() { + TT_LOG_I(TAG, "Stopping Xpt2046SoftSpi touch driver"); + instance = nullptr; + cleanup(); + return true; +} + +void Xpt2046SoftSpi::cleanup() { + if (deviceHandle != nullptr) { + lv_indev_delete(deviceHandle); + deviceHandle = nullptr; + } +} + +int Xpt2046SoftSpi::readSPI(uint8_t command) { + int result = 0; + + // Pull CS low for this transaction + gpio_set_level(configuration->csPin, 0); + ets_delay_us(1); + + // Send 8-bit command + for (int i = 7; i >= 0; i--) { + gpio_set_level(configuration->mosiPin, command & (1 << i)); + gpio_set_level(configuration->clkPin, 1); + ets_delay_us(1); + gpio_set_level(configuration->clkPin, 0); + ets_delay_us(1); + } + + for (int i = 11; i >= 0; i--) { + gpio_set_level(configuration->clkPin, 1); + ets_delay_us(1); + if (gpio_get_level(configuration->misoPin)) { + result |= (1 << i); + } + gpio_set_level(configuration->clkPin, 0); + ets_delay_us(1); + } + + // Pull CS high for this transaction + gpio_set_level(configuration->csPin, 1); + + return result; +} + +void Xpt2046SoftSpi::calibrate() { + const int samples = 8; // More samples for better accuracy + + TT_LOG_I(TAG, "Calibration starting..."); + + TT_LOG_I(TAG, "Touch TOP-LEFT corner"); + + while (!isTouched()) { + vTaskDelay(pdMS_TO_TICKS(50)); + } + + int sumX = 0, sumY = 0; + for (int i = 0; i < samples; i++) { + sumX += readSPI(CMD_READ_X); + sumY += readSPI(CMD_READ_Y); + vTaskDelay(pdMS_TO_TICKS(10)); + } + cal.xMin = sumX / samples; + cal.yMin = sumY / samples; + + TT_LOG_I(TAG, "Top-left calibrated: xMin=%d, yMin=%d", cal.xMin, cal.yMin); + + TT_LOG_I(TAG, "Touch BOTTOM-RIGHT corner"); + + while (!isTouched()) { + vTaskDelay(pdMS_TO_TICKS(50)); + } + + sumX = sumY = 0; + for (int i = 0; i < samples; i++) { + sumX += readSPI(CMD_READ_X); + sumY += readSPI(CMD_READ_Y); + vTaskDelay(pdMS_TO_TICKS(10)); + } + cal.xMax = sumX / samples; + cal.yMax = sumY / samples; + + TT_LOG_I(TAG, "Bottom-right calibrated: xMax=%d, yMax=%d", cal.xMax, cal.yMax); + + TT_LOG_I(TAG, "Calibration completed! xMin=%d, yMin=%d, xMax=%d, yMax=%d", cal.xMin, cal.yMin, cal.xMax, cal.yMax); +} + +bool Xpt2046SoftSpi::loadCalibration() { + TT_LOG_W(TAG, "Calibration load disabled (using fresh calibration only)."); + return false; +} + +void Xpt2046SoftSpi::saveCalibration() { + nvs_handle_t handle; + esp_err_t err = nvs_open("xpt2046", NVS_READWRITE, &handle); + if (err != ESP_OK) { + TT_LOG_E(TAG, "Failed to open NVS for writing (%s)", esp_err_to_name(err)); + return; + } + + err = nvs_set_blob(handle, "cal", &cal, sizeof(cal)); + if (err == ESP_OK) { + nvs_commit(handle); + TT_LOG_I(TAG, "Calibration saved to NVS"); + } else { + TT_LOG_E(TAG, "Failed to write calibration data to NVS (%s)", esp_err_to_name(err)); + } + + nvs_close(handle); +} + +void Xpt2046SoftSpi::setCalibration(int xMin, int yMin, int xMax, int yMax) { + cal.xMin = xMin; + cal.yMin = yMin; + cal.xMax = xMax; + cal.yMax = yMax; + TT_LOG_I(TAG, "Manual calibration set: xMin=%d, yMin=%d, xMax=%d, yMax=%d", xMin, yMin, xMax, yMax); +} + +Point Xpt2046SoftSpi::getTouch() { + + const int samples = 8; // More samples for better accuracy + int totalX = 0, totalY = 0; + int validSamples = 0; + + for (int i = 0; i < samples; i++) { + int rawX = readSPI(CMD_READ_X); + int rawY = readSPI(CMD_READ_Y); + + // Only use valid readings + if (rawX > 100 && rawX < 3900 && rawY > 100 && rawY < 3900) { + totalX += rawX; + totalY += rawY; + validSamples++; + } + + vTaskDelay(pdMS_TO_TICKS(1)); + } + + if (validSamples == 0) { + return Point {0, 0}; + } + + int rawX = totalX / validSamples; + int rawY = totalY / validSamples; + + const int xRange = cal.xMax - cal.xMin; + const int yRange = cal.yMax - cal.yMin; + + if (xRange <= 0 || yRange <= 0) { + TT_LOG_W(TAG, "Invalid calibration: xRange=%d, yRange=%d", xRange, yRange); + return Point {0, 0}; + } + + int x = (rawX - cal.xMin) * configuration->xMax / xRange; + int y = (rawY - cal.yMin) * configuration->yMax / yRange; + + if (configuration->swapXy) std::swap(x, y); + if (configuration->mirrorX) x = configuration->xMax - x; + if (configuration->mirrorY) y = configuration->yMax - y; + + x = std::clamp(x, 0, (int)configuration->xMax); + y = std::clamp(y, 0, (int)configuration->yMax); + + return Point {x, y}; +} + +bool Xpt2046SoftSpi::isTouched() { + const int samples = 3; + int xTotal = 0, yTotal = 0; + int validSamples = 0; + + for (int i = 0; i < samples; i++) { + int x = readSPI(CMD_READ_X); + int y = readSPI(CMD_READ_Y); + + // Basic validity check - XPT2046 typically returns values in range 100-3900 when touched + if (x > 100 && x < 3900 && y > 100 && y < 3900) { + xTotal += x; + yTotal += y; + validSamples++; + } + + vTaskDelay(pdMS_TO_TICKS(1)); // Small delay between samples + } + gpio_set_level(configuration->csPin, 1); + + // Consider touched if we got valid readings + bool touched = validSamples >= 2; + + // Debug logging (remove this once working) + if (touched) { + TT_LOG_I(TAG, "Touch detected: validSamples=%d, avgX=%d, avgY=%d", validSamples, xTotal / validSamples, yTotal / validSamples); + } + + return touched; +} + +void Xpt2046SoftSpi::touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data) { + Xpt2046SoftSpi* touch = static_cast(lv_indev_get_user_data(indev)); + + if (touch && touch->isTouched()) { + Point point = touch->getTouch(); + data->point.x = point.x; + data->point.y = point.y; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} + +// Zero-argument start +bool Xpt2046SoftSpi::start() { + // Default to LVGL-less startup if needed + return startLvgl(nullptr); +} + +// Whether this device supports LVGL +bool Xpt2046SoftSpi::supportsLvgl() const { + return true; +} + +// Start with LVGL display +bool Xpt2046SoftSpi::startLvgl(lv_display_t* display) { + return start(display); +} + +// Stop LVGL +bool Xpt2046SoftSpi::stopLvgl() { + cleanup(); + return true; +} + +// Supports a separate touch driver? Yes/No +bool Xpt2046SoftSpi::supportsTouchDriver() { + return true; +} + +// Return driver instance if any +std::shared_ptr Xpt2046SoftSpi::getTouchDriver() { + return nullptr; // replace with actual driver later +} diff --git a/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h new file mode 100644 index 00000000..3c820b3a --- /dev/null +++ b/Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h @@ -0,0 +1,101 @@ +#pragma once + +#include "Tactility/hal/touch/TouchDevice.h" +#include "Tactility/hal/touch/TouchDriver.h" +#include +#include "lvgl.h" +#include +#include +#include +#include +#include +#include + +#ifndef TFT_WIDTH +#define TFT_WIDTH 240 +#endif + +#ifndef TFT_HEIGHT +#define TFT_HEIGHT 320 +#endif + +struct Point { + int x; + int y; +}; + +class Xpt2046SoftSpi : public tt::hal::touch::TouchDevice { +public: + class Configuration { + public: + Configuration( + gpio_num_t mosiPin, + gpio_num_t misoPin, + gpio_num_t clkPin, + gpio_num_t csPin, + uint16_t xMax = TFT_WIDTH, + uint16_t yMax = TFT_HEIGHT, + bool swapXy = false, + bool mirrorX = false, + bool mirrorY = false + ) : mosiPin(mosiPin), + misoPin(misoPin), + clkPin(clkPin), + csPin(csPin), + xMax(xMax), + yMax(yMax), + swapXy(swapXy), + mirrorX(mirrorX), + mirrorY(mirrorY) + {} + + gpio_num_t mosiPin; + gpio_num_t misoPin; + gpio_num_t clkPin; + gpio_num_t csPin; + uint16_t xMax; + uint16_t yMax; + bool swapXy; + bool mirrorX; + bool mirrorY; + }; + +private: + static Xpt2046SoftSpi* instance; + std::unique_ptr configuration; + lv_indev_t* deviceHandle = nullptr; + + int readSPI(uint8_t command); + void cleanup(); + bool loadCalibration(); + void saveCalibration(); + static void touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data); + +public: + explicit Xpt2046SoftSpi(std::unique_ptr inConfiguration); + + // TouchDevice interface + std::string getName() const final { return "Xpt2046SoftSpi"; } + std::string getDescription() const final { return "Xpt2046 Soft SPI touch driver"; } + + bool start() override; // zero-arg start + bool supportsLvgl() const override; + bool startLvgl(lv_display_t* display) override; + bool stopLvgl() override; + bool stop() override; + bool supportsTouchDriver() override; + std::shared_ptr getTouchDriver() override; + lv_indev_t* getLvglIndev() override { return deviceHandle; } + + // Original LVGL-specific start + bool start(lv_display_t* display); + + // XPT2046-specific methods + Point getTouch(); + void calibrate(); + void setCalibration(int xMin, int yMin, int xMax, int yMax); + bool isTouched(); + + // Static instance access + static Xpt2046SoftSpi* getInstance() { return instance; } +};