diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index a73b0a4..dd8f1ff 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -5,6 +5,7 @@ #include "M5Unified.hpp" #include "utility/PI4IOE5V6408_Class.hpp" +#include "utility/M5IOE1_Class.hpp" #if !defined (M5UNIFIED_PC_BUILD) #include @@ -428,7 +429,6 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { static constexpr uint8_t es8388_i2c_addr = 0x10; static constexpr uint8_t pi4io1_i2c_addr = 0x43; static constexpr uint8_t m5pm1_i2c_addr = 0x6E; - static constexpr uint8_t m5ioe1_i2c_addr = 0x4F; #if defined (CONFIG_IDF_TARGET_ESP32S3) static constexpr uint8_t aw88298_i2c_addr = 0x36; static constexpr uint8_t aw9523_i2c_addr = 0x58; @@ -599,15 +599,17 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { }; if (enabled) { - self->In_I2C.bitOn(m5ioe1_i2c_addr, 0x05, 0b00000100, 100000); // Enable Audio Power (M5IOE1_G3) + auto& ioe1 = self->getIOExpander(0); + ioe1.digitalWrite(M5IOE1_Class::gpio3, true); // Enable Audio Power (M5IOE1_G3) self->delay(10); in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); - self->In_I2C.bitOn(m5ioe1_i2c_addr, 0x06, 0b00000010, 100000); // Enable PA (M5IOE1_G10) + ioe1.digitalWrite(M5IOE1_Class::gpio10, true); // Enable PA (M5IOE1_G10) } else { - self->In_I2C.bitOff(m5ioe1_i2c_addr, 0x06, 0b00000010, 100000); // Disable PA (M5IOE1_G10) - self->In_I2C.bitOff(m5ioe1_i2c_addr, 0x05, 0b00000100, 100000); // Disable Audio Power (M5IOE1_G3) + auto& ioe1 = self->getIOExpander(0); + ioe1.digitalWrite(M5IOE1_Class::gpio10, false); // Disable PA (M5IOE1_G10) + ioe1.digitalWrite(M5IOE1_Class::gpio3, false); // Disable Audio Power (M5IOE1_G3) } #endif return true; @@ -1041,7 +1043,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { }; if (enabled) { - self->In_I2C.bitOn(m5ioe1_i2c_addr, 0x05, 0b00000100, 100000); // Enable Audio Power (M5IOE1_G3) + self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio3, true); // Enable Audio Power (M5IOE1_G3) self->delay(5); } m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_47, GPIO_NUM_48); @@ -1730,6 +1732,14 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { _io_expander[0].reset(ioexp); } break; + case board_t::board_M5PaperMono: + case board_t::board_M5StopWatch: + { + auto ioexp = new M5IOE1_Class; + ioexp->begin(); + _io_expander[0].reset(ioexp); + } + break; #endif default: break; @@ -1999,20 +2009,23 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { case board_t::board_M5StopWatch: m5gfx::pinMode(GPIO_NUM_1, m5gfx::pin_mode_t::input); m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input); - // M5IOE1@0x4F: display power M5IOE1_G8 + // M5IOE1 PIN4: display power { - this->In_I2C.writeRegister8(m5ioe1_i2c_addr, 0x23, 0x00, 100000); - this->In_I2C.bitOff(m5ioe1_i2c_addr, 0x13, 0b00001000, 100000); - this->In_I2C.bitOn(m5ioe1_i2c_addr, 0x03, 0b00001000, 100000); - this->In_I2C.bitOn(m5ioe1_i2c_addr, 0x05, 0b00001000, 100000); + auto& ioe1 = getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio4, false); + ioe1.setDirection(M5IOE1_Class::gpio4, true); + ioe1.digitalWrite(M5IOE1_Class::gpio4, true); } // M5IOE1_G3 codec power / M5IOE1_G10 PA - this->In_I2C.bitOff(m5ioe1_i2c_addr, 0x13, 0b00000100, 100000); - this->In_I2C.bitOff(m5ioe1_i2c_addr, 0x14, 0b00000010, 100000); - this->In_I2C.bitOn(m5ioe1_i2c_addr, 0x03, 0b00000100, 100000); - this->In_I2C.bitOn(m5ioe1_i2c_addr, 0x04, 0b00000010, 100000); - this->In_I2C.bitOff(m5ioe1_i2c_addr, 0x05, 0b00000100, 100000); - this->In_I2C.bitOff(m5ioe1_i2c_addr, 0x06, 0b00000010, 100000); + { + auto& ioe1 = getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio3, false); + ioe1.setHighImpedance(M5IOE1_Class::gpio10, false); + ioe1.setDirection(M5IOE1_Class::gpio3, true); + ioe1.setDirection(M5IOE1_Class::gpio10, true); + ioe1.digitalWrite(M5IOE1_Class::gpio3, false); + ioe1.digitalWrite(M5IOE1_Class::gpio10, false); + } break; #elif defined (CONFIG_IDF_TARGET_ESP32P4) diff --git a/src/utility/M5IOE1_Class.cpp b/src/utility/M5IOE1_Class.cpp new file mode 100644 index 0000000..4473ba5 --- /dev/null +++ b/src/utility/M5IOE1_Class.cpp @@ -0,0 +1,139 @@ +// Copyright (c) M5Stack. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "M5IOE1_Class.hpp" + +namespace m5 +{ + namespace + { + static constexpr std::uint8_t M5IOE1_REG_UID_L = 0x00; + static constexpr std::uint8_t M5IOE1_REG_GPIO_MODE_L = 0x03; + static constexpr std::uint8_t M5IOE1_REG_GPIO_OUT_L = 0x05; + static constexpr std::uint8_t M5IOE1_REG_GPIO_IN_L = 0x07; + static constexpr std::uint8_t M5IOE1_REG_GPIO_PU_L = 0x09; + static constexpr std::uint8_t M5IOE1_REG_GPIO_PD_L = 0x0B; + static constexpr std::uint8_t M5IOE1_REG_GPIO_IE_L = 0x0D; + static constexpr std::uint8_t M5IOE1_REG_GPIO_IS_L = 0x11; + static constexpr std::uint8_t M5IOE1_REG_GPIO_DRV_L = 0x13; + static constexpr std::uint8_t M5IOE1_REG_PWM1_DUTY_L = 0x1B; + static constexpr std::uint8_t M5IOE1_REG_I2C_CFG = 0x23; + static constexpr std::uint8_t M5IOE1_REG_PWM_FREQ_L = 0x25; + static constexpr std::uint8_t M5IOE1_PWM_POLARITY = 1 << 6; + static constexpr std::uint8_t M5IOE1_PWM_ENABLE = 1 << 7; + static constexpr std::uint32_t M5IOE1_I2C_FREQ_100K = 100000; + } + + bool M5IOE1_Class::begin() + { + if (!_init) { + _freq = M5IOE1_I2C_FREQ_100K; + std::uint8_t uid[2] = {}; + auto res = readRegister(M5IOE1_REG_UID_L, uid, sizeof(uid)); + if (res) { + // Disable I2C idle sleep and keep the IOE1 bus at its default 100 kHz speed. + writeRegister8(M5IOE1_REG_I2C_CFG, 0x00); + } + _init = res; + } + return _init; + } + + void M5IOE1_Class::setDirection(uint8_t pin, bool direction) + { + if (!_isValidPin(pin)) { return; } + // false=input, true=output. MODE bit set means output. + const auto reg = _regForPin(M5IOE1_REG_GPIO_MODE_L, pin); + const auto bit = _bitForPin(pin); + direction ? bitOn(reg, bit) : bitOff(reg, bit); + } + + void M5IOE1_Class::enablePull(uint8_t pin, bool enablePull) + { + if (!_isValidPin(pin)) { return; } + const auto pu_reg = _regForPin(M5IOE1_REG_GPIO_PU_L, pin); + const auto pd_reg = _regForPin(M5IOE1_REG_GPIO_PD_L, pin); + const auto bit = _bitForPin(pin); + if (enablePull) { + bitOn(pu_reg, bit); + } else { + bitOff(pu_reg, bit); + bitOff(pd_reg, bit); + } + } + + void M5IOE1_Class::setPullMode(uint8_t pin, bool mode) + { + if (!_isValidPin(pin)) { return; } + const auto pu_reg = _regForPin(M5IOE1_REG_GPIO_PU_L, pin); + const auto pd_reg = _regForPin(M5IOE1_REG_GPIO_PD_L, pin); + const auto bit = _bitForPin(pin); + // false=pull-down, true=pull-up. + mode ? bitOn(pu_reg, bit) : bitOff(pu_reg, bit); + mode ? bitOff(pd_reg, bit) : bitOn(pd_reg, bit); + } + + void M5IOE1_Class::setHighImpedance(uint8_t pin, bool enable) + { + if (!_isValidPin(pin)) { return; } + // M5IOE1 exposes drive mode here: 0=push-pull, 1=open-drain. + const auto reg = _regForPin(M5IOE1_REG_GPIO_DRV_L, pin); + const auto bit = _bitForPin(pin); + enable ? bitOn(reg, bit) : bitOff(reg, bit); + } + + bool M5IOE1_Class::getWriteValue(uint8_t pin) + { + if (!_isValidPin(pin)) { return false; } + return (readRegister8(_regForPin(M5IOE1_REG_GPIO_OUT_L, pin)) & _bitForPin(pin)) != 0; + } + + void M5IOE1_Class::digitalWrite(uint8_t pin, bool level) + { + if (!_isValidPin(pin)) { return; } + const auto reg = _regForPin(M5IOE1_REG_GPIO_OUT_L, pin); + const auto bit = _bitForPin(pin); + level ? bitOn(reg, bit) : bitOff(reg, bit); + } + + bool M5IOE1_Class::digitalRead(uint8_t pin) + { + if (!_isValidPin(pin)) { return false; } + return (readRegister8(_regForPin(M5IOE1_REG_GPIO_IN_L, pin)) & _bitForPin(pin)) != 0; + } + + void M5IOE1_Class::setPwmFrequency(std::uint16_t frequency) + { + std::uint8_t data[2] = { static_cast(frequency & 0xFF), static_cast(frequency >> 8) }; + writeRegister(M5IOE1_REG_PWM_FREQ_L, data, sizeof(data)); + } + + void M5IOE1_Class::setPwmDuty(std::uint8_t channel, std::uint16_t duty12, bool enable, bool polarity) + { + if (channel > pwm_ch4) { return; } + duty12 &= 0x0FFF; + std::uint8_t high = static_cast(duty12 >> 8); + if (enable) { high |= M5IOE1_PWM_ENABLE; } + if (polarity) { high |= M5IOE1_PWM_POLARITY; } + std::uint8_t data[2] = { static_cast(duty12 & 0xFF), high }; + writeRegister(static_cast(M5IOE1_REG_PWM1_DUTY_L + channel * 2), data, sizeof(data)); + } + + void M5IOE1_Class::resetIrq() + { + std::uint8_t data[2] = { 0x00, 0x00 }; + writeRegister(M5IOE1_REG_GPIO_IS_L, data, sizeof(data)); + } + + void M5IOE1_Class::disableIrq() + { + std::uint8_t data[2] = { 0x00, 0x00 }; + writeRegister(M5IOE1_REG_GPIO_IE_L, data, sizeof(data)); + } + + void M5IOE1_Class::enableIrq() + { + std::uint8_t data[2] = { 0xFF, 0x3F }; + writeRegister(M5IOE1_REG_GPIO_IE_L, data, sizeof(data)); + } +} diff --git a/src/utility/M5IOE1_Class.hpp b/src/utility/M5IOE1_Class.hpp new file mode 100644 index 0000000..3154fde --- /dev/null +++ b/src/utility/M5IOE1_Class.hpp @@ -0,0 +1,78 @@ +// Copyright (c) M5Stack. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef __M5_M5IOE1_CLASS_H__ +#define __M5_M5IOE1_CLASS_H__ + +#include "IOExpander_Base.hpp" + +namespace m5 +{ + class M5IOE1_Class : public IOExpander_Base + { + public: + static constexpr std::uint8_t DEFAULT_ADDRESS = 0x4F; + static constexpr std::uint32_t DEFAULT_FREQ = 100000; + + enum gpio_t : std::uint8_t + { gpio1 = 0 + , gpio2 = 1 + , gpio3 = 2 + , gpio4 = 3 + , gpio5 = 4 + , gpio6 = 5 + , gpio7 = 6 + , gpio8 = 7 + , gpio9 = 8 + , gpio10 = 9 + , gpio11 = 10 + , gpio12 = 11 + , gpio13 = 12 + , gpio14 = 13 + }; + + enum pwm_channel_t : std::uint8_t + { pwm_ch1 = 0 // IO_9 + , pwm_ch2 = 1 // IO_8 + , pwm_ch3 = 2 // IO_11 + , pwm_ch4 = 3 // IO_10 + }; + + M5IOE1_Class(std::uint8_t i2c_addr = DEFAULT_ADDRESS, std::uint32_t freq = DEFAULT_FREQ, m5::I2C_Class* i2c = &m5::In_I2C) + : IOExpander_Base(i2c_addr, freq, i2c) + {} + + bool begin(); + + void setDirection(uint8_t pin, bool direction) override; + + void enablePull(uint8_t pin, bool enablePull) override; + + void setPullMode(uint8_t pin, bool mode) override; + + void setHighImpedance(uint8_t pin, bool enable) override; + + bool getWriteValue(uint8_t pin) override; + + void digitalWrite(uint8_t pin, bool level) override; + + bool digitalRead(uint8_t pin) override; + + void setPwmFrequency(std::uint16_t frequency); + + void setPwmDuty(std::uint8_t channel, std::uint16_t duty12, bool enable = true, bool polarity = false); + + void resetIrq() override; + + void disableIrq() override; + + void enableIrq() override; + + private: + static bool _isValidPin(uint8_t pin) { return pin < 14; } + static std::uint8_t _regForPin(std::uint8_t reg_low, std::uint8_t pin) { return reg_low + (pin >> 3); } + static std::uint8_t _bitForPin(std::uint8_t pin) { return 1 << (pin & 7); } + }; +} + +#endif diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index efa4364..4be4353 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -3,6 +3,7 @@ #include "../M5Unified.hpp" #include "Power_Class.hpp" +#include "M5IOE1_Class.hpp" #if !defined (M5UNIFIED_PC_BUILD) @@ -35,7 +36,6 @@ namespace m5 #if defined (CONFIG_IDF_TARGET_ESP32S3) static constexpr uint8_t aw9523_i2c_addr = 0x58; static constexpr uint8_t powerhub_i2c_addr = 0x50; - static constexpr uint8_t m5ioe1_i2c_addr = 0x4F; static constexpr uint8_t ip2315_i2c_addr = 0x75; // M5PaperMono USB fast-charger static constexpr int M5PaperS3_CHG_STAT_PIN = GPIO_NUM_4; @@ -215,17 +215,12 @@ namespace m5 // M5IOE1: PWM1 drives IO9 (G9 motor). REG_PWM_FREQ 0x25/0x26 Hz LE; REG_PWM1_DUTY 0x1B/0x1C (bit7 EN). constexpr uint16_t motor_pwm_hz = 2000; - M5.In_I2C.writeRegister8(m5ioe1_i2c_addr, 0x23, 0x00, i2c_freq); // REG_I2C_CFG: disable I2C sleep - uint8_t pwm_freq_le[2] = { - static_cast(motor_pwm_hz & 0xFF), - static_cast((motor_pwm_hz >> 8) & 0xFF), - }; - M5.In_I2C.writeRegister(m5ioe1_i2c_addr, 0x25, pwm_freq_le, sizeof(pwm_freq_le), i2c_freq); + auto& ioe1 = static_cast(M5.getIOExpander(0)); + ioe1.setPwmFrequency(motor_pwm_hz); // IO9 (G9 motor / PWM1): push-pull output, duty off until setVibration - M5.In_I2C.bitOff(m5ioe1_i2c_addr, 0x14, 0b00000001, i2c_freq); - M5.In_I2C.bitOn(m5ioe1_i2c_addr, 0x04, 0b00000001, i2c_freq); - M5.In_I2C.writeRegister8(m5ioe1_i2c_addr, 0x1B, 0x00, i2c_freq); - M5.In_I2C.writeRegister8(m5ioe1_i2c_addr, 0x1C, 0x00, i2c_freq); // PWM off at boot + ioe1.setHighImpedance(M5IOE1_Class::gpio9, false); + ioe1.setDirection(M5IOE1_Class::gpio9, true); + ioe1.setPwmDuty(M5IOE1_Class::pwm_ch1, 0, false); // PWM off at boot } break; @@ -268,10 +263,12 @@ namespace m5 // IP2316 stays off the I2C bus while IO11 is low, and answers ~1.3ms after high // (measured), so polling its address is enough; no fixed startup delay is needed. // IO11 = bit10 of the 16-bit GPIO regs = bit2 of the high byte (P14-P9). - M5.In_I2C.writeRegister8(m5ioe1_i2c_addr, 0x23, 0x00, i2c_freq); // I2C_CFG: disable IOE1 idle-sleep - M5.In_I2C.bitOff(m5ioe1_i2c_addr, 0x14, 1 << 2, i2c_freq); // GPIO_DRV_H: IO11 push-pull - M5.In_I2C.bitOn (m5ioe1_i2c_addr, 0x04, 1 << 2, i2c_freq); // GPIO_MODE_H: IO11 output - M5.In_I2C.bitOn (m5ioe1_i2c_addr, 0x06, 1 << 2, i2c_freq); // GPIO_OUT_H: IO11 high + { + auto& ioe1 = M5.getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio11, false); + ioe1.setDirection(M5IOE1_Class::gpio11, true); + ioe1.digitalWrite(M5IOE1_Class::gpio11, true); + } // Wait for the IP2316 to wake, then enable battery charging (SYS_CTL1 0x01 bit0 = EN_CHG). for (int i = 0; i < 64 && !M5.In_I2C.scanID(ip2315_i2c_addr, i2c_freq); ++i) {} M5.In_I2C.bitOn(ip2315_i2c_addr, 0x01, 1 << 0, i2c_freq); @@ -1094,7 +1091,7 @@ namespace m5 case board_t::board_M5Tab5: for (int i = 0; i < 10; ++i) { - M5.getIOExpander(1).digitalWrite(4, i & 1); // io1.pin4 == PWROFF_PLUSE + M5.getIOExpander(1).digitalWrite(4, i & 1); // io1.gpio4 == PWROFF_PLUSE m5gfx::delay(50); } break; @@ -1894,7 +1891,7 @@ namespace m5 #endif #if defined (CONFIG_IDF_TARGET_ESP32P4) case board_t::board_M5Tab5: - return M5.getIOExpander(1).digitalRead(6) // io1.pin6 == CHG_STAT + return M5.getIOExpander(1).digitalRead(6) // io1.gpio6 == CHG_STAT ? is_charging_t::is_charging : is_charging_t::is_discharging; #endif default: @@ -2022,19 +2019,15 @@ namespace m5 if (M5.getBoard() == board_t::board_M5StopWatch) { // M5IOE1 PWM1 (0x1B/0x1C) -> pin IO9 / G9 motor; duty 12-bit in [11:0], EN=bit7 of high byte. + auto& ioe1 = static_cast(M5.getIOExpander(0)); if (level == 0) { - uint8_t pwm_off[2] = { 0x00, 0x00 }; - M5.In_I2C.writeRegister(m5ioe1_i2c_addr, 0x1B, pwm_off, sizeof(pwm_off), i2c_freq); + ioe1.setPwmDuty(M5IOE1_Class::pwm_ch1, 0, false); } else { // PWM needs IO9 in output mode (M5IOE1 pin index 8 -> GPIO_MODE_H bit0). - M5.In_I2C.bitOff(m5ioe1_i2c_addr, 0x14, 0b00000001, i2c_freq); - M5.In_I2C.bitOn(m5ioe1_i2c_addr, 0x04, 0b00000001, i2c_freq); + ioe1.setHighImpedance(M5IOE1_Class::gpio9, false); + ioe1.setDirection(M5IOE1_Class::gpio9, true); uint16_t duty12 = static_cast((static_cast(level) * 0x0FFFu) / 255u); - uint8_t pwm_on[2] = { - static_cast(duty12 & 0xFF), - static_cast(((duty12 >> 8) & 0x0Fu) | 0x80u), - }; - M5.In_I2C.writeRegister(m5ioe1_i2c_addr, 0x1B, pwm_on, sizeof(pwm_on), i2c_freq); + ioe1.setPwmDuty(M5IOE1_Class::pwm_ch1, duty12); } return; }