diff --git a/.github/workflows/build-firmware.yml b/.github/workflows/build-firmware.yml index 30996362..d0d788ba 100644 --- a/.github/workflows/build-firmware.yml +++ b/.github/workflows/build-firmware.yml @@ -189,3 +189,12 @@ jobs: with: board_id: waveshare-s3-touch-43 arch: esp32s3 + waveshare-s3-touch-lcd-147: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Build" + uses: ./.github/actions/build-firmware + with: + board_id: waveshare-s3-touch-lcd-147 + arch: esp32s3 diff --git a/App/Kconfig b/App/Kconfig index 5cc4ce73..11fcabb2 100644 --- a/App/Kconfig +++ b/App/Kconfig @@ -51,6 +51,8 @@ menu "Tactility App" bool "unPhone" config TT_BOARD_WAVESHARE_S3_TOUCH_43 bool "Waveshare ESP32 S3 Touch LCD 4.3" + config TT_BOARD_WAVESHARE_S3_TOUCH_LCD_147 + bool "Waveshare ESP32 S3 Touch LCD 1.47" help Select a board/hardware configuration. Use TT_BOARD_CUSTOM if you will manually configure the board in your project. diff --git a/App/Source/Boards.h b/App/Source/Boards.h index c91850f9..230961a4 100644 --- a/App/Source/Boards.h +++ b/App/Source/Boards.h @@ -65,6 +65,9 @@ #elif defined(CONFIG_TT_BOARD_WAVESHARE_S3_TOUCH_43) #include "WaveshareS3Touch43.h" #define TT_BOARD_HARDWARE &waveshare_s3_touch_43 +#elif defined(CONFIG_TT_BOARD_WAVESHARE_S3_TOUCH_LCD_147) +#include "WaveshareS3TouchLcd147.h" +#define TT_BOARD_HARDWARE &waveshare_s3_touch_lcd_147 #else #define TT_BOARD_HARDWARE NULL #error Replace TT_BOARD_HARDWARE in main.c with your own. Or copy one of the ./sdkconfig.board.* files into ./sdkconfig. diff --git a/Boards/WaveshareS3TouchLcd147/CMakeLists.txt b/Boards/WaveshareS3TouchLcd147/CMakeLists.txt new file mode 100644 index 00000000..68ceecc2 --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + INCLUDE_DIRS "Source" + REQUIRES Tactility esp_lvgl_port EspLcdCompat AXS5106 JD9853 +) diff --git a/Boards/WaveshareS3TouchLcd147/Source/Init.cpp b/Boards/WaveshareS3TouchLcd147/Source/Init.cpp new file mode 100644 index 00000000..99080dab --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/Init.cpp @@ -0,0 +1,43 @@ +#include +#include + +constexpr auto* TAG = "Waveshare"; + +constexpr auto LCD_BL_LEDC_TIMER = LEDC_TIMER_0; +constexpr auto LCD_BL_LEDC_MODE = LEDC_LOW_SPEED_MODE; +constexpr auto LCD_BL_LEDC_CHANNEL = LEDC_CHANNEL_0; +constexpr auto LCD_BL_LEDC_DUTY_RES = LEDC_TIMER_10_BIT; +constexpr auto LCD_BL_LEDC_DUTY = 1024; +constexpr auto LCD_BL_LEDC_FREQUENCY = 10000; + +void setBacklightDuty(uint8_t level) { + uint32_t duty = (level * (LCD_BL_LEDC_DUTY - 1)) / 255; + ESP_ERROR_CHECK(ledc_set_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL, duty)); + ESP_ERROR_CHECK(ledc_update_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL)); +} + +void initBacklight() { + ledc_timer_config_t timer_config = { + .speed_mode = LCD_BL_LEDC_MODE, + .duty_resolution = LCD_BL_LEDC_DUTY_RES, + .timer_num = LCD_BL_LEDC_TIMER, + .freq_hz = LCD_BL_LEDC_FREQUENCY, + .clk_cfg = LEDC_AUTO_CLK}; + ESP_ERROR_CHECK(ledc_timer_config(&timer_config)); + + ledc_channel_config_t channel_config = { + .gpio_num = GPIO_NUM_46, + .speed_mode = LCD_BL_LEDC_MODE, + .channel = LCD_BL_LEDC_CHANNEL, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LCD_BL_LEDC_TIMER, + .duty = 0, // Set duty to 0% + .hpoint = 0}; + ESP_ERROR_CHECK(ledc_channel_config(&channel_config)); +} + +bool initBoot() { + ESP_LOGI(TAG, LOG_MESSAGE_POWER_ON_START); + initBacklight(); + return true; +} diff --git a/Boards/WaveshareS3TouchLcd147/Source/WaveshareS3TouchLcd147.cpp b/Boards/WaveshareS3TouchLcd147/Source/WaveshareS3TouchLcd147.cpp new file mode 100644 index 00000000..09933b9c --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/WaveshareS3TouchLcd147.cpp @@ -0,0 +1,91 @@ +#include +#include + +#include "devices/Display.h" +#include "devices/Sdcard.h" + +#define SPI_TRANSFER_SIZE_LIMIT (172 * 320 * (LV_COLOR_DEPTH / 8)) + +bool initBoot(); + +using namespace tt::hal; + +static std::vector> createDevices() { + return { + createDisplay(), + createSdCard() + }; +} + +extern const Configuration waveshare_s3_touch_lcd_147 = { + .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_42, + .scl_io_num = GPIO_NUM_41, + .sda_pullup_en = true, + .scl_pullup_en = true, + .master = { + .clk_speed = 400000 + }, + .clk_flags = 0 + } + } + }, + .spi { + spi::Configuration { + .device = SPI2_HOST, + .dma = SPI_DMA_CH_AUTO, + .config = { + .mosi_io_num = GPIO_NUM_39, + .miso_io_num = GPIO_NUM_NC, + .sclk_io_num = GPIO_NUM_38, + .quadwp_io_num = GPIO_NUM_NC, // Quad SPI LCD driver is not yet supported + .quadhd_io_num = GPIO_NUM_NC, // Quad SPI LCD driver is not yet supported + .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 = SPI_TRANSFER_SIZE_LIMIT, + .flags = 0, + .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, + .intr_flags = 0 + }, + .initMode = spi::InitMode::ByTactility, + .isMutable = false, + .lock = tt::lvgl::getSyncLock() // esp_lvgl_port owns the lock for the display + }, + spi::Configuration { + .device = SPI3_HOST, + .dma = SPI_DMA_CH_AUTO, + .config = { + .mosi_io_num = GPIO_NUM_15, + .miso_io_num = GPIO_NUM_17, + .sclk_io_num = GPIO_NUM_16, + .data2_io_num = GPIO_NUM_NC, + .data3_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 = SPI_TRANSFER_SIZE_LIMIT, + .flags = 0, + .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, + .intr_flags = 0 + }, + .initMode = spi::InitMode::ByTactility, + .isMutable = false, + .lock = nullptr + } + }, + .uart {} +}; diff --git a/Boards/WaveshareS3TouchLcd147/Source/WaveshareS3TouchLcd147.h b/Boards/WaveshareS3TouchLcd147/Source/WaveshareS3TouchLcd147.h new file mode 100644 index 00000000..a6653bc5 --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/WaveshareS3TouchLcd147.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const tt::hal::Configuration waveshare_s3_touch_lcd_147; diff --git a/Boards/WaveshareS3TouchLcd147/Source/devices/Axs5106Touch.cpp b/Boards/WaveshareS3TouchLcd147/Source/devices/Axs5106Touch.cpp new file mode 100644 index 00000000..6115c5d5 --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/devices/Axs5106Touch.cpp @@ -0,0 +1,37 @@ +#include "Axs5106Touch.h" + +#include +#include + +constexpr auto* TAG = "AXS5106"; + +bool Axs5106Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) { + esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_AXS5106_CONFIG(); + return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK; +} + +bool Axs5106Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) { + return esp_lcd_touch_new_i2c_axs5106(ioHandle, &configuration, &panelHandle) == ESP_OK; +} + +esp_lcd_touch_config_t Axs5106Touch::createEspLcdTouchConfig() { + return { + .x_max = configuration->xMax, + .y_max = configuration->yMax, + .rst_gpio_num = configuration->pinReset, + .int_gpio_num = configuration->pinInterrupt, + .levels = { + .reset = configuration->pinResetLevel, + .interrupt = configuration->pinInterruptLevel, + }, + .flags = { + .swap_xy = configuration->swapXy, + .mirror_x = configuration->mirrorX, + .mirror_y = configuration->mirrorY, + }, + .process_coordinates = nullptr, + .interrupt_callback = nullptr, + .user_data = nullptr, + .driver_data = nullptr + }; +} diff --git a/Boards/WaveshareS3TouchLcd147/Source/devices/Axs5106Touch.h b/Boards/WaveshareS3TouchLcd147/Source/devices/Axs5106Touch.h new file mode 100644 index 00000000..7848d186 --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/devices/Axs5106Touch.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include + +#include + +class Axs5106Touch final : public EspLcdTouch { + +public: + + class Configuration { + public: + + Configuration( + i2c_port_t port, + uint16_t xMax, + uint16_t yMax, + bool swapXy = false, + bool mirrorX = false, + bool mirrorY = false, + gpio_num_t pinReset = GPIO_NUM_NC, + gpio_num_t pinInterrupt = GPIO_NUM_NC, + unsigned int pinResetLevel = 0, + unsigned int pinInterruptLevel = 0 + ) : port(port), + xMax(xMax), + yMax(yMax), + swapXy(swapXy), + mirrorX(mirrorX), + mirrorY(mirrorY), + pinReset(pinReset), + pinInterrupt(pinInterrupt), + pinResetLevel(pinResetLevel), + pinInterruptLevel(pinInterruptLevel) + {} + + i2c_port_t port; + uint16_t xMax; + uint16_t yMax; + bool swapXy; + bool mirrorX; + bool mirrorY; + gpio_num_t pinReset; + gpio_num_t pinInterrupt; + unsigned int pinResetLevel; + unsigned int pinInterruptLevel; + }; + +private: + + std::unique_ptr configuration; + + bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override; + + bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) override; + + esp_lcd_touch_config_t createEspLcdTouchConfig() override; + +public: + + explicit Axs5106Touch(std::unique_ptr inConfiguration) : configuration(std::move(inConfiguration)) { + assert(configuration != nullptr); + } + + std::string getName() const override { return "AXS5106"; } + + std::string getDescription() const override { return "AXS5106 I2C touch driver"; } +}; diff --git a/Boards/WaveshareS3TouchLcd147/Source/devices/Display.cpp b/Boards/WaveshareS3TouchLcd147/Source/devices/Display.cpp new file mode 100644 index 00000000..b31c4d8b --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/devices/Display.cpp @@ -0,0 +1,52 @@ +#include "Display.h" +#include "Jd9853Display.h" +#include "Axs5106Touch.h" + +constexpr auto LCD_SPI_HOST = SPI2_HOST; +constexpr auto LCD_PIN_CS = GPIO_NUM_21; +constexpr auto LCD_PIN_DC = GPIO_NUM_45; +constexpr auto LCD_PIN_RESET = GPIO_NUM_40; +constexpr auto LCD_HORIZONTAL_RESOLUTION = 172; +constexpr auto LCD_VERTICAL_RESOLUTION = 320; +constexpr auto LCD_SPI_TRANSFER_HEIGHT = (LCD_VERTICAL_RESOLUTION / 10); + +void setBacklightDuty(uint8_t level); + +static std::shared_ptr createTouch() { + // Note for future changes: Reset pin is 48 and interrupt pin is 47 + auto configuration = std::make_unique( + I2C_NUM_0, + 172, + 320, + false, + false, + false, + GPIO_NUM_47, + GPIO_NUM_48 + ); + + return std::make_shared(std::move(configuration)); +} + +std::shared_ptr createDisplay() { + auto touch = createTouch(); + + auto configuration = std::make_unique( + LCD_SPI_HOST, + LCD_PIN_CS, + LCD_PIN_DC, + LCD_PIN_RESET, + 172, + 320, + touch, + false, + false, + false, + true + ); + + configuration->backlightDutyFunction = setBacklightDuty; + + auto display = std::make_shared(std::move(configuration)); + return std::reinterpret_pointer_cast(display); +} diff --git a/Boards/WaveshareS3TouchLcd147/Source/devices/Display.h b/Boards/WaveshareS3TouchLcd147/Source/devices/Display.h new file mode 100644 index 00000000..7a9b967d --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/devices/Display.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::shared_ptr createDisplay(); diff --git a/Boards/WaveshareS3TouchLcd147/Source/devices/Jd9853Display.cpp b/Boards/WaveshareS3TouchLcd147/Source/devices/Jd9853Display.cpp new file mode 100644 index 00000000..1f7b1299 --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/devices/Jd9853Display.cpp @@ -0,0 +1,162 @@ +#include "Jd9853Display.h" + +#include + +#include +#include +#include + +constexpr const char* TAG = "JD9853"; + +bool Jd9853Display::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) { + const esp_lcd_panel_io_spi_config_t panel_io_config = { + .cs_gpio_num = configuration->csPin, + .dc_gpio_num = configuration->dcPin, + .spi_mode = 0, + .pclk_hz = configuration->pixelClockFrequency, + .trans_queue_depth = configuration->transactionQueueDepth, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .flags = { + .dc_high_on_cmd = 0, + .dc_low_on_data = 0, + .dc_low_on_param = 0, + .octal_mode = 0, + .quad_mode = 0, + .sio_mode = 0, + .lsb_first = 0, + .cs_high_active = 0 + } + }; + + return esp_lcd_new_panel_io_spi(configuration->spiHostDevice, &panel_io_config, &outHandle) == ESP_OK; +} + +bool Jd9853Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) { + jd9853_vendor_config_t vendor_config = { + .init_cmds = nullptr, + .init_cmds_size = 0 + }; + + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = configuration->resetPin, + .rgb_ele_order = configuration->rgbElementOrder, + .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, + .bits_per_pixel = 16, + .flags = { + .reset_active_high = false + }, + .vendor_config = &vendor_config + }; + + if (esp_lcd_new_panel_jd9853(ioHandle, &panel_config, &panelHandle) != ESP_OK) { + TT_LOG_E(TAG, "Failed to create panel"); + return false; + } + + if (esp_lcd_panel_reset(panelHandle) != ESP_OK) { + TT_LOG_E(TAG, "Failed to reset panel"); + return false; + } + + if (esp_lcd_panel_init(panelHandle) != ESP_OK) { + TT_LOG_E(TAG, "Failed to init panel"); + return false; + } + + if (esp_lcd_panel_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) { + TT_LOG_E(TAG, "Failed to swap XY "); + return false; + } + + if (esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) { + TT_LOG_E(TAG, "Failed to set panel to mirror"); + return false; + } + + if (esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) { + TT_LOG_E(TAG, "Failed to set panel to invert"); + return false; + } + + if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) { + TT_LOG_E(TAG, "Failed to turn display on"); + return false; + } + + if (esp_lcd_panel_set_gap(panelHandle, 34, 0) != ESP_OK) { + TT_LOG_E(TAG, "Failed to set panel gap"); + return false; + } + + return true; +} + +lvgl_port_display_cfg_t Jd9853Display::getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) { + return { + .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 = true, + .full_refresh = false, + .direct_mode = false + } + }; +} + +/** + * Note: + * The datasheet implies this should work, but it doesn't: + * https://www.digikey.com/htmldatasheets/production/1640716/0/0/1/ILI9341-Datasheet.pdf + * + * This repo claims it only has 1 curve: + * https://github.com/brucemack/hello-ili9341 + * + * I'm leaving it in as I'm not sure if it's just my hardware that's problematic. + */ +void Jd9853Display::setGammaCurve(uint8_t index) { + uint8_t gamma_curve; + switch (index) { + case 0: + gamma_curve = 0x01; + break; + case 1: + gamma_curve = 0x04; + break; + case 2: + gamma_curve = 0x02; + break; + case 3: + gamma_curve = 0x08; + break; + default: + return; + } + const uint8_t param[] = { + gamma_curve + }; + + if (esp_lcd_panel_io_tx_param(getIoHandle() , LCD_CMD_GAMSET, param, 1) != ESP_OK) { + TT_LOG_E(TAG, "Failed to set gamma"); + } +} diff --git a/Boards/WaveshareS3TouchLcd147/Source/devices/Jd9853Display.h b/Boards/WaveshareS3TouchLcd147/Source/devices/Jd9853Display.h new file mode 100644 index 00000000..b84754e2 --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/devices/Jd9853Display.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include + +class Jd9853Display final : public EspLcdDisplay { + +public: + + class Configuration { + + public: + + Configuration( + spi_host_device_t spiHostDevice, + gpio_num_t csPin, + gpio_num_t dcPin, + gpio_num_t resetPin, + unsigned int horizontalResolution, + unsigned int verticalResolution, + std::shared_ptr touch, + bool swapXY = false, + bool mirrorX = false, + bool mirrorY = false, + bool invertColor = false, + uint32_t bufferSize = 0, // Size in pixel count. 0 means default, which is 1/10 of the screen size, + lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB + ) : spiHostDevice(spiHostDevice), + csPin(csPin), + dcPin(dcPin), + resetPin(resetPin), + horizontalResolution(horizontalResolution), + verticalResolution(verticalResolution), + swapXY(swapXY), + mirrorX(mirrorX), + mirrorY(mirrorY), + invertColor(invertColor), + bufferSize(bufferSize), + rgbElementOrder(rgbElementOrder), + touch(std::move(touch) + ) { + if (this->bufferSize == 0) { + this->bufferSize = horizontalResolution * verticalResolution / 10; + } + } + + spi_host_device_t spiHostDevice; + gpio_num_t csPin; + gpio_num_t dcPin; + gpio_num_t resetPin; + unsigned int pixelClockFrequency = 40'000'000; // Hertz + size_t transactionQueueDepth = 10; + unsigned int horizontalResolution; + unsigned int verticalResolution; + bool swapXY; + bool mirrorX; + bool mirrorY; + bool invertColor; + uint32_t bufferSize; // Size in pixel count. 0 means default, which is 1/10 of the screen size + lcd_rgb_element_order_t rgbElementOrder; + std::shared_ptr touch; + std::function _Nullable backlightDutyFunction = nullptr; + }; + +private: + + std::unique_ptr configuration; + + bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override; + + bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) override; + + lvgl_port_display_cfg_t getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) override; + +public: + + explicit Jd9853Display(std::unique_ptr inConfiguration) : + EspLcdDisplay(tt::hal::spi::getLock(inConfiguration->spiHostDevice)), + configuration(std::move(inConfiguration) + ) { + assert(configuration != nullptr); + } + + std::string getName() const override { return "JD9853"; } + + std::string getDescription() const override { return "JD9853 display"; } + + std::shared_ptr _Nullable getTouchDevice() override { return configuration->touch; } + + void setBacklightDuty(uint8_t backlightDuty) override { + if (configuration->backlightDutyFunction != nullptr) { + configuration->backlightDutyFunction(backlightDuty); + } + } + + bool supportsBacklightDuty() const override { return configuration->backlightDutyFunction != nullptr; } + + void setGammaCurve(uint8_t index) override; + + uint8_t getGammaCurveCount() const override { return 4; }; +}; diff --git a/Boards/WaveshareS3TouchLcd147/Source/devices/Sdcard.cpp b/Boards/WaveshareS3TouchLcd147/Source/devices/Sdcard.cpp new file mode 100644 index 00000000..46ec8738 --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/devices/Sdcard.cpp @@ -0,0 +1,26 @@ +#include "Sdcard.h" + +#include +#include + +using tt::hal::sdcard::SpiSdCardDevice; + +constexpr auto SDCARD_PIN_CS = GPIO_NUM_14; +constexpr auto LCD_PIN_CS = GPIO_NUM_21; + +std::shared_ptr createSdCard() { + auto configuration = std::make_unique( + SDCARD_PIN_CS, + GPIO_NUM_NC, + GPIO_NUM_NC, + GPIO_NUM_NC, + SdCardDevice::MountBehaviour::AtBoot, + tt::hal::spi::getLock(SPI3_HOST), + std::vector { LCD_PIN_CS }, + SPI3_HOST + ); + + return std::make_shared( + std::move(configuration) + ); +} diff --git a/Boards/WaveshareS3TouchLcd147/Source/devices/Sdcard.h b/Boards/WaveshareS3TouchLcd147/Source/devices/Sdcard.h new file mode 100644 index 00000000..6f5443c7 --- /dev/null +++ b/Boards/WaveshareS3TouchLcd147/Source/devices/Sdcard.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +using tt::hal::sdcard::SdCardDevice; + +std::shared_ptr createSdCard(); diff --git a/Buildscripts/board.cmake b/Buildscripts/board.cmake index 344a554b..32a9d93c 100644 --- a/Buildscripts/board.cmake +++ b/Buildscripts/board.cmake @@ -61,6 +61,8 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE) set(TACTILITY_BOARD_PROJECT UnPhone) elseif (board_id STREQUAL "waveshare-s3-touch-43") set(TACTILITY_BOARD_PROJECT WaveshareS3Touch43) + elseif (board_id STREQUAL "waveshare-s3-touch-lcd-147") + set(TACTILITY_BOARD_PROJECT WaveshareS3TouchLcd147) else () set(TACTILITY_BOARD_PROJECT "") endif () diff --git a/Buildscripts/build-and-release-all.sh b/Buildscripts/build-and-release-all.sh index f931a76b..6db9c90e 100755 --- a/Buildscripts/build-and-release-all.sh +++ b/Buildscripts/build-and-release-all.sh @@ -75,6 +75,9 @@ release m5stack-cores3 build waveshare-s3-touch-43 release waveshare-s3-touch-43 +build waveshare-s3-touch-lcd-147 +release waveshare-s3-touch-lcd-147 + build unphone release unphone diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 69cff13e..300034b9 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -16,6 +16,7 @@ - CrowPanel Basic 3.5": check why System Info doesn't show storage info - Update to LVGL v9.3 stable - Files app: delete folder recursively +- Create `app::getSettingsPath()` to get paths to properties files by first trying sd card and then trying `/data` ## Medium Priority @@ -29,6 +30,7 @@ ## Lower Priority +- The boot button on some devices can be used as GPIO_NUM_0 at runtime - Localize all apps - Support hot-plugging SD card (note: this is not possible if they require the CS pin hack) - Explore LVGL9's FreeRTOS functionality diff --git a/Drivers/AXS5106/CMakeLists.txt b/Drivers/AXS5106/CMakeLists.txt new file mode 100644 index 00000000..8dfb70c0 --- /dev/null +++ b/Drivers/AXS5106/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "esp_lcd_touch_axs5106.c" + INCLUDE_DIRS "include" + REQUIRES "esp_lcd" "driver" "espressif__esp_lcd_touch") diff --git a/Drivers/AXS5106/README.md b/Drivers/AXS5106/README.md new file mode 100644 index 00000000..bd808d63 --- /dev/null +++ b/Drivers/AXS5106/README.md @@ -0,0 +1,7 @@ +# AXS5106 + +I2C touch driver. + +Source: https://files.waveshare.com/wiki/1.47inch%20Touch%20LCD/1.47inch_Touch_LCD_Demo_ESP32.zip + +License: Apache 2.0 diff --git a/Drivers/AXS5106/esp_lcd_touch_axs5106.c b/Drivers/AXS5106/esp_lcd_touch_axs5106.c new file mode 100644 index 00000000..6035f628 --- /dev/null +++ b/Drivers/AXS5106/esp_lcd_touch_axs5106.c @@ -0,0 +1,260 @@ +#include +#include "esp_lcd_touch_axs5106.h" + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_touch.h" + +static const char *TAG = "esp_lcd_touch_axs5106"; + +#define TOUCH_AXS5106_TOUCH_POINTS_REG (0X01) +#define TOUCH_AXS5106_TOUCH_P1_XH_REG (0x03) +#define TOUCH_AXS5106_TOUCH_P1_XL_REG (0x04) +#define TOUCH_AXS5106_TOUCH_P1_YH_REG (0x05) +#define TOUCH_AXS5106_TOUCH_P1_YL_REG (0x06) + +#define TOUCH_AXS5106_TOUCH_ID_REG (0x08) +#define TOUCH_AXS5106_TOUCH_P2_XH_REG (0x09) +#define TOUCH_AXS5106_TOUCH_P2_XL_REG (0x0A) +#define TOUCH_AXS5106_TOUCH_P2_YH_REG (0x0B) +#define TOUCH_AXS5106_TOUCH_P2_YL_REG (0x0C) + +/******************************************************************************* + * Function definitions + *******************************************************************************/ +static esp_err_t esp_lcd_touch_axs5106_read_data(esp_lcd_touch_handle_t tp); +static bool esp_lcd_touch_axs5106_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num); +static esp_err_t esp_lcd_touch_axs5106_del(esp_lcd_touch_handle_t tp); + +/* I2C read */ +static esp_err_t touch_axs5106_i2c_write(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t *data, uint8_t len); +static esp_err_t touch_axs5106_i2c_read(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t *data, uint8_t len); + +/* AXS5106 init */ +static esp_err_t touch_axs5106_init(esp_lcd_touch_handle_t tp); +/* AXS5106 reset */ +static esp_err_t touch_axs5106_reset(esp_lcd_touch_handle_t tp); + +esp_err_t esp_lcd_touch_new_i2c_axs5106(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *out_touch) +{ + esp_err_t ret = ESP_OK; + assert(io != NULL); + assert(config != NULL); + assert(out_touch != NULL); + + /* Prepare main structure */ + esp_lcd_touch_handle_t esp_lcd_touch_axs5106 = heap_caps_calloc(1, sizeof(esp_lcd_touch_t), MALLOC_CAP_DEFAULT); + ESP_GOTO_ON_FALSE(esp_lcd_touch_axs5106, ESP_ERR_NO_MEM, err, TAG, "no mem for AXS5106 controller"); + + /* Communication interface */ + esp_lcd_touch_axs5106->io = io; + + /* Only supported callbacks are set */ + esp_lcd_touch_axs5106->read_data = esp_lcd_touch_axs5106_read_data; + esp_lcd_touch_axs5106->get_xy = esp_lcd_touch_axs5106_get_xy; + esp_lcd_touch_axs5106->del = esp_lcd_touch_axs5106_del; + + /* Mutex */ + esp_lcd_touch_axs5106->data.lock.owner = portMUX_FREE_VAL; + + /* Save config */ + memcpy(&esp_lcd_touch_axs5106->config, config, sizeof(esp_lcd_touch_config_t)); + + /* Prepare pin for touch interrupt */ + if (esp_lcd_touch_axs5106->config.int_gpio_num != GPIO_NUM_NC) + { + const gpio_config_t int_gpio_config = { + .mode = GPIO_MODE_INPUT, + .intr_type = (esp_lcd_touch_axs5106->config.levels.interrupt ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE), + .pin_bit_mask = BIT64(esp_lcd_touch_axs5106->config.int_gpio_num)}; + ret = gpio_config(&int_gpio_config); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed"); + + /* Register interrupt callback */ + if (esp_lcd_touch_axs5106->config.interrupt_callback) + { + esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_axs5106, esp_lcd_touch_axs5106->config.interrupt_callback); + } + } + + /* Prepare pin for touch controller reset */ + if (esp_lcd_touch_axs5106->config.rst_gpio_num != GPIO_NUM_NC) + { + const gpio_config_t rst_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = BIT64(esp_lcd_touch_axs5106->config.rst_gpio_num)}; + ret = gpio_config(&rst_gpio_config); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed"); + } + + /* Reset controller */ + ret = touch_axs5106_reset(esp_lcd_touch_axs5106); + ESP_GOTO_ON_ERROR(ret, err, TAG, "AXS5106 reset failed"); + + /* Init controller */ + ret = touch_axs5106_init(esp_lcd_touch_axs5106); + ESP_GOTO_ON_ERROR(ret, err, TAG, "AXS5106 init failed"); + + uint8_t data[3] = { 0 }; + if (touch_axs5106_i2c_read(esp_lcd_touch_axs5106, TOUCH_AXS5106_TOUCH_ID_REG, data, 3) == ESP_OK) { + if (data[0] != 0x00) { + ESP_LOGI(TAG, "Read: %02x %02x %02x", data[0], data[1], data[2]); + } else { + ESP_LOGW(TAG, "Failed to read id: received zeroes"); + } + } else { + ESP_LOGE(TAG, "Failed to read id: receive failed"); + } + +err: + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Error (0x%x)! Touch controller AXS5106 initialization failed!", ret); + if (esp_lcd_touch_axs5106) + { + esp_lcd_touch_axs5106_del(esp_lcd_touch_axs5106); + } + } + + *out_touch = esp_lcd_touch_axs5106; + + return ret; +} + +static esp_err_t esp_lcd_touch_axs5106_read_data(esp_lcd_touch_handle_t tp) +{ + uint8_t data[30] = {0}; + size_t i = 0; + + assert(tp != NULL); + + esp_err_t err = touch_axs5106_i2c_read(tp, TOUCH_AXS5106_TOUCH_POINTS_REG, data, 14); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error: %s", esp_err_to_name(err)); + + uint8_t points = data[1]; + points = points & 0x0F; + + if (points == 0) + { + return ESP_OK; + } + + /* Number of touched points */ + points = (points > 2 ? 2 : points); + + // err = touch_axs5106_i2c_read(tp, TOUCH_AXS5106_TOUCH_P1_XH_REG, data, 6 * points); + // ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + portENTER_CRITICAL(&tp->data.lock); + + /* Number of touched points */ + tp->data.points = points; + + /* Fill all coordinates */ + for (i = 0; i < points; i++) + { + tp->data.coords[i].y = (((uint16_t)(data[4 + i * 6] & 0x0f)) << 8); + tp->data.coords[i].y |= data[5 + i * 6]; + tp->data.coords[i].x = ((uint16_t)(data[2 + i * 6] & 0x0f)) << 8; + tp->data.coords[i].x |= data[3 + i * 6]; + } + portEXIT_CRITICAL(&tp->data.lock); + return ESP_OK; +} + +static bool esp_lcd_touch_axs5106_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num) +{ + assert(tp != NULL); + assert(x != NULL); + assert(y != NULL); + assert(point_num != NULL); + assert(max_point_num > 0); + + portENTER_CRITICAL(&tp->data.lock); + + /* Count of points */ + *point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points); + + for (size_t i = 0; i < *point_num; i++) + { + x[i] = tp->data.coords[i].x; + y[i] = tp->data.coords[i].y; + + if (strength) + { + strength[i] = tp->data.coords[i].strength; + } + } + + /* Invalidate */ + tp->data.points = 0; + + portEXIT_CRITICAL(&tp->data.lock); + + return (*point_num > 0); +} + +static esp_err_t esp_lcd_touch_axs5106_del(esp_lcd_touch_handle_t tp) +{ + assert(tp != NULL); + + /* Reset GPIO pin settings */ + if (tp->config.int_gpio_num != GPIO_NUM_NC) + { + gpio_reset_pin(tp->config.int_gpio_num); + if (tp->config.interrupt_callback) + { + gpio_isr_handler_remove(tp->config.int_gpio_num); + } + } + + /* Reset GPIO pin settings */ + if (tp->config.rst_gpio_num != GPIO_NUM_NC) + { + gpio_reset_pin(tp->config.rst_gpio_num); + } + + free(tp); + + return ESP_OK; +} + +static esp_err_t touch_axs5106_i2c_write(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t *data, uint8_t len) +{ + assert(tp != NULL); + return esp_lcd_panel_io_tx_param(tp->io, reg, data, len); +} + +static esp_err_t touch_axs5106_i2c_read(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t *data, uint8_t len) +{ + assert(tp != NULL); + assert(data != NULL); + return esp_lcd_panel_io_rx_param(tp->io, reg, data, len); +} + +static esp_err_t touch_axs5106_init(esp_lcd_touch_handle_t tp) +{ + return ESP_OK; +} + +static esp_err_t touch_axs5106_reset(esp_lcd_touch_handle_t tp) +{ + assert(tp != NULL); + + if (tp->config.rst_gpio_num != GPIO_NUM_NC) + { + ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + return ESP_OK; +} \ No newline at end of file diff --git a/Drivers/AXS5106/include/esp_lcd_touch_axs5106.h b/Drivers/AXS5106/include/esp_lcd_touch_axs5106.h new file mode 100644 index 00000000..fba26e74 --- /dev/null +++ b/Drivers/AXS5106/include/esp_lcd_touch_axs5106.h @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LCD touch: AXS5106 + */ + +#pragma once + +#include "esp_lcd_touch.h" +#include "driver/i2c_master.h" +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create a new AXS5106 touch driver + * + * @note The I2C communication should be initialized before use this function. + * + * @param io LCD/Touch panel IO handle + * @param config: Touch configuration + * @param out_touch: Touch instance handle + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if there is no memory for allocating main structure + */ +esp_err_t esp_lcd_touch_new_i2c_axs5106(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *out_touch); + +/** + * @brief I2C address of the AXS5106 controller + * + */ +#define ESP_LCD_TOUCH_IO_I2C_AXS5106_ADDRESS (0x63) + +/** + * @brief Touch IO configuration structure + * + */ +#define ESP_LCD_TOUCH_IO_I2C_AXS5106_CONFIG() \ + { \ + .dev_addr = ESP_LCD_TOUCH_IO_I2C_AXS5106_ADDRESS, \ + .control_phase_bytes = 1, \ + .dc_bit_offset = 0, \ + .lcd_cmd_bits = 8, \ + .flags = \ + { \ + .disable_control_phase = 1, \ + } \ + } + + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/JD9853/CMakeLists.txt b/Drivers/JD9853/CMakeLists.txt new file mode 100644 index 00000000..4d433e02 --- /dev/null +++ b/Drivers/JD9853/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "esp_lcd_jd9853.c" + INCLUDE_DIRS "include" + REQUIRES "esp_lcd" "driver") diff --git a/Drivers/JD9853/README.md b/Drivers/JD9853/README.md new file mode 100644 index 00000000..ae5e7238 --- /dev/null +++ b/Drivers/JD9853/README.md @@ -0,0 +1,7 @@ +# JD9853 + +SPI display driver. + +Source: https://files.waveshare.com/wiki/1.47inch%20Touch%20LCD/1.47inch_Touch_LCD_Demo_ESP32.zip + +License: Apache 2.0 diff --git a/Drivers/JD9853/esp_lcd_jd9853.c b/Drivers/JD9853/esp_lcd_jd9853.c new file mode 100644 index 00000000..dea68f2e --- /dev/null +++ b/Drivers/JD9853/esp_lcd_jd9853.c @@ -0,0 +1,460 @@ +#include +#include "esp_lcd_jd9853.h" +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_check.h" + +static const char *TAG = "JD9853"; + +static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel); +static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel); +static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool off); + +typedef struct +{ + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + bool reset_level; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register + const jd9853_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; +} jd9853_panel_t; + +esp_err_t esp_lcd_new_panel_jd9853(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) +{ + esp_err_t ret = ESP_OK; + jd9853_panel_t *jd9853 = NULL; + gpio_config_t io_conf = {0}; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + jd9853 = (jd9853_panel_t *)calloc(1, sizeof(jd9853_panel_t)); + ESP_GOTO_ON_FALSE(jd9853, ESP_ERR_NO_MEM, err, TAG, "no mem for jd9853 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) + { + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + switch (panel_dev_config->color_space) + { + case ESP_LCD_COLOR_SPACE_RGB: + jd9853->madctl_val = 0; + break; + case ESP_LCD_COLOR_SPACE_BGR: + jd9853->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); + break; + } +#else + switch (panel_dev_config->rgb_endian) + { + case LCD_RGB_ENDIAN_RGB: + jd9853->madctl_val = 0; + break; + case LCD_RGB_ENDIAN_BGR: + jd9853->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); + break; + } +#endif + + switch (panel_dev_config->bits_per_pixel) + { + case 16: // RGB565 + jd9853->colmod_val = 0x55; + jd9853->fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + jd9853->colmod_val = 0x66; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + jd9853->fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + jd9853->io = io; + jd9853->reset_gpio_num = panel_dev_config->reset_gpio_num; + jd9853->reset_level = panel_dev_config->flags.reset_active_high; + if (panel_dev_config->vendor_config) + { + jd9853->init_cmds = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; + jd9853->init_cmds_size = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; + } + jd9853->base.del = panel_jd9853_del; + jd9853->base.reset = panel_jd9853_reset; + jd9853->base.init = panel_jd9853_init; + jd9853->base.draw_bitmap = panel_jd9853_draw_bitmap; + jd9853->base.invert_color = panel_jd9853_invert_color; + jd9853->base.set_gap = panel_jd9853_set_gap; + jd9853->base.mirror = panel_jd9853_mirror; + jd9853->base.swap_xy = panel_jd9853_swap_xy; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + jd9853->base.disp_off = panel_jd9853_disp_on_off; +#else + jd9853->base.disp_on_off = panel_jd9853_disp_on_off; +#endif + *ret_panel = &(jd9853->base); + ESP_LOGD(TAG, "new jd9853 panel @%p", jd9853); + + // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_jd9853_VER_MAJOR, ESP_LCD_jd9853_VER_MINOR, + // ESP_LCD_jd9853_VER_PATCH); + + return ESP_OK; + +err: + if (jd9853) + { + if (panel_dev_config->reset_gpio_num >= 0) + { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(jd9853); + } + return ret; +} + +static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + + if (jd9853->reset_gpio_num >= 0) + { + gpio_reset_pin(jd9853->reset_gpio_num); + } + ESP_LOGD(TAG, "del jd9853 panel @%p", jd9853); + free(jd9853); + return ESP_OK; +} + +static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + + // perform hardware reset + if (jd9853->reset_gpio_num >= 0) + { + gpio_set_level(jd9853->reset_gpio_num, jd9853->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(jd9853->reset_gpio_num, !jd9853->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + } + else + { // perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command + } + + return ESP_OK; +} + +typedef struct +{ + uint8_t cmd; + uint8_t data[16]; + uint8_t data_bytes; // Length of data in above data array; 0xFF = end of cmds. +} lcd_init_cmd_t; + +// static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = { +// // {cmd, { data }, data_size, delay_ms} +// /* Power contorl B, power control = 0, DC_ENA = 1 */ +// {0xCF, (uint8_t []){0x00, 0xAA, 0XE0}, 3, 0}, +// /* Power on sequence control, +// * cp1 keeps 1 frame, 1st frame enable +// * vcl = 0, ddvdh=3, vgh=1, vgl=2 +// * DDVDH_ENH=1 +// */ +// {0xED, (uint8_t []){0x67, 0x03, 0X12, 0X81}, 4, 0}, +// /* Driver timing control A, +// * non-overlap=default +1 +// * EQ=default - 1, CR=default +// * pre-charge=default - 1 +// */ +// {0xE8, (uint8_t []){0x8A, 0x01, 0x78}, 3, 0}, +// /* Power control A, Vcore=1.6V, DDVDH=5.6V */ +// {0xCB, (uint8_t []){0x39, 0x2C, 0x00, 0x34, 0x02}, 5, 0}, +// /* Pump ratio control, DDVDH=2xVCl */ +// {0xF7, (uint8_t []){0x20}, 1, 0}, + +// {0xF7, (uint8_t []){0x20}, 1, 0}, +// /* Driver timing control, all=0 unit */ +// {0xEA, (uint8_t []){0x00, 0x00}, 2, 0}, +// /* Power control 1, GVDD=4.75V */ +// {0xC0, (uint8_t []){0x23}, 1, 0}, +// /* Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 */ +// {0xC1, (uint8_t []){0x11}, 1, 0}, +// /* VCOM control 1, VCOMH=4.025V, VCOML=-0.950V */ +// {0xC5, (uint8_t []){0x43, 0x4C}, 2, 0}, +// /* VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 */ +// {0xC7, (uint8_t []){0xA0}, 1, 0}, +// /* Frame rate control, f=fosc, 70Hz fps */ +// {0xB1, (uint8_t []){0x00, 0x1B}, 2, 0}, +// /* Enable 3G, disabled */ +// {0xF2, (uint8_t []){0x00}, 1, 0}, +// /* Gamma set, curve 1 */ +// {0x26, (uint8_t []){0x01}, 1, 0}, +// /* Positive gamma correction */ +// {0xE0, (uint8_t []){0x1F, 0x36, 0x36, 0x3A, 0x0C, 0x05, 0x4F, 0X87, 0x3C, 0x08, 0x11, 0x35, 0x19, 0x13, 0x00}, 15, 0}, +// /* Negative gamma correction */ +// {0xE1, (uint8_t []){0x00, 0x09, 0x09, 0x05, 0x13, 0x0A, 0x30, 0x78, 0x43, 0x07, 0x0E, 0x0A, 0x26, 0x2C, 0x1F}, 15, 0}, +// /* Entry mode set, Low vol detect disabled, normal display */ +// {0xB7, (uint8_t []){0x07}, 1, 0}, +// /* Display function control */ +// {0xB6, (uint8_t []){0x08, 0x82, 0x27}, 3, 0}, +// }; + +static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = { + {0x11, (uint8_t []){ 0x00 }, 0, 120}, + {0xDF, (uint8_t[]){0x98, 0x53}, 2, 0}, + {0xDF, (uint8_t[]){0x98, 0x53}, 2, 0}, + {0xB2, (uint8_t[]){0x23}, 1, 0}, + {0xB7, (uint8_t[]){0x00, 0x47, 0x00, 0x6F}, 4, 0}, + {0xBB, (uint8_t[]){0x1C, 0x1A, 0x55, 0x73, 0x63, 0xF0}, 6, 0}, + {0xC0, (uint8_t[]){0x44, 0xA4}, 2, 0}, + {0xC1, (uint8_t[]){0x16}, 1, 0}, + {0xC3, (uint8_t[]){0x7D, 0x07, 0x14, 0x06, 0xCF, 0x71, 0x72, 0x77}, 8, 0}, + {0xC4, (uint8_t[]){0x00, 0x00, 0xA0, 0x79, 0x0B, 0x0A, 0x16, 0x79, 0x0B, 0x0A, 0x16, 0x82}, 12, 0}, // 00=60Hz 06=57Hz 08=51Hz, LN=320 Line + {0xC8, (uint8_t[]){0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00, 0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00}, 32, 0}, // SET_R_GAMMA + {0xD0, (uint8_t[]){0x04, 0x06, 0x6B, 0x0F, 0x00}, 5, 0}, + {0xD7, (uint8_t[]){0x00, 0x30}, 2, 0}, + {0xE6, (uint8_t[]){0x14}, 1, 0}, + {0xDE, (uint8_t[]){0x01}, 1, 0}, + {0xB7, (uint8_t[]){0x03, 0x13, 0xEF, 0x35, 0x35}, 5, 0}, + {0xC1, (uint8_t[]){0x14, 0x15, 0xC0}, 3, 0}, + {0xC2, (uint8_t[]){0x06, 0x3A}, 2, 0}, + {0xC4, (uint8_t[]){0x72, 0x12}, 2, 0}, + {0xBE, (uint8_t[]){0x00}, 1, 0}, + {0xDE, (uint8_t[]){0x02}, 1, 0}, + {0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0}, + {0xE5, (uint8_t[]){0x01, 0x02, 0x00}, 3, 0}, + {0xDE, (uint8_t[]){0x00}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x3A, (uint8_t[]){0x05}, 1, 0}, // 06=RGB666;05=RGB565 + {0x2A, (uint8_t[]){0x00, 0x22, 0x00, 0xCD}, 4, 0}, // Start_X=34, End_X=205 + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x3F}, 4, 0}, // Start_Y=0, End_Y=319 + {0xDE, (uint8_t[]){0x02}, 1, 0}, + {0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0}, + {0xDE, (uint8_t[]){0x00}, 1, 0}, + {0x29, (uint8_t []){ 0x00 }, 0, 0}, +}; + +static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + + // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){ + jd9853->madctl_val, + }, + 1), + TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){ + jd9853->colmod_val, + }, + 1), + TAG, "send command failed"); + + const jd9853_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (jd9853->init_cmds) + { + init_cmds = jd9853->init_cmds; + init_cmds_size = jd9853->init_cmds_size; + } + else + { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(jd9853_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++) + { + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd) + { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + jd9853->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + jd9853->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) + { + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = jd9853->io; + + x_start += jd9853->x_gap; + x_end += jd9853->x_gap; + y_start += jd9853->y_gap; + y_end += jd9853->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){ + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, + 4), + TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){ + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, + 4), + TAG, "send command failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * jd9853->fb_bits_per_pixel / 8; + esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len); + + return ESP_OK; +} + +static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + int command = 0; + if (invert_color_data) + { + command = LCD_CMD_INVON; + } + else + { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + if (mirror_x) + { + jd9853->madctl_val |= LCD_CMD_MX_BIT; + } + else + { + jd9853->madctl_val &= ~LCD_CMD_MX_BIT; + } + if (mirror_y) + { + jd9853->madctl_val |= LCD_CMD_MY_BIT; + } + else + { + jd9853->madctl_val &= ~LCD_CMD_MY_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + if (swap_axes) + { + jd9853->madctl_val |= LCD_CMD_MV_BIT; + } + else + { + jd9853->madctl_val &= ~LCD_CMD_MV_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + jd9853->x_gap = x_gap; + jd9853->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + int command = 0; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + on_off = !on_off; +#endif + + if (on_off) + { + command = LCD_CMD_DISPON; + } + else + { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/Drivers/JD9853/include/esp_lcd_jd9853.h b/Drivers/JD9853/include/esp_lcd_jd9853.h new file mode 100644 index 00000000..487e7497 --- /dev/null +++ b/Drivers/JD9853/include/esp_lcd_jd9853.h @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +/** + * @file + * @brief ESP LCD: jd9853 + */ + +#pragma once + +#include "hal/spi_ll.h" +#include "esp_lcd_panel_vendor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* _Nullable lock; }; diff --git a/Tactility/Source/hal/spi/Spi.cpp b/Tactility/Source/hal/spi/Spi.cpp index 114333b8..01553ebe 100644 --- a/Tactility/Source/hal/spi/Spi.cpp +++ b/Tactility/Source/hal/spi/Spi.cpp @@ -2,10 +2,10 @@ #include -#define TAG "spi" - namespace tt::hal::spi { +constexpr auto* TAG = "SPI"; + struct Data { std::shared_ptr lock; bool isConfigured = false; @@ -15,7 +15,7 @@ struct Data { static Data dataArray[SPI_HOST_MAX]; -bool init(const std::vector& configurations) { +bool init(const std::vector& configurations) { TT_LOG_I(TAG, "Init"); for (const auto& configuration: configurations) { Data& data = dataArray[configuration.device]; @@ -24,7 +24,7 @@ bool init(const std::vector& configurations) { if (configuration.lock != nullptr) { data.lock = configuration.lock; } else { - data.lock = std::make_shared(); + data.lock = std::make_shared(Mutex::Type::Recursive); } } @@ -76,7 +76,6 @@ bool start(spi_host_device_t device) { #ifdef ESP_PLATFORM - Configuration& config = data.configuration; auto result = spi_bus_initialize(device, &data.configuration.config, data.configuration.dma); if (result != ESP_OK) { TT_LOG_E(TAG, "(%d) Starting: Failed to initialize: %s", device, esp_err_to_name(result)); diff --git a/sdkconfig.board.waveshare-s3-touch-lcd-147 b/sdkconfig.board.waveshare-s3-touch-lcd-147 new file mode 100644 index 00000000..2c656a84 --- /dev/null +++ b/sdkconfig.board.waveshare-s3-touch-lcd-147 @@ -0,0 +1,59 @@ +# Software defaults +# Increase stack size for WiFi (fixes crash after scan) +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072 +CONFIG_LV_FONT_MONTSERRAT_14=y +CONFIG_LV_FONT_MONTSERRAT_18=y +CONFIG_LV_USE_USER_DATA=y +CONFIG_LV_USE_FS_STDIO=y +CONFIG_LV_FS_STDIO_LETTER=65 +CONFIG_LV_FS_STDIO_PATH="" +CONFIG_LV_FS_STDIO_CACHE_SIZE=4096 +CONFIG_LV_USE_LODEPNG=y +CONFIG_LV_USE_BUILTIN_MALLOC=n +CONFIG_LV_USE_CLIB_MALLOC=y +CONFIG_LV_USE_MSGBOX=n +CONFIG_LV_USE_SPINNER=n +CONFIG_LV_USE_WIN=n +CONFIG_LV_USE_SNAPSHOT=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 +CONFIG_FREERTOS_SMP=n +CONFIG_FREERTOS_UNICORE=n +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=5120 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 + +# Hardware: Main +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-16mb.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions-16mb.csv" +CONFIG_TT_BOARD_WAVESHARE_S3_TOUCH_LCD_147=y +CONFIG_TT_BOARD_NAME="Waveshare ESP32 S3 Touch LCD 1.47" +CONFIG_TT_BOARD_ID="waveshare-s3-touch-lcd-147" +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_FLASHMODE_QIO=y +# Hardware: SPI RAM +CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_120M=y +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +# SPI Flash (can set back to 80MHz after ESP-IDF bug is resolved) +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +# LVGL +CONFIG_LV_DPI_DEF=600 +CONFIG_LV_DISP_DEF_REFR_PERIOD=10 +CONFIG_LV_THEME_DEFAULT_DARK=y +# USB +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard"