diff --git a/src/M5GFX.cpp b/src/M5GFX.cpp index 8cbcc4c3..861f6c69 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 @@ -879,6 +885,12 @@ namespace m5gfx // 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; @@ -3449,6 +3461,81 @@ The usage of each pin is as follows. 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); + } + // 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(); } + } + #elif defined (CONFIG_IDF_TARGET_ESP32H2) #endif @@ -3514,6 +3601,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/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; + } + } + }; + +//---------------------------------------------------------------------------- + } +}