diff --git a/.github/workflows/ArduinoBuild.yml b/.github/workflows/ArduinoBuild.yml index 6eb8f73f..81c6d0e6 100644 --- a/.github/workflows/ArduinoBuild.yml +++ b/.github/workflows/ArduinoBuild.yml @@ -12,6 +12,11 @@ on: pull_request: workflow_dispatch: +# supersede queued/running builds of the same branch or PR +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build: name: ${{ matrix.board }} (${{ matrix.esp32_version }}) @@ -44,6 +49,10 @@ jobs: - board: esp32:esp32:esp32c3 esp32_version: 3.1.1 board_manager_url: https://espressif.github.io/arduino-esp32/package_esp32_index.json + # ESP32-C5 (v3.3+ only) + - board: esp32:esp32:esp32c5 + esp32_version: 3.3.11 + board_manager_url: https://espressif.github.io/arduino-esp32/package_esp32_index.json # ESP32-C6 (v3 only) - board: esp32:esp32:esp32c6 esp32_version: 3.1.1 diff --git a/.github/workflows/IDFBuild.yml b/.github/workflows/IDFBuild.yml index 7e7101c0..110786c9 100644 --- a/.github/workflows/IDFBuild.yml +++ b/.github/workflows/IDFBuild.yml @@ -12,6 +12,11 @@ on: pull_request: workflow_dispatch: +# supersede queued/running builds of the same branch or PR +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build: name: ${{ matrix.target }} (IDF ${{ matrix.idf_version }}) @@ -27,6 +32,8 @@ jobs: idf_version: "5.3" - target: esp32 idf_version: "5.1.6" + - target: esp32 + idf_version: "6.0.2" # ESP32-S3 - target: esp32s3 idf_version: "5.3" @@ -37,9 +44,14 @@ jobs: idf_version: "5.3" - target: esp32c3 idf_version: "5.1.6" + # ESP32-C5 (IDF 5.5+ only) + - target: esp32c5 + idf_version: "5.5.2" # ESP32-C6 (IDF 5.3+ only) - target: esp32c6 idf_version: "5.3" + - target: esp32c6 + idf_version: "5.5.2" # ESP32-H2 - target: esp32h2 idf_version: "5.3" diff --git a/examples/PlatformIO_SDL/src/sdl_main.cpp b/examples/PlatformIO_SDL/src/sdl_main.cpp index 87abf61b..7137e416 100644 --- a/examples/PlatformIO_SDL/src/sdl_main.cpp +++ b/examples/PlatformIO_SDL/src/sdl_main.cpp @@ -17,6 +17,9 @@ int user_func(bool* running) int main(int, char**) { +#if defined(__APPLE__) + SDL_SetHint(SDL_HINT_MAC_BACKGROUND_APP, "0"); +#endif // The second argument is effective for step execution with breakpoints. // You can specify the time in milliseconds to perform slow execution that ensures screen updates. return lgfx::Panel_sdl::main(user_func, 128); diff --git a/idf_component.yml b/idf_component.yml index f5f821dc..61c53d38 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.26 +version: 0.2.27 diff --git a/library.json b/library.json index caa49f33..b6821377 100644 --- a/library.json +++ b/library.json @@ -10,7 +10,7 @@ "type": "git", "url": "https://github.com/m5stack/M5GFX.git" }, - "version": "0.2.26", + "version": "0.2.27", "frameworks": ["arduino", "espidf", "*"], "platforms": ["espressif32", "native"], "headers": "M5GFX.h" diff --git a/library.properties b/library.properties index 14e359b9..70d7309e 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=M5GFX -version=0.2.26 +version=0.2.27 author=M5Stack maintainer=M5Stack sentence=Library for M5Stack All Display diff --git a/src/M5AtomDisplay.h b/src/M5AtomDisplay.h index 62f003a1..7c311040 100644 --- a/src/M5AtomDisplay.h +++ b/src/M5AtomDisplay.h @@ -138,7 +138,7 @@ class M5AtomDisplay : public M5GFX int spi_sclk = GPIO_NUM_7; #elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - #define M5GFX_SPI_HOST VSPI_HOST + #define M5GFX_SPI_HOST SPI3_HOST int i2c_port = 1; int i2c_sda = GPIO_NUM_25; diff --git a/src/M5GFX.cpp b/src/M5GFX.cpp index 5e8b4fba..f6f3bac8 100644 --- a/src/M5GFX.cpp +++ b/src/M5GFX.cpp @@ -19,6 +19,7 @@ #include "lgfx/v1/panel/Panel_ILI9342.hpp" #include "lgfx/v1/panel/Panel_SSD1306.hpp" #include "lgfx/v1/panel/Panel_SSD1677.hpp" +#include "lgfx/v1/panel/Panel_TM1680.hpp" #include "lgfx/v1/panel/Panel_ST7735.hpp" #include "lgfx/v1/panel/Panel_ST7789.hpp" #include "lgfx/v1/panel/Panel_GC9A01.hpp" @@ -50,6 +51,11 @@ static constexpr int_fast16_t in_i2c_port = I2C_NUM_1; #include #endif + +#elif defined ( CONFIG_IDF_TARGET_ESP32C61 ) + +#include "lgfx/v1/platforms/esp32/Bus_I2C.hpp" + #endif #else @@ -82,6 +88,13 @@ namespace m5gfx } } + // ボード未確定段階の I2C プローブに使うソフトウェア I2C ポート (GPIO ビットバン)。 + // ハードウェアのペリフェラルを一切確保・設定しないため、候補ボードの試行が + // ペリフェラルやピンの状態を汚さない。ボード確定後の常用バスは従来どおり + // ハードウェアポートを使う。 + __attribute__ ((unused)) + static constexpr int_fast16_t probe_i2c_port = -1; + // I2Cデバイスの存在をチェックする。 // SDA,SCLのプルアップが確認できない場合は0を返す。 // プルアップが確認できた場合は ~0u を返すが、存在しないデバイスに対応するビットは 0 となる。 @@ -134,31 +147,17 @@ namespace m5gfx // 全ビットを立てる result = ~0u; - // for (int addr7bit = 0x08; addr7bit < 0x78; ++addr7bit) { + // アドレスの存在確認はソフトウェア I2C ポートで行う (オープンドレイン駆動で + // ACK 競合が起きず、ハードウェアのペリフェラルにも触れない) + lgfx::i2c::init(probe_i2c_port, pin_sda, pin_scl); for (; addr_list[0] != 0; ++addr_list) { - // 7bit addr + write flag + ack check; uint_fast8_t addr7bit = addr_list[0]; - // ACKチェック用ビットも含め左2bitシフトし、9bit相当にする - uint_fast16_t sda_bits = (addr7bit << 2u) + 1u; - lgfx::delayMicroseconds(5); - lgfx::gpio_lo(pin_sda); - lgfx::delayMicroseconds(5); - for (int i = 0; i < 9; ++i) { - lgfx::gpio_lo(pin_scl); - if (sda_bits & 0x100u) { lgfx::gpio_hi(pin_sda); } - else { lgfx::gpio_lo(pin_sda); } - sda_bits <<= 1; - lgfx::delayMicroseconds(3); - lgfx::gpio_hi(pin_scl); - lgfx::delayMicroseconds(6); - } - bool hit = !lgfx::gpio_in(pin_sda); + bool hit = lgfx::i2c::beginTransaction(probe_i2c_port, addr7bit, 100000, false).has_value() + && lgfx::i2c::endTransaction(probe_i2c_port).has_value(); result = (result << 1) + hit; ESP_LOGV(LIBRARY_NAME, "[Autodetect] i2c addr:%02x = %s", (int)addr7bit, hit ? "hit" : "--"); - - // i2c stop - lgfx::gpio::command(cmd_i2c_stop_list); } + lgfx::i2c::release(probe_i2c_port); } else { result = 0; } @@ -255,6 +254,71 @@ namespace m5gfx _rotation = 1; // default rotation } + void initPanelByTouchVersion() + { + static constexpr uint8_t touch_addr = 0x38; + static constexpr uint8_t cipher_reg = 0xA3; + static constexpr uint8_t firmid_reg = 0xA6; + static constexpr uint8_t vendid_reg = 0xA8; + static constexpr uint8_t ili9342c_firmid = 0x10; + static constexpr uint8_t ili9342e_firmid = 0x12; + static constexpr uint8_t m5stack_vendor = 0x11; + static constexpr int32_t version_i2c_freq = 100000; + + uint8_t touch_cipher = 0; + uint8_t touch_firmid = 0; + uint8_t touch_vendid = 0; + bool touch_info_valid = false; + for (int retry = 0; retry < 5 && !touch_info_valid; ++retry) + { + auto set_work_mode = lgfx::i2c::writeRegister8( + axp_i2c_port, touch_addr, 0x00, 0x00, 0, version_i2c_freq); + auto read_cipher = lgfx::i2c::readRegister8( + axp_i2c_port, touch_addr, cipher_reg, version_i2c_freq); + auto read_firmid = lgfx::i2c::readRegister8( + axp_i2c_port, touch_addr, firmid_reg, version_i2c_freq); + auto read_vendid = lgfx::i2c::readRegister8( + axp_i2c_port, touch_addr, vendid_reg, version_i2c_freq); + + touch_cipher = read_cipher.has_value() ? read_cipher.value() : 0; + touch_firmid = read_firmid.has_value() ? read_firmid.value() : 0; + touch_vendid = read_vendid.has_value() ? read_vendid.value() : 0; + touch_info_valid = set_work_mode.has_value() + && read_firmid.has_value() + && read_vendid.has_value() + && touch_vendid == m5stack_vendor + && (touch_firmid == ili9342c_firmid + || touch_firmid == ili9342e_firmid); + if (!touch_info_valid) + { + lgfx::delay(20); + } + } + + bool use_ili9342e = touch_info_valid + && touch_firmid == ili9342e_firmid; + if (touch_info_valid) + { + ESP_LOGI(LIBRARY_NAME, + "Core2 touch CIPHER:0x%02x / FIRMID:0x%02x / VENDID:0x%02x, panel:%s", + (int)touch_cipher, (int)touch_firmid, (int)touch_vendid, + use_ili9342e ? "ILI9342E" : "ILI9342C"); + } + else + { + ESP_LOGW(LIBRARY_NAME, + "Core2 touch version read failed (CIPHER:0x%02x / FIRMID:0x%02x / VENDID:0x%02x), panel:ILI9342C", + (int)touch_cipher, (int)touch_firmid, (int)touch_vendid); + } + + if (use_ili9342e) + { + startWrite(true); + command_list(getIli9342EInitCommands()); + endWrite(); + } + } + void rst_control(bool level) override { uint8_t bits = level ? 2 : 0; @@ -262,6 +326,31 @@ namespace m5gfx // AXP192 reg 0x96 = GPIO3&4 control lgfx::i2c::writeRegister8(axp_i2c_port, axp_i2c_addr, 0x96, bits, mask, axp_i2c_freq); } + + protected: + static const uint8_t* getIli9342EInitCommands() + { + static constexpr uint8_t list0[] = + { + 0xDD, 1, 0x01, + 0x3A, 1, 0x55, + 0x21, 0, + 0x36, 1, 0x08, + 0xD5, 1, 0x00, + 0xB1, 1, 0x22, + 0xC8, 1, 0x38, + 0xCB, 1, 0x1C, + 0xC9, 1, 0x1A, + 0xCA, 1, 0x1A, + 0xB7, 4, 0x5A,0x41,0x11,0x19, + 0xE4,15, 0x04,0x08,0x11,0x06,0x12,0x07,0x3A,0x76,0x47,0x07,0x0F,0x0A,0x11,0x19,0x05, + 0xE5,15, 0x02,0x03,0x07,0x06,0x12,0x07,0x36,0x5F,0x48,0x06,0x10,0x0C,0x16,0x14,0x09, + 0x11, 0 + CMD_INIT_DELAY, 120, + 0x29, 0 + CMD_INIT_DELAY, 120, + 0xFF,0xFF, + }; + return list0; + } }; struct Light_M5StackCore2 : public lgfx::ILight @@ -413,6 +502,14 @@ namespace m5gfx static constexpr int_fast16_t aw9523_i2c_addr = 0x58; // AW9523B static constexpr int_fast16_t axp_i2c_addr = 0x34; // AXP2101 static constexpr int_fast16_t gc0308_i2c_addr = 0x21; // GC0308 + static constexpr int_fast16_t ft5x06_i2c_addr = 0x38; + static constexpr uint8_t ft5x06_cipher_reg = 0xA3; + static constexpr uint8_t ft5x06_firmid_reg = 0xA6; + static constexpr uint8_t ft5x06_vendid_reg = 0xA8; + static constexpr uint8_t ft5x06_ili9342c_firmid = 0x10; + static constexpr uint8_t ft5x06_ili9342e_firmid = 0x12; + static constexpr uint8_t ft5x06_m5stack_vendor = 0x11; + static constexpr int32_t ft5x06_version_i2c_freq = 100000; static constexpr int_fast16_t i2c_port = I2C_NUM_1; static constexpr int_fast16_t i2c_sda = GPIO_NUM_12; static constexpr int_fast16_t i2c_scl = GPIO_NUM_11; @@ -430,6 +527,61 @@ namespace m5gfx _rotation = 1; // default rotation } + void initPanelByTouchVersion() + { + uint8_t touch_cipher = 0; + uint8_t touch_firmid = 0; + uint8_t touch_vendid = 0; + bool touch_info_valid = false; + for (int retry = 0; retry < 5 && !touch_info_valid; ++retry) + { + auto set_work_mode = lgfx::i2c::writeRegister8( + i2c_port, ft5x06_i2c_addr, 0x00, 0x00, 0, ft5x06_version_i2c_freq); + auto read_cipher = lgfx::i2c::readRegister8( + i2c_port, ft5x06_i2c_addr, ft5x06_cipher_reg, ft5x06_version_i2c_freq); + auto read_firmid = lgfx::i2c::readRegister8( + i2c_port, ft5x06_i2c_addr, ft5x06_firmid_reg, ft5x06_version_i2c_freq); + auto read_vendid = lgfx::i2c::readRegister8( + i2c_port, ft5x06_i2c_addr, ft5x06_vendid_reg, ft5x06_version_i2c_freq); + touch_cipher = read_cipher.has_value() ? read_cipher.value() : 0; + touch_firmid = read_firmid.has_value() ? read_firmid.value() : 0; + touch_vendid = read_vendid.has_value() ? read_vendid.value() : 0; + touch_info_valid = set_work_mode.has_value() + && read_firmid.has_value() + && read_vendid.has_value() + && touch_vendid == ft5x06_m5stack_vendor + && (touch_firmid == ft5x06_ili9342c_firmid + || touch_firmid == ft5x06_ili9342e_firmid); + if (!touch_info_valid) + { + lgfx::delay(20); + } + } + + bool use_ili9342e = touch_info_valid + && touch_firmid == ft5x06_ili9342e_firmid; + if (touch_info_valid) + { + ESP_LOGI(LIBRARY_NAME, + "CoreS3 touch CIPHER:0x%02x / FIRMID:0x%02x / VENDID:0x%02x, panel:%s", + (int)touch_cipher, (int)touch_firmid, (int)touch_vendid, + use_ili9342e ? "ILI9342E" : "ILI9342C"); + } + else + { + ESP_LOGW(LIBRARY_NAME, + "CoreS3 touch version read failed (CIPHER:0x%02x / FIRMID:0x%02x / VENDID:0x%02x), panel:ILI9342C", + (int)touch_cipher, (int)touch_firmid, (int)touch_vendid); + } + + if (use_ili9342e) + { + startWrite(true); + command_list(getIli9342EInitCommands()); + endWrite(); + } + } + void rst_control(bool level) override { static constexpr uint8_t lcd_rst_bit = 1 << 1; // AW9523B P1_1 @@ -454,6 +606,31 @@ namespace m5gfx : GPIO_ENABLE1_W1TS_REG ) = 1u << (GPIO_NUM_35 & 31); } + + protected: + static const uint8_t* getIli9342EInitCommands() + { + static constexpr uint8_t list0[] = + { + 0xDD, 1, 0x01, + 0x3A, 1, 0x55, + 0x21, 0, + 0x36, 1, 0x08, + 0xD5, 1, 0x00, + 0xB1, 1, 0x22, + 0xC8, 1, 0x38, + 0xCB, 1, 0x1C, + 0xC9, 1, 0x1A, + 0xCA, 1, 0x1A, + 0xB7, 4, 0x5A,0x41,0x11,0x19, + 0xE4,15, 0x04,0x08,0x11,0x06,0x12,0x07,0x3A,0x76,0x47,0x07,0x0F,0x0A,0x11,0x19,0x05, + 0xE5,15, 0x02,0x03,0x07,0x06,0x12,0x07,0x36,0x5F,0x48,0x06,0x10,0x0C,0x16,0x14,0x09, + 0x11, 0 + CMD_INIT_DELAY, 120, + 0x29, 0 + CMD_INIT_DELAY, 120, + 0xFF,0xFF, + }; + return list0; + } }; struct Touch_M5StackCoreS3 : public lgfx::Touch_FT5x06 @@ -672,31 +849,55 @@ namespace m5gfx #elif defined (CONFIG_IDF_TARGET_ESP32C5) static constexpr int32_t i2c_freq = 400000; + // 内部 I2C (G2/G3) は LP_I2C の固定パッドと一致するため LP ポートへ割り当てる。 + // PortA も同じバスの物理分配のため、これで HP の I2C0 が丸ごと空く。 + // (ポート番号は HP ポート数の次 = C5 では 1。Arduino ビルドでも LP は + // TwoWire を介さず ESP-IDF ドライバで直接駆動される) + // 条件は common.cpp が LP 対応をコンパイルする条件と同一に保つこと + // (条件を満たさない SDK では LP ポートを開けないため HP へフォールバックする) +#if defined ( SOC_LP_I2C_NUM ) && ( SOC_LP_I2C_NUM > 0 ) && __has_include ( ) \ + && defined ( ESP_IDF_VERSION_VAL ) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) + static constexpr int_fast16_t i2c_port = LP_I2C_NUM_0; +#else static constexpr int_fast16_t i2c_port = I2C_NUM_0; +#endif struct Light_M5ToughC5 : public lgfx::ILight { + // LCD backlight = M5IOE1 PIN10, driven by the expander's PWM channel 4. + // Registers: 0x21/0x22 = PWM4 duty (12bit, H[7]=enable), 0x25/0x26 = shared PWM frequency (Hz). bool init(uint8_t brightness) override { + static constexpr uint8_t freq_1khz[] = { 0x25, 0xE8, 0x03 }; + lgfx::i2c::transactionWrite(i2c_port, m5ioe1_i2c_addr, freq_1khz, sizeof(freq_1khz), m5ioe1_i2c_freq); + lgfx::i2c::bitOn(i2c_port, m5ioe1_i2c_addr, 0x04, 0x02, m5ioe1_i2c_freq); // PIN10 output mode setBrightness(brightness); return true; } + void writeDuty(uint_fast16_t duty12) + { + uint8_t buf[] = { 0x21, (uint8_t)duty12, (uint8_t)(0x80 | (duty12 >> 8)) }; + lgfx::i2c::transactionWrite(i2c_port, m5ioe1_i2c_addr, buf, sizeof(buf), m5ioe1_i2c_freq); + } + void setBrightness(uint8_t brightness) override { - // M5IOE1 PIN10 (bit1 in high register 0x06) controls LCD backlight - static constexpr uint8_t IOE1_PIN_10 = 9; - static constexpr uint8_t IOE1_BIT_10_H = (1u << (IOE1_PIN_10 - 8)); - if (brightness) { - lgfx::i2c::bitOn(i2c_port, m5ioe1_i2c_addr, 0x06, IOE1_BIT_10_H, m5ioe1_i2c_freq); - } else { - lgfx::i2c::bitOff(i2c_port, m5ioe1_i2c_addr, 0x06, IOE1_BIT_10_H, m5ioe1_i2c_freq); - } + // gamma 2.0: perceived brightness tracks the setting instead of the raw duty + uint_fast16_t duty12 = ((uint32_t)brightness * brightness * 4095u + 32512u) / 65025u; + if (brightness && duty12 == 0) { duty12 = 1; } + writeDuty(duty12); } }; // Touch_M5ToughC5 は lgfx::Touch_CHSC6540 に統合済み +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + + static constexpr int32_t i2c_freq = 400000; + static constexpr int_fast16_t i2c_port = I2C_NUM_0; + static constexpr std::uint8_t tm1680_i2c_addr = 0x72; // CoreMatrix LED matrix driver + #elif defined (CONFIG_IDF_TARGET_ESP32C6) static constexpr int32_t i2c_freq = 400000; @@ -906,11 +1107,26 @@ namespace m5gfx return false; } +#if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + if (board == board_t::board_M5StackCore2) + { + static_cast(_panel_last.get()) + ->initPanelByTouchVersion(); + } +#endif + #if defined (CONFIG_IDF_TARGET_ESP32S3) switch (board) { default: break; + case board_t::board_M5StackCoreS3: + case board_t::board_M5StackCoreS3SE: + case board_t::board_M5StackChan: + static_cast(_panel_last.get()) + ->initPanelByTouchVersion(); + break; + case board_t::board_M5StopWatch: auto p = reinterpret_cast(_panel_last.get()); if (p->initPanelFb() ) { @@ -944,7 +1160,7 @@ namespace m5gfx #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - bus_cfg.spi_host = VSPI_HOST; + bus_cfg.spi_host = SPI3_HOST; bus_cfg.dma_channel = 1; std::uint32_t id; @@ -971,7 +1187,7 @@ namespace m5gfx board = board_t::board_M5StickCPlus; ESP_LOGI(LIBRARY_NAME, "[Autodetect] M5StickCPlus"); bus_spi->release(); - bus_cfg.spi_host = HSPI_HOST; + bus_cfg.spi_host = SPI2_HOST; bus_cfg.freq_write = 40000000; bus_cfg.freq_read = 15000000; bus_spi->config(bus_cfg); @@ -986,7 +1202,7 @@ namespace m5gfx board = board_t::board_M5StickC; ESP_LOGI(LIBRARY_NAME, "[Autodetect] M5StickC"); bus_spi->release(); - bus_cfg.spi_host = HSPI_HOST; + bus_cfg.spi_host = SPI2_HOST; bus_cfg.freq_write = 27000000; bus_cfg.freq_read = 14000000; bus_spi->config(bus_cfg); @@ -1071,7 +1287,7 @@ namespace m5gfx board = board_t::board_M5StickCPlus2; ESP_LOGI(LIBRARY_NAME, "[Autodetect] M5StickCPlus2"); bus_spi->release(); - bus_cfg.spi_host = HSPI_HOST; + bus_cfg.spi_host = SPI2_HOST; bus_cfg.freq_write = 40000000; bus_cfg.freq_read = 15000000; bus_spi->config(bus_cfg); @@ -1141,7 +1357,7 @@ namespace m5gfx board = board_t::board_M5Station; bus_spi->release(); - bus_cfg.spi_host = HSPI_HOST; + bus_cfg.spi_host = SPI2_HOST; bus_cfg.freq_write = 40000000; bus_cfg.freq_read = 15000000; bus_spi->config(bus_cfg); @@ -1672,11 +1888,14 @@ namespace m5gfx board = board_t::board_M5StopWatch; ESP_LOGI(LIBRARY_NAME, "[Autodetect] board_M5StopWatch"); -#if !(defined(CONFIG_ESP32S3_SPIRAM_SUPPORT)) - ESP_LOGE(LIBRARY_NAME, "M5StopWatch need OPI-PSRAM enabled"); -#elif !defined (CONFIG_SPIRAM_MODE_OCT) - ESP_LOGE(LIBRARY_NAME, "M5StopWatch need OPI-PSRAM enabled"); -#else + // Panel_CO5300 supports direct drawing; the optional PSRAM frame + // buffer (initPanelFb) may fail to allocate and is not required, + // so no PSRAM requirement applies here (unlike the EPD boards). +#if !(defined(CONFIG_ESP32S3_SPIRAM_SUPPORT)) || !defined (CONFIG_SPIRAM_MODE_OCT) + // Without the frame buffer, drawing whose origin is at an odd + // coordinate may render incorrectly (this affects e.g. text glyphs). + ESP_LOGW(LIBRARY_NAME, "M5StopWatch: OPI-PSRAM is disabled; the display falls back to direct drawing, which may render incorrectly when the drawing origin is at an odd coordinate. Enable OPI-PSRAM for correct rendering."); +#endif // GPIO39:OLED CS Pin lgfx::pinMode(GPIO_NUM_39, lgfx::pin_mode_t::output); @@ -1767,7 +1986,6 @@ namespace m5gfx } goto init_clear; -#endif } if (is_papermono) { @@ -1776,8 +1994,14 @@ namespace m5gfx #if !(defined(CONFIG_ESP32S3_SPIRAM_SUPPORT)) ESP_LOGE(LIBRARY_NAME, "M5PaperMono need OPI-PSRAM enabled"); + _panel_last.reset(); + _touch_last.reset(); + goto init_clear; // keep the board identification; the display stays unavailable #elif !defined (CONFIG_SPIRAM_MODE_OCT) ESP_LOGE(LIBRARY_NAME, "M5PaperMono need OPI-PSRAM enabled"); + _panel_last.reset(); + _touch_last.reset(); + goto init_clear; // keep the board identification; the display stays unavailable #else // GPIO16:EINK CS Pin @@ -1886,8 +2110,14 @@ namespace m5gfx #if !(defined(CONFIG_ESP32S3_SPIRAM_SUPPORT)) ESP_LOGE(LIBRARY_NAME, "M5ChainCaptain needs OPI-PSRAM enabled"); + _panel_last.reset(); + _touch_last.reset(); + goto init_clear; // keep the board identification; the display stays unavailable #elif !defined (CONFIG_SPIRAM_MODE_OCT) ESP_LOGE(LIBRARY_NAME, "M5ChainCaptain needs OPI-PSRAM enabled"); + _panel_last.reset(); + _touch_last.reset(); + goto init_clear; // keep the board identification; the display stays unavailable #else // M5PM1 and M5IOE1 may retain their idle-sleep settings across battery-powered shutdown. lgfx::i2c::writeRegister8(i2c_port, m5pm1_i2c_addr, 0x09, 0x00, 0, m5pm1_i2c_freq); @@ -1969,8 +2199,14 @@ namespace m5gfx #if !(defined(CONFIG_ESP32S3_SPIRAM_SUPPORT)) ESP_LOGE(LIBRARY_NAME, "M5PaperColor need OPI-PSRAM enabled"); + _panel_last.reset(); + _touch_last.reset(); + goto init_clear; // keep the board identification; the display stays unavailable #elif !defined (CONFIG_SPIRAM_MODE_OCT) ESP_LOGE(LIBRARY_NAME, "M5PaperColor need OPI-PSRAM enabled"); + _panel_last.reset(); + _touch_last.reset(); + goto init_clear; // keep the board identification; the display stays unavailable #else // Disable watchdog (WDT_CNT=0 disables) lgfx::i2c::writeRegister8(i2c_port, m5pm1_i2c_addr, 0x0A, 0x00, 0x00, m5pm1_i2c_freq); @@ -2095,8 +2331,14 @@ namespace m5gfx #if !(defined(CONFIG_ESP32S3_SPIRAM_SUPPORT)) ESP_LOGE(LIBRARY_NAME, "%s need OPI-PSRAM enabled", board == board_t::board_M5PaperDIY ? "M5PaperDIY" : "M5PaperS3"); + _panel_last.reset(); + _touch_last.reset(); + goto init_clear; // keep the board identification; the display stays unavailable #elif !defined (CONFIG_SPIRAM_MODE_OCT) ESP_LOGE(LIBRARY_NAME, "%s need OPI-PSRAM enabled", board == board_t::board_M5PaperDIY ? "M5PaperDIY" : "M5PaperS3"); + _panel_last.reset(); + _touch_last.reset(); + goto init_clear; // keep the board identification; the display stays unavailable #else auto bus_epd = new Bus_EPD(); _bus_last.reset(bus_epd); @@ -2337,10 +2579,12 @@ The usage of each pin is as follows. for (auto &bup : backup_pins2) { bup.restore(); } if ((result & 3) == 3) { m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_5, GPIO_NUM_6); - result = (m5gfx::i2c::transactionWrite(1, 0x40, nullptr, 0).has_value() - && m5gfx::i2c::transactionWrite(1, 0x41, nullptr, 0).has_value()); + // Keep the probe result out of `result`; the CardputerADV check below + // still needs the G8/G9 bits of the GPIO read. + bool is_vameter = (m5gfx::i2c::transactionWrite(1, 0x40, nullptr, 0).has_value() + && m5gfx::i2c::transactionWrite(1, 0x41, nullptr, 0).has_value()); backup_i2c_setting.restore(); - if (result) { + if (is_vameter) { board = board_t::board_M5VAMeter; } } @@ -2516,6 +2760,24 @@ The usage of each pin is as follows. bool is_st7735 = ((id & 0xFFFF) == 0x7683 || (id & 0xFFFF) == 0x897C); bool is_gc9107 = (id & 0xFFFFFF) == 0x079100; // ESP_LOGI(LIBRARY_NAME, "[Autodetect] panel_id: 0x%08x", id); + if (!is_st7735 && !is_gc9107) + { // Some GC9107 batches return a valid ID only at low clock rates; + // re-probe slowly before giving up. + // (ST7735 does not answer at this rate, but a healthy one has + // already been caught by the first probe above.) + bus_spi->release(); + bus_cfg.freq_write = 100000; + bus_cfg.freq_read = 100000; + bus_spi->config(bus_cfg); + bus_spi->init(); + id = _read_panel_id(bus_spi, GPIO_NUM_14); + // restore the probe speed in bus_cfg: later board probes reuse it + // without setting the frequency themselves. + bus_cfg.freq_write = 8000000; + bus_cfg.freq_read = 8000000; + is_st7735 = ((id & 0xFFFF) == 0x7683 || (id & 0xFFFF) == 0x897C); + is_gc9107 = (id & 0xFFFFFF) == 0x079100; + } if (is_st7735 || is_gc9107) { board = board_t::board_M5AtomS3R; @@ -2666,10 +2928,11 @@ The usage of each pin is as follows. // TP INT = GPIO_NUM_23 lgfx::pinMode(GPIO_NUM_23, lgfx::pin_mode_t::output); // TP INT lgfx::gpio_hi(GPIO_NUM_23); // select I2C Addr (high=0x14 / low=0x5D) - lgfx::i2c::init(in_i2c_port, GPIO_NUM_31, GPIO_NUM_32); + // ボード確定まではソフトウェア I2C で通信し、ハードウェアポートを温存する + lgfx::i2c::init(probe_i2c_port, GPIO_NUM_31, GPIO_NUM_32); - id = lgfx::i2c::readRegister8(in_i2c_port, pi4io1_i2c_addr, 0x01).has_value() - && lgfx::i2c::readRegister8(in_i2c_port, pi4io2_i2c_addr, 0x01).has_value(); + 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"); @@ -2698,10 +2961,10 @@ The usage of each pin is as follows. 0xFF,0xFF,0xFF, }; - i2c_write_register8_array(in_i2c_port, pi4io1_i2c_addr, reg_data_io1_1, 100000); - i2c_write_register8_array(in_i2c_port, pi4io2_i2c_addr, reg_data_io2, 100000); + i2c_write_register8_array(probe_i2c_port, pi4io1_i2c_addr, reg_data_io1_1, 100000); + i2c_write_register8_array(probe_i2c_port, pi4io2_i2c_addr, reg_data_io2, 100000); lgfx::delay(10); - i2c_write_register8_array(in_i2c_port, pi4io1_i2c_addr, reg_data_io1_2, 100000); + i2c_write_register8_array(probe_i2c_port, pi4io1_i2c_addr, reg_data_io1_2, 100000); lgfx::pinMode(GPIO_NUM_23, lgfx::pin_mode_t::input); // TP INT lgfx::delay(100); @@ -2721,12 +2984,26 @@ The usage of each pin is as follows. bool hit_st7121 = false; bool hit_st7123 = false; bool read_st_touch_fw = false; - for (int i = 0; i < 3; ++i) { + bool found_gt911 = false; + // ST71xx のタッチコントローラは、スキャン動作中にリセットされた場合、 + // 応答可能になるまで数十 ms を要する (実測: 稼働中からのソフトリセット後 + // およそ 50ms)。待ちを打ち切ると ST パネルの判別に失敗して表示が出ない + // (ST7121/ST7123 は lane 速度が異なり DSI ID では安全に区別できない) ため、 + // ST touch の既知 FW 版数 (1/3) を認識するか GT911 (ILI9881C 個体) が + // ACK するまで待つ。 + int last_logged_fw = -1; + for (int i = 0; i < 60; ++i) { uint8_t fw_version = 0; uint8_t fw_reg[2] = { 0, 0 }; - if (lgfx::i2c::transactionWriteRead(in_i2c_port, Touch_ST7123::default_addr, fw_reg, sizeof(fw_reg), &fw_version, 1, 100000).has_value()) { + if (lgfx::i2c::transactionWriteRead(probe_i2c_port, Touch_ST7123::default_addr, fw_reg, sizeof(fw_reg), &fw_version, 1, 100000).has_value()) { read_st_touch_fw = true; - ESP_LOGI(LIBRARY_NAME, "M5Tab5 ST touch FW version %02x", fw_version); + if (fw_version != last_logged_fw) { // 未知値のリトライ継続で同じログを繰り返さない + last_logged_fw = fw_version; + ESP_LOGI(LIBRARY_NAME, "M5Tab5 ST touch FW version %02x", fw_version); + if (fw_version != 1 && fw_version != 3) { + ESP_LOGW(LIBRARY_NAME, "M5Tab5 unknown ST touch FW version %02x", fw_version); + } + } if (fw_version == 1) { hit_st7121 = true; break; @@ -2735,14 +3012,26 @@ The usage of each pin is as follows. hit_st7123 = true; break; } - ESP_LOGW(LIBRARY_NAME, "M5Tab5 unknown ST touch FW version %02x", fw_version); + } else { + // GT911 はアドレス ACK の確認のみ (実読すると内部ポインタを進めてしまう) + if (lgfx::i2c::beginTransaction(probe_i2c_port, Touch_GT911::default_addr_1, 100000, false).has_value() + && lgfx::i2c::endTransaction(probe_i2c_port).has_value()) { + found_gt911 = true; + ESP_LOGI(LIBRARY_NAME, "M5Tab5 GT911 touch detected"); + break; + } } lgfx::delay(10); } - if (!read_st_touch_fw) { + if (!read_st_touch_fw && !found_gt911) { ESP_LOGW(LIBRARY_NAME, "M5Tab5 ST touch FW version read failed"); } + // ボードが確定し I2C の用は済んだので、常用するハードウェアポートへ + // バスを引き継ぐ (タッチがこのポートを使う) + lgfx::i2c::release(probe_i2c_port); + lgfx::i2c::init(in_i2c_port, GPIO_NUM_31, GPIO_NUM_32); + auto bus_dsi = new Bus_DSI(); _bus_last.reset(bus_dsi); auto bus_cfg = bus_dsi->config(); @@ -2867,6 +3156,8 @@ The usage of each pin is as follows. } goto init_clear; } + // ボード不成立時のみここへ来る。プローブに使ったソフトウェアポートを返す + lgfx::i2c::release(probe_i2c_port); } } @@ -2968,7 +3259,8 @@ The usage of each pin is as follows. } else if (result == 0x03) { // NessoN1 ? - lgfx::i2c::init(i2c_port, GPIO_NUM_10, GPIO_NUM_8); + // パネル ID で確定するまではソフトウェア I2C で通信する + lgfx::i2c::init(probe_i2c_port, GPIO_NUM_10, GPIO_NUM_8); // PI4IO E0 // P0 BTN1 // P1 BTN2 @@ -3004,8 +3296,8 @@ The usage of each pin is as follows. 0x05, 0b10000010, 0, // PI4IO_REG_OUT_SET 0xFF,0xFF,0xFF, }; - i2c_write_register8_array(i2c_port, pi4io2_i2c_addr, reg_data_io2, 100000); - i2c_write_register8_array(i2c_port, pi4io1_i2c_addr, reg_data_io1, 100000); + i2c_write_register8_array(probe_i2c_port, pi4io2_i2c_addr, reg_data_io2, 100000); + i2c_write_register8_array(probe_i2c_port, pi4io1_i2c_addr, reg_data_io1, 100000); bus_cfg.pin_mosi = GPIO_NUM_21; bus_cfg.pin_miso = GPIO_NUM_22; @@ -3023,6 +3315,11 @@ The usage of each pin is as follows. board = board_t::board_ArduinoNessoN1; ESP_LOGI(LIBRARY_NAME, "[Autodetect] board_ArduinoNessoN1"); + // ボードが確定したので、常用するハードウェアポートへバスを引き継ぐ + // (バックライトとタッチがこのポートを使う) + lgfx::i2c::release(probe_i2c_port); + lgfx::i2c::init(i2c_port, GPIO_NUM_10, GPIO_NUM_8); + bus_spi->release(); bus_cfg.freq_write = 40000000; bus_cfg.freq_read = 16000000; @@ -3077,6 +3374,9 @@ The usage of each pin is as follows. } bus_spi->release(); } + // ボード不成立時のみここへ来る (成立時は goto で抜けている)。 + // プローブに使ったソフトウェアポートを返し、ピンを元へ戻す + lgfx::i2c::release(probe_i2c_port); for (auto &bup : backup_pins) { bup.restore(); } } } @@ -3116,49 +3416,30 @@ The usage of each pin is as follows. , GPIO_NUM_26 }; - lgfx::i2c::init(i2c_port, toughc5_i2c_sda, toughc5_i2c_scl); - - if (_check_m5pm1(i2c_port) && _check_m5ioe1(i2c_port)) { - // M5PM1 初期化 - lgfx::i2c::writeRegister8(i2c_port, m5pm1_i2c_addr, 0x09, 0x00, 0, m5pm1_i2c_freq); // I2C sleep disable - lgfx::i2c::writeRegister8(i2c_port, m5pm1_i2c_addr, 0x0A, 0x00, 0, m5pm1_i2c_freq); // WDT disable - lgfx::i2c::bitOn( i2c_port, m5pm1_i2c_addr, 0x06, 0x17, m5pm1_i2c_freq); // PWR_CFG: LED_CTRL, LDO, DCDC, CHG enable + // ボード確定まではソフトウェア I2C で通信し、ハードウェアポートを温存する + lgfx::i2c::init(probe_i2c_port, toughc5_i2c_sda, toughc5_i2c_scl); - // M5IOE1 初期化 - lgfx::i2c::writeRegister8(i2c_port, m5ioe1_i2c_addr, 0x23, 0x00, 0, m5ioe1_i2c_freq); // I2C sleep disable + if (_check_m5pm1(probe_i2c_port) && _check_m5ioe1(probe_i2c_port)) { + // ボード確定 (パネル ID 確認) 前の書き込みは、ID 読みに必要な最小限 + // (LCD 電源とリセット解除) に留め、不成立時に復元できるよう元値を控える + auto pm1_06 = lgfx::i2c::readRegister8(probe_i2c_port, m5pm1_i2c_addr, 0x06, m5pm1_i2c_freq); + auto ioe1_03 = lgfx::i2c::readRegister8(probe_i2c_port, m5ioe1_i2c_addr, 0x03, m5ioe1_i2c_freq); + auto ioe1_05 = lgfx::i2c::readRegister8(probe_i2c_port, m5ioe1_i2c_addr, 0x05, m5ioe1_i2c_freq); + auto ioe1_13 = lgfx::i2c::readRegister8(probe_i2c_port, m5ioe1_i2c_addr, 0x13, m5ioe1_i2c_freq); + if (pm1_06.has_value() && ioe1_03.has_value() && ioe1_05.has_value() && ioe1_13.has_value()) { - // M5IOE1 PIN4(LCD_RST), PIN5(LCD_EN), PIN10(LCD_BL) を出力に設定 + // M5IOE1 PIN4(LCD_RST), PIN5(LCD_EN) を High で出力に設定 // PIN4=bit3, PIN5=bit4 → low register (0x03/0x05/0x13) - // PIN10=bit1(high) → high register (0x04/0x06/0x14) - static constexpr uint8_t IOE1_PIN_10 = 9; - static constexpr uint8_t IOE1_BIT_10_H = (1u << (IOE1_PIN_10 - 8)); + // リセット線 (PIN4, 基板プルアップ無し) を Low のまま駆動しないよう、 + // 出力ラッチへ High を先に書いてから push-pull / 出力化する + lgfx::i2c::bitOn( probe_i2c_port, m5ioe1_i2c_addr, 0x05, 0b00011000, m5ioe1_i2c_freq); // PIN4,5 latch HIGH + lgfx::i2c::bitOff(probe_i2c_port, m5ioe1_i2c_addr, 0x13, 0b00011000, m5ioe1_i2c_freq); // PIN4,5 push-pull + lgfx::i2c::bitOn( probe_i2c_port, m5ioe1_i2c_addr, 0x03, 0b00011000, m5ioe1_i2c_freq); // PIN4,5 output - lgfx::i2c::bitOff(i2c_port, m5ioe1_i2c_addr, 0x13, 0b00011000, m5ioe1_i2c_freq); // PIN4,5 push-pull - lgfx::i2c::bitOff(i2c_port, m5ioe1_i2c_addr, 0x14, IOE1_BIT_10_H, m5ioe1_i2c_freq); // PIN10 push-pull - lgfx::i2c::bitOn( i2c_port, m5ioe1_i2c_addr, 0x03, 0b00011000, m5ioe1_i2c_freq); // PIN4,5 output - lgfx::i2c::bitOn( i2c_port, m5ioe1_i2c_addr, 0x04, IOE1_BIT_10_H, m5ioe1_i2c_freq); // PIN10 output - - // LCD enable - lgfx::i2c::bitOn( i2c_port, m5ioe1_i2c_addr, 0x05, 1 << 4, m5ioe1_i2c_freq); // PIN5(LCD_EN) HIGH - - // LCD reset - if (use_reset) { - lgfx::i2c::bitOff(i2c_port, m5ioe1_i2c_addr, 0x05, 1 << 3, m5ioe1_i2c_freq); // PIN4(LCD_RST) LOW - lgfx::delay(2); - lgfx::i2c::bitOn( i2c_port, m5ioe1_i2c_addr, 0x05, 1 << 3, m5ioe1_i2c_freq); // PIN4(LCD_RST) HIGH - lgfx::delay(10); - } - - // M5PM1 GPIO2(TP_RST) を出力に設定してリセット - lgfx::i2c::bitOff(i2c_port, m5pm1_i2c_addr, 0x16, 0b11 << (2*2), m5pm1_i2c_freq); // GPIO2 → GPIO function - lgfx::i2c::bitOn( i2c_port, m5pm1_i2c_addr, 0x10, 1 << 2, m5pm1_i2c_freq); // GPIO2 output - lgfx::i2c::bitOff(i2c_port, m5pm1_i2c_addr, 0x13, 1 << 2, m5pm1_i2c_freq); // GPIO2 push-pull - if (use_reset) { - lgfx::i2c::bitOff(i2c_port, m5pm1_i2c_addr, 0x11, 1 << 2, m5pm1_i2c_freq); // GPIO2(TP_RST) LOW - lgfx::delay(2); - lgfx::i2c::bitOn( i2c_port, m5pm1_i2c_addr, 0x11, 1 << 2, m5pm1_i2c_freq); // GPIO2(TP_RST) HIGH - lgfx::delay(10); - } + // LCD のロジック電源 (PM1 LDO → PYB_LCD_EN 経由)。PM1 リセット後は + // PWR_CFG がクリアされているため、コールドブートではここで入れる + lgfx::i2c::bitOn( probe_i2c_port, m5pm1_i2c_addr, 0x06, 0x04, m5pm1_i2c_freq); // PWR_CFG: LDO enable + lgfx::delay(10); // SPI バスを設定して LCD パネル ID を確認 bus_cfg.pin_mosi = GPIO_NUM_7; @@ -3171,13 +3452,60 @@ The usage of each pin is as follows. bus_spi->init(); std::uint32_t id = _read_panel_id(bus_spi, GPIO_NUM_25); + if ((id & 0xFF) != 0xE3) + { // 一時的な読み損ないでボード不成立に落ちないよう一度だけ再試行する + lgfx::delay(2); + id = _read_panel_id(bus_spi, GPIO_NUM_25); + } if ((id & 0xFF) == 0xE3) { // ILI9342c board = board_t::board_M5ToughC5; ESP_LOGI(LIBRARY_NAME, "[Autodetect] board_M5ToughC5"); + // --- ボードが確定したので、確認前に行えなかった初期化をまとめて行う + // M5PM1 初期化 + lgfx::i2c::writeRegister8(probe_i2c_port, m5pm1_i2c_addr, 0x09, 0x00, 0, m5pm1_i2c_freq); // I2C sleep disable + lgfx::i2c::writeRegister8(probe_i2c_port, m5pm1_i2c_addr, 0x0A, 0x00, 0, m5pm1_i2c_freq); // WDT disable + lgfx::i2c::bitOn( probe_i2c_port, m5pm1_i2c_addr, 0x06, 0x17, m5pm1_i2c_freq); // PWR_CFG: LED_CTRL, LDO, DCDC, CHG enable + + // M5IOE1 初期化 + lgfx::i2c::writeRegister8(probe_i2c_port, m5ioe1_i2c_addr, 0x23, 0x00, 0, m5ioe1_i2c_freq); // I2C sleep disable + + // M5IOE1 PIN10(LCD_BL) を出力に設定 + // PIN10=bit1(high) → high register (0x04/0x06/0x14) + static constexpr uint8_t IOE1_PIN_10 = 9; + static constexpr uint8_t IOE1_BIT_10_H = (1u << (IOE1_PIN_10 - 8)); + lgfx::i2c::bitOff(probe_i2c_port, m5ioe1_i2c_addr, 0x14, IOE1_BIT_10_H, m5ioe1_i2c_freq); // PIN10 push-pull + lgfx::i2c::bitOn( probe_i2c_port, m5ioe1_i2c_addr, 0x04, IOE1_BIT_10_H, m5ioe1_i2c_freq); // PIN10 output + + // LCD reset + if (use_reset) { + lgfx::i2c::bitOff(probe_i2c_port, m5ioe1_i2c_addr, 0x05, 1 << 3, m5ioe1_i2c_freq); // PIN4(LCD_RST) LOW + lgfx::delay(2); + lgfx::i2c::bitOn( probe_i2c_port, m5ioe1_i2c_addr, 0x05, 1 << 3, m5ioe1_i2c_freq); // PIN4(LCD_RST) HIGH + lgfx::delay(10); + } + + // M5PM1 GPIO2(TP_RST) を出力に設定してリセット + // ラッチへ High (リセット解除) を先に書いてから出力化する + lgfx::i2c::bitOn( probe_i2c_port, m5pm1_i2c_addr, 0x11, 1 << 2, m5pm1_i2c_freq); // GPIO2 latch HIGH + lgfx::i2c::bitOff(probe_i2c_port, m5pm1_i2c_addr, 0x16, 0b11 << (2*2), m5pm1_i2c_freq); // GPIO2 → GPIO function + lgfx::i2c::bitOn( probe_i2c_port, m5pm1_i2c_addr, 0x10, 1 << 2, m5pm1_i2c_freq); // GPIO2 output + lgfx::i2c::bitOff(probe_i2c_port, m5pm1_i2c_addr, 0x13, 1 << 2, m5pm1_i2c_freq); // GPIO2 push-pull + if (use_reset) { + lgfx::i2c::bitOff(probe_i2c_port, m5pm1_i2c_addr, 0x11, 1 << 2, m5pm1_i2c_freq); // GPIO2(TP_RST) LOW + lgfx::delay(2); + lgfx::i2c::bitOn( probe_i2c_port, m5pm1_i2c_addr, 0x11, 1 << 2, m5pm1_i2c_freq); // GPIO2(TP_RST) HIGH + lgfx::delay(10); + } + + // ボードが確定したので、常用するハードウェアポートへバスを引き継ぐ + // (バックライトとタッチがこのポートを使う) + lgfx::i2c::release(probe_i2c_port); + lgfx::i2c::init(i2c_port, toughc5_i2c_sda, toughc5_i2c_scl); + bus_spi->release(); - bus_cfg.freq_write = 20000000; + bus_cfg.freq_write = 40000000; bus_cfg.freq_read = 16000000; bus_spi->config(bus_cfg); @@ -3207,7 +3535,7 @@ The usage of each pin is as follows. cfg.pin_sda = toughc5_i2c_sda; cfg.pin_scl = toughc5_i2c_scl; cfg.i2c_addr = 0x2E; - cfg.i2c_port = I2C_NUM_0; + cfg.i2c_port = i2c_port; cfg.freq = 400000; cfg.x_min = 0; cfg.x_max = 319; @@ -3221,8 +3549,92 @@ The usage of each pin is as follows. goto init_clear; } bus_spi->release(); + + // 不成立: 確定前に書いた最小限のレジスタを元値へ復元する + lgfx::i2c::writeRegister8(probe_i2c_port, m5ioe1_i2c_addr, 0x03, ioe1_03.value(), 0, m5ioe1_i2c_freq); + lgfx::i2c::writeRegister8(probe_i2c_port, m5ioe1_i2c_addr, 0x13, ioe1_13.value(), 0, m5ioe1_i2c_freq); + lgfx::i2c::writeRegister8(probe_i2c_port, m5ioe1_i2c_addr, 0x05, ioe1_05.value(), 0, m5ioe1_i2c_freq); + lgfx::i2c::writeRegister8(probe_i2c_port, m5pm1_i2c_addr, 0x06, pm1_06.value(), 0, m5pm1_i2c_freq); + } + } + // ここへ来るのはボード不成立の場合のみ。デバイスへ書いた分は復元済みで、 + // ソフトウェア I2C と ESP 側のピン状態を返す + lgfx::i2c::release(probe_i2c_port); + for (auto &bup : backup_pins) { bup.restore(); } + } + +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + + if (board == 0 || board == board_t::board_M5CoreMatrix) + { + // CoreMatrix: system I2C SDA=G0 / SCL=G1 (physically shared with Grove and M-Bus) + static constexpr int_fast16_t corematrix_i2c_sda = GPIO_NUM_0; + static constexpr int_fast16_t corematrix_i2c_scl = GPIO_NUM_1; + + gpio::pin_backup_t backup_pins[] = + { GPIO_NUM_0 + , GPIO_NUM_1 + }; + + // Probe over software I2C; the hardware port is left untouched until the board is confirmed + lgfx::i2c::init(probe_i2c_port, corematrix_i2c_sda, corematrix_i2c_scl); + + if (_check_m5pm1(probe_i2c_port) && _check_m5ioe1(probe_i2c_port)) { + lgfx::i2c::writeRegister8(probe_i2c_port, m5pm1_i2c_addr, 0x09, 0x00, 0, m5pm1_i2c_freq); // I2C sleep disable + lgfx::i2c::writeRegister8(probe_i2c_port, m5pm1_i2c_addr, 0x0A, 0x00, 0, m5pm1_i2c_freq); // WDT disable + + // Drive M5IOE1 PIN4 (LEDS_EN) high to power the LED matrix rail. + // The TM1680 sits on that rail and does not respond on I2C until powered. + lgfx::i2c::writeRegister8(probe_i2c_port, m5ioe1_i2c_addr, 0x23, 0x00, 0, m5ioe1_i2c_freq); // I2C sleep disable + lgfx::i2c::bitOff(probe_i2c_port, m5ioe1_i2c_addr, 0x13, 1 << 3, m5ioe1_i2c_freq); // PIN4 push-pull + lgfx::i2c::bitOn (probe_i2c_port, m5ioe1_i2c_addr, 0x03, 1 << 3, m5ioe1_i2c_freq); // PIN4 output + lgfx::i2c::bitOn (probe_i2c_port, m5ioe1_i2c_addr, 0x05, 1 << 3, m5ioe1_i2c_freq); // PIN4 HIGH + lgfx::delay(20); // wait for the rail to rise and the TM1680 to complete POR + + // The TM1680 has no ID register; check for an address ACK only + bool hit = lgfx::i2c::beginTransaction(probe_i2c_port, tm1680_i2c_addr, 100000, false).has_value() + && lgfx::i2c::endTransaction(probe_i2c_port).has_value(); + if (hit) + { + board = board_t::board_M5CoreMatrix; + ESP_LOGI(LIBRARY_NAME, "[Autodetect] board_M5CoreMatrix"); + + // Board confirmed: hand the bus over to the hardware I2C port + lgfx::i2c::release(probe_i2c_port); + + auto bus_i2c = new Bus_I2C(); + { + auto cfg = bus_i2c->config(); + cfg.i2c_port = i2c_port; + cfg.freq_write = i2c_freq; + cfg.freq_read = i2c_freq; + cfg.pin_sda = corematrix_i2c_sda; + cfg.pin_scl = corematrix_i2c_scl; + cfg.i2c_addr = tm1680_i2c_addr; + cfg.prefix_len = 0; // the TM1680 protocol has no command/data prefix byte + bus_i2c->config(cfg); + } + _bus_last.reset(bus_i2c); + + auto p = new lgfx::Panel_TM1680(); + { + auto cfg = p->config(); + cfg.bus_shared = false; + // rotation 1 (the default) shows upright with the buttons at the top + cfg.offset_rotation = 3; + p->config(cfg); + p->setRotation(1); + } + p->bus(bus_i2c); + _panel_last.reset(p); + + goto init_clear; + } + // Not this board: restore the LED matrix power rail + lgfx::i2c::bitOff(probe_i2c_port, m5ioe1_i2c_addr, 0x05, 1 << 3, m5ioe1_i2c_freq); } - lgfx::i2c::release(i2c_port); + // Reached only when no board was detected; only the software port was touched + lgfx::i2c::release(probe_i2c_port); for (auto &bup : backup_pins) { bup.restore(); } } @@ -3291,6 +3703,7 @@ The usage of each pin is as follows. case board_M5Tough: title = "M5Tough"; break; case board_M5ToughC5: title = "M5ToughC5"; break; case board_M5StampC5: title = "M5StampC5"; break; + case board_M5CoreMatrix: title = "M5CoreMatrix"; break; case board_M5Station: title = "M5Station"; break; case board_M5StopWatch: title = "M5StopWatch"; break; case board_M5ChainCaptain: title = "M5ChainCaptain"; break; diff --git a/src/M5ModuleDisplay.h b/src/M5ModuleDisplay.h index f26e296a..c6969f08 100644 --- a/src/M5ModuleDisplay.h +++ b/src/M5ModuleDisplay.h @@ -149,7 +149,7 @@ class M5ModuleDisplay : public M5GFX int spi_sclk = GPIO_NUM_36; #elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - #define M5GFX_SPI_HOST VSPI_HOST + #define M5GFX_SPI_HOST SPI3_HOST int i2c_port = 1; int i2c_sda = GPIO_NUM_21; diff --git a/src/lgfx/utility/lgfx_qoi.c b/src/lgfx/utility/lgfx_qoi.c index da859b20..478e4683 100644 --- a/src/lgfx/utility/lgfx_qoi.c +++ b/src/lgfx/utility/lgfx_qoi.c @@ -67,8 +67,6 @@ struct _qoi_t uint8_t read_buf[LGFX_QOI_READBUF_LEN]; }; -#define QOI_DEBUG - #ifdef QOI_DEBUG #define debug_printf(...) fprintf(stderr, __VA_ARGS__) #define QOI_ERROR(...) (fprintf(stderr, __VA_ARGS__), -2) diff --git a/src/lgfx/v1/gitTagVersion.h b/src/lgfx/v1/gitTagVersion.h index b392faf7..187a0872 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 26 +#define LGFX_VERSION_PATCH 27 #define LOVYANGFX_VERSION F( LGFX_VERSION_MAJOR "." LGFX_VERSION_MINOR "." LGFX_VERSION_PATCH ) diff --git a/src/lgfx/v1/lgfx_fonts.cpp b/src/lgfx/v1/lgfx_fonts.cpp index 8709cdf2..333b3a2a 100644 --- a/src/lgfx/v1/lgfx_fonts.cpp +++ b/src/lgfx/v1/lgfx_fonts.cpp @@ -941,7 +941,7 @@ namespace lgfx if (bs->bit_pos + bpp > bs->bit_length) break; ret = (uint8_t)bs->read_bits(bpp); - if (bs->bit_pos != bpp && prev_v == ret) + if (out != 0 && prev_v == ret) { count = 0; state = RLE_REPEATED; @@ -1910,11 +1910,14 @@ namespace lgfx int32_t sy = 65536 * style->size_y; y += (metrics->y_offset * sy) >> 16; - if (code == 0x20) { + if (!this->getUnicodeIndex(code, &gNum)) { + if (code != 0x20) { + return drawCharDummy(gfx, x, y, this->spaceWidth, metrics->height, style, filled_x); + } + // Some VLW exporters omit the space glyph; for such fonts, keep the + // guessed advance instead of rendering the missing-glyph box. gNum = 0xFFFF; buffer[2] = getSwap32(this->spaceWidth); - } else if (!this->getUnicodeIndex(code, &gNum)) { - return drawCharDummy(gfx, x, y, this->spaceWidth, metrics->height, style, filled_x); } else { file->preRead(); file->seek(28 + gNum * 28); diff --git a/src/lgfx/v1/panel/Panel_AMOLED.cpp b/src/lgfx/v1/panel/Panel_AMOLED.cpp index 4533b038..195327cc 100644 --- a/src/lgfx/v1/panel/Panel_AMOLED.cpp +++ b/src/lgfx/v1/panel/Panel_AMOLED.cpp @@ -45,6 +45,10 @@ namespace lgfx case 1: madctl = 0x60; break; case 2: madctl = 0xc0; break; case 3: madctl = 0xa0; break; + case 4: madctl = 0xc2; break; + case 5: madctl = 0x62; break; + case 6: madctl = 0x02; break; + case 7: madctl = 0xa2; break; } startWrite(); diff --git a/src/lgfx/v1/panel/Panel_IT8951.cpp b/src/lgfx/v1/panel/Panel_IT8951.cpp index 223b4a0f..325ffd4c 100644 --- a/src/lgfx/v1/panel/Panel_IT8951.cpp +++ b/src/lgfx/v1/panel/Panel_IT8951.cpp @@ -241,10 +241,14 @@ IT8951 Registers defines _bus->endTransaction(); } - bool Panel_IT8951::_wait_busy(uint32_t timeout) + // Bounded poll of the BUSY pin, without touching chip-select. + // Returns false after `timeout` ms if BUSY never deasserts (e.g. the + // panel's boost converter browned out and left BUSY stuck), instead of + // spinning forever. Shared by _wait_busy() below and by _write_args(), + // which must keep CS asserted continuously across a multi-word burst + // and therefore can't use _wait_busy() directly. + bool Panel_IT8951::_wait_busy_pin(uint32_t timeout) { - _bus->wait(); - cs_control(true); if (_cfg.pin_busy >= 0 && !lgfx::gpio_in(_cfg.pin_busy)) { auto start_ms = millis(); @@ -264,6 +268,17 @@ IT8951 Registers defines } } while (!lgfx::gpio_in(_cfg.pin_busy)); } + return true; + } + + bool Panel_IT8951::_wait_busy(uint32_t timeout) + { + _bus->wait(); + cs_control(true); + if (!_wait_busy_pin(timeout)) + { + return false; + } cs_control(false); return true; } @@ -339,7 +354,14 @@ IT8951 Registers defines { uint32_t buf = getSwap16(args[i]); _bus->wait(); - while (!lgfx::gpio_in(_cfg.pin_busy)); + // CS must stay asserted for the whole multi-word burst, so this + // can't go through _wait_busy() (it toggles CS). A stuck BUSY + // line must still fail out here rather than hang forever. + if (!_wait_busy_pin()) + { + cs_control(true); + return false; + } _bus->writeData(buf, 16); } while ( ++i < length ); return true; diff --git a/src/lgfx/v1/panel/Panel_IT8951.hpp b/src/lgfx/v1/panel/Panel_IT8951.hpp index 0a44a700..89033e56 100644 --- a/src/lgfx/v1/panel/Panel_IT8951.hpp +++ b/src/lgfx/v1/panel/Panel_IT8951.hpp @@ -88,6 +88,7 @@ namespace lgfx uint16_t _vcom = 0; bool _wait_busy( uint32_t timeout = 4096); + bool _wait_busy_pin( uint32_t timeout = 4096); bool _write_command( uint16_t cmd); bool _write_word( uint16_t data); bool _write_args( uint16_t cmd, uint16_t *args, int32_t length); diff --git a/src/lgfx/v1/panel/Panel_SSD1677.cpp b/src/lgfx/v1/panel/Panel_SSD1677.cpp index ce7f63e2..232f7005 100644 --- a/src/lgfx/v1/panel/Panel_SSD1677.cpp +++ b/src/lgfx/v1/panel/Panel_SSD1677.cpp @@ -34,7 +34,6 @@ namespace lgfx //---------------------------------------------------------------------------- static constexpr int8_t Bayer[16] = { -30, 18, -22, 26, -14, 2, -6, 10, -18, 30, -26, 22, -2, 14, -10, 6 }; - // static constexpr int8_t Bayer[16] = { 0, }; // SSD1677 commands static constexpr uint8_t CMD_DEEP_SLEEP = 0x10; @@ -58,115 +57,112 @@ namespace lgfx static constexpr uint8_t CTRL1_BYPASS_RED = 0x40; //-------------------------------------------------------------------------- - // LUTs (ported from community-sdk EInkDisplay, non-X3 / GDEQ0426T82). + // SSD1677 LUT layout. // Layout: VS patterns (5 groups x 10 bytes) + TP/RP timing (10 groups x 5 bytes) // + frame rate (5 bytes) = 105 bytes -> command 0x32. // Then voltages [VGH, VSH1, VSH2, VSL, VCOM] (bytes 105..109) -> 0x03/0x04/0x2C. //-------------------------------------------------------------------------- - static constexpr uint8_t lut_fastest[110] = { - 0x55, 0x55, 0x55, 0x55, 0x55, 0x5A, 0xAA, 0xAA, 0x00, 0x00, // LUT0: 00 white - 0xAA, 0x95, 0x55, 0x55, 0x55, 0x5A, 0x82, 0xA0, 0x00, 0x00, // LUT1: 01 light - 0xAA, 0xA5, 0x55, 0x55, 0x55, 0x5A, 0xA0, 0x00, 0x00, 0x00, // LUT2: 10 dark - 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0x00, 0x00, // LUT3: 11 black - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM - 0x02, 0x02, 0x02, 0x02, 0x00, // TP G0: A, B, C, D, RP - 0x02, 0x02, 0x02, 0x02, 0x00, // TP G1: A, B, C, D, RP - 0x02, 0x02, 0x02, 0x02, 0x00, // TP G2: A, B, C, D, RP - 0x02, 0x02, 0x02, 0x02, 0x00, // TP G3: A, B, C, D, RP - 0x02, 0x02, 0x02, 0x02, 0x00, // TP G4: A, B, C, D, RP - 0x02, 0x02, 0x02, 0x02, 0x00, // TP G5: A, B, C, D, RP - 0x02, 0x02, 0x02, 0x02, 0x00, // TP G6: A, B, C, D, RP - 0x02, 0x02, 0x02, 0x02, 0x00, // TP G7: A, B, C, D, RP - 0x00, 0x00, 0x00, 0x00, 0x00, // TP G8: A, B, C, D, RP - 0x00, 0x00, 0x00, 0x00, 0x00, // TP G9: A, B, C, D, RP - 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, // frame rate - 0x17, 0x41, 0xA8, 0x32, 0x30, // VGH, VSH1, VSH2, VSL, VCOM(-1.2V) - }; - - // Factory absolute LUTs. 2-bit pixel encoding: BW=bit0(LSB), RED=bit1(MSB). - // 00=black, 01=dark, 10=light, 11=white. (i.e. value v(0..3) = (MSB<<1)|LSB.) - static constexpr uint8_t lut_factory_fast[110] = { - 0x00, 0x4A, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT0: 00 black - 0x80, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT1: 01 dark - 0x88, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT2: 10 light - 0xA8, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT3: 11 white + // VS codes: 0 = VSS (no drive), 1 = VSH1 (darken), 2 = VSL (lighten), + // 3 = VSH2 (weak darken). + // For the absolute waveforms (quality/text/fast) the RAM group selects the + // target level: group 0 = white, 1 = light gray, 2 = dark gray, 3 = black. + + // Quality: an oscillation prefix erases the previous image, then the + // four-gray tail forms the target. + // Keep |VSH1| == |VSL|: the net drive stays identical for all groups, + // which prevents image-correlated ghosting over repeated refreshes. + // The timing groups repeat the tail to stabilize the black and white + // endpoints. + static constexpr uint8_t lut_quality[110] = { + 0x66, 0x66, 0x00, 0x4A, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, // white + 0x66, 0x66, 0x80, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // light + 0x66, 0x66, 0x88, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // dark + 0x66, 0x66, 0xA8, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // black 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM - 0x09, 0x0C, 0x03, 0x03, 0x00, - 0x0F, 0x03, 0x07, 0x03, 0x00, - 0x03, 0x00, 0x02, 0x00, 0x00, + 0x05, 0x05, 0x05, 0x05, 0x00, + 0x05, 0x05, 0x05, 0x05, 0x01, + 0x08, 0x0B, 0x02, 0x03, 0x01, + 0x0C, 0x02, 0x07, 0x02, 0x01, + 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x44, 0x44, 0x44, 0x44, 0x44, // frame rate (faster clock) - 0x17, 0x41, 0xA8, 0x32, 0x50, // VGH, VSH1, VSH2, VSL, VCOM(-2.0V) + 0x22, 0x22, 0x22, 0x22, 0x22, // frame rate + 0x17, 0x46, 0xA8, 0x36, 0x30, // VGH, VSH1(+16V), VSH2, VSL(-16V), VCOM(-1.2V) }; - static constexpr uint8_t lut_factory_quality[110] = { - 0x00, 0x4A, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT0: 00 black - 0x80, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT1: 01 dark - 0x88, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT2: 10 light - 0xA8, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT3: 11 white + // Text: 64-frame absolute four-gray waveform for Mode 1. + static constexpr uint8_t lut_text[110] = { + 0x55, 0x55, 0x55, 0x55, 0x55, 0x5A, 0xAA, 0xAA, 0x00, 0x00, // white + 0xAA, 0x95, 0x55, 0x55, 0x55, 0x5A, 0x82, 0xA0, 0x00, 0x00, // light + 0xAA, 0xA5, 0x55, 0x55, 0x55, 0x5A, 0xA0, 0x00, 0x00, 0x00, // dark + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x50, 0x00, 0x00, // black 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM - 0x08, 0x0B, 0x02, 0x03, 0x00, - 0x0C, 0x02, 0x07, 0x02, 0x00, - 0x01, 0x00, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, // frame rate + 0x17, 0x41, 0xA8, 0x32, 0x30, // VGH, VSH1(+15V), VSH2, VSL(-15V), VCOM(-1.2V) + }; + + // Fast: 48-frame absolute four-gray waveform for Mode 2. + // The first 16 phases run for one frame and the remaining phases for two, + // while the final HOLD phase of each middle-gray row uses VSH2 for a weak + // two-frame darkening trim. + static constexpr uint8_t lut_fast[110] = { + 0x55, 0x55, 0x55, 0x55, 0x55, 0x5A, 0xAA, 0xAA, 0x00, 0x00, // white + 0xAA, 0x95, 0x55, 0x55, 0x55, 0x5A, 0x82, 0xAC, 0x00, 0x00, // light + 0xAA, 0xA5, 0x55, 0x55, 0x55, 0x5A, 0xAC, 0x00, 0x00, 0x00, // dark + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x50, 0x00, 0x00, // black + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM + 0x01, 0x01, 0x01, 0x01, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, - 0x22, 0x22, 0x22, 0x22, 0x22, // frame rate (slower clock) - 0x17, 0x41, 0xA8, 0x32, 0x30, // VGH, VSH1, VSH2, VSL, VCOM(-1.2V) + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, // frame rate + 0x17, 0x41, 0xA8, 0x32, 0x30, // VGH, VSH1(+15V), VSH2, VSL(-15V), VCOM(-1.2V) }; - // Single-activation Quality waveform. The first eight phases apply the same - // VSL/VSH1 sequence to all four LUT groups (four rapid white/black round - // trips, two frames per phase). The remaining phases are the calibrated - // four-gray target waveform. No intermediate image transfer or separate - // activation is required. - static constexpr uint8_t lut_quality_oscillating[110] = { - 0x99, 0x99, 0x00, 0x4A, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT0 - 0x99, 0x99, 0x80, 0x60, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT1 - 0x99, 0x99, 0x88, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT2 - 0x99, 0x99, 0xA8, 0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT3 + // Fastest: differential monochrome update (Mode 2). Here the RAM group is + // the transition class computed in _send_transition_planes: group 0 holds + // dark pixels, 3 holds light pixels, 1 drives black-to-white and 2 drives + // white-to-black. A 2-frame reverse-polarity prepulse precedes the + // 8-frame dose to curb ghosting from repeated partial updates. + static constexpr uint8_t lut_fastest[110] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // hold dark + 0x6A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // black -> white + 0x95, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // white -> black + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // hold light 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM 0x02, 0x02, 0x02, 0x02, 0x00, - 0x02, 0x02, 0x02, 0x02, 0x00, - 0x08, 0x0B, 0x02, 0x03, 0x00, - 0x0C, 0x02, 0x07, 0x02, 0x00, - 0x01, 0x00, 0x02, 0x00, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x22, 0x22, 0x22, 0x22, 0x22, - 0x17, 0x41, 0xA8, 0x36, 0x30, - }; - - // Differential Mode 2 base. Groups 00 and 11 are state-aware holds, - // group 01 lightens, and group 10 darkens. The actual pulse positions and - // durations are installed for each transition step below. - static constexpr uint8_t lut_gray_differential[110] = { - 0,0,0,0,0,0,0,0,0,0, - 0x54,0x54,0x40,0,0,0,0,0,0,0, - 0xAA,0xA0,0xA8,0,0,0,0,0,0,0, - 0xA2,0x22,0x20,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,0, 1,1,1,1,0, - 1,1,1,1,0, 0,0,0,0,0, - 0,0,0,0,0, 0,0,0,0,0, - 0,0,0,0,0, 0,0,0,0,0, - 0,0,0,0,0, 0,0,0,0,0, - 0x8F,0x8F,0x8F,0x8F,0x8F, - 0x17,0x41,0xA8,0x32,0x30, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, // frame rate + // VCOM -1.2V keeps the hold groups near field-free. + 0x17, 0x46, 0xA8, 0x32, 0x30, // VGH, VSH1(+16V), VSH2, VSL(-15V), VCOM(-1.2V) }; // Write a 110-byte LUT: 105 waveform bytes -> 0x32, voltages -> 0x03/0x04/0x2C. @@ -401,7 +397,7 @@ namespace lgfx _send_plane(CMD_WRITE_RAM_RED, img, tr); // full/half: same image to both } - // Refresh sequence (community-sdk refreshDisplay, B/W). + // Built-in monochrome refresh sequence. _wait_busy(); _bus->writeCommand(CMD_DISP_UPDATE_CTRL1, 8); _bus->writeData(is_full ? CTRL1_BYPASS_RED : CTRL1_NORMAL, 8); @@ -684,6 +680,26 @@ namespace lgfx return result; } + void Panel_SSD1677_4Gray::setSleep(bool flg) + { + Panel_SSD1677::setSleep(flg); + _invalidate_gray_state(); + if (!flg) + { + // The hardware reset in the wake path establishes the even RAM face. + _mode2_face_odd = false; + _mode2_face_known = true; + } + } + + void Panel_SSD1677_4Gray::setPowerSave(bool flg) + { + Panel_SSD1677::setPowerSave(flg); + // Analog power transitions make the retained Mode 2 face/history unsafe. + // The next Fast/Fastest update will reset and rebuild them. + _invalidate_gray_state(); + } + void Panel_SSD1677_4Gray::_invalidate_gray_state(void) { _optical_state = optical_state_t::unknown; @@ -724,29 +740,24 @@ namespace lgfx _displayed_valid = true; } - static uint8_t transition_group(uint8_t old_level, uint8_t new_level, - uint8_t step, bool monochrome) + static uint8_t dirty_byte_mask(int32_t byte_index, + const range_rect_t& dirty) { - if (monochrome) + uint8_t mask = 0xFF; + if (byte_index == (dirty.left >> 3)) + { + mask &= uint8_t(0xFFu >> (dirty.left & 7)); + } + if (byte_index == (dirty.right >> 3)) { - new_level = new_level < 2 ? 0 : 3; - if (old_level == new_level) { return old_level < 2 ? 0 : 3; } - return old_level < new_level ? 1 : 2; + mask &= uint8_t(0xFFu << (7 - (dirty.right & 7))); } - const uint8_t light_boundary = step; - const uint8_t dark_boundary = 2 - step; - const bool light = old_level < new_level - && old_level <= light_boundary - && new_level > light_boundary; - const bool dark = old_level > new_level - && old_level > dark_boundary - && new_level <= dark_boundary; - return light ? 1 : dark ? 2 : old_level < 2 ? 0 : 3; + return mask; } - void Panel_SSD1677_4Gray::_send_transition_plane( - uint8_t command, const uint8_t* new_lsb, const uint8_t* new_msb, - const range_rect_t& dirty, uint8_t step, bool monochrome) + void Panel_SSD1677_4Gray::_send_transition_planes( + const uint8_t* new_msb, + const range_rect_t& dirty) { const auto full = _full_range(); const uint32_t row_bytes = ((_cfg.panel_width + 7) & ~7) >> 3; @@ -754,160 +765,65 @@ namespace lgfx const uint8_t* old_msb = _displayed_buf + _buf_x1_len; uint8_t row[128]; - _set_ram_area(full.left, full.top, full.right + 1, full.bottom + 1); - _bus->writeCommand(command, 8); - for (int32_t y = 0; y < (int32_t)_cfg.panel_height; ++y) + // New levels are thresholded by new_msb, while an old middle gray must be + // driven all the way to the requested black/white endpoint. The group + // bits can still be formed bytewise without decoding individual pixels: + // BW = new_msb + // RED = new_msb ? (old_lsb & old_msb) : (old_lsb | old_msb) + const bool full_dirty = dirty.left == full.left + && dirty.top == full.top + && dirty.right == full.right + && dirty.bottom == full.bottom; + if (full_dirty) { - // Outside the dirty rectangle, preserve black/dark with group 00 and - // light/white with group 11. This distinction is essential because a - // single nominal no-drive group is not optically neutral over repeated - // full-panel Mode 2 scans. - memcpy(row, old_msb + size_t(y) * row_bytes, row_bytes); - if (y >= dirty.top && y <= dirty.bottom) - { - for (int32_t x = dirty.left; x <= dirty.right; ++x) - { - const size_t index = size_t(y) * row_bytes + (x >> 3); - const uint8_t mask = uint8_t(0x80u >> (x & 7)); - const uint8_t old_level = ((old_lsb[index] & mask) ? 1 : 0) - | ((old_msb[index] & mask) ? 2 : 0); - const uint8_t new_level = ((new_lsb[index] & mask) ? 1 : 0) - | ((new_msb[index] & mask) ? 2 : 0); - const uint8_t group = transition_group(old_level, new_level, - step, monochrome); - const uint8_t group_bit = command == CMD_WRITE_RAM_BW ? 1 : 2; - if (group & group_bit) { row[x >> 3] |= mask; } - else { row[x >> 3] &= uint8_t(~mask); } - } - } - _bus->writeBytes(row, row_bytes, true, false); + _send_plane(CMD_WRITE_RAM_BW, new_msb, full); } - } - - bool Panel_SSD1677_4Gray::_activate_transition_step( - const uint8_t* new_lsb, const uint8_t* new_msb, - const range_rect_t& dirty, uint8_t step, bool monochrome, - bool shortened, bool stronger_mono) - { - static constexpr uint8_t light_phases[] = {7, 4, 9}; - static constexpr uint8_t vsl[] = {0x36, 0x32, 0x32}; - static constexpr uint8_t dark_phases[] = {7, 6, 4}; - static constexpr uint8_t vsh1[] = {0x46, 0x3F, 0x46}; - static constexpr uint8_t light_position[] = { - 0, 1, 3, 5, 7, 9, 11, 13, 15, - }; - static constexpr uint8_t dark_position[] = { - 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, - }; - - _send_transition_plane(CMD_WRITE_RAM_BW, new_lsb, new_msb, - dirty, step, monochrome); - _send_transition_plane(CMD_WRITE_RAM_RED, new_lsb, new_msb, - dirty, step, monochrome); - - uint8_t lut[sizeof(lut_gray_differential)]; - memcpy(lut, lut_gray_differential, sizeof(lut)); - memset(lut, 0, 40); // groups 00 and 11 start as no-drive - - if (monochrome || step == 2) + else { - // Drive lightening and darkening concurrently. Fast uses the validated - // 12-frame 3+3+3+2+1 dose; Fastest uses the accepted eight-frame - // 2+2+2+1+1 dose. - // The fifth Fastest phase is already present for the darkening group. - // Use that same final frame for lightening as well: this raises the - // black-to-white dose from seven to eight frames without extending the - // eight-frame activation or changing outside-area hold groups. A - // 30-step navigation UI test retained the same 105 ms average and made - // the erased black cursor residue visually negligible. - const uint8_t light_end = shortened ? 5 : 8; - const uint8_t dark_end = shortened ? 5 : 8; - for (uint8_t position = 0; position < light_end; ++position) + _set_ram_area(full.left, full.top, full.right + 1, full.bottom + 1); + _bus->writeCommand(CMD_WRITE_RAM_BW, 8); + const int32_t first_byte = dirty.left >> 3; + const int32_t last_byte = dirty.right >> 3; + for (int32_t y = 0; y < (int32_t)_cfg.panel_height; ++y) { - lut[10 + (position >> 2)] |= - uint8_t{2} << (6 - ((position & 3) << 1)); - } - for (uint8_t position = 0; position < dark_end; ++position) - { - lut[20 + (position >> 2)] |= - uint8_t{1} << (6 - ((position & 3) << 1)); - } - for (uint8_t timing_group = 0; timing_group < 4; ++timing_group) - { - for (uint8_t phase = 0; phase < 4; ++phase) + const size_t row_offset = size_t(y) * row_bytes; + memcpy(row, old_msb + row_offset, row_bytes); + if (y >= dirty.top && y <= dirty.bottom) { - const uint8_t position = timing_group * 4 + phase; - uint8_t tp = 0; - if (shortened) + for (int32_t b = first_byte; b <= last_byte; ++b) { - if (stronger_mono) - { - // Fast retains the validated 12-frame dose. - if (position < 3) { tp = 3; } - else if (position == 3) { tp = 2; } - else if (position == 4) { tp = 1; } - } - else - { - // Fastest: keep the same five pulse positions and - // calibrated voltages, but shorten 3+3+3+2+1 to 2+2+2+1+1. - // This isolates the optical effect of an eight-frame dose from - // the already-validated state-aware outside-area holds. - if (position < 3) { tp = 2; } - else if (position < 5) { tp = 1; } - } + const uint8_t mask = dirty_byte_mask(b, dirty); + row[b] = (row[b] & uint8_t(~mask)) + | (new_msb[row_offset + b] & mask); } - else - { - if (position < 7) { tp = 3; } - else if (position == 7) { tp = 1; } - } - lut[50 + timing_group * 5 + phase] = tp; } + _bus->writeBytes(row, row_bytes, true, false); } } - else + + _set_ram_area(full.left, full.top, full.right + 1, full.bottom + 1); + _bus->writeCommand(CMD_WRITE_RAM_RED, 8); + const int32_t first_byte = dirty.left >> 3; + const int32_t last_byte = dirty.right >> 3; + for (int32_t y = 0; y < (int32_t)_cfg.panel_height; ++y) { - const uint8_t light_boundary = step; - const uint8_t dark_boundary = 2 - step; - for (uint8_t i = 0; i < light_phases[light_boundary]; ++i) - { - const uint8_t position = light_position[i]; - lut[10 + (position >> 2)] |= - uint8_t{2} << (6 - ((position & 3) << 1)); - } - for (uint8_t i = 0; i < dark_phases[dark_boundary]; ++i) - { - const uint8_t position = dark_position[i]; - lut[20 + (position >> 2)] |= - uint8_t{1} << (6 - ((position & 3) << 1)); - } - for (uint8_t timing_group = 0; timing_group < 4; ++timing_group) + const size_t row_offset = size_t(y) * row_bytes; + memcpy(row, old_msb + row_offset, row_bytes); + if (y >= dirty.top && y <= dirty.bottom) { - for (uint8_t phase = 0; phase < 4; ++phase) + for (int32_t b = first_byte; b <= last_byte; ++b) { - lut[50 + timing_group * 5 + phase] = 1; + const size_t index = row_offset + b; + const uint8_t mask = dirty_byte_mask(b, dirty); + const uint8_t new_side = new_msb[index]; + const uint8_t red = (new_side & (old_lsb[index] & old_msb[index])) + | (uint8_t(~new_side) + & (old_lsb[index] | old_msb[index])); + row[b] = (row[b] & uint8_t(~mask)) | (red & mask); } } + _bus->writeBytes(row, row_bytes, true, false); } - - // Groups 00 and 11 remain source-no-drive holds. Repeated 12-frame tests - // showed that reducing VCOM from -1.2 V to -0.4 V greatly accelerated - // whiteward drift in untouched black/dark pixels. Move in the opposite - // direction. Hardware validation over 60 consecutive dirty updates found - // the SSD1677 factory-fast value (-2.0 V) neutral for all four untouched - // gray levels, so both monochrome differential modes use it while the - // four-gray transition passes retain -1.2 V. - if (monochrome) { lut[109] = 0x50; } - const uint8_t dark_boundary = monochrome ? 0 : uint8_t(2 - step); - const uint8_t light_boundary = monochrome ? 2 : step; - lut[106] = vsh1[dark_boundary]; - // Fast gives black-to-white transitions the full twelfth frame and the - // calibrated -16 V VSL. Fastest retains the lower-dose -15 V setting. - lut[108] = stronger_mono ? 0x36 : vsl[light_boundary]; - _send_gray_lut(lut); - return _activate(CTRL1_NORMAL, 0x0C, false, true, - !shortened); } bool Panel_SSD1677_4Gray::_activate(uint8_t ctrl1, uint8_t ctrl2, @@ -938,7 +854,7 @@ namespace lgfx return true; } - bool Panel_SSD1677_4Gray::_reset_controller_for_mode2(void) + bool Panel_SSD1677_4Gray::_reset_controller_and_face(void) { _bus->writeCommand(0x12, 8); _send_msec = millis(); @@ -962,73 +878,51 @@ namespace lgfx return true; } - bool Panel_SSD1677_4Gray::_refresh_quality(const uint8_t* lsb, const uint8_t* msb) + bool Panel_SSD1677_4Gray::_ensure_known_face(void) + { + if (_mode2_face_known || _reset_controller_and_face()) { return true; } + _invalidate_gray_state(); + return false; + } + + bool Panel_SSD1677_4Gray::_refresh_mode1_absolute( + const uint8_t* lsb, const uint8_t* msb, const uint8_t* lut) { - // A sleep/power transition can invalidate the controller's Mode 2 face - // parity before the first visible refresh. Quality is an absolute, - // full-screen operation, so recover a deterministic RAM face here rather - // than silently dropping the user's first frame. - if (!_mode2_face_known && !_reset_controller_for_mode2()) + // Mode 1 needs a known RAM face to map the two middle gray levels. + if (!_ensure_known_face()) { - _invalidate_gray_state(); return false; } + // The ping-pong face exchanges the two middle codes in Mode 1. Swap the + // plane destinations on the odd face; 00 black and 11 white are symmetric. const auto full = _full_range(); const uint8_t* bw = _mode2_face_odd ? msb : lsb; const uint8_t* red = _mode2_face_odd ? lsb : msb; _send_plane(CMD_WRITE_RAM_BW, bw, full, true); _send_plane(CMD_WRITE_RAM_RED, red, full, true); - _send_gray_lut(lut_quality_oscillating); - if (!_activate(CTRL1_NORMAL, 0x07, true, false)) { return false; } + _send_gray_lut(lut); + // Wait on the BUSY line itself, without the 400 ms refresh-minimum floor. + if (!_activate(CTRL1_NORMAL, 0x07, true, false, false)) { return false; } _optical_state = optical_state_t::gray4; return true; } - bool Panel_SSD1677_4Gray::_refresh_text(const uint8_t* lsb, const uint8_t* msb) + bool Panel_SSD1677_4Gray::_refresh_mode2_absolute( + const uint8_t* lsb, const uint8_t* msb, const uint8_t* lut) { + if (!lut || !_ensure_known_face()) { return false; } + + // Mode 2 consumes the group bits written to 0x24/0x26 directly on every + // activation. Both RAMs are overwritten, so their command mapping stays + // fixed even though the controller advances its ping-pong face. Swapping + // these destinations on odd faces would exchange the two middle grays. const auto full = _full_range(); - // The Mode 2 ping-pong face exchanges the physical interpretation of the - // two middle codes. Match Quality's parity-aware plane order before the - // self-contained white-first waveform; 00 black and 11 white are - // symmetric, so a parity error otherwise appears only as dark/light swap. - const uint8_t* bw = _mode2_face_odd ? msb : lsb; - const uint8_t* red = _mode2_face_odd ? lsb : msb; - _send_plane(CMD_WRITE_RAM_BW, bw, full, true); - _send_plane(CMD_WRITE_RAM_RED, red, full, true); - uint8_t lut[sizeof(lut_factory_quality)] = {}; - auto set_vs = [&](uint8_t group, uint8_t phase, uint8_t code) - { - const size_t index = size_t(group) * 10 + (phase >> 2); - const uint8_t shift = 6 - 2 * (phase & 3); - lut[index] |= uint8_t(code << shift); - }; - auto set_tp = [&](uint8_t phase, uint8_t frames) - { - lut[50 + size_t(phase >> 2) * 5 + (phase & 3)] = frames; - }; - for (uint8_t phase = 0; phase < 3; ++phase) - { - for (uint8_t group = 0; group < 4; ++group) { set_vs(group, phase, 2); } - set_tp(phase, 8); - } - for (uint8_t phase = 3; phase < 12; ++phase) - { - set_vs(3, phase, 1); - if (phase <= 9) { set_vs(2, phase, 1); } - if (phase <= 4) { set_vs(1, phase, 1); } - set_tp(phase, phase == 10 ? 5 : phase == 11 ? 6 : 1); - } - set_vs(2, 10, 3); - for (uint8_t phase = 5; phase <= 9; ++phase) { set_vs(1, phase, 3); } - for (size_t i = 100; i < 105; ++i) { lut[i] = 0x22; } - lut[105] = 0x17; - lut[106] = 0x46; - lut[107] = 0xA8; - lut[108] = 0x36; - lut[109] = 0x30; + _send_plane(CMD_WRITE_RAM_BW, lsb, full, true); + _send_plane(CMD_WRITE_RAM_RED, msb, full, true); _send_gray_lut(lut); - if (!_activate(CTRL1_NORMAL, 0x07, true, false)) { return false; } + if (!_activate(CTRL1_NORMAL, 0x0C, false, true, false)) { return false; } + _optical_state = optical_state_t::gray4; return true; } @@ -1039,59 +933,36 @@ namespace lgfx const uint32_t row_bytes = ((_cfg.panel_width + 7) & ~7) >> 3; uint8_t* old_lsb = _displayed_buf; uint8_t* old_msb = _displayed_buf + _buf_x1_len; + const int32_t first_byte = dirty.left >> 3; + const int32_t last_byte = dirty.right >> 3; for (int32_t y = dirty.top; y <= dirty.bottom; ++y) { - for (int32_t x = dirty.left; x <= dirty.right; ++x) + const size_t row_offset = size_t(y) * row_bytes; + for (int32_t b = first_byte; b <= last_byte; ++b) { - const size_t index = size_t(y) * row_bytes + (x >> 3); - const uint8_t mask = uint8_t(0x80u >> (x & 7)); - if (msb[index] & mask) - { - old_lsb[index] |= mask; - old_msb[index] |= mask; - } - else - { - old_lsb[index] &= uint8_t(~mask); - old_msb[index] &= uint8_t(~mask); - } + const size_t index = row_offset + b; + const uint8_t mask = dirty_byte_mask(b, dirty); + const uint8_t mono = msb[index] & mask; + old_lsb[index] = (old_lsb[index] & uint8_t(~mask)) | mono; + old_msb[index] = (old_msb[index] & uint8_t(~mask)) | mono; } } } - bool Panel_SSD1677_4Gray::_refresh_fast(const uint8_t* lsb, const uint8_t* msb, - const range_rect_t& current_dirty) - { - if (!_displayed_valid) - { - return _refresh_quality(lsb, msb); - } - - // Fast retains the 12-frame activation and stable outside-area holds, and - // uses a stronger lightening dose than the eight-frame Fastest path to - // reduce black-to-white ghosting in the rewritten rectangle. - if (!_activate_transition_step(lsb, msb, current_dirty, 2, true, true, true)) - { - return false; - } - - _optical_state = optical_state_t::mono_synchronized; - _remember_mono_dirty(msb, current_dirty); - return true; - } - - bool Panel_SSD1677_4Gray::_refresh_fastest(const uint8_t* lsb, const uint8_t* msb, - const range_rect_t& current_dirty) + bool Panel_SSD1677_4Gray::_refresh_mode2_fastest( + const uint8_t* lsb, const uint8_t* msb, + const range_rect_t& current_dirty) { if (!_displayed_valid) { - return _refresh_quality(lsb, msb); + // Establish a complete optical/history baseline without leaving Mode 2. + return _refresh_mode2_absolute(lsb, msb, lut_fast); } + if (!_ensure_known_face()) { return false; } - if (!_activate_transition_step(lsb, msb, current_dirty, 2, true, true)) - { - return false; - } + _send_transition_planes(msb, current_dirty); + _send_gray_lut(lut_fastest); + if (!_activate(CTRL1_NORMAL, 0x0C, false, true, false)) { return false; } _optical_state = optical_state_t::mono_synchronized; _remember_mono_dirty(msb, current_dirty); @@ -1123,16 +994,16 @@ namespace lgfx switch (mode) { case epd_mode_t::epd_quality: - success = _refresh_quality(planeL, planeM); + success = _refresh_mode1_absolute(planeL, planeM, lut_quality); break; case epd_mode_t::epd_text: - success = _refresh_text(planeL, planeM); + success = _refresh_mode1_absolute(planeL, planeM, lut_text); break; case epd_mode_t::epd_fast: - success = _refresh_fast(planeL, planeM, current_dirty); + success = _refresh_mode2_absolute(planeL, planeM, lut_fast); break; case epd_mode_t::epd_fastest: - success = _refresh_fastest(planeL, planeM, current_dirty); + success = _refresh_mode2_fastest(planeL, planeM, current_dirty); break; default: success = false; @@ -1141,7 +1012,9 @@ namespace lgfx if (success) { - if (mode == epd_mode_t::epd_quality || mode == epd_mode_t::epd_text) + // Every absolute refresh (including Fastest's fallback) leaves the full + // 2-bit planes on glass, so record them as the differential baseline. + if (_optical_state == optical_state_t::gray4) { _remember_displayed(planeL, planeM); } diff --git a/src/lgfx/v1/panel/Panel_SSD1677.hpp b/src/lgfx/v1/panel/Panel_SSD1677.hpp index 14bb3e2b..5f25b775 100644 --- a/src/lgfx/v1/panel/Panel_SSD1677.hpp +++ b/src/lgfx/v1/panel/Panel_SSD1677.hpp @@ -29,7 +29,7 @@ namespace lgfx /* SSD1677 (GDEQ0426T82 / 800x480) E-Paper panel driver. - Buffer / coordinate model (EPD native, see docs/Panel_SSD1677_rebuild_plan.md): + Buffer / coordinate model (EPD native): - Panel native orientation = landscape 800(X=source) x 480(Y=gate). - 8 pixels per byte are packed along X (source) direction, matching the controller's source-shift scan order. byte = 8 consecutive X pixels. @@ -41,8 +41,9 @@ namespace lgfx - _draw_pixel stores an abstract gray level v(0=black..3=white) split into two bit planes: planeL = v&1 (-> BW RAM 0x24), planeM = v&2 (-> RED RAM 0x26). - The hardware RAM encoding is generated at send time per epd_mode: - quality/text : absolute four-gray LUT (planes sent as-is) - fast/fastest : monochrome differential LUT vs displayed history + quality/text : Mode 1 absolute four-gray update + fast : Mode 2 absolute four-gray update + fastest : Mode 2 monochrome differential update vs history */ struct Panel_SSD1677 : public Panel_HasBuffer { @@ -72,7 +73,7 @@ namespace lgfx protected: - // Longer busy ceiling for the 480x800 panel; full refresh ~3.8s. + // Minimum wait used by the base monochrome refresh path. static constexpr unsigned long _refresh_msec = 400; range_rect_t _range_old; @@ -100,9 +101,8 @@ namespace lgfx const uint8_t* getInitCommands(uint8_t listno) const override { - // SSD1677 power-up sequence (community-sdk EInkDisplay, non-X3). - // panel native: 800(X=source) x 480(Y=gate). 0x01 sets 480 gates. - // RAM window / auto-clear / power-on are issued in _after_wake (computed). + // SSD1677 power-up sequence for the native 800x480 panel. + // 0x01 configures 480 gates; RAM setup and power-on follow in _after_wake. static constexpr uint8_t list0[] = { 0x12, 0 + CMD_INIT_DELAY, 10, // SW Reset + 10 msec 0x18, 1, 0x80, // Temp sensor: internal @@ -122,6 +122,8 @@ namespace lgfx { ~Panel_SSD1677_4Gray(void) override; bool init(bool use_reset) override; + void setSleep(bool flg) override; + void setPowerSave(bool flg) override; void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override; protected: @@ -143,22 +145,19 @@ namespace lgfx range_rect_t _full_range(void) const; void _send_gray_lut(const uint8_t* lut); void _remember_displayed(const uint8_t* lsb, const uint8_t* msb); - void _send_transition_plane(uint8_t command, - const uint8_t* new_lsb, const uint8_t* new_msb, - const range_rect_t& dirty, uint8_t step, - bool monochrome); - bool _activate_transition_step(const uint8_t* new_lsb, const uint8_t* new_msb, - const range_rect_t& dirty, uint8_t step, - bool monochrome, bool shortened = false, - bool stronger_mono = false); + void _send_transition_planes(const uint8_t* new_msb, + const range_rect_t& dirty); void _remember_mono_dirty(const uint8_t* msb, const range_rect_t& dirty); bool _activate(uint8_t ctrl1, uint8_t ctrl2, bool powers_down, bool mode2_activation, bool enforce_refresh_minimum = true); - bool _reset_controller_for_mode2(void); - bool _refresh_quality(const uint8_t* lsb, const uint8_t* msb); - bool _refresh_text(const uint8_t* lsb, const uint8_t* msb); - bool _refresh_fast(const uint8_t* lsb, const uint8_t* msb, const range_rect_t& dirty); - bool _refresh_fastest(const uint8_t* lsb, const uint8_t* msb, const range_rect_t& dirty); + bool _reset_controller_and_face(void); + bool _ensure_known_face(void); + bool _refresh_mode1_absolute(const uint8_t* lsb, const uint8_t* msb, + const uint8_t* lut); + bool _refresh_mode2_absolute(const uint8_t* lsb, const uint8_t* msb, + const uint8_t* lut); + bool _refresh_mode2_fastest(const uint8_t* lsb, const uint8_t* msb, + const range_rect_t& dirty); }; //---------------------------------------------------------------------------- diff --git a/src/lgfx/v1/panel/Panel_TM1680.cpp b/src/lgfx/v1/panel/Panel_TM1680.cpp new file mode 100644 index 00000000..04f4dda9 --- /dev/null +++ b/src/lgfx/v1/panel/Panel_TM1680.cpp @@ -0,0 +1,142 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +#include "Panel_TM1680.hpp" +#include "../Bus.hpp" +#include "../platforms/common.hpp" + +#include + +namespace lgfx +{ + inline namespace v1 + { +//---------------------------------------------------------------------------- + + bool Panel_TM1680::init(bool use_reset) + { + // The TM1680 does not respond to I2C reads, so the read probe of + // Panel_1bitOLED::init cannot be used; send the init commands without probing. + if (!Panel_HasBuffer::init(use_reset)) + { + return false; + } + // Panel_HasBuffer::init does not clear the allocated buffer; + // clear it here so setInvert below does not push garbage to the LEDs. + memset(_buf, 0, _get_buffer_length()); + + startWrite(true); + for (size_t i = 0; auto cmds = getInitCommands(i); i++) + { + size_t idx = 0; + while (cmds[idx] != 0xFF || cmds[idx + 1] != 0xFF) ++idx; + if (idx) { _bus->writeBytes(cmds, idx, false, true); } + } + setInvert(_invert); + setRotation(_rotation); + endWrite(); + + return true; + } + + void Panel_TM1680::setInvert(bool invert) + { + _invert = invert; + // The TM1680 has no display-invert command; invert while packing in display(). + if (_buf) + { + startWrite(); + display(0, 0, _cfg.panel_width, _cfg.panel_height); + endWrite(); + } + } + + void Panel_TM1680::setSleep(bool flg) + { + startWrite(); + if (flg) + { + _bus->writeCommand(CMD_LED_OFF, 8); + _bus->writeCommand(CMD_SYS_DIS, 8); + } + else + { + _bus->writeCommand(CMD_SYS_EN, 8); + _bus->writeCommand(CMD_LED_ON, 8); + } + endWrite(); + } + + void Panel_TM1680::setBrightness(uint8_t brightness) + { + startWrite(); + if (brightness) + { + _bus->writeCommand(CMD_PWM_DUTY + (((brightness * 16) >> 8) & 0x0F), 8); + _bus->writeCommand(CMD_LED_ON, 8); + } + else + { + _bus->writeCommand(CMD_LED_OFF, 8); + } + endWrite(); + } + + void Panel_TM1680::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) + { + if (0 < w && 0 < h) + { + uint_fast16_t xs = x, xe = x + w - 1; + uint_fast16_t ys = y, ye = y + h - 1; + _update_transferred_rect(xs, ys, xe, ye); + } + if (_range_mod.empty()) { return; } + + // The whole frame is only 32 bytes; always send it in full. + // The leading byte is the RAM write address. + uint8_t sendbuf[1 + ((16 * 16) >> 3)]; + sendbuf[0] = 0x00; + bool invert = _invert ^ _cfg.invert; + + for (uint_fast8_t py = 0; py < 16; ++py) + { + uint32_t mask = 1 << (py & 7); + auto src = &_buf[(py >> 3) * 16]; + for (uint_fast8_t xb = 0; xb < 2; ++xb) + { + uint_fast8_t v = 0; + for (uint_fast8_t bit = 0; bit < 8; ++bit) + { + if (src[(xb << 3) + bit] & mask) + { // in TM1680 RAM, rows A3-A0 occupy the upper nibble half + v |= 1 << ((bit > 3) ? (bit & 3) : (4 + bit)); + } + } + sendbuf[1 + py * 2 + xb] = invert ? (v ^ 0xFF) : v; + } + } + + _bus->writeBytes(sendbuf, sizeof(sendbuf), true, true); + _range_mod.top = INT16_MAX; + _range_mod.left = INT16_MAX; + _range_mod.right = 0; + _range_mod.bottom = 0; + } + +//---------------------------------------------------------------------------- + } +} diff --git a/src/lgfx/v1/panel/Panel_TM1680.hpp b/src/lgfx/v1/panel/Panel_TM1680.hpp new file mode 100644 index 00000000..d07dbdb8 --- /dev/null +++ b/src/lgfx/v1/panel/Panel_TM1680.hpp @@ -0,0 +1,76 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +#pragma once + +#include "Panel_SSD1306.hpp" + +namespace lgfx +{ + inline namespace v1 + { +//---------------------------------------------------------------------------- + + // Titan Micro TM1680 LED matrix driver (I2C, 24ROW x 16COM mode). + // The frame buffer keeps the Panel_1bitOLED page layout and is converted + // to the TM1680 RAM layout when pushed by display(). + // The protocol has no command/data prefix byte: configure Bus_I2C with prefix_len = 0. + struct Panel_TM1680 : public Panel_1bitOLED + { + Panel_TM1680(void) + { + _cfg.memory_width = _cfg.panel_width = 16; + _cfg.memory_height = _cfg.panel_height = 16; + _auto_display = true; + } + + bool init(bool use_reset) override; + void setInvert(bool invert) override; + void setSleep(bool flg) override; + void setBrightness(uint8_t brightness) override; + void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override; + + protected: + static constexpr uint8_t CMD_SYS_DIS = 0x80; + static constexpr uint8_t CMD_SYS_EN = 0x81; + static constexpr uint8_t CMD_LED_OFF = 0x82; + static constexpr uint8_t CMD_LED_ON = 0x83; + static constexpr uint8_t CMD_RC_MASTER = 0x98; // internal RC oscillator, master mode + static constexpr uint8_t CMD_COM_16NMOS = 0xA4; // 24ROW x 16COM (NMOS) + static constexpr uint8_t CMD_PWM_DUTY = 0xB0; // +0..15 = duty 1/16..16/16 + + const uint8_t* getInitCommands(uint8_t listno) const override + { + static constexpr uint8_t list0[] = { + CMD_SYS_DIS, + CMD_COM_16NMOS, + CMD_RC_MASTER, + CMD_SYS_EN, + CMD_PWM_DUTY + 1, + CMD_LED_ON, + 0xFF, 0xFF, // end + }; + switch (listno) { + case 0: return list0; + default: return nullptr; + } + } + }; + +//---------------------------------------------------------------------------- + } +} diff --git a/src/lgfx/v1/platforms/esp32/Bus_I2C.cpp b/src/lgfx/v1/platforms/esp32/Bus_I2C.cpp index d079d6e4..118fea57 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_I2C.cpp +++ b/src/lgfx/v1/platforms/esp32/Bus_I2C.cpp @@ -21,35 +21,12 @@ Original Source: #include "Bus_I2C.hpp" #include "../../misc/pixelcopy.hpp" -#include -#include - namespace lgfx { inline namespace v1 { //---------------------------------------------------------------------------- - // status_reg メンバーの存在を検出 - template - struct has_status_reg : std::false_type {}; - - template - struct has_status_reg().status_reg))> : std::true_type {}; - - // bus_busy 取得 - template - static inline typename std::enable_if::value, bool>::type - i2c_dev_bus_busy(const T& dev) { - return dev.status_reg.bus_busy; - } - - template - static inline typename std::enable_if::value, bool>::type - i2c_dev_bus_busy(const T& dev) { - return dev.sr.bus_busy; - } - void Bus_I2C::config(const config_t& config) { _cfg = config; @@ -119,24 +96,12 @@ namespace lgfx void Bus_I2C::wait(void) { -#if I2C_NUM_MAX > 1 - auto dev = (_cfg.i2c_port == 0) ? &I2C0 : &I2C1; -#else - auto dev = &I2C0; -#endif - - while (i2c_dev_bus_busy(*dev)) { taskYIELD(); } + lgfx::i2c::wait(_cfg.i2c_port); } bool Bus_I2C::busy(void) const { -#if I2C_NUM_MAX > 1 - auto dev = (_cfg.i2c_port == 0) ? &I2C0 : &I2C1; -#else - auto dev = &I2C0; -#endif - - return i2c_dev_bus_busy(*dev); + return lgfx::i2c::busy(_cfg.i2c_port); } void Bus_I2C::dc_control(bool dc) diff --git a/src/lgfx/v1/platforms/esp32/Bus_I2C.hpp b/src/lgfx/v1/platforms/esp32/Bus_I2C.hpp index 21a52441..fa4330d2 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_I2C.hpp +++ b/src/lgfx/v1/platforms/esp32/Bus_I2C.hpp @@ -35,7 +35,7 @@ namespace lgfx uint32_t freq_read = 400000; int16_t pin_scl = 22; int16_t pin_sda = 21; - uint8_t i2c_port = 0; // e.g. ESP32 0=I2C_NUM_0 / 1=I2C_NUM_1 + int8_t i2c_port = 0; // e.g. ESP32 0=I2C_NUM_0 / 1=I2C_NUM_1 / negative=software uint8_t i2c_addr = 0x3C; uint32_t prefix_cmd = 0x00; uint32_t prefix_data = 0x40; diff --git a/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp b/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp index 673686b6..aa2d0c9e 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp +++ b/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp @@ -103,6 +103,12 @@ Original Source: #define DMA_OUTLINK_START_CH0 AHB_DMA_OUTLINK_START_CH0 #define DMA_OUTFIFO_EMPTY_CH0 AHB_DMA_OUTFIFO_EMPTY_CH0 #define SIZE_OF_DMA_OUT_CH (sizeof(AHB_DMA.channel[0])) + // AHB_DMA 世代のうち C5/C61 は OUT_LINK レジスタが制御ビットのみになり、 + // ディスクリプタアドレスは別レジスタ (チャンネルごとに 4 バイト刻み) へ書く; + // (同じ AHB_DMA でも H4 はアドレスレジスタをチャンネルブロック内に持つため対象外) + #if defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + #define DMA_OUT_LINK_ADDR_CH0_REG AHB_DMA_OUT_LINK_ADDR_CH0_REG + #endif #else #if __has_include() #if !defined DMA_OUT_LINK_CH0_REG @@ -224,6 +230,8 @@ namespace lgfx _spi_dma_outstatus_reg = reg(DMA_OUTFIFO_STATUS_CH0_REG + assigned_dma_ch * SIZE_OF_DMA_OUT_CH); #if defined ( CONFIG_IDF_TARGET_ESP32P4 ) _spi_dma_out_link2_reg = reg(AXI_DMA_OUT_LINK2_CH0_REG + assigned_dma_ch * SIZE_OF_DMA_OUT_CH); + #elif defined ( DMA_OUT_LINK_ADDR_CH0_REG ) + _spi_dma_out_link2_reg = reg(DMA_OUT_LINK_ADDR_CH0_REG + assigned_dma_ch * sizeof(uint32_t)); #endif } #elif defined ( CONFIG_IDF_TARGET_ESP32 ) || !defined ( CONFIG_IDF_TARGET ) @@ -712,7 +720,7 @@ namespace lgfx if (use_dma) { auto spi_dma_out_link_reg = _spi_dma_out_link_reg; - #if defined ( CONFIG_IDF_TARGET_ESP32P4 ) + #if defined ( CONFIG_IDF_TARGET_ESP32P4 ) || defined ( DMA_OUT_LINK_ADDR_CH0_REG ) auto spi_dma_out_link2_reg = _spi_dma_out_link2_reg; #endif auto cmd = _spi_cmd_reg; @@ -728,7 +736,7 @@ namespace lgfx auto dma = reg(SPI_DMA_CONF_REG(_spi_port)); *dma = 0; /// Clear previous transfer uint32_t len = ((length - 1) & ((SPI_MS_DATA_BITLEN)>>3)) + 1; - #if defined ( CONFIG_IDF_TARGET_ESP32P4 ) + #if defined ( CONFIG_IDF_TARGET_ESP32P4 ) || defined ( DMA_OUT_LINK_ADDR_CH0_REG ) *spi_dma_out_link2_reg = ((uint32_t)(_dmadesc)); *spi_dma_out_link_reg = DMA_OUTLINK_START_CH0 ; #else @@ -946,7 +954,7 @@ namespace lgfx *_spi_dma_out_link_reg = 0; #if defined ( SOC_GDMA_SUPPORTED ) && defined ( DMA_OUTLINK_START_CH0 ) - #if defined ( CONFIG_IDF_TARGET_ESP32P4 ) + #if defined ( CONFIG_IDF_TARGET_ESP32P4 ) || defined ( DMA_OUT_LINK_ADDR_CH0_REG ) *_spi_dma_out_link2_reg = ((uint32_t)(_dmadesc)); *_spi_dma_out_link_reg = DMA_OUTLINK_START_CH0; #else diff --git a/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp b/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp index e0c75598..996c8260 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp +++ b/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp @@ -193,7 +193,8 @@ namespace lgfx volatile uint32_t* _spi_cmd_reg = nullptr; volatile uint32_t* _spi_user_reg = nullptr; volatile uint32_t* _spi_dma_out_link_reg = nullptr; - #if defined (CONFIG_IDF_TARGET_ESP32P4) + // P4 (AXI_DMA) と C5/C61 (AHB_DMA) はディスクリプタアドレスを OUT_LINK と別のレジスタへ書く; + #if defined (CONFIG_IDF_TARGET_ESP32P4) || defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) volatile uint32_t* _spi_dma_out_link2_reg = nullptr; #endif volatile uint32_t* _spi_dma_outstatus_reg = nullptr; diff --git a/src/lgfx/v1/platforms/esp32/Light_PWM.cpp b/src/lgfx/v1/platforms/esp32/Light_PWM.cpp index f70bf148..2286881c 100644 --- a/src/lgfx/v1/platforms/esp32/Light_PWM.cpp +++ b/src/lgfx/v1/platforms/esp32/Light_PWM.cpp @@ -19,6 +19,9 @@ Original Source: #include #include "Light_PWM.hpp" +#include "common.hpp" + +#include #if defined ( ARDUINO ) #include @@ -26,10 +29,33 @@ Original Source: #include #endif #else - #include #include #endif +#if defined ( ARDUINO ) + + #if defined ESP_ARDUINO_VERSION // use ledc* functions shipped with arduino-esp32 core + #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) + #define LGFX_LEDCINIT(pin_bl, freq, bits, pwm_channel) ledcAttach(pin_bl, freq, bits) + #define LGFX_LEDCWRITE(pin_bl, pwm_channel, duty) ledcWrite(pin_bl, duty) + #endif + #endif + #if !defined LGFX_LEDCINIT // pre-3.x cores, including 1.x where ESP_ARDUINO_VERSION is absent + #define LGFX_LEDCINIT(pin_bl, freq, bits, pwm_channel) ( ledcSetup(pwm_channel, freq, bits) > 0 && (ledcAttachPin(pin_bl, pwm_channel), true) ) + #define LGFX_LEDCWRITE(pin_bl, pwm_channel, duty) ledcWrite(pwm_channel, duty) + #endif + +#else // esp-idf + + #if SOC_LEDC_SUPPORT_HS_MODE + #define LGFX_LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE + #else + #define LGFX_LEDC_SPEED_MODE LEDC_LOW_SPEED_MODE + #endif + +#endif + + namespace lgfx { inline namespace v1 @@ -41,62 +67,53 @@ namespace lgfx bool Light_PWM::init(uint8_t brightness) { + if ((size_t)_cfg.pin_bl >= GPIO_NUM_MAX || !GPIO_IS_VALID_OUTPUT_GPIO((gpio_num_t)_cfg.pin_bl)) + { + return false; + } + + // The LEDC driver does not fully normalize the pad state: the open-drain + // flag is never cleared, and ESP-IDF v6 no longer selects the GPIO + // function in IO_MUX. Reconfigure the pin as a push-pull GPIO output + // first, latching it to the "off" level to avoid a visible glitch. + if (_cfg.invert) { gpio_hi(_cfg.pin_bl); } else { gpio_lo(_cfg.pin_bl); } + lgfx::pinMode(_cfg.pin_bl, pin_mode_t::output); #if defined ( ARDUINO ) -#if defined ESP_ARDUINO_VERSION - #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) - #define LEDC_USE_IDF_V5 // esp32-arduino core 3.x.x uses the new ledC syntax - #endif - #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(6, 0, 0) - #define LEDC_USE_IDF_V6 // esp32-arduino core 6.x.x - #endif -#endif + bool result = LGFX_LEDCINIT(_cfg.pin_bl, _cfg.freq, PWM_BITS, _cfg.pwm_channel); -#if defined LEDC_USE_IDF_V5 - ledcAttach(_cfg.pin_bl, _cfg.freq, PWM_BITS); - setBrightness(brightness); -#else - ledcSetup(_cfg.pwm_channel, _cfg.freq, PWM_BITS); - ledcAttachPin(_cfg.pin_bl, _cfg.pwm_channel); - setBrightness(brightness); -#endif -#else +#else // esp-idf static ledc_channel_config_t ledc_channel; { ledc_channel.gpio_num = (gpio_num_t)_cfg.pin_bl; -#if SOC_LEDC_SUPPORT_HS_MODE - ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE; -#else - ledc_channel.speed_mode = LEDC_LOW_SPEED_MODE; -#endif + ledc_channel.speed_mode = LGFX_LEDC_SPEED_MODE; ledc_channel.channel = (ledc_channel_t)_cfg.pwm_channel; -#if !defined LEDC_USE_IDF_V6 // ledc_channel_config_t.intr_type is deprecated, no need to explicitly configure interrupt, handled in the driver - ledc_channel.intr_type = LEDC_INTR_DISABLE; -#endif ledc_channel.timer_sel = (ledc_timer_t)((_cfg.pwm_channel >> 1) & 3); ledc_channel.duty = _cfg.invert ? (1 << PWM_BITS) : 0; ledc_channel.hpoint = 0; + #if defined ESP_IDF_VERSION_VAL + // when ledc_channel_config_t.intr_type is deprecated, no need to explicitly configure interrupt, handled in the driver + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0) + ledc_channel.intr_type = LEDC_INTR_DISABLE; + #endif + #endif }; - ledc_channel_config(&ledc_channel); + bool result = (ESP_OK == ledc_channel_config(&ledc_channel)); static ledc_timer_config_t ledc_timer; { -#if SOC_LEDC_SUPPORT_HS_MODE - ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE; // timer mode -#else - ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE; -#endif + ledc_timer.speed_mode = LGFX_LEDC_SPEED_MODE; // timer mode ledc_timer.duty_resolution = (ledc_timer_bit_t)PWM_BITS; // resolution of PWM duty ledc_timer.freq_hz = _cfg.freq; // frequency of PWM signal ledc_timer.timer_num = ledc_channel.timer_sel; // timer index }; - ledc_timer_config(&ledc_timer); - - setBrightness(brightness); + result = (ESP_OK == ledc_timer_config(&ledc_timer)) && result; #endif + if (!result) { return false; } + setBrightness(brightness); return true; } @@ -115,17 +132,14 @@ namespace lgfx if (_cfg.invert) duty = (1 << PWM_BITS) - duty; #if defined ( ARDUINO ) -#if defined LEDC_USE_IDF_V5 - ledcWrite(_cfg.pin_bl, duty); -#else - ledcWrite(_cfg.pwm_channel, duty); -#endif -#elif SOC_LEDC_SUPPORT_HS_MODE - ledc_set_duty(LEDC_HIGH_SPEED_MODE, (ledc_channel_t)_cfg.pwm_channel, duty); - ledc_update_duty(LEDC_HIGH_SPEED_MODE, (ledc_channel_t)_cfg.pwm_channel); -#else - ledc_set_duty(LEDC_LOW_SPEED_MODE, (ledc_channel_t)_cfg.pwm_channel, duty); - ledc_update_duty(LEDC_LOW_SPEED_MODE, (ledc_channel_t)_cfg.pwm_channel); + + LGFX_LEDCWRITE(_cfg.pin_bl, _cfg.pwm_channel, duty); + +#else // esp-idf + + ledc_set_duty(LGFX_LEDC_SPEED_MODE, (ledc_channel_t)_cfg.pwm_channel, duty); + ledc_update_duty(LGFX_LEDC_SPEED_MODE, (ledc_channel_t)_cfg.pwm_channel); + #endif } diff --git a/src/lgfx/v1/platforms/esp32/common.cpp b/src/lgfx/v1/platforms/esp32/common.cpp index 376d412c..8a944f6c 100644 --- a/src/lgfx/v1/platforms/esp32/common.cpp +++ b/src/lgfx/v1/platforms/esp32/common.cpp @@ -84,14 +84,32 @@ Original Source: #include #endif -#ifndef SOC_GPIO_SUPPORT_RTC_INDEPENDENT -#define SOC_GPIO_SUPPORT_RTC_INDEPENDENT 0 +// Whether the digital pads (pulls in IO_MUX) work independently of the RTC IO +// domain; only the plain ESP32 needs the RTC-domain path for RTC-capable pins. +// ESP-IDF v6 removed the SOC_GPIO_SUPPORT_RTC_INDEPENDENT macro, so derive +// the value from the build target when it is absent. +#if defined (SOC_GPIO_SUPPORT_RTC_INDEPENDENT) + #define LGFX_GPIO_RTC_INDEPENDENT SOC_GPIO_SUPPORT_RTC_INDEPENDENT +#elif defined (CONFIG_IDF_TARGET) && !defined (CONFIG_IDF_TARGET_ESP32) + #define LGFX_GPIO_RTC_INDEPENDENT 1 +#else + #define LGFX_GPIO_RTC_INDEPENDENT 0 #endif #if __has_include() #include #endif +#include + +#if __has_include() + #include +#endif + +#if __has_include() + #include +#endif + #if defined (ESP_IDF_VERSION_VAL) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) #include @@ -230,29 +248,6 @@ namespace lgfx static inline typename std::enable_if::value && !has_func_in_sel::value, uint32_t>::type get_gpio_func_in_sel(T& cfg) { return cfg.in_sel; } - // ------- func_out_sel detect ------- - // func_sel/funcn_out_sel/out_sel: A type characteristic that detects the presence of members - template - struct has_funcn_out_sel : std::false_type {}; - - template - struct has_funcn_out_sel().funcn_out_sel))> : std::true_type {}; - - // func_sel exist - template - static inline typename std::enable_if::value>::type - set_gpio_func_out_sel(T& cfg, uint32_t val) { cfg.func_sel = val; } - - // func_sel does not exist, funcn_out_sel exist - template - static inline typename std::enable_if::value && has_funcn_out_sel::value>::type - set_gpio_func_out_sel(T& cfg, uint32_t val) { cfg.funcn_out_sel = val; } - - // func_sel && funcn_out_sel does not exist (use out_sel) - template - static inline typename std::enable_if::value && !has_funcn_out_sel::value>::type - set_gpio_func_out_sel(T& cfg, uint32_t val) { cfg.out_sel = val; } - static int search_pin_number(int peripheral_sig) { uint32_t result = get_gpio_func_in_sel(GPIO.func_in_sel_cfg[peripheral_sig]); @@ -429,7 +424,7 @@ namespace lgfx auto io_mux_val = *io_mux_reg; // & ~(FUN_PU_M | FUN_PD_M | SLP_PU_M | SLP_PD_M | MCU_SEL_M); #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED - if (!SOC_GPIO_SUPPORT_RTC_INDEPENDENT && rtc_gpio_is_valid_gpio(gpio_num)) { + if (!LGFX_GPIO_RTC_INDEPENDENT && rtc_gpio_is_valid_gpio(gpio_num)) { rtc_gpio_deinit(gpio_num); if (mode == pin_mode_t::input_pulldown) { rtc_gpio_pulldown_en((gpio_num_t)pin); } @@ -456,9 +451,11 @@ namespace lgfx *io_mux_reg = io_mux_val; -#if defined(CONFIG_IDF_TARGET_ESP32C61) - GPIO.pinn[pin].pinn_pad_driver = (mode == pin_mode_t::output) ? 0 : 1; // 1 = OpenDrain / 0 = normal output -#else + // レジスタ構造体のメンバ名はチップ毎に異なるため、命名差を吸収する LL/ROM API へ委譲する +#if __has_include() + if (mode == pin_mode_t::output) { gpio_ll_od_disable(&GPIO, (gpio_num_t)pin); } + else { gpio_ll_od_enable (&GPIO, (gpio_num_t)pin); } // OpenDrain +#else // ESP-IDF v3 系 (無印 ESP32 のみ) GPIO.pin[pin].pad_driver = (mode == pin_mode_t::output) ? 0 : 1; // 1 = OpenDrain / 0 = normal output #endif if (mode != pin_mode_t::output) { @@ -466,10 +463,10 @@ namespace lgfx } auto gpio_en_reg = gpio_en_regs[((pin >> 5) << 1) + 1]; *gpio_en_reg = 1u << (pin & 31); -#if defined(CONFIG_IDF_TARGET_ESP32C61) - set_gpio_func_out_sel(GPIO.funcn_out_sel_cfg[pin], SIG_GPIO_OUT_IDX); -#else - set_gpio_func_out_sel(GPIO.func_out_sel_cfg[pin], SIG_GPIO_OUT_IDX); +#if __has_include() + esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); +#else // ESP-IDF v3 系 (無印 ESP32 のみ) + GPIO.func_out_sel_cfg[pin].func_sel = SIG_GPIO_OUT_IDX; #endif } @@ -630,6 +627,33 @@ namespace lgfx #endif static spi_device_handle_t _spi_dev_handle[spi_periph_num] = {nullptr}; +// ------------------------------------------------------------------------ +// Software SPI ( soft_spi.inl ), reached through the negative host numbers. + +#define LGFX_INTERNAL_SOFT_SPI +#include "../soft_spi.inl" + +// ------------------------------------------------------------------------ + + // Clear stale open-drain state on the bus pins. + // spi_bus_initialize does not clear the pad open-drain flag (ESP-IDF v6 + // no longer configures it at all), so a pin left in open-drain mode by a + // previous use may not rise fast enough at typical SPI clock rates. + static void clear_open_drain(std::initializer_list pins) + { + for (int pin : pins) + { + if ((size_t)pin < GPIO_NUM_MAX) + { +#if __has_include() + gpio_ll_od_disable(&GPIO, (gpio_num_t)pin); +#else // ESP-IDF v3 (plain ESP32 only) + GPIO.pin[pin].pad_driver = 0; +#endif + } + } + } + cpp::result init(int spi_host, int spi_sclk, int spi_miso, int spi_mosi) { return init(spi_host, spi_sclk, spi_miso, spi_mosi, 0); // SPI_DMA_CH_AUTO; @@ -638,6 +662,11 @@ namespace lgfx cpp::result init(int spi_host, int spi_sclk, int spi_miso, int spi_mosi, int dma_channel) { //ESP_LOGI("LGFX","spi::init host:%d, sclk:%d, miso:%d, mosi:%d, dma:%d", spi_host, spi_sclk, spi_miso, spi_mosi, dma_channel); + if (spi_host < 0) + { + return soft_spi_init(spi_host, spi_sclk, spi_miso, spi_mosi); + } + uint32_t spi_port = (spi_host + 1); (void)spi_port; @@ -700,6 +729,8 @@ namespace lgfx } } + clear_open_drain({ spi_sclk, spi_mosi, spi_miso }); + writereg(SPI_USER_REG(spi_port), SPI_USR_MOSI | SPI_USR_MISO | SPI_DOUTDIN); // need SD card access (full duplex setting) writereg(SPI_CTRL_REG(spi_port), 0); #if defined ( SPI_CTRL1_REG ) @@ -725,6 +756,7 @@ namespace lgfx cpp::result initQuad(int spi_host, int spi_sclk, int spi_io0, int spi_io1, int spi_io2, int spi_io3, int dma_channel) { //ESP_LOGI("LGFX","spi::init host:%d, sclk:%d, miso:%d, mosi:%d, dma:%d", spi_host, spi_sclk, spi_miso, spi_mosi, dma_channel); + if (spi_host < 0) { return cpp::fail(error_t::invalid_arg); } // the software hosts are single bit only uint32_t spi_port = (spi_host + 1); (void)spi_port; @@ -780,6 +812,8 @@ namespace lgfx #pragma GCC diagnostic pop + clear_open_drain({ spi_sclk, spi_io0, spi_io1, spi_io2, spi_io3 }); + writereg(SPI_USER_REG(spi_port), SPI_USR_MOSI | SPI_USR_MISO | SPI_DOUTDIN); // need SD card access (full duplex setting) writereg(SPI_CTRL_REG(spi_port), 0); #if defined ( SPI_CTRL1_REG ) @@ -800,6 +834,11 @@ namespace lgfx void release(int spi_host) { //ESP_LOGI("LGFX","spi::release"); + if (spi_host < 0) + { + soft_spi_release(spi_host); + return; + } #if defined (ARDUINO) && __has_include () // Arduino ESP32 if (_spi_handle[spi_host] != nullptr) { @@ -824,6 +863,11 @@ namespace lgfx void beginTransaction(int spi_host) { + if (spi_host < 0) + { + soft_spi_beginTransaction(spi_host); + return; + } #if defined (ARDUINO) // Arduino ESP32 spiSimpleTransaction(_spi_handle[spi_host]); #else // ESP-IDF @@ -840,6 +884,11 @@ namespace lgfx void beginTransaction(int spi_host, uint32_t freq, int spi_mode) { + if (spi_host < 0) + { + soft_spi_beginTransaction(spi_host, freq, spi_mode); + return; + } uint32_t spi_port = (spi_host + 1); (void)spi_port; uint32_t clkdiv = FreqToClockDiv(getApbFrequency(), freq); @@ -885,6 +934,7 @@ namespace lgfx void endTransaction(int spi_host) { + if (spi_host < 0) { return; } // a software host holds nothing to release if (_spi_dev_handle[spi_host]) { #if defined (ARDUINO) // Arduino ESP32 spiEndTransaction(_spi_handle[spi_host]); @@ -902,6 +952,11 @@ namespace lgfx void writeBytes(int spi_host, const uint8_t* data, size_t len) { + if (spi_host < 0) + { + soft_spi_writeBytes(spi_host, data, len); + return; + } uint32_t spi_port = (spi_host + 1); (void)spi_port; if (len > 64) len = 64; @@ -913,6 +968,11 @@ namespace lgfx void readBytes(int spi_host, uint8_t* data, size_t len) { + if (spi_host < 0) + { + soft_spi_readBytes(spi_host, data, len); + return; + } uint32_t spi_port = (spi_host + 1); (void)spi_port; if (len > 64) len = 64; @@ -938,6 +998,110 @@ namespace lgfx #define I2C_ACK_ERR_INT_RAW_M I2C_NACK_INT_RAW_M #endif +// From the ESP32-C6 on, some chips carry a low power I2C in addition to the normal ones, +// and SOC_I2C_NUM counts both of them. SOC_HP_I2C_NUM / SOC_LP_I2C_NUM tell them apart, +// but they only exist in recent ESP-IDF, so the chips with a single high power port have +// to be named when they are missing. +#if defined ( SOC_HP_I2C_NUM ) + #define LGFX_HP_I2C_NUM SOC_HP_I2C_NUM +#elif defined ( CONFIG_IDF_TARGET_ESP32C2 ) || defined ( CONFIG_IDF_TARGET_ESP32C5 ) || defined ( CONFIG_IDF_TARGET_ESP32C6 ) || defined ( CONFIG_IDF_TARGET_ESP32C61 ) + #define LGFX_HP_I2C_NUM 1 +#else + #define LGFX_HP_I2C_NUM SOC_I2C_NUM +#endif + +// ESP-IDF numbers the low power ports after all the high power ones, so the first low +// power port index is the number of high power ports. ( see i2c_port_t in hal/i2c_types.h ) +// +// Bringing a low power port up is left to the ESP-IDF driver: there is no TwoWire for it, +// and the pads reach the peripheral differently depending on the chip ( a fixed LP IO MUX +// function on some, the LP GPIO matrix on others ). The driver already knows which, so it +// is asked to open the bus and the registers are taken over afterwards, exactly as this +// code does for the normal ports. +#if defined ( SOC_LP_I2C_NUM ) && ( SOC_LP_I2C_NUM > 0 ) && __has_include ( ) \ + && defined ( ESP_IDF_VERSION_VAL ) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) + #define LGFX_LP_I2C_NUM SOC_LP_I2C_NUM + #define LGFX_LP_I2C_PORT LGFX_HP_I2C_NUM + // The default source is the RC oscillator, which drifts with temperature. XTAL_D2 is a + // divided crystal, so the SCL frequency comes out as asked. + #define LGFX_LP_I2C_SCLK LP_I2C_SCLK_XTAL_D2 + #include +#else + #define LGFX_LP_I2C_NUM 0 +#endif + +// The ports this implementation can drive: the high power ones, plus the low power ones +// when they are supported. This is the bound the port arguments are checked against. +// I2C_NUM_MAX cannot serve there: on an ESP-IDF recent enough to count the low power +// port but too old for the support above, a low power port number would pass the check +// and getDev() would fold it onto a high power port, silently driving that one instead. +// ( SOC_*_NUM carry unsigned suffixes, so the sum is brought back to int for the +// comparisons against a port number. ) +#define LGFX_I2C_PORT_NUM ( (int)( LGFX_HP_I2C_NUM + LGFX_LP_I2C_NUM ) ) + + /// True if this port index belongs to the low power I2C. + static inline bool isLpPort(int i2c_port) + { +#if LGFX_LP_I2C_NUM > 0 + return i2c_port >= LGFX_LP_I2C_PORT; +#else + (void)i2c_port; + return false; +#endif + } + + /// Hardware FIFO depth of this port. The low power I2C has a shallower FIFO than the + /// normal one, so this cannot be a single constant for the whole chip. + static inline uint32_t getFifoLen(int i2c_port) + { +#if LGFX_LP_I2C_NUM > 0 && defined ( SOC_LP_I2C_FIFO_LEN ) + if (isLpPort(i2c_port)) { return SOC_LP_I2C_FIFO_LEN; } +#else + (void)i2c_port; +#endif +#if defined ( SOC_I2C_FIFO_LEN ) + return SOC_I2C_FIFO_LEN; +#else + return 32; +#endif + } + + /// Clock feeding the SCL divider of this port [Hz]. + /// This is not a property of the chip alone: the low power I2C runs from its own + /// clock, and on the older chips the normal one follows the CPU frequency. + static inline uint32_t getSourceClock(int i2c_port) + { +#if LGFX_LP_I2C_NUM > 0 + if (isLpPort(i2c_port)) + { // Ask the clock tree rather than assuming a figure: which source the low power + // port runs from is a choice made when the bus is opened, and the two are not + // the same clock even where they happen to share a nominal frequency. + uint32_t hz = 0; + if (ESP_OK == esp_clk_tree_src_get_freq_hz((soc_module_clk_t)LGFX_LP_I2C_SCLK + , ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &hz) && hz) + { + return hz; + } + return 20 * 1000 * 1000; + } +#else + (void)i2c_port; +#endif + +#if defined (CONFIG_IDF_TARGET_ESP32C2) || defined (CONFIG_IDF_TARGET_ESP32C3) || defined ( CONFIG_IDF_TARGET_ESP32C5 ) || defined (CONFIG_IDF_TARGET_ESP32S3) || defined ( CONFIG_IDF_TARGET_ESP32C6 ) || defined ( CONFIG_IDF_TARGET_ESP32C61 ) || defined ( CONFIG_IDF_TARGET_ESP32P4 ) + return 40 * 1000 * 1000; // XTAL clock +#else + rtc_cpu_freq_config_t cpu_freq_conf; + rtc_clk_cpu_freq_get_config(&cpu_freq_conf); + if (cpu_freq_conf.freq_mhz < 80) + { // The source follows the CPU frequency here, so it has to be read every time + // rather than cached when the port is initialized. + return (cpu_freq_conf.source_freq_mhz * 1000000) / cpu_freq_conf.div; + } + return 80 * 1000 * 1000; +#endif + } + #if !defined ( I2C_CLOCK_SRC_ATOMIC ) #if __cplusplus <= 201103L #define LGFX_PERIPH_MODULE_T periph_module_t @@ -948,7 +1112,7 @@ namespace lgfx __attribute__ ((unused)) static LGFX_PERIPH_MODULE_T getPeriphModule(int num) { -#if SOC_I2C_NUM == 1 || defined CONFIG_IDF_TARGET_ESP32C2 || defined CONFIG_IDF_TARGET_ESP32C5 || defined CONFIG_IDF_TARGET_ESP32C6 || defined CONFIG_IDF_TARGET_ESP32C61 +#if LGFX_HP_I2C_NUM == 1 return PERIPH_I2C0_MODULE; #else return num == 0 ? PERIPH_I2C0_MODULE : PERIPH_I2C1_MODULE; @@ -960,7 +1124,13 @@ namespace lgfx static i2c_dev_t* getDev(int num) { -#if SOC_I2C_NUM == 1 || defined CONFIG_IDF_TARGET_ESP32C2 || defined CONFIG_IDF_TARGET_ESP32C5 || defined CONFIG_IDF_TARGET_ESP32C6 || defined CONFIG_IDF_TARGET_ESP32C61 +#if LGFX_LP_I2C_NUM > 0 + // The register layout of the low power I2C matches the normal one, which is why + // ESP-IDF publishes it as an i2c_dev_t as well. Everything below the port lookup + // therefore works on it unchanged. + if (isLpPort(num)) { return &LP_I2C; } +#endif +#if LGFX_HP_I2C_NUM == 1 return &I2C0; #else return num == 0 ? &I2C0 : &I2C1; @@ -999,6 +1169,13 @@ namespace lgfx __attribute__ ((unused)) static void i2c_periph_reset(int i2c_num) { +#if LGFX_LP_I2C_NUM > 0 + if (isLpPort(i2c_num)) + { // HP 用の i2c_ll_reset_register は SoC の PCR I2C 配列を範囲外参照するため LP 専用関数を使う; + lp_i2c_ll_reset_register(i2c_num - LGFX_HP_I2C_NUM); + return; + } +#endif I2C_RCC_ATOMIC() { i2c_ll_reset_register(i2c_num); (void)__DECLARE_RCC_ATOMIC_ENV; @@ -1144,7 +1321,7 @@ namespace lgfx #endif #endif -#if SOC_I2C_NUM == 1 || defined CONFIG_IDF_TARGET_ESP32C2 || defined CONFIG_IDF_TARGET_ESP32C5 || defined CONFIG_IDF_TARGET_ESP32C6 || defined CONFIG_IDF_TARGET_ESP32C61 +#if LGFX_HP_I2C_NUM == 1 auto twowire = &Wire; #else auto twowire = ((dev == &I2C0) ? &Wire : &Wire1); @@ -1158,8 +1335,10 @@ namespace lgfx #endif } -#if defined ( ARDUINO ) && __has_include () -#elif __has_include() +// A low power port is opened through the ESP-IDF driver even in an Arduino build, where +// the normal ports go through TwoWire, so the handle is needed in both cases. +#if __has_include() \ + && ( LGFX_LP_I2C_NUM > 0 || !( defined ( ARDUINO ) && __has_include () ) ) i2c_master_bus_handle_t i2c_bus_handle = nullptr; #endif private: @@ -1167,8 +1346,53 @@ namespace lgfx }; i2c_context_t i2c_context[I2C_NUM_MAX]; + static inline bool isSoftPort(int i2c_port) { return i2c_port < 0; } + +// ------------------------------------------------------------------------ +// Software I2C ( soft_i2c.inl ), reached through the negative port numbers. +// pinMode() here puts a pin in open drain output with the latch high and the +// input stage enabled, so a line is driven and released by toggling the latch +// alone; the direction changing default of the fragment is not needed. + +#define SOFT_I2C_LINE_LO(pin) gpio_lo(pin) +#define SOFT_I2C_LINE_HI(pin) gpio_hi(pin) +#define SOFT_I2C_LOCK(ctx) do { \ + if ((ctx).lock_handle == nullptr) { (ctx).lock_handle = xSemaphoreCreateMutex(); } \ + xSemaphoreTake((SemaphoreHandle_t)(ctx).lock_handle, portMAX_DELAY); \ + } while (0) +#define SOFT_I2C_UNLOCK(ctx) xSemaphoreGive((SemaphoreHandle_t)(ctx).lock_handle) +#define SOFT_I2C_YIELD() taskYIELD() +#define LGFX_INTERNAL_SOFT_I2C +#include "../soft_i2c.inl" + +// ------------------------------------------------------------------------ + +#if LGFX_LP_I2C_NUM > 0 && SOC_RTCIO_PIN_COUNT > 0 + /// Hand a pin back from the low power IO domain. + /// A pin routed to the low power I2C keeps that routing across a reset, because it is + /// held in the low power domain rather than by the CPU. Any later attempt to drive it + /// from a normal port then finds a pad that no longer reaches the peripheral, and the + /// bus looks dead for reasons nothing in the running program explains. Releasing it + /// here makes taking a pin over idempotent, whether or not the previous user shut + /// down in an orderly way. + static void releaseLpPad(gpio_num_t pin) + { + if ((int)pin >= 0 && rtc_gpio_is_valid_gpio(pin)) + { + rtc_gpio_deinit(pin); + } + } +#endif + static void set_pin(i2c_port_t i2c_num, gpio_num_t pin_sda, gpio_num_t pin_scl) { +#if LGFX_LP_I2C_NUM > 0 && SOC_RTCIO_PIN_COUNT > 0 + // Callers keep the low power ports away from here, so reaching this point means the + // pins are wanted for a normal port and any low power routing left on them, possibly + // by a previous run, has to go. ( see releaseLpPad ) + releaseLpPad(pin_sda); + releaseLpPad(pin_scl); +#endif #if __has_include() if ((int8_t)pin_sda >= 0) { gpio_set_level(pin_sda, true); @@ -1210,6 +1434,15 @@ namespace lgfx #endif } + static bool getBusBusy(i2c_dev_t* dev) + { +#if defined ( CONFIG_IDF_TARGET_ESP32C3 ) || 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 ) + return dev->sr.bus_busy; +#else + return dev->status_reg.bus_busy; +#endif + } + static void i2c_set_cmd(i2c_dev_t* dev, uint8_t index, uint8_t op_code, uint8_t byte_num, bool flg_nack = false) { /* @@ -1285,6 +1518,7 @@ namespace lgfx if (i2c_context[i2c_port].state == i2c_context_t::state_disconnect) { return res; } 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; if (i2c_context[i2c_port].wait_ack_stage) @@ -1328,12 +1562,17 @@ namespace lgfx if (flg_stop || res.has_error()) { #if 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 ) - if (res.has_error() || i2c_context[i2c_port].state == i2c_context_t::state_read || !int_raw.end_detect_int_raw) +// エラー発生後はペリフェラルが強制停止済みの場合があり、通常のSTOPコマンド発行では完了割り込みが来ずタイムアウトまで待たされるため強制STOP側へ分岐する; + if (res.has_error() || i2c_context[i2c_port].state.has_error() || i2c_context[i2c_port].state == i2c_context_t::state_read || !int_raw.end_detect_int_raw) #else - if (res.has_error() || i2c_context[i2c_port].state == i2c_context_t::state_read || !int_raw.end_detect) + if (res.has_error() || i2c_context[i2c_port].state.has_error() || i2c_context[i2c_port].state == i2c_context_t::state_read || !int_raw.end_detect) #endif { // force stop - i2c_stop(i2c_port); + // state が既にエラーの場合はエラー検出箇所で停止済みのため再停止しない (res のエラーはこの呼び出しで検出されたもので未停止); + if (res.has_error() || !i2c_context[i2c_port].state.has_error()) + { + i2c_stop(i2c_port); + } } else { @@ -1370,15 +1609,35 @@ namespace lgfx cpp::result release(int i2c_port) { - if (i2c_port >= I2C_NUM_MAX) { return cpp::fail(error_t::invalid_arg); } + if (isSoftPort(i2c_port)) { return soft_i2c_release(i2c_port); } + if (i2c_port >= LGFX_I2C_PORT_NUM) { return cpp::fail(error_t::invalid_arg); } if (i2c_context[i2c_port].initialized) { i2c_context[i2c_port].initialized = false; +#if LGFX_LP_I2C_NUM > 0 + if (isLpPort(i2c_port)) + { // Opened through the ESP-IDF driver in init(), so it is closed the same way. + auto bus_handle = i2c_context[i2c_port].i2c_bus_handle; + if (bus_handle) { + i2c_context[i2c_port].i2c_bus_handle = nullptr; + i2c_del_master_bus(bus_handle); + } + #if SOC_RTCIO_PIN_COUNT > 0 + // The pins have to be handed back explicitly: their routing lives in the low + // power domain and would otherwise outlive this program, leaving them unusable + // from a normal port even after a reset. ( see releaseLpPad ) + releaseLpPad(i2c_context[i2c_port].pin_sda); + releaseLpPad(i2c_context[i2c_port].pin_scl); + #endif + } + else +#endif + { #if defined ( ARDUINO ) && __has_include () && defined ( ESP_IDF_VERSION_VAL ) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) #if defined ARDUINO_ESP32_GIT_VER #if ARDUINO_ESP32_GIT_VER != 0x44c11981 - #if SOC_I2C_NUM == 1 || defined CONFIG_IDF_TARGET_ESP32C2 || defined CONFIG_IDF_TARGET_ESP32C5 || defined CONFIG_IDF_TARGET_ESP32C6 || defined CONFIG_IDF_TARGET_ESP32C61 + #if LGFX_HP_I2C_NUM == 1 auto twowire = &Wire; #else auto twowire = ((i2c_port == 0) ? &Wire : &Wire1); @@ -1396,6 +1655,7 @@ namespace lgfx #else i2c_periph_disable(i2c_port); #endif + } if ((int)i2c_context[i2c_port].pin_scl >= 0) { pinMode(i2c_context[i2c_port].pin_scl, pin_mode_t::input_pullup); @@ -1411,12 +1671,13 @@ namespace lgfx cpp::result setPins(int i2c_port, int pin_sda, int pin_scl) { - if ((i2c_port >= I2C_NUM_MAX) - || ((uint32_t)pin_scl >= GPIO_NUM_MAX) + if (((uint32_t)pin_scl >= GPIO_NUM_MAX) || ((uint32_t)pin_sda >= GPIO_NUM_MAX)) { return cpp::fail(error_t::invalid_arg); } + if (isSoftPort(i2c_port)) { return soft_i2c_setPins(i2c_port, pin_sda, pin_scl); } + if (i2c_port >= LGFX_I2C_PORT_NUM) { return cpp::fail(error_t::invalid_arg); } if (i2c_context[i2c_port].initialized && i2c_context[i2c_port].pin_scl == (gpio_num_t)pin_scl @@ -1429,6 +1690,11 @@ namespace lgfx release(i2c_port).has_value(); i2c_context[i2c_port].pin_scl = (gpio_num_t)pin_scl; i2c_context[i2c_port].pin_sda = (gpio_num_t)pin_sda; +#if LGFX_LP_I2C_NUM > 0 + // The TwoWire instances belong to the normal ports; a low power port has none, + // and handing these pins to one would redirect that port to them. + if (isLpPort(i2c_port)) { return {}; } +#endif #if defined ( ARDUINO ) && __has_include () #if defined ( ESP_IDF_VERSION_VAL ) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0) @@ -1437,7 +1703,7 @@ namespace lgfx #endif #if defined ( USE_TWOWIRE_SETPINS ) -#if SOC_I2C_NUM == 1 || defined CONFIG_IDF_TARGET_ESP32C2 || defined CONFIG_IDF_TARGET_ESP32C5 || defined CONFIG_IDF_TARGET_ESP32C6 || defined CONFIG_IDF_TARGET_ESP32C61 +#if LGFX_HP_I2C_NUM == 1 auto twowire = &Wire; #else auto twowire = ((i2c_port == 0) ? &Wire : &Wire1); @@ -1450,20 +1716,38 @@ namespace lgfx cpp::result getPinSDA(int i2c_port) { + if (isSoftPort(i2c_port)) { return soft_i2c_getPinSDA(i2c_port); } + if (i2c_port >= LGFX_I2C_PORT_NUM) { return cpp::fail(error_t::invalid_arg); } return i2c_context[i2c_port].pin_sda; } cpp::result getPinSCL(int i2c_port) { + if (isSoftPort(i2c_port)) { return soft_i2c_getPinSCL(i2c_port); } + if (i2c_port >= LGFX_I2C_PORT_NUM) { return cpp::fail(error_t::invalid_arg); } return i2c_context[i2c_port].pin_scl; } cpp::result init(int i2c_port) { + if (isSoftPort(i2c_port)) + { +#if LGFX_LP_I2C_NUM > 0 && SOC_RTCIO_PIN_COUNT > 0 + if (soft_i2c_valid_port(i2c_port)) + { // 低電力ポートのルーティングはリセットを跨いで残るため、前回実行が + // LP ポートとして使ったピンをソフトポートで取り直す場合にも返却が + // 必要になる。( see releaseLpPad ) + auto& ctx = soft_i2c_ctx(i2c_port); + releaseLpPad((gpio_num_t)ctx.pin_sda); + releaseLpPad((gpio_num_t)ctx.pin_scl); + } +#endif + return soft_i2c_init(i2c_port); + } + if (i2c_port >= LGFX_I2C_PORT_NUM) { return cpp::fail(error_t::invalid_arg); } gpio_num_t pin_sda = i2c_context[i2c_port].pin_sda; gpio_num_t pin_scl = i2c_context[i2c_port].pin_scl; - if ((i2c_port >= I2C_NUM_MAX) - || ((uint32_t)pin_scl >= GPIO_NUM_MAX) + if (((uint32_t)pin_scl >= GPIO_NUM_MAX) || ((uint32_t)pin_sda >= GPIO_NUM_MAX)) { return cpp::fail(error_t::invalid_arg); @@ -1476,8 +1760,37 @@ namespace lgfx i2c_stop(i2c_port); +#if LGFX_LP_I2C_NUM > 0 + if (isLpPort(i2c_port)) + { + i2c_master_bus_config_t bus_config; + memset(&bus_config, 0, sizeof(i2c_master_bus_config_t)); + bus_config.i2c_port = i2c_port; + bus_config.sda_io_num = pin_sda; + bus_config.scl_io_num = pin_scl; + bus_config.lp_source_clk = LGFX_LP_I2C_SCLK; + bus_config.glitch_ignore_cnt = 7; + bus_config.flags.enable_internal_pullup = true; + bus_config.intr_priority = 1; + + i2c_master_bus_handle_t bus_handle = nullptr; + if (ESP_OK != i2c_new_master_bus(&bus_config, &bus_handle)) + { // The pins of the low power port are constrained: they have to be LP IO, and on + // the chips without an LP GPIO matrix they are a fixed pair. + return cpp::fail(error_t::invalid_arg); + } + i2c_context[i2c_port].i2c_bus_handle = bus_handle; + i2c_context[i2c_port].initialized = true; + + // The pads are already routed to the peripheral by the call above, and they do not + // go through the normal GPIO matrix that set_pin() drives, so it is not called. + i2c_context[i2c_port].save_reg(getDev(i2c_port)); + return {}; + } +#endif + #if defined ( ARDUINO ) && __has_include () -#if SOC_I2C_NUM == 1 || defined CONFIG_IDF_TARGET_ESP32C2 || defined CONFIG_IDF_TARGET_ESP32C5 || defined CONFIG_IDF_TARGET_ESP32C6 || defined CONFIG_IDF_TARGET_ESP32C61 +#if LGFX_HP_I2C_NUM == 1 auto twowire = &Wire; #else auto twowire = ((i2c_port == 0) ? &Wire : &Wire1); @@ -1525,7 +1838,8 @@ namespace lgfx cpp::result restart(int i2c_port, int i2c_addr, uint32_t freq, bool read) { - if (i2c_port >= I2C_NUM_MAX) { return cpp::fail(error_t::invalid_arg); } + if (isSoftPort(i2c_port)) { return soft_i2c_restart(i2c_port, i2c_addr, freq, read); } + if (i2c_port >= LGFX_I2C_PORT_NUM) { return cpp::fail(error_t::invalid_arg); } if (i2c_addr < I2C_7BIT_ADDR_MIN || i2c_addr > I2C_10BIT_ADDR_MAX) return cpp::fail(error_t::invalid_arg); auto res = i2c_wait(i2c_port); @@ -1561,22 +1875,7 @@ namespace lgfx { i2c_context[i2c_port].freq = freq; static constexpr uint32_t MIN_I2C_CYCLE = 35; -#if defined (CONFIG_IDF_TARGET_ESP32C2) || defined (CONFIG_IDF_TARGET_ESP32C3) || defined ( CONFIG_IDF_TARGET_ESP32C5 ) || defined (CONFIG_IDF_TARGET_ESP32S3) || defined ( CONFIG_IDF_TARGET_ESP32C6 ) || defined ( CONFIG_IDF_TARGET_ESP32C61 ) || defined ( CONFIG_IDF_TARGET_ESP32P4 ) - uint32_t src_clock = 40 * 1000 * 1000; // XTAL clock -#else - rtc_cpu_freq_config_t cpu_freq_conf; - rtc_clk_cpu_freq_get_config(&cpu_freq_conf); - uint32_t src_clock = 80 * 1000 * 1000; - if (cpu_freq_conf.freq_mhz < 80) - { - src_clock = (cpu_freq_conf.source_freq_mhz * 1000000) / cpu_freq_conf.div; - } -// ESP_LOGI("LGFX", "i2c::restart : port:%d / addr:%02x / freq:%d / rw:%d", i2c_port, i2c_addr, freq, read); -// ESP_LOGI("LGFX", "cpu_freq_conf.div :%d", cpu_freq_conf.div); -// ESP_LOGI("LGFX", "cpu_freq_conf.freq_mhz :%d", cpu_freq_conf.freq_mhz); -// ESP_LOGI("LGFX", "cpu_freq_conf.source :%d", cpu_freq_conf.source); -// ESP_LOGI("LGFX", "cpu_freq_conf.source_freq_mhz :%d", cpu_freq_conf.source_freq_mhz); -#endif + uint32_t src_clock = getSourceClock(i2c_port); auto cycle = std::min(32767u, std::max(MIN_I2C_CYCLE, (src_clock / (freq + 1) + 1))); freq = src_clock / cycle; @@ -1666,8 +1965,8 @@ namespace lgfx cpp::result beginTransaction(int i2c_port, int i2c_addr, uint32_t freq, bool read) { - if (i2c_port >= I2C_NUM_MAX) return cpp::fail(error_t::invalid_arg); - + if (isSoftPort(i2c_port)) { return soft_i2c_beginTransaction(i2c_port, i2c_addr, freq, read); } + if (i2c_port >= LGFX_I2C_PORT_NUM) return cpp::fail(error_t::invalid_arg); if ((uint32_t)i2c_context[i2c_port].pin_sda >= GPIO_NUM_MAX || (uint32_t)i2c_context[i2c_port].pin_scl >= GPIO_NUM_MAX) return cpp::fail(error_t::invalid_arg); //ESP_LOGI("LGFX", "i2c::beginTransaction : port:%d / addr:%02x / freq:%d / rw:%d", i2c_port, i2c_addr, freq, read); @@ -1675,11 +1974,7 @@ namespace lgfx auto dev = getDev(i2c_port); i2c_context[i2c_port].lock(); -#if defined ( CONFIG_IDF_TARGET_ESP32C3 ) || 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 ) - if (dev->sr.bus_busy) -#else - if (dev->status_reg.bus_busy) -#endif + if (getBusBusy(dev)) { //ESP_LOGI("LGFX", "i2c::begin wait"); auto ms = micros(); @@ -1687,15 +1982,19 @@ namespace lgfx { taskYIELD(); } -#if defined ( CONFIG_IDF_TARGET_ESP32C3 ) || 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 ) - while (dev->sr.bus_busy && micros() - ms < 128); -#else - while (dev->status_reg.bus_busy && micros() - ms < 128); -#endif + while (getBusBusy(dev) && micros() - ms < 128); } i2c_context[i2c_port].save_reg(dev); - set_pin((i2c_port_t)i2c_port, i2c_context[i2c_port].pin_sda, i2c_context[i2c_port].pin_scl); +#if LGFX_LP_I2C_NUM > 0 + // A low power port reaches its pads through the low power IO domain, which set_pin() + // knows nothing about: routing them through the normal GPIO matrix here would + // disconnect the port from its own pins on every transaction. + if (!isLpPort(i2c_port)) +#endif + { + set_pin((i2c_port_t)i2c_port, i2c_context[i2c_port].pin_sda, i2c_context[i2c_port].pin_scl); + } #if SOC_I2C_SUPPORT_HW_FSM_RST dev->ctr.fsm_rst = 1; @@ -1716,8 +2015,16 @@ namespace lgfx ctrl_reg.val = 0; ctrl_reg.ms_mode = 1; // master mode ctrl_reg.clk_en = 1; - ctrl_reg.sda_force_out = 1; - ctrl_reg.scl_force_out = 1; +#if LGFX_LP_I2C_NUM > 0 + // The low power controller reaches the bus only through its internal open drain + // mode ( sda_force_out = 0 ): with the outputs forced, the state machine runs to + // completion without ever driving the lines, so no device can respond. + if (!isLpPort(i2c_port)) +#endif + { + ctrl_reg.sda_force_out = 1; + ctrl_reg.scl_force_out = 1; + } dev->ctr.val = ctrl_reg.val; // ---------- i2c_ll_master_init typeof(dev->fifo_conf) fifo_conf_reg; @@ -1740,22 +2047,39 @@ namespace lgfx cpp::result endTransaction(int i2c_port) { - if (i2c_port >= I2C_NUM_MAX) return cpp::fail(error_t::invalid_arg); + if (isSoftPort(i2c_port)) { return soft_i2c_endTransaction(i2c_port); } + if (i2c_port >= LGFX_I2C_PORT_NUM) return cpp::fail(error_t::invalid_arg); return i2c_wait(i2c_port, true); } //*/ + bool busy(int i2c_port) + { + if (isSoftPort(i2c_port)) { return false; } // a software port transfers synchronously + if (i2c_port >= LGFX_I2C_PORT_NUM) { return false; } + return getBusBusy(getDev(i2c_port)); + } + + void wait(int i2c_port) + { + if (isSoftPort(i2c_port)) { return; } // a software port transfers synchronously + if (i2c_port >= LGFX_I2C_PORT_NUM) { return; } + auto dev = getDev(i2c_port); + while (getBusBusy(dev)) { taskYIELD(); } + } + cpp::result writeBytes(int i2c_port, const uint8_t *data, size_t length) { - if (i2c_port >= I2C_NUM_MAX) { return cpp::fail(error_t::invalid_arg); } + if (isSoftPort(i2c_port)) { return soft_i2c_writeBytes(i2c_port, data, length); } + if (i2c_port >= LGFX_I2C_PORT_NUM) { return cpp::fail(error_t::invalid_arg); } if (i2c_context[i2c_port].state.has_error()) { return cpp::fail(i2c_context[i2c_port].state.error()); } if (i2c_context[i2c_port].state != i2c_context_t::state_write) { return cpp::fail(error_t::mode_mismatch); } cpp::result res {}; if (!length) return res; - static constexpr int txfifo_limit = 32; + const uint32_t txfifo_limit = getFifoLen(i2c_port); auto dev = getDev(i2c_port); auto fifo_addr = getFifoAddr(i2c_port); - size_t len = ((length - 1) & (txfifo_limit-1)) + 1; + size_t len = ((length - 1) % txfifo_limit) + 1; do { res = i2c_wait(i2c_port); @@ -1782,15 +2106,17 @@ namespace lgfx return res; } - cpp::result readBytes(int i2c_port, uint8_t *readdata, size_t length, bool last_nack = false) + cpp::result readBytes(int i2c_port, uint8_t *readdata, size_t length, bool last_nack) { - if (i2c_port >= I2C_NUM_MAX) { return cpp::fail(error_t::invalid_arg); } + if (isSoftPort(i2c_port)) { return soft_i2c_readBytes(i2c_port, readdata, length, last_nack); } + if (i2c_port >= LGFX_I2C_PORT_NUM) { return cpp::fail(error_t::invalid_arg); } if (i2c_context[i2c_port].state.has_error()) { return cpp::fail(i2c_context[i2c_port].state.error()); } if (i2c_context[i2c_port].state != i2c_context_t::state_read) { return cpp::fail(error_t::mode_mismatch); } cpp::result res {}; if (!length) return res; 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; + const uint32_t rxfifo_limit = getFifoLen(i2c_port); auto fifo_addr = getFifoAddr(i2c_port); auto dev = getDev(i2c_port); @@ -1811,7 +2137,7 @@ namespace lgfx break; } - len = length < 32 ? length : 32; + len = length < rxfifo_limit ? length : rxfifo_limit; #if defined ( CONFIG_IDF_TARGET_ESP32 ) || !defined ( CONFIG_IDF_TARGET ) // workaround for ESP32 i2c bug. if (last_nack && len == length && len > 1) { len -= 1; } @@ -1844,8 +2170,17 @@ namespace lgfx if (0 == getRxFifoCount(dev)) { + uint32_t int_raw_val = dev->int_raw.val; i2c_stop(i2c_port); - ESP_LOGW("LGFX", "i2c read error : read timeout"); + if ((int_raw_val & I2C_ACK_ERR_INT_RAW_M) + && !(int_raw_val & (I2C_TIME_OUT_INT_RAW_M | I2C_ARBITRATION_LOST_INT_RAW_M))) + { // Pure address NACK means no device is present: not a timeout, so no warning. + ESP_LOGV("LGFX", "i2c read error : nack"); + } + else + { + ESP_LOGW("LGFX", "i2c read error : read timeout"); + } res = cpp::fail(error_t::connection_lost); i2c_context[i2c_port].state = cpp::fail(error_t::connection_lost); i2c_context[i2c_port].wait_ack_stage = 0; diff --git a/src/lgfx/v1/platforms/esp32/common.hpp b/src/lgfx/v1/platforms/esp32/common.hpp index 4effeda5..fa8b4c80 100644 --- a/src/lgfx/v1/platforms/esp32/common.hpp +++ b/src/lgfx/v1/platforms/esp32/common.hpp @@ -30,9 +30,13 @@ Original Source: #include #include #if __has_include() -#include -#include -#include + #include +#endif +#if __has_include() + #include +#endif +#if __has_include() + #include #endif #include #include @@ -355,6 +359,8 @@ namespace lgfx cpp::result init(int i2c_port); cpp::result getPinSDA(int i2c_port); cpp::result getPinSCL(int i2c_port); + bool busy(int i2c_port); + void wait(int i2c_port); struct i2c_temporary_switcher_t { diff --git a/src/lgfx/v1/platforms/esp32s2/Bus_Parallel16.cpp b/src/lgfx/v1/platforms/esp32s2/Bus_Parallel16.cpp index 4e46c2e3..0a778a21 100644 --- a/src/lgfx/v1/platforms/esp32s2/Bus_Parallel16.cpp +++ b/src/lgfx/v1/platforms/esp32s2/Bus_Parallel16.cpp @@ -96,6 +96,13 @@ namespace lgfx bool Bus_Parallel16::init(void) { + // The GPIO matrix routing used below does not configure pad direction / + // open-drain, so normalize the data pins as push-pull GPIO outputs first. + for (int pin : _cfg.pin_data) + { + lgfx::pinMode(pin, pin_mode_t::output); + } + _init_pin(); for (size_t i = 0; i < 3; ++i) diff --git a/src/lgfx/v1/platforms/esp32s2/Bus_Parallel8.cpp b/src/lgfx/v1/platforms/esp32s2/Bus_Parallel8.cpp index 5fc1312d..1c89f482 100644 --- a/src/lgfx/v1/platforms/esp32s2/Bus_Parallel8.cpp +++ b/src/lgfx/v1/platforms/esp32s2/Bus_Parallel8.cpp @@ -96,6 +96,13 @@ namespace lgfx bool Bus_Parallel8::init(void) { + // The GPIO matrix routing used below does not configure pad direction / + // open-drain, so normalize the data pins as push-pull GPIO outputs first. + for (int pin : _cfg.pin_data) + { + lgfx::pinMode(pin, pin_mode_t::output); + } + _init_pin(); for (size_t i = 0; i < 3; ++i) diff --git a/src/lgfx/v1/platforms/esp32s3/Bus_Parallel16.cpp b/src/lgfx/v1/platforms/esp32s3/Bus_Parallel16.cpp index 8dd0a399..96aae269 100644 --- a/src/lgfx/v1/platforms/esp32s3/Bus_Parallel16.cpp +++ b/src/lgfx/v1/platforms/esp32s3/Bus_Parallel16.cpp @@ -80,6 +80,14 @@ namespace lgfx bool Bus_Parallel16::init(void) { + // esp_lcd_new_i80_bus does not configure pad direction / open-drain, so + // normalize the data pins as push-pull GPIO outputs first (same + // normalization used by Bus_EPD). + for (int pin : _cfg.pin_data) + { + lgfx::pinMode(pin, pin_mode_t::output); + } + _init_pin(); for (size_t i = 0; i < 3; ++i) diff --git a/src/lgfx/v1/platforms/esp32s3/Bus_Parallel8.cpp b/src/lgfx/v1/platforms/esp32s3/Bus_Parallel8.cpp index bdcfa058..a69ceacd 100644 --- a/src/lgfx/v1/platforms/esp32s3/Bus_Parallel8.cpp +++ b/src/lgfx/v1/platforms/esp32s3/Bus_Parallel8.cpp @@ -80,6 +80,14 @@ namespace lgfx bool Bus_Parallel8::init(void) { + // esp_lcd_new_i80_bus does not configure pad direction / open-drain, so + // normalize the data pins as push-pull GPIO outputs first (same + // normalization used by Bus_EPD). + for (int pin : _cfg.pin_data) + { + lgfx::pinMode(pin, pin_mode_t::output); + } + _init_pin(); for (size_t i = 0; i < 3; ++i) diff --git a/src/lgfx/v1/platforms/sdl/Bus_I2C.hpp b/src/lgfx/v1/platforms/sdl/Bus_I2C.hpp index 2662b455..a55557bb 100644 --- a/src/lgfx/v1/platforms/sdl/Bus_I2C.hpp +++ b/src/lgfx/v1/platforms/sdl/Bus_I2C.hpp @@ -35,7 +35,7 @@ namespace lgfx uint32_t freq_read = 400000; int16_t pin_scl = 22; int16_t pin_sda = 21; - uint8_t i2c_port = 0; + int8_t i2c_port = 0; uint8_t i2c_addr = 0x3C; uint32_t prefix_cmd = 0x00; uint32_t prefix_data = 0x40; diff --git a/src/lgfx/v1/platforms/sdl/Panel_sdl.cpp b/src/lgfx/v1/platforms/sdl/Panel_sdl.cpp index 13b5cc58..2052d98b 100644 --- a/src/lgfx/v1/platforms/sdl/Panel_sdl.cpp +++ b/src/lgfx/v1/platforms/sdl/Panel_sdl.cpp @@ -257,6 +257,11 @@ namespace lgfx _update_in_semaphore = SDL_CreateSemaphore(0); _update_out_semaphore = SDL_CreateSemaphore(0); for (size_t pin = 0; pin < EMULATED_GPIO_MAX; ++pin) { gpio_hi(pin); } +#if defined ( __APPLE__ ) + /// Since SDL 2.30.4, non-bundled apps are no longer brought to the foreground on macOS 14 or later. + /// Set the hint before SDL_Init so that the window appears in front. (can be overridden by the environment variable) + SDL_SetHint(SDL_HINT_MAC_BACKGROUND_APP, "0"); +#endif /*Initialize the SDL*/ SDL_Init(SDL_INIT_VIDEO); SDL_StartTextInput(); diff --git a/src/lgfx/v1/platforms/soft_i2c.inl b/src/lgfx/v1/platforms/soft_i2c.inl new file mode 100644 index 00000000..8752e205 --- /dev/null +++ b/src/lgfx/v1/platforms/soft_i2c.inl @@ -0,0 +1,409 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +// I2C on plain GPIO, selected by a negative port number: port -1 is slot 0, +// port -2 is slot 1. It reaches a bus without claiming a peripheral unit, for +// when every unit is taken or when touching one is undesirable. +// +// This file is not a header but a code fragment: a platform implementation +// includes it inside its i2c namespace, and delegates to the soft_i2c_*() +// functions at the top of each public function when the port is negative. +// Everything here has internal linkage, so nothing beyond the negative port +// convention becomes part of the library interface. +// +// The transaction semantics mirror the peripheral paths: an unacknowledged +// address surfaces at the next operation or at endTransaction, not at +// beginTransaction. On a failure the bus is stopped and the lock released +// right away, and the stored error is reported (and cleared) later. +// Clock stretching is honored wherever SCL is released. +#if !defined ( LGFX_INTERNAL_SOFT_I2C ) + #error "soft_i2c.inl is an implementation fragment of the i2c namespace; it cannot be included directly." +#endif + +// 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 +// on any platform. A platform whose pinMode() gives an input mode an open +// drain output stage (esp32) overrides these with plain latch toggles and +// skips the direction changes. +#if !defined ( SOFT_I2C_LINE_LO ) + #define SOFT_I2C_LINE_LO(pin) do { gpio_lo(pin); pinMode(pin, pin_mode_t::output); } while (0) + #define SOFT_I2C_LINE_HI(pin) pinMode(pin, pin_mode_t::input_pullup) +#endif + +// Serialization of the transactions on a slot. A platform with an RTOS wraps +// a mutex over the lock_handle member; the default is single threaded. +#if !defined ( SOFT_I2C_LOCK ) + #define SOFT_I2C_LOCK(ctx) + #define SOFT_I2C_UNLOCK(ctx) +#endif + +// Called while waiting out a stretched clock. +#if !defined ( SOFT_I2C_YIELD ) + #if defined ( ARDUINO ) + #define SOFT_I2C_YIELD() yield() + #else + #define SOFT_I2C_YIELD() + #endif +#endif + +//---------------------------------------------------------------------------- + + static constexpr int soft_i2c_port_count = 2; + + static constexpr int soft_i2c_7bit_addr_min = 0x08; + static constexpr int soft_i2c_7bit_addr_max = 0x77; + static constexpr int soft_i2c_10bit_addr_max = 1023; + + struct soft_i2c_context_t + { + enum state_t + { + state_disconnect, + state_write, + state_read + }; + cpp::result state; + void* lock_handle = nullptr; // storage for the SOFT_I2C_LOCK hooks + int pin_sda = -1; + int pin_scl = -1; + uint32_t freq = 0; + uint32_t half_us = 0; // 0 = unthrottled + bool initialized = false; + }; + static soft_i2c_context_t soft_i2c_context[soft_i2c_port_count]; + + 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]; } + + static inline void soft_i2c_half_wait(uint32_t half_us) + { if (half_us) { delayMicroseconds(half_us); } } + + static inline void soft_i2c_set_freq(soft_i2c_context_t& ctx, uint32_t freq) + { + if (freq == 0) { freq = 100000; } // an unset frequency falls back to the usual default + ctx.freq = freq; + // Rounded up: the throttled clock must not come out above the request. + ctx.half_us = (freq >= 500000) ? 0 : (500000 + freq - 1) / freq; + } + + /// 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) + { + SOFT_I2C_LINE_HI(ctx.pin_scl); + soft_i2c_half_wait(ctx.half_us); + if (gpio_in(ctx.pin_scl)) { return true; } + auto us = micros(); + do + { + SOFT_I2C_YIELD(); + if (gpio_in(ctx.pin_scl)) { return true; } + } while ((micros() - us) < 13000); // the same order as a peripheral timeout + return false; + } + + /// Returns true when the byte was acknowledged. + 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); } + 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); + 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; + } + + static inline bool soft_i2c_read_byte(const soft_i2c_context_t& ctx, uint8_t* data, bool ack) + { + uint_fast8_t byte = 0; + SOFT_I2C_LINE_HI(ctx.pin_sda); // released: the device drives the data line + size_t i = 0; + do + { + SOFT_I2C_LINE_LO(ctx.pin_scl); + 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); } + soft_i2c_half_wait(ctx.half_us); + if (!soft_i2c_scl_hi(ctx)) { return false; } + SOFT_I2C_LINE_LO(ctx.pin_scl); + SOFT_I2C_LINE_HI(ctx.pin_sda); + *data = byte; + return true; + } + + /// 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); + soft_i2c_half_wait(ctx.half_us); + bool ok = soft_i2c_scl_hi(ctx); + soft_i2c_half_wait(ctx.half_us); + SOFT_I2C_LINE_HI(ctx.pin_sda); + soft_i2c_half_wait(ctx.half_us); + return ok; + } + + /// Free a bus whose device was left mid transfer (e.g. by a reset) and holds + /// the data line, by clocking the leftover bits out. + static inline void soft_i2c_recover(const soft_i2c_context_t& ctx) + { + 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); + soft_i2c_half_wait(ctx.half_us); + soft_i2c_scl_hi(ctx); + } + soft_i2c_stop_cond(ctx); + } + + /// Ends a transfer on a failure. The bus is stopped and the lock released + /// right away; the error is stored so that it surfaces from the following + /// operation or from endTransaction, as it does on a peripheral path. + static inline void soft_i2c_abort(soft_i2c_context_t& ctx) + { + soft_i2c_stop_cond(ctx); + ctx.state = cpp::fail(error_t::connection_lost); + SOFT_I2C_UNLOCK(ctx); + } + + static inline cpp::result soft_i2c_send_address(soft_i2c_context_t& ctx, int i2c_addr, bool read) + { + bool ack; + if (i2c_addr <= soft_i2c_7bit_addr_max) + { + ack = soft_i2c_write_byte(ctx, i2c_addr << 1 | (read ? 1 : 0)); + } + else + { + ack = soft_i2c_write_byte(ctx, 0xF0 | (i2c_addr >> 8) << 1) + && 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); + soft_i2c_half_wait(ctx.half_us); + 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); + } + } + } + if (!ack) + { + soft_i2c_abort(ctx); + return {}; + } + ctx.state = read ? soft_i2c_context_t::state_t::state_read : soft_i2c_context_t::state_t::state_write; + return {}; + } + +// ------------------------------------------------------------------------ +// Mirrors of the public functions, for the negative ports. + + static inline cpp::result soft_i2c_release(int i2c_port) + { + if (!soft_i2c_valid_port(i2c_port)) { return cpp::fail(error_t::invalid_arg); } + auto& ctx = soft_i2c_ctx(i2c_port); + if (ctx.initialized) + { + ctx.initialized = false; + // Park the pins the same way the peripheral paths do. + if (ctx.pin_scl >= 0) { pinMode(ctx.pin_scl, pin_mode_t::input_pullup); } + if (ctx.pin_sda >= 0) { pinMode(ctx.pin_sda, pin_mode_t::input_pullup); } + } + return {}; + } + + static inline cpp::result soft_i2c_setPins(int i2c_port, int pin_sda, int pin_scl) + { + if (!soft_i2c_valid_port(i2c_port) || pin_sda < 0 || pin_scl < 0) + { + return cpp::fail(error_t::invalid_arg); + } + auto& ctx = soft_i2c_ctx(i2c_port); + if (ctx.initialized && ctx.pin_sda == pin_sda && ctx.pin_scl == pin_scl) + { + return {}; + } + soft_i2c_release(i2c_port).has_value(); + ctx.pin_sda = pin_sda; + ctx.pin_scl = pin_scl; + return {}; + } + + static inline cpp::result soft_i2c_getPinSDA(int i2c_port) + { + if (!soft_i2c_valid_port(i2c_port)) { return cpp::fail(error_t::invalid_arg); } + return soft_i2c_ctx(i2c_port).pin_sda; + } + + static inline cpp::result soft_i2c_getPinSCL(int i2c_port) + { + if (!soft_i2c_valid_port(i2c_port)) { return cpp::fail(error_t::invalid_arg); } + return soft_i2c_ctx(i2c_port).pin_scl; + } + + static inline cpp::result soft_i2c_init(int i2c_port) + { + if (!soft_i2c_valid_port(i2c_port)) { return cpp::fail(error_t::invalid_arg); } + auto& ctx = soft_i2c_ctx(i2c_port); + if (ctx.pin_sda < 0 || ctx.pin_scl < 0) { return cpp::fail(error_t::invalid_arg); } + if (ctx.initialized) + { + soft_i2c_release(i2c_port).has_value(); + } + soft_i2c_set_freq(ctx, 100000); // until a transfer brings its own + // Both lines are released; the pullup carries them high when the bus is + // healthy. ( see SOFT_I2C_LINE_HI for how a line is driven afterwards ) + pinMode(ctx.pin_sda, pin_mode_t::input_pullup); + pinMode(ctx.pin_scl, pin_mode_t::input_pullup); + ctx.state = soft_i2c_context_t::state_t::state_disconnect; + ctx.initialized = true; + return {}; + } + + static inline cpp::result soft_i2c_beginTransaction(int i2c_port, int i2c_addr, uint32_t freq, bool read) + { + if (!soft_i2c_valid_port(i2c_port)) { return cpp::fail(error_t::invalid_arg); } + auto& ctx = soft_i2c_ctx(i2c_port); + if (ctx.pin_sda < 0 || ctx.pin_scl < 0) { return cpp::fail(error_t::invalid_arg); } + if (i2c_addr < soft_i2c_7bit_addr_min || i2c_addr > soft_i2c_10bit_addr_max) { return cpp::fail(error_t::invalid_arg); } + 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); + if (!soft_i2c_scl_hi(ctx)) + { // The clock never rose: no start condition can be made on this bus. + soft_i2c_abort(ctx); + return {}; + } + if (!gpio_in(ctx.pin_sda)) + { + soft_i2c_recover(ctx); + if (!gpio_in(ctx.pin_sda)) + { // The data line is still held; without it no start condition can be + // made, and the acknowledge bits would read as permanently asserted. + soft_i2c_abort(ctx); + return {}; + } + } + SOFT_I2C_LINE_LO(ctx.pin_sda); // start condition + soft_i2c_half_wait(ctx.half_us); + SOFT_I2C_LINE_LO(ctx.pin_scl); + return soft_i2c_send_address(ctx, i2c_addr, read); + } + + static inline cpp::result soft_i2c_restart(int i2c_port, int i2c_addr, uint32_t freq, bool read) + { + if (!soft_i2c_valid_port(i2c_port)) { return cpp::fail(error_t::invalid_arg); } + if (i2c_addr < soft_i2c_7bit_addr_min || i2c_addr > soft_i2c_10bit_addr_max) { return cpp::fail(error_t::invalid_arg); } + auto& ctx = soft_i2c_ctx(i2c_port); + if (ctx.state.has_error()) { return cpp::fail(ctx.state.error()); } + if (ctx.state == soft_i2c_context_t::state_disconnect) + { // Only an open transaction can be restarted; the lock is not held here. + return cpp::fail(error_t::mode_mismatch); + } + soft_i2c_set_freq(ctx, freq); + SOFT_I2C_LINE_HI(ctx.pin_sda); // repeated start + soft_i2c_half_wait(ctx.half_us); + if (!soft_i2c_scl_hi(ctx)) + { + soft_i2c_abort(ctx); + return {}; + } + SOFT_I2C_LINE_LO(ctx.pin_sda); + soft_i2c_half_wait(ctx.half_us); + SOFT_I2C_LINE_LO(ctx.pin_scl); + return soft_i2c_send_address(ctx, i2c_addr, read); + } + + static inline cpp::result soft_i2c_endTransaction(int i2c_port) + { + if (!soft_i2c_valid_port(i2c_port)) { return cpp::fail(error_t::invalid_arg); } + auto& ctx = soft_i2c_ctx(i2c_port); + if (ctx.state.has_error()) + { // The failing operation has already stopped the bus and released the lock; + // report its error here and clear it. + auto err = ctx.state.error(); + ctx.state = soft_i2c_context_t::state_t::state_disconnect; + return cpp::fail(err); + } + if (ctx.state == soft_i2c_context_t::state_disconnect) { return {}; } + bool stopped = soft_i2c_stop_cond(ctx); + ctx.state = soft_i2c_context_t::state_t::state_disconnect; + SOFT_I2C_UNLOCK(ctx); + if (!stopped) { return cpp::fail(error_t::connection_lost); } + return {}; + } + + static inline cpp::result soft_i2c_writeBytes(int i2c_port, const uint8_t *data, size_t length) + { + if (!soft_i2c_valid_port(i2c_port)) { return cpp::fail(error_t::invalid_arg); } + auto& ctx = soft_i2c_ctx(i2c_port); + if (ctx.state.has_error()) { return cpp::fail(ctx.state.error()); } + if (ctx.state != soft_i2c_context_t::state_write) { return cpp::fail(error_t::mode_mismatch); } + if (!length) { return {}; } + do + { + if (!soft_i2c_write_byte(ctx, *data++)) + { + soft_i2c_abort(ctx); + return cpp::fail(error_t::connection_lost); + } + } while (--length); + return {}; + } + + static inline cpp::result soft_i2c_readBytes(int i2c_port, uint8_t *readdata, size_t length, bool last_nack) + { + if (!soft_i2c_valid_port(i2c_port)) { return cpp::fail(error_t::invalid_arg); } + auto& ctx = soft_i2c_ctx(i2c_port); + if (ctx.state.has_error()) { return cpp::fail(ctx.state.error()); } + if (ctx.state != soft_i2c_context_t::state_read) { return cpp::fail(error_t::mode_mismatch); } + if (!length) { return {}; } + do + { + if (!soft_i2c_read_byte(ctx, readdata++, !(last_nack && length == 1))) + { + soft_i2c_abort(ctx); + return cpp::fail(error_t::connection_lost); + } + } while (--length); + return {}; + } + +//---------------------------------------------------------------------------- diff --git a/src/lgfx/v1/platforms/soft_spi.inl b/src/lgfx/v1/platforms/soft_spi.inl new file mode 100644 index 00000000..c6d847c5 --- /dev/null +++ b/src/lgfx/v1/platforms/soft_spi.inl @@ -0,0 +1,168 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +// SPI on plain GPIO, selected by a negative host number: host -1 is slot 0, +// host -2 is slot 1. It reaches a bus without claiming an SPI peripheral, and +// the convention matches the touch driver configs, where a negative host has +// always meant bit banged GPIO. +// +// This file is not a header but a code fragment: a platform implementation +// includes it inside its spi namespace, and delegates to the soft_spi_*() +// functions at the top of each public function when the host is negative. +// Everything here has internal linkage, so nothing beyond the negative host +// convention becomes part of the library interface. Only the portable pin +// functions are used, which keeps the fragment platform independent. +#if !defined ( LGFX_INTERNAL_SOFT_SPI ) + #error "soft_spi.inl is an implementation fragment of the spi namespace; it cannot be included directly." +#endif + +//---------------------------------------------------------------------------- + + static constexpr int soft_spi_host_count = 2; + + struct soft_spi_context_t + { + int pin_sclk = -1; + int pin_miso = -1; + int pin_mosi = -1; + uint32_t half_us = 0; // 0 = unthrottled + uint8_t mode = 0; + }; + static soft_spi_context_t soft_spi_context[soft_spi_host_count]; + + static inline bool soft_spi_valid_host(int spi_host) { return -soft_spi_host_count <= spi_host && spi_host < 0; } + static inline soft_spi_context_t& soft_spi_ctx(int spi_host) { return soft_spi_context[~spi_host]; } + + static inline void soft_spi_half_wait(uint32_t half_us) + { + if (half_us) { delayMicroseconds(half_us); } + } + + static inline void soft_spi_clock_idle(int pin_sclk, uint8_t spi_mode) + { + if (pin_sclk < 0) { return; } + if (spi_mode & 2) { gpio_hi(pin_sclk); } else { gpio_lo(pin_sclk); } + } + + /// Full duplex byte shift, MSB first. read_data may be the write_data + /// buffer (in place), or nullptr to discard the incoming bits. + /// half_us throttles the clock (0 = unthrottled); the clock is left at its + /// idle level, which the caller has to establish before the first call + /// ( see soft_spi_clock_idle ). + static inline void soft_spi_transfer(uint8_t* read_data, const uint8_t* write_data, size_t len, const soft_spi_context_t& ctx) + { + if (!len) { return; } + const int pin_sclk = ctx.pin_sclk; + const int pin_miso = ctx.pin_miso; + const int pin_mosi = ctx.pin_mosi; + const uint32_t half_us = ctx.half_us; + const bool cpha = ctx.mode & 1; + const bool lead = !(ctx.mode & 2); // the clock level of the leading edge + do + { + uint_fast8_t w = *write_data++; + uint_fast8_t r = 0; + uint_fast8_t mask = 0x80; + do + { + if (!cpha) + { // The data is set while the clock idles and sampled on the leading edge. + if (pin_mosi >= 0) { if (w & mask) { gpio_hi(pin_mosi); } else { gpio_lo(pin_mosi); } } + soft_spi_half_wait(half_us); + if (lead) { gpio_hi(pin_sclk); } else { gpio_lo(pin_sclk); } + if (pin_miso >= 0 && gpio_in(pin_miso)) { r += mask; } + soft_spi_half_wait(half_us); + if (lead) { gpio_lo(pin_sclk); } else { gpio_hi(pin_sclk); } + } + else + { // The data is set on the leading edge and sampled on the trailing edge. + if (lead) { gpio_hi(pin_sclk); } else { gpio_lo(pin_sclk); } + if (pin_mosi >= 0) { if (w & mask) { gpio_hi(pin_mosi); } else { gpio_lo(pin_mosi); } } + soft_spi_half_wait(half_us); + if (lead) { gpio_lo(pin_sclk); } else { gpio_hi(pin_sclk); } + if (pin_miso >= 0 && gpio_in(pin_miso)) { r += mask; } + soft_spi_half_wait(half_us); + } + } while (mask >>= 1); + if (read_data) { *read_data++ = r; } + } while (--len); + } + +// ------------------------------------------------------------------------ +// Mirrors of the public functions, for the negative hosts. + + static inline cpp::result soft_spi_init(int spi_host, int spi_sclk, int spi_miso, int spi_mosi) + { + if (!soft_spi_valid_host(spi_host)) { return cpp::fail(error_t::invalid_arg); } + if (spi_sclk < 0) { return cpp::fail(error_t::invalid_arg); } // the clock is not optional + auto& ctx = soft_spi_ctx(spi_host); + ctx.pin_sclk = spi_sclk; + ctx.pin_miso = spi_miso; + ctx.pin_mosi = spi_mosi; + ctx.mode = 0; + ctx.half_us = 0; + if (spi_sclk >= 0) + { + gpio_lo(spi_sclk); // low first, so that no HIGH pulse leaves the pin (panels without CS) + pinMode(spi_sclk, pin_mode_t::output); + } + if (spi_mosi >= 0) { pinMode(spi_mosi, pin_mode_t::output); } + if (spi_miso >= 0) { pinMode(spi_miso, pin_mode_t::input_pullup); } + return {}; + } + + static inline void soft_spi_release(int spi_host) + { + if (!soft_spi_valid_host(spi_host)) { return; } + auto& ctx = soft_spi_ctx(spi_host); + if (ctx.pin_sclk >= 0) { pinMode(ctx.pin_sclk, pin_mode_t::input); } + if (ctx.pin_miso >= 0) { pinMode(ctx.pin_miso, pin_mode_t::input); } + if (ctx.pin_mosi >= 0) { pinMode(ctx.pin_mosi, pin_mode_t::input); } + ctx = soft_spi_context_t(); + } + + static inline void soft_spi_beginTransaction(int spi_host) + { // Nothing to acquire; just make sure the clock rests at its idle level. + if (!soft_spi_valid_host(spi_host)) { return; } + auto& ctx = soft_spi_ctx(spi_host); + soft_spi_clock_idle(ctx.pin_sclk, ctx.mode); + } + + static inline void soft_spi_beginTransaction(int spi_host, uint32_t freq, int spi_mode) + { + if (!soft_spi_valid_host(spi_host)) { return; } + auto& ctx = soft_spi_ctx(spi_host); + ctx.mode = spi_mode & 3; + if (freq == 0) { freq = 100000; } // an unset frequency falls back to a safe default + // Best effort, rounded up: the throttled clock must not come out above the request. + ctx.half_us = (freq >= 500000) ? 0 : (500000 + freq - 1) / freq; + soft_spi_beginTransaction(spi_host); + } + + static inline void soft_spi_writeBytes(int spi_host, const uint8_t* data, size_t length) + { + if (!soft_spi_valid_host(spi_host)) { return; } + soft_spi_transfer(nullptr, data, length, soft_spi_ctx(spi_host)); + } + + static inline void soft_spi_readBytes(int spi_host, uint8_t* data, size_t length) + { // Full duplex, in place: the same semantics as the peripheral paths. + if (!soft_spi_valid_host(spi_host)) { return; } + soft_spi_transfer(data, data, length, soft_spi_ctx(spi_host)); + } + +//---------------------------------------------------------------------------- diff --git a/src/lgfx/v1/touch/Touch_CHSC6540.cpp b/src/lgfx/v1/touch/Touch_CHSC6540.cpp index f82dbf16..2f619512 100644 --- a/src/lgfx/v1/touch/Touch_CHSC6540.cpp +++ b/src/lgfx/v1/touch/Touch_CHSC6540.cpp @@ -72,13 +72,17 @@ namespace lgfx { flip = !flip; size_t points = std::min(count, tmp[0]); - if (points && lgfx::i2c::readBytes(_cfg.i2c_port, &tmp[1], points * 6 - 2)) - {} + if (points && !lgfx::i2c::readBytes(_cfg.i2c_port, &tmp[1], points * 6 - 2)) + { // 座標を読み切れなかったフレームは破棄する (ゼロ埋めの座標を有効タッチとして返さないため); + tmp[0] = 0; + } } if (lgfx::i2c::endTransaction(_cfg.i2c_port)) {} if (tmp[0] == 0 || memcmp(buf[0], buf[1], len) == 0) break; } - if (0 == --retry) return 0; + // リトライを使い切った場合はタッチ無しとせず直近の読み値を採用する。 + // (指の移動中は連続読出しが一致しないため、return 0 だとフリックが取れない) + if (0 == --retry) { break; } } if (count > tmp[0]) count = tmp[0]; diff --git a/src/lgfx/v1/touch/Touch_CSTxxx.cpp b/src/lgfx/v1/touch/Touch_CSTxxx.cpp index 07df4ca2..ea8ca19c 100644 --- a/src/lgfx/v1/touch/Touch_CSTxxx.cpp +++ b/src/lgfx/v1/touch/Touch_CSTxxx.cpp @@ -315,7 +315,7 @@ namespace lgfx if( requested_count > 1 ) // an array of touch_point_t was provided { int pIdx = 0; // points index with corrected offset - for (int i=0; i> 4) & 0xf));