1. Add examples and fix some code

This commit is contained in:
onexs-xsi
2026-01-21 17:08:09 +08:00
parent 7ad8d28857
commit 0768467668
5 changed files with 241 additions and 68 deletions
+3 -2
View File
@@ -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
+13 -9
View File
@@ -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 | 支持连续读写 |
## 版本与依赖
+157
View File
@@ -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
@@ -8,14 +8,20 @@
#include <M5PM1.h>
/*
* //
* 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. 1010
*
* PM1 510
* 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)
+8
View File
@@ -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, &regVal)) 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 {