From 5bad7526f4651211a77cd6b45f07a08b81366441 Mon Sep 17 00:00:00 2001 From: onexs-xsi <850559025@qq.com> Date: Thu, 26 Feb 2026 15:10:20 +0800 Subject: [PATCH] 1. Support for the M5Unified I2C_Class driver --- CMakeLists.txt | 4 ++ src/M5PM1.cpp | 140 ++++++++++++++++++++++++++++++++++++++--- src/M5PM1.h | 24 +++++++ src/M5PM1_i2c_compat.h | 106 ++++++++++++++++++++++++++++++- 4 files changed, 262 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bf15769..b24e5a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,3 +7,7 @@ idf_component_register( "driver" "espressif__i2c_bus" ) + +# M5Unified 为可选依赖:组件存在时自动添加,否则静默跳过 +# M5Unified is optional: added automatically if present, silently ignored otherwise. +idf_component_optional_requires(PRIVATE M5Unified) diff --git a/src/M5PM1.cpp b/src/M5PM1.cpp index f572132..5b22c44 100644 --- a/src/M5PM1.cpp +++ b/src/M5PM1.cpp @@ -172,6 +172,10 @@ M5PM1::M5PM1() _sda = -1; _scl = -1; _port = I2C_NUM_0; +#if M5PM1_HAS_M5UNIFIED_I2C + _m5_i2c = nullptr; + _commFreq = M5PM1_I2C_FREQ_100K; +#endif #endif } @@ -824,6 +828,84 @@ m5pm1_err_t M5PM1::begin(i2c_bus_handle_t bus, uint8_t addr, uint32_t speed) } #endif // M5PM1_HAS_I2C_BUS +#if M5PM1_HAS_M5UNIFIED_I2C +m5pm1_err_t M5PM1::begin(m5::I2C_Class* i2c, uint8_t addr, uint32_t speed) +{ + if (i2c == nullptr || !i2c->isEnabled()) { + M5PM1_LOG_E(TAG, "M5Unified I2C_Class is null or not initialized"); + return M5PM1_ERR_I2C_CONFIG; + } + + _addr = addr; + _m5_i2c = i2c; + _busExternal = true; + _i2cDriverType = M5PM1_I2C_DRIVER_M5UNIFIED; + + // 步骤1:校验用户频率并记录 + // Step 1: Validate requested speed + if (!_isValidI2cFrequency(speed)) { + M5PM1_LOG_W(TAG, "Invalid I2C frequency: %lu Hz. Falling back to 100KHz.", (unsigned long)speed); + _requestedSpeed = M5PM1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + + // 步骤2:以 100KHz 发送唤醒信号 + // Step 2: Send wake signal at 100KHz + _commFreq = M5PM1_I2C_FREQ_100K; + M5PM1_M5UNIFIED_SEND_WAKE(_m5_i2c, _addr, _commFreq); + M5PM1_DELAY_MS(10); + + // 步骤3:验证设备通信(失败则等待 800ms 重试一次) + // Step 3: Verify device communication (retry once after 800ms) + if (!_initDevice()) { + M5PM1_LOG_W(TAG, "Device init failed, retrying after 800ms..."); + M5PM1_DELAY_MS(800); + M5PM1_M5UNIFIED_SEND_WAKE(_m5_i2c, _addr, _commFreq); + M5PM1_DELAY_MS(10); + if (!_initDevice()) { + // 100K 再次失败,尝试 400K + // 100K failed again, try 400K + M5PM1_LOG_W(TAG, "Device init failed at 100KHz (retry), trying 400KHz..."); + _commFreq = M5PM1_I2C_FREQ_400K; + M5PM1_M5UNIFIED_SEND_WAKE(_m5_i2c, _addr, _commFreq); + M5PM1_DELAY_MS(10); + if (!_initDevice()) { + M5PM1_LOG_E(TAG, "Failed at 100KHz (twice) and 400KHz"); + _i2cDriverType = M5PM1_I2C_DRIVER_NONE; + _m5_i2c = nullptr; + return M5PM1_ERR_I2C_COMM; + } + } + } + _initialized = true; + + // 步骤4:配置设备 I2C 参数(关闭睡眠 + 目标频率) + // Step 4: Configure device I2C (disable sleep + target speed) + m5pm1_i2c_speed_t targetSpeed = + (_requestedSpeed == M5PM1_I2C_FREQ_400K) ? M5PM1_I2C_SPEED_400K : M5PM1_I2C_SPEED_100K; + if (setI2cConfig(0, targetSpeed) != M5PM1_OK) { + M5PM1_LOG_W(TAG, "Failed to set I2C config"); + } + + // 步骤5:切换通信频率到目标亟(M5Unified 无需重装驱动,直接更新 _commFreq) + // Step 5: Switch to target frequency (no driver reinstall needed; update _commFreq only) + _commFreq = _requestedSpeed; + + // 步骤6:刷新快照并完成初始化 + // Step 6: Refresh snapshot and finish initialization + _lastCommTime = M5PM1_GET_TIME_MS(); + if (!_snapshotAll()) { + _clearAll(); + } + + _initialized = true; + M5PM1_LOG_I(TAG, "M5PM1 initialized via M5Unified I2C at address 0x%02X (%lu Hz)", _addr, + (unsigned long)_requestedSpeed); + return M5PM1_OK; +} +#endif // M5PM1_HAS_M5UNIFIED_I2C + #endif // ARDUINO // ============================ @@ -1393,6 +1475,11 @@ bool M5PM1::_writeReg(uint8_t reg, uint8_t value) case M5PM1_I2C_DRIVER_LEGACY: success = M5PM1_I2C_LEGACY_WRITE_BYTE(_port, _addr, reg, value) == ESP_OK; break; +#endif +#if M5PM1_HAS_M5UNIFIED_I2C + case M5PM1_I2C_DRIVER_M5UNIFIED: + success = M5PM1_M5UNIFIED_WRITE_BYTE(_m5_i2c, _addr, reg, value, _commFreq); + break; #endif default: success = false; @@ -1434,6 +1521,11 @@ bool M5PM1::_writeReg16(uint8_t reg, uint16_t value) case M5PM1_I2C_DRIVER_LEGACY: success = M5PM1_I2C_LEGACY_WRITE_REG16(_port, _addr, reg, value) == ESP_OK; break; +#endif +#if M5PM1_HAS_M5UNIFIED_I2C + case M5PM1_I2C_DRIVER_M5UNIFIED: + success = M5PM1_M5UNIFIED_WRITE_REG16(_m5_i2c, _addr, reg, value, _commFreq); + break; #endif default: success = false; @@ -1475,6 +1567,11 @@ bool M5PM1::_readReg(uint8_t reg, uint8_t* value) case M5PM1_I2C_DRIVER_LEGACY: success = M5PM1_I2C_LEGACY_READ_BYTE(_port, _addr, reg, value) == ESP_OK; break; +#endif +#if M5PM1_HAS_M5UNIFIED_I2C + case M5PM1_I2C_DRIVER_M5UNIFIED: + success = M5PM1_M5UNIFIED_READ_BYTE(_m5_i2c, _addr, reg, value, _commFreq); + break; #endif default: success = false; @@ -1516,6 +1613,11 @@ bool M5PM1::_readReg16(uint8_t reg, uint16_t* value) case M5PM1_I2C_DRIVER_LEGACY: success = M5PM1_I2C_LEGACY_READ_REG16(_port, _addr, reg, value) == ESP_OK; break; +#endif +#if M5PM1_HAS_M5UNIFIED_I2C + case M5PM1_I2C_DRIVER_M5UNIFIED: + success = M5PM1_M5UNIFIED_READ_REG16(_m5_i2c, _addr, reg, value, _commFreq); + break; #endif default: success = false; @@ -1557,6 +1659,11 @@ bool M5PM1::_writeBytes(uint8_t reg, const uint8_t* data, uint8_t len) case M5PM1_I2C_DRIVER_LEGACY: success = M5PM1_I2C_LEGACY_WRITE_BYTES(_port, _addr, reg, len, data) == ESP_OK; break; +#endif +#if M5PM1_HAS_M5UNIFIED_I2C + case M5PM1_I2C_DRIVER_M5UNIFIED: + success = M5PM1_M5UNIFIED_WRITE_BYTES(_m5_i2c, _addr, reg, len, data, _commFreq); + break; #endif default: success = false; @@ -1598,6 +1705,11 @@ bool M5PM1::_readBytes(uint8_t reg, uint8_t* data, uint8_t len) case M5PM1_I2C_DRIVER_LEGACY: success = M5PM1_I2C_LEGACY_READ_BYTES(_port, _addr, reg, len, data) == ESP_OK; break; +#endif +#if M5PM1_HAS_M5UNIFIED_I2C + case M5PM1_I2C_DRIVER_M5UNIFIED: + success = M5PM1_M5UNIFIED_READ_BYTES(_m5_i2c, _addr, reg, len, data, _commFreq); + break; #endif default: success = false; @@ -2392,7 +2504,7 @@ m5pm1_err_t M5PM1::boostSetPowerHold(bool enable) if (!_readReg(M5PM1_REG_HOLD_CFG, ®Val)) return M5PM1_ERR_I2C_COMM; if (enable) { - regVal |= (1 << 6); // DCDC bit + regVal |= (1 << 6); // BOOST bit } else { regVal &= ~(1 << 6); } @@ -2767,9 +2879,11 @@ m5pm1_err_t M5PM1::setPwmConfig(m5pm1_pwm_channel_t channel, bool enable, bool p } } - m5pm1_err_t err = setPwmDuty12bit(channel, duty12, polarity, enable); + // 先设频率再设 duty,避免频率设置失败时 duty 已以旧频率写入 + // Set frequency first, then duty, to avoid duty being written with old frequency if frequency fails + m5pm1_err_t err = setPwmFrequency(frequency); if (err != M5PM1_OK) return err; - return setPwmFrequency(frequency); + return setPwmDuty12bit(channel, duty12, polarity, enable); } m5pm1_err_t M5PM1::analogWrite(m5pm1_pwm_channel_t channel, uint8_t value) @@ -4157,8 +4271,9 @@ m5pm1_err_t M5PM1::setAw8737aPulse(m5pm1_gpio_num_t pin, m5pm1_aw8737a_pulse_t n // 检查引脚是否为输出模式,如果不是则自动配置为推挽输出 // Check if pin is output mode, if not auto-configure as push-pull output - if (_pinStatus[pin].mode != M5PM1_GPIO_MODE_OUTPUT) { - M5PM1_LOG_I(TAG, "AW8737A: Pin %d not output mode, auto-configuring as push-pull output", pin); + if (!_cacheValid || _pinStatus[pin].mode != M5PM1_GPIO_MODE_OUTPUT) { + M5PM1_LOG_I(TAG, "AW8737A: Pin %d not output mode (or cache invalid), auto-configuring as push-pull output", + pin); pinMode(pin, OUTPUT); } @@ -4228,8 +4343,9 @@ m5pm1_err_t M5PM1::refreshAw8737aPulse() // 检查引脚是否为输出模式,如果不是则自动配置为推挽输出 // Check if pin is output mode, if not auto-configure as push-pull output - if (_pinStatus[_aw8737aPin].mode != M5PM1_GPIO_MODE_OUTPUT) { - M5PM1_LOG_I(TAG, "AW8737A: Pin %d not output mode, auto-configuring as push-pull output", _aw8737aPin); + if (!_cacheValid || _pinStatus[_aw8737aPin].mode != M5PM1_GPIO_MODE_OUTPUT) { + M5PM1_LOG_I(TAG, "AW8737A: Pin %d not output mode (or cache invalid), auto-configuring as push-pull output", + _aw8737aPin); pinMode(_aw8737aPin, OUTPUT); } @@ -4729,6 +4845,10 @@ m5pm1_err_t M5PM1::sendWakeSignal() #if !M5PM1_HAS_I2C_MASTER && !M5PM1_HAS_I2C_BUS case M5PM1_I2C_DRIVER_LEGACY: return M5PM1_I2C_LEGACY_SEND_WAKE(_port, _addr); +#endif +#if M5PM1_HAS_M5UNIFIED_I2C + case M5PM1_I2C_DRIVER_M5UNIFIED: + return M5PM1_M5UNIFIED_SEND_WAKE(_m5_i2c, _addr, _commFreq) ? M5PM1_OK : M5PM1_ERR_I2C_COMM; #endif default: return M5PM1_ERR_INTERNAL; @@ -4764,7 +4884,7 @@ m5pm1_err_t M5PM1::updateSnapshot() m5pm1_snapshot_verify_t M5PM1::verifySnapshot() { - m5pm1_snapshot_verify_t result = {0}; + m5pm1_snapshot_verify_t result = {}; result.consistent = true; if (!_initialized) { @@ -4844,7 +4964,9 @@ m5pm1_snapshot_verify_t M5PM1::verifySnapshot() uint8_t actualChannel = (ctrl >> 1) & 0x07; bool actualBusy = (ctrl & 0x01) != 0; - if (actualChannel != _adcState.channel || actualBusy != _adcState.busy || value != _adcState.lastValue) { + // 仅比较 channel 和 busy 状态,不比较 lastValue(ADC 活跃时值持续变化会导致误报) + // Only compare channel and busy status, not lastValue (active ADC causes false positives) + if (actualChannel != _adcState.channel || actualBusy != _adcState.busy) { result.adc_mismatch = true; result.consistent = false; } diff --git a/src/M5PM1.h b/src/M5PM1.h index aeee586..6844fcd 100644 --- a/src/M5PM1.h +++ b/src/M5PM1.h @@ -1089,6 +1089,22 @@ public: return M5PM1_ERR_NOT_SUPPORTED; } #endif + +#if M5PM1_HAS_M5UNIFIED_I2C + /** + * @brief Initialize with M5Unified I2C_Class instance (ESP-IDF) + * @note The I2C bus must already be initialized (i2c->begin() called) by the caller. + * M5PM1 borrows the I2C_Class; caller retains ownership and lifecycle. + * 不负责驱动安装或释放,I2C 由调用方全程管理。 + * @param i2c 已 begin() 的 m5::I2C_Class 指针 + * Pointer to an already begin()-ed m5::I2C_Class + * @param addr I2C 地址 / I2C address (default 0x6E) + * @param speed I2C 速率(Hz,100000 或 400000)/ I2C speed in Hz (100000 or 400000) + * @return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t begin(m5::I2C_Class* i2c, uint8_t addr = M5PM1_DEFAULT_ADDR, uint32_t speed = M5PM1_I2C_FREQ_DEFAULT); +#endif // M5PM1_HAS_M5UNIFIED_I2C + #endif /** @@ -2909,6 +2925,14 @@ private: int _sda; int _scl; i2c_port_t _port; + + // M5Unified I2C_Class 借用句柄及当前通信频率 + // Borrowed M5Unified I2C_Class handle and current communication frequency +#if M5PM1_HAS_M5UNIFIED_I2C + m5::I2C_Class* _m5_i2c; + uint32_t _commFreq; // M5UNIFIED 路径专用:init 时从 100K 起,完成后切换至 _requestedSpeed + // M5UNIFIED path: starts at 100K during init, switches to _requestedSpeed after +#endif #endif // 内部辅助函数 diff --git a/src/M5PM1_i2c_compat.h b/src/M5PM1_i2c_compat.h index 8c7b159..244eb98 100644 --- a/src/M5PM1_i2c_compat.h +++ b/src/M5PM1_i2c_compat.h @@ -177,10 +177,10 @@ static inline void M5PM1_I2C_ARDUINO_SEND_WAKE(TwoWire *wire, uint8_t addr) // IDF >= 5.3.0 #if defined(CONFIG_I2C_BUS_BACKWARD_CONFIG) #define M5PM1_HAS_I2C_BUS 1 // BACKWARD_CONFIG:i2c_bus.h 使用 driver/i2c.h,无冲突 / no conflict -#elif defined(_DRIVER_I2C_H_) +#elif defined(_DRIVER_I2C_H_) || (defined(__cplusplus) && __has_include()) #define M5PM1_HAS_I2C_BUS \ - 0 // driver/i2c.h 已提前包含 + 无 BACKWARD_CONFIG → 冲突风险,禁用 - // driver/i2c.h already included + no BACKWARD_CONFIG → conflict risk, disabled + 0 // driver/i2c.h 已包含或 M5Unified 可用(将包含 driver/i2c.h)→ i2c_config_t 冲突风险,禁用 + // driver/i2c.h already included or M5Unified available (will include driver/i2c.h) → conflict risk, disabled #else #define M5PM1_HAS_I2C_BUS 1 // driver/i2c.h 尚未包含,无冲突风险 / driver/i2c.h not yet included, no conflict #endif @@ -211,6 +211,10 @@ static inline void M5PM1_I2C_ARDUINO_SEND_WAKE(TwoWire *wire, uint8_t addr) #define M5PM1_HAS_I2C_MASTER 0 #endif +// ============================ +// M5Unified I2C_Class 检测 +// M5Unified I2C_Class Detection +// ============================ #if M5PM1_HAS_I2C_BUS #include #else @@ -220,6 +224,20 @@ typedef void *i2c_bus_handle_t; typedef void *i2c_bus_device_handle_t; #endif +// +// 仅 C++ 环境下检测 M5Unified 的 I2C_Class 是否可用。 +// Detects whether M5Unified's I2C_Class is available (C++ only). +// 启用条件:__cplusplus 已定义(即编译单元为 C++)且 utility/I2C_Class.hpp 可被 include。 +// Enabled when: __cplusplus is defined (i.e., C++ TU) AND utility/I2C_Class.hpp is reachable. +// 注意:i2c_bus.h 必须先于 I2C_Class.hpp 包含,避免 i2c_config_t typedef 冲突。 +// Note: i2c_bus.h must be included before I2C_Class.hpp to avoid i2c_config_t typedef conflict. +#if defined(__cplusplus) && __has_include() +#define M5PM1_HAS_M5UNIFIED_I2C 1 +#include +#else +#define M5PM1_HAS_M5UNIFIED_I2C 0 +#endif + #ifdef __cplusplus extern "C" { #endif @@ -241,6 +259,10 @@ typedef enum { M5PM1_I2C_DRIVER_LEGACY, // 传统 driver/i2c.h API(IDF < 5.3.0 且无 i2c_bus) // Legacy driver/i2c.h API (IDF < 5.3.0 without i2c_bus) #endif +#if M5PM1_HAS_M5UNIFIED_I2C + M5PM1_I2C_DRIVER_M5UNIFIED, // 借用 M5Unified I2C_Class 通信(不负责驱动安装/释放) + // Borrow M5Unified I2C_Class for communication (no driver lifecycle) +#endif } m5pm1_i2c_driver_t; // ============================ @@ -504,6 +526,84 @@ static inline esp_err_t M5PM1_I2C_MASTER_SEND_WAKE(i2c_master_bus_handle_t bus, } #endif +// ============================ +// M5Unified I2C_Class 通信封装 +// M5Unified I2C_Class Communication Wrappers +// (C++ only — m5::I2C_Class is a C++ class) +// ============================ +#if M5PM1_HAS_M5UNIFIED_I2C +#ifdef __cplusplus + +#ifndef M5PM1_M5UNIFIED_READ_BYTE +static inline bool M5PM1_M5UNIFIED_READ_BYTE(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint8_t *data, + uint32_t freq) +{ + return i2c->readRegister(addr, reg, data, 1, freq); +} +#endif + +#ifndef M5PM1_M5UNIFIED_READ_BYTES +static inline bool M5PM1_M5UNIFIED_READ_BYTES(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, size_t len, uint8_t *data, + uint32_t freq) +{ + return i2c->readRegister(addr, reg, data, len, freq); +} +#endif + +#ifndef M5PM1_M5UNIFIED_READ_REG16 +static inline bool M5PM1_M5UNIFIED_READ_REG16(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint16_t *data, + uint32_t freq) +{ + uint8_t buf[2]; + if (!i2c->readRegister(addr, reg, buf, 2, freq)) return false; + // 小端模式:低字节在前 / Little-endian: low byte first + *data = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8); + return true; +} +#endif + +#ifndef M5PM1_M5UNIFIED_WRITE_BYTE +static inline bool M5PM1_M5UNIFIED_WRITE_BYTE(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint8_t data, + uint32_t freq) +{ + return i2c->writeRegister8(addr, reg, data, freq); +} +#endif + +#ifndef M5PM1_M5UNIFIED_WRITE_BYTES +static inline bool M5PM1_M5UNIFIED_WRITE_BYTES(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, size_t len, + const uint8_t *data, uint32_t freq) +{ + return i2c->writeRegister(addr, reg, data, len, freq); +} +#endif + +#ifndef M5PM1_M5UNIFIED_WRITE_REG16 +static inline bool M5PM1_M5UNIFIED_WRITE_REG16(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint16_t data, + uint32_t freq) +{ + uint8_t buf[2]; + // 小端模式:低字节在前 / Little-endian: low byte first + buf[0] = (uint8_t)(data & 0xFF); + buf[1] = (uint8_t)((data >> 8) & 0xFF); + return i2c->writeRegister(addr, reg, buf, 2, freq); +} +#endif + +#ifndef M5PM1_M5UNIFIED_SEND_WAKE +// 产生 I2C START 信号以唤醒处于睡眠的 PM1 +// Generate I2C START signal to wake PM1 from sleep +static inline bool M5PM1_M5UNIFIED_SEND_WAKE(m5::I2C_Class *i2c, uint8_t addr, uint32_t freq) +{ + i2c->start(addr, false, freq); + i2c->stop(); + return true; +} +#endif + +#endif // __cplusplus +#endif // M5PM1_HAS_M5UNIFIED_I2C + #endif // ARDUINO #endif // __M5PM1_I2C_COMPAT_H__