diff --git a/README.md b/README.md index 045781d..78e63b7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ M5PM1 is a dual-platform (ESP-IDF & Arduino) driver library for M5Stack PM1 Powe ## Related Link -- N/A +- [README_FUNCTION_CN](README_FUNCTION_CN.md) +- [README_FUNCTION_EN](README_FUNCTION_EN.md) ## Required Libraries: @@ -18,8 +19,8 @@ M5PM1 is a dual-platform (ESP-IDF & Arduino) driver library for M5Stack PM1 Powe - examples/basic_power_adc/basic_power_adc.ino - examples/gpio_pwm/gpio_pwm.ino -- examples/interrupt_button_sleep/interrupt_button_sleep.ino - examples/neopixel/neopixel.ino +- examples/usb_interrupt_sleep/usb_interrupt_sleep.ino ## License diff --git a/README_FUNCTION_CN.md b/README_FUNCTION_CN.md index 55a1d50..621fa5b 100644 --- a/README_FUNCTION_CN.md +++ b/README_FUNCTION_CN.md @@ -4,7 +4,7 @@ - **版本**: 1.0.1 - **默认 I2C 地址**: `0x6E` -- **I2C 速度**: 默认 100 kHz,可切换 400 kHz(切换后需重新初始化 I2C) +- **I2C 速度**: 默认 100 kHz,可切换 400 kHz(切换后库内部会处理 I2C 重建) ## 项目结构与角色 @@ -86,8 +86,8 @@ ### 10) 按钮 - `btnSetConfig`/`btnGetState`/`btnGetFlag` -- `setSingleResetDisable`/`getSingleResetDisable` -- `setDoubleOffDisable`/`getDoubleOffDisable` +- `setSingleResetDisable`(高危)/`getSingleResetDisable` +- `setDoubleOffDisable`(高危)/`getDoubleOffDisable` ### 11) 中断 @@ -105,7 +105,7 @@ ### 12) 系统命令 - `sysCmd`/`shutdown`/`reboot`/`enterDownloadMode` -- `setDownloadLock`/`getDownloadLock` +- `setDownloadLock`(高危)/`getDownloadLock` ### 13) NeoPixel @@ -134,17 +134,21 @@ - `getCachedPowerConfig`/`getCachedButtonConfig` - `getCachedIrqMasks`/`getCachedIrqStatus` +## 注意事项 + +如果使用 `setDownloadLock`、`setSingleResetDisable`、`setDoubleOffDisable` 高危函数导致无法启用烧录、单击复位、双击关断,请拔插电池(内置电池设备可能损坏壳体和器件)或快速短接 BAT 和 GND(此操作可能损坏电池)。 + ## 引脚与功能限制 | 功能区域 | 说明 | 备注 | | :--- | :--- | :--- | -| **GPIO 互斥** | GPIO0/2 共享 WAKE/IRQ 线;GPIO3/4 互斥 | GPIO0/2 互斥 | -| **唤醒 (WAKE)** | GPIO0, GPIO2 支持唤醒 | GPIO1 不支持 WAKE | +| **WAKE 互斥** | GPIO0/2 互斥;GPIO3/4 互斥 | 仅 WAKE 功能生效 | +| **唤醒 (WAKE)** | GPIO0/2/3/4 支持唤醒 | GPIO1 不支持 WAKE | | **ADC 通道** | GPIO1=ADC1, GPIO2=ADC2 | 温度为内部通道 | | **PWM 通道** | GPIO3=PWM0, GPIO4=PWM1 | 频率全通道共享 | -| **NeoPixel** | 数据区 0x60 - 0x9F | 刷新时 I2C 短暂不可中断 | -| **I2C 休眠** | 空闲超时可进入低功耗 | PWM 使能或下载模式下禁止休眠 | -| **寄存器区块** | 0x00 - 0x0C, 0x10 - 0x19, 0x20 - 0x2A, 0x30 - 0x35 0x38 - 0x3D, 0x40 - 0x45, 0x48 - 0x4A, 0x50 0x53, 0x60 - 0x9F, 0xA0 - 0xBF | 支持连续读写 | +| **NeoPixel** | 仅 GPIO0 支持;LED 数量 1-31;数据区 0x60 - 0x9F | 刷新时 I2C 短暂不可中断 | +| **I2C 休眠** | 空闲超时可进入低功耗 | PWM 使能或下载模式下休眠失效 | +| **寄存器区块** | 0x00 - 0x0C, 0x10 - 0x19, 0x20 - 0x2A, 0x30 - 0x35, 0x38 - 0x3D, 0x40 - 0x45, 0x48 - 0x4A, 0x50, 0x53, 0x60 - 0x9F, 0xA0 - 0xBF | 支持连续读写 | ## 版本与依赖 diff --git a/README_FUNCTION_EN.md b/README_FUNCTION_EN.md new file mode 100644 index 0000000..bdae1b8 --- /dev/null +++ b/README_FUNCTION_EN.md @@ -0,0 +1,157 @@ +# M5PM1 Function Overview (English) + +Feature overview of the PM1 power management driver library for Arduino/ESP-IDF. + +- **Version**: 1.0.1 +- **Default I2C address**: `0x6E` +- **I2C speed**: Default 100 kHz, can switch to 400 kHz (the library rebuilds I2C internally after switching) + +## Project Structure and Roles + +- `src/M5PM1.h`: Public APIs, registers/enums, cache structure definitions. +- `src/M5PM1.cpp`: Driver implementation, conflict checks, snapshot and cache logic. +- `src/M5PM1_i2c_compat.h`: Arduino/ESP-IDF I2C compatibility wrapper. + +## Examples + +- `examples/basic_power_adc/basic_power_adc.ino`: Initialization, device info, VBAT/VIN/5V readings, ADC/temperature sampling, power rail control. +- `examples/gpio_pwm/gpio_pwm.ino`: GPIO output/input reads and PWM breathing LED demo. +- `examples/interrupt_button_sleep/interrupt_button_sleep.ino`: GPIO interrupt, button APIs, timer/wake, and shutdown demo. +- `examples/neopixel/neopixel.ino`: NeoPixel rainbow demo. + +## Feature Categories + +### 1) Initialization and Logging + +- **Initialization** + - Arduino: `begin(TwoWire*, addr, sda, scl, speed)` + - ESP-IDF: `begin(i2c_port_t, addr, sda, scl, speed)` + - ESP-IDF: `begin(i2c_master_bus_handle_t, addr, speed)` + - ESP-IDF: `begin(i2c_bus_handle_t, addr, speed)` +- **Logging** + - `setLogLevel`/`getLogLevel` + +### 2) Device Information + +- `getDeviceId`/`getDeviceModel`/`getHwVersion`/`getSwVersion` + +### 3) GPIO Basics and Advanced Control + +- **Arduino style** + - `pinMode`/`digitalWrite`/`digitalRead` +- **With return status** + - `pinModeWithRes`/`digitalWriteWithRes`/`digitalReadWithRes` +- **Advanced configuration** + - `gpioSet`/`gpioSetFunc`/`gpioSetMode`/`gpioSetOutput`/`gpioGetInput` + - `gpioSetPull`/`gpioSetDrive`/`ledEnSetDrive` + - `gpioSetWakeEnable`/`gpioSetWakeEdge` +- **Pin status and validation** + - `dumpPinStatus`/`verifyPinConfig`/`getPinStatus`/`getPinStatusArray` + +### 4) Power Hold + +- `gpioSetPowerHold`/`gpioGetPowerHold` +- `ldoSetPowerHold`/`ldoGetPowerHold` +- `dcdcSetPowerHold`/`dcdcGetPowerHold` + +### 5) ADC and Temperature + +- `analogRead`/`isAdcBusy`/`disableAdc`/`readTemperature` + +### 6) PWM + +- `setPwmFrequency`/`getPwmFrequency` +- `setPwmDuty`/`getPwmDuty` (0-100%) +- `setPwmDuty12bit`/`getPwmDuty12bit` (0-4095) +- `setPwmConfig` +- `analogWrite` (0-255) + +### 7) Voltage Reading + +- `readVref`/`getRefVoltage`/`readVbat`/`readVin`/`read5VInOut` + +### 8) Power Management and Battery + +- `getPowerSource` +- `getWakeSource`/`clearWakeSource` +- `setPowerConfig`/`getPowerConfig`/`clearPowerConfig` +- `setChargeEnable`/`setDcdcEnable`/`setLdoEnable`/`set5VInOutEnable`/`setLedEnLevel` +- `setBatteryLvp` + +### 9) Watchdog and Timer + +- `wdtSet`/`wdtFeed`/`wdtGetCount` +- `timerSet`/`timerClear` + +### 10) Button + +- `btnSetConfig`/`btnGetState`/`btnGetFlag` +- `setSingleResetDisable` (High risk)/`getSingleResetDisable` +- `setDoubleOffDisable` (High risk)/`getDoubleOffDisable` + +### 11) Interrupts + +- **Status read and clear** + - `irqGetGpioStatus`/`irqClearGpio` + - `irqGetSysStatus`/`irqClearSys` + - `irqGetBtnStatus`/`irqClearBtn` +- **Enum-style read** + - `irqGetGpioStatusEnum`/`irqGetSysStatusEnum`/`irqGetBtnStatusEnum` +- **Interrupt masks** + - `irqSetGpioMask`/`irqGetGpioMask`/`irqSetGpioMaskAll`/`irqGetGpioMaskAll` + - `irqSetSysMask`/`irqGetSysMask`/`irqSetSysMaskAll`/`irqGetSysMaskAll` + - `irqSetBtnMask`/`irqGetBtnMask`/`irqSetBtnMaskAll`/`irqGetBtnMaskAll` + +### 12) System Commands + +- `sysCmd`/`shutdown`/`reboot`/`enterDownloadMode` +- `setDownloadLock` (High risk)/`getDownloadLock` + +### 13) NeoPixel + +- `setLeds`/`setLedCount`/`setLedColor`/`refreshLeds`/`disableLeds` + +### 14) AW8737A Pulse Control + +- `setAw8737aPulse`/`refreshAw8737aPulse` + +### 15) RTC RAM + +- `writeRtcRAM`/`readRtcRAM` + +### 16) I2C Configuration and Auto Wake + +- `setI2cConfig`/`switchI2cSpeed`/`getI2cSpeed` +- `setI2cSleepTime`/`getI2cSleepTime` +- `setAutoWakeEnable`/`isAutoWakeEnabled`/`sendWakeSignal` +- Compatibility layer: `M5PM1_i2c_compat.h` + +### 17) State Snapshot/Cache/Validation + +- `setAutoSnapshot`/`isAutoSnapshotEnabled`/`updateSnapshot`/`verifySnapshot` +- `validateConfig` +- `getCachedPwmFrequency`/`getCachedPwmState`/`getCachedAdcState` +- `getCachedPowerConfig`/`getCachedButtonConfig` +- `getCachedIrqMasks`/`getCachedIrqStatus` + +## Precautions + +If using high-risk functions `setDownloadLock`, `setSingleResetDisable`, or `setDoubleOffDisable` results in the inability to enable download mode, single-click reset, or double-click shutdown, please disconnect/reconnect the battery (devices with built-in batteries may risk damage to the casing and components) or quickly short BAT and GND (this operation may damage the battery). + +## Pin and Function Limits + +| Area | Description | Notes | +| :--- | :--- | :--- | +| **WAKE mutual exclusion** | GPIO0/2 are mutually exclusive; GPIO3/4 are mutually exclusive | Only for WAKE function | +| **Wake (WAKE)** | GPIO0/2/3/4 support wake | GPIO1 does not support WAKE | +| **ADC channels** | GPIO1=ADC1, GPIO2=ADC2 | Temperature is an internal channel | +| **PWM channels** | GPIO3=PWM0, GPIO4=PWM1 | Frequency shared across channels | +| **NeoPixel** | GPIO0 only; LED count 1-31; data area 0x60 - 0x9F | I2C briefly non-interruptible during refresh | +| **I2C sleep** | Idle timeout can enter low power | Sleep is ineffective when PWM is enabled or in download mode | +| **Register blocks** | 0x00 - 0x0C, 0x10 - 0x19, 0x20 - 0x2A, 0x30 - 0x35, 0x38 - 0x3D, 0x40 - 0x45, 0x48 - 0x4A, 0x50, 0x53, 0x60 - 0x9F, 0xA0 - 0xBF | Supports continuous read/write | + +## Version and Dependencies + +- **Version**: 1.0.1 +- **Platforms**: Arduino / ESP-IDF (>=4.4) +- **Dependency**: espressif/i2c_bus diff --git a/examples/interrupt_button_sleep/interrupt_button_sleep.ino b/examples/usb_interrupt_sleep/usb_interrupt_sleep.ino similarity index 52% rename from examples/interrupt_button_sleep/interrupt_button_sleep.ino rename to examples/usb_interrupt_sleep/usb_interrupt_sleep.ino index 877b161..46670c5 100644 --- a/examples/interrupt_button_sleep/interrupt_button_sleep.ino +++ b/examples/usb_interrupt_sleep/usb_interrupt_sleep.ino @@ -8,14 +8,20 @@ #include /* - * 中断/按键/关机与唤醒示例。 - * Interrupt/button/sleep & wake demo. + * USB拔插中断/关机与唤醒示例。 + * USB Plug/Unplug Interrupt/Shutdown & Wake Demo. * - * IRQ_GPIO=GPIO2 用于外部中断/唤醒输入;GPIO0/2 共用线,勿同时启用唤醒。 - * IRQ_GPIO=GPIO2 for external IRQ/wake; GPIO0/2 share a line, don't enable both. + * 功能: + * 1. 启动时打印唤醒源。 + * 2. 运行时监听 USB (5VIN) 的拔插事件。 + * 3. 当检测到 USB 拔出 (5VIN Remove) 时,倒计时5秒后关机。 + * 4. 关机前设置10秒定时器唤醒,10秒后自动开机。 * - * 双击电源键触发 PM1 关机(延迟5秒),10 秒后定时唤醒。 - * Double-click power button triggers PM1 shutdown (5s delay); timer wakes after 10s. + * Features: + * 1. Print wake source on startup. + * 2. Listen for USB (5VIN) plug/unplug events. + * 3. When USB unplug (5VIN Remove) detected, shutdown after 5s countdown. + * 4. Set 10s timer wake up before shutdown, auto wake up after 10s. */ M5PM1 pm1; @@ -43,8 +49,7 @@ void IRAM_ATTR pm1_irq_handler() irqFlag = true; } -static const m5pm1_gpio_num_t IRQ_GPIO = M5PM1_GPIO_NUM_1; -static const uint32_t WAKE_TIMER_SEC = 10; +static const uint32_t WAKE_TIMER_SEC = 10; static void printDivider() { @@ -65,23 +70,25 @@ static void printWakeSource(uint8_t src) static void enterSleep() { - LOGW("Prepare for shutdown with wake sources"); + LOGW("Prepare for shutdown..."); // 先配置10s的定时开机 // Configure 10s timer wake up first pm1.timerSet(WAKE_TIMER_SEC, M5PM1_TIM_ACTION_POWERON); - // 触发双击后等待5s关机 - // Wait 5s before shutdown after double click - LOGW("Wait 5s before shutdown..."); - delay(5000); + // 等待5s关机 + // Wait 5s before shutdown + for (int i = 5; i > 0; i--) { + LOGW("Shutdown in %d s...", i); + delay(1000); + } // 关闭LED_EN灯显(将默认电平配置为低电平) // Turn off LED_EN indicator (by setting default level to LOW) pm1.setLedEnLevel(false); vTaskDelay(100 / portTICK_PERIOD_MS); - LOGW("Shutdown now. Wake by GPIO%u or %us timer", IRQ_GPIO, WAKE_TIMER_SEC); + LOGW("Shutdown now. Wake by %us timer", WAKE_TIMER_SEC); pm1.shutdown(); } @@ -90,7 +97,7 @@ void setup() Serial.begin(115200); delay(200); printDivider(); - LOGI("Interrupt + Button + Sleep demo start"); + LOGI("USB Interrupt + Sleep demo start"); m5pm1_err_t err = pm1.begin(&Wire, M5PM1_DEFAULT_ADDR, PM1_I2C_SDA, PM1_I2C_SCL, PM1_I2C_FREQ); if (err != M5PM1_OK) { @@ -111,35 +118,27 @@ void setup() // Clear timer settings to avoid mis-trigger. pm1.timerClear(); - // 禁用单击复位/双击关机,避免与示例流程冲突。 - // Disable single-reset/double-off to avoid conflicts with demo flow. - pm1.setSingleResetDisable(true); - pm1.setDoubleOffDisable(true); - - pm1.btnSetConfig(M5PM1_BTN_TYPE_CLICK, M5PM1_BTN_DELAY_250MS); - pm1.btnSetConfig(M5PM1_BTN_TYPE_DOUBLE, M5PM1_BTN_DELAY_250MS); - pm1.btnSetConfig(M5PM1_BTN_TYPE_LONG, M5PM1_BTN_DELAY_500MS); - - pm1.gpioSetFunc(IRQ_GPIO, M5PM1_GPIO_FUNC_IRQ); - pm1.gpioSetMode(IRQ_GPIO, M5PM1_GPIO_MODE_INPUT); - pm1.gpioSetPull(IRQ_GPIO, M5PM1_GPIO_PULL_UP); - // 设置ESP32的中断引脚,用于接收PM1的IRQ信号。 // Setup ESP32 interrupt pin to receive IRQ signal from PM1. pinMode(PM1_ESP_IRQ_GPIO, INPUT_PULLUP); attachInterrupt(PM1_ESP_IRQ_GPIO, pm1_irq_handler, FALLING); - // 启用 PM1 GPIO1 的中断输出(这里的Mask Disable代表允许产生中断)。 - // Enable PM1 GPIO1 IRQ output (Mask Disable means interrupt enabled). - pm1.irqSetGpioMask(IRQ_GPIO, M5PM1_IRQ_MASK_DISABLE); - - // 启用按钮中断输出。 - // Enable button IRQ output. - pm1.irqSetBtnMaskAll(M5PM1_IRQ_MASK_DISABLE); + // 屏蔽所有GPIO和按钮中断 + // Mask all GPIO and Button IRQs (disable them) pm1.irqSetGpioMaskAll(M5PM1_IRQ_MASK_ENABLE); - pm1.irqSetSysMaskAll(M5PM1_IRQ_MASK_ENABLE); + pm1.irqSetBtnMaskAll(M5PM1_IRQ_MASK_ENABLE); + + // 配置系统中断:只启用 VIN 移除中断(也可以全部开启) + // Configure System IRQs: Enable VIN remove interrupt + pm1.irqSetSysMaskAll(M5PM1_IRQ_MASK_ENABLE); // 先全部屏蔽 + pm1.irqSetSysMask(M5PM1_IRQ_SYS_5VIN_REMOVE, M5PM1_IRQ_MASK_DISABLE); // 开启5VIN移除 + // pm1.irqSetSysMask(M5PM1_IRQ_SYS_5VIN_INSERT, M5PM1_IRQ_MASK_DISABLE); // 可选开启插入 + + // 注意:有些板子 TypeC 可能连接到 5VINOUT + pm1.irqSetSysMask(M5PM1_IRQ_SYS_5VINOUT_REMOVE, M5PM1_IRQ_MASK_DISABLE); printDivider(); + LOGI("Waiting for USB unplug event..."); } void loop() @@ -147,30 +146,34 @@ void loop() if (irqFlag) { irqFlag = false; - uint8_t gpioIrq = 0; - if (pm1.irqGetGpioStatus(&gpioIrq, M5PM1_CLEAN_ONCE) == M5PM1_OK) { - if (gpioIrq != M5PM1_IRQ_GPIO_NONE) { - LOGI("GPIO IRQ status: 0x%02X", gpioIrq); - if (gpioIrq & M5PM1_IRQ_GPIO0) LOGI("- GPIO0 IRQ triggered"); - if (gpioIrq & M5PM1_IRQ_GPIO1) LOGI("- GPIO1 IRQ triggered"); - if (gpioIrq & M5PM1_IRQ_GPIO2) LOGI("- GPIO2 IRQ triggered"); - if (gpioIrq & M5PM1_IRQ_GPIO3) LOGI("- GPIO3 IRQ triggered"); - if (gpioIrq & M5PM1_IRQ_GPIO4) LOGI("- GPIO4 IRQ triggered"); + uint8_t sysIrq = 0; + if (pm1.irqGetSysStatus(&sysIrq, M5PM1_CLEAN_ONCE) == M5PM1_OK) { + if (sysIrq != M5PM1_IRQ_SYS_NONE) { + LOGI("System IRQ status: 0x%02X", sysIrq); + + // 检测 5VIN 移除 + if (sysIrq & M5PM1_IRQ_SYS_5VIN_REMOVE) { + LOGI("Event: 5VIN Removed"); + enterSleep(); + } + + // 检测 5VINOUT 移除 (以防万一) + if (sysIrq & M5PM1_IRQ_SYS_5VINOUT_REMOVE) { + LOGI("Event: 5VINOUT Removed"); + enterSleep(); + } + + if (sysIrq & M5PM1_IRQ_SYS_5VIN_INSERT) { + LOGI("Event: 5VIN Inserted"); + } } } - uint8_t btnIrq = 0; - if (pm1.irqGetBtnStatus(&btnIrq, M5PM1_CLEAN_ONCE) == M5PM1_OK) { - if (btnIrq != M5PM1_IRQ_BTN_NONE) { - LOGI("Button IRQ status: 0x%02X", btnIrq); - if (btnIrq & M5PM1_IRQ_BTN_CLICK) LOGI("- Single Click triggered"); - if (btnIrq & M5PM1_IRQ_BTN_DOUBLE) { - LOGI("- Double Click triggered"); - enterSleep(); - } - if (btnIrq & M5PM1_IRQ_BTN_WAKE) LOGI("- Wake Button triggered"); - } - } + // 同时也清除任何可能的误触发(虽然我们mask了btn和gpio) + uint8_t dummy; + pm1.irqGetGpioStatus(&dummy, M5PM1_CLEAN_ALL); + pm1.irqGetBtnStatus(&dummy, M5PM1_CLEAN_ALL); + } else if (digitalRead(PM1_ESP_IRQ_GPIO) == LOW) { // 存在未处理的中断信号,读取以清除(但忽略内容) // Unhandled IRQ signal exists, read to clear (but ignore content) diff --git a/src/M5PM1.cpp b/src/M5PM1.cpp index 286000f..17a736b 100644 --- a/src/M5PM1.cpp +++ b/src/M5PM1.cpp @@ -3575,6 +3575,7 @@ m5pm1_err_t M5PM1::sysCmd(m5pm1_sys_cmd_t cmd) M5PM1_LOG_E(TAG, "Not initialized"); return M5PM1_ERR_NOT_INIT; } + M5PM1_DELAY_MS(100); uint8_t val = M5PM1_SYS_CMD_KEY | (uint8_t)cmd; if (!_writeReg(M5PM1_REG_SYS_CMD, val)) { return M5PM1_ERR_I2C_COMM; @@ -3606,6 +3607,13 @@ m5pm1_err_t M5PM1::setDownloadLock(bool lock) uint8_t regVal; if (!_readReg(M5PM1_REG_BTN_CFG_1, ®Val)) return M5PM1_ERR_I2C_COMM; + // 检查锁定逻辑: 一旦锁定(变为1),除断电外无法解锁(变为0) + // Check lock logic: Once locked (set to 1), it cannot be unlocked (set to 0) until power cycle + if ((regVal & 0x80) && !lock) { + M5PM1_LOG_E(TAG, "Download lock is permanent until power cycle"); + return M5PM1_ERR_RULE_VIOLATION; + } + if (lock) { regVal |= 0x80; // DL_LOCK is bit 7 } else {