diff --git a/CMakeLists.txt b/CMakeLists.txt index c349e0b0..d4b121e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,12 +10,22 @@ file(GLOB SRCS src/lgfx/v1/misc/*.cpp src/lgfx/v1/panel/*.cpp src/lgfx/v1/platforms/esp32/*.cpp - src/lgfx/v1/platforms/esp32c3/*.cpp - src/lgfx/v1/platforms/esp32s2/*.cpp - src/lgfx/v1/platforms/esp32s3/*.cpp - src/lgfx/v1/platforms/esp32p4/*.cpp src/lgfx/v1/touch/*.cpp ) + +# Keep common ESP32 sources, then add only the active chip's implementation. +# This also prevents PlatformIO's flat object layout from colliding on same-named +# sources that live in different platform directories. +if(IDF_TARGET STREQUAL "esp32c3") + file(GLOB PLATFORM_SRCS src/lgfx/v1/platforms/esp32c3/*.cpp) +elseif(IDF_TARGET STREQUAL "esp32s2") + file(GLOB PLATFORM_SRCS src/lgfx/v1/platforms/esp32s2/*.cpp) +elseif(IDF_TARGET STREQUAL "esp32s3") + file(GLOB PLATFORM_SRCS src/lgfx/v1/platforms/esp32s3/*.cpp) +elseif(IDF_TARGET STREQUAL "esp32p4") + file(GLOB PLATFORM_SRCS src/lgfx/v1/platforms/esp32p4/*.cpp) +endif() +list(APPEND SRCS ${PLATFORM_SRCS}) set(COMPONENT_SRCS ${SRCS}) if (IDF_VERSION_MAJOR GREATER_EQUAL 6) diff --git a/idf_component.yml b/idf_component.yml index 61c53d38..c06ca055 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -2,4 +2,4 @@ description: Graphics library for M5Stack series issues: https://github.com/m5stack/M5GFX/issues repository: https://github.com/m5stack/M5GFX.git url: https://github.com/m5stack/M5GFX.git -version: 0.2.27 +version: 0.2.28 diff --git a/library.json b/library.json index b6821377..4fcefb72 100644 --- a/library.json +++ b/library.json @@ -10,7 +10,7 @@ "type": "git", "url": "https://github.com/m5stack/M5GFX.git" }, - "version": "0.2.27", + "version": "0.2.28", "frameworks": ["arduino", "espidf", "*"], "platforms": ["espressif32", "native"], "headers": "M5GFX.h" diff --git a/library.properties b/library.properties index 70d7309e..8e8ce71f 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=M5GFX -version=0.2.27 +version=0.2.28 author=M5Stack maintainer=M5Stack sentence=Library for M5Stack All Display diff --git a/src/M5GFX.cpp b/src/M5GFX.cpp index f6f3bac8..a64627ee 100644 --- a/src/M5GFX.cpp +++ b/src/M5GFX.cpp @@ -35,6 +35,7 @@ #include "lgfx/v1/platforms/esp32p4/Bus_DSI.hpp" #include "lgfx/v1/platforms/esp32p4/Panel_ILI9881C.hpp" +#include "lgfx/v1/platforms/esp32p4/Panel_ST7102.hpp" #include "lgfx/v1/platforms/esp32p4/Panel_ST7121.hpp" #include "lgfx/v1/platforms/esp32p4/Panel_ST7123.hpp" #include "lgfx/v1/platforms/esp32p4/Touch_ST7123.hpp" @@ -209,6 +210,34 @@ namespace m5gfx return false; } +#if defined (CONFIG_IDF_TARGET_ESP32P4) + struct Light_M5CoreP4X : public lgfx::ILight + { + bool init(uint8_t brightness) override + { + static constexpr uint16_t pwm_freq = 1000; + const uint8_t freq_data[] = { + 0x25, static_cast(pwm_freq), static_cast(pwm_freq >> 8) + }; + lgfx::i2c::transactionWrite(in_i2c_port, m5ioe1_i2c_addr, + freq_data, sizeof(freq_data), m5ioe1_i2c_freq); + lgfx::i2c::bitOn(in_i2c_port, m5ioe1_i2c_addr, 0x06, 1u << 0, m5ioe1_i2c_freq); + setBrightness(brightness); + return true; + } + + void setBrightness(uint8_t brightness) override + { + uint16_t duty = (brightness << 4) | (brightness >> 4); + const uint8_t duty_data[] = { + 0x1B, static_cast(duty), static_cast(0x80 | (duty >> 8)) + }; + lgfx::i2c::transactionWrite(in_i2c_port, m5ioe1_i2c_addr, + duty_data, sizeof(duty_data), m5ioe1_i2c_freq); + } + }; +#endif + #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) static constexpr std::int32_t axp_i2c_freq = 400000; static constexpr std::uint_fast8_t axp_i2c_addr = 0x34; @@ -2921,7 +2950,118 @@ The usage of each pin is as follows. if (pkg_ver == 0) // pkg_ver == EFUSE_RD_CHIP_VER_PKG_ { - if (board == 0 || board == board_t::board_M5Tab5) + if (board == 0 || board == board_t::board_M5CoreP4X) + { + static constexpr uint8_t corep4x_i2c_addr_list[] = { + 0x4Fu, // M5IOE1 + 0x6Eu, // M5PM1 + 0u + }; + uint32_t i2c_result = _detect_i2c_device(GPIO_NUM_11, GPIO_NUM_9, corep4x_i2c_addr_list); + if (i2c_result == ~0u) { + lgfx::i2c::init(in_i2c_port, GPIO_NUM_11, GPIO_NUM_9); + board = board_t::board_M5CoreP4X; + ESP_LOGI(LIBRARY_NAME, "[Autodetect] board_M5CoreP4X"); + + lgfx::i2c::writeRegister8(in_i2c_port, m5pm1_i2c_addr, 0x09, 0x00, 0, m5pm1_i2c_freq); + lgfx::i2c::writeRegister8(in_i2c_port, m5pm1_i2c_addr, 0x0A, 0x00, 0, m5pm1_i2c_freq); + lgfx::i2c::bitOn(in_i2c_port, m5pm1_i2c_addr, 0x06, 1u << 3, m5pm1_i2c_freq); + + // M5IOE1 G8/G9/G10/G11 control touch reset, backlight, LCD power, + // and LCD reset. G12 supplies the shared 3V3 rail for MBUS, TF card, IMU, + // infrared and Ethernet. + static constexpr uint8_t touch_reset_bit = 1u << 7; + static constexpr uint8_t display_bits_h = 0b00000111; + static constexpr uint8_t shared_power_bit = 1u << 3; + lgfx::i2c::writeRegister8(in_i2c_port, m5ioe1_i2c_addr, 0x23, 0x00, 0, m5ioe1_i2c_freq); + lgfx::i2c::bitOff(in_i2c_port, m5ioe1_i2c_addr, 0x13, touch_reset_bit, m5ioe1_i2c_freq); + lgfx::i2c::bitOff(in_i2c_port, m5ioe1_i2c_addr, 0x14, display_bits_h, m5ioe1_i2c_freq); + lgfx::i2c::bitOff(in_i2c_port, m5ioe1_i2c_addr, 0x14, shared_power_bit, m5ioe1_i2c_freq); + lgfx::i2c::bitOn( in_i2c_port, m5ioe1_i2c_addr, 0x03, touch_reset_bit, m5ioe1_i2c_freq); + lgfx::i2c::bitOn( in_i2c_port, m5ioe1_i2c_addr, 0x04, display_bits_h, m5ioe1_i2c_freq); + lgfx::i2c::bitOn( in_i2c_port, m5ioe1_i2c_addr, 0x04, shared_power_bit, m5ioe1_i2c_freq); + lgfx::i2c::bitOn(in_i2c_port, m5ioe1_i2c_addr, 0x05, touch_reset_bit, m5ioe1_i2c_freq); + lgfx::i2c::bitOn(in_i2c_port, m5ioe1_i2c_addr, 0x06, display_bits_h, m5ioe1_i2c_freq); + lgfx::i2c::bitOn(in_i2c_port, m5ioe1_i2c_addr, 0x06, shared_power_bit, m5ioe1_i2c_freq); + + static constexpr uint16_t pwm_freq = 1000; + const uint8_t freq_data[] = { + 0x25, static_cast(pwm_freq), static_cast(pwm_freq >> 8) + }; + const uint8_t duty_data[] = { 0x1B, 0x00, 0x80 }; + lgfx::i2c::transactionWrite(in_i2c_port, m5ioe1_i2c_addr, + freq_data, sizeof(freq_data), m5ioe1_i2c_freq); + lgfx::i2c::transactionWrite(in_i2c_port, m5ioe1_i2c_addr, + duty_data, sizeof(duty_data), m5ioe1_i2c_freq); + lgfx::delay(150); + +#if !CONFIG_SPIRAM + ESP_LOGE(LIBRARY_NAME, "M5CoreP4X needs PSRAM enabled"); +#else + auto bus_dsi = new Bus_DSI(); + _bus_last.reset(bus_dsi); + auto bus_cfg = bus_dsi->config(); + bus_cfg.bus_id = 0; + bus_cfg.lane_num = 2; + bus_cfg.lane_mbps = 600; + bus_cfg.ldo_chan_id = 3; + bus_cfg.ldo_voltage_mv = 2500; + bus_dsi->config(bus_cfg); + if (bus_dsi->init()) { + lgfx::delay(50); + auto p = new Panel_ST7102(); + _panel_last.reset(p); + auto det = p->config_detail(); + det.dpi_freq_mhz = 24; + det.hsync_back_porch = 40; + det.hsync_pulse_width = 2; + det.hsync_front_porch = 40; + det.vsync_back_porch = 8; + det.vsync_pulse_width = 4; + det.vsync_front_porch = 200; + p->config_detail(det); + + auto cfg = p->config(); + cfg.memory_width = 480; + cfg.memory_height = 480; + cfg.panel_width = 480; + cfg.panel_height = 480; + cfg.readable = false; + cfg.rgb_order = true; + cfg.bus_shared = false; + cfg.offset_x = 0; + cfg.offset_y = 0; + cfg.offset_rotation = 2; + cfg.pin_cs = GPIO_NUM_NC; + cfg.pin_rst = GPIO_NUM_NC; + p->config(cfg); + p->setBus(bus_dsi); + + auto t = new lgfx::Touch_CST3530(); + _touch_last.reset(t); + auto tcfg = t->config(); + tcfg.pin_rst = -1; + tcfg.pin_sda = GPIO_NUM_11; + tcfg.pin_scl = GPIO_NUM_9; + tcfg.pin_int = GPIO_NUM_1; + tcfg.freq = 400000; + tcfg.x_min = 0; + tcfg.x_max = 479; + tcfg.y_min = 0; + tcfg.y_max = 479; + tcfg.i2c_port = I2C_NUM_1; + tcfg.bus_shared = false; + tcfg.offset_rotation = 2; + t->config(tcfg); + _panel_last->setTouch(t); + } + _set_backlight(new Light_M5CoreP4X()); +#endif + goto init_clear; + } + } + + if (board == 0 || board == board_t::board_M5Tab5 || board == board_t::board_M5Tab5X) { // SDA = GPIO_NUM_31 // SCL = GPIO_NUM_32 @@ -2934,8 +3074,10 @@ The usage of each pin is as follows. id = lgfx::i2c::readRegister8(probe_i2c_port, pi4io1_i2c_addr, 0x01).has_value() && lgfx::i2c::readRegister8(probe_i2c_port, pi4io2_i2c_addr, 0x01).has_value(); if (id != 0) { - board = board_t::board_M5Tab5; - ESP_LOGI(LIBRARY_NAME, "[Autodetect] board_M5Tab5"); + if (board == 0) + board = board_t::board_M5Tab5; + ESP_LOGI(LIBRARY_NAME, "[Autodetect] %s", + board == board_t::board_M5Tab5X ? "board_M5Tab5X" : "board_M5Tab5"); static constexpr const uint8_t reg_data_io1_1[] = { 0x03, 0b01111111, 0, // PI4IO_REG_IO_DIR @@ -3707,6 +3849,7 @@ The usage of each pin is as follows. case board_M5Station: title = "M5Station"; break; case board_M5StopWatch: title = "M5StopWatch"; break; case board_M5ChainCaptain: title = "M5ChainCaptain"; break; + case board_M5CoreP4X: title = "M5CoreP4X"; break; case board_M5AtomS3: title = "M5AtomS3"; break; case board_M5AtomS3R: title = "M5AtomS3R"; break; case board_M5Dial: title = "M5Dial"; break; @@ -3717,6 +3860,7 @@ The usage of each pin is as follows. case board_M5VAMeter: title = "M5VAMeter"; break; case board_M5StampPLC: title = "M5StampPLC"; break; case board_M5Tab5: title = "M5Tab5"; break; + case board_M5Tab5X: title = "M5Tab5X"; break; case board_M5UnitPoEP4: title = "M5UnitPoEP4"; break; case board_ArduinoNessoN1: title = "ArduinoNessoN1"; break; default: title = "M5GFX"; break; @@ -3809,6 +3953,7 @@ The usage of each pin is as follows. break; case board_M5Tab5: + case board_M5Tab5X: w = 720; h = 1280; break; diff --git a/src/lgfx/boards.hpp b/src/lgfx/boards.hpp index 8bb8a0ab..88a3bda1 100644 --- a/src/lgfx/boards.hpp +++ b/src/lgfx/boards.hpp @@ -43,6 +43,7 @@ namespace lgfx // This should not be changed to "m5gfx" , board_M5ChainCaptain = 32 , board_M5ToughC5 = 33 , board_M5PaperDIY = 34 + , board_M5Tab5X = 35 /// non display boards , board_M5AtomLite = 128 diff --git a/src/lgfx/v1/gitTagVersion.h b/src/lgfx/v1/gitTagVersion.h index 187a0872..3519c98d 100644 --- a/src/lgfx/v1/gitTagVersion.h +++ b/src/lgfx/v1/gitTagVersion.h @@ -1,4 +1,4 @@ #define LGFX_VERSION_MAJOR 1 #define LGFX_VERSION_MINOR 2 -#define LGFX_VERSION_PATCH 27 +#define LGFX_VERSION_PATCH 28 #define LOVYANGFX_VERSION F( LGFX_VERSION_MAJOR "." LGFX_VERSION_MINOR "." LGFX_VERSION_PATCH ) diff --git a/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp b/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp index aa2d0c9e..c08fb593 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp +++ b/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp @@ -39,6 +39,17 @@ Original Source: #include #include +#if defined (ARDUINO) && (defined (CONFIG_IDF_TARGET_ESP32P4) \ + || defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C6) \ + || defined (CONFIG_IDF_TARGET_ESP32C61)) + #define LGFX_SPI_CLOCK_TAKEOVER + #if defined (CONFIG_IDF_TARGET_ESP32P4) + #include + #else + #include + #endif +#endif + #if __has_include () #include #else @@ -149,6 +160,199 @@ namespace lgfx static __attribute__ ((always_inline)) inline void writereg(uint32_t addr, uint32_t value) { *(volatile uint32_t*)addr = value; } #pragma GCC diagnostic pop +#if defined (LGFX_SPI_CLOCK_TAKEOVER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" + struct spi_clock_state_t + { + uint32_t saved0 = 0; + uint32_t saved1 = 0; + const Bus_SPI* owner = nullptr; + bool active = false; + }; + + static spi_clock_state_t spi_clock_state[SOC_SPI_PERIPH_NUM]; + + static bool spi_clock_host_supported(int spi_host) + { +#if defined (CONFIG_IDF_TARGET_ESP32P4) + return spi_host == SPI2_HOST || spi_host == SPI3_HOST; +#else + return spi_host == SPI2_HOST; +#endif + } + + static uint32_t spi_clock_output_frequency(uint32_t source_hz, uint32_t requested_hz) + { + const uint32_t clock_div = FreqToClockDiv(source_hz, requested_hz); + if (clock_div & SPI_CLK_EQU_SYSCLK) { return source_hz; } + const uint32_t pre = VALUE_GET_FIELD(clock_div, SPI_CLKDIV_PRE) + 1u; + const uint32_t n = VALUE_GET_FIELD(clock_div, SPI_CLKCNT_N) + 1u; + return source_hz / pre / n; + } + + static uint32_t spi_clock_error(uint32_t actual_hz, uint32_t requested_hz) + { + return actual_hz > requested_hz ? actual_hz - requested_hz : requested_hz - actual_hz; + } + + struct spi_clock_target_t + { + uint32_t base_hz; + uint32_t source_div; + }; + + static spi_clock_target_t spi_clock_find_target(uint32_t requested_hz) + { +#if defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + (void)requested_hz; + // The 80 MHz root preserves both common write and read rates (for example + // 40 and 16 MHz), while a 40 MHz root would make 16 MHz inexact. + return { 80000000u, 2u }; +#else + (void)requested_hz; + return { 80000000u, 1u }; +#endif + } + + static bool spi_clock_acquire(const Bus_SPI* owner, int spi_host + , uint32_t write_hz, uint32_t read_hz) + { + if (!spi_clock_host_supported(spi_host) || write_hz == 0 || read_hz == 0) { return false; } + + const auto target = spi_clock_find_target(std::max(write_hz, read_hz)); + const uint32_t current_hz = getSpiClockFrequency(spi_host); + const uint32_t current_write = spi_clock_output_frequency(current_hz, write_hz); + const uint32_t target_write = spi_clock_output_frequency(target.base_hz, write_hz); + const uint32_t target_read = spi_clock_output_frequency(target.base_hz, read_hz); + const uint32_t current_write_error = spi_clock_error(current_write, write_hz); + const uint32_t target_write_error = spi_clock_error(target_write, write_hz); + // Prefer write throughput: a slower read clock is acceptable, but neither + // clock may exceed its request. Equal write results leave the current owner + // untouched to avoid an unnecessary source change. + if (target_write > write_hz || target_read > read_hz + || target_write_error >= current_write_error) + { + return false; + } + + auto& state = spi_clock_state[spi_host]; + if (state.active) { return false; } // Host-scoped ownership is not nestable. + + // The Arduino bus mutex does not serialize ESP-IDF SPI driver users on the + // same host; mixing the two APIs cannot provide transaction-wide exclusion. + PERIPH_RCC_ATOMIC() + { +#if defined (CONFIG_IDF_TARGET_ESP32P4) + if (spi_host == SPI2_HOST) + { + constexpr uint32_t mask = HP_SYS_CLKRST_REG_GPSPI2_CLK_SRC_SEL_M + | HP_SYS_CLKRST_REG_GPSPI2_HS_CLK_DIV_NUM_M + | HP_SYS_CLKRST_REG_GPSPI2_MST_CLK_DIV_NUM_M; + constexpr uint32_t target_value = (4u << HP_SYS_CLKRST_REG_GPSPI2_CLK_SRC_SEL_S) + | (2u << HP_SYS_CLKRST_REG_GPSPI2_HS_CLK_DIV_NUM_S) + | (1u << HP_SYS_CLKRST_REG_GPSPI2_MST_CLK_DIV_NUM_S); + const uint32_t current = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG); + state.saved0 = current & mask; + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG, (current & ~mask) | target_value); + } + else + { + constexpr uint32_t source_mask = HP_SYS_CLKRST_REG_GPSPI3_CLK_SRC_SEL_M; + constexpr uint32_t source_target = 4u << HP_SYS_CLKRST_REG_GPSPI3_CLK_SRC_SEL_S; + constexpr uint32_t divider_mask = HP_SYS_CLKRST_REG_GPSPI3_HS_CLK_DIV_NUM_M + | HP_SYS_CLKRST_REG_GPSPI3_MST_CLK_DIV_NUM_M; + constexpr uint32_t divider_target = (2u << HP_SYS_CLKRST_REG_GPSPI3_HS_CLK_DIV_NUM_S) + | (1u << HP_SYS_CLKRST_REG_GPSPI3_MST_CLK_DIV_NUM_S); + const uint32_t ctrl116 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG); + const uint32_t ctrl117 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL117_REG); + state.saved0 = ctrl116 & source_mask; + state.saved1 = ctrl117 & divider_mask; + // Install safe dividers before selecting the 480 MHz source. + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL117_REG, (ctrl117 & ~divider_mask) | divider_target); + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG, (ctrl116 & ~source_mask) | source_target); + } +#elif defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + constexpr uint32_t mask = PCR_SPI2_CLKM_SEL_M | PCR_SPI2_CLKM_DIV_NUM_M; + const uint32_t target_value = (1u << PCR_SPI2_CLKM_SEL_S) + | ((target.source_div - 1u) << PCR_SPI2_CLKM_DIV_NUM_S); + const uint32_t current = REG_READ(PCR_SPI2_CLKM_CONF_REG); + state.saved0 = current & mask; + REG_WRITE(PCR_SPI2_CLKM_CONF_REG, (current & ~mask) | target_value); +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + constexpr uint32_t mask = PCR_SPI2_CLKM_SEL_M; + constexpr uint32_t target_value = 1u << PCR_SPI2_CLKM_SEL_S; + const uint32_t current = REG_READ(PCR_SPI2_CLKM_CONF_REG); + state.saved0 = current & mask; + REG_WRITE(PCR_SPI2_CLKM_CONF_REG, (current & ~mask) | target_value); +#endif + state.owner = owner; + state.active = true; + } + return true; + } + + static bool spi_clock_owned_by(const Bus_SPI* owner, int spi_host) + { + if (!spi_clock_host_supported(spi_host)) { return false; } + const auto& state = spi_clock_state[spi_host]; + return state.active && state.owner == owner; + } + + static bool spi_clock_restore(const Bus_SPI* owner, int spi_host) + { + if (!spi_clock_host_supported(spi_host)) { return false; } + + auto& state = spi_clock_state[spi_host]; + if (!state.active || state.owner != owner) { return false; } + + PERIPH_RCC_ATOMIC() + { +#if defined (CONFIG_IDF_TARGET_ESP32P4) + if (spi_host == SPI2_HOST) + { + constexpr uint32_t mask = HP_SYS_CLKRST_REG_GPSPI2_CLK_SRC_SEL_M + | HP_SYS_CLKRST_REG_GPSPI2_HS_CLK_DIV_NUM_M + | HP_SYS_CLKRST_REG_GPSPI2_MST_CLK_DIV_NUM_M; + const uint32_t current = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG); + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG, (current & ~mask) | state.saved0); + } + else + { + constexpr uint32_t source_mask = HP_SYS_CLKRST_REG_GPSPI3_CLK_SRC_SEL_M; + constexpr uint32_t divider_mask = HP_SYS_CLKRST_REG_GPSPI3_HS_CLK_DIV_NUM_M + | HP_SYS_CLKRST_REG_GPSPI3_MST_CLK_DIV_NUM_M; + const uint32_t ctrl116 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG); + const uint32_t ctrl117 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL117_REG); + // Leave the 480 MHz source before restoring potentially smaller dividers. + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG, (ctrl116 & ~source_mask) | state.saved0); + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL117_REG, (ctrl117 & ~divider_mask) | state.saved1); + } +#elif defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + constexpr uint32_t mask = PCR_SPI2_CLKM_SEL_M | PCR_SPI2_CLKM_DIV_NUM_M; + const uint32_t current = REG_READ(PCR_SPI2_CLKM_CONF_REG); + REG_WRITE(PCR_SPI2_CLKM_CONF_REG, (current & ~mask) | state.saved0); +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + constexpr uint32_t mask = PCR_SPI2_CLKM_SEL_M; + const uint32_t current = REG_READ(PCR_SPI2_CLKM_CONF_REG); + REG_WRITE(PCR_SPI2_CLKM_CONF_REG, (current & ~mask) | state.saved0); +#endif + state.active = false; + state.owner = nullptr; + } + return true; + } +#pragma GCC diagnostic pop + + Bus_SPI::~Bus_SPI(void) + { + if (spi_clock_owned_by(this, _cfg.spi_host)) + { + release(); + } + } +#endif + void Bus_SPI::config(const config_t& cfg) { _cfg = cfg; @@ -262,6 +466,18 @@ namespace lgfx void Bus_SPI::release(void) { //ESP_LOGI("LGFX","Bus_SPI::release"); +#if defined (LGFX_SPI_CLOCK_TAKEOVER) + if (spi_clock_owned_by(this, _cfg.spi_host)) + { + // An active takeover implies this instance still owns the Arduino bus + // mutex. Finish the transfer and restore the host before releasing it. + dc_control(true); + if (spi_clock_restore(this, _cfg.spi_host)) + { + spi::endTransaction(_cfg.spi_host); + } + } +#endif if (!_inited) return; _inited = false; spi::release(_cfg.spi_host); @@ -288,7 +504,15 @@ namespace lgfx void Bus_SPI::beginTransaction(void) { //ESP_LOGI("LGFX","Bus_SPI::beginTransaction"); - uint32_t freq_apb = getApbFrequency(); + // Bus acquisition can change the SPI source or its pre-dividers. + if (_cfg.use_lock) + { + spi::beginTransaction(_cfg.spi_host); +#if defined (LGFX_SPI_CLOCK_TAKEOVER) + spi_clock_acquire(this, _cfg.spi_host, _cfg.freq_write, _cfg.freq_read); +#endif + } + uint32_t freq_apb = getSpiClockFrequency(_cfg.spi_host); uint32_t clkdiv_write = _clkdiv_write; if (_last_freq_apb != freq_apb) { @@ -323,8 +547,6 @@ namespace lgfx #endif ; - if (_cfg.use_lock) spi::beginTransaction(_cfg.spi_host); - *_spi_user_reg = _user_reg; auto spi_port = _spi_port; (void)spi_port; @@ -340,6 +562,9 @@ namespace lgfx dc_control(true); #if defined ( LGFX_SPIDMA_WORKAROUND ) if (_dma_ch) { spicommon_dmaworkaround_idle(_dma_ch); } +#endif +#if defined (LGFX_SPI_CLOCK_TAKEOVER) + if (_cfg.use_lock) { spi_clock_restore(this, _cfg.spi_host); } #endif if (_cfg.use_lock) spi::endTransaction(_cfg.spi_host); #if defined (ARDUINO) // Arduino ESP32 diff --git a/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp b/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp index 996c8260..c144b7b0 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp +++ b/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp @@ -103,6 +103,11 @@ namespace lgfx }; constexpr Bus_SPI(void) = default; +#if defined (ARDUINO) && (defined (CONFIG_IDF_TARGET_ESP32P4) \ + || defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C6) \ + || defined (CONFIG_IDF_TARGET_ESP32C61)) + ~Bus_SPI(void) override; +#endif const config_t& config(void) const { return _cfg; } diff --git a/src/lgfx/v1/platforms/esp32/common.cpp b/src/lgfx/v1/platforms/esp32/common.cpp index 8a944f6c..109eb450 100644 --- a/src/lgfx/v1/platforms/esp32/common.cpp +++ b/src/lgfx/v1/platforms/esp32/common.cpp @@ -38,6 +38,17 @@ Original Source: #include #include #include +#if defined ( CONFIG_IDF_TARGET_ESP32P4 ) + #include +#elif defined ( CONFIG_IDF_TARGET_ESP32C5 ) || defined ( CONFIG_IDF_TARGET_ESP32C6 ) \ + || defined ( CONFIG_IDF_TARGET_ESP32C61 ) || defined ( CONFIG_IDF_TARGET_ESP32H2 ) + #include +#endif +#if __has_include() && __has_include() + #include + #include + #define LGFX_HAS_ESP_CLK_TREE +#endif #include #include #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) @@ -268,12 +279,139 @@ namespace lgfx #endif } + uint32_t getSpiClockFrequency(int spi_host) + { +#if defined ( CONFIG_IDF_TARGET_ESP32 ) || defined ( CONFIG_IDF_TARGET_ESP32S2 ) \ + || !defined ( CONFIG_IDF_TARGET ) + (void)spi_host; + return getApbFrequency(); +#else + const auto get_xtal_frequency = []() -> uint32_t + { + return static_cast(rtc_clk_xtal_freq_get()) * 1000000u; + }; + const auto get_rc_fast_frequency = []() -> uint32_t + { +#if defined ( LGFX_HAS_ESP_CLK_TREE ) && defined ( SOC_MOD_CLK_RC_FAST ) + uint32_t frequency = 0; + if (ESP_OK == esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_RC_FAST + , ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &frequency) && frequency) + { + return frequency; + } +#endif +#if defined ( SOC_CLK_RC_FAST_FREQ_APPROX ) + return SOC_CLK_RC_FAST_FREQ_APPROX; +#else + return 17500000u; +#endif + }; + +#if defined ( CONFIG_IDF_TARGET_ESP32S3 ) || defined ( CONFIG_IDF_TARGET_ESP32C2 ) \ + || defined ( CONFIG_IDF_TARGET_ESP32C3 ) + static_assert(SPI_MST_CLK_SEL_V == 1u, "SPI clock source selector must be one bit"); + // SPI_MST_CLK_SEL is a shifted mask in the older SPI register headers; + // decode with the explicit value mask rather than VALUE_GET_FIELD. + const uint32_t source_sel = (REG_READ(SPI_CLK_GATE_REG(spi_host + 1)) + >> SPI_MST_CLK_SEL_S) & SPI_MST_CLK_SEL_V; + if (source_sel == 0) { return get_xtal_frequency(); } + #if defined ( CONFIG_IDF_TARGET_ESP32C2 ) + return 40000000u; + #else + return 80000000u; // PLL_F80M is independent of CPU/APB frequency scaling. + #endif + +#elif defined ( CONFIG_IDF_TARGET_ESP32C5 ) || defined ( CONFIG_IDF_TARGET_ESP32C61 ) + if (spi_host != SPI2_HOST) { return 160000000u; } + const uint32_t clkm = REG_READ(PCR_SPI2_CLKM_CONF_REG); + const uint32_t source_sel = VALUE_GET_FIELD(clkm, PCR_SPI2_CLKM_SEL); + const uint32_t source_div = VALUE_GET_FIELD(clkm, PCR_SPI2_CLKM_DIV_NUM) + 1u; + uint32_t source_hz; + switch (source_sel) + { + case 0: source_hz = get_xtal_frequency(); break; + case 1: source_hz = 160000000u; break; + case 2: source_hz = get_rc_fast_frequency(); break; +#if defined ( CONFIG_IDF_TARGET_ESP32C5 ) + case 3: source_hz = 120000000u; break; +#endif + default: source_hz = 160000000u; break; // Safe upper bound for an unknown source. + } + return source_hz / source_div; + +#elif defined ( CONFIG_IDF_TARGET_ESP32C6 ) || defined ( CONFIG_IDF_TARGET_ESP32H2 ) + if (spi_host != SPI2_HOST) + { + #if defined ( CONFIG_IDF_TARGET_ESP32C6 ) + return 80000000u; + #else + return 48000000u; + #endif + } + const uint32_t clkm = REG_READ(PCR_SPI2_CLKM_CONF_REG); + const uint32_t source_sel = VALUE_GET_FIELD(clkm, PCR_SPI2_CLKM_SEL); + switch (source_sel) + { + case 0: return get_xtal_frequency(); + #if defined ( CONFIG_IDF_TARGET_ESP32C6 ) + case 1: return 80000000u; + #else + case 1: return 48000000u; + #endif + case 2: return get_rc_fast_frequency(); + default: + #if defined ( CONFIG_IDF_TARGET_ESP32C6 ) + return 80000000u; // Safe upper bound for an unknown source. + #else + return 48000000u; // Safe upper bound for an unknown source. + #endif + } + +#elif defined ( CONFIG_IDF_TARGET_ESP32P4 ) + const uint32_t ctrl116 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG); + uint32_t source_sel; + uint32_t hs_div; + uint32_t mst_div; + if (spi_host == SPI2_HOST) + { + source_sel = VALUE_GET_FIELD(ctrl116, HP_SYS_CLKRST_REG_GPSPI2_CLK_SRC_SEL); + hs_div = VALUE_GET_FIELD(ctrl116, HP_SYS_CLKRST_REG_GPSPI2_HS_CLK_DIV_NUM); + mst_div = VALUE_GET_FIELD(ctrl116, HP_SYS_CLKRST_REG_GPSPI2_MST_CLK_DIV_NUM); + } + else if (spi_host == SPI3_HOST) + { + const uint32_t ctrl117 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL117_REG); + source_sel = VALUE_GET_FIELD(ctrl116, HP_SYS_CLKRST_REG_GPSPI3_CLK_SRC_SEL); + hs_div = VALUE_GET_FIELD(ctrl117, HP_SYS_CLKRST_REG_GPSPI3_HS_CLK_DIV_NUM); + mst_div = VALUE_GET_FIELD(ctrl117, HP_SYS_CLKRST_REG_GPSPI3_MST_CLK_DIV_NUM); + } + else + { + return getApbFrequency(); + } + + uint32_t source_hz; + switch (source_sel) + { + case 0: source_hz = get_xtal_frequency(); break; + case 1: source_hz = get_rc_fast_frequency(); break; + case 4: source_hz = 480000000u; break; // SPLL + default: source_hz = 480000000u; break; // Safe upper bound for an unknown source. + } + return source_hz / (hs_div + 1) / (mst_div + 1); +#else + (void)spi_host; + return getApbFrequency(); +#endif +#endif + } + uint32_t FreqToClockDiv(uint32_t fapb, uint32_t hz) { if (fapb <= hz) return SPI_CLK_EQU_SYSCLK; uint32_t div_num = fapb / (1 + hz); - uint32_t pre = div_num / 64u; - div_num = div_num / (pre+1); + uint32_t pre = std::min(div_num / (SPI_CLKCNT_N_V + 1u), SPI_CLKDIV_PRE_V); + div_num = std::min(div_num / (pre+1), SPI_CLKCNT_N_V); return div_num << 12 | ((div_num-1)>>1) << 6 | div_num | pre << 18; } @@ -490,8 +628,10 @@ namespace lgfx _gpio_func_out_reg = *reinterpret_cast(GPIO_FUNC0_OUT_SEL_CFG_REG + (pin_num * 4)); #if defined ( GPIO_ENABLE1_REG ) _gpio_enable = *reinterpret_cast(((pin_num & 32) ? GPIO_ENABLE1_REG : GPIO_ENABLE_REG)) & (1 << (pin_num & 31)); + _gpio_out = *reinterpret_cast(((pin_num & 32) ? GPIO_OUT1_REG : GPIO_OUT_REG )) & (1 << (pin_num & 31)); #else _gpio_enable = *reinterpret_cast(GPIO_ENABLE_REG) & (1 << (pin_num & 31)); + _gpio_out = *reinterpret_cast(GPIO_OUT_REG ) & (1 << (pin_num & 31)); #endif _in_func_num = -1; @@ -517,6 +657,22 @@ namespace lgfx auto pin_num = (size_t)_pin_num; if (pin_num < GPIO_NUM_MAX) { + uint32_t pin_mask = 1 << (pin_num & 31); +#if defined ( GPIO_ENABLE1_REG ) + auto gpio_enable_w1ts = reinterpret_cast((pin_num & 32) ? GPIO_ENABLE1_W1TS_REG : GPIO_ENABLE_W1TS_REG); + auto gpio_enable_w1tc = reinterpret_cast((pin_num & 32) ? GPIO_ENABLE1_W1TC_REG : GPIO_ENABLE_W1TC_REG); +#else + auto gpio_enable_w1ts = reinterpret_cast(GPIO_ENABLE_W1TS_REG); + auto gpio_enable_w1tc = reinterpret_cast(GPIO_ENABLE_W1TC_REG); +#endif + // Stop driving before anything else changes what driving would mean. + // Restoring the pad configuration can turn an open drain output back + // into a push-pull one, and the latch left behind by whoever borrowed + // the pin is usually high - the pin would drive that high for as long + // as it takes to reach the latch below. + *gpio_enable_w1tc = pin_mask; + *(_gpio_out ? get_gpio_hi_reg(_pin_num) : get_gpio_lo_reg(_pin_num)) = pin_mask; + if ((uint16_t)_in_func_num < 256) { GPIO.func_in_sel_cfg[_in_func_num].val = _gpio_func_in_reg; // ESP_LOGD("DEBUG","pin:%d in_func_num:%d", (int)pin_num, (int)_in_func_num); @@ -530,24 +686,11 @@ namespace lgfx *reinterpret_cast(GPIO_PIN0_REG + (pin_num * 4)) = _gpio_pin_reg; *reinterpret_cast(GPIO_FUNC0_OUT_SEL_CFG_REG + (pin_num * 4)) = _gpio_func_out_reg; -#if defined ( GPIO_ENABLE1_REG ) - auto gpio_enable_reg = reinterpret_cast(((pin_num & 32) ? GPIO_ENABLE1_REG : GPIO_ENABLE_REG)); -#else - auto gpio_enable_reg = reinterpret_cast(GPIO_ENABLE_REG); -#endif - - uint32_t pin_mask = 1 << (pin_num & 31); - uint32_t val = *gpio_enable_reg; - // ESP_LOGD("DEBUG","restore GPIO_ENABLE_REG:%08x", (int)*gpio_enable_reg); - if (_gpio_enable) - { - val |= pin_mask; - } - else - { - val &= ~pin_mask; - } - *gpio_enable_reg = val; + // The pin drives again only once it is configured and holding the level + // it held before. Set and clear go through their own registers so a pin + // being restored on another core is not caught in a read-modify-write. + if (_gpio_enable) { *gpio_enable_w1ts = pin_mask; } + else { *gpio_enable_w1tc = pin_mask; } } } @@ -891,7 +1034,9 @@ namespace lgfx } uint32_t spi_port = (spi_host + 1); (void)spi_port; - uint32_t clkdiv = FreqToClockDiv(getApbFrequency(), freq); + // Bus acquisition may select a different source or pre-divider. + beginTransaction(spi_host); + uint32_t clkdiv = FreqToClockDiv(getSpiClockFrequency(spi_host), freq); uint32_t user = SPI_USR_MOSI | SPI_USR_MISO | SPI_DOUTDIN; if (spi_mode == 1 || spi_mode == 2) user |= SPI_CK_OUT_EDGE; @@ -917,8 +1062,6 @@ namespace lgfx #endif ; - beginTransaction(spi_host); - writereg(SPI_USER_REG(spi_port), user); #if defined (SPI_PIN_REG) writereg(SPI_PIN_REG(spi_port), pin); @@ -1511,6 +1654,11 @@ namespace lgfx for (auto &bup : backup_pins) { bup.restore(); } } + /// Minimum time a transfer may stall (clock stretching or a wedged bus) + /// before it is given up on; 25ms covers the SMBus Tlow:sext ceiling. + /// NACK handling does not depend on this limit. + static constexpr uint32_t i2c_stall_limit_us = 25000; + static cpp::result i2c_wait(int i2c_port, bool flg_stop = false) { if (flg_stop == false && i2c_context[i2c_port].state.has_error()) { return cpp::fail(i2c_context[i2c_port].state.error()); } @@ -1519,7 +1667,7 @@ namespace lgfx auto dev = getDev(i2c_port); typeof(dev->int_raw) int_raw; int_raw.val = dev->int_raw.val; // ACK待ちステージをスキップした場合も後段の分岐で参照されるため必ず初期化する; - static constexpr uint32_t intmask = I2C_ACK_ERR_INT_RAW_M | I2C_END_DETECT_INT_RAW_M | I2C_ARBITRATION_LOST_INT_RAW_M; + static constexpr uint32_t intmask = I2C_ACK_ERR_INT_RAW_M | I2C_TIME_OUT_INT_RAW_M | I2C_END_DETECT_INT_RAW_M | I2C_ARBITRATION_LOST_INT_RAW_M; if (i2c_context[i2c_port].wait_ack_stage) { @@ -1533,7 +1681,7 @@ namespace lgfx #else uint32_t us_limit = (dev->scl_high_period.period + dev->scl_low_period.period + 16 ) * (1 + dev->status_reg.tx_fifo_cnt); #endif - us_limit += 512 << i2c_context[i2c_port].wait_ack_stage; + us_limit += i2c_stall_limit_us; do { @@ -1545,6 +1693,13 @@ namespace lgfx int_raw.val = dev->int_raw.val; dev->int_clr.val = int_raw.val; + // A timeout or lost arbitration is fatal even when END is also set. + if (int_raw.val & (I2C_TIME_OUT_INT_RAW_M | I2C_ARBITRATION_LOST_INT_RAW_M)) + { + res = cpp::fail(error_t::connection_lost); + i2c_context[i2c_port].state = cpp::fail(error_t::connection_lost); + } + else #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) if (!int_raw.end_detect || int_raw.ack_err) #elif defined ( CONFIG_IDF_TARGET_ESP32C2 ) || defined ( CONFIG_IDF_TARGET_ESP32S3 ) || defined ( CONFIG_IDF_TARGET_ESP32C5 ) || defined ( CONFIG_IDF_TARGET_ESP32C6 ) || defined ( CONFIG_IDF_TARGET_ESP32C61 ) || defined ( CONFIG_IDF_TARGET_ESP32P4 ) || defined ( CONFIG_IDF_TARGET_ESP32H2 ) @@ -1578,13 +1733,27 @@ namespace lgfx { i2c_set_cmd(dev, 0, i2c_cmd_stop, 0); i2c_set_cmd(dev, 1, i2c_cmd_end, 0); - static constexpr uint32_t intmask_ = I2C_ACK_ERR_INT_RAW_M | I2C_TIME_OUT_INT_RAW_M | I2C_END_DETECT_INT_RAW_M | I2C_ARBITRATION_LOST_INT_RAW_M | I2C_TRANS_COMPLETE_INT_RAW_M; + // Wake only on events that end the STOP; a NACK is evaluated after it completes. + static constexpr uint32_t intmask_ = I2C_TIME_OUT_INT_RAW_M | I2C_ARBITRATION_LOST_INT_RAW_M | I2C_TRANS_COMPLETE_INT_RAW_M; updateDev(dev); - dev->int_clr.val = intmask_; + dev->int_clr.val = intmask_ | I2C_ACK_ERR_INT_RAW_M | I2C_END_DETECT_INT_RAW_M; dev->ctr.trans_start = 1; uint32_t ms = lgfx::millis(); taskYIELD(); - while (!(dev->int_raw.val & intmask_) && ((millis() - ms) < 14)); + while (!(dev->int_raw.val & intmask_) && ((millis() - ms) < (i2c_stall_limit_us / 1000))) { taskYIELD(); } + // A STOP that did not complete leaves the bus in an unknown state: + // recover it and refuse further use of this transaction. + { + uint32_t stop_raw = dev->int_raw.val; + if (res.has_value() + && ((stop_raw & (I2C_TIME_OUT_INT_RAW_M | I2C_ARBITRATION_LOST_INT_RAW_M)) + || !(stop_raw & I2C_TRANS_COMPLETE_INT_RAW_M))) + { + res = cpp::fail(error_t::connection_lost); + i2c_context[i2c_port].state = cpp::fail(error_t::connection_lost); + i2c_stop(i2c_port); + } + } #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) if (res.has_value() && dev->int_raw.ack_err) #elif defined ( CONFIG_IDF_TARGET_ESP32C2 ) || defined ( CONFIG_IDF_TARGET_ESP32S3 ) || defined ( CONFIG_IDF_TARGET_ESP32C5 ) || defined ( CONFIG_IDF_TARGET_ESP32C6 ) || defined ( CONFIG_IDF_TARGET_ESP32C61 ) || defined ( CONFIG_IDF_TARGET_ESP32P4 ) || defined ( CONFIG_IDF_TARGET_ESP32H2 ) @@ -1592,8 +1761,9 @@ namespace lgfx #else if (res.has_value() && dev->int_raw.nack) #endif - { + { // The STOP completed but a byte went unacknowledged. res = cpp::fail(error_t::connection_lost); + i2c_context[i2c_port].state = cpp::fail(error_t::connection_lost); } //ESP_LOGI("LGFX", "I2C stop"); } @@ -2000,11 +2170,14 @@ namespace lgfx dev->ctr.fsm_rst = 1; #endif +// SCL-low (clock stretch) watchdog. 2^21 source clocks stays past +// i2c_stall_limit_us on every supported source; the ESP32 register below is at +// its ceiling, about 13ms. #if defined ( CONFIG_IDF_TARGET_ESP32C3 ) - dev->timeout.time_out_value = 31; + dev->timeout.time_out_value = 21; dev->timeout.time_out_en = 1; #elif defined ( CONFIG_IDF_TARGET_ESP32C2 ) || defined ( CONFIG_IDF_TARGET_ESP32S3 ) || defined ( CONFIG_IDF_TARGET_ESP32C5 ) || defined ( CONFIG_IDF_TARGET_ESP32C6 ) || defined ( CONFIG_IDF_TARGET_ESP32C61 ) || defined ( CONFIG_IDF_TARGET_ESP32P4 ) || defined ( CONFIG_IDF_TARGET_ESP32H2 ) - dev->to.time_out_value = 31; + dev->to.time_out_value = 21; dev->to.time_out_en = 1; #else dev->timeout.tout = 0xFFFFF; // max 13ms @@ -2153,6 +2326,7 @@ namespace lgfx updateDev(dev); dev->int_clr.val = intmask; dev->ctr.trans_start = 1; + i2c_context[i2c_port].wait_ack_stage = 2; uint32_t us = lgfx::micros(); taskYIELD(); @@ -2166,7 +2340,7 @@ namespace lgfx do { taskYIELD(); - } while ((len>>1) >= getRxFifoCount(dev) && !(dev->int_raw.val & intmask) && ((lgfx::micros() - us) <= us_limit + 1024)); + } while ((len>>1) >= getRxFifoCount(dev) && !(dev->int_raw.val & intmask) && ((lgfx::micros() - us) <= us_limit + i2c_stall_limit_us)); if (0 == getRxFifoCount(dev)) { diff --git a/src/lgfx/v1/platforms/esp32/common.hpp b/src/lgfx/v1/platforms/esp32/common.hpp index fa8b4c80..b8c31f4c 100644 --- a/src/lgfx/v1/platforms/esp32/common.hpp +++ b/src/lgfx/v1/platforms/esp32/common.hpp @@ -192,6 +192,7 @@ namespace lgfx static inline void gpio_lo(int_fast8_t pin) { if (pin >= 0) *get_gpio_lo_reg(pin) = 1 << (pin & 31); } // ESP_LOGI("LGFX", "gpio_lo: %d", pin); } uint32_t getApbFrequency(void); + uint32_t getSpiClockFrequency(int spi_host); uint32_t FreqToClockDiv(uint32_t fapb, uint32_t hz); /// for I2S and LCD_CAM peripheral clock @@ -321,6 +322,7 @@ namespace lgfx int16_t _in_func_num = -1; int8_t _pin_num = -1; //GPIO_NUM_NC bool _gpio_enable; + bool _gpio_out; }; enum command_t : uint8_t diff --git a/src/lgfx/v1/platforms/esp32p4/Bus_DSI.cpp b/src/lgfx/v1/platforms/esp32p4/Bus_DSI.cpp index 3936d742..7daf8cee 100644 --- a/src/lgfx/v1/platforms/esp32p4/Bus_DSI.cpp +++ b/src/lgfx/v1/platforms/esp32p4/Bus_DSI.cpp @@ -31,10 +31,9 @@ namespace lgfx if (_mipi_dsi_bus) { return true; } - esp_lcd_dsi_bus_config_t bus_config; + esp_lcd_dsi_bus_config_t bus_config = {}; bus_config.bus_id = _cfg.bus_id; bus_config.num_data_lanes = _cfg.lane_num; - bus_config.phy_clk_src = static_cast(MIPI_DSI_PHY_CLK_SRC_DEFAULT); bus_config.lane_bit_rate_mbps = _cfg.lane_mbps; esp_ldo_channel_config_t ldo_cfg; @@ -42,7 +41,7 @@ namespace lgfx ldo_cfg.chan_id = _cfg.ldo_chan_id; ldo_cfg.voltage_mv = _cfg.ldo_voltage_mv; - esp_lcd_dbi_io_config_t dbi_config; + esp_lcd_dbi_io_config_t dbi_config = {}; dbi_config.virtual_channel = 0; dbi_config.lcd_cmd_bits = _cfg.lcd_cmd_bits; dbi_config.lcd_param_bits = _cfg.lcd_param_bits; diff --git a/src/lgfx/v1/platforms/esp32p4/Panel_DSI.cpp b/src/lgfx/v1/platforms/esp32p4/Panel_DSI.cpp index 9462adc1..58ce42de 100644 --- a/src/lgfx/v1/platforms/esp32p4/Panel_DSI.cpp +++ b/src/lgfx/v1/platforms/esp32p4/Panel_DSI.cpp @@ -38,16 +38,10 @@ namespace lgfx auto bus = getBusDSI(); if (bus == nullptr) { return false; } - const uint8_t* params; - for (size_t i = 0; nullptr != (params = getInitParams(i)); ++i) - { - size_t len; - while (0 != (len = params[0])) { -// printf("cmd: %02x, len: %d\n", params[1], len); - bus->writeParams(params[1], ¶ms[2], len - 1); - params += len + 1; - } - vTaskDelay(pdMS_TO_TICKS(getInitDelay(i))); + const bool init_in_command_mode = initInCommandMode(); + if (!init_in_command_mode + && ESP_OK != esp_lcd_panel_init(_disp_panel_handle)) { + return false; } uint8_t madctl_val = 0; @@ -60,10 +54,24 @@ namespace lgfx colmod_val = 0x77; } - bus->writeParams(CMD_MADCTL, &(madctl_val), 1); - bus->writeParams(CMD_COLMOD, &(colmod_val), 1); + const uint8_t* params; + for (size_t i = 0; nullptr != (params = getInitParams(i)); ++i) + { + size_t len; + while (0 != (len = params[0])) { +// printf("cmd: %02x, len: %d\n", params[1], len); + bus->writeParams(params[1], ¶ms[2], len - 1); + params += len + 1; + } + vTaskDelay(pdMS_TO_TICKS(getInitDelay(i))); + } - return (ESP_OK == esp_lcd_panel_init(_disp_panel_handle)); + if (init_in_command_mode) { + bus->writeParams(CMD_MADCTL, &(madctl_val), 1); + bus->writeParams(CMD_COLMOD, &(colmod_val), 1); + return (ESP_OK == esp_lcd_panel_init(_disp_panel_handle)); + } + return true; } @@ -82,7 +90,7 @@ namespace lgfx #else dpi_config.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565; #endif - dpi_config.num_fbs = 1; + dpi_config.num_fbs = 2; dpi_config.video_timing.h_size = _cfg.panel_width; dpi_config.video_timing.v_size = _cfg.panel_height; dpi_config.video_timing.hsync_back_porch = _config_detail.hsync_back_porch; @@ -122,6 +130,14 @@ namespace lgfx auto bus = getBusDSI(); if (bus == nullptr) { return false; } + const size_t reset_delay = getResetDelayBeforeDpi(); + if (reset_delay + && (!bus->writeParams(CMD_SWRESET, nullptr, 0))) { + return false; + } + if (reset_delay) { + vTaskDelay(pdMS_TO_TICKS(reset_delay)); + } if (init_dpi(bus) && init_panel()) { esp_lcd_dpi_panel_get_frame_buffer(_disp_panel_handle, 1, &(_config_detail.buffer)); diff --git a/src/lgfx/v1/platforms/esp32p4/Panel_DSI.hpp b/src/lgfx/v1/platforms/esp32p4/Panel_DSI.hpp index 2316b53f..0c1fc543 100644 --- a/src/lgfx/v1/platforms/esp32p4/Panel_DSI.hpp +++ b/src/lgfx/v1/platforms/esp32p4/Panel_DSI.hpp @@ -74,6 +74,7 @@ namespace lgfx protected: + static constexpr uint8_t CMD_SWRESET = 0x01; static constexpr uint8_t CMD_SLPIN = 0x10; static constexpr uint8_t CMD_SLPOUT = 0x11; static constexpr uint8_t CMD_INVOFF = 0x20; @@ -87,6 +88,8 @@ namespace lgfx virtual const uint8_t* getInitParams(size_t listno) const { return nullptr; } virtual size_t getInitDelay(size_t listno) const { return 0; } + virtual size_t getResetDelayBeforeDpi(void) const { return 0; } + virtual bool initInCommandMode(void) const { return true; } bool write_params(uint32_t cmd, const uint8_t* data = nullptr, size_t length = 0); diff --git a/src/lgfx/v1/platforms/esp32p4/Panel_ST7102.hpp b/src/lgfx/v1/platforms/esp32p4/Panel_ST7102.hpp new file mode 100644 index 00000000..d7884af2 --- /dev/null +++ b/src/lgfx/v1/platforms/esp32p4/Panel_ST7102.hpp @@ -0,0 +1,118 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + + ST7102 MIPI DSI panel parameters for M5Stack CoreP4X. +/----------------------------------------------------------------------------*/ +#pragma once + +#include "Panel_DSI.hpp" +#if SOC_MIPI_DSI_SUPPORTED + +namespace lgfx +{ + inline namespace v1 + { + + struct Panel_ST7102 : public Panel_DSI + { + protected: + + bool initInCommandMode(void) const override { return false; } + size_t getResetDelayBeforeDpi(void) const override { return 120; } + + const uint8_t* getInitParams(size_t listno) const override + { + static constexpr uint8_t list0[] = + {//len(cmd+params), cmd, params + 4, 0x99, 0x71, 0x02, 0xA2, + 4, 0x99, 0x71, 0x02, 0xA3, + 4, 0x99, 0x71, 0x02, 0xA4, + 2, 0x78, 0x21, + 2, 0x79, 0xCF, + 8, 0xB0, 0x22, 0x43, 0x1E, 0x43, 0x2F, 0x57, 0x57, + 3, 0xB7, 0x7D, 0x7D, + 3, 0xBF, 0x7A, 0x7A, + 38, 0xC8, 0x00, 0x00, 0x13, 0x23, 0x3E, 0x00, 0x6A, 0x03, 0xB0, 0x06, + 0x11, 0x0F, 0x07, 0x85, 0x03, 0x21, 0xD5, 0x01, 0x18, 0x00, + 0x22, 0x56, 0x0F, 0x98, 0x0A, 0x32, 0xF8, 0x0D, 0x48, 0x0F, + 0xF3, 0x80, 0x0F, 0xAC, 0xC1, 0x03, 0xC4, + 38, 0xC9, 0x00, 0x00, 0x13, 0x23, 0x3E, 0x00, 0x6A, 0x03, 0xB0, 0x06, + 0x11, 0x0F, 0x07, 0x85, 0x03, 0x21, 0xD5, 0x01, 0x18, 0x00, + 0x22, 0x56, 0x0F, 0x98, 0x0A, 0x32, 0xF8, 0x0D, 0x48, 0x0F, + 0xF3, 0x80, 0x0F, 0xAC, 0xC1, 0x03, 0xC4, + 7, 0xD7, 0x10, 0x0C, 0x02, 0x19, 0x40, 0x40, + 33, 0xA3, 0x40, 0x03, 0x80, 0xCF, 0x44, 0x00, 0x00, 0x00, 0x02, 0x05, + 0x6F, 0x6F, 0x00, 0x1A, 0x00, 0x45, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x46, 0x00, 0x00, 0x02, 0x20, 0x52, 0x00, 0x05, 0x00, + 0x00, 0xFF, + 45, 0xA6, 0x02, 0x00, 0x24, 0x55, 0x35, 0x00, 0x38, 0x00, 0x97, 0x97, + 0x00, 0x24, 0x55, 0x36, 0x00, 0x37, 0x00, 0x97, 0x97, 0x02, + 0xAC, 0x51, 0x3A, 0x00, 0x00, 0x00, 0x97, 0x97, 0x00, 0xAC, + 0x21, 0x00, 0x0B, 0x00, 0x00, 0x97, 0x97, 0x00, 0x00, 0x06, + 0x00, 0x00, 0x00, 0x00, + 49, 0xA7, 0x19, 0x19, 0x00, 0x64, 0x40, 0x07, 0x16, 0x40, 0x00, 0x04, + 0x03, 0x97, 0x97, 0x00, 0x64, 0x40, 0x25, 0x34, 0x00, 0x00, + 0x02, 0x01, 0x97, 0x97, 0x00, 0x64, 0x40, 0x4B, 0x5A, 0x00, + 0x00, 0x02, 0x01, 0x97, 0x97, 0x00, 0x24, 0x40, 0x69, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x97, 0x97, 0x00, 0x44, + 38, 0xAC, 0x11, 0x08, 0x13, 0x0A, 0x18, 0x1A, 0x1B, 0x00, 0x06, 0x03, + 0x19, 0x1B, 0x1B, 0x1B, 0x18, 0x1B, 0x10, 0x09, 0x12, 0x0B, + 0x18, 0x1A, 0x1B, 0x02, 0x06, 0x01, 0x19, 0x1B, 0x1B, 0x1B, + 0x18, 0x1B, 0xFF, 0x67, 0xFF, 0x67, 0x00, + 8, 0xAD, 0xCC, 0x40, 0x46, 0x11, 0x04, 0x6F, 0x6F, + 15, 0xE8, 0x30, 0x07, 0x00, 0xB3, 0xB3, 0x9C, 0x00, 0xE2, 0x04, 0x00, + 0x00, 0x00, 0x00, 0xEF, + 3, 0x75, 0x03, 0x04, + 34, 0xE7, 0x8B, 0x3C, 0x00, 0x0C, 0xF0, 0x5D, 0x00, 0x5D, 0x00, 0x5D, + 0x00, 0x5D, 0x00, 0xFF, 0x00, 0x08, 0x7B, 0x00, 0x00, 0xC8, + 0x6A, 0x5A, 0x08, 0x1A, 0x3C, 0x00, 0x71, 0x01, 0x8C, 0x01, + 0x7F, 0xF0, 0x22, + 10, 0xE9, 0x3C, 0x7F, 0x08, 0x07, 0x1A, 0x7A, 0x22, 0x1A, 0x33, + 0, + }; + + static constexpr uint8_t list1[] = + { + 1, CMD_SLPOUT, + 0, + }; + + static constexpr uint8_t list2[] = + { + 2, CMD_MADCTL, 0b11, + 1, CMD_DISPON, + 0, + }; + + static constexpr uint8_t list3[] = + { + 2, 0x35, 0x00, + 1, CMD_DISPON, + 0, + }; + + switch (listno) + { + case 0: return list0; + case 1: return list1; + case 2: return list2; + case 3: return list3; + default: return nullptr; + } + } + + size_t getInitDelay(size_t listno) const override + { + switch (listno) + { + case 1: return 20; + case 2: return 20; + default: return 0; + } + } + }; + + } +} + +#endif diff --git a/src/lgfx/v1/platforms/soft_i2c.inl b/src/lgfx/v1/platforms/soft_i2c.inl index 8752e205..1281e2f5 100644 --- a/src/lgfx/v1/platforms/soft_i2c.inl +++ b/src/lgfx/v1/platforms/soft_i2c.inl @@ -34,6 +34,14 @@ Contributors: #error "soft_i2c.inl is an implementation fragment of the i2c namespace; it cannot be included directly." #endif +// The negative ports are shared while a board is being brought up: M5GFX board +// autodetection opens -1 and the M5Unified board check opens -2, both from +// inside begin(). Neither slot carries ownership - opening one takes over the +// pins the last user left behind, and a transfer does not check whether the +// slot is still the one it was given - so a sketch that wants a bus of its own +// should open it once begin() is done, and open it again if it runs detection +// a second time. +// // An I2C line is only ever driven low or released, never driven high. The // default implementation releases by turning the pin back into an input and // drives by turning it into an output whose latch was parked low, which works @@ -90,6 +98,10 @@ Contributors: static inline bool soft_i2c_valid_port(int i2c_port) { return -soft_i2c_port_count <= i2c_port && i2c_port < 0; } static inline soft_i2c_context_t& soft_i2c_ctx(int i2c_port) { return soft_i2c_context[~i2c_port]; } + /// Spin limit for the settle waits. A line that never reaches its level is + /// handled by the checks that follow, not by spinning here forever. + static constexpr size_t soft_i2c_settle_guard = 4096; + static inline void soft_i2c_half_wait(uint32_t half_us) { if (half_us) { delayMicroseconds(half_us); } } @@ -101,6 +113,47 @@ Contributors: ctx.half_us = (freq >= 500000) ? 0 : (500000 + freq - 1) / freq; } + /// Drive SCL low and wait until the line actually reads low. + /// The data line may only move once the clock is under its low threshold: + /// a data change while the clock still reads high is a start or a stop to + /// every device on the bus, which ends the transfer instead of carrying a + /// bit. The clock is driven low rather than released, so waiting for it + /// costs the fall time of this bus and nothing more - a fixed hold would + /// instead have to come out of the setup time, which is exactly what the + /// slow rise of a released data line needs at the higher clock rates. + /// @return false when the clock never reached its low level. Reported the + /// same way as a clock that will not rise: carrying on regardless would + /// move the data line while the clock still reads high, which is the very + /// thing this wait exists to prevent. + static inline bool soft_i2c_scl_lo(const soft_i2c_context_t& ctx) + { + SOFT_I2C_LINE_LO(ctx.pin_scl); + size_t guard = 0; + while (gpio_in(ctx.pin_scl)) + { + if (++guard >= soft_i2c_settle_guard) { return false; } + } + return true; + } + + /// Release SDA and wait for the pullup to carry it high. + /// Only for the data bits: it is the rise that is slow, and giving it the + /// time it actually takes keeps the setup time intact where a fixed wait + /// would fall short on a loaded bus. Not for the acknowledge, where the + /// device holds the line low on purpose. + /// @return false when the data line stayed low. Something else is holding + /// it, so the bit about to be clocked out would not be the bit intended. + static inline bool soft_i2c_sda_hi(const soft_i2c_context_t& ctx) + { + SOFT_I2C_LINE_HI(ctx.pin_sda); + size_t guard = 0; + while (!gpio_in(ctx.pin_sda)) + { + if (++guard >= soft_i2c_settle_guard) { return false; } + } + return true; + } + /// Release SCL and wait for it to actually rise, honoring clock stretching. static inline bool soft_i2c_scl_hi(const soft_i2c_context_t& ctx) { @@ -112,29 +165,45 @@ Contributors: { SOFT_I2C_YIELD(); if (gpio_in(ctx.pin_scl)) { return true; } - } while ((micros() - us) < 13000); // the same order as a peripheral timeout + } while ((micros() - us) < 25000); // the same stall allowance as the peripheral ports return false; } - /// Returns true when the byte was acknowledged. + /// Returns true when the byte was acknowledged and the clock could be taken + /// low again afterwards. A clock that will not settle is reported the same + /// way as a missing acknowledge, which ends the transfer either way. static inline bool soft_i2c_write_byte(const soft_i2c_context_t& ctx, uint8_t data) { size_t i = 0; do { - SOFT_I2C_LINE_LO(ctx.pin_scl); - if (data & 0x80) { SOFT_I2C_LINE_HI(ctx.pin_sda); } else { SOFT_I2C_LINE_LO(ctx.pin_sda); } + if (!soft_i2c_scl_lo(ctx)) { return false; } + if (data & 0x80) { if (!soft_i2c_sda_hi(ctx)) { return false; } } + else { SOFT_I2C_LINE_LO(ctx.pin_sda); } data <<= 1; soft_i2c_half_wait(ctx.half_us); if (!soft_i2c_scl_hi(ctx)) { return false; } } while (++i < 8); - SOFT_I2C_LINE_LO(ctx.pin_scl); + if (!soft_i2c_scl_lo(ctx)) { return false; } SOFT_I2C_LINE_HI(ctx.pin_sda); // release the data line for the acknowledge soft_i2c_half_wait(ctx.half_us); if (!soft_i2c_scl_hi(ctx)) { return false; } bool ack = !gpio_in(ctx.pin_sda); - SOFT_I2C_LINE_LO(ctx.pin_scl); - return ack; + if (ack) + { // This master drove the data line low for the bit before the + // acknowledge, so a low here is either a device holding the line or a + // rise that has not finished. A device holds it for the whole pulse, + // while a rise is over within the time the bus is allowed to take for + // one ( 1us for the slowest mode ), so looking again separates them. + // A bus slower than the specification allows is read as an acknowledge + // that is not there; the limit is the specified rise time, not a + // measurement of this bus. + auto us = micros(); + while (!gpio_in(ctx.pin_sda) && (micros() - us) <= 1) {} + ack = !gpio_in(ctx.pin_sda); + } + bool low = soft_i2c_scl_lo(ctx); + return ack && low; } static inline bool soft_i2c_read_byte(const soft_i2c_context_t& ctx, uint8_t* data, bool ack) @@ -144,16 +213,17 @@ Contributors: size_t i = 0; do { - SOFT_I2C_LINE_LO(ctx.pin_scl); + if (!soft_i2c_scl_lo(ctx)) { return false; } soft_i2c_half_wait(ctx.half_us); if (!soft_i2c_scl_hi(ctx)) { return false; } byte = (byte << 1) + (gpio_in(ctx.pin_sda) ? 1 : 0); } while (++i < 8); - SOFT_I2C_LINE_LO(ctx.pin_scl); - if (ack) { SOFT_I2C_LINE_LO(ctx.pin_sda); } else { SOFT_I2C_LINE_HI(ctx.pin_sda); } + if (!soft_i2c_scl_lo(ctx)) { return false; } + if (ack) { SOFT_I2C_LINE_LO(ctx.pin_sda); } + else { if (!soft_i2c_sda_hi(ctx)) { return false; } } soft_i2c_half_wait(ctx.half_us); if (!soft_i2c_scl_hi(ctx)) { return false; } - SOFT_I2C_LINE_LO(ctx.pin_scl); + if (!soft_i2c_scl_lo(ctx)) { return false; } SOFT_I2C_LINE_HI(ctx.pin_sda); *data = byte; return true; @@ -162,12 +232,16 @@ Contributors: /// Returns false when the clock could not be released for the stop condition. static inline bool soft_i2c_stop_cond(const soft_i2c_context_t& ctx) { - SOFT_I2C_LINE_LO(ctx.pin_scl); - SOFT_I2C_LINE_LO(ctx.pin_sda); + bool low = soft_i2c_scl_lo(ctx); + // Taking the data line low while the clock is still high is a start, not + // the beginning of a stop, so it is only done once the clock is down. + if (low) { SOFT_I2C_LINE_LO(ctx.pin_sda); } soft_i2c_half_wait(ctx.half_us); - bool ok = soft_i2c_scl_hi(ctx); + bool ok = soft_i2c_scl_hi(ctx) && low; soft_i2c_half_wait(ctx.half_us); - SOFT_I2C_LINE_HI(ctx.pin_sda); + // The stop is the rise of the data line while the clock is high, so it + // is not made until the line has actually risen. + ok = soft_i2c_sda_hi(ctx) && ok; soft_i2c_half_wait(ctx.half_us); return ok; } @@ -179,8 +253,9 @@ Contributors: SOFT_I2C_LINE_HI(ctx.pin_sda); size_t i = 0; while (!gpio_in(ctx.pin_sda) && ++i <= 9) - { - SOFT_I2C_LINE_LO(ctx.pin_scl); + { // this is the attempt to free a stuck bus: a clock that will not settle + // is the condition being recovered from, so it does not end the loop. + (void)soft_i2c_scl_lo(ctx); soft_i2c_half_wait(ctx.half_us); soft_i2c_scl_hi(ctx); } @@ -210,15 +285,17 @@ Contributors: && soft_i2c_write_byte(ctx, i2c_addr & 0xFF); if (ack && read) { // A 10 bit read re-addresses the high byte in read mode. - SOFT_I2C_LINE_HI(ctx.pin_sda); + // The repeated start needs the data line to be high first, the same + // as the first start does. + ack = soft_i2c_sda_hi(ctx); soft_i2c_half_wait(ctx.half_us); - ack = soft_i2c_scl_hi(ctx); + ack = ack && soft_i2c_scl_hi(ctx); if (ack) { SOFT_I2C_LINE_LO(ctx.pin_sda); soft_i2c_half_wait(ctx.half_us); - SOFT_I2C_LINE_LO(ctx.pin_scl); - ack = soft_i2c_write_byte(ctx, 0xF0 | (i2c_addr >> 8) << 1 | 1); + ack = soft_i2c_scl_lo(ctx) + && soft_i2c_write_byte(ctx, 0xF0 | (i2c_addr >> 8) << 1 | 1); } } } @@ -305,7 +382,9 @@ Contributors: SOFT_I2C_LOCK(ctx); ctx.state = soft_i2c_context_t::state_t::state_disconnect; soft_i2c_set_freq(ctx, freq); - SOFT_I2C_LINE_HI(ctx.pin_sda); + // Wait for the release to take effect before reading the line below: + // a rise still in progress is not a bus that someone else is holding. + (void)soft_i2c_sda_hi(ctx); if (!soft_i2c_scl_hi(ctx)) { // The clock never rose: no start condition can be made on this bus. soft_i2c_abort(ctx); @@ -323,7 +402,11 @@ Contributors: } SOFT_I2C_LINE_LO(ctx.pin_sda); // start condition soft_i2c_half_wait(ctx.half_us); - SOFT_I2C_LINE_LO(ctx.pin_scl); + if (!soft_i2c_scl_lo(ctx)) + { + soft_i2c_abort(ctx); + return {}; + } return soft_i2c_send_address(ctx, i2c_addr, read); } @@ -338,7 +421,15 @@ Contributors: return cpp::fail(error_t::mode_mismatch); } soft_i2c_set_freq(ctx, freq); - SOFT_I2C_LINE_HI(ctx.pin_sda); // repeated start + // Unlike the first start there is no independent recheck below, so the + // release is judged here: without the data line going high first, the + // falling edge that makes the repeated start never appears on the bus + // and the address that follows is sent into a frame no one opened. + if (!soft_i2c_sda_hi(ctx)) + { + soft_i2c_abort(ctx); + return {}; + } soft_i2c_half_wait(ctx.half_us); if (!soft_i2c_scl_hi(ctx)) { @@ -347,7 +438,11 @@ Contributors: } SOFT_I2C_LINE_LO(ctx.pin_sda); soft_i2c_half_wait(ctx.half_us); - SOFT_I2C_LINE_LO(ctx.pin_scl); + if (!soft_i2c_scl_lo(ctx)) + { + soft_i2c_abort(ctx); + return {}; + } return soft_i2c_send_address(ctx, i2c_addr, read); } diff --git a/src/lgfx/v1/touch/Touch_CSTxxx.cpp b/src/lgfx/v1/touch/Touch_CSTxxx.cpp index ea8ca19c..e1e0a4c2 100644 --- a/src/lgfx/v1/touch/Touch_CSTxxx.cpp +++ b/src/lgfx/v1/touch/Touch_CSTxxx.cpp @@ -27,6 +27,102 @@ namespace lgfx { inline namespace v1 { +//---------------------------------------------------------------------------- + + bool Touch_CST3530::_write_reg32(uint32_t reg) + { + uint8_t data[4] = + { static_cast(reg >> 24) + , static_cast(reg >> 16) + , static_cast(reg >> 8) + , static_cast(reg) + }; + return i2c::transactionWrite(_cfg.i2c_port, _cfg.i2c_addr, data, sizeof(data), _cfg.freq).has_value(); + } + + bool Touch_CST3530::_read_reg32(uint32_t reg, uint8_t* data, size_t length) + { + uint8_t address[4] = + { static_cast(reg >> 24) + , static_cast(reg >> 16) + , static_cast(reg >> 8) + , static_cast(reg) + }; + return i2c::transactionWriteRead(_cfg.i2c_port, _cfg.i2c_addr, address, sizeof(address), data, length, _cfg.freq).has_value(); + } + + bool Touch_CST3530::_check_init(void) + { + if (_inited) return true; + + bool ok = _write_reg32(0xD0000400); + lgfx::delay(20); + ok = _write_reg32(0xD0000400) && ok; + lgfx::delay(20); + ok = _write_reg32(0xD0000000) && ok; + ok = _write_reg32(0xD0000C00) && ok; + ok = _write_reg32(0xD0000100) && ok; + _inited = ok; + return _inited; + } + + bool Touch_CST3530::init(void) + { + _inited = false; + if (_cfg.pin_int >= 0) { + lgfx::pinMode(_cfg.pin_int, pin_mode_t::input); + } + i2c::init(_cfg.i2c_port, _cfg.pin_sda, _cfg.pin_scl).has_value(); + return true; + } + + void Touch_CST3530::wakeup(void) + { + _inited = false; + _check_init(); + } + + void Touch_CST3530::sleep(void) + { + } + + uint_fast8_t Touch_CST3530::getTouchRaw(touch_point_t* tp, uint_fast8_t count) + { + if (count == 0 || !_check_init()) return 0; + + uint8_t data[50] = { 0 }; + if (!_read_reg32(0xD0070000, data, 9)) return 0; + + uint8_t finger_num = data[3] & 0x0F; + uint8_t key_num = (data[3] >> 4) & 0x0F; + uint8_t total_num = key_num + finger_num; + if (total_num > 1) { + size_t extra_length = (total_num - 1) * 5; + if (extra_length > sizeof(data) - 9) extra_length = sizeof(data) - 9; + if (!i2c::transactionRead(_cfg.i2c_port, _cfg.i2c_addr, &data[9], extra_length, _cfg.freq).has_value()) { + _write_reg32(0xD00002AB); + return 0; + } + } + _write_reg32(0xD00002AB); + + if (finger_num == 0 || (data[8] >> 4) == 0) return 0; + + uint_fast8_t points = finger_num; + if (points > max_touch_points) points = max_touch_points; + if (points > count) points = count; + for (uint_fast8_t i = 0; i < points; ++i) { + uint16_t index = (key_num + i) * 5; + uint16_t x = data[index + 4] | ((uint16_t)(data[index + 7] & 0x0F) << 8); + uint16_t y = data[index + 5] | ((uint16_t)(data[index + 7] & 0xF0) << 4); + tp[i].id = i; + tp[i].size = 1; + tp[i].x = x; + tp[i].y = y; + } + return points; + } + //---------------------------------------------------------------------------- static constexpr uint8_t CST816S_TOUCH_REG = 0x01; diff --git a/src/lgfx/v1/touch/Touch_CSTxxx.hpp b/src/lgfx/v1/touch/Touch_CSTxxx.hpp index 15b78d6c..9390c2d4 100644 --- a/src/lgfx/v1/touch/Touch_CSTxxx.hpp +++ b/src/lgfx/v1/touch/Touch_CSTxxx.hpp @@ -28,6 +28,34 @@ namespace lgfx struct Touch_CST226; // CST226/CST226SE struct Touch_CST816S; + struct Touch_CST3530; + + +//---------------------------------------------------------------------------- + + struct Touch_CST3530 : public ITouch + { + Touch_CST3530(void) + { + _cfg.i2c_addr = 0x58; + _cfg.x_min = 0; + _cfg.x_max = 479; + _cfg.y_min = 0; + _cfg.y_max = 479; + } + + bool init(void) override; + void wakeup(void) override; + void sleep(void) override; + uint_fast8_t getTouchRaw(touch_point_t* tp, uint_fast8_t count) override; + + private: + static constexpr uint_fast8_t max_touch_points = 5; + + bool _check_init(void); + bool _write_reg32(uint32_t reg); + bool _read_reg32(uint32_t reg, uint8_t* data, size_t length); + }; //---------------------------------------------------------------------------- diff --git a/src/picture_frame/picture_frame.h b/src/picture_frame/picture_frame.h index f09400b9..27f34ba5 100644 --- a/src/picture_frame/picture_frame.h +++ b/src/picture_frame/picture_frame.h @@ -42,7 +42,8 @@ namespace m5gfx case board_M5StickCPlus: return &picture_frame_M5StickCPlus; case board_M5StickCPlus2: return &picture_frame_M5StickCPlus2; case board_M5Dial: return &picture_frame_M5Dial; - case board_M5Tab5: return &picture_frame_M5Tab5; + case board_M5Tab5: + case board_M5Tab5X: return &picture_frame_M5Tab5; default: return nullptr; } }