diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bf15769 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register( + SRCS + "src/M5PM1.cpp" + INCLUDE_DIRS + "src" + REQUIRES + "driver" + "espressif__i2c_bus" +) diff --git a/README.md b/README.md index 2be4654..23470bc 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,207 @@ -# Product Name +# M5PM1 ## Overview -### SKU:xxx +**SKU: N/A** -Description of the product +M5PM1 is a dual-platform (ESP-IDF & Arduino) driver library for M5Stack PM1 Power Management IC. It provides comprehensive power management features including: -## Related Link +- Battery charging and monitoring +- Multiple power rails (DCDC 5V, LDO 3.3V) +- 5 GPIO pins with various functions (GPIO/IRQ/WAKE/PWM/ADC) +- PWM output (2 channels) +- ADC input (2 channels + temperature) +- NeoPixel LED control (up to 32 LEDs) +- Watchdog timer +- RTC RAM (32 bytes, retained in sleep) +- I2C auto-sleep/wake feature -- [Document & Datasheet](https://docs.m5stack.com/en/unit/product_Link) +## Features -## Required Libraries: +- **Dual-Platform Support**: Works with both Arduino and ESP-IDF frameworks +- **Arduino-Style API**: Familiar `pinMode()`, `digitalWrite()`, `digitalRead()` functions +- **I2C Auto-Wake**: Automatically handles PM1 sleep mode wake-up +- **Comprehensive Power Management**: Battery charging, voltage monitoring, power hold -- [Adafruit_BMP280_Library](https://github.com/adafruit/Required_Libraries_Link) +## Hardware -## License +- **I2C Address**: 0x6E (default) +- **I2C Speed**: 100KHz (default), 400KHz (configurable) +- **GPIO Pins**: 5 (GPIO0-GPIO4) +- **PWM Channels**: 2 (GPIO3, GPIO4) +- **ADC Channels**: 2 (GPIO1, GPIO2) + Internal Temperature -- [Product Name- MIT](LICENSE) +## Pin Functions -## Remaining steps(Editorial Staff Look,After following the steps, remember to delete all the content below) +| GPIO | Special Function | Description | +|------|-----------------|-------------| +| GPIO0 | LED_EN | NeoPixel LED enable | +| GPIO1 | ADC1 | Analog input channel 1 | +| GPIO2 | ADC2 | Analog input channel 2 | +| GPIO3 | PWM0 | PWM output channel 0 | +| GPIO4 | PWM1 | PWM output channel 1 | -1. Change [clang format check path](./.github/workflows/clang-format-check.yml#L42-L47). -2. Add License content to [LICENSE](/LICENSE). -3. Change link on line 78 of [bug-report.yml](./.github/ISSUE_TEMPLATE/bug-report.yml#L79). +## Usage + +### Arduino ```cpp -Example -# M5Unit-ENV +#include -## Overview +M5PM1 pm1; -### SKU:U001 & U001-B & U001-C +void setup() { + Serial.begin(115200); + Wire.begin(38, 39); // SDA, SCL -Contains M5Stack-**UNIT ENV** series related case programs.ENV is an environmental sensor with integrated SHT30 and QMP6988 internally to detect temperature, humidity, and atmospheric pressure data. + if (pm1.begin(&Wire) != M5PM1_OK) { + Serial.println("PM1 init failed!"); + while (1) delay(100); + } + + // Use Arduino-style API + pm1.pinMode(0, OUTPUT); + pm1.digitalWrite(0, HIGH); + + // Read battery voltage + uint16_t vbat; + if (pm1.readVbat(&vbat) == M5PM1_OK) { + Serial.printf("Battery: %d mV\n", vbat); + } +} + +void loop() { + // Toggle GPIO0 + pm1.digitalWrite(0, HIGH); + delay(500); + pm1.digitalWrite(0, LOW); + delay(500); +} +``` + +### ESP-IDF + +```cpp +#include "M5PM1.h" + +M5PM1 pm1; + +void app_main() { + // Initialize with self-created I2C bus + if (pm1.begin(I2C_NUM_0, M5PM1_DEFAULT_ADDR, 21, 22, 100000) != M5PM1_OK) { + ESP_LOGE("PM1", "Init failed!"); + return; + } + + // Or use existing i2c_master_bus_handle_t + // pm1.begin(existing_bus_handle, M5PM1_DEFAULT_ADDR, 100000); + + // Configure GPIO + pm1.gpioSetFunc(M5PM1_GPIO_NUM_0, M5PM1_GPIO_FUNC_GPIO); + pm1.gpioSetMode(M5PM1_GPIO_NUM_0, M5PM1_GPIO_MODE_OUTPUT); + pm1.gpioSetOutput(M5PM1_GPIO_NUM_0, 1); +} +``` + +## API Reference + +### Initialization + +| Method | Description | +|--------|-------------| +| `begin()` | Initialize the PM1 device | +| `setAutoWakeEnable()` | Enable/disable auto-wake feature | +| `sendWakeSignal()` | Manually send wake signal | + +### GPIO Functions + +| Method | Description | +|--------|-------------| +| `pinMode()` | Set GPIO mode (Arduino-style) | +| `digitalWrite()` | Set GPIO output (Arduino-style) | +| `digitalRead()` | Read GPIO input (Arduino-style) | +| `gpioSetFunc()` | Set GPIO function (GPIO/IRQ/WAKE/OTHER) | +| `gpioSetMode()` | Set GPIO direction | +| `gpioSetOutput()` | Set GPIO output level | +| `gpioGetInput()` | Get GPIO input level | +| `gpioSetPull()` | Set GPIO pull-up/pull-down | +| `gpioSetDrive()` | Set GPIO drive mode | + +### Power Management + +| Method | Description | +|--------|-------------| +| `getPowerSource()` | Get current power source | +| `getWakeSource()` | Get wake source flags | +| `setPowerConfig()` | Set power configuration | +| `setChargeEnable()` | Enable/disable battery charging | +| `setDcdcEnable()` | Enable/disable 5V DCDC | +| `setLdoEnable()` | Enable/disable 3.3V LDO | +| `shutdown()` | Shutdown the system | +| `reboot()` | Reboot the system | + +### Voltage Reading + +| Method | Description | +|--------|-------------| +| `readVref()` | Read reference voltage | +| `readVbat()` | Read battery voltage | +| `readVin()` | Read VIN voltage | +| `read5VInOut()` | Read 5VINOUT voltage | + +### PWM Functions + +| Method | Description | +|--------|-------------| +| `setPwmFrequency()` | Set PWM frequency | +| `getPwmFrequency()` | Get PWM frequency | +| `setPwmDuty()` | Set PWM duty (percentage) | +| `getPwmDuty()` | Get PWM duty (percentage) | +| `setPwmDuty12bit()` | Set PWM duty (12-bit) | +| `getPwmDuty12bit()` | Get PWM duty (12-bit) | +| `analogWrite()` | Arduino-compatible PWM output | + +### ADC Functions + +| Method | Description | +|--------|-------------| +| `analogRead()` | Read ADC value | +| `isAdcBusy()` | Check ADC busy status | +| `disableAdc()` | Disable ADC conversion | + +### NeoPixel Functions + +| Method | Description | +|--------|-------------| +| `setLeds()` | Configure and set all LEDs | +| `setLedCount()` | Set LED count | +| `setLedColor()` | Set LED color | +| `refreshLeds()` | Refresh LED display | +| `disableLeds()` | Disable LED output | + +### Watchdog Functions + +| Method | Description | +|--------|-------------| +| `wdtSet()` | Set watchdog timeout | +| `wdtFeed()` | Feed the watchdog | +| `wdtGetCount()` | Get watchdog countdown | + +### RTC RAM Functions + +| Method | Description | +|--------|-------------| +| `writeRtcRAM()` | Write to RTC RAM | +| `readRtcRAM()` | Read from RTC RAM | ## Related Link -- [Document & Datasheet](https://docs.m5stack.com/en/unit/envIII) +- [Document & Datasheet](https://docs.m5stack.com/en/unit/pm1) -## Required Libraries: +## Required Libraries -- [Adafruit_BMP280_Library](https://github.com/adafruit/Adafruit_BMP280_Library) +- None (standalone library) ## License -- [M5Unit-ENV - MIT](LICENSE) -``` \ No newline at end of file +- [M5PM1 - MIT](LICENSE) diff --git a/docs/review.txt b/docs/review.txt new file mode 100644 index 0000000..3443816 --- /dev/null +++ b/docs/review.txt @@ -0,0 +1,273 @@ +PY32 PMIC 说明手册 +================================================================================ +基本信息 +================================================================================ +I2C 地址: 0x6E +硬件/固件版本: HW:5 / SW:6 (硬件修订: 5, 固件主版本: 6) +文档版本: 1.7 (2025-12-13) +代码仓库: stamp_timerpower2_code +后续软件工程师: @任张涛 + +================================================================================ +一、 芯片概述 +================================================================================ +[功能] +1. 电源管理: 支持 3.3V LDO/DCDC、5V IN/OUT、充电控制 +2. GPIO 控制: 5路可复用 GPIO (输入/输出、NeoPixel、ADC、PWM、Wake In、IRQ Out) +3. 定时唤醒: 32-bit 定时器 (最大 214748364 秒), 支持多种唤醒动作 +4. 安全监控: 看门狗, 低压保护 +5. 存储备份: 32 字节 RTC RAM + +[接口] +I2C 协议 (支持 100kHz/400kHz 模式, 默认支持 100kHz, 400kHz 需要配置) + +-------------------------------------------------------------------------------- +GPIO 功能分布表 +-------------------------------------------------------------------------------- +| 序号 | PIN | GPIO | WAKE支持情况 | IRQ | 复用功能 | +|-------|-----|------|---------------------|------|-----------| +| GPIO0 | 3 | 支持 | 支持 (与GPIO2互斥) | 支持 | Neopixel | +| GPIO1 | 17 | 支持 | 不支持 | 支持 | ADC | +| GPIO2 | 20 | 支持 | 支持 (与GPIO0互斥) | 支持 | ADC | +| GPIO3 | 13 | 支持 | 支持 (与GPIO4互斥) | 支持 | PWM | +| GPIO4 | 12 | 支持 | 支持 (与GPIO3互斥) | 支持 | PWM | + +-------------------------------------------------------------------------------- +内部 Pin 功能分布表 +-------------------------------------------------------------------------------- +| PIN | IO类型 | 上下拉 | 功能描述 | +|-----|----------|--------|----------| +| 1 | 模拟输出 | 无 | 开关电流采样: 高电平开启采样, 低电平关闭采样。默认为高电平 | +| 2 | 模拟输出 | 无 | 使能电池充电: 低电平停止充电, 高电平开启充电。默认为高电平 | +| 3 | GPIO | 无 | GPIO0, 默认为输入 | +| 4 | 电源VSS | 无 | 无 | +| 5 | 模拟输出 | 无 | LED灯控制, 输出低电平或高电平, 默认为高电平 | +| 6 | 电源VCC | 无 | 无 | +| 7 | 输入 | 上拉 | 按键检测 | +| 8 | I2C | 无 | I2C SDA | +| 9 | I2C | 无 | I2C SCL | +| 10 | 模拟输出 | 无 | 5V DC/DC控制, 输出低电平或高电平, 默认为低电平 | +| 11 | ADC | 无 | 电池ADC输入 (分压系数为1:1) | +| 12 | GPIO | 无 | GPIO4, 默认为输入 | +| 13 | GPIO | 无 | GPIO3, 默认为输入 | +| 14 | 模拟输出 | 无 | 3V3 DC/DC控制, 输出低电平或高电平, 默认为高电平 | +| 15 | 开漏输出 | 无 | 控制ESP32 BOOT, 默认为高电平 | +| 16 | ADC | 无 | 5VIN ADC输入 (分压系数为1:1) | +| 17 | GPIO | 无 | GPIO1, 默认为输入 | +| 18 | 模拟输出 | 无 | 3V3 LDO控制, 输出低电平或高电平, 默认为高电平 | +| 19 | ADC | 无 | 5VOUT ADC值输入 (分压系数为1:1) | +| 20 | GPIO | 无 | GPIO2, 默认为输入 | + +================================================================================ +二、 注意事项 +================================================================================ +1. 内部通道 ADC 的使用 + - 3个通道都有特定功能, 必须严格对应接入。 + - ADC 5VOUT: + * 5VOUT 有输入和输出功能, 5VOUT 升压打开前, 需要先检测 5VOUT 的电压, 确保上面没有电源输入才能打开升压, 否则保持输入。 + * 低电压阈值检测。 + * 5VOUT 电源插入唤醒。 + * 5VOUT 电源移除与插入检测。 + - ADC BAT: + * 低电压阈值检测。 + * 电池插入移除检测。 + - ADC 5VIN: + * 低电压阈值检测。 + * 5VIN 插入移除检测。 + +2. 低电压自动关机 + - 低电压阈值由寄存器 BATT_LVP(0x08) 决定。 + - 在 5VIN 或 5VINOUT 没有插入的前提下, 电池电压低于阈值, 会自动关机。 + +================================================================================ +逻辑流程描述 +================================================================================ + +[流程1: 低电压保护 (LVP) 与自动关机] +步骤: +1. 检测是否触发 LVP (低电压保护)。 +2. 判断: 5VIN 或 5VINOUT 是否有电? + - 是: 进入 [正常运行]。 + - 否: 判断 VBAT 是否大于最低电压? + - 是: 进入 [正常运行]。 + - 否: 进入 [DeepSleep待机]。 +注: 在没有插入 Vin 的情况下, 需要 BAT 电压大于 BATT_LVP 电压 + 100mV, 才能退出低电压待机循环。 + +[流程2: 关机逻辑] +步骤: +1. 接收到关机指令。 +2. 检查寄存器 gpio_power_hold_cfg 的 bit5/6 是否为 1? + - 否: bit5=0 (3.3V LDO关闭); bit6=0 (5V VOUT关闭)。 + - 是: bit5=1 (3.3V LDO保持); bit6=1 (5V VOUT保持)。 +3. 执行操作: + (1) 3.3V DCDC 关闭。 + (2) LED_EN 关闭。 + (3) 充电使能不受影响。 + +[流程3: 下载模式] +步骤: +1. 进入下载模式。 +2. 复位电源与gpio寄存器; 置位 power_gpio_hold 寄存器。 +3. 下电。 +4. BOOT_OUT 输出低电平。 +5. 延时 300ms。 +6. 上电。 +7. 延时 500ms。 +8. BOOT_OUT 输出高电平。 + +[流程4: 复位模式] +步骤: +1. 进入复位模式。 +2. 复位电源与gpio寄存器; 置位 power_gpio_hold 寄存器。 +3. 下电。 +4. 延时 500ms。 +5. 上电。 + +================================================================================ +特殊功能细节与寄存器访问限制 +================================================================================ +[寄存器访问] +I2C Burst 读写支持连续读写特定区块: +0x00~0x0C, 0x10~0x19, 0x20~0x2A, 0x30~0x35, 0x38~0x3D, 0x40~0x45, 0x48~0x4A, 0x50、0x53, 0x60~0x9F, 0xA0~0xBF。 +超出范围需分次操作。 + +[GPIO 限制] +1. GPIO1 与 SDA 共用中断线, 不可用干唤醒。 +2. GPIO0/2 共用中断线; GPIO3/4 共用中断线 (均为互斥使用)。 +3. GPIO_WAKE_EN (0x18) 和 GPIO_WAKE_CFG (0x19) 的配置不会因为进入下载模式、复位和关机而失效。 +4. WAKE 是否启用上下拉由 GPIO_PU/PD_0 (0x14) 和 GPIO_PU/PD_1 (0x15) 控制。 +5. GPIO_DRV (0x13) 输出类型拥有最高优先级, 不会随复用功能失效。默认为开漏, 即使复用为 PWM 或 Neopixel, 若配置为开漏则仍为开漏。 + +[电源事件] +IRQ Status 2 中的电池插入/移除事件仅在充电未使能 (CHG_EN=0) 时有效; 5VINOUT 事件仅在 INPUT 模式 (5VIN/OUT=0) 有效。 + +[NeoPixel 数据] +数据存储在 PIX0_L=0x60 ... PIX31_H=0x9F (共64字节)。刷新时 (REFRESH=1) 禁止 I2C 中断约 7ms。 + +[PWM 配置] +PWM0/1 的高低位配置分为两个字节, 需一次写入两个字节, 否则可能在极短时间内出现参数不一致。 + +[按键逻辑] +单击复位; 双击关机; 长按进入下载模式; 具体时间由 BTN_CFG_1 决定。 + +================================================================================ +三、 寄存器映射表 (Register Map) +================================================================================ +说明: 以下表格中 "Def" 代表默认值 (Default)。 +Reset行为包括: 按键复位, 命令复位, I2C看门狗复位, 用户定时器复位。 +关机行为包括: 按键关机, 命令关机, 用户定时器关机。 + +| Register | Type | Addr | Bits | R/W | Def | 描述 | Reset后状态 | Download后状态 | 关机后状态 | +|---|---|---|---|---|---|---|---|---|---| +| Device_ID | Sys | 0x00 | [7:0] | R | 0x50 | 设备类型 | - | - | - | +| Device_Model | Sys | 0x01 | [7:0] | R | 0x20 | 设备型号 | - | - | - | +| HW_REV | Sys | 0x02 | [7:0] | R | 0x05 | 硬件版本号 | - | - | - | +| SW_REV | Sys | 0x03 | [7:0] | R | 0x06 | 固件版本号 | - | - | - | +| PWR_SRC | Sys | 0x04 | [7:3] Rsv; [2:0] VALID | R | - | 电源来源位图 | - | - | - | +| WAKE_SRC | Sys | 0x05 | [7] Rsv; [6:0] FLAGS | R/W | - | 唤醒源标志 | - | - | - | +| PWR_CFG | Sys | 0x06 | [7:5] Rsv; [4] LED CTRL; [3] 5V IO; [2] 3V3 LDO; [1] 3V3 DCDC; [0] CHG EN | R/W | 0x17 | 电源管理位 | 0b00010011x (充电状态不受复位影响) | 0b00010011x (充电状态不受复位影响) | 0b00010011x (充电状态不受关机影响) | +| HOLD_CFG | Sys | 0x07 | [7] Rsv; [6] 5V IO; [5] 3V3 LDO; [4:0] GPIO4~0 | R/W | 0x00 | 置1则对应状态在关机后保留; 复位或下载模式时此寄存器复位为0 | 0x00 | 0x00 | - | +| BATT_LVP | Sys | 0x08 | [7:0] | R/W | 0x40 | 低压阈值: 2000mV + n*7.81mV | - | - | - | +| I2C_CFG | Sys | 0x09 | [7:4] Rsv; [4] SPD; [3:0] SLP_TO | R/W | 0x00 | SPD: 0=100k, 1=400k; SLP_TO: I2C超时休眠(秒)。PWM使能或下载模式时禁止休眠 | - | - | - | +| WDT_CNT | Sys | 0x0A | [7:0] | R/W | 0x00 | 看门狗倒计时(秒), 0=关闭 | - | - | - | +| WDT_KEY | Sys | 0x0B | [7:0] | W | - | 写 0xA5 清零/复位 | - | - | - | +| SYS_CMD | Sys | 0x0C | [7:4] KEY(0xA); [1:0] CMD | W | - | CMD: 01=关机, 10=重启, 11=下载 | - | - | - | +| GPIO_MODE | GPIO | 0x10 | [7:5] Rsv; [4:0] GPIO4~0 | R/W | 0x00 | 1=输出, 0=输入 (需GPIO_FUNC=00) | 由Hold寄存器决定 (1=保持, 0=复位) | 0x00 | 由Hold寄存器决定 | +| GPIO_OUT | GPIO | 0x11 | [7:5] Rsv; [4:0] GPIO4~0 | R/W | 0x00 | 写1=高电平 | 由Hold寄存器决定 | 0x00 | 由Hold寄存器决定 | +| GPIO_IN | GPIO | 0x12 | 同上 | R | - | 实时输入值 | - | - | - | +| GPIO_DRV | GPIO | 0x13 | [7:6] Rsv; [5] LED; [4:0] GPIO | R/W | 0x1F | 1=开漏, 0=推挽 | 由Hold寄存器决定 (LED不受影响) | 0x1F | 由Hold寄存器决定 | +| GPIO_PU/PD_0 | GPIO | 0x14 | [7:6] G3; [5:4] G2; [3:2] G1; [1:0] G0 | R/W | 0x00 | 00=无, 01=上拉, 10=下拉 | 由Hold寄存器决定 | 0x00 | 由Hold寄存器决定 | +| GPIO_PU/PD_1 | GPIO | 0x15 | [7:2] Rsv; [1:0] GPIO4 | R/W | 0x00 | 同上 | 由Hold寄存器决定 | 0x00 | 由Hold寄存器决定 | +| GPIO_FUNC0 | GPIO | 0x16 | [7:6] G3; [5:4] G2; [3:2] G1; [1:0] G0 | R/W | 0x00 | 00=GPIO, 01=IRQ, 11=特殊, 10=保留 | 由Hold寄存器决定 | 0x00 | 由Hold寄存器决定 | +| GPIO_FUNC1 | GPIO | 0x17 | [7:2] Rsv; [1:0] GPIO4 | R/W | 0x00 | 同上 | 由Hold寄存器决定 | 0x00 | 由Hold寄存器决定 | +| GPIO_WAKE_EN | GPIO | 0x18 | [7:5] Rsv; [4:0] GPIO4~0 | R/W | 0x00 | 1=使能WAKE; 注意中断线互斥规则 | - | - | - | +| GPIO_WAKE_CFG | GPIO | 0x19 | [7:5] Rsv; [4:0] GPIO4~0 | R/W | 0x00 | 1=上升沿, 0=下降沿 | - | - | - | +| VREF_L/H | ADC | 0x20 | [7:0] | R | - | MCU ADC 参考电压 (L:0x20, H:0x21) | - | - | - | +| VBAT_L/H | ADC | 0x22 | [7:0] | R | - | 电池电压 (L:0x22, H:0x23) | - | - | - | +| VIN_L/H | ADC | 0x24 | [7:0] | R | - | 5VIN 电压 (L:0x24, H:0x25) | - | - | - | +| 5VOUT_L/H | ADC | 0x26 | [7:0] | R | - | 5VOUT 电压 (L:0x26, H:0x27) | - | - | - | +| ADC_RES_L/H | ADC | 0x28 | [7:0] / [3:0] Data | R | - | ADC 转换结果 | - | - | - | +| ADC_CTRL | ADC | 0x2A | [3:1] CH_SEL; [0] START | R/W | 0x00 | START=1开始; CH_SEL: 1/2=GPIO, 6=Temp | - | - | - | +| PWM0_L/HC | PWM | 0x30 | [7:0] / [5] POL [4] EN | R/W | 0x00 | PWM0 控制 | - | - | - | +| PWM1_L/HC | PWM | 0x32 | [7:0] / [5] POL [4] EN | R/W | 0x00 | PWM1 控制 | - | - | - | +| PWM_FREQ_L/H | PWM | 0x34 | [7:0] | R/W | F4/01 | PWM 频率 (16bit) | - | - | - | +| TIM_CNT_BYTE | Tim | 0x38 | [7:0] (0x38-0x3B) | R/W | 0x00 | 定时唤醒计数器 (32bit, 秒) | - | - | - | +| TIM_CFG | Tim | 0x3C | [3] ARM; [2:0] ACTION | R/W | 0x00 | ACTION: 000=停, 001=标志, 010=重启, 011=上电, 100=关机 | - | - | - | +| TIM_KEY | Tim | 0x3D | [7:0] | W | - | 写 0xA5 清零重载 | - | - | - | +| IRQ Status 1 | IRQ | 0x40 | [4:0] GPIO4~0 | R/W | 0x00 | GPIO 电平变化 | - | - | - | +| IRQ Status 2 | IRQ | 0x41 | [5/4] Bat Rm/Add; [3/2] 5Vout Rm/Add; [1/0] 5Vin Rm/Add | R/W | 0x00 | 电源事件 (写1清零) | - | - | - | +| IRQ Status 3 | IRQ | 0x42 | [2] Dbl Click; [1] Wake; [0] Click | R/W | 0x00 | 按键/唤醒事件 (bit0=RST中断, bit1=Wakeup中断, bit2=双击中断) | - | - | - | +| IRQ Mask 1/2/3| IRQ | 0x43 | 对应 Status 位 | R/W | 0x00 | 写1屏蔽对应中断 (0x43~0x45) | - | - | - | +| BTN_Status | BTN | 0x48 | [7] Event; [0] Status | R | 0x00 | 按键状态 | - | - | - | +| BTN_CFG_1 | BTN | 0x49 | [7] Lock; [6:5] Dbl; [4:3] Long; [2:1] Single; [0] Sngl_Rst_Dis | R/W | 0x2A | 按键时间配置; bit0=1禁止单击复位 | - | - | - | +| BTN_CFG_2 | BTN | 0x4A | [0] Dbl_Off_Dis | R/W | 0x00 | bit0=1禁止双击关机 | - | - | - | +| NEO_CFG | RGB | 0x50 | [6] Refresh; [5:0] Count | R/W | 0x00 | NeoPixel控制 | - | - | - | +| PULSE_CTRL | Pulse| 0x53 | [7] Refresh; [6:5] Num; [4:0] GPIO | R/W | 0x00 | AW8737A 脉冲触发 | - | - | - | +| NEO_PIXn | RGB | 0x60 | [7:0] | R/W | 0x00 | 0x60-0x9F RGB数据缓存 | - | - | - | +| RTC_MEM | RTC | 0xA0 | [7:0] | R/W | 0x00 | 0xA0-0xBF RTC数据缓存 | - | - | - | + +================================================================================ +四、 关键寄存器功能详解 +================================================================================ +1. PWR_SRC (0x04) - 电源来源状态 + - [2] BAT: 电池有效 + - [1] 5VINOUT: 5VINOUT 有效 (仅当 5V 升压关闭时有效) + - [0] 5VIN: 5VIN 有效 + - 注: 系统通过 ADC 检测电压判断电源来源。建议无电池时关闭电池充电。 + +2. WAKE_SRC (0x05) - 唤醒来源 + - [6] 5V INOUT 插入唤醒 (仅当 5V 升压关闭时) + - [5] EXT_WAKE: GPIO 唤醒 + - [4] CMD_RST: 复位命令 + - [3] RSTBTN: 按键复位 + - [2] PWRBTN: 电源按钮 + - [1] VIN: 5VIN 插入 + - [0] TIM: 定时器 + +3. HOLD_CFG (0x07) - 状态保持 + - 将对应位设为 1, 则关机后对应的 GPIO、LDO 或 5VINOUT 状态会保持原样。 + - 注意: 进入下载模式或触发任何 Reset (包括看门狗), 该寄存器会清零。 + +4. BATT_LVP (0x08) - 低压保护 + - 低压阈值计算: 2.0 V + (reg_value * 7.81 mV)。 + - 重新开机条件: 电池电压 > 阈值+100mV; 或插入 5VIN; 或插入 5VINOUT。 + +5. I2C_CFG (0x09) - I2C配置 + - SPD: 0=100k, 1=400k。 + - SLP_TO: 若设置非0, 代表 I2C 总线空闲多少秒后芯片休眠。 + - 限制: PWM 使能或下载模式下, I2C 空闲休眠强制关闭。 + +6. GPIO_MODE / OUT / DRV + - GPIO_MODE: 1=输出, 0=输入。需 GPIO_FUNC 设置为 00 才生效。 + - GPIO_OUT: 1=高, 0=低。 + - GPIO_DRV: 1=开漏, 0=推挽。DRV 优先级最高, 若复用功能开启但 DRV=1, 仍为开漏。 + +7. ADC_CTRL (0x2A) - ADC控制 + - CH_SEL: 通道选择。1=GPIO1, 2=GPIO2, 6=内部温度。 + - 使用 GPIO 作 ADC 输入时, 必须将对应的 GPIO_FUNC 设为 11。 + +8. IRQ Status 注意事项 + - 若没有 GPIO 被配置为 IRQ 引脚, Status 寄存器会自动清零。 + - 若要使用 Wakeup IRQ (Status 3 bit1), 请勿清除 WAKE_SRC 寄存器, 直到重新配置 GPIO 为 IRQ 引脚。 + +================================================================================ +五、 附加功能说明与变更记录 (Change Log) +================================================================================ +[附加功能说明] +(1) 32-bit 定时器最大秒数限制为 214749364 秒。 +(2) 关机时, GPIO 恢复为默认输入无上下拉; 电源恢复默认状态 (除非 HOLD_CFG 对应位为 1)。 +(3) 进入下载模式, HOLD_CFG 清零, I2C 看门狗/休眠/用户定时器停止, GPIO 恢复默认。 +(4) LED 提示逻辑: 复位闪烁1次; 下载模式 500ms 间隔闪烁; 屏蔽按键复位且有 IRQ 时 200ms 间隔闪烁; 屏蔽关机且有 IRQ 时 100ms 间隔闪烁。 +(5) 充电功能默认打开。 + +[文档变更记录] +| 版本 | 日期 | 变更描述 | +|------|------------|----------| +| 1.0 | 2025-06-30 | 初始版本 | +| 1.1 | 2025-07-23 | 硬件修订: 3, 固件主版本: 2 | +| 1.2 | 2025-08-04 | 修订部分描述错误 | +| 1.3 | 2025-09-01 | 硬件修订: 4, 固件主版本: 3 | +| 1.4 | 2025-09-10 | 对关键寄存器详解进行了补充 | +| 1.5 | 2025-09-17 | 硬件修订: 5, 固件主版本: 4; (1) LED 指示灯在单击或双击中断触发指示; (2) 升级失败时 GPIO 和 bootout 设置为开漏; (3) 改用 ADC 采样判断 5VINOUT; (4) PIN1 逻辑修改。 | +| 1.6 | 2025-11-04 | 硬件修订: 5, 固件主版本: 5; (1) 添加 AW8737A 调频脉冲输出; (2) 添加 DCDC_5V 保持功能; (3) 看门狗默认关闭。 | +| 1.7 | 2025-12-13 | 硬件修订: 5, 固件主版本: 6; (1) 更新寄存器映射; (2) 新增 BTN_Status (0x48); (3) 移除 UID 寄存器, 新增 Device_ID/Model。 | \ No newline at end of file diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 0000000..69397e0 --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,14 @@ +description: "M5Stack M5PM1 Power Management IC Library" +maintainers: + - "M5Stack" +license: MIT +tags: + - "m5stack" + - "power-management" +dependencies: + idf: + version: ">=4.4" + espressif/i2c_bus: + version: "^1.0.0" + public: true +version: "1.0.1" \ No newline at end of file diff --git a/library.json b/library.json new file mode 100644 index 0000000..9b336fb --- /dev/null +++ b/library.json @@ -0,0 +1,49 @@ +{ + "name": "M5PM1", + "version": "1.0.1", + "description": "M5Stack PM1 Power Management IC Driver Library", + "keywords": [ + "m5stack", + "pm1", + "power-management", + "i2c", + "esp32", + "arduino" + ], + "repository": { + "type": "git", + "url": "https://github.com/m5stack/M5PM1.git" + }, + "authors": [ + { + "name": "M5Stack", + "email": "m5stack@m5stack.com", + "url": "https://m5stack.com", + "maintainer": true + } + ], + "license": "MIT", + "homepage": "https://github.com/m5stack/M5PM1", + "frameworks": [ + "arduino", + "espidf" + ], + "platforms": [ + "espressif32" + ], + "export": { + "include": [ + "src/*", + "library.json", + "library.properties", + "CMakeLists.txt", + "idf_component.yml", + "LICENSE", + "README.md" + ] + }, + "build": { + "srcDir": "src", + "includeDir": "src" + } +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..c604dcf --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +name=M5PM1 +version=1.0.1 +author=M5Stack +maintainer=M5Stack +sentence=M5Stack PM1 Power Management IC Driver Library +paragraph=Dual-platform (ESP-IDF & Arduino) driver for PM1 power management IC with battery charging, GPIO, PWM, ADC, NeoPixel, and watchdog support. +category=Device Control +url=https://github.com/m5stack/M5PM1 +architectures=esp32 +includes=M5PM1.h diff --git a/migration_script.md b/migration_script.md new file mode 100644 index 0000000..e69de29 diff --git a/src/M5PM1.cpp b/src/M5PM1.cpp new file mode 100644 index 0000000..18ff516 --- /dev/null +++ b/src/M5PM1.cpp @@ -0,0 +1,4140 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include "M5PM1.h" +#include +#include + +static const char* TAG = "M5PM1"; + +#ifdef ARDUINO + #include + #define M5PM1_DELAY_MS(ms) delay(ms) + #define M5PM1_GET_TIME_MS() millis() + + // Arduino 日志级别控制 + // Arduino log level control + static m5pm1_log_level_t _m5pm1_log_level = M5PM1_LOG_LEVEL_INFO; + + #define M5PM1_LOG_I(tag, fmt, ...) \ + do { \ + if (_m5pm1_log_level >= M5PM1_LOG_LEVEL_INFO) { \ + Serial.printf("[%s] " fmt "\r\n", tag, ##__VA_ARGS__); \ + } \ + } while (0) + + #define M5PM1_LOG_W(tag, fmt, ...) \ + do { \ + if (_m5pm1_log_level >= M5PM1_LOG_LEVEL_WARN) { \ + Serial.printf("[%s] WARN: " fmt "\r\n", tag, ##__VA_ARGS__); \ + } \ + } while (0) + + #define M5PM1_LOG_E(tag, fmt, ...) \ + do { \ + if (_m5pm1_log_level >= M5PM1_LOG_LEVEL_ERROR) { \ + Serial.printf("[%s] ERROR: " fmt "\r\n", tag, ##__VA_ARGS__); \ + } \ + } while (0) +#else + #include "esp_log.h" + #include "freertos/FreeRTOS.h" + #include "freertos/task.h" + #define M5PM1_DELAY_MS(ms) vTaskDelay(pdMS_TO_TICKS(ms)) + #define M5PM1_GET_TIME_MS() (xTaskGetTickCount() * portTICK_PERIOD_MS) + #define M5PM1_LOG_I(tag, fmt, ...) ESP_LOGI(tag, fmt, ##__VA_ARGS__) + #define M5PM1_LOG_W(tag, fmt, ...) ESP_LOGW(tag, fmt, ##__VA_ARGS__) + #define M5PM1_LOG_E(tag, fmt, ...) ESP_LOGE(tag, fmt, ##__VA_ARGS__) + + // ESP-IDF 平台日志级别控制 + // ESP-IDF platform log level control + static m5pm1_log_level_t _m5pm1_current_log_level = M5PM1_LOG_LEVEL_INFO; +#endif + +// ============================ +// 全局日志级别控制 +// Global Log Level Control +// ============================ + +void M5PM1::setLogLevel(m5pm1_log_level_t level) +{ +#ifdef ARDUINO + _m5pm1_log_level = level; +#else + _m5pm1_current_log_level = level; + + // 将 M5PM1 日志级别映射到 ESP-IDF 日志级别 + // Map M5PM1 log level to ESP-IDF log level + esp_log_level_t esp_level; + switch (level) { + case M5PM1_LOG_LEVEL_NONE: + esp_level = ESP_LOG_NONE; + break; + case M5PM1_LOG_LEVEL_ERROR: + esp_level = ESP_LOG_ERROR; + break; + case M5PM1_LOG_LEVEL_WARN: + esp_level = ESP_LOG_WARN; + break; + case M5PM1_LOG_LEVEL_INFO: + esp_level = ESP_LOG_INFO; + break; + case M5PM1_LOG_LEVEL_DEBUG: + esp_level = ESP_LOG_DEBUG; + break; + case M5PM1_LOG_LEVEL_VERBOSE: + esp_level = ESP_LOG_VERBOSE; + break; + default: + esp_level = ESP_LOG_INFO; + break; + } + + esp_log_level_set(TAG, esp_level); +#endif +} + +m5pm1_log_level_t M5PM1::getLogLevel() +{ +#ifdef ARDUINO + return _m5pm1_log_level; +#else + return _m5pm1_current_log_level; +#endif +} + +// ============================ +// Constructor / Destructor +// 构造函数 / 析构函数 +// ============================ + +M5PM1::M5PM1() { + _addr = M5PM1_DEFAULT_ADDR; + _initialized = false; + _autoWakeEnabled = false; + _autoSnapshot = true; + _i2cSleepTime = 0; + _requestedSpeed = M5PM1_I2C_FREQ_100K; + _i2cConfig.sleepTime = 0; + _i2cConfig.speed400k = false; + _i2cConfigValid = false; + _lastCommTime = 0; + memset(_pwmStates, 0, sizeof(_pwmStates)); + _pwmFrequency = 0; + _pwmStatesValid = false; + _adcState.channel = 0; + _adcState.busy = false; + _adcState.lastValue = 0; + _adcStateValid = false; + _powerCfg = 0; + _holdCfg = 0; + _powerConfigValid = false; + _btnCfg1 = 0; + _btnCfg2 = 0; + _btnConfigValid = false; + _irqMask1 = 0; + _irqMask2 = 0; + _irqMask3 = 0; + _irqMaskValid = false; + _irqStatus1 = 0; + _irqStatus2 = 0; + _irqStatus3 = 0; + _irqStatusValid = false; + _neoCfg = 0; + _neoConfigValid = false; + _cacheValid = false; + +#ifdef ARDUINO + _wire = nullptr; + _sda = -1; + _scl = -1; +#else + _i2cDriverType = M5PM1_I2C_DRIVER_NONE; + _i2c_master_bus = nullptr; + _i2c_master_dev = nullptr; + _i2c_bus = nullptr; + _i2c_device = nullptr; + _busExternal = false; + _sda = -1; + _scl = -1; + _port = I2C_NUM_0; +#endif +} + +M5PM1::~M5PM1() { +#ifndef ARDUINO + // Cleanup based on driver type + switch (_i2cDriverType) { + case M5PM1_I2C_DRIVER_SELF_CREATED: + if (_i2c_master_dev) { + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + } + if (_i2c_master_bus) { + i2c_del_master_bus(_i2c_master_bus); + _i2c_master_bus = nullptr; + } + break; + + case M5PM1_I2C_DRIVER_MASTER: + if (_i2c_master_dev) { + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + } + break; + + case M5PM1_I2C_DRIVER_BUS: + if (_i2c_device) { + i2c_bus_device_delete(&_i2c_device); + _i2c_device = nullptr; + } + break; + + default: + break; + } +#endif +} + +// ============================ +// 初始化函数 +// Initialization Functions +// ============================ + +#ifdef ARDUINO + +m5pm1_err_t M5PM1::begin(TwoWire *wire, uint8_t addr, int8_t sda, int8_t scl, uint32_t speed) { + _wire = wire; + _addr = addr; + _sda = sda; + _scl = scl; + + + // 步骤1:校验用户频率并记录 + // Step 1: Validate requested speed and store it + if (!_isValidI2cFrequency(speed)) { + M5PM1_LOG_W(TAG, + "Invalid I2C frequency: %lu Hz. PM1 only supports 100KHz or 400KHz. Falling back to 100KHz.", + (unsigned long)speed); + _requestedSpeed = M5PM1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + + // 步骤2:以100KHz初始化主机I2C + // Step 2: Initialize host I2C at 100KHz + _wire->end(); + M5PM1_DELAY_MS(50); + if (!_wire->begin(_sda, _scl, M5PM1_I2C_FREQ_100K)) { + M5PM1_LOG_E(TAG, "Failed to initialize I2C bus (SDA=%d, SCL=%d)", _sda, _scl); + return M5PM1_ERR_I2C_CONFIG; + } + M5PM1_DELAY_MS(100); + + // 尝试唤醒设备 - 发送 I2C START 信号 + // Try to wake up the device - send I2C START signal + M5PM1_I2C_SEND_WAKE(_wire, _addr); + M5PM1_DELAY_MS(10); + + // 步骤3:验证设备通信(失败则等待800ms重试一次) + // Step 3: Verify device communication (retry once after 800ms if failed) + if (!_initDevice()) { + M5PM1_LOG_W(TAG, "Device init failed, retrying after 800ms..."); + M5PM1_DELAY_MS(800); + // 重试前再次发送唤醒信号 + // Send wake signal again before retry + M5PM1_I2C_SEND_WAKE(_wire, _addr); + 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..."); + + _wire->end(); + M5PM1_DELAY_MS(50); + if (!_wire->begin(_sda, _scl, M5PM1_I2C_FREQ_400K)) { + M5PM1_LOG_E(TAG, "Failed to initialize I2C bus at 400KHz"); + return M5PM1_ERR_I2C_CONFIG; + } + M5PM1_DELAY_MS(50); + + // 尝试唤醒设备 - 发送 I2C START 信号 + // Try to wake up the device - send I2C START signal + M5PM1_I2C_SEND_WAKE(_wire, _addr); + M5PM1_DELAY_MS(10); + + if (!_initDevice()) { + M5PM1_LOG_E(TAG, "Failed at 100KHz (twice) and 400KHz"); + return M5PM1_ERR_I2C_COMM; + } + } + } + + _initialized = true; + + // 步骤4:配置设备I2C参数(关闭睡眠 + 目标频率) + // Step 4: Configure device I2C (sleep off + 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:切换主机到目标频率 + // Step 5: Switch host to target speed + _wire->end(); + M5PM1_DELAY_MS(10); + if (!_wire->begin(_sda, _scl, _requestedSpeed)) { + M5PM1_LOG_E(TAG, "Failed to switch host to %lu Hz", (unsigned long)_requestedSpeed); + _initialized = false; + return M5PM1_ERR_I2C_CONFIG; + } + M5PM1_DELAY_MS(50); + + // 步骤6:刷新快照并完成初始化 + // Step 6: Refresh snapshot and finish initialization + _lastCommTime = M5PM1_GET_TIME_MS(); + if (!_snapshotAll()) { + _clearAll(); + } + + M5PM1_LOG_I(TAG, "M5PM1 initialized at address 0x%02X (I2C: %lu Hz)", _addr, + (unsigned long)_requestedSpeed); + return M5PM1_OK; +} + +#else // ESP-IDF + +m5pm1_err_t M5PM1::begin(i2c_port_t port, uint8_t addr, int sda, int scl, uint32_t speed) { + _addr = addr; + _busExternal = false; + _i2cDriverType = M5PM1_I2C_DRIVER_SELF_CREATED; + _port = port; + _sda = sda; + _scl = scl; + + // 步骤1:校验用户频率并记录 + // Step 1: Validate requested speed and store it + if (!_isValidI2cFrequency(speed)) { + M5PM1_LOG_W(TAG, + "Invalid I2C frequency: %lu Hz. PM1 only supports 100KHz or 400KHz. Falling back to 100KHz.", + (unsigned long)speed); + _requestedSpeed = M5PM1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + + // 步骤2:创建I2C主总线 + // Step 2: Create I2C master bus + i2c_master_bus_config_t bus_config = { + .i2c_port = port, + .sda_io_num = (gpio_num_t)sda, + .scl_io_num = (gpio_num_t)scl, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = true, + .allow_pd = false, + }, + }; + + esp_err_t ret = i2c_new_master_bus(&bus_config, &_i2c_master_bus); + if (ret != ESP_OK) { + M5PM1_LOG_E(TAG, "Failed to create I2C master bus: %s", esp_err_to_name(ret)); + return M5PM1_ERR_I2C_CONFIG; + } + + // 步骤2:以100KHz创建设备句柄 + // Step 2: Create device handle at 100KHz + i2c_device_config_t dev_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = _addr, + .scl_speed_hz = M5PM1_I2C_FREQ_100K, + .scl_wait_us = 0, + .flags = { + .disable_ack_check = false, + }, + }; + + ret = i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + if (ret != ESP_OK) { + M5PM1_LOG_E(TAG, "Failed to add I2C device: %s", esp_err_to_name(ret)); + i2c_del_master_bus(_i2c_master_bus); + _i2c_master_bus = nullptr; + _initialized = false; + return M5PM1_ERR_I2C_CONFIG; + } + + // 尝试唤醒设备 + // Try to wake up the device + M5PM1_I2C_MASTER_SEND_WAKE(_i2c_master_bus, _addr); + M5PM1_DELAY_MS(10); + + // 步骤3:验证设备通信(失败则等待800ms重试一次) + // Step 3: Verify device communication (retry once after 800ms if failed) + if (!_initDevice()) { + M5PM1_LOG_W(TAG, "Device init failed, retrying after 800ms..."); + M5PM1_DELAY_MS(800); + // 重试前再次发送唤醒信号 + // Send wake signal again before retry + M5PM1_I2C_MASTER_SEND_WAKE(_i2c_master_bus, _addr); + 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..."); + + // 删除当前100K设备句柄 + // Remove current 100K device handle + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + + // 以400K重新创建设备句柄 + // Recreate device handle at 400K + dev_config.scl_speed_hz = M5PM1_I2C_FREQ_400K; + ret = i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + if (ret != ESP_OK) { + M5PM1_LOG_E(TAG, "Failed to add I2C device at 400KHz: %s", esp_err_to_name(ret)); + i2c_del_master_bus(_i2c_master_bus); + _i2c_master_bus = nullptr; + return M5PM1_ERR_I2C_CONFIG; + } + + // 尝试唤醒设备 + // Try to wake up the device + M5PM1_I2C_MASTER_SEND_WAKE(_i2c_master_bus, _addr); + M5PM1_DELAY_MS(10); + + if (!_initDevice()) { + M5PM1_LOG_E(TAG, "Failed at 100KHz (twice) and 400KHz"); + i2c_master_bus_rm_device(_i2c_master_dev); + i2c_del_master_bus(_i2c_master_bus); + _i2c_master_dev = nullptr; + _i2c_master_bus = nullptr; + return M5PM1_ERR_I2C_COMM; + } + } + } + _initialized = true; + + + // 步骤5:配置设备I2C参数(关闭睡眠 + 目标频率) + // Step 5: Configure device I2C (sleep off + 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"); + } + + + // 步骤6:重建设备句柄到目标频率 + // Step 6: Recreate device handle at target speed + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + + dev_config.scl_speed_hz = _requestedSpeed; + ret = i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + if (ret != ESP_OK) { + M5PM1_LOG_E(TAG, "Failed to switch device to %lu Hz: %s", + (unsigned long)_requestedSpeed, esp_err_to_name(ret)); + i2c_del_master_bus(_i2c_master_bus); + _i2c_master_bus = nullptr; + _initialized = false; + return M5PM1_ERR_I2C_CONFIG; + } + + // 步骤7:刷新快照并完成初始化 + // Step 7: Refresh snapshot and finish initialization + _lastCommTime = M5PM1_GET_TIME_MS(); + if (!_snapshotAll()) { + _clearAll(); + } + + _initialized = true; + M5PM1_LOG_I(TAG, "M5PM1 initialized at address 0x%02X (I2C: %lu Hz)", _addr, + (unsigned long)_requestedSpeed); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::begin(i2c_master_bus_handle_t bus, uint8_t addr, uint32_t speed) { + _addr = addr; + _busExternal = true; + _i2cDriverType = M5PM1_I2C_DRIVER_MASTER; + _i2c_master_bus = bus; + + // 步骤1:校验用户频率并记录 + // Step 1: Validate requested speed and store it + if (!_isValidI2cFrequency(speed)) { + M5PM1_LOG_W(TAG, + "Invalid I2C frequency: %lu Hz. PM1 only supports 100KHz or 400KHz. Falling back to 100KHz.", + (unsigned long)speed); + _requestedSpeed = M5PM1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + + // 步骤3:以100KHz创建设备句柄 + // Step 3: Create device handle at 100KHz + i2c_device_config_t dev_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = _addr, + .scl_speed_hz = M5PM1_I2C_FREQ_100K, + .scl_wait_us = 0, + .flags = { + .disable_ack_check = false, + }, + }; + + esp_err_t ret = i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + if (ret != ESP_OK) { + M5PM1_LOG_E(TAG, "Failed to add I2C device: %s", esp_err_to_name(ret)); + return M5PM1_ERR_I2C_CONFIG; + } + + // 尝试唤醒设备 + // Try to wake up the device + M5PM1_I2C_MASTER_SEND_WAKE(_i2c_master_bus, _addr); + M5PM1_DELAY_MS(10); + + // 步骤3:验证设备通信(失败则等待800ms重试一次) + // Step 3: Verify device communication (retry once after 800ms if failed) + if (!_initDevice()) { + M5PM1_LOG_W(TAG, "Device init failed, retrying after 800ms..."); + M5PM1_DELAY_MS(800); + // 重试前再次发送唤醒信号 + // Send wake signal again before retry + M5PM1_I2C_MASTER_SEND_WAKE(_i2c_master_bus, _addr); + 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..."); + + // 删除当前100K设备句柄 + // Remove current 100K device handle + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + + // 以400K重新创建设备句柄 + // Recreate device handle at 400K + dev_config.scl_speed_hz = M5PM1_I2C_FREQ_400K; + ret = i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + if (ret != ESP_OK) { + M5PM1_LOG_E(TAG, "Failed to add I2C device at 400KHz: %s", esp_err_to_name(ret)); + return M5PM1_ERR_I2C_CONFIG; + } + + // 尝试唤醒设备 + // Try to wake up the device + M5PM1_I2C_MASTER_SEND_WAKE(_i2c_master_bus, _addr); + M5PM1_DELAY_MS(10); + + if (!_initDevice()) { + M5PM1_LOG_E(TAG, "Failed at 100KHz (twice) and 400KHz"); + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + return M5PM1_ERR_I2C_COMM; + } + } + } + _initialized = true; + + // 步骤4:配置设备I2C参数(关闭睡眠 + 目标频率) + // Step 4: Configure device I2C (sleep off + 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:重建设备句柄到目标频率 + // Step 5: Recreate device handle at target speed + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + + dev_config.scl_speed_hz = _requestedSpeed; + ret = i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + if (ret != ESP_OK) { + M5PM1_LOG_E(TAG, "Failed to switch device to %lu Hz: %s", + (unsigned long)_requestedSpeed, esp_err_to_name(ret)); + _initialized = false; + return M5PM1_ERR_I2C_CONFIG; + } + + // 步骤6:刷新快照并完成初始化 + // Step 6: Refresh snapshot and finish initialization + _lastCommTime = M5PM1_GET_TIME_MS(); + if (!_snapshotAll()) { + _clearAll(); + } + + _initialized = true; + M5PM1_LOG_I(TAG, "M5PM1 initialized at address 0x%02X (I2C: %lu Hz)", _addr, + (unsigned long)_requestedSpeed); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::begin(i2c_bus_handle_t bus, uint8_t addr, uint32_t speed) { + _addr = addr; + _busExternal = true; + _i2cDriverType = M5PM1_I2C_DRIVER_BUS; + _i2c_bus = bus; + + // 步骤1:校验用户频率并记录 + // Step 1: Validate requested speed and store it + if (!_isValidI2cFrequency(speed)) { + M5PM1_LOG_W(TAG, + "Invalid I2C frequency: %lu Hz. PM1 only supports 100KHz or 400KHz. Falling back to 100KHz.", + (unsigned long)speed); + _requestedSpeed = M5PM1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + + // 步骤2:以100KHz创建设备句柄 + // Step 2: Create device handle at 100KHz + _i2c_device = i2c_bus_device_create(bus, addr, M5PM1_I2C_FREQ_100K); + if (_i2c_device == nullptr) { + M5PM1_LOG_E(TAG, "Failed to create I2C device"); + return M5PM1_ERR_I2C_CONFIG; + } + + // 尝试唤醒设备 + // Try to wake up the device + M5PM1_I2C_SEND_WAKE(_i2c_device, M5PM1_REG_HW_REV); + M5PM1_DELAY_MS(10); + + // 步骤3:验证设备通信(失败则等待800ms重试一次) + // Step 3: Verify device communication (retry once after 800ms if failed) + if (!_initDevice()) { + M5PM1_LOG_W(TAG, "Device init failed, retrying after 800ms..."); + M5PM1_DELAY_MS(800); + // 重试前再次发送唤醒信号 + // Send wake signal again before retry + M5PM1_I2C_SEND_WAKE(_i2c_device, M5PM1_REG_HW_REV); + 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..."); + + // 删除当前100K设备句柄 + // Remove current 100K device handle + i2c_bus_device_delete(&_i2c_device); + _i2c_device = nullptr; + + // 以400K重新创建设备句柄 + // Recreate device handle at 400K + _i2c_device = i2c_bus_device_create(bus, addr, M5PM1_I2C_FREQ_400K); + if (_i2c_device == nullptr) { + M5PM1_LOG_E(TAG, "Failed to create I2C device at 400KHz"); + return M5PM1_ERR_I2C_CONFIG; + } + + // 尝试唤醒设备 + // Try to wake up the device + M5PM1_I2C_SEND_WAKE(_i2c_device, M5PM1_REG_HW_REV); + M5PM1_DELAY_MS(10); + + if (!_initDevice()) { + M5PM1_LOG_E(TAG, "Failed at 100KHz (twice) and 400KHz"); + i2c_bus_device_delete(&_i2c_device); + _i2c_device = nullptr; + return M5PM1_ERR_I2C_COMM; + } + } + } + _initialized = true; + + + // 步骤4:配置设备I2C参数(关闭睡眠 + 目标频率) + // Step 4: Configure device I2C (sleep off + 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:重建设备句柄到目标频率 + // Step 5: Recreate device handle at target speed + i2c_bus_device_delete(&_i2c_device); + _i2c_device = nullptr; + + _i2c_device = i2c_bus_device_create(bus, addr, _requestedSpeed); + if (_i2c_device == nullptr) { + M5PM1_LOG_E(TAG, "Failed to switch device to %lu Hz", (unsigned long)_requestedSpeed); + _initialized = false; + return M5PM1_ERR_I2C_CONFIG; + } + + // 步骤6:刷新快照并完成初始化 + // Step 6: Refresh snapshot and finish initialization + _lastCommTime = M5PM1_GET_TIME_MS(); + if (!_snapshotAll()) { + _clearAll(); + } + + _initialized = true; + M5PM1_LOG_I(TAG, "M5PM1 initialized at address 0x%02X (I2C: %lu Hz)", _addr, + (unsigned long)_requestedSpeed); + return M5PM1_OK; +} + +#endif // ARDUINO + +// ============================ +// 内部辅助函数 +// Internal Helper Functions +// ============================ + +bool M5PM1::_initDevice() { + // Verify device ID + uint8_t id; + if (!_readReg(M5PM1_REG_DEVICE_ID, &id)) { + return false; + } + M5PM1_LOG_I(TAG, "Device ID: 0x%02X", id); + + _clearAll(); + + if (!_snapshotI2cConfig()) { + _clearI2cConfig(); + } + + return true; +} + +bool M5PM1::_isValidPin(uint8_t pin) { + return pin < M5PM1_MAX_GPIO_PINS; +} + +bool M5PM1::_isAdcPin(uint8_t pin) { + return (pin == M5PM1_GPIO_NUM_1 || pin == M5PM1_GPIO_NUM_2); +} + +bool M5PM1::_isPwmPin(uint8_t pin) { + return (pin == M5PM1_GPIO_NUM_3 || pin == M5PM1_GPIO_NUM_4); +} + +bool M5PM1::_isNeoPin(uint8_t pin) { + return (pin == M5PM1_GPIO_NUM_0); +} + +bool M5PM1::_hasActiveAdc(uint8_t pin) { + if (!_adcStateValid) return false; + + if (pin == M5PM1_GPIO_NUM_1) { + return _adcState.channel == M5PM1_ADC_CH_1; + } + if (pin == M5PM1_GPIO_NUM_2) { + return _adcState.channel == M5PM1_ADC_CH_2; + } + return false; +} + +bool M5PM1::_hasActivePwm(uint8_t pin) { + if (!_pwmStatesValid) return false; + + if (pin == M5PM1_GPIO_NUM_3) { + return _pwmStates[M5PM1_PWM_CH_0].enabled; + } + if (pin == M5PM1_GPIO_NUM_4) { + return _pwmStates[M5PM1_PWM_CH_1].enabled; + } + return false; +} + +bool M5PM1::_hasActiveIrq(uint8_t pin) { + if (!_cacheValid) return false; + return _pinStatus[pin].func == M5PM1_GPIO_FUNC_IRQ; +} + +bool M5PM1::_hasActiveWake(uint8_t pin) { + if (!_cacheValid) return false; + return _pinStatus[pin].func == M5PM1_GPIO_FUNC_WAKE || _pinStatus[pin].wake_en; +} + +bool M5PM1::_hasActiveNeo(uint8_t pin) { + if (!_cacheValid) return false; + return _isNeoPin(pin) && _pinStatus[pin].func == M5PM1_GPIO_FUNC_OTHER; +} + +bool M5PM1::_isValidI2cFrequency(uint32_t speed) { + return (speed == M5PM1_I2C_FREQ_100K || speed == M5PM1_I2C_FREQ_400K); +} + +void M5PM1::_checkAutoWake() { + if (!_autoWakeEnabled || !_i2cConfigValid || _i2cConfig.sleepTime == 0) return; + + uint32_t now = M5PM1_GET_TIME_MS(); + uint32_t elapsed = now - _lastCommTime; + + // If more than 1 second since last communication, send wake signal + if (elapsed >= 1000) { + sendWakeSignal(); + M5PM1_DELAY_MS(10); + } +} + +bool M5PM1::_snapshotI2cConfig() { + uint8_t cfg = 0; + if (!_readReg(M5PM1_REG_I2C_CFG, &cfg)) { + return false; + } + + _i2cConfig.sleepTime = cfg & M5PM1_I2C_CFG_SLEEP_MASK; + _i2cConfig.speed400k = (cfg & M5PM1_I2C_CFG_SPEED_400K) != 0; + _i2cConfigValid = true; + _i2cSleepTime = _i2cConfig.sleepTime; + return true; +} + +void M5PM1::_clearI2cConfig() { + _i2cConfig.sleepTime = 0; + _i2cConfig.speed400k = false; + _i2cConfigValid = false; + _i2cSleepTime = 0; +} + +bool M5PM1::_snapshotNeoConfig() { + uint8_t cfg = 0; + if (!_readReg(M5PM1_REG_NEO_CFG, &cfg)) { + _neoConfigValid = false; + return false; + } + + _neoCfg = cfg & M5PM1_NEO_CFG_COUNT_MASK; + _neoConfigValid = true; + return true; +} + +void M5PM1::_clearNeoConfig() { + _neoCfg = 0; + _neoConfigValid = false; +} + +// ============================ +// 缓存管理函数 +// Cache Management Functions +// ============================ + +void M5PM1::_clearPinStates() { + for (int i = 0; i < M5PM1_MAX_GPIO_PINS; i++) { + _pinStatus[i].func = M5PM1_GPIO_FUNC_GPIO; + _pinStatus[i].mode = M5PM1_GPIO_MODE_INPUT; + _pinStatus[i].output = 0; + _pinStatus[i].pull = M5PM1_GPIO_PULL_NONE; + _pinStatus[i].wake_en = false; + _pinStatus[i].wake_edge = M5PM1_GPIO_WAKE_FALLING; + _pinStatus[i].drive = M5PM1_GPIO_DRIVE_PUSHPULL; + _pinStatus[i].power_hold = false; + } + _cacheValid = false; +} + +void M5PM1::_clearPwmStates() { + memset(_pwmStates, 0, sizeof(_pwmStates)); + _pwmFrequency = 0; + _pwmStatesValid = false; +} + +void M5PM1::_clearAdcState() { + _adcState.channel = 0; + _adcState.busy = false; + _adcState.lastValue = 0; + _adcStateValid = false; +} + +void M5PM1::_clearPowerConfig() { + _powerCfg = 0; + _holdCfg = 0; + _powerConfigValid = false; +} + +void M5PM1::_clearButtonConfig() { + _btnCfg1 = 0; + _btnCfg2 = 0; + _btnConfigValid = false; +} + +void M5PM1::_clearIrqMasks() { + _irqMask1 = 0; + _irqMask2 = 0; + _irqMask3 = 0; + _irqMaskValid = false; +} + +void M5PM1::_clearIrqStatus() { + _irqStatus1 = 0; + _irqStatus2 = 0; + _irqStatus3 = 0; + _irqStatusValid = false; +} + +void M5PM1::_clearAll() { + _clearPinStates(); + _clearPwmStates(); + _clearAdcState(); + _clearPowerConfig(); + _clearButtonConfig(); + _clearIrqMasks(); + _clearIrqStatus(); + _clearI2cConfig(); + _clearNeoConfig(); +} + +bool M5PM1::_snapshotPinStates() { + uint8_t mode = 0; + uint8_t out = 0; + uint8_t drv = 0; + uint8_t pupd0 = 0; + uint8_t pupd1 = 0; + uint8_t func0 = 0; + uint8_t func1 = 0; + uint8_t wakeEn = 0; + uint8_t wakeCfg = 0; + uint8_t holdCfg = 0; + + if (!_readReg(M5PM1_REG_GPIO_MODE, &mode)) { + _cacheValid = false; + return false; + } + if (!_readReg(M5PM1_REG_GPIO_OUT, &out)) { + _cacheValid = false; + return false; + } + if (!_readReg(M5PM1_REG_GPIO_DRV, &drv)) { + _cacheValid = false; + return false; + } + if (!_readReg(M5PM1_REG_GPIO_PUPD0, &pupd0)) { + _cacheValid = false; + return false; + } + if (!_readReg(M5PM1_REG_GPIO_PUPD1, &pupd1)) { + _cacheValid = false; + return false; + } + if (!_readReg(M5PM1_REG_GPIO_FUNC0, &func0)) { + _cacheValid = false; + return false; + } + if (!_readReg(M5PM1_REG_GPIO_FUNC1, &func1)) { + _cacheValid = false; + return false; + } + if (!_readReg(M5PM1_REG_GPIO_WAKE_EN, &wakeEn)) { + _cacheValid = false; + return false; + } + if (!_readReg(M5PM1_REG_GPIO_WAKE_CFG, &wakeCfg)) { + _cacheValid = false; + return false; + } + if (!_readReg(M5PM1_REG_HOLD_CFG, &holdCfg)) { + _cacheValid = false; + return false; + } + + for (int pin = 0; pin < M5PM1_MAX_GPIO_PINS; pin++) { + _pinStatus[pin].mode = (mode & (1 << pin)) ? M5PM1_GPIO_MODE_OUTPUT : M5PM1_GPIO_MODE_INPUT; + _pinStatus[pin].output = (out >> pin) & 0x01; + _pinStatus[pin].drive = (drv & (1 << pin)) ? M5PM1_GPIO_DRIVE_OPENDRAIN : M5PM1_GPIO_DRIVE_PUSHPULL; + _pinStatus[pin].wake_en = (wakeEn & (1 << pin)) != 0; + _pinStatus[pin].wake_edge = (wakeCfg & (1 << pin)) ? M5PM1_GPIO_WAKE_RISING : M5PM1_GPIO_WAKE_FALLING; + _pinStatus[pin].power_hold = (holdCfg & (1 << pin)) != 0; + + if (pin < 4) { + _pinStatus[pin].func = (m5pm1_gpio_func_t)((func0 >> (pin * 2)) & 0x03); + _pinStatus[pin].pull = (m5pm1_gpio_pull_t)((pupd0 >> (pin * 2)) & 0x03); + } else { + _pinStatus[pin].func = (m5pm1_gpio_func_t)((func1 >> 0) & 0x03); + _pinStatus[pin].pull = (m5pm1_gpio_pull_t)((pupd1 >> 0) & 0x03); + } + } + + _cacheValid = true; + return true; +} + +bool M5PM1::_snapshotPwmStates() { + if (!_readReg16(M5PM1_REG_PWM_FREQ_L, &_pwmFrequency)) { + _pwmStatesValid = false; + return false; + } + + for (uint8_t ch = 0; ch < M5PM1_MAX_PWM_CHANNELS; ch++) { + uint8_t regL = (ch == 0) ? M5PM1_REG_PWM0_L : M5PM1_REG_PWM1_L; + uint8_t regH = (ch == 0) ? M5PM1_REG_PWM0_HC : M5PM1_REG_PWM1_HC; + uint8_t low = 0; + uint8_t high = 0; + + if (!_readReg(regL, &low)) { + _pwmStatesValid = false; + return false; + } + if (!_readReg(regH, &high)) { + _pwmStatesValid = false; + return false; + } + + _pwmStates[ch].duty12 = (uint16_t)(low | ((high & 0x0F) << 8)); + _pwmStates[ch].enabled = (high & 0x10) != 0; + _pwmStates[ch].polarity = (high & 0x20) != 0; + } + + _pwmStatesValid = true; + return true; +} + +bool M5PM1::_snapshotAdcState() { + uint8_t ctrl = 0; + if (!_readReg(M5PM1_REG_ADC_CTRL, &ctrl)) { + _adcStateValid = false; + return false; + } + + _adcState.channel = (ctrl >> 1) & 0x07; + _adcState.busy = (ctrl & 0x01) != 0; + if (!_readReg16(M5PM1_REG_ADC_RES_L, &_adcState.lastValue)) { + _adcStateValid = false; + return false; + } + + _adcStateValid = true; + return true; +} + +bool M5PM1::_snapshotPowerConfig() { + if (!_readReg(M5PM1_REG_PWR_CFG, &_powerCfg)) { + _powerConfigValid = false; + return false; + } + if (!_readReg(M5PM1_REG_HOLD_CFG, &_holdCfg)) { + _powerConfigValid = false; + return false; + } + + _powerConfigValid = true; + return true; +} + +bool M5PM1::_snapshotButtonConfig() { + if (!_readReg(M5PM1_REG_BTN_CFG_1, &_btnCfg1)) { + _btnConfigValid = false; + return false; + } + if (!_readReg(M5PM1_REG_BTN_CFG_2, &_btnCfg2)) { + _btnConfigValid = false; + return false; + } + + _btnConfigValid = true; + return true; +} + +bool M5PM1::_snapshotIrqMasks() { + if (!_readReg(M5PM1_REG_IRQ_MASK1, &_irqMask1)) { + _irqMaskValid = false; + return false; + } + if (!_readReg(M5PM1_REG_IRQ_MASK2, &_irqMask2)) { + _irqMaskValid = false; + return false; + } + if (!_readReg(M5PM1_REG_IRQ_MASK3, &_irqMask3)) { + _irqMaskValid = false; + return false; + } + + _irqMaskValid = true; + return true; +} + +bool M5PM1::_snapshotIrqStatus() { + if (!_readReg(M5PM1_REG_IRQ_STATUS1, &_irqStatus1)) { + _irqStatusValid = false; + return false; + } + if (!_readReg(M5PM1_REG_IRQ_STATUS2, &_irqStatus2)) { + _irqStatusValid = false; + return false; + } + if (!_readReg(M5PM1_REG_IRQ_STATUS3, &_irqStatus3)) { + _irqStatusValid = false; + return false; + } + + _irqStatusValid = true; + return true; +} + +bool M5PM1::_snapshotAll() { + bool gpio = _snapshotPinStates(); + bool pwm = _snapshotPwmStates(); + bool adc = _snapshotAdcState(); + bool power = _snapshotPowerConfig(); + bool button = _snapshotButtonConfig(); + bool irqMask = _snapshotIrqMasks(); + bool irqStatus = _snapshotIrqStatus(); + bool i2c = _snapshotI2cConfig(); + bool neo = _snapshotNeoConfig(); + + if (!i2c) { + _clearI2cConfig(); + } + if (!neo) { + _clearNeoConfig(); + } + + return gpio && pwm && adc && power && button && irqMask && irqStatus && i2c && neo; +} + +void M5PM1::_autoSnapshotUpdate(uint16_t domains) { + if (!_autoSnapshot) return; + + if (domains & M5PM1_SNAPSHOT_DOMAIN_GPIO) { + _snapshotPinStates(); + } + if (domains & M5PM1_SNAPSHOT_DOMAIN_PWM) { + _snapshotPwmStates(); + } + if (domains & M5PM1_SNAPSHOT_DOMAIN_ADC) { + _snapshotAdcState(); + } + if (domains & M5PM1_SNAPSHOT_DOMAIN_POWER) { + _snapshotPowerConfig(); + } + if (domains & M5PM1_SNAPSHOT_DOMAIN_BUTTON) { + _snapshotButtonConfig(); + } + if (domains & M5PM1_SNAPSHOT_DOMAIN_IRQ_MASK) { + _snapshotIrqMasks(); + } + if (domains & M5PM1_SNAPSHOT_DOMAIN_IRQ_STATUS) { + _snapshotIrqStatus(); + } + if (domains & M5PM1_SNAPSHOT_DOMAIN_I2C) { + if (!_snapshotI2cConfig()) { + _clearI2cConfig(); + } + } + if (domains & M5PM1_SNAPSHOT_DOMAIN_NEO) { + if (!_snapshotNeoConfig()) { + _clearNeoConfig(); + } + } +} + +void M5PM1::_initPinCache() { + _clearPinStates(); +} + +bool M5PM1::_writeReg(uint8_t reg, uint8_t value) { + _checkAutoWake(); + bool success = false; + for (int attempt = 0; attempt < M5PM1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO + success = M5PM1_I2C_WRITE_BYTE(_wire, _addr, reg, value); +#else + switch (_i2cDriverType) { + case M5PM1_I2C_DRIVER_SELF_CREATED: + case M5PM1_I2C_DRIVER_MASTER: + success = M5PM1_I2C_MASTER_WRITE_BYTE(_i2c_master_dev, reg, value) == ESP_OK; + break; + case M5PM1_I2C_DRIVER_BUS: + success = M5PM1_I2C_WRITE_BYTE(_i2c_device, reg, value) == ESP_OK; + break; + default: + success = false; + break; + } +#endif + if (success) { + break; + } + if (attempt + 1 < M5PM1_I2C_RETRY_COUNT) { + M5PM1_DELAY_MS(M5PM1_I2C_RETRY_DELAY_MS); + } + } + _lastCommTime = M5PM1_GET_TIME_MS(); + return success; +} + +bool M5PM1::_writeReg16(uint8_t reg, uint16_t value) { + _checkAutoWake(); + bool success = false; + for (int attempt = 0; attempt < M5PM1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO + success = M5PM1_I2C_WRITE_REG16(_wire, _addr, reg, value); +#else + switch (_i2cDriverType) { + case M5PM1_I2C_DRIVER_SELF_CREATED: + case M5PM1_I2C_DRIVER_MASTER: + success = M5PM1_I2C_MASTER_WRITE_REG16(_i2c_master_dev, reg, value) == ESP_OK; + break; + case M5PM1_I2C_DRIVER_BUS: + success = M5PM1_I2C_WRITE_REG16(_i2c_device, reg, value) == ESP_OK; + break; + default: + success = false; + break; + } +#endif + if (success) { + break; + } + if (attempt + 1 < M5PM1_I2C_RETRY_COUNT) { + M5PM1_DELAY_MS(M5PM1_I2C_RETRY_DELAY_MS); + } + } + _lastCommTime = M5PM1_GET_TIME_MS(); + return success; +} + +bool M5PM1::_readReg(uint8_t reg, uint8_t* value) { + _checkAutoWake(); + bool success = false; + for (int attempt = 0; attempt < M5PM1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO + success = M5PM1_I2C_READ_BYTE(_wire, _addr, reg, value); +#else + switch (_i2cDriverType) { + case M5PM1_I2C_DRIVER_SELF_CREATED: + case M5PM1_I2C_DRIVER_MASTER: + success = M5PM1_I2C_MASTER_READ_BYTE(_i2c_master_dev, reg, value) == ESP_OK; + break; + case M5PM1_I2C_DRIVER_BUS: + success = M5PM1_I2C_READ_BYTE(_i2c_device, reg, value) == ESP_OK; + break; + default: + success = false; + break; + } +#endif + if (success) { + break; + } + if (attempt + 1 < M5PM1_I2C_RETRY_COUNT) { + M5PM1_DELAY_MS(M5PM1_I2C_RETRY_DELAY_MS); + } + } + _lastCommTime = M5PM1_GET_TIME_MS(); + return success; +} + +bool M5PM1::_readReg16(uint8_t reg, uint16_t* value) { + _checkAutoWake(); + bool success = false; + for (int attempt = 0; attempt < M5PM1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO + success = M5PM1_I2C_READ_REG16(_wire, _addr, reg, value); +#else + switch (_i2cDriverType) { + case M5PM1_I2C_DRIVER_SELF_CREATED: + case M5PM1_I2C_DRIVER_MASTER: + success = M5PM1_I2C_MASTER_READ_REG16(_i2c_master_dev, reg, value) == ESP_OK; + break; + case M5PM1_I2C_DRIVER_BUS: + success = M5PM1_I2C_READ_REG16(_i2c_device, reg, value) == ESP_OK; + break; + default: + success = false; + break; + } +#endif + if (success) { + break; + } + if (attempt + 1 < M5PM1_I2C_RETRY_COUNT) { + M5PM1_DELAY_MS(M5PM1_I2C_RETRY_DELAY_MS); + } + } + _lastCommTime = M5PM1_GET_TIME_MS(); + return success; +} + +bool M5PM1::_writeBytes(uint8_t reg, const uint8_t* data, uint8_t len) { + _checkAutoWake(); + bool success = false; + for (int attempt = 0; attempt < M5PM1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO + success = M5PM1_I2C_WRITE_BYTES(_wire, _addr, reg, len, data); +#else + switch (_i2cDriverType) { + case M5PM1_I2C_DRIVER_SELF_CREATED: + case M5PM1_I2C_DRIVER_MASTER: + success = M5PM1_I2C_MASTER_WRITE_BYTES(_i2c_master_dev, reg, len, data) == ESP_OK; + break; + case M5PM1_I2C_DRIVER_BUS: + success = M5PM1_I2C_WRITE_BYTES(_i2c_device, reg, len, data) == ESP_OK; + break; + default: + success = false; + break; + } +#endif + if (success) { + break; + } + if (attempt + 1 < M5PM1_I2C_RETRY_COUNT) { + M5PM1_DELAY_MS(M5PM1_I2C_RETRY_DELAY_MS); + } + } + _lastCommTime = M5PM1_GET_TIME_MS(); + return success; +} + +bool M5PM1::_readBytes(uint8_t reg, uint8_t* data, uint8_t len) { + _checkAutoWake(); + bool success = false; + for (int attempt = 0; attempt < M5PM1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO + success = M5PM1_I2C_READ_BYTES(_wire, _addr, reg, len, data); +#else + switch (_i2cDriverType) { + case M5PM1_I2C_DRIVER_SELF_CREATED: + case M5PM1_I2C_DRIVER_MASTER: + success = M5PM1_I2C_MASTER_READ_BYTES(_i2c_master_dev, reg, len, data) == ESP_OK; + break; + case M5PM1_I2C_DRIVER_BUS: + success = M5PM1_I2C_READ_BYTES(_i2c_device, reg, len, data) == ESP_OK; + break; + default: + success = false; + break; + } +#endif + if (success) { + break; + } + if (attempt + 1 < M5PM1_I2C_RETRY_COUNT) { + M5PM1_DELAY_MS(M5PM1_I2C_RETRY_DELAY_MS); + } + } + _lastCommTime = M5PM1_GET_TIME_MS(); + return success; +} + +// ============================ +// Device Information +// 设备信息 +// ============================ + +m5pm1_err_t M5PM1::getDeviceId(uint8_t* id) { + if (id == nullptr) { + M5PM1_LOG_E(TAG, "getDeviceId id is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_DEVICE_ID, id)) { + M5PM1_LOG_E(TAG, "Failed to read device ID"); + return M5PM1_ERR_I2C_COMM; + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getDeviceModel(uint8_t* model) { + if (model == nullptr) { + M5PM1_LOG_E(TAG, "getDeviceModel model is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_DEVICE_MODEL, model)) { + M5PM1_LOG_E(TAG, "Failed to read device model"); + return M5PM1_ERR_I2C_COMM; + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getHwVersion(uint8_t* version) { + if (version == nullptr) { + M5PM1_LOG_E(TAG, "getHwVersion version is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_HW_REV, version)) { + M5PM1_LOG_E(TAG, "Failed to read hardware version"); + return M5PM1_ERR_I2C_COMM; + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getSwVersion(uint8_t* version) { + if (version == nullptr) { + M5PM1_LOG_E(TAG, "getSwVersion version is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_SW_REV, version)) { + M5PM1_LOG_E(TAG, "Failed to read software version"); + return M5PM1_ERR_I2C_COMM; + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getVersion(uint8_t* version) { + return getSwVersion(version); +} + +// ============================ +// GPIO 功能 (Arduino风格 - 带返回值) +// GPIO Functions (Arduino-style - WithRes) +// ============================ + +void M5PM1::pinModeWithRes(uint8_t pin, uint8_t mode, m5pm1_err_t* err) { + m5pm1_err_t localErr = M5PM1_OK; + + if (!_isValidPin(pin)) { + localErr = M5PM1_ERR_INVALID_ARG; + if (err) *err = localErr; + return; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + localErr = M5PM1_ERR_NOT_INIT; + if (err) *err = localErr; + return; + } + + m5pm1_gpio_func_t func = M5PM1_GPIO_FUNC_GPIO; + m5pm1_gpio_mode_t gpioMode = M5PM1_GPIO_MODE_INPUT; + m5pm1_gpio_pull_t pull = M5PM1_GPIO_PULL_NONE; + m5pm1_gpio_drive_t drive = M5PM1_GPIO_DRIVE_PUSHPULL; + bool setDrive = false; + + switch (mode) { + case INPUT: + gpioMode = M5PM1_GPIO_MODE_INPUT; + pull = M5PM1_GPIO_PULL_NONE; + break; + case OUTPUT: + gpioMode = M5PM1_GPIO_MODE_OUTPUT; + pull = M5PM1_GPIO_PULL_NONE; + drive = M5PM1_GPIO_DRIVE_PUSHPULL; + setDrive = true; + break; + case PULLUP: + case INPUT_PULLUP: + gpioMode = M5PM1_GPIO_MODE_INPUT; + pull = M5PM1_GPIO_PULL_UP; + break; + case PULLDOWN: + case INPUT_PULLDOWN: + gpioMode = M5PM1_GPIO_MODE_INPUT; + pull = M5PM1_GPIO_PULL_DOWN; + break; + case OPEN_DRAIN: + case OUTPUT_OPEN_DRAIN: + gpioMode = M5PM1_GPIO_MODE_OUTPUT; + pull = M5PM1_GPIO_PULL_NONE; + drive = M5PM1_GPIO_DRIVE_OPENDRAIN; + setDrive = true; + break; + case ANALOG: + func = M5PM1_GPIO_FUNC_OTHER; + gpioMode = M5PM1_GPIO_MODE_INPUT; + pull = M5PM1_GPIO_PULL_NONE; + break; + default: + M5PM1_LOG_E(TAG, "Invalid mode: 0x%02X", mode); + localErr = M5PM1_ERR_INVALID_ARG; + if (err) *err = localErr; + return; + } + + localErr = gpioSetFunc((m5pm1_gpio_num_t)pin, func); + if (localErr != M5PM1_OK) { + if (err) *err = localErr; + return; + } + + localErr = gpioSetMode((m5pm1_gpio_num_t)pin, gpioMode); + if (localErr != M5PM1_OK) { + if (err) *err = localErr; + return; + } + + localErr = gpioSetPull((m5pm1_gpio_num_t)pin, pull); + if (localErr != M5PM1_OK) { + if (err) *err = localErr; + return; + } + + if (setDrive) { + localErr = gpioSetDrive((m5pm1_gpio_num_t)pin, drive); + if (localErr != M5PM1_OK) { + if (err) *err = localErr; + return; + } + } + + if (err) *err = localErr; +} + +void M5PM1::digitalWriteWithRes(uint8_t pin, uint8_t value, m5pm1_err_t* err) { + m5pm1_err_t localErr = M5PM1_OK; + + if (!_isValidPin(pin)) { + localErr = M5PM1_ERR_INVALID_ARG; + if (err) *err = localErr; + return; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + localErr = M5PM1_ERR_NOT_INIT; + if (err) *err = localErr; + return; + } + + localErr = gpioSetOutput((m5pm1_gpio_num_t)pin, value); + if (err) *err = localErr; +} + +int M5PM1::digitalReadWithRes(uint8_t pin, m5pm1_err_t* err) { + m5pm1_err_t localErr = M5PM1_OK; + + if (!_isValidPin(pin)) { + localErr = M5PM1_ERR_INVALID_ARG; + if (err) *err = localErr; + return -1; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + localErr = M5PM1_ERR_NOT_INIT; + if (err) *err = localErr; + return -1; + } + uint8_t value = 0; + localErr = gpioGetInput((m5pm1_gpio_num_t)pin, &value); + if (localErr != M5PM1_OK) { + if (err) *err = localErr; + return -1; + } + if (err) *err = M5PM1_OK; + return value; +} + +// ============================ +// GPIO 功能 (Arduino风格) +// GPIO Functions (Arduino-style) +// ============================ + +void M5PM1::pinMode(uint8_t pin, uint8_t mode) { pinModeWithRes(pin, mode, nullptr); } +void M5PM1::digitalWrite(uint8_t pin, uint8_t value) { digitalWriteWithRes(pin, value, nullptr); } +int M5PM1::digitalRead(uint8_t pin) { return digitalReadWithRes(pin, nullptr); } + +// ============================ +// 高级 GPIO 功能 +// Advanced GPIO Functions +// ============================ + +m5pm1_err_t M5PM1::gpioSetFunc(m5pm1_gpio_num_t pin, m5pm1_gpio_func_t func) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regAddr = (pin < 4) ? M5PM1_REG_GPIO_FUNC0 : M5PM1_REG_GPIO_FUNC1; + uint8_t shift = (pin < 4) ? (pin * 2) : ((pin - 4) * 2); + + uint8_t regVal; + if (!_readReg(regAddr, ®Val)) return M5PM1_ERR_I2C_COMM; + + regVal &= ~(0x03 << shift); + regVal |= ((uint8_t)func << shift); + + if (!_writeReg(regAddr, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_GPIO); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::gpioSetMode(m5pm1_gpio_num_t pin, m5pm1_gpio_mode_t mode) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_GPIO_MODE, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (mode == M5PM1_GPIO_MODE_OUTPUT) { + regVal |= (1 << pin); + } else { + regVal &= ~(1 << pin); + } + + if (!_writeReg(M5PM1_REG_GPIO_MODE, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_GPIO); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::gpioSetOutput(m5pm1_gpio_num_t pin, uint8_t value) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_GPIO_OUT, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (value) { + regVal |= (1 << pin); + } else { + regVal &= ~(1 << pin); + } + + if (!_writeReg(M5PM1_REG_GPIO_OUT, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_GPIO); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::gpioGetInput(m5pm1_gpio_num_t pin, uint8_t* value) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (value == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_GPIO_IN, ®Val)) return M5PM1_ERR_I2C_COMM; + + *value = (regVal >> pin) & 0x01; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::gpioSetPull(m5pm1_gpio_num_t pin, m5pm1_gpio_pull_t pull) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regAddr = (pin < 4) ? M5PM1_REG_GPIO_PUPD0 : M5PM1_REG_GPIO_PUPD1; + uint8_t shift = (pin < 4) ? (pin * 2) : ((pin - 4) * 2); + + uint8_t regVal; + if (!_readReg(regAddr, ®Val)) return M5PM1_ERR_I2C_COMM; + + regVal &= ~(0x03 << shift); + regVal |= ((uint8_t)pull << shift); + + if (!_writeReg(regAddr, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_GPIO); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::gpioSetDrive(m5pm1_gpio_num_t pin, m5pm1_gpio_drive_t drive) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_GPIO_DRV, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (drive == M5PM1_GPIO_DRIVE_OPENDRAIN) { + regVal |= (1 << pin); + } else { + regVal &= ~(1 << pin); + } + + if (!_writeReg(M5PM1_REG_GPIO_DRV, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_GPIO); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::gpioSetWakeEnable(m5pm1_gpio_num_t pin, bool enable) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_GPIO_WAKE_EN, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (enable) { + regVal |= (1 << pin); + } else { + regVal &= ~(1 << pin); + } + + if (!_writeReg(M5PM1_REG_GPIO_WAKE_EN, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_GPIO); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::gpioSetWakeEdge(m5pm1_gpio_num_t pin, m5pm1_gpio_wake_edge_t edge) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_GPIO_WAKE_CFG, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (edge == M5PM1_GPIO_WAKE_RISING) { + regVal |= (1 << pin); + } else { + regVal &= ~(1 << pin); + } + + if (!_writeReg(M5PM1_REG_GPIO_WAKE_CFG, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_GPIO); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::gpioSet(m5pm1_gpio_num_t pin, m5pm1_gpio_mode_t mode, + uint8_t value, m5pm1_gpio_pull_t pull, m5pm1_gpio_drive_t drive) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + m5pm1_err_t err = gpioSetFunc(pin, M5PM1_GPIO_FUNC_GPIO); + if (err != M5PM1_OK) return err; + + err = gpioSetMode(pin, mode); + if (err != M5PM1_OK) return err; + + if (mode == M5PM1_GPIO_MODE_OUTPUT) { + err = gpioSetOutput(pin, value); + if (err != M5PM1_OK) return err; + } + + err = gpioSetPull(pin, pull); + if (err != M5PM1_OK) return err; + + err = gpioSetDrive(pin, drive); + if (err != M5PM1_OK) return err; + + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::ledEnSetDrive(m5pm1_gpio_drive_t drive) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_GPIO_DRV, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (drive == M5PM1_GPIO_DRIVE_OPENDRAIN) { + regVal |= (1 << 5); // LED_EN_DRV is bit 5 + } else { + regVal &= ~(1 << 5); + } + + if (!_writeReg(M5PM1_REG_GPIO_DRV, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_GPIO); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::dumpPinStatus() { + uint8_t mode, out, in, drv, pupd0, pupd1, func0, func1, wakeEn, wakeCfg; + + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_GPIO_MODE, &mode)) return M5PM1_ERR_I2C_COMM; + if (!_readReg(M5PM1_REG_GPIO_OUT, &out)) return M5PM1_ERR_I2C_COMM; + if (!_readReg(M5PM1_REG_GPIO_IN, &in)) return M5PM1_ERR_I2C_COMM; + if (!_readReg(M5PM1_REG_GPIO_DRV, &drv)) return M5PM1_ERR_I2C_COMM; + if (!_readReg(M5PM1_REG_GPIO_PUPD0, &pupd0)) return M5PM1_ERR_I2C_COMM; + if (!_readReg(M5PM1_REG_GPIO_PUPD1, &pupd1)) return M5PM1_ERR_I2C_COMM; + if (!_readReg(M5PM1_REG_GPIO_FUNC0, &func0)) return M5PM1_ERR_I2C_COMM; + if (!_readReg(M5PM1_REG_GPIO_FUNC1, &func1)) return M5PM1_ERR_I2C_COMM; + if (!_readReg(M5PM1_REG_GPIO_WAKE_EN, &wakeEn)) return M5PM1_ERR_I2C_COMM; + if (!_readReg(M5PM1_REG_GPIO_WAKE_CFG, &wakeCfg)) return M5PM1_ERR_I2C_COMM; + + M5PM1_LOG_I(TAG, "=== PM1 Pin Status ==="); + M5PM1_LOG_I(TAG, "MODE: 0x%02X OUT: 0x%02X IN: 0x%02X DRV: 0x%02X", mode, out, in, drv); + M5PM1_LOG_I(TAG, "PUPD0: 0x%02X PUPD1: 0x%02X", pupd0, pupd1); + M5PM1_LOG_I(TAG, "FUNC0: 0x%02X FUNC1: 0x%02X", func0, func1); + M5PM1_LOG_I(TAG, "WAKE_EN: 0x%02X WAKE_CFG: 0x%02X", wakeEn, wakeCfg); + + const char* funcNames[] = {"GPIO", "IRQ", "WAKE", "OTHER"}; + const char* pullNames[] = {"NONE", "UP", "DOWN", "?"}; + const char* drvNames[] = {"PP", "OD"}; + + for (int i = 0; i < 5; i++) { + uint8_t funcVal = (i < 4) ? ((func0 >> (i * 2)) & 0x03) : ((func1 >> 0) & 0x03); + uint8_t pullVal = (i < 4) ? ((pupd0 >> (i * 2)) & 0x03) : ((pupd1 >> 0) & 0x03); + uint8_t modeVal = (mode >> i) & 0x01; + uint8_t outVal = (out >> i) & 0x01; + uint8_t inVal = (in >> i) & 0x01; + uint8_t drvVal = (drv >> i) & 0x01; + + M5PM1_LOG_I(TAG, "GPIO%d: %s %s %s OUT=%d IN=%d", + i, funcNames[funcVal], modeVal ? "OUT" : "IN", + pullNames[pullVal], outVal, inVal); + } + + M5PM1_LOG_I(TAG, "LED_EN_DRV: %s", drvNames[(drv >> 5) & 0x01]); + M5PM1_LOG_I(TAG, "======================"); + + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getPinStatus(m5pm1_gpio_num_t pin, m5pm1_pin_status_t* status) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (status == nullptr) return M5PM1_ERR_INVALID_ARG; + + if (!_cacheValid) { + if (!_snapshotPinStates()) return M5PM1_ERR_I2C_COMM; + } + + *status = _pinStatus[pin]; + return M5PM1_OK; +} + +const m5pm1_pin_status_t* M5PM1::getPinStatusArray() const { + return _pinStatus; +} + +// ============================ +// 电源保持功能 +// Power Hold Functions +// ============================ + +m5pm1_err_t M5PM1::gpioSetPowerHold(m5pm1_gpio_num_t pin, bool enable) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_HOLD_CFG, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (enable) { + regVal |= (1 << pin); + } else { + regVal &= ~(1 << pin); + } + + if (!_writeReg(M5PM1_REG_HOLD_CFG, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_GPIO | M5PM1_SNAPSHOT_DOMAIN_POWER); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::gpioGetPowerHold(m5pm1_gpio_num_t pin, bool* enable) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (enable == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_HOLD_CFG, ®Val)) return M5PM1_ERR_I2C_COMM; + + *enable = (regVal >> pin) & 0x01; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::ldoSetPowerHold(bool enable) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_HOLD_CFG, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (enable) { + regVal |= (1 << 5); // LDO bit + } else { + regVal &= ~(1 << 5); + } + + if (!_writeReg(M5PM1_REG_HOLD_CFG, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_POWER); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::ldoGetPowerHold(bool* enable) { + if (enable == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_HOLD_CFG, ®Val)) return M5PM1_ERR_I2C_COMM; + + *enable = (regVal >> 5) & 0x01; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::dcdcSetPowerHold(bool enable) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_HOLD_CFG, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (enable) { + regVal |= (1 << 6); // DCDC bit + } else { + regVal &= ~(1 << 6); + } + + if (!_writeReg(M5PM1_REG_HOLD_CFG, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_POWER); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::dcdcGetPowerHold(bool* enable) { + if (enable == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_HOLD_CFG, ®Val)) return M5PM1_ERR_I2C_COMM; + + *enable = (regVal >> 6) & 0x01; + return M5PM1_OK; +} + +// ============================ +// ADC 功能 +// ADC Functions +// ============================ + +m5pm1_err_t M5PM1::analogRead(m5pm1_adc_channel_t channel, uint16_t* value) { + if (value == nullptr) { + M5PM1_LOG_E(TAG, "analogRead value is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (channel != M5PM1_ADC_CH_1 && channel != M5PM1_ADC_CH_2 && channel != M5PM1_ADC_CH_TEMP) { + M5PM1_LOG_E(TAG, "Invalid ADC channel: %d", channel); + return M5PM1_ERR_INVALID_ARG; + } + + // 启动转换 + // Start conversion + uint8_t ctrl = ((uint8_t)channel << 1) | 0x01; + if (!_writeReg(M5PM1_REG_ADC_CTRL, ctrl)) { + M5PM1_LOG_E(TAG, "Failed to write ADC_CTRL register"); + return M5PM1_ERR_I2C_COMM; + } + + // 等待转换完成 + // Wait for conversion completion + uint8_t reg = 0; + int tries = 0; + do { + M5PM1_DELAY_MS(1); + if (!_readReg(M5PM1_REG_ADC_CTRL, ®)) { + M5PM1_LOG_E(TAG, "Failed to read ADC_CTRL register"); + return M5PM1_ERR_I2C_COMM; + } + tries++; + } while ((reg & 0x01) && tries < 20); + + if (reg & 0x01) { + M5PM1_LOG_E(TAG, "ADC conversion timeout on channel %d", channel); + return M5PM1_ERR_TIMEOUT; + } + + if (!_readReg16(M5PM1_REG_ADC_RES_L, value)) { + M5PM1_LOG_E(TAG, "Failed to read ADC result register"); + return M5PM1_ERR_I2C_COMM; + } + + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_ADC); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::isAdcBusy(bool* busy) { + if (busy == nullptr) { + M5PM1_LOG_E(TAG, "isAdcBusy busy is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t ctrl = 0; + if (!_readReg(M5PM1_REG_ADC_CTRL, &ctrl)) { + M5PM1_LOG_E(TAG, "Failed to read ADC_CTRL register"); + return M5PM1_ERR_I2C_COMM; + } + + *busy = (ctrl & 0x01) != 0; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::disableAdc() { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + // 写入寄存器 + // Write register + if (!_writeReg(M5PM1_REG_ADC_CTRL, 0)) { + M5PM1_LOG_E(TAG, "Failed to write ADC_CTRL register for disable"); + return M5PM1_ERR_I2C_COMM; + } + + // 回读验证 + // Read-back verification + uint8_t actualCtrl = 0; + if (!_readReg(M5PM1_REG_ADC_CTRL, &actualCtrl)) { + M5PM1_LOG_E(TAG, "Failed to read back ADC_CTRL register for disable"); + return M5PM1_ERR_I2C_COMM; + } + + // 验证关键位是否匹配 + // Verify critical bits match + if (actualCtrl != 0) { + M5PM1_LOG_E(TAG, "ADC disable verification failed: expected=0, actual=0x%02X", actualCtrl); + return M5PM1_FAIL; + } + + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_ADC); + M5PM1_LOG_I(TAG, "ADC disabled and verified"); + return M5PM1_OK; +} + +// ============================ +// 温度传感器 +// Temperature Sensor +// ============================ + +m5pm1_err_t M5PM1::readTemperature(uint16_t* temperature) { + return analogRead(M5PM1_ADC_CH_TEMP, temperature); +} + +// ============================ +// PWM 功能 +// PWM Functions +// ============================ + +m5pm1_err_t M5PM1::setPwmFrequency(uint16_t frequency) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + // 写入寄存器 + // Write register + if (!_writeReg16(M5PM1_REG_PWM_FREQ_L, frequency)) { + M5PM1_LOG_E(TAG, "Failed to write PWM frequency register"); + return M5PM1_ERR_I2C_COMM; + } + + // 回读验证 + // Read-back verification + uint16_t actualFreq = 0; + if (!_readReg16(M5PM1_REG_PWM_FREQ_L, &actualFreq)) { + M5PM1_LOG_E(TAG, "Failed to read back PWM frequency register"); + return M5PM1_ERR_I2C_COMM; + } + + // 验证关键位是否匹配 + // Verify critical bits match + if (actualFreq != frequency) { + M5PM1_LOG_E(TAG, "PWM frequency verification failed: expected=%d, actual=%d", + frequency, actualFreq); + return M5PM1_FAIL; + } + + _pwmFrequency = frequency; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_PWM); + M5PM1_LOG_I(TAG, "PWM frequency set and verified: %d Hz", frequency); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getPwmFrequency(uint16_t* frequency) { + if (frequency == nullptr) { + M5PM1_LOG_E(TAG, "getPwmFrequency frequency is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + if (!_readReg16(M5PM1_REG_PWM_FREQ_L, frequency)) { + M5PM1_LOG_E(TAG, "Failed to read PWM frequency register"); + return M5PM1_ERR_I2C_COMM; + } + + _pwmFrequency = *frequency; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::setPwmDuty(m5pm1_pwm_channel_t channel, uint8_t duty, + bool polarity, bool enable) { + if (channel > M5PM1_PWM_CH_1 || duty > 100) { + M5PM1_LOG_E(TAG, "Invalid PWM channel or duty: ch=%d duty=%d", channel, duty); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + // 百分比转换为 12-bit + // Convert percentage to 12-bit + uint16_t duty12 = (uint16_t)((duty * 0x0FFF) / 100); + return setPwmDuty12bit(channel, duty12, polarity, enable); +} + +m5pm1_err_t M5PM1::getPwmDuty(m5pm1_pwm_channel_t channel, uint8_t* duty, + bool* polarity, bool* enable) { + if (channel > M5PM1_PWM_CH_1) { + M5PM1_LOG_E(TAG, "Invalid PWM channel: %d", channel); + return M5PM1_ERR_INVALID_ARG; + } + if (duty == nullptr || polarity == nullptr || enable == nullptr) { + M5PM1_LOG_E(TAG, "getPwmDuty output pointer is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regL = (channel == M5PM1_PWM_CH_0) ? M5PM1_REG_PWM0_L : M5PM1_REG_PWM1_L; + uint16_t data = 0; + if (!_readReg16(regL, &data)) { + M5PM1_LOG_E(TAG, "Failed to read PWM channel %d duty register", channel); + return M5PM1_ERR_I2C_COMM; + } + + uint16_t duty12 = data & 0x0FFF; + *duty = (uint8_t)((duty12 * 100) / 0x0FFF); + *polarity = (data & ((uint16_t)0x20 << 8)) != 0; + *enable = (data & ((uint16_t)0x10 << 8)) != 0; + + _pwmStates[channel].duty12 = duty12; + _pwmStates[channel].polarity = *polarity; + _pwmStates[channel].enabled = *enable; + _pwmStatesValid = true; + + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::setPwmDuty12bit(m5pm1_pwm_channel_t channel, uint16_t duty12, + bool polarity, bool enable) { + if (channel > M5PM1_PWM_CH_1 || duty12 > 0x0FFF) { + M5PM1_LOG_E(TAG, "Invalid PWM channel or duty12: ch=%d duty12=%d", channel, duty12); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regL = (channel == M5PM1_PWM_CH_0) ? M5PM1_REG_PWM0_L : M5PM1_REG_PWM1_L; + uint8_t dataL = (uint8_t)(duty12 & 0xFF); + uint8_t dataH = (uint8_t)((duty12 >> 8) & 0x0F); + if (polarity) dataH |= 0x20; + if (enable) dataH |= 0x10; + + uint8_t buf[2] = {dataL, dataH}; + + // 写入寄存器 + // Write register + if (!_writeBytes(regL, buf, 2)) { + M5PM1_LOG_E(TAG, "Failed to write PWM channel %d duty register", channel); + return M5PM1_ERR_I2C_COMM; + } + + // 回读验证 + // Read-back verification + uint16_t actualData = 0; + if (!_readReg16(regL, &actualData)) { + M5PM1_LOG_E(TAG, "Failed to read back PWM channel %d duty register", channel); + return M5PM1_ERR_I2C_COMM; + } + + // 验证关键位是否匹配 + // Verify critical bits match + uint16_t actualDuty12 = actualData & 0x0FFF; + bool actualPolarity = (actualData & ((uint16_t)0x20 << 8)) != 0; + bool actualEnable = (actualData & ((uint16_t)0x10 << 8)) != 0; + + if (actualDuty12 != duty12 || actualPolarity != polarity || actualEnable != enable) { + M5PM1_LOG_E(TAG, "PWM channel %d verification failed: duty=%d/%d, pol=%d/%d, en=%d/%d", + channel, duty12, actualDuty12, polarity, actualPolarity, enable, actualEnable); + return M5PM1_FAIL; + } + + _pwmStates[channel].duty12 = duty12; + _pwmStates[channel].polarity = polarity; + _pwmStates[channel].enabled = enable; + _pwmStatesValid = true; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_PWM); + + M5PM1_LOG_I(TAG, "PWM channel %d set and verified: duty=%d, pol=%d, en=%d", + channel, duty12, polarity, enable); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getPwmDuty12bit(m5pm1_pwm_channel_t channel, uint16_t* duty12, + bool* polarity, bool* enable) { + if (channel > M5PM1_PWM_CH_1 || duty12 == nullptr || polarity == nullptr || enable == nullptr) { + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regL = (channel == M5PM1_PWM_CH_0) ? M5PM1_REG_PWM0_L : M5PM1_REG_PWM1_L; + uint16_t data = 0; + if (!_readReg16(regL, &data)) { + M5PM1_LOG_E(TAG, "Failed to read PWM channel %d duty register", channel); + return M5PM1_ERR_I2C_COMM; + } + + *duty12 = data & 0x0FFF; + *polarity = (data & ((uint16_t)0x20 << 8)) != 0; + *enable = (data & ((uint16_t)0x10 << 8)) != 0; + + _pwmStates[channel].duty12 = *duty12; + _pwmStates[channel].polarity = *polarity; + _pwmStates[channel].enabled = *enable; + _pwmStatesValid = true; + + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::analogWrite(m5pm1_pwm_channel_t channel, uint8_t value) { + if (channel > M5PM1_PWM_CH_1) { + M5PM1_LOG_E(TAG, "Invalid channel: ch=%d", channel); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + // 值为 0 时关闭 PWM 输出 + // Turn off PWM when value is 0 + if (value == 0) { + return setPwmDuty12bit(channel, 0, false, false); + } + + // 将 8-bit 值缩放到 12-bit + // Scale 8-bit value to 12-bit + uint16_t duty12 = (uint16_t)value * 16 + (uint16_t)value / 16; + return setPwmDuty12bit(channel, duty12, false, true); +} + +// ============================ +// 电压读取功能 +// Voltage Reading Functions +// ============================ + +m5pm1_err_t M5PM1::readVref(uint16_t* mv) { + if (mv == nullptr) { + M5PM1_LOG_E(TAG, "readVref mv is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg16(M5PM1_REG_VREF_L, mv)) { + M5PM1_LOG_E(TAG, "Failed to read reference voltage"); + return M5PM1_ERR_I2C_COMM; + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getRefVoltage(uint16_t* mv) { + return readVref(mv); +} + +m5pm1_err_t M5PM1::readVbat(uint16_t* mv) { + if (mv == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg16(M5PM1_REG_VBAT_L, mv)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::readVin(uint16_t* mv) { + if (mv == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg16(M5PM1_REG_VIN_L, mv)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::read5VInOut(uint16_t* mv) { + if (mv == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg16(M5PM1_REG_5VINOUT_L, mv)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +// ============================ +// Power Management +// 电源管理 +// ============================ + +m5pm1_err_t M5PM1::getPowerSource(m5pm1_pwr_src_t* src) { + if (src == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t val; + if (!_readReg(M5PM1_REG_PWR_SRC, &val)) return M5PM1_ERR_I2C_COMM; + *src = (m5pm1_pwr_src_t)(val & 0x07); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getWakeSource(uint8_t* src, uint8_t clearAfterRead) { + if (src == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_WAKE_SRC, src)) return M5PM1_ERR_I2C_COMM; + + if (clearAfterRead == 1 && *src != 0) { + // Clear triggered bits + if (!_writeReg(M5PM1_REG_WAKE_SRC, *src)) return M5PM1_ERR_I2C_COMM; + } else if (clearAfterRead == 2) { + // Clear all + if (!_writeReg(M5PM1_REG_WAKE_SRC, 0x7F)) return M5PM1_ERR_I2C_COMM; + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::clearWakeSource(uint8_t mask) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_writeReg(M5PM1_REG_WAKE_SRC, mask)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::setPowerConfig(uint8_t mask, uint8_t value) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t current; + if (!_readReg(M5PM1_REG_PWR_CFG, ¤t)) return M5PM1_ERR_I2C_COMM; + current = (current & ~mask) | (value & mask); + if (!_writeReg(M5PM1_REG_PWR_CFG, current)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_POWER); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getPowerConfig(uint8_t* config) { + if (config == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_PWR_CFG, config)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::clearPowerConfig(uint8_t mask) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t current; + if (!_readReg(M5PM1_REG_PWR_CFG, ¤t)) return M5PM1_ERR_I2C_COMM; + current &= ~mask; + if (!_writeReg(M5PM1_REG_PWR_CFG, current)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_POWER); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::setChargeEnable(bool enable) { + return setPowerConfig(M5PM1_PWR_CFG_CHG_EN, enable ? M5PM1_PWR_CFG_CHG_EN : 0); +} + +m5pm1_err_t M5PM1::setDcdcEnable(bool enable) { + return setPowerConfig(M5PM1_PWR_CFG_DCDC_EN, enable ? M5PM1_PWR_CFG_DCDC_EN : 0); +} + +m5pm1_err_t M5PM1::setLdoEnable(bool enable) { + return setPowerConfig(M5PM1_PWR_CFG_LDO_EN, enable ? M5PM1_PWR_CFG_LDO_EN : 0); +} + +m5pm1_err_t M5PM1::set5VInOutEnable(bool enable) { + return setPowerConfig(M5PM1_PWR_CFG_5V_INOUT, enable ? M5PM1_PWR_CFG_5V_INOUT : 0); +} + +m5pm1_err_t M5PM1::setLedControlEnable(bool enable) { + return setPowerConfig(M5PM1_PWR_CFG_LED_CTRL, enable ? M5PM1_PWR_CFG_LED_CTRL : 0); +} + +// ============================ +// 电池功能 +// Battery Functions +// ============================ + +m5pm1_err_t M5PM1::setBatteryLvp(uint16_t mv) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + // LVP value = (voltage_mv - 2000) / 7.81 + uint8_t lvp = (uint8_t)((mv - 2000) / 7.81f); + if (!_writeReg(M5PM1_REG_BATT_LVP, lvp)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +// ============================ +// 看门狗功能 +// Watchdog Functions +// ============================ + +m5pm1_err_t M5PM1::wdtSet(uint8_t timeout_sec) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_writeReg(M5PM1_REG_WDT_CNT, timeout_sec)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::wdtFeed() { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_writeReg(M5PM1_REG_WDT_KEY, M5PM1_WDT_FEED_KEY)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::wdtGetCount(uint8_t* count) { + if (count == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_WDT_CNT, count)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +// ============================ +// 定时器功能 +// Timer Functions +// ============================ + +m5pm1_err_t M5PM1::timerSet(uint32_t seconds, m5pm1_tim_action_t action, bool autoRearm) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + // Write 31-bit timer value + uint8_t data[4]; + data[0] = (seconds >> 0) & 0xFF; + data[1] = (seconds >> 8) & 0xFF; + data[2] = (seconds >> 16) & 0xFF; + data[3] = (seconds >> 24) & 0x7F; + + if (!_writeBytes(M5PM1_REG_TIM_CNT_0, data, 4)) return M5PM1_ERR_I2C_COMM; + + // Configure timer + uint8_t cfg = (uint8_t)action; + if (autoRearm) cfg |= 0x08; + + if (!_writeReg(M5PM1_REG_TIM_CFG, cfg)) return M5PM1_ERR_I2C_COMM; + + // Reload timer + if (!_writeReg(M5PM1_REG_TIM_KEY, M5PM1_TIM_RELOAD_KEY)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::timerClear() { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_writeReg(M5PM1_REG_TIM_CFG, 0)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +// ============================ +// 按钮功能 +// Button Functions +// ============================ + +m5pm1_err_t M5PM1::btnSetConfig(m5pm1_btn_type_t type, m5pm1_btn_delay_t delay) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_BTN_CFG_1, ®Val)) return M5PM1_ERR_I2C_COMM; + + uint8_t shift; + switch (type) { + case M5PM1_BTN_TYPE_CLICK: + shift = 1; + break; + case M5PM1_BTN_TYPE_LONG: + shift = 3; + break; + case M5PM1_BTN_TYPE_DOUBLE: + shift = 5; + break; + default: + return M5PM1_ERR_INVALID_ARG; + } + + regVal &= ~(0x03 << shift); + regVal |= ((uint8_t)delay << shift); + + if (!_writeReg(M5PM1_REG_BTN_CFG_1, regVal)) return M5PM1_ERR_I2C_COMM; + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_BUTTON); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::btnGetState(bool* pressed) { + if (pressed == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t val; + if (!_readReg(M5PM1_REG_BTN_STATUS, &val)) return M5PM1_ERR_I2C_COMM; + *pressed = (val & 0x01) != 0; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::btnGetFlag(bool* wasPressed) { + if (wasPressed == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t val; + if (!_readReg(M5PM1_REG_BTN_STATUS, &val)) return M5PM1_ERR_I2C_COMM; + *wasPressed = (val & 0x80) != 0; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::setSingleResetDisable(bool disable) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_BTN_CFG_1, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (disable) { + regVal |= 0x01; + } else { + regVal &= ~0x01; + } + + bool ok = _writeReg(M5PM1_REG_BTN_CFG_1, regVal); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_BUTTON); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getSingleResetDisable(bool* disabled) { + if (disabled == nullptr) { + M5PM1_LOG_E(TAG, "getSingleResetDisable disabled is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_BTN_CFG_1, ®Val)) return M5PM1_ERR_I2C_COMM; + *disabled = (regVal & 0x01) != 0; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::setDoubleOffDisable(bool disable) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_BTN_CFG_2, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (disable) { + regVal |= 0x01; + } else { + regVal &= ~0x01; + } + + bool ok = _writeReg(M5PM1_REG_BTN_CFG_2, regVal); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_BUTTON); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getDoubleOffDisable(bool* disabled) { + if (disabled == nullptr) { + M5PM1_LOG_E(TAG, "getDoubleOffDisable disabled is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_BTN_CFG_2, ®Val)) return M5PM1_ERR_I2C_COMM; + *disabled = (regVal & 0x01) != 0; + return M5PM1_OK; +} + +// ============================ +// 中断功能 +// IRQ Functions +// ============================ + +m5pm1_err_t M5PM1::irqGetGpioStatus(uint8_t* status, uint8_t clearAfterRead) { + if (status == nullptr) { + M5PM1_LOG_E(TAG, "irqGetGpioStatus status is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_IRQ_STATUS1, status)) return M5PM1_ERR_I2C_COMM; + + if (clearAfterRead == 1 && *status != 0) { + // Clear triggered bits + if (!_writeReg(M5PM1_REG_IRQ_STATUS1, *status)) return M5PM1_ERR_I2C_COMM; + } else if (clearAfterRead == 2) { + // Clear all + if (!_writeReg(M5PM1_REG_IRQ_STATUS1, 0x1F)) return M5PM1_ERR_I2C_COMM; + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqClearGpio(uint8_t mask) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + bool ok = _writeReg(M5PM1_REG_IRQ_STATUS1, mask); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_IRQ_STATUS); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqGetSysStatus(uint8_t* status, uint8_t clearAfterRead) { + if (status == nullptr) { + M5PM1_LOG_E(TAG, "irqGetSysStatus status is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_IRQ_STATUS2, status)) return M5PM1_ERR_I2C_COMM; + + if (clearAfterRead == 1 && *status != 0) { + // Clear triggered bits + if (!_writeReg(M5PM1_REG_IRQ_STATUS2, *status)) return M5PM1_ERR_I2C_COMM; + } else if (clearAfterRead == 2) { + // Clear all + if (!_writeReg(M5PM1_REG_IRQ_STATUS2, 0x3F)) return M5PM1_ERR_I2C_COMM; + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqClearSys(uint8_t mask) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + bool ok = _writeReg(M5PM1_REG_IRQ_STATUS2, mask); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_IRQ_STATUS); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqGetBtnStatus(uint8_t* status, uint8_t clearAfterRead) { + if (status == nullptr) { + M5PM1_LOG_E(TAG, "irqGetBtnStatus status is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_IRQ_STATUS3, status)) return M5PM1_ERR_I2C_COMM; + + if (clearAfterRead == 1 && *status != 0) { + // Clear triggered bits + if (!_writeReg(M5PM1_REG_IRQ_STATUS3, *status)) return M5PM1_ERR_I2C_COMM; + } else if (clearAfterRead == 2) { + // Clear all + if (!_writeReg(M5PM1_REG_IRQ_STATUS3, 0x07)) return M5PM1_ERR_I2C_COMM; + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqClearBtn(uint8_t mask) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + bool ok = _writeReg(M5PM1_REG_IRQ_STATUS3, mask); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_IRQ_STATUS); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqSetGpioMask(m5pm1_gpio_num_t pin, bool mask) { + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_IRQ_MASK1, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (mask) { + regVal |= (1 << pin); + } else { + regVal &= ~(1 << pin); + } + + bool ok = _writeReg(M5PM1_REG_IRQ_MASK1, regVal); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_IRQ_MASK); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqGetGpioMask(m5pm1_gpio_num_t pin, bool* mask) { + if (mask == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_isValidPin(pin)) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_IRQ_MASK1, ®Val)) return M5PM1_ERR_I2C_COMM; + + *mask = (regVal >> pin) & 0x01; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqSetGpioMaskAll(uint8_t mask) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + bool ok = _writeReg(M5PM1_REG_IRQ_MASK1, mask & 0x1F); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_IRQ_MASK); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqGetGpioMaskAll(uint8_t* mask) { + if (mask == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_IRQ_MASK1, mask)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqSetSysMask(uint8_t event, bool mask) { + if (event > 5) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_IRQ_MASK2, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (mask) { + regVal |= (1 << event); + } else { + regVal &= ~(1 << event); + } + + bool ok = _writeReg(M5PM1_REG_IRQ_MASK2, regVal); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_IRQ_MASK); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqGetSysMask(uint8_t event, bool* mask) { + if (mask == nullptr) return M5PM1_ERR_INVALID_ARG; + if (event > 5) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_IRQ_MASK2, ®Val)) return M5PM1_ERR_I2C_COMM; + + *mask = (regVal >> event) & 0x01; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqSetSysMaskAll(uint8_t mask) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + bool ok = _writeReg(M5PM1_REG_IRQ_MASK2, mask & 0x3F); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_IRQ_MASK); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqGetSysMaskAll(uint8_t* mask) { + if (mask == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_IRQ_MASK2, mask)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqSetBtnMask(m5pm1_btn_irq_t type, bool mask) { + if (type > M5PM1_BTN_IRQ_DOUBLE) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_IRQ_MASK3, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (mask) { + regVal |= (1 << (uint8_t)type); + } else { + regVal &= ~(1 << (uint8_t)type); + } + + bool ok = _writeReg(M5PM1_REG_IRQ_MASK3, regVal); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_IRQ_MASK); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqGetBtnMask(m5pm1_btn_irq_t type, bool* mask) { + if (mask == nullptr) return M5PM1_ERR_INVALID_ARG; + if (type > M5PM1_BTN_IRQ_DOUBLE) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regVal; + if (!_readReg(M5PM1_REG_IRQ_MASK3, ®Val)) return M5PM1_ERR_I2C_COMM; + + *mask = (regVal >> (uint8_t)type) & 0x01; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqSetBtnMaskAll(uint8_t mask) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + bool ok = _writeReg(M5PM1_REG_IRQ_MASK3, mask & 0x07); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_IRQ_MASK); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::irqGetBtnMaskAll(uint8_t* mask) { + if (mask == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_readReg(M5PM1_REG_IRQ_MASK3, mask)) return M5PM1_ERR_I2C_COMM; + return M5PM1_OK; +} + +// ============================ +// System Commands +// 系统命令 +// ============================ + +m5pm1_err_t M5PM1::sysCmd(m5pm1_sys_cmd_t cmd) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t val = M5PM1_SYS_CMD_KEY | (uint8_t)cmd; + if (!_writeReg(M5PM1_REG_SYS_CMD, val)) { + return M5PM1_ERR_I2C_COMM; + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::shutdown() { + return sysCmd(M5PM1_SYS_CMD_OFF); +} + +m5pm1_err_t M5PM1::reboot() { + return sysCmd(M5PM1_SYS_CMD_RESET); +} + +m5pm1_err_t M5PM1::enterDownloadMode() { + return sysCmd(M5PM1_SYS_CMD_DL); +} + +m5pm1_err_t M5PM1::setDownloadLock(bool lock) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_BTN_CFG_1, ®Val)) return M5PM1_ERR_I2C_COMM; + + if (lock) { + regVal |= 0x80; // DL_LOCK is bit 7 + } else { + regVal &= ~0x80; + } + + bool ok = _writeReg(M5PM1_REG_BTN_CFG_1, regVal); + if (!ok) { + return M5PM1_ERR_I2C_COMM; + } + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_BUTTON); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getDownloadLock(bool* lock) { + if (lock == nullptr) { + M5PM1_LOG_E(TAG, "getDownloadLock lock is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + uint8_t regVal; + if (!_readReg(M5PM1_REG_BTN_CFG_1, ®Val)) return M5PM1_ERR_I2C_COMM; + *lock = (regVal & 0x80) != 0; + return M5PM1_OK; +} + +// ============================ +// NeoPixel 功能 +// NeoPixel Functions +// ============================ + +m5pm1_err_t M5PM1::setLedCount(uint8_t count) { + if (count > M5PM1_MAX_LED_COUNT) { + M5PM1_LOG_E(TAG, "LED count %d exceeds maximum %d", count, M5PM1_MAX_LED_COUNT); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t cfg = count & M5PM1_NEO_CFG_COUNT_MASK; + + // 写入寄存器 + // Write register + if (!_writeReg(M5PM1_REG_NEO_CFG, cfg)) { + M5PM1_LOG_E(TAG, "Failed to write NEO_CFG register"); + return M5PM1_ERR_I2C_COMM; + } + + // 回读验证 + // Read-back verification + uint8_t actualCfg = 0; + if (!_readReg(M5PM1_REG_NEO_CFG, &actualCfg)) { + M5PM1_LOG_E(TAG, "Failed to read back NEO_CFG register"); + return M5PM1_ERR_I2C_COMM; + } + + // 验证关键位是否匹配 + // Verify critical bits match + bool countMatch = ((actualCfg & M5PM1_NEO_CFG_COUNT_MASK) == (cfg & M5PM1_NEO_CFG_COUNT_MASK)); + if (!countMatch) { + M5PM1_LOG_E(TAG, "LED count verification failed: expected=%d, actual=%d", + count, actualCfg & M5PM1_NEO_CFG_COUNT_MASK); + return M5PM1_FAIL; + } + + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_NEO); + M5PM1_LOG_I(TAG, "LED count set and verified: %d", count); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::setLedColor(uint8_t index, uint8_t r, uint8_t g, uint8_t b) { + if (index >= M5PM1_MAX_LED_COUNT) { + M5PM1_LOG_E(TAG, "Invalid LED index: %d", index); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + // 转换为 RGB565 + // Convert to RGB565 + uint16_t rgb565 = ((uint16_t)(r & 0xF8) << 8) | ((uint16_t)(g & 0xFC) << 3) | (b >> 3); + uint8_t regAddr = M5PM1_REG_NEO_DATA_START + (index * 2); + + // 写入寄存器 + // Write register + if (!_writeReg16(regAddr, rgb565)) { + M5PM1_LOG_E(TAG, "Failed to write NEO data for index %d", index); + return M5PM1_ERR_I2C_COMM; + } + + // 回读验证 + // Read-back verification + uint16_t actualRgb565 = 0; + if (!_readReg16(regAddr, &actualRgb565)) { + M5PM1_LOG_E(TAG, "Failed to read back NEO data for index %d", index); + return M5PM1_ERR_I2C_COMM; + } + + // 验证关键位是否匹配 + // Verify critical bits match + if (actualRgb565 != rgb565) { + M5PM1_LOG_E(TAG, "LED color verification failed for index %d: expected=0x%04X, actual=0x%04X", + index, rgb565, actualRgb565); + return M5PM1_FAIL; + } + + M5PM1_LOG_I(TAG, "LED color set and verified for index %d: RGB(%d,%d,%d)", index, r, g, b); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::setLedColor(uint8_t index, m5pm1_rgb_t color) { + return setLedColor(index, color.r, color.g, color.b); +} + +m5pm1_err_t M5PM1::refreshLeds() { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5PM1_REG_NEO_CFG, &cfg)) { + M5PM1_LOG_E(TAG, "Failed to read NEO_CFG register"); + return M5PM1_ERR_I2C_COMM; + } + + cfg |= M5PM1_NEO_CFG_REFRESH; + if (!_writeReg(M5PM1_REG_NEO_CFG, cfg)) { + M5PM1_LOG_E(TAG, "Failed to write NEO_CFG register"); + return M5PM1_ERR_I2C_COMM; + } + + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::disableLeds() { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + // 写入寄存器 + // Write register + if (!_writeReg(M5PM1_REG_NEO_CFG, 0)) { + M5PM1_LOG_E(TAG, "Failed to write NEO_CFG register for disable"); + return M5PM1_ERR_I2C_COMM; + } + + // 回读验证 + // Read-back verification + uint8_t actualCfg = 0; + if (!_readReg(M5PM1_REG_NEO_CFG, &actualCfg)) { + M5PM1_LOG_E(TAG, "Failed to read back NEO_CFG register for disable"); + return M5PM1_ERR_I2C_COMM; + } + + // 验证关键位是否匹配 + // Verify critical bits match + if (actualCfg != 0) { + M5PM1_LOG_E(TAG, "LED disable verification failed: expected=0, actual=0x%02X", actualCfg); + return M5PM1_FAIL; + } + + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_NEO); + M5PM1_LOG_I(TAG, "LEDs disabled and verified"); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::setLeds(const m5pm1_rgb_t* colors, uint8_t arraySize, + uint8_t count, bool autoRefresh) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Device not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (colors == nullptr) { + M5PM1_LOG_E(TAG, "Colors array is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (count == 0) { + M5PM1_LOG_E(TAG, "LED count cannot be 0"); + return M5PM1_ERR_INVALID_ARG; + } + if (count > M5PM1_MAX_LED_COUNT) { + M5PM1_LOG_E(TAG, "LED count %d exceeds maximum %d", count, M5PM1_MAX_LED_COUNT); + return M5PM1_ERR_INVALID_ARG; + } + if (count > arraySize) { + M5PM1_LOG_E(TAG, "LED count %d exceeds array size %d", count, arraySize); + return M5PM1_ERR_INVALID_ARG; + } + + m5pm1_err_t err = setLedCount(count); + if (err != M5PM1_OK) { + return err; + } + + uint8_t data[M5PM1_MAX_LED_COUNT * 2]; + for (uint8_t i = 0; i < count; i++) { + uint16_t rgb565 = ((uint16_t)(colors[i].r & 0xF8) << 8) + | ((uint16_t)(colors[i].g & 0xFC) << 3) + | (colors[i].b >> 3); + data[i * 2] = (uint8_t)(rgb565 & 0xFF); + data[i * 2 + 1] = (uint8_t)((rgb565 >> 8) & 0xFF); + } + + if (!_writeBytes(M5PM1_REG_NEO_DATA_START, data, (uint8_t)(count * 2))) { + M5PM1_LOG_E(TAG, "Failed to write NEO data buffer"); + return M5PM1_ERR_I2C_COMM; + } + + if (autoRefresh) { + err = refreshLeds(); + if (err != M5PM1_OK) { + return err; + } + } + + _autoSnapshotUpdate(M5PM1_SNAPSHOT_DOMAIN_NEO); + M5PM1_LOG_I(TAG, "Set %d LEDs successfully%s", count, autoRefresh ? " (refreshed)" : ""); + return M5PM1_OK; +} + +// ============================ +// AW8737A 脉冲功能 +// AW8737A Pulse Functions +// ============================ + +m5pm1_err_t M5PM1::setAw8737aPulse(m5pm1_gpio_num_t pin, m5pm1_aw8737a_pulse_t num, + m5pm1_aw8737a_refresh_t refresh) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Device not initialized"); + return M5PM1_ERR_NOT_INIT; + } + if (!_isValidPin(pin)) { + M5PM1_LOG_E(TAG, "Invalid pin number: %d", pin); + return M5PM1_ERR_INVALID_ARG; + } + if (num > M5PM1_AW8737A_PULSE_3) { + M5PM1_LOG_E(TAG, "Invalid pulse number: %d", num); + return M5PM1_ERR_INVALID_ARG; + } + + // 构建寄存器值 + // Build register value + uint8_t regValue = (uint8_t)pin & 0x1F; + regValue |= ((uint8_t)num << 5); + if (refresh == M5PM1_AW8737A_REFRESH_NOW) { + regValue |= 0x80; + } + + // 写入寄存器 + // Write register + if (!_writeReg(M5PM1_REG_AW8737A_PULSE, regValue)) { + M5PM1_LOG_E(TAG, "Failed to set AW8737A pulse config"); + return M5PM1_ERR_I2C_COMM; + } + + if (refresh == M5PM1_AW8737A_REFRESH_NOW) { + M5PM1_DELAY_MS(20); + } + + // 回读验证 + // Read-back verification + uint8_t actualReg = 0; + if (!_readReg(M5PM1_REG_AW8737A_PULSE, &actualReg)) { + M5PM1_LOG_E(TAG, "Failed to read back AW8737A pulse register"); + return M5PM1_ERR_I2C_COMM; + } + + // 验证关键位是否匹配 + // Verify critical bits match + uint8_t expectedValue = regValue & 0x7F; + uint8_t actualValue = actualReg & 0x7F; + if (actualValue != expectedValue) { + M5PM1_LOG_E(TAG, "AW8737A pulse verification failed: expected=0x%02X, actual=0x%02X", + expectedValue, actualValue); + return M5PM1_FAIL; + } + + M5PM1_LOG_I(TAG, "AW8737A pulse set and verified: pin=%d, num=%d, refresh=%d (reg=0x%02X)", + pin, num, refresh, regValue); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::refreshAw8737aPulse() { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Device not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + // 读取当前寄存器值 + // Read current register value + uint8_t regValue = 0; + if (!_readReg(M5PM1_REG_AW8737A_PULSE, ®Value)) { + M5PM1_LOG_E(TAG, "Failed to read AW8737A pulse register"); + return M5PM1_ERR_I2C_COMM; + } + + regValue |= 0x80; + if (!_writeReg(M5PM1_REG_AW8737A_PULSE, regValue)) { + M5PM1_LOG_E(TAG, "Failed to refresh AW8737A pulse"); + return M5PM1_ERR_I2C_COMM; + } + + M5PM1_LOG_I(TAG, "AW8737A pulse refresh triggered (reg=0x%02X)", regValue); + M5PM1_DELAY_MS(20); + return M5PM1_OK; +} + +// ============================ +// RTC RAM 功能 +// RTC RAM Functions +// ============================ + +m5pm1_err_t M5PM1::writeRtcRAM(uint8_t offset, const uint8_t* data, uint8_t len) { + if (data == nullptr || offset >= M5PM1_RTC_RAM_SIZE || len == 0 || + (offset + len) > M5PM1_RTC_RAM_SIZE) { + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regAddr = M5PM1_REG_RTC_RAM_START + offset; + + // 写入寄存器 + // Write register + if (!_writeBytes(regAddr, data, len)) { + M5PM1_LOG_E(TAG, "Failed to write RTC_RAM register at offset %d", offset); + return M5PM1_ERR_I2C_COMM; + } + + // 回读验证 + // Read-back verification + uint8_t actualData[M5PM1_RTC_RAM_SIZE]; + if (!_readBytes(regAddr, actualData, len)) { + M5PM1_LOG_E(TAG, "Failed to read back RTC_RAM register at offset %d", offset); + return M5PM1_ERR_I2C_COMM; + } + + // 验证关键位是否匹配 + // Verify critical bits match + for (uint8_t i = 0; i < len; i++) { + if (actualData[i] != data[i]) { + M5PM1_LOG_E(TAG, "RTC_RAM verification failed at offset %d: expected=0x%02X, actual=0x%02X", + offset + i, data[i], actualData[i]); + return M5PM1_FAIL; + } + } + + M5PM1_LOG_I(TAG, "RTC_RAM write and verified: offset=%d, length=%d", offset, len); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::readRtcRAM(uint8_t offset, uint8_t* data, uint8_t len) { + if (data == nullptr || offset >= M5PM1_RTC_RAM_SIZE || len == 0 || + (offset + len) > M5PM1_RTC_RAM_SIZE) { + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + return M5PM1_ERR_NOT_INIT; + } + + uint8_t regAddr = M5PM1_REG_RTC_RAM_START + offset; + return _readBytes(regAddr, data, len) ? M5PM1_OK : M5PM1_ERR_I2C_COMM; +} + +// ============================ +// I2C Configuration +// I2C 配置 +// ============================ + + m5pm1_err_t M5PM1::setI2cConfig(uint8_t sleepTime, m5pm1_i2c_speed_t speed) { + if (sleepTime > 15) { + M5PM1_LOG_E(TAG, "Invalid I2C sleep time: %u", sleepTime); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint32_t targetFreq = (speed == M5PM1_I2C_SPEED_400K) ? M5PM1_I2C_FREQ_400K : M5PM1_I2C_FREQ_100K; + bool speedChanged = (targetFreq != _requestedSpeed); + + uint8_t cfg = (sleepTime & M5PM1_I2C_CFG_SLEEP_MASK); + if (speed == M5PM1_I2C_SPEED_400K) { + cfg |= M5PM1_I2C_CFG_SPEED_400K; + } + + // Step 1: Write register + if (!_writeReg(M5PM1_REG_I2C_CFG, cfg)) { + M5PM1_LOG_E(TAG, "Failed to write I2C_CFG register"); + return M5PM1_ERR_I2C_COMM; + } + + // Step 2: Read-back verification + uint8_t actualCfg = 0; + if (!_readReg(M5PM1_REG_I2C_CFG, &actualCfg)) { + M5PM1_LOG_E(TAG, "Failed to read back I2C_CFG register"); + return M5PM1_ERR_I2C_COMM; + } + + uint8_t expectedSleep = sleepTime & M5PM1_I2C_CFG_SLEEP_MASK; + uint8_t actualSleep = actualCfg & M5PM1_I2C_CFG_SLEEP_MASK; + bool actualSpeed400k = (actualCfg & M5PM1_I2C_CFG_SPEED_400K) != 0; + + if (actualSleep != expectedSleep || + actualSpeed400k != (speed == M5PM1_I2C_SPEED_400K)) { + M5PM1_LOG_E(TAG, "I2C_CFG verification failed: expected=0x%02X, actual=0x%02X", cfg, actualCfg); + return M5PM1_FAIL; + } + + if (speedChanged) { + m5pm1_err_t ret = switchI2cSpeed(speed); + if (ret != M5PM1_OK) { + return ret; + } + } + + _i2cConfig.sleepTime = sleepTime; + _i2cConfig.speed400k = (speed == M5PM1_I2C_SPEED_400K); + _i2cConfigValid = true; + _i2cSleepTime = sleepTime; + + if (sleepTime > 0 && !_autoWakeEnabled) { + setAutoWakeEnable(true); + M5PM1_LOG_W(TAG, "I2C sleep enabled, auto-wake automatically enabled"); + } + + M5PM1_LOG_I(TAG, "I2C config set and verified: sleep=%u, speed=%s", + sleepTime, speed == M5PM1_I2C_SPEED_400K ? "400K" : "100K"); + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getI2cSpeed(m5pm1_i2c_speed_t* speed) { + if (speed == nullptr) { + M5PM1_LOG_E(TAG, "getI2cSpeed speed is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5PM1_REG_I2C_CFG, &cfg)) { + return M5PM1_ERR_I2C_COMM; + } + + _i2cConfig.speed400k = (cfg & M5PM1_I2C_CFG_SPEED_400K) != 0; + _i2cConfigValid = true; + + *speed = _i2cConfig.speed400k ? M5PM1_I2C_SPEED_400K : M5PM1_I2C_SPEED_100K; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::setI2cSleepTime(uint8_t seconds) { + if (seconds > 15) { + M5PM1_LOG_E(TAG, "Invalid I2C sleep time: %u", seconds); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5PM1_REG_I2C_CFG, &cfg)) { + M5PM1_LOG_E(TAG, "Failed to read I2C_CFG register"); + return M5PM1_ERR_I2C_COMM; + } + + cfg = (cfg & ~M5PM1_I2C_CFG_SLEEP_MASK) | (seconds & M5PM1_I2C_CFG_SLEEP_MASK); + + if (!_writeReg(M5PM1_REG_I2C_CFG, cfg)) { + M5PM1_LOG_E(TAG, "Failed to write I2C_CFG register"); + return M5PM1_ERR_I2C_COMM; + } + + uint8_t actualCfg = 0; + if (!_readReg(M5PM1_REG_I2C_CFG, &actualCfg)) { + M5PM1_LOG_E(TAG, "Failed to read back I2C_CFG register"); + return M5PM1_ERR_I2C_COMM; + } + + uint8_t actualSleep = actualCfg & M5PM1_I2C_CFG_SLEEP_MASK; + if (actualSleep != seconds) { + M5PM1_LOG_E(TAG, "I2C_CFG sleep time verification failed: expected=%u, actual=%u", + seconds, actualSleep); + return M5PM1_FAIL; + } + + _i2cConfig.sleepTime = seconds; + _i2cConfigValid = true; + _i2cSleepTime = seconds; + + M5PM1_LOG_I(TAG, "I2C sleep time set and verified to %u", seconds); + if (seconds > 0 && !_autoWakeEnabled) { + setAutoWakeEnable(true); + M5PM1_LOG_W(TAG, "I2C sleep enabled, auto-wake automatically enabled"); + } + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getI2cSleepTime(uint8_t* seconds) { + if (seconds == nullptr) { + M5PM1_LOG_E(TAG, "getI2cSleepTime seconds is null"); + return M5PM1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5PM1_LOG_E(TAG, "Not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5PM1_REG_I2C_CFG, &cfg)) { + return M5PM1_ERR_I2C_COMM; + } + + _i2cConfig.sleepTime = cfg & M5PM1_I2C_CFG_SLEEP_MASK; + _i2cConfigValid = true; + _i2cSleepTime = _i2cConfig.sleepTime; + + *seconds = _i2cConfig.sleepTime; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::switchI2cSpeed(m5pm1_i2c_speed_t speed) { + if (!_initialized) { + M5PM1_LOG_E(TAG, "Cannot switch I2C speed: device not initialized"); + return M5PM1_ERR_NOT_INIT; + } + + uint32_t targetFreq = (speed == M5PM1_I2C_SPEED_400K) ? M5PM1_I2C_FREQ_400K : M5PM1_I2C_FREQ_100K; + if (targetFreq == _requestedSpeed) { + M5PM1_LOG_I(TAG, "I2C speed already at %lu Hz, no change needed", (unsigned long)targetFreq); + return M5PM1_OK; + } + + uint8_t i2cCfg = 0; + if (!_readReg(M5PM1_REG_I2C_CFG, &i2cCfg)) { + M5PM1_LOG_E(TAG, "Failed to read I2C config register"); + return M5PM1_ERR_I2C_COMM; + } + + uint8_t originalCfg = i2cCfg; + uint32_t originalFreq = (i2cCfg & M5PM1_I2C_CFG_SPEED_400K) + ? M5PM1_I2C_FREQ_400K : M5PM1_I2C_FREQ_100K; + + if (speed == M5PM1_I2C_SPEED_400K) { + i2cCfg |= M5PM1_I2C_CFG_SPEED_400K; + } else { + i2cCfg &= ~M5PM1_I2C_CFG_SPEED_400K; + } + + if (!_writeReg(M5PM1_REG_I2C_CFG, i2cCfg)) { + M5PM1_LOG_E(TAG, "Failed to write I2C config register"); + return M5PM1_ERR_I2C_COMM; + } + + M5PM1_DELAY_MS(5); + +#ifdef ARDUINO + _wire->end(); + M5PM1_DELAY_MS(10); + if (!_wire->begin(_sda, _scl, targetFreq)) { + M5PM1_LOG_E(TAG, "Failed to re-initialize I2C bus at %lu Hz", (unsigned long)targetFreq); + _wire->begin(_sda, _scl, originalFreq); + _writeReg(M5PM1_REG_I2C_CFG, originalCfg); + return M5PM1_ERR_I2C_CONFIG; + } + M5PM1_DELAY_MS(10); +#else + esp_err_t ret; + switch (_i2cDriverType) { + case M5PM1_I2C_DRIVER_SELF_CREATED: + case M5PM1_I2C_DRIVER_MASTER: { + if (_i2c_master_dev != nullptr) { + ret = i2c_master_bus_rm_device(_i2c_master_dev); + if (ret != ESP_OK) { + M5PM1_LOG_E(TAG, "Failed to remove I2C device: %s", esp_err_to_name(ret)); + return M5PM1_ERR_I2C_CONFIG; + } + _i2c_master_dev = nullptr; + + i2c_device_config_t dev_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = _addr, + .scl_speed_hz = targetFreq, + .scl_wait_us = 0, + .flags = { + .disable_ack_check = false, + }, + }; + + ret = i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + if (ret != ESP_OK) { + M5PM1_LOG_E(TAG, "Failed to add I2C device at %lu Hz: %s", + (unsigned long)targetFreq, esp_err_to_name(ret)); + dev_config.scl_speed_hz = originalFreq; + i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + _writeReg(M5PM1_REG_I2C_CFG, originalCfg); + return M5PM1_ERR_I2C_CONFIG; + } + } + break; + } + case M5PM1_I2C_DRIVER_BUS: + if (_i2c_device != nullptr) { + ret = i2c_bus_device_delete(&_i2c_device); + if (ret != ESP_OK) { + M5PM1_LOG_E(TAG, "Failed to delete I2C device: %s", esp_err_to_name(ret)); + return M5PM1_ERR_I2C_CONFIG; + } + _i2c_device = i2c_bus_device_create(_i2c_bus, _addr, targetFreq); + if (_i2c_device == nullptr) { + M5PM1_LOG_E(TAG, "Failed to create I2C device at %lu Hz", (unsigned long)targetFreq); + _i2c_device = i2c_bus_device_create(_i2c_bus, _addr, originalFreq); + _writeReg(M5PM1_REG_I2C_CFG, originalCfg); + return M5PM1_ERR_I2C_CONFIG; + } + } + break; + default: + M5PM1_LOG_E(TAG, "Unknown I2C driver type"); + return M5PM1_ERR_INTERNAL; + } +#endif + + uint8_t id = 0; + if (!_readReg(M5PM1_REG_DEVICE_ID, &id)) { + M5PM1_LOG_E(TAG, "Communication failed after switching to %lu Hz, reverting", + (unsigned long)targetFreq); + +#ifdef ARDUINO + _wire->end(); + M5PM1_DELAY_MS(10); + _wire->begin(_sda, _scl, originalFreq); + M5PM1_DELAY_MS(10); +#else + switch (_i2cDriverType) { + case M5PM1_I2C_DRIVER_SELF_CREATED: + case M5PM1_I2C_DRIVER_MASTER: { + if (_i2c_master_dev != nullptr) { + i2c_master_bus_rm_device(_i2c_master_dev); + i2c_device_config_t dev_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = _addr, + .scl_speed_hz = originalFreq, + .scl_wait_us = 0, + .flags = { .disable_ack_check = false }, + }; + i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + } + break; + } + case M5PM1_I2C_DRIVER_BUS: + if (_i2c_device != nullptr) { + i2c_bus_device_delete(&_i2c_device); + _i2c_device = i2c_bus_device_create(_i2c_bus, _addr, originalFreq); + } + break; + default: + break; + } +#endif + _writeReg(M5PM1_REG_I2C_CFG, originalCfg); + return M5PM1_ERR_I2C_COMM; + } + + _requestedSpeed = targetFreq; + _lastCommTime = M5PM1_GET_TIME_MS(); + + _i2cConfig.speed400k = (speed == M5PM1_I2C_SPEED_400K); + _i2cConfigValid = true; + + M5PM1_LOG_I(TAG, "Successfully switched to %lu Hz I2C mode", (unsigned long)targetFreq); + return M5PM1_OK; +} + +// ============================ +// 自动唤醒功能 +// Auto Wake Feature +// ============================ + +void M5PM1::setAutoWakeEnable(bool enable) { + _autoWakeEnabled = enable; + if (enable) { + _lastCommTime = M5PM1_GET_TIME_MS(); + } +} + +bool M5PM1::isAutoWakeEnabled() const { + return _autoWakeEnabled; +} + + m5pm1_err_t M5PM1::sendWakeSignal() { +#ifdef ARDUINO + M5PM1_I2C_SEND_WAKE(_wire, _addr); + return M5PM1_OK; +#else + switch (_i2cDriverType) { + case M5PM1_I2C_DRIVER_SELF_CREATED: + case M5PM1_I2C_DRIVER_MASTER: + return M5PM1_I2C_MASTER_SEND_WAKE(_i2c_master_bus, _addr) == ESP_OK + ? M5PM1_OK : M5PM1_ERR_I2C_COMM; + case M5PM1_I2C_DRIVER_BUS: + return M5PM1_I2C_SEND_WAKE(_i2c_device, M5PM1_REG_HW_REV) == ESP_OK + ? M5PM1_OK : M5PM1_ERR_I2C_COMM; + default: + return M5PM1_ERR_INTERNAL; + } +#endif +} + +// ============================ +// 状态快照功能 +// State Snapshot Functions +// ============================ + +void M5PM1::setAutoSnapshot(bool enable) { + _autoSnapshot = enable; +} + +bool M5PM1::isAutoSnapshotEnabled() const { + return _autoSnapshot; +} + +m5pm1_err_t M5PM1::updateSnapshot() { + if (!_initialized) return M5PM1_ERR_NOT_INIT; + return _snapshotAll() ? M5PM1_OK : M5PM1_ERR_I2C_COMM; +} + +// ============================ +// 快照验证 +// Snapshot Verification +// ============================ + +m5pm1_snapshot_verify_t M5PM1::verifySnapshot() { + m5pm1_snapshot_verify_t result = { + true, false, false, false, false, false, false, false, false, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if (!_initialized) { + result.consistent = false; + return result; + } + + if (_cacheValid) { + uint8_t actualMode = 0; + uint8_t actualOut = 0; + + if (_readReg(M5PM1_REG_GPIO_MODE, &actualMode) && + _readReg(M5PM1_REG_GPIO_OUT, &actualOut)) { + uint8_t expectedMode = 0; + uint8_t expectedOut = 0; + for (uint8_t i = 0; i < M5PM1_MAX_GPIO_PINS; i++) { + if (_pinStatus[i].mode == M5PM1_GPIO_MODE_OUTPUT) { + expectedMode |= (1 << i); + } + if (_pinStatus[i].output) { + expectedOut |= (1 << i); + } + } + + result.expected_gpio_mode = expectedMode; + result.actual_gpio_mode = actualMode & 0x1F; + result.expected_gpio_out = expectedOut; + result.actual_gpio_out = actualOut & 0x1F; + + if (result.expected_gpio_mode != result.actual_gpio_mode || + result.expected_gpio_out != result.actual_gpio_out) { + result.gpio_mismatch = true; + result.consistent = false; + } + } else { + result.consistent = false; + } + } + + if (_pwmStatesValid) { + uint16_t actualFreq = 0; + if (_readReg16(M5PM1_REG_PWM_FREQ_L, &actualFreq)) { + if (actualFreq != _pwmFrequency) { + result.pwm_mismatch = true; + result.consistent = false; + } + } else { + result.consistent = false; + } + + for (uint8_t ch = 0; ch < M5PM1_MAX_PWM_CHANNELS; ch++) { + uint8_t regL = (ch == 0) ? M5PM1_REG_PWM0_L : M5PM1_REG_PWM1_L; + uint8_t regH = (ch == 0) ? M5PM1_REG_PWM0_HC : M5PM1_REG_PWM1_HC; + uint8_t low = 0; + uint8_t high = 0; + + if (!_readReg(regL, &low) || !_readReg(regH, &high)) { + result.consistent = false; + continue; + } + + uint16_t actualDuty = (uint16_t)(low | ((high & 0x0F) << 8)); + bool actualEnabled = (high & 0x10) != 0; + bool actualPolarity = (high & 0x20) != 0; + + if (actualDuty != _pwmStates[ch].duty12 || + actualEnabled != _pwmStates[ch].enabled || + actualPolarity != _pwmStates[ch].polarity) { + result.pwm_mismatch = true; + result.consistent = false; + } + } + } + + if (_adcStateValid) { + uint8_t ctrl = 0; + uint16_t value = 0; + if (_readReg(M5PM1_REG_ADC_CTRL, &ctrl) && + _readReg16(M5PM1_REG_ADC_RES_L, &value)) { + uint8_t actualChannel = (ctrl >> 1) & 0x07; + bool actualBusy = (ctrl & 0x01) != 0; + + if (actualChannel != _adcState.channel || + actualBusy != _adcState.busy || + value != _adcState.lastValue) { + result.adc_mismatch = true; + result.consistent = false; + } + } else { + result.consistent = false; + } + } + + if (_powerConfigValid) { + uint8_t actualPwr = 0; + uint8_t actualHold = 0; + if (_readReg(M5PM1_REG_PWR_CFG, &actualPwr) && + _readReg(M5PM1_REG_HOLD_CFG, &actualHold)) { + result.expected_pwr_cfg = _powerCfg; + result.actual_pwr_cfg = actualPwr; + result.expected_hold_cfg = _holdCfg; + result.actual_hold_cfg = actualHold; + + if (_powerCfg != actualPwr || _holdCfg != actualHold) { + result.power_mismatch = true; + result.consistent = false; + } + } else { + result.consistent = false; + } + } + + if (_btnConfigValid) { + uint8_t actualCfg1 = 0; + uint8_t actualCfg2 = 0; + if (_readReg(M5PM1_REG_BTN_CFG_1, &actualCfg1) && + _readReg(M5PM1_REG_BTN_CFG_2, &actualCfg2)) { + if (_btnCfg1 != actualCfg1 || _btnCfg2 != actualCfg2) { + result.button_mismatch = true; + result.consistent = false; + } + } else { + result.consistent = false; + } + } + + if (_irqMaskValid) { + uint8_t actualMask1 = 0; + uint8_t actualMask2 = 0; + uint8_t actualMask3 = 0; + if (_readReg(M5PM1_REG_IRQ_MASK1, &actualMask1) && + _readReg(M5PM1_REG_IRQ_MASK2, &actualMask2) && + _readReg(M5PM1_REG_IRQ_MASK3, &actualMask3)) { + if (_irqMask1 != actualMask1 || _irqMask2 != actualMask2 || _irqMask3 != actualMask3) { + result.irq_mask_mismatch = true; + result.consistent = false; + } + } else { + result.consistent = false; + } + } + + if (_i2cConfigValid) { + uint8_t actualCfg = 0; + if (_readReg(M5PM1_REG_I2C_CFG, &actualCfg)) { + uint8_t expectedCfg = _i2cConfig.sleepTime & M5PM1_I2C_CFG_SLEEP_MASK; + if (_i2cConfig.speed400k) { + expectedCfg |= M5PM1_I2C_CFG_SPEED_400K; + } + uint8_t actualMasked = actualCfg & (M5PM1_I2C_CFG_SLEEP_MASK | M5PM1_I2C_CFG_SPEED_400K); + + result.expected_i2c_cfg = expectedCfg; + result.actual_i2c_cfg = actualMasked; + + if (expectedCfg != actualMasked) { + result.i2c_mismatch = true; + result.consistent = false; + } + } else { + result.consistent = false; + } + } + + if (_neoConfigValid) { + uint8_t actualCfg = 0; + if (_readReg(M5PM1_REG_NEO_CFG, &actualCfg)) { + uint8_t expectedCfg = _neoCfg & M5PM1_NEO_CFG_COUNT_MASK; + uint8_t actualMasked = actualCfg & M5PM1_NEO_CFG_COUNT_MASK; + + result.expected_neo_cfg = expectedCfg; + result.actual_neo_cfg = actualMasked; + + if (expectedCfg != actualMasked) { + result.neo_mismatch = true; + result.consistent = false; + } + } else { + result.consistent = false; + } + } + + return result; +} + +// ============================ +// 配置验证 +// Configuration Validation +// ============================ + +m5pm1_validation_t M5PM1::validateConfig(uint8_t pin, m5pm1_config_type_t configType, bool enable) { + m5pm1_validation_t result = {false, {0}, 0xFF}; + + if (!_isValidPin(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), "Invalid pin %d", pin); + return result; + } + + if (!_initialized) { + snprintf(result.error_msg, sizeof(result.error_msg), "Not initialized"); + return result; + } + + if (!enable) { + result.valid = true; + return result; + } + + switch (configType) { + case M5PM1_CONFIG_GPIO_INPUT: + case M5PM1_CONFIG_GPIO_OUTPUT: + if (_hasActivePwm(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for PWM", pin); + return result; + } + if (_hasActiveAdc(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for ADC", pin); + return result; + } + if (_hasActiveNeo(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for LED_EN", pin); + return result; + } + if (_hasActiveIrq(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d has IRQ enabled", pin); + return result; + } + if (_hasActiveWake(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d has WAKE enabled", pin); + return result; + } + break; + + case M5PM1_CONFIG_GPIO_INTERRUPT: + if (_hasActivePwm(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for PWM", pin); + return result; + } + if (_hasActiveAdc(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for ADC", pin); + return result; + } + if (_hasActiveNeo(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for LED_EN", pin); + return result; + } + if (_hasActiveWake(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d has WAKE enabled", pin); + return result; + } + break; + + case M5PM1_CONFIG_GPIO_WAKE: + if (_hasActivePwm(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for PWM", pin); + return result; + } + if (_hasActiveAdc(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for ADC", pin); + return result; + } + if (_hasActiveNeo(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for LED_EN", pin); + return result; + } + if (_hasActiveIrq(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d has IRQ enabled", pin); + return result; + } + break; + + case M5PM1_CONFIG_ADC: + if (!_isAdcPin(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d does not support ADC", pin); + return result; + } + if (_cacheValid && _pinStatus[pin].mode == M5PM1_GPIO_MODE_OUTPUT) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is configured as output", pin); + return result; + } + if (_hasActiveIrq(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d has IRQ enabled", pin); + return result; + } + if (_hasActiveWake(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d has WAKE enabled", pin); + return result; + } + break; + + case M5PM1_CONFIG_PWM: + if (!_isPwmPin(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d does not support PWM", pin); + return result; + } + if (_hasActiveIrq(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d has IRQ enabled", pin); + return result; + } + if (_hasActiveWake(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d has WAKE enabled", pin); + return result; + } + break; + + case M5PM1_CONFIG_NEOPIXEL: + if (!_isNeoPin(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "NeoPixel only supported on GPIO0"); + return result; + } + if (_hasActiveIrq(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "GPIO0 has IRQ enabled"); + return result; + } + if (_hasActiveWake(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "GPIO0 has WAKE enabled"); + return result; + } + break; + + default: + snprintf(result.error_msg, sizeof(result.error_msg), "Unknown config type"); + return result; + } + + result.valid = true; + return result; +} + +// ============================ +// 缓存状态查询函数 +// Cached State Query Functions +// ============================ + +m5pm1_err_t M5PM1::getCachedPwmFrequency(uint16_t* frequency) { + if (frequency == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_pwmStatesValid) return M5PM1_FAIL; + *frequency = _pwmFrequency; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getCachedPwmState(m5pm1_pwm_channel_t channel, uint16_t* duty12, bool* enable, bool* polarity) { + if (channel > M5PM1_PWM_CH_1 || duty12 == nullptr || enable == nullptr || polarity == nullptr) { + return M5PM1_ERR_INVALID_ARG; + } + if (!_pwmStatesValid) return M5PM1_FAIL; + + *duty12 = _pwmStates[channel].duty12; + *enable = _pwmStates[channel].enabled; + *polarity = _pwmStates[channel].polarity; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getCachedAdcState(m5pm1_adc_channel_t* channel, bool* busy, uint16_t* lastValue) { + if (channel == nullptr || busy == nullptr || lastValue == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_adcStateValid) return M5PM1_FAIL; + + *channel = (m5pm1_adc_channel_t)_adcState.channel; + *busy = _adcState.busy; + *lastValue = _adcState.lastValue; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getCachedPowerConfig(uint8_t* pwrCfg, uint8_t* holdCfg) { + if (pwrCfg == nullptr || holdCfg == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_powerConfigValid) return M5PM1_FAIL; + + *pwrCfg = _powerCfg; + *holdCfg = _holdCfg; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getCachedButtonConfig(uint8_t* cfg1, uint8_t* cfg2) { + if (cfg1 == nullptr || cfg2 == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_btnConfigValid) return M5PM1_FAIL; + + *cfg1 = _btnCfg1; + *cfg2 = _btnCfg2; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getCachedIrqMasks(uint8_t* mask1, uint8_t* mask2, uint8_t* mask3) { + if (mask1 == nullptr || mask2 == nullptr || mask3 == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_irqMaskValid) return M5PM1_FAIL; + + *mask1 = _irqMask1; + *mask2 = _irqMask2; + *mask3 = _irqMask3; + return M5PM1_OK; +} + +m5pm1_err_t M5PM1::getCachedIrqStatus(uint8_t* status1, uint8_t* status2, uint8_t* status3) { + if (status1 == nullptr || status2 == nullptr || status3 == nullptr) return M5PM1_ERR_INVALID_ARG; + if (!_irqStatusValid) return M5PM1_FAIL; + + *status1 = _irqStatus1; + *status2 = _irqStatus2; + *status3 = _irqStatus3; + return M5PM1_OK; +} diff --git a/src/M5PM1.h b/src/M5PM1.h new file mode 100644 index 0000000..986a0c3 --- /dev/null +++ b/src/M5PM1.h @@ -0,0 +1,1484 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +/** + * @file M5PM1.h + * @brief M5Stack PM1 Power Management IC Driver Library (Dual-Platform: ESP-IDF & Arduino) + * M5Stack PM1 电源管理IC驱动库(双平台:ESP-IDF 和 Arduino) + * + * @note PM1 is a multi-function power management IC supporting: + * PM1 是一款多功能电源管理IC,支持: + * - Battery charging and monitoring / 电池充电和监控 + * - Multiple power rails (DCDC 5V, LDO 3.3V) / 多路电源轨(DCDC 5V,LDO 3.3V) + * - 5 GPIO pins with various functions / 5个GPIO引脚支持多种功能 + * - PWM output, ADC input / PWM输出、ADC输入 + * - NeoPixel LED control / NeoPixel LED控制 + * - Watchdog timer / 看门狗定时器 + * - RTC RAM (32 bytes, retained in sleep) / RTC RAM(32字节,睡眠保持) + * - I2C auto-sleep/wake feature / I2C自动睡眠/唤醒功能 + */ + +#ifndef _M5PM1_H_ +#define _M5PM1_H_ + +#include "M5PM1_i2c_compat.h" +#include +#include + +#ifdef ARDUINO +// Arduino: FreeRTOS headers included via Arduino framework +#else +// ESP-IDF specific includes +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_attr.h" +#endif + +// ============================ +// Error Codes +// 错误码 +// ============================ +typedef enum { + M5PM1_OK = 0, // 成功 + // Success + M5PM1_FAIL = -1, // 一般失败 + // General failure + M5PM1_ERR_I2C_CONFIG = -2, // I2C 配置错误 (如频率不支持) + // I2C configuration error + M5PM1_ERR_RULE_VIOLATION = -3, // 条件规则错误 (如引脚冲突,互斥功能) + // Condition rule violation + M5PM1_ERR_INVALID_ARG = -4, // 无效参数 + // Invalid argument + M5PM1_ERR_TIMEOUT = -5, // 超时 + // Timeout + M5PM1_ERR_NOT_SUPPORTED = -6, // 不支持的功能 + // Function not supported + M5PM1_ERR_I2C_COMM = -7, // I2C 通信错误 + // I2C communication error + M5PM1_ERR_NOT_INIT = -8, // 设备未初始化 + // Device not initialized + M5PM1_ERR_INTERNAL = -9, // 内部错误 + // Internal error +} m5pm1_err_t; + +// ============================ +// Device Constants +// 设备常量 +// ============================ +#define M5PM1_DEFAULT_ADDR 0x6E // Default I2C address / 默认I2C地址 +#define M5PM1_MAX_GPIO_PINS 5 // GPIO0-GPIO4, total 5 pins / GPIO0-GPIO4,共5个引脚 +#define M5PM1_MAX_PWM_CHANNELS 2 // PWM0 (GPIO3), PWM1 (GPIO4) / PWM0(GPIO3),PWM1(GPIO4) +#define M5PM1_MAX_ADC_CHANNELS 3 // ADC1 (GPIO1), ADC2 (GPIO2), TEMP / ADC1(GPIO1),ADC2(GPIO2),温度 +#define M5PM1_MAX_LED_COUNT 32 // Maximum NeoPixel LED count / 最大NeoPixel LED数量 +#define M5PM1_RTC_RAM_SIZE 32 // RTC RAM size in bytes (retained in sleep) / RTC RAM大小(睡眠保持) + +// ============================ +// I2C Frequency Constants +// I2C 频率常量 +// ============================ +#define M5PM1_I2C_FREQ_100K 100000 // Standard mode / 标准模式 +#define M5PM1_I2C_FREQ_400K 400000 // Fast mode / 快速模式 +#define M5PM1_I2C_FREQ_DEFAULT M5PM1_I2C_FREQ_100K +// I2C 重试参数 +// I2C retry settings +#define M5PM1_I2C_RETRY_COUNT 2 +#define M5PM1_I2C_RETRY_DELAY_MS 2 + +// ============================ +// Register Addresses +// 寄存器地址 +// ============================ + +// ---- System Registers ---- +// ---- 系统寄存器 ---- +#define M5PM1_REG_DEVICE_ID 0x00 // R [7:0] Device ID / 设备ID +#define M5PM1_REG_DEVICE_MODEL 0x01 // R [7:0] Device Model / 设备型号 +#define M5PM1_REG_HW_REV 0x02 // R [7:0] Hardware version / 硬件版本 +#define M5PM1_REG_SW_REV 0x03 // R [7:0] Software/Firmware version / 固件版本 + +// ---- Power Status Registers ---- +// ---- 电源状态寄存器 ---- +#define M5PM1_REG_PWR_SRC 0x04 // R [2:0] Power source / 当前供电源 + // 0: 5VIN (USB/DC input) + // 5VIN(USB/DC输入) + // 1: 5VINOUT (bidirectional port) + // 5VINOUT(双向端口) + // 2: BAT (battery) + // 电池 +#define M5PM1_REG_WAKE_SRC 0x05 // R/W [6:0] Wake source flags (write 1 to clear) + // 唤醒源标志(写1清除) + // [6] 5VINOUT插入唤醒 + // 5VINOUT insertion wake + // [5] 外部GPIO唤醒 + // External GPIO wake + // [4] 命令复位唤醒 + // Command reset wake + // [3] 复位按钮唤醒 + // Reset button wake + // [2] 电源按钮唤醒 + // Power button wake + // [1] VIN插入唤醒 + // VIN insertion wake + // [0] 定时器唤醒 + // Timer wake + +// ---- Power Configuration ---- +// ---- 电源配置 ---- +#define M5PM1_REG_PWR_CFG 0x06 // R/W [7-5] Reserved 保留 + // [4] LED_CONTROL - LED控制权: 0=PM1控制 1=外部MCU控制 + // [3] 5V_INOUT - 5V双向端口: 0=输入模式 1=输出模式(由DCDC供电) + // [2] LDO_EN - 3.3V LDO使能: 0=关闭 1=开启 + // [1] DCDC_EN - 5V DCDC使能: 0=关闭 1=开启 + // [0] CHG_EN - 充电使能: 0=关闭 1=开启 + // 注意:下载模式或复位事件时自动清零 + // Note: Auto-clears on download mode or reset events + +#define M5PM1_REG_HOLD_CFG 0x07 // R/W Power hold configuration / 电源保持配置 + // [7] Reserved 保留 + // [6] DCDC(5V) - DCDC电源保持 + // [5] LDO(3.3V) - LDO电源保持 + // [4] GPIO4 - GPIO4输出状态保持 + // [3] GPIO3 - GPIO3输出状态保持 + // [2] GPIO2 - GPIO2输出状态保持 + // [1] GPIO1 - GPIO1输出状态保持 + // [0] GPIO0 - GPIO0输出状态保持 + // 注意:复位/下载模式/关机时自动清零为0x00 + // Note: Auto-clears to 0x00 on reset/download/shutdown + +#define M5PM1_REG_BATT_LVP 0x08 // R/W Battery low voltage protection threshold + // 电池低压保护阈值 + // Value = (voltage_mV - 2000) / 7.81 + // 电压(mV) = 2000 + n * 7.81 + // Range: 2000mV ~ 3990mV + // 范围:2000mV ~ 3990mV + +#define M5PM1_REG_I2C_CFG 0x09 // R/W I2C configuration / I2C配置 + // [7-5] Reserved 保留 + // [4] SPD - I2C速度: 0=100KHz 1=400KHz + // [3:0] SLP_TO - I2C睡眠超时(秒): 0=禁用 1-15=1-15秒 + // Note: PM1 enters sleep after I2C idle timeout + // 注意:I2C空闲超时后PM1进入睡眠模式 + +// ---- Watchdog Registers ---- +// ---- 看门狗寄存器 ---- +#define M5PM1_REG_WDT_CNT 0x0A // R/W Watchdog countdown (seconds) / 看门狗倒计时(秒) + // 0 = disabled + // 0=禁用 + // 1-255 = timeout in seconds + // 1-255=超时秒数 + // 系统复位当倒计时到0 + // System resets when countdown reaches 0 +#define M5PM1_REG_WDT_KEY 0x0B // W Write 0xA5 to feed watchdog / 写入0xA5喂狗 +#define M5PM1_REG_SYS_CMD 0x0C // W System command / 系统命令 + // [7:4] KEY must be 0xA + // 密钥必须为0xA + // [3:2] Reserved 保留 + // [1:0] CMD - 00:无操作 01:关机 10:复位 11:下载模式 + +// ---- GPIO Registers ---- +// ---- GPIO寄存器 ---- +#define M5PM1_REG_GPIO_MODE 0x10 // R/W [4:0] GPIO direction / GPIO方向 + // 每bit对应一个GPIO: 1=输出 0=输入 + // Each bit: 1=output, 0=input +#define M5PM1_REG_GPIO_OUT 0x11 // R/W [4:0] GPIO output level / GPIO输出电平 + // 每bit对应一个GPIO: 1=高 0=低 + // Each bit: 1=high, 0=low +#define M5PM1_REG_GPIO_IN 0x12 // R [4:0] GPIO input state / GPIO输入状态 + // 每bit对应一个GPIO的当前输入电平 + // Each bit: current input level of GPIO +#define M5PM1_REG_GPIO_DRV 0x13 // R/W GPIO drive mode / GPIO驱动模式 + // [7-6] Reserved 保留 + // [5] LED_EN_DRV - LED使能引脚驱动: 0=推挽 1=开漏 + // [4:0] GPIO drive + // GPIO驱动: 1=开漏 0=推挽 +#define M5PM1_REG_GPIO_PUPD0 0x14 // R/W Pull-up/down for GPIO0-3 / GPIO0-3上下拉配置 + // 每GPIO占2bit: 00=无 01=上拉 10=下拉 11=保留 + // [7:6] GPIO3, [5:4] GPIO2, [3:2] GPIO1, [1:0] GPIO0 +#define M5PM1_REG_GPIO_PUPD1 0x15 // R/W Pull-up/down for GPIO4 / GPIO4上下拉配置 + // [1:0] GPIO4: 00=无 01=上拉 10=下拉 11=保留 +#define M5PM1_REG_GPIO_FUNC0 0x16 // R/W GPIO0-3 function / GPIO0-3功能选择 + // 每GPIO占2bit: 00=GPIO 01=IRQ 10=WAKE 11=特殊功能 + // GPIO0: 11=LED_EN (NeoPixel使能) + // GPIO1: 11=ADC1 + // GPIO2: 11=ADC2 + // GPIO3: 11=PWM0 +#define M5PM1_REG_GPIO_FUNC1 0x17 // R/W GPIO4 function / GPIO4功能选择 + // [1:0] GPIO4: 00=GPIO 01=IRQ 10=WAKE 11=PWM1 +#define M5PM1_REG_GPIO_WAKE_EN 0x18 // R/W [4:0] GPIO wake enable / GPIO唤醒使能 + // 每bit使能对应GPIO的唤醒功能 + // Each bit enables wake function for corresponding GPIO +#define M5PM1_REG_GPIO_WAKE_CFG 0x19 // R/W [4:0] GPIO wake edge config / GPIO唤醒边沿配置 + // 每bit: 0=下降沿唤醒 1=上升沿唤醒 + // Each bit: 0=falling edge, 1=rising edge + +// ---- Voltage Reading Registers ---- +// ---- 电压读取寄存器 ---- +#define M5PM1_REG_VREF_L 0x20 // R VREF low byte (mV) / 参考电压低字节 +#define M5PM1_REG_VREF_H 0x21 // R VREF high byte / 参考电压高字节 +#define M5PM1_REG_VBAT_L 0x22 // R Battery voltage low byte (mV) / 电池电压低字节 +#define M5PM1_REG_VBAT_H 0x23 // R Battery voltage high 4 bits / 电池电压高4位 +#define M5PM1_REG_VIN_L 0x24 // R VIN voltage low byte (mV) / VIN电压低字节 +#define M5PM1_REG_VIN_H 0x25 // R VIN voltage high 4 bits / VIN电压高4位 +#define M5PM1_REG_5VINOUT_L 0x26 // R 5VINOUT voltage low byte (mV) / 5VINOUT电压低字节 +#define M5PM1_REG_5VINOUT_H 0x27 // R 5VINOUT voltage high 4 bits / 5VINOUT电压高4位 +#define M5PM1_REG_ADC_RES_L 0x28 // R ADC result low byte (mV) / ADC结果低字节 +#define M5PM1_REG_ADC_RES_H 0x29 // R ADC result high 4 bits / ADC结果高4位 +#define M5PM1_REG_ADC_CTRL 0x2A // R/W ADC control / ADC控制 + // [7-4] Reserved 保留 + // [3:1] Channel + // 通道: 1=ADC1(GPIO1) 2=ADC2(GPIO2) 6=温度 + // [0] START - 写1启动转换 + // Write 1 to start conversion + +// ---- PWM Registers ---- +// ---- PWM寄存器 ---- +#define M5PM1_REG_PWM0_L 0x30 // R/W PWM0 duty low byte / PWM0占空比低字节 +#define M5PM1_REG_PWM0_HC 0x31 // R/W PWM0 high byte + control / PWM0高字节+控制 + // [7-6] Reserved 保留 + // [5] POL - 极性: 0=正常 1=反转 + // [4] EN - 使能: 0=关闭 1=开启 + // [3:0] Duty high 4 bits + // 占空比高4位 + // 12-bit duty: 0-4095 (0-100%) + // 12位占空比 +#define M5PM1_REG_PWM1_L 0x32 // R/W PWM1 duty low byte / PWM1占空比低字节 +#define M5PM1_REG_PWM1_HC 0x33 // R/W PWM1 high byte + control / PWM1高字节+控制 + // Same format as PWM0_HC + // 格式同PWM0_HC +#define M5PM1_REG_PWM_FREQ_L 0x34 // R/W PWM frequency low byte (Hz) / PWM频率低字节 +#define M5PM1_REG_PWM_FREQ_H 0x35 // R/W PWM frequency high byte / PWM频率高字节 + // Range: 1-65535 Hz + // 范围:1-65535 Hz + +// ---- Timer Registers ---- +// ---- 定时器寄存器 ---- +#define M5PM1_REG_TIM_CNT_0 0x38 // R/W Timer counter byte 0 (LSB) / 定时器计数字节0 +#define M5PM1_REG_TIM_CNT_1 0x39 // R/W Timer counter byte 1 / 定时器计数字节1 +#define M5PM1_REG_TIM_CNT_2 0x3A // R/W Timer counter byte 2 / 定时器计数字节2 +#define M5PM1_REG_TIM_CNT_3 0x3B // R/W Timer counter byte 3 (bit 6:0, max 31 bits) + // 31位定时器,单位秒,最大约68年 + // 31-bit timer in seconds, max ~68 years +#define M5PM1_REG_TIM_CFG 0x3C // R/W Timer configuration / 定时器配置 + // [7-4] Reserved 保留 + // [3] ARM - 自动重装: 0=单次 1=自动重装 + // [2:0] ACTION - 超时动作: + // 000=停止 001=标志 010=复位 011=开机 100=关机 +#define M5PM1_REG_TIM_KEY 0x3D // W Write 0xA5 to reload timer / 写入0xA5重载定时器 + +// ---- IRQ Registers ---- +// ---- 中断寄存器 ---- +// 注意:状态寄存器写1清除对应位 +// Note: Write 1 to clear status bits +#define M5PM1_REG_IRQ_STATUS1 0x40 // R/W [4:0] GPIO interrupt status / GPIO中断状态 + // 触发条件:GPIO配置为IRQ功能时的边沿触发 + // Trigger: Edge trigger when GPIO configured as IRQ +#define M5PM1_REG_IRQ_STATUS2 0x41 // R/W System interrupt status / 系统中断状态 + // [7-6] Reserved 保留 + // [5] 电池移除 + // Battery removed + // [4] 电池插入 + // Battery inserted + // [3] 5VINOUT移除 + // 5VINOUT removed + // [2] 5VINOUT插入 + // 5VINOUT inserted + // [1] 5VIN移除 + // 5VIN removed + // [0] 5VIN插入 + // 5VIN inserted + // 备注:电池事件仅充电使能时有效,5VINOUT事件仅输入模式时有效 + // Note: Battery events only when charging enabled + // 5VINOUT events only when in input mode +#define M5PM1_REG_IRQ_STATUS3 0x42 // R/W Button interrupt status / 按钮中断状态 + // [7-3] Reserved 保留 + // [2] 双击事件 + // Double click event + // [1] 唤醒事件 + // Wakeup event + // [0] 单击事件 + // Single click event +#define M5PM1_REG_IRQ_MASK1 0x43 // R/W GPIO interrupt mask / GPIO中断屏蔽 + // [4:0] 每bit: 0=使能中断 1=屏蔽中断 +#define M5PM1_REG_IRQ_MASK2 0x44 // R/W System interrupt mask / 系统中断屏蔽 +#define M5PM1_REG_IRQ_MASK3 0x45 // R/W Button interrupt mask / 按钮中断屏蔽 + +// ---- Button Registers ---- +// ---- 按钮寄存器 ---- +#define M5PM1_REG_BTN_STATUS 0x48 // R Button status / 按钮状态 + // [7] BTN_FLAG - 按钮曾被按下标志(读取后自动清除) + // [6-1] Reserved 保留 + // [0] BTN_STATE - 当前按钮状态: 0=释放 1=按下 +#define M5PM1_REG_BTN_CFG_1 0x49 // R/W Button configuration / 按钮配置 + // [7] Reserved 保留 + // [6:5] LONG_DLY - 长按延时: 00=125ms 01=250ms 10=500ms 11=1s + // [4:3] DBL_DLY - 双击间隔: 00=125ms 01=250ms 10=500ms 11=1s + // [2:1] CLK_DLY - 单击延时: 00=125ms 01=250ms 10=500ms 11=1s + // [0] SINGLE_RST_DIS - 单击复位禁用: 0=使能 1=禁用 +#define M5PM1_REG_BTN_CFG_2 0x4A // R/W Button configuration 2 / 按钮配置2 + // [7-1] Reserved 保留 + // [0] DOUBLE_OFF_DIS - 双击关机禁用: 0=使能 1=禁用 + +// ---- NeoPixel Registers ---- +// ---- NeoPixel寄存器 ---- +#define M5PM1_REG_NEO_CFG 0x50 // R/W NeoPixel configuration / NeoPixel配置 + // [7-6] Reserved 保留 + // [5] REFRESH - 写1刷新LED + // Write 1 to refresh LEDs + // [4:0] LED_CNT - LED数量 (0-32) +#define M5PM1_REG_AW8737A_PULSE 0x53 // R/W AW8737A pulse control / AW8737A脉冲控制 + // 用于控制AW8737A音频放大器增益 + // For controlling AW8737A audio amplifier gain + // [7] REFRESH - 写1执行脉冲 + // Write 1 to execute pulse + // [6:5] NUM - 脉冲数量: 00=0 01=1 10=2 11=3 + // [4:0] GPIO - 输出GPIO引脚号 + +// ---- NeoPixel Data Area ---- +// ---- NeoPixel数据区 ---- +#define M5PM1_REG_NEO_DATA_START 0x60 // R/W NeoPixel RGB565 data start / 数据起始地址 +#define M5PM1_REG_NEO_DATA_END 0x9F // R/W NeoPixel RGB565 data end / 数据结束地址 + // 每LED占2字节(RGB565格式), 共32个LED + // Each LED: 2 bytes (RGB565), total 32 LEDs + +// ---- RTC RAM Area ---- +// ---- RTC RAM区域 ---- +#define M5PM1_REG_RTC_RAM_START 0xA0 // R/W RTC RAM start (32 bytes) / RTC RAM起始 +#define M5PM1_REG_RTC_RAM_END 0xBF // R/W RTC RAM end / RTC RAM结束 + // 睡眠期间数据保持,可用于存储小量重要数据 + // Data retained during sleep, for storing small important data + +// ============================ +// Bit Definitions +// 位定义 +// ============================ + +// ---- PWR_CFG Register Bits ---- +// ---- 电源配置寄存器位 ---- +#define M5PM1_PWR_CFG_CHG_EN (1 << 0) // 充电使能 / Charge enable +#define M5PM1_PWR_CFG_DCDC_EN (1 << 1) // 5V DCDC使能 / 5V DCDC enable +#define M5PM1_PWR_CFG_LDO_EN (1 << 2) // 3.3V LDO使能 / 3.3V LDO enable +#define M5PM1_PWR_CFG_5V_INOUT (1 << 3) // 5V双向端口模式 / 5V bidirectional port mode + // 0=输入模式 1=输出模式(由DCDC供电) +#define M5PM1_PWR_CFG_LED_CTRL (1 << 4) // LED控制权 / LED control + // 0=PM1控制 1=外部MCU控制 + +// ---- I2C_CFG Register Bits ---- +// ---- I2C配置寄存器位 ---- +#define M5PM1_I2C_CFG_SLEEP_MASK 0x0F // I2C睡眠超时掩码 / I2C sleep timeout mask + // 0=禁用 1-15=1-15秒 +#define M5PM1_I2C_CFG_SPEED_400K (1 << 4) // I2C速度选择 / I2C speed select + // 0=100KHz 1=400KHz + +// ---- SYS_CMD Register Values ---- +// ---- 系统命令寄存器值 ---- +#define M5PM1_SYS_CMD_KEY 0xA0 // 命令密钥 / Command key (must be 0xA in high nibble) +#define M5PM1_SYS_CMD_SHUTDOWN 0x01 // 关机命令 / Shutdown command +#define M5PM1_SYS_CMD_REBOOT 0x02 // 复位命令 / Reboot command +#define M5PM1_SYS_CMD_JTAG 0x03 // 下载模式命令 / Download mode command + +// ---- Key Values ---- +// ---- 密钥值 ---- +#define M5PM1_WDT_FEED_KEY 0xA5 // 喂狗密钥 / Watchdog feed key +#define M5PM1_TIM_RELOAD_KEY 0xA5 // 定时器重载密钥 / Timer reload key + +// ---- NEO_CFG Register Bits ---- +// ---- NeoPixel配置寄存器位 ---- +#define M5PM1_NEO_CFG_REFRESH (1 << 5) // 刷新标志 / Refresh flag (write 1 to update LEDs) +#define M5PM1_NEO_CFG_COUNT_MASK 0x1F // LED数量掩码 / LED count mask (0-31) + +// ============================ +// Enumerations +// 枚举类型 +// ============================ + +/** + * @brief GPIO pin number / GPIO引脚编号 + * @note GPIO0: LED_EN功能 / LED_EN function + * GPIO1: ADC1功能 / ADC1 function + * GPIO2: ADC2功能 / ADC2 function + * GPIO3: PWM0功能 / PWM0 function + * GPIO4: PWM1功能 / PWM1 function + */ +typedef enum { + M5PM1_GPIO_NUM_0 = 0, // GPIO0 (可用于LED_EN) + // (can be LED_EN) + M5PM1_GPIO_NUM_1 = 1, // GPIO1 (可用于ADC1) + // (can be ADC1) + M5PM1_GPIO_NUM_2 = 2, // GPIO2 (可用于ADC2) + // (can be ADC2) + M5PM1_GPIO_NUM_3 = 3, // GPIO3 (可用于PWM0) + // (can be PWM0) + M5PM1_GPIO_NUM_4 = 4, // GPIO4 (可用于PWM1) + // (can be PWM1) + M5PM1_GPIO_NUM_NC = 255 // 未连接 + // Not connected +} m5pm1_gpio_num_t; + +/** + * @brief GPIO direction mode / GPIO方向模式 + */ +typedef enum { + M5PM1_GPIO_MODE_INPUT = 0, // 输入模式 + // Input mode + M5PM1_GPIO_MODE_OUTPUT = 1 // 输出模式 + // Output mode +} m5pm1_gpio_mode_t; + +/** + * @brief GPIO function selection / GPIO功能选择 + * @note 特殊功能(0b11)因引脚而异: + * Special function (0b11) varies by pin: + * - GPIO0: LED_EN (NeoPixel使能) + * - GPIO1: ADC1 (模拟输入) + * - GPIO2: ADC2 (模拟输入) + * - GPIO3: PWM0 (脉宽调制输出) + * - GPIO4: PWM1 (脉宽调制输出) + */ +typedef enum { + M5PM1_GPIO_FUNC_GPIO = 0b00, // 普通GPIO功能 + // Normal GPIO function + M5PM1_GPIO_FUNC_IRQ = 0b01, // 中断触发功能 + // Interrupt trigger function + M5PM1_GPIO_FUNC_WAKE = 0b10, // 唤醒功能 + // Wake function + M5PM1_GPIO_FUNC_OTHER = 0b11 // 特殊功能(LED/PWM/ADC) + // Special function +} m5pm1_gpio_func_t; + +/** + * @brief GPIO pull-up/pull-down configuration / GPIO上下拉配置 + */ +typedef enum { + M5PM1_GPIO_PULL_NONE = 0b00, // 无上下拉 + // No pull + M5PM1_GPIO_PULL_UP = 0b01, // 上拉使能 + // Pull-up enabled + M5PM1_GPIO_PULL_DOWN = 0b10 // 下拉使能 + // Pull-down enabled +} m5pm1_gpio_pull_t; + +/** + * @brief GPIO output drive mode / GPIO输出驱动模式 + */ +typedef enum { + M5PM1_GPIO_DRIVE_PUSHPULL = 0, // 推挽输出 + // Push-pull output + M5PM1_GPIO_DRIVE_OPENDRAIN = 1 // 开漏输出 + // Open-drain output +} m5pm1_gpio_drive_t; + +/** + * @brief GPIO wake edge configuration / GPIO唤醒边沿配置 + */ +typedef enum { + M5PM1_GPIO_WAKE_FALLING = 0, // 下降沿唤醒 + // Wake on falling edge + M5PM1_GPIO_WAKE_RISING = 1 // 上升沿唤醒 + // Wake on rising edge +} m5pm1_gpio_wake_edge_t; + +/** + * @brief ADC channel selection / ADC通道选择 + * @note GPIO必须配置为FUNC_OTHER(0b11)才能使用ADC功能 + * GPIO must be configured as FUNC_OTHER (0b11) to use ADC function + */ +typedef enum { + M5PM1_ADC_CH_1 = 1, // ADC通道1 (GPIO1) + // ADC channel 1 (GPIO1) + M5PM1_ADC_CH_2 = 2, // ADC通道2 (GPIO2) + // ADC channel 2 (GPIO2) + M5PM1_ADC_CH_TEMP = 6 // 内部温度传感器 + // Internal temperature sensor +} m5pm1_adc_channel_t; + +/** + * @brief PWM channel selection / PWM通道选择 + * @note GPIO必须配置为FUNC_OTHER(0b11)才能使用PWM功能 + * GPIO must be configured as FUNC_OTHER (0b11) to use PWM function + */ +typedef enum { + M5PM1_PWM_CH_0 = 0, // PWM通道0 (GPIO3) + // PWM channel 0 (GPIO3) + M5PM1_PWM_CH_1 = 1 // PWM通道1 (GPIO4) + // PWM channel 1 (GPIO4) +} m5pm1_pwm_channel_t; + +/** + * @brief Current power source / 当前供电源 + */ +typedef enum { + M5PM1_PWR_SRC_5VIN = 0, // USB/DC 5V输入 + // USB/DC 5V input + M5PM1_PWR_SRC_5VINOUT = 1, // 5V双向端口输入 + // 5V bidirectional port input + M5PM1_PWR_SRC_BAT = 2, // 电池供电 + // Battery power + M5PM1_PWR_SRC_UNKNOWN = 3 // 未知/无供电 + // Unknown/no power +} m5pm1_pwr_src_t; + +/** + * @brief Wake source flags / 唤醒源标志 + * @note 可组合使用(位掩码) / Can be combined (bitmask) + * 写1清除对应唤醒标志 / Write 1 to clear corresponding flag + */ +typedef enum { + M5PM1_WAKE_SRC_TIM = 0x01, // 定时器唤醒 + // Timer wake + M5PM1_WAKE_SRC_VIN = 0x02, // VIN插入唤醒 + // VIN insertion wake + M5PM1_WAKE_SRC_PWRBTN = 0x04, // 电源按钮唤醒 + // Power button wake + M5PM1_WAKE_SRC_RSTBTN = 0x08, // 复位按钮唤醒 + // Reset button wake + M5PM1_WAKE_SRC_CMD_RST = 0x10, // 命令复位唤醒 + // Command reset wake + M5PM1_WAKE_SRC_EXT_WAKE = 0x20, // 外部GPIO唤醒 + // External GPIO wake + M5PM1_WAKE_SRC_5VINOUT = 0x40 // 5VINOUT插入唤醒 + // 5VINOUT insertion wake +} m5pm1_wake_src_t; + +/** + * @brief System command / 系统命令 + * @note 命令需要与密钥(0xA0)组合使用 + * Command needs to be combined with key (0xA0) + */ +typedef enum { + M5PM1_SYS_CMD_NONE = 0x00, // 无操作 + // No operation + M5PM1_SYS_CMD_OFF = 0x01, // 关机 + // Shutdown + M5PM1_SYS_CMD_RESET = 0x02, // 复位 + // Reset + M5PM1_SYS_CMD_DL = 0x03 // 进入下载模式 + // Enter download mode +} m5pm1_sys_cmd_t; + +/** + * @brief Timer timeout action / 定时器超时动作 + */ +typedef enum { + M5PM1_TIM_ACTION_STOP = 0b000, // 停止,无动作 + // Stop, no action + M5PM1_TIM_ACTION_FLAG = 0b001, // 仅设置标志 + // Set flag only + M5PM1_TIM_ACTION_REBOOT = 0b010, // 系统复位 + // System reboot + M5PM1_TIM_ACTION_POWERON = 0b011, // 开机 + // Power on + M5PM1_TIM_ACTION_POWEROFF = 0b100 // 关机 + // Power off +} m5pm1_tim_action_t; + +/** + * @brief Button event type / 按钮事件类型 + */ +typedef enum { + M5PM1_BTN_TYPE_CLICK = 0, // 单击 + // Single click + M5PM1_BTN_TYPE_DOUBLE = 1, // 双击 + // Double click + M5PM1_BTN_TYPE_LONG = 2 // 长按 + // Long press +} m5pm1_btn_type_t; + +/** + * @brief Button delay/timeout configuration / 按钮延时配置 + */ +typedef enum { + M5PM1_BTN_DELAY_125MS = 0, // 125毫秒 + // 125 milliseconds + M5PM1_BTN_DELAY_250MS = 1, // 250毫秒 + // 250 milliseconds + M5PM1_BTN_DELAY_500MS = 2, // 500毫秒 + // 500 milliseconds + M5PM1_BTN_DELAY_1000MS = 3 // 1000毫秒 + // 1000 milliseconds +} m5pm1_btn_delay_t; + +/** + * @brief Button IRQ interrupt type / 按钮中断类型 + * @note 用于 IRQ_STATUS3 寄存器的位定义 / For IRQ_STATUS3 register bit definitions + */ +typedef enum { + M5PM1_BTN_IRQ_CLICK = 0x00, // 单击中断 + // Single click interrupt + M5PM1_BTN_IRQ_WAKEUP = 0x01, // 唤醒中断 + // Wakeup interrupt + M5PM1_BTN_IRQ_DOUBLE = 0x02, // 双击中断 + // Double click interrupt + M5PM1_BTN_IRQ_ALL = 0x07, // 所有按钮中断 + // All button interrupts + M5PM1_BTN_IRQ_NONE = 0xFF // 无中断 + // No interrupt +} m5pm1_btn_irq_t; + +/** + * @brief I2C speed selection / I2C速度选择 + * @note PM1上电默认100KHz,切换400KHz需要先写入配置再重新初始化I2C + * PM1 defaults to 100KHz on power-up, switching to 400KHz requires + * writing config first then re-initializing I2C + */ +typedef enum { + M5PM1_I2C_SPEED_100K = 0, // 100KHz 标准模式 + // Standard mode + M5PM1_I2C_SPEED_400K = 1 // 400KHz 快速模式 + // Fast mode +} m5pm1_i2c_speed_t; + +// 日志级别定义 +// Log level definitions +typedef enum { + M5PM1_LOG_LEVEL_NONE = 0, // 无日志输出 + // No log output + M5PM1_LOG_LEVEL_ERROR, // 仅错误消息 + // Error messages only + M5PM1_LOG_LEVEL_WARN, // 警告和错误消息 + // Warning and error messages + M5PM1_LOG_LEVEL_INFO, // 信息、警告和错误消息(默认) + // Info, warning and error messages (default) + M5PM1_LOG_LEVEL_DEBUG, // 调试、信息、警告和错误消息 + // Debug, info, warning and error messages + M5PM1_LOG_LEVEL_VERBOSE // 所有消息包括详细输出 + // All messages including verbose +} m5pm1_log_level_t; + +/** + * @brief AW8737A pulse count for gain control / AW8737A增益控制脉冲数 + * @note AW8737A是一款音频功率放大器,通过脉冲数设置增益 + * AW8737A is an audio power amplifier, gain is set by pulse count + */ +typedef enum { + M5PM1_AW8737A_PULSE_0 = 0, // 0个脉冲 (静音) + // 0 pulses (mute) + M5PM1_AW8737A_PULSE_1 = 1, // 1个脉冲 + // 1 pulse + M5PM1_AW8737A_PULSE_2 = 2, // 2个脉冲 + // 2 pulses + M5PM1_AW8737A_PULSE_3 = 3 // 3个脉冲 + // 3 pulses +} m5pm1_aw8737a_pulse_t; + +/** + * @brief AW8737A pulse refresh mode / AW8737A脉冲刷新模式 + */ +typedef enum { + M5PM1_AW8737A_REFRESH_WAIT = 0, // 等待手动刷新 + // Wait for manual refresh + M5PM1_AW8737A_REFRESH_NOW = 1 // 立即执行 + // Execute immediately +} m5pm1_aw8737a_refresh_t; + +// ============================ +// GPIO 电平定义 +// GPIO Level Definitions +// ============================ +#ifndef LOW +#define LOW 0 +#endif +#ifndef HIGH +#define HIGH 1 +#endif + +// ============================ +// GPIO 模式定义(Arduino 兼容) +// GPIO Mode Definitions (Arduino-compatible) +// ============================ +#ifndef INPUT +#define INPUT 0x01 +#endif +#ifndef OUTPUT +#define OUTPUT 0x03 +#endif +#ifndef PULLUP +#define PULLUP 0x04 +#endif +#ifndef INPUT_PULLUP +#define INPUT_PULLUP 0x05 +#endif +#ifndef PULLDOWN +#define PULLDOWN 0x08 +#endif +#ifndef INPUT_PULLDOWN +#define INPUT_PULLDOWN 0x09 +#endif +#ifndef OPEN_DRAIN +#define OPEN_DRAIN 0x10 +#endif +#ifndef OUTPUT_OPEN_DRAIN +#define OUTPUT_OPEN_DRAIN 0x13 +#endif +#ifndef ANALOG +#define ANALOG 0xC0 +#endif + +// ============================ +// RGB Color Structure +// RGB 颜色结构 +// ============================ +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; +} m5pm1_rgb_t; + +// ============================ +// Pin Status Structure +// Pin 状态结构 +// ============================ +/** + * @brief Cached GPIO pin status / 缓存的 GPIO 引脚状态 + * @note This structure maintains software-level pin state to reduce I2C transactions + * 此结构维护软件层引脚状态以减少 I2C 事务 + */ +typedef struct { + m5pm1_gpio_func_t func; ///< GPIO function / GPIO 功能 (GPIO/IRQ/WAKE/OTHER) + m5pm1_gpio_mode_t mode; ///< Direction / 方向 (INPUT/OUTPUT) + uint8_t output; ///< Output value / 输出值 (0/1) + m5pm1_gpio_pull_t pull; ///< Pull-up/pull-down / 上下拉 (NONE/UP/DOWN) + bool wake_en; ///< Wake enabled / 唤醒使能 + m5pm1_gpio_wake_edge_t wake_edge; ///< Wake edge / 唤醒边沿 (RISING/FALLING) + m5pm1_gpio_drive_t drive; ///< Drive mode / 驱动模式 (PUSHPULL/OPENDRAIN) + bool power_hold; ///< Power hold state / 电源保持状态 +} m5pm1_pin_status_t; + +// ============================ +// 用于验证的配置类型 +// Configuration Type for Validation +// ============================ +typedef enum { + M5PM1_CONFIG_GPIO_INPUT = 0, + M5PM1_CONFIG_GPIO_OUTPUT, + M5PM1_CONFIG_GPIO_INTERRUPT, + M5PM1_CONFIG_GPIO_WAKE, + M5PM1_CONFIG_ADC, + M5PM1_CONFIG_PWM, + M5PM1_CONFIG_NEOPIXEL +} m5pm1_config_type_t; + +// ============================ +// 快照更新域 +// Snapshot Update Domains +// ============================ +typedef enum { + M5PM1_SNAPSHOT_DOMAIN_GPIO = 1 << 0, + M5PM1_SNAPSHOT_DOMAIN_PWM = 1 << 1, + M5PM1_SNAPSHOT_DOMAIN_ADC = 1 << 2, + M5PM1_SNAPSHOT_DOMAIN_POWER = 1 << 3, + M5PM1_SNAPSHOT_DOMAIN_BUTTON = 1 << 4, + M5PM1_SNAPSHOT_DOMAIN_IRQ_MASK = 1 << 5, + M5PM1_SNAPSHOT_DOMAIN_IRQ_STATUS = 1 << 6, + M5PM1_SNAPSHOT_DOMAIN_I2C = 1 << 7, + M5PM1_SNAPSHOT_DOMAIN_NEO = 1 << 8, + M5PM1_SNAPSHOT_DOMAIN_ALL = 0x1FF +} m5pm1_snapshot_domain_t; + +// ============================ +// 配置验证结果 +// Configuration Validation Result +// ============================ +typedef struct { + bool valid; + char error_msg[64]; + uint8_t conflicting_pin; +} m5pm1_validation_t; + +// ============================ +// 快照验证结果 +// Snapshot Verification Result +// ============================ +typedef struct { + bool consistent; // 缓存与寄存器一致时为 true + // True if cached values match registers + bool gpio_mismatch; // GPIO 快照不一致 + // GPIO snapshot mismatch + bool pwm_mismatch; // PWM 快照不一致 + // PWM snapshot mismatch + bool adc_mismatch; // ADC 快照不一致 + // ADC snapshot mismatch + bool power_mismatch; // 电源配置快照不一致 + // Power config snapshot mismatch + bool button_mismatch; // 按钮配置快照不一致 + // Button config snapshot mismatch + bool irq_mask_mismatch; // IRQ 掩码快照不一致 + // IRQ mask snapshot mismatch + bool i2c_mismatch; // I2C 配置快照不一致 + // I2C config snapshot mismatch + bool neo_mismatch; // Neo 配置快照不一致 + // Neo config snapshot mismatch + uint8_t expected_gpio_mode; // 缓存的 GPIO 模式值 + // Cached GPIO mode value + uint8_t actual_gpio_mode; // 实际的 GPIO 模式值 + // Actual GPIO mode value + uint8_t expected_gpio_out; // 缓存的 GPIO 输出值 + // Cached GPIO output value + uint8_t actual_gpio_out; // 实际的 GPIO 输出值 + // Actual GPIO output value + uint8_t expected_pwr_cfg; // 缓存的 PWR_CFG 值 + // Cached PWR_CFG value + uint8_t actual_pwr_cfg; // 实际的 PWR_CFG 值 + // Actual PWR_CFG value + uint8_t expected_hold_cfg; // 缓存的 HOLD_CFG 值 + // Cached HOLD_CFG value + uint8_t actual_hold_cfg; // 实际的 HOLD_CFG 值 + // Actual HOLD_CFG value + uint8_t expected_i2c_cfg; // 缓存的 I2C_CFG 值 + // Cached I2C_CFG value + uint8_t actual_i2c_cfg; // 实际的 I2C_CFG 值 + // Actual I2C_CFG value + uint8_t expected_neo_cfg; // 缓存的 NEO_CFG 值 + // Cached NEO_CFG value + uint8_t actual_neo_cfg; // 实际的 NEO_CFG 值 + // Actual NEO_CFG value +} m5pm1_snapshot_verify_t; + +// ============================ +// M5PM1 Class +// M5PM1 类 +// ============================ +class M5PM1 { +public: + M5PM1(); + ~M5PM1(); + + // ======================== + // Initialization + // 初始化 + // ======================== +#ifdef ARDUINO + /** + * @brief Initialize the device (Arduino) + * @param wire Pointer to TwoWire instance + * @param addr I2C address (default 0x6E) + * @param sda SDA pin (default -1, uses default I2C pins) + * @param scl SCL pin (default -1, uses default I2C pins) + * @param speed I2C speed in Hz (default 100000) + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t begin(TwoWire *wire = &Wire, uint8_t addr = M5PM1_DEFAULT_ADDR, + int8_t sda = -1, int8_t scl = -1, uint32_t speed = M5PM1_I2C_FREQ_100K); +#else // ESP-IDF + /** + * @brief Initialize with self-created I2C bus (ESP-IDF) + * @param port I2C port number + * @param addr I2C address (default 0x6E) + * @param sda SDA pin + * @param scl SCL pin + * @param speed I2C speed in Hz + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t begin(i2c_port_t port = I2C_NUM_0, uint8_t addr = M5PM1_DEFAULT_ADDR, + int sda = 21, int scl = 22, uint32_t speed = M5PM1_I2C_FREQ_100K); + + /** + * @brief Initialize with existing i2c_master_bus handle (ESP-IDF native) + * @param bus Existing i2c_master_bus_handle_t + * @param addr I2C address (default 0x6E) + * @param speed I2C speed in Hz + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t begin(i2c_master_bus_handle_t bus, uint8_t addr = M5PM1_DEFAULT_ADDR, + uint32_t speed = M5PM1_I2C_FREQ_100K); + + /** + * @brief Initialize with existing i2c_bus handle (esp-idf-lib) + * @param bus Existing i2c_bus_handle_t + * @param addr I2C address (default 0x6E) + * @param speed I2C speed in Hz + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t begin(i2c_bus_handle_t bus, uint8_t addr = M5PM1_DEFAULT_ADDR, + uint32_t speed = M5PM1_I2C_FREQ_100K); +#endif + + /** + * @brief 设置全局日志级别 + * Set global log level + * @param level 日志级别 + * Log level + */ + static void setLogLevel(m5pm1_log_level_t level); + + /** + * @brief 获取当前日志级别 + * Get current log level + * @return 日志级别 + * Log level + */ + static m5pm1_log_level_t getLogLevel(); + + // ======================== + // 设备信息 + // Device Information + // ======================== + m5pm1_err_t getDeviceId(uint8_t* id); + m5pm1_err_t getDeviceModel(uint8_t* model); + m5pm1_err_t getHwVersion(uint8_t* version); + m5pm1_err_t getSwVersion(uint8_t* version); + m5pm1_err_t getVersion(uint8_t* version); + + // ======================== + // GPIO 功能 (Arduino风格 - 带返回值) + // GPIO Functions (Arduino-style - WithRes) + // ======================== + void pinModeWithRes(uint8_t pin, uint8_t mode, m5pm1_err_t* err); + void digitalWriteWithRes(uint8_t pin, uint8_t value, m5pm1_err_t* err); + int digitalReadWithRes(uint8_t pin, m5pm1_err_t* err); + + // ======================== + // GPIO 功能 (Arduino风格) + // GPIO Functions (Arduino-style) + // ======================== + void pinMode(uint8_t pin, uint8_t mode); + void digitalWrite(uint8_t pin, uint8_t value); + int digitalRead(uint8_t pin); + + // ======================== + // 高级 GPIO 功能 + // Advanced GPIO Functions + // ======================== + /** + * @brief Configure GPIO in one call / 一键配置 GPIO + * @param pin GPIO pin number (0-4) / GPIO 引脚号 + * @param mode Mode: M5PM1_GPIO_MODE_INPUT / OUTPUT + * @param value Output level (only for output mode): 0/1 + * @param pull Pull mode: M5PM1_GPIO_PULL_NONE / UP / DOWN + * @param drive Drive mode: M5PM1_GPIO_DRIVE_PUSHPULL / OPENDRAIN + * @return M5PM1_OK if successful, error code otherwise + */ + m5pm1_err_t gpioSet(m5pm1_gpio_num_t pin, m5pm1_gpio_mode_t mode, + uint8_t value, m5pm1_gpio_pull_t pull, m5pm1_gpio_drive_t drive); + + m5pm1_err_t gpioSetFunc(m5pm1_gpio_num_t pin, m5pm1_gpio_func_t func); + m5pm1_err_t gpioSetMode(m5pm1_gpio_num_t pin, m5pm1_gpio_mode_t mode); + m5pm1_err_t gpioSetOutput(m5pm1_gpio_num_t pin, uint8_t value); + m5pm1_err_t gpioGetInput(m5pm1_gpio_num_t pin, uint8_t* value); + m5pm1_err_t gpioSetPull(m5pm1_gpio_num_t pin, m5pm1_gpio_pull_t pull); + m5pm1_err_t gpioSetDrive(m5pm1_gpio_num_t pin, m5pm1_gpio_drive_t drive); + m5pm1_err_t gpioSetWakeEnable(m5pm1_gpio_num_t pin, bool enable); + m5pm1_err_t gpioSetWakeEdge(m5pm1_gpio_num_t pin, m5pm1_gpio_wake_edge_t edge); + + /** + * @brief Set LED_EN pin drive mode / 设置 LED_EN 引脚驱动模式 + * @param drive Drive mode: M5PM1_GPIO_DRIVE_PUSHPULL / OPENDRAIN + * @return M5PM1_OK if successful, error code otherwise + * @note LED_EN is a special pin (bit 5 in GPIO_DRV register) + */ + m5pm1_err_t ledEnSetDrive(m5pm1_gpio_drive_t drive); + + /** + * @brief Dump all pin status for debugging / 打印所有引脚状态(调试用) + * @return M5PM1_OK if successful, error code otherwise + */ + m5pm1_err_t dumpPinStatus(); + + /** + * @brief Get cached pin status / 获取缓存的引脚状态 + * @param pin GPIO pin number (0-4) / GPIO 引脚号 + * @param status Output: cached pin status / 输出: 缓存的引脚状态 + * @return M5PM1_OK if successful, error code otherwise + * @note This reads from software cache, no I2C transaction + * 此函数从软件缓存读取,无 I2C 事务 + */ + m5pm1_err_t getPinStatus(m5pm1_gpio_num_t pin, m5pm1_pin_status_t* status); + + /** + * @brief Get cached pin status array pointer / 获取缓存引脚状态数组指针 + * @return Pointer to internal pin status array (size: 5) + * @note Returns pointer to internal cache, do not modify externally + * 返回内部缓存指针,请勿在外部修改 + */ + const m5pm1_pin_status_t* getPinStatusArray() const; + + // ======================== + // 电源保持功能 + // Power Hold Functions + // ======================== + m5pm1_err_t gpioSetPowerHold(m5pm1_gpio_num_t pin, bool enable); + m5pm1_err_t gpioGetPowerHold(m5pm1_gpio_num_t pin, bool* enable); + m5pm1_err_t ldoSetPowerHold(bool enable); + m5pm1_err_t ldoGetPowerHold(bool* enable); + m5pm1_err_t dcdcSetPowerHold(bool enable); + m5pm1_err_t dcdcGetPowerHold(bool* enable); + + // ======================== + // ADC 功能 + // ADC Functions + // ======================== + m5pm1_err_t analogRead(m5pm1_adc_channel_t channel, uint16_t* value); + m5pm1_err_t isAdcBusy(bool* busy); + m5pm1_err_t disableAdc(); + + // ======================== + // 温度传感器 + // Temperature Sensor + // ======================== + m5pm1_err_t readTemperature(uint16_t* temperature); + + // ======================== + // PWM 功能 + // PWM Functions + // ======================== + m5pm1_err_t setPwmFrequency(uint16_t frequency); + m5pm1_err_t getPwmFrequency(uint16_t* frequency); + m5pm1_err_t setPwmDuty(m5pm1_pwm_channel_t channel, uint8_t duty, + bool polarity = false, bool enable = true); + m5pm1_err_t getPwmDuty(m5pm1_pwm_channel_t channel, uint8_t* duty, + bool* polarity, bool* enable); + m5pm1_err_t setPwmDuty12bit(m5pm1_pwm_channel_t channel, uint16_t duty12, + bool polarity = false, bool enable = true); + m5pm1_err_t getPwmDuty12bit(m5pm1_pwm_channel_t channel, uint16_t* duty12, + bool* polarity, bool* enable); + m5pm1_err_t analogWrite(m5pm1_pwm_channel_t channel, uint8_t value); + + // ======================== + // 电压读取功能 + // Voltage Reading Functions + // ======================== + m5pm1_err_t readVref(uint16_t* mv); + m5pm1_err_t getRefVoltage(uint16_t* mv); + m5pm1_err_t readVbat(uint16_t* mv); + m5pm1_err_t readVin(uint16_t* mv); + m5pm1_err_t read5VInOut(uint16_t* mv); + + // ======================== + // 电源管理 + // Power Management + // ======================== + m5pm1_err_t getPowerSource(m5pm1_pwr_src_t* src); + + /** + * @brief Read wake source / 读取唤醒源 + * @param src Output: wake source bitmask / 输出: 唤醒源位掩码 + * @param clearAfterRead 0=no clear, 1=clear triggered bits, 2=clear all + * 0=不清除, 1=清除触发位, 2=清除全部 + * @return M5PM1_OK if successful, error code otherwise + */ + m5pm1_err_t getWakeSource(uint8_t* src, uint8_t clearAfterRead = 0); + m5pm1_err_t clearWakeSource(uint8_t mask); + + m5pm1_err_t setPowerConfig(uint8_t mask, uint8_t value); + m5pm1_err_t getPowerConfig(uint8_t* config); + + /** + * @brief Clear power config bits / 清除电源配置位 + * @param mask Bits to clear / 要清除的位 + * @return M5PM1_OK if successful, error code otherwise + */ + m5pm1_err_t clearPowerConfig(uint8_t mask); + + m5pm1_err_t setChargeEnable(bool enable); + m5pm1_err_t setDcdcEnable(bool enable); + m5pm1_err_t setLdoEnable(bool enable); + m5pm1_err_t set5VInOutEnable(bool enable); + m5pm1_err_t setLedControlEnable(bool enable); + + // ======================== + // 电池功能 + // Battery Functions + // ======================== + m5pm1_err_t setBatteryLvp(uint16_t mv); + + // ======================== + // 看门狗功能 + // Watchdog Functions + // ======================== + m5pm1_err_t wdtSet(uint8_t timeout_sec); + m5pm1_err_t wdtFeed(); + m5pm1_err_t wdtGetCount(uint8_t* count); + + // ======================== + // 定时器功能 + // Timer Functions + // ======================== + m5pm1_err_t timerSet(uint32_t seconds, m5pm1_tim_action_t action, bool autoRearm = false); + m5pm1_err_t timerClear(); + + // ======================== + // 按钮功能 + // Button Functions + // ======================== + m5pm1_err_t btnSetConfig(m5pm1_btn_type_t type, m5pm1_btn_delay_t delay); + m5pm1_err_t btnGetState(bool* pressed); + m5pm1_err_t btnGetFlag(bool* wasPressed); + m5pm1_err_t setSingleResetDisable(bool disable); + m5pm1_err_t getSingleResetDisable(bool* disabled); + m5pm1_err_t setDoubleOffDisable(bool disable); + m5pm1_err_t getDoubleOffDisable(bool* disabled); + + // ======================== + // 中断功能 + // IRQ Functions + // ======================== + /** + * @brief Read GPIO interrupt status / 读取 GPIO 中断状态 + * @param status Output: status bitmask / 输出: 状态位掩码 + * @param clearAfterRead 0=no clear, 1=clear triggered bits, 2=clear all + * 0=不清除, 1=清除触发位, 2=清除全部 + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t irqGetGpioStatus(uint8_t* status, uint8_t clearAfterRead = 0); + m5pm1_err_t irqClearGpio(uint8_t mask); + + /** + * @brief Read system interrupt status / 读取系统中断状态 + * @param status Output: status bitmask / 输出: 状态位掩码 + * @param clearAfterRead 0=no clear, 1=clear triggered bits, 2=clear all + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t irqGetSysStatus(uint8_t* status, uint8_t clearAfterRead = 0); + m5pm1_err_t irqClearSys(uint8_t mask); + + /** + * @brief Read button interrupt status / 读取按钮中断状态 + * @param status Output: status bitmask / 输出: 状态位掩码 + * @param clearAfterRead 0=no clear, 1=clear triggered bits, 2=clear all + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t irqGetBtnStatus(uint8_t* status, uint8_t clearAfterRead = 0); + m5pm1_err_t irqClearBtn(uint8_t mask); + + /** + * @brief Set single GPIO pin interrupt mask / 设置单个 GPIO 引脚中断屏蔽 + * @param pin GPIO pin number (0-4) / GPIO 引脚号 + * @param mask true=mask(disable), false=unmask(enable) / true=屏蔽, false=不屏蔽 + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t irqSetGpioMask(m5pm1_gpio_num_t pin, bool mask); + m5pm1_err_t irqGetGpioMask(m5pm1_gpio_num_t pin, bool* mask); + + /** + * @brief Set all GPIO interrupt mask at once / 一次性设置所有 GPIO 中断屏蔽 + * @param mask Bitmask for all GPIO pins / 所有 GPIO 引脚的位掩码 + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t irqSetGpioMaskAll(uint8_t mask); + m5pm1_err_t irqGetGpioMaskAll(uint8_t* mask); + + /** + * @brief Set single system event interrupt mask / 设置单个系统事件中断屏蔽 + * @param event Event bit (0-5): 0=5VIN_IN, 1=5VIN_OUT, 2=5VINOUT_IN, 3=5VINOUT_OUT, 4=BAT_IN, 5=BAT_OUT + * @param mask true=mask(disable), false=unmask(enable) + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t irqSetSysMask(uint8_t event, bool mask); + m5pm1_err_t irqGetSysMask(uint8_t event, bool* mask); + + /** + * @brief Set all system interrupt mask at once / 一次性设置所有系统中断屏蔽 + * @param mask Bitmask for all system events / 所有系统事件的位掩码 + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t irqSetSysMaskAll(uint8_t mask); + m5pm1_err_t irqGetSysMaskAll(uint8_t* mask); + + /** + * @brief Set single button event interrupt mask / 设置单个按钮事件中断屏蔽 + * @param type Button event type: M5PM1_BTN_IRQ_CLICK / WAKEUP / DOUBLE + * @param mask true=mask(disable), false=unmask(enable) + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t irqSetBtnMask(m5pm1_btn_irq_t type, bool mask); + m5pm1_err_t irqGetBtnMask(m5pm1_btn_irq_t type, bool* mask); + + /** + * @brief Set all button interrupt mask at once / 一次性设置所有按钮中断屏蔽 + * @param mask Bitmask for all button events / 所有按钮事件的位掩码 + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + */ + m5pm1_err_t irqSetBtnMaskAll(uint8_t mask); + m5pm1_err_t irqGetBtnMaskAll(uint8_t* mask); + + // ======================== + // 系统命令 + // System Commands + // ======================== + m5pm1_err_t sysCmd(m5pm1_sys_cmd_t cmd); + m5pm1_err_t shutdown(); + m5pm1_err_t reboot(); + m5pm1_err_t enterDownloadMode(); + + /** + * @brief Set download mode lock / 设置下载模式锁 + * @param lock true=lock(disable download), false=unlock(enable download) + * true=锁定(禁止下载), false=解锁(允许下载) + * @return 成功返回 M5PM1_OK,否则返回错误码 + * Return M5PM1_OK on success, error code otherwise + * @note This controls bit 7 (DL_LOCK) in BTN_CFG register + */ + m5pm1_err_t setDownloadLock(bool lock); + m5pm1_err_t getDownloadLock(bool* lock); + + // ======================== + // NeoPixel 功能 + // NeoPixel Functions + // ======================== + m5pm1_err_t setLedCount(uint8_t count); + m5pm1_err_t setLedColor(uint8_t index, uint8_t r, uint8_t g, uint8_t b); + m5pm1_err_t setLedColor(uint8_t index, m5pm1_rgb_t color); + m5pm1_err_t refreshLeds(); + m5pm1_err_t disableLeds(); + + /** + * @brief Configure NeoPixel in one call / 一键配置 NeoPixel + * @param count LED count (1-32) / LED 数量 + * @param rgb565Data RGB565 format color data array / RGB565 格式颜色数据数组 + * @param refresh Whether to refresh immediately / 是否立即刷新 + * @return true if successful + */ + m5pm1_err_t setLeds(const m5pm1_rgb_t* colors, uint8_t arraySize, uint8_t count, + bool autoRefresh = true); + + // ======================== + // AW8737A 脉冲功能 + // AW8737A Pulse Functions + // ======================== + m5pm1_err_t setAw8737aPulse(m5pm1_gpio_num_t pin, m5pm1_aw8737a_pulse_t num, + m5pm1_aw8737a_refresh_t refresh = M5PM1_AW8737A_REFRESH_NOW); + m5pm1_err_t refreshAw8737aPulse(); + + // ======================== + // RTC RAM 功能 + // RTC RAM Functions + // ======================== + m5pm1_err_t writeRtcRAM(uint8_t offset, const uint8_t* data, uint8_t len); + m5pm1_err_t readRtcRAM(uint8_t offset, uint8_t* data, uint8_t len); + + // ======================== + // I2C 配置 + // I2C Configuration + // ======================== + m5pm1_err_t setI2cConfig(uint8_t sleepTime, + m5pm1_i2c_speed_t speed = M5PM1_I2C_SPEED_100K); + m5pm1_err_t switchI2cSpeed(m5pm1_i2c_speed_t speed); + m5pm1_err_t getI2cSpeed(m5pm1_i2c_speed_t* speed); + m5pm1_err_t setI2cSleepTime(uint8_t seconds); + m5pm1_err_t getI2cSleepTime(uint8_t* seconds); + + // ======================== + // 自动唤醒功能 + // Auto Wake Feature + // ======================== + /** + * @brief Enable/disable automatic wake signal before I2C operations + * 启用/禁用 I2C 操作前的自动唤醒信号 + * @param enable true to enable auto-wake, false to disable + * @note When PM1 enters sleep mode (I2C sleep timeout), it needs a + * START signal on SDA to wake up. This feature automatically + * sends the wake signal when needed. + * 当 PM1 进入睡眠模式(I2C 睡眠超时)后,需要在 SDA 上发送 + * START 信号来唤醒。此功能会在需要时自动发送唤醒信号。 + * @note Even without enabling this option, communication will likely + * succeed in most cases, as the first I2C transaction itself + * can wake the device. Enable this for guaranteed reliability. + * 即使不启用此选项,通讯在大多数情况下也能成功,因为第一次 + * I2C 传输本身就能唤醒设备。启用此选项可确保可靠性。 + */ + void setAutoWakeEnable(bool enable); + + /** + * @brief Check if auto wake is enabled / 检查自动唤醒是否启用 + * @return true if enabled + */ + bool isAutoWakeEnabled() const; + + /** + * @brief Manually send wake signal to PM1 / 手动发送唤醒信号到 PM1 + * @return M5PM1_OK if successful, error code otherwise + */ + m5pm1_err_t sendWakeSignal(); + + // ======================== + // 状态快照功能 + // State Snapshot Functions + // ======================== + void setAutoSnapshot(bool enable); + bool isAutoSnapshotEnabled() const; + m5pm1_err_t updateSnapshot(); + + // ======================== + // 快照验证 + // Snapshot Verification + // ======================== + m5pm1_snapshot_verify_t verifySnapshot(); + + // ======================== + // 配置验证 + // Configuration Validation + // ======================== + m5pm1_validation_t validateConfig(uint8_t pin, m5pm1_config_type_t configType, bool enable = true); + + // ======================== + // 缓存状态查询函数 + // Cached State Query Functions + // ======================== + m5pm1_err_t getCachedPwmFrequency(uint16_t* frequency); + m5pm1_err_t getCachedPwmState(m5pm1_pwm_channel_t channel, uint16_t* duty12, bool* enable, bool* polarity); + m5pm1_err_t getCachedAdcState(m5pm1_adc_channel_t* channel, bool* busy, uint16_t* lastValue); + m5pm1_err_t getCachedPowerConfig(uint8_t* pwrCfg, uint8_t* holdCfg); + m5pm1_err_t getCachedButtonConfig(uint8_t* cfg1, uint8_t* cfg2); + m5pm1_err_t getCachedIrqMasks(uint8_t* mask1, uint8_t* mask2, uint8_t* mask3); + m5pm1_err_t getCachedIrqStatus(uint8_t* status1, uint8_t* status2, uint8_t* status3); + +private: + // Device state + // 设备状态 + uint8_t _addr; + bool _initialized; + bool _autoWakeEnabled; + bool _autoSnapshot; + uint8_t _i2cSleepTime; + uint32_t _requestedSpeed; + struct { + uint8_t sleepTime; + bool speed400k; + } _i2cConfig; + bool _i2cConfigValid; + uint32_t _lastCommTime; + + // PWM state cache + // PWM 状态缓存 + struct { + uint16_t duty12; + bool enabled; + bool polarity; + } _pwmStates[M5PM1_MAX_PWM_CHANNELS]; + uint16_t _pwmFrequency; + bool _pwmStatesValid; + + // ADC state cache + // ADC 状态缓存 + struct { + uint8_t channel; + bool busy; + uint16_t lastValue; + } _adcState; + bool _adcStateValid; + + // Power config cache + // 电源配置缓存 + uint8_t _powerCfg; + uint8_t _holdCfg; + bool _powerConfigValid; + + // Button config cache + // 按钮配置缓存 + uint8_t _btnCfg1; + uint8_t _btnCfg2; + bool _btnConfigValid; + + // IRQ cache + // IRQ 缓存 + uint8_t _irqMask1; + uint8_t _irqMask2; + uint8_t _irqMask3; + bool _irqMaskValid; + uint8_t _irqStatus1; + uint8_t _irqStatus2; + uint8_t _irqStatus3; + bool _irqStatusValid; + + // Neo 配置缓存 + // Neo config cache + uint8_t _neoCfg; + bool _neoConfigValid; + + // Pin status cache + // Pin 状态缓存 + m5pm1_pin_status_t _pinStatus[M5PM1_MAX_GPIO_PINS]; // Cached status for GPIO0-4 + bool _cacheValid; // Cache validity flag / 缓存有效性标志 + +#ifdef ARDUINO + TwoWire *_wire; + int8_t _sda; + int8_t _scl; +#else + // I2C driver type selection + // I2C 驱动类型选择 + m5pm1_i2c_driver_t _i2cDriverType; + + // I2C handles + // I2C 句柄 + i2c_master_bus_handle_t _i2c_master_bus; + i2c_master_dev_handle_t _i2c_master_dev; + i2c_bus_handle_t _i2c_bus; + i2c_bus_device_handle_t _i2c_device; + + // I2C management flags + // I2C 管理标志 + bool _busExternal; + + // Self-created bus pins + // 自创建总线引脚 + int _sda; + int _scl; + i2c_port_t _port; +#endif + + // Internal helper functions + // 内部辅助函数 + bool _writeReg(uint8_t reg, uint8_t value); + bool _writeReg16(uint8_t reg, uint16_t value); + bool _readReg(uint8_t reg, uint8_t* value); + bool _readReg16(uint8_t reg, uint16_t* value); + bool _writeBytes(uint8_t reg, const uint8_t* data, uint8_t len); + bool _readBytes(uint8_t reg, uint8_t* data, uint8_t len); + + bool _isValidPin(uint8_t pin); + bool _isAdcPin(uint8_t pin); + bool _isPwmPin(uint8_t pin); + bool _isNeoPin(uint8_t pin); + bool _hasActiveAdc(uint8_t pin); + bool _hasActivePwm(uint8_t pin); + bool _hasActiveIrq(uint8_t pin); + bool _hasActiveWake(uint8_t pin); + bool _hasActiveNeo(uint8_t pin); + bool _isValidI2cFrequency(uint32_t speed); + void _checkAutoWake(); + bool _initDevice(); + void _clearPinStates(); + void _clearPwmStates(); + void _clearAdcState(); + void _clearPowerConfig(); + void _clearButtonConfig(); + void _clearIrqMasks(); + void _clearIrqStatus(); + void _clearAll(); + bool _snapshotPinStates(); + bool _snapshotPwmStates(); + bool _snapshotAdcState(); + bool _snapshotPowerConfig(); + bool _snapshotButtonConfig(); + bool _snapshotIrqMasks(); + bool _snapshotIrqStatus(); + bool _snapshotAll(); + void _autoSnapshotUpdate(uint16_t domains); + bool _snapshotI2cConfig(); + void _clearI2cConfig(); + bool _snapshotNeoConfig(); + void _clearNeoConfig(); + + // Cache management functions + // 缓存管理函数 + void _initPinCache(); + void _updatePinCache(m5pm1_gpio_num_t pin); +}; + +#endif // _M5PM1_H_ diff --git a/src/M5PM1_i2c_compat.h b/src/M5PM1_i2c_compat.h new file mode 100644 index 0000000..b21092f --- /dev/null +++ b/src/M5PM1_i2c_compat.h @@ -0,0 +1,278 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __M5PM1_I2C_COMPAT_H__ +#define __M5PM1_I2C_COMPAT_H__ + +#include +#include +#include + +#ifdef ARDUINO + +#include "Wire.h" + +// ============================ +// Arduino I2C Functions +// Arduino I2C 功能 +// ============================ + +#ifndef M5PM1_I2C_READ_BYTE +static inline bool M5PM1_I2C_READ_BYTE(TwoWire *wire, uint8_t addr, uint8_t reg, uint8_t *data) { + wire->beginTransmission(addr); + wire->write(reg); + if (wire->endTransmission(false) != 0) { + return false; + } + if (wire->requestFrom(addr, (uint8_t)1) != 1) { + return false; + } + *data = wire->read(); + return true; +} +#endif + +#ifndef M5PM1_I2C_READ_BYTES +static inline bool M5PM1_I2C_READ_BYTES(TwoWire *wire, uint8_t addr, uint8_t start_reg, size_t len, uint8_t *data) { + wire->beginTransmission(addr); + wire->write(start_reg); + if (wire->endTransmission(false) != 0) { + return false; + } + if (wire->requestFrom(addr, (uint8_t)len) != len) { + return false; + } + for (size_t i = 0; i < len; i++) { + data[i] = wire->read(); + } + return true; +} +#endif + +#ifndef M5PM1_I2C_READ_REG16 +static inline bool M5PM1_I2C_READ_REG16(TwoWire *wire, uint8_t addr, uint8_t reg, uint16_t *data) { + uint8_t buf[2]; + if (!M5PM1_I2C_READ_BYTES(wire, addr, reg, 2, buf)) { + return false; + } + // Little-endian: low byte first + // 小端模式:低字节在前 + *data = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8); + return true; +} +#endif + +#ifndef M5PM1_I2C_WRITE_BYTE +static inline bool M5PM1_I2C_WRITE_BYTE(TwoWire *wire, uint8_t addr, uint8_t reg, uint8_t data) { + wire->beginTransmission(addr); + wire->write(reg); + wire->write(data); + if (wire->endTransmission() != 0) { + return false; + } + return true; +} +#endif + +#ifndef M5PM1_I2C_WRITE_BYTES +static inline bool M5PM1_I2C_WRITE_BYTES(TwoWire *wire, uint8_t addr, uint8_t start_reg, size_t len, const uint8_t *data) { + wire->beginTransmission(addr); + wire->write(start_reg); + for (size_t i = 0; i < len; i++) { + wire->write(data[i]); + } + if (wire->endTransmission() != 0) { + return false; + } + return true; +} +#endif + +#ifndef M5PM1_I2C_WRITE_REG16 +static inline bool M5PM1_I2C_WRITE_REG16(TwoWire *wire, uint8_t addr, uint8_t reg, uint16_t data) { + uint8_t buf[2]; + // Little-endian: low byte first + // 小端模式:低字节在前 + buf[0] = (uint8_t)(data & 0xFF); + buf[1] = (uint8_t)((data >> 8) & 0xFF); + return M5PM1_I2C_WRITE_BYTES(wire, addr, reg, 2, buf); +} +#endif + +// Wake signal for PM1 sleep mode +// PM1睡眠模式唤醒信号 +#ifndef M5PM1_I2C_SEND_WAKE +static inline void M5PM1_I2C_SEND_WAKE(TwoWire *wire, uint8_t addr) { + // Send START signal to generate SDA falling edge for PM1 wake + // PM1 uses SDA pin (PB4) falling edge to trigger EXTI4 interrupt for wakeup + wire->beginTransmission(addr); + wire->endTransmission(false); // Send START without STOP +} +#endif + +#else // ESP-IDF + +#include +#include // ESP-IDF native i2c_master driver +#include // esp-idf-lib i2c_bus component + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================ +// I2C Driver Type Selection +// I2C 驱动类型选择 +// ============================ +typedef enum { + M5PM1_I2C_DRIVER_NONE = 0, // Not initialized / 未初始化 + M5PM1_I2C_DRIVER_SELF_CREATED, // Self-created using i2c_port_t / 使用 i2c_port_t 自创建 + M5PM1_I2C_DRIVER_MASTER, // ESP-IDF native i2c_master driver / ESP-IDF 原生 i2c_master 驱动 + M5PM1_I2C_DRIVER_BUS // esp-idf-lib i2c_bus component / esp-idf-lib i2c_bus 组件 +} m5pm1_i2c_driver_t; + +// ============================ +// ESP-IDF I2C Functions (i2c_bus) +// ESP-IDF I2C 函数 (i2c_bus) +// ============================ + +#ifndef M5PM1_I2C_READ_BYTE +static inline esp_err_t M5PM1_I2C_READ_BYTE(i2c_bus_device_handle_t dev, uint8_t reg, uint8_t *data) { + return i2c_bus_read_byte(dev, reg, data); +} +#endif + +#ifndef M5PM1_I2C_READ_BYTES +static inline esp_err_t M5PM1_I2C_READ_BYTES(i2c_bus_device_handle_t dev, uint8_t start_reg, size_t len, uint8_t *data) { + return i2c_bus_read_bytes(dev, start_reg, len, data); +} +#endif + +#ifndef M5PM1_I2C_READ_REG16 +static inline esp_err_t M5PM1_I2C_READ_REG16(i2c_bus_device_handle_t dev, uint8_t reg, uint16_t *data) { + uint8_t buf[2]; + esp_err_t ret = i2c_bus_read_bytes(dev, reg, 2, buf); + if (ret == ESP_OK) { + // Little-endian: low byte first + // 小端模式:低字节在前 + *data = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8); + } + return ret; +} +#endif + +#ifndef M5PM1_I2C_WRITE_BYTE +static inline esp_err_t M5PM1_I2C_WRITE_BYTE(i2c_bus_device_handle_t dev, uint8_t reg, uint8_t data) { + return i2c_bus_write_byte(dev, reg, data); +} +#endif + +#ifndef M5PM1_I2C_WRITE_BYTES +static inline esp_err_t M5PM1_I2C_WRITE_BYTES(i2c_bus_device_handle_t dev, uint8_t start_reg, size_t len, const uint8_t *data) { + return i2c_bus_write_bytes(dev, start_reg, len, (uint8_t*)data); +} +#endif + +#ifndef M5PM1_I2C_WRITE_REG16 +static inline esp_err_t M5PM1_I2C_WRITE_REG16(i2c_bus_device_handle_t dev, uint8_t reg, uint16_t data) { + uint8_t buf[2]; + // Little-endian: low byte first + // 小端模式:低字节在前 + buf[0] = (uint8_t)(data & 0xFF); + buf[1] = (uint8_t)((data >> 8) & 0xFF); + return i2c_bus_write_bytes(dev, reg, 2, buf); +} +#endif + +// ============================ +// ESP-IDF I2C Functions (i2c_master - native driver) +// ESP-IDF I2C 函数 (i2c_master - 原生驱动) +// ============================ + +#ifndef M5PM1_I2C_MASTER_READ_BYTE +static inline esp_err_t M5PM1_I2C_MASTER_READ_BYTE(i2c_master_dev_handle_t dev, uint8_t reg, uint8_t *data) { + return i2c_master_transmit_receive(dev, ®, 1, data, 1, -1); +} +#endif + +#ifndef M5PM1_I2C_MASTER_READ_BYTES +static inline esp_err_t M5PM1_I2C_MASTER_READ_BYTES(i2c_master_dev_handle_t dev, uint8_t start_reg, size_t len, uint8_t *data) { + return i2c_master_transmit_receive(dev, &start_reg, 1, data, len, -1); +} +#endif + +#ifndef M5PM1_I2C_MASTER_READ_REG16 +static inline esp_err_t M5PM1_I2C_MASTER_READ_REG16(i2c_master_dev_handle_t dev, uint8_t reg, uint16_t *data) { + uint8_t buf[2]; + esp_err_t ret = i2c_master_transmit_receive(dev, ®, 1, buf, 2, -1); + if (ret == ESP_OK) { + // Little-endian: low byte first + // 小端模式:低字节在前 + *data = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8); + } + return ret; +} +#endif + +#ifndef M5PM1_I2C_MASTER_WRITE_BYTE +static inline esp_err_t M5PM1_I2C_MASTER_WRITE_BYTE(i2c_master_dev_handle_t dev, uint8_t reg, uint8_t data) { + uint8_t buf[2] = {reg, data}; + return i2c_master_transmit(dev, buf, 2, -1); +} +#endif + +#ifndef M5PM1_I2C_MASTER_WRITE_BYTES +static inline esp_err_t M5PM1_I2C_MASTER_WRITE_BYTES(i2c_master_dev_handle_t dev, uint8_t start_reg, size_t len, const uint8_t *data) { + // Need to prepend register address + // 需要在数据前添加寄存器地址 + uint8_t *buf = (uint8_t*)malloc(len + 1); + if (buf == NULL) return ESP_ERR_NO_MEM; + buf[0] = start_reg; + memcpy(buf + 1, data, len); + esp_err_t ret = i2c_master_transmit(dev, buf, len + 1, -1); + free(buf); + return ret; +} +#endif + +#ifndef M5PM1_I2C_MASTER_WRITE_REG16 +static inline esp_err_t M5PM1_I2C_MASTER_WRITE_REG16(i2c_master_dev_handle_t dev, uint8_t reg, uint16_t data) { + uint8_t buf[3]; + buf[0] = reg; + // Little-endian: low byte first + // 小端模式:低字节在前 + buf[1] = (uint8_t)(data & 0xFF); + buf[2] = (uint8_t)((data >> 8) & 0xFF); + return i2c_master_transmit(dev, buf, 3, -1); +} +#endif + +// Wake signal for PM1 sleep mode using i2c_bus +// 使用i2c_bus的PM1睡眠模式唤醒信号 +#ifndef M5PM1_I2C_SEND_WAKE +static inline esp_err_t M5PM1_I2C_SEND_WAKE(i2c_bus_device_handle_t dev, uint8_t reg) { + // Read any register to generate I2C start signal for wake + uint8_t dummy; + return i2c_bus_read_byte(dev, reg, &dummy); +} +#endif + +// Wake signal for PM1 sleep mode using i2c_master +// 使用i2c_master的PM1睡眠模式唤醒信号 +#ifndef M5PM1_I2C_MASTER_SEND_WAKE +static inline esp_err_t M5PM1_I2C_MASTER_SEND_WAKE(i2c_master_bus_handle_t bus, uint8_t addr) { + // Use i2c_master_probe to generate START signal for wake + return i2c_master_probe(bus, addr, 10); +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif // ARDUINO + +#endif // __M5PM1_I2C_COMPAT_H__