1. Support for the M5Unified I2C_Class driver

This commit is contained in:
onexs-xsi
2026-02-26 15:10:20 +08:00
parent 26601f40c7
commit 5bad7526f4
4 changed files with 262 additions and 12 deletions
+4
View File
@@ -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)
+131 -9
View File
@@ -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, &regVal)) 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;
}
+24
View File
@@ -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 Hz100000 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
// 内部辅助函数
+103 -3
View File
@@ -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_CONFIGi2c_bus.h 使用 driver/i2c.h,无冲突 / no conflict
#elif defined(_DRIVER_I2C_H_)
#elif defined(_DRIVER_I2C_H_) || (defined(__cplusplus) && __has_include(<utility/I2C_Class.hpp>))
#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 <i2c_bus.h>
#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(<utility/I2C_Class.hpp>)
#define M5PM1_HAS_M5UNIFIED_I2C 1
#include <utility/I2C_Class.hpp>
#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 APIIDF < 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__