简体中文
English
简体中文
日本語

Arduino 上手教程

2. 设备开发 & 案例程序

5. 扩展模块

6. 应用案例

CoreMatrix M5PM1 & M5IOE1 电源管理

1. 多级电源开关设计

CoreMatrix 内部集成 Stamp Timer Power2(M5PM1)+ M5IOE1,结合硬件电路实现了多级电源开关,不同级别对应不同的主控运行状态及外设与接口供电状态。用户可根据运行需求,切换主控运行状态及相关电源使能,关闭未使用的外设部分,实现整机低功耗。

M5PM1 / M5IOE1 驱动库

使用 M5PM1 / M5IOE1 驱动库能够非常便捷地配置 M5PM1 / M5IOE1 的引脚功能,用于低功耗唤醒以及外设供电开关。

注意事项
CoreMatrix 的多级电源开关设计并非串联结构。L1 ~ L3B 开关的电源输入均来自于 L0 (SYS_VBUS) 级源头,而非上一级电源,因此支持独立控制不同级别的电源开关,以上分级根据外设供电与功耗进行区分。M5PM1 启动上电后 L1,L3A 将自动开启,在 M5Unified 初始化流程中,将进一步控制 M5IOE1 打开 L3B,使能其他的外设供电或使能引脚。

L0

此级电源条件下,电池保持对 M5PM1 的供电。电池电量未耗尽的情况下,该层电源将一直保持,M5PM1 支持基础的按键开关机操作。

此外,L0 域还包含 M5PM1 的 EXT_5V boost 输出(5V_INOUT):它为 M5-Bus 提供 5V,并转换后产生 M5BUS_3V3_L0。该路输出由 M5PM1 的 EXT_5V_EN 单独控制,默认关闭,M5Unified 初始化时会自动打开,不随 L1 ~ L3B 的分级开关变化。

关闭输出后,5V_INOUT 可作为外部 5V 输入;开启输出时请勿从 M5-Bus 的 5V 引脚反向供电。

pm1.setBoostEnable(true);  // M5-Bus 5V + M5-Bus 3V3 ON
pm1.setBoostEnable(false); // M5-Bus 5V + M5-Bus 3V3 OFF

L1

此级电源条件下,将启用 IMU、KEY1~KEY3 按键上拉及相关逻辑供电。该层级的电源开关使用 M5PM1 的 3V3_L1,可通过以下 API 实现开关控制。

pm1.setLdoEnable(true); // L1 ON
pm1.setLdoEnable(false); // L1 OFF

同时,BMI270 INT1 连接至 M5PM1 G4,KEY1~KEY3 分别连接 M5PM1 G0~G2。需要由 IMU 或按键唤醒 M5PM1 时,应保持 L1 供电后 M5PM1 进入休眠:

pm1.setLdoEnable(true);
pm1.ldoSetPowerHold(true);
pm1.setLedEnLevel(true);
pm1.shutdown();

L3A

此级电源条件下,将启用 ESP32-C61、M5IOE1、I2C 上拉及主控相关外设供电。该层级使用 M5PM1 的 DCDC 输出 3V3_L3A,可通过以下 API 实现开关控制。

pm1.setDcdcEnable(true); // L3A ON
pm1.setDcdcEnable(false); // L3A OFF

L3B

L3B 层级控制 LED 点阵、microSD、蜂鸣器和 Grove 等外设的供电或相关配置。该层级基于 M5IOE1 IO 拓展芯片实现控制,允许独立控制该层级的部分外设。

M5IOE1 PYG1 PYG2 PYG3 PYG4 PYG5 PYG8 PYG10 PYG11
microSD PYB_TF_EN PYB_TFCARD_DET
Buzzer PYB_BUZZER_EN PYB_BUZZER_PWM
Charge PYB_CHG_PROG PYB_CHG_STAT
LED Matrix PYB_LEDS_EN
Grove 5V PYB_GROVE_EN
  • PYB_TF_EN: microSD TF_3V3_L3B 电源使能
  • PYB_BUZZER_EN: 蜂鸣器 BUZZER_3V3_L3B 电源使能
  • PYB_CHG_PROG: 充电电流档位控制
  • PYB_LEDS_EN: LED 点阵 LEDS_3V3_L3B 电源使能
  • PYB_GROVE_EN: Grove 5V 输出使能
  • PYB_CHG_STAT: 充电状态输入,低电平表示充电
  • PYB_TFCARD_DET: microSD 检测,低电平表示检测到卡
  • PYB_BUZZER_PWM: 蜂鸣器 PWM 驱动,通道 M5IOE1_PWM_CH3(索引 2)

具体的外设供电控制,通过下方 M5IOE1 的 API 进行独立控制:

ioe1.pinMode(M5IOE1_PIN_4, OUTPUT);
ioe1.digitalWrite(M5IOE1_PIN_4, HIGH);

Grove 接口的 5V 输出由 PYB_GROVE_EN (PYG5) 控制。M5Unified 的 cfg.output_power 默认为 true,即 M5.begin() 后 Grove 5V 默认已打开,可通过以下 API 控制:

M5.Power.setExtOutput(true);  // 打开 Grove 5V
M5.Power.setExtOutput(false); // 关闭 Grove 5V

2. M5PM1 休眠

手动休眠

M5PM1 可通过程序手动控制进入休眠状态,降低整机功耗。默认状态下,直接设置休眠将回退至低功耗供电状态,此时 M5PM1 保持供电。

pm1.shutdown();

需要保留 IMU、按键上拉等 L1 电路时,应使用 M5PM1 驱动库配置 L1 供电保持和对应唤醒源,再执行 pm1.shutdown()。

I2C 空闲休眠

M5PM1 支持配置 I2C 空闲自动休眠。进入休眠后,ESP32 与 M5PM1 的首次通信用于唤醒 M5PM1,有效通信将在下一次通信中完成。

m5pm1_err_t setI2cSleepTime(uint8_t seconds);

3. M5PM1 Timer 定时器

M5PM1 支持配置定时器功能,当计时结束后执行开机、关机、复位等操作。

m5pm1_err_t timerSet(uint32_t seconds, m5pm1_tim_action_t 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;

定时开机 / 关机案例

案例说明:按下 KEY1 设置 3 秒后自动开机并立即关机,按下 KEY2 设置 5 秒后自动关机。关机后可通过单击 PWR_BTN 重新开机。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
#include <M5Unified.h>
#include <M5PM1.h>

M5PM1 pm1;

static void drawState(uint8_t state)
{
  M5.Display.startWrite();
  M5.Display.fillScreen(TFT_BLACK);
  M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);

  if (state == 0) {
    M5.Display.fillRect(7, 7, 2, 2, TFT_WHITE);
  } else if (state == 1) {
    M5.Display.fillRect(3, 3, 4, 4, TFT_WHITE);
  } else {
    M5.Display.fillRect(9, 9, 4, 4, TFT_WHITE);
  }

  M5.Display.endWrite();
  M5.Display.display();
}

void setup()
{
  auto cfg = M5.config();
  cfg.serial_baudrate = 115200;
  M5.begin(cfg);

  M5.Display.setColorDepth(8);
  M5.Display.setRotation(1);
  M5.Display.setBrightness(128);

  const m5pm1_err_t err = pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);
  if (err != M5PM1_OK) {
    Serial.printf("M5PM1 init failed: %d\n", err);
    while (true) delay(1000);
  }

  Serial.println("KEY1: power on after 3s");
  Serial.println("KEY2: power off after 5s");
  drawState(0);
}

void loop()
{
  M5.update();

  if (M5.BtnA.wasClicked()) {
    Serial.println("Set power-on timer: 3s");
    drawState(1);
    delay(200);
    pm1.timerSet(3, M5PM1_TIM_ACTION_POWERON);
    pm1.shutdown();
  }

  if (M5.BtnB.wasClicked()) {
    Serial.println("Set power-off timer: 5s");
    drawState(2);
    pm1.timerSet(5, M5PM1_TIM_ACTION_POWEROFF);
  }

  delay(20);
}

4. IMU 唤醒

BMI270 驱动库

IMU M5PM1 唤醒

电源切换至 L1 Mode,CoreMatrix 的 IMU、KEY1~KEY3 按键上拉和 M5PM1 处于供电状态。在配置 IMU 唤醒功能后,M5PM1 进入休眠状态,同时保持 L1 的输出供电 (3V3_L1_EN),用于维持 BMI270 工作。

此时可通过翻转或移动设备,触发 BMI270 INT1 输出低电平,唤醒 M5PM1 重新启动。

M5PM1 唤醒后将重新运行 L0、L1 和 L3A 上电流程。ESP32-C61 重新执行初始化。

案例说明:设备开机后,单击按键 A(KEY1)配置 IMU 中断模式与 M5PM1 L1 电源保持,M5PM1 进入休眠。此时翻转或移动设备即可触发 M5PM1 唤醒,ESP32-C61 重新上电。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
#include <M5Unified.h>
#include <M5PM1.h>
#include <Wire.h>
#include "SparkFun_BMI270_Arduino_Library.h"

BMI270 imu;
M5PM1 pm1;

void setup(void)
{
    M5.begin();
    Serial.begin(115200);

    Wire.setPins(M5.getPin(m5::pin_name_t::in_i2c_sda), M5.getPin(m5::pin_name_t::in_i2c_scl));

    // Initialize PM1
    m5pm1_err_t err = pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);

    if (err == M5PM1_OK) {
        Serial.println("PM1 initialization successful");
        pm1.gpioSetWakeEnable(M5PM1_GPIO_NUM_4, true);
        pm1.gpioSetWakeEdge(M5PM1_GPIO_NUM_4, M5PM1_GPIO_WAKE_FALLING);  // Falling edge
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", err);
    }

    // Check if sensor is connected and initialize
    // Address defaults to 0x68
    while(imu.beginI2C(BMI2_I2C_PRIM_ADDR, Wire) != BMI2_OK)
    {
        Serial.println("Error: BMI270 not connected, check wiring and I2C address!");
        delay(1000);
    }
    imu.disableFeature(BMI2_ANY_MOTION);
    Serial.println("BMI270 initialization successful");

    M5.Display.setColorDepth(8);
    M5.Display.setRotation(1);
    M5.Display.setBrightness(128);
    M5.Display.fillScreen(TFT_BLACK);
    M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
    M5.Display.fillRect(7, 7, 2, 2, TFT_WHITE);
    M5.Display.display();
}

void loop(void)
{
    M5.update();
    if (M5.BtnA.wasPressed()) {
        int8_t ret = imu.enableFeature(BMI2_ANY_MOTION);
        // Optional
        // bmi2_sens_config config;
        // config.type = BMI2_ANY_MOTION;
        // config.cfg.any_motion.threshold = 0xA0;  // 1LSB equals to 0.48mg. Default is 83mg. Lower is more sensitive
        // config.cfg.any_motion.duration = 0x0A;   // 1LSB equals 20ms. Default is 100ms.
        // ret |= imu.setConfig(config);
        //
        bmi2_int_pin_config intPinConfig;
        intPinConfig.pin_type = BMI2_INT1;
        intPinConfig.int_latch = BMI2_INT_NON_LATCH;
        intPinConfig.pin_cfg[0].lvl = BMI2_INT_ACTIVE_LOW;
        intPinConfig.pin_cfg[0].od = BMI2_INT_PUSH_PULL;
        intPinConfig.pin_cfg[0].output_en = BMI2_INT_OUTPUT_ENABLE;
        intPinConfig.pin_cfg[0].input_en = BMI2_INT_INPUT_DISABLE;
        ret |= imu.setInterruptPinConfig(intPinConfig);
        ret |= imu.mapInterruptToPin(BMI2_ANY_MOTION_INT, BMI2_INT1);
        if (!ret) {
            Serial.println("BMI270 AnyMotionInterrupt enabled successfully");
        } else {
            Serial.println("Failed to enable BMI270 AnyMotionInterrupt");
        }

        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(5, 5, 6, 6, TFT_WHITE);
        M5.Display.display();
        delay(1000);
        // Shutdown
        pm1.setLdoEnable(true);
        pm1.ldoSetPowerHold(true);
        pm1.setLedEnLevel(true);
        pm1.shutdown();
    }
}

例程效果演示:

IMU 唤醒 ESP32-C61

M5PM1 的 G3 IRQ 引脚在电路上连接到了 ESP32-C61 的 G2 上,可以通过链式的唤醒信号实现 ESP32-C61 唤醒,实现步骤如下:

  1. 配置 M5PM1 G4 为输入模式,该引脚连接 BMI270 的 INT1 输出信号。
  2. 配置 M5PM1 G3 为 IRQ 输出信号引脚。当 G4 (BMI270 产生中断信号) 引脚状态变化时,G3 IRQ 引脚将输出中断信号。
  3. 将 ESP32-C61 的 G2 配置为下降沿中断输入,用来检测 M5PM1 G3 IRQ;实际深度休眠代码在示例中保持注释。
  4. 翻转或移动设备:触发 BMI270 唤醒信号 -> 触发 M5PM1 G3 IRQ -> 触发 ESP32-C61 G2 中断处理函数,CoreMatrix 点阵图案随之变化。

案例说明:设备开机后,单击按键 A(KEY1)配置 IMU 中断模式并注册 ESP32-C61 G2 下降沿中断。翻转或移动设备即可触发 GPIO 中断处理函数,点阵图案由待触发状态变为已触发状态。再次单击按键 A 可清除 M5PM1 IRQ 标志位并重新测试;若启用代码中注释的深度休眠部分,运动后会唤醒 ESP32-C61 并从 setup() 重新运行。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
#include <M5Unified.h>
#include <M5PM1.h>
#include <Wire.h>
#include "SparkFun_BMI270_Arduino_Library.h"
#include "driver/rtc_io.h"

BMI270 imu;
M5PM1 pm1;

void setup(void)
{
    M5.begin();
    Serial.begin(115200);

    Wire.setPins(M5.getPin(m5::pin_name_t::in_i2c_sda), M5.getPin(m5::pin_name_t::in_i2c_scl));

    // Initialize PM1
    m5pm1_err_t err = pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);

    if (err == M5PM1_OK) {
        Serial.println("PM1 initialization successful");
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();

        pm1.irqSetGpioMaskAll(M5PM1_IRQ_MASK_ENABLE);
        pm1.irqSetSysMaskAll(M5PM1_IRQ_MASK_ENABLE);
        pm1.irqSetBtnMaskAll(M5PM1_IRQ_MASK_ENABLE);

        pm1.irqSetGpioMask(M5PM1_IRQ_GPIO4, M5PM1_IRQ_MASK_DISABLE);
        pm1.gpioSetMode(M5PM1_GPIO_NUM_4, M5PM1_GPIO_MODE_INPUT);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_4, M5PM1_GPIO_PULL_UP);

        pm1.gpioSetMode(M5PM1_GPIO_NUM_3, M5PM1_GPIO_MODE_OUTPUT);
        pm1.gpioSetDrive(M5PM1_GPIO_NUM_3, M5PM1_GPIO_DRIVE_PUSHPULL);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_3, M5PM1_GPIO_PULL_UP);
        pm1.gpioSetOutput(M5PM1_GPIO_NUM_3, true);
        pm1.gpioSetFunc(M5PM1_GPIO_NUM_3, M5PM1_GPIO_FUNC_IRQ);
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", err);
    }

    // Check if sensor is connected and initialize
    // Address defaults to 0x68
    while(imu.beginI2C(BMI2_I2C_PRIM_ADDR, Wire) != BMI2_OK)
    {
        Serial.println("Error: BMI270 not connected, check wiring and I2C address!");
        delay(1000);
    }
    imu.disableFeature(BMI2_ANY_MOTION);
    Serial.println("BMI270 initialization successful");

    M5.Display.setColorDepth(8);
    M5.Display.setRotation(1);
    M5.Display.setBrightness(128);
    M5.Display.fillScreen(TFT_BLACK);
    M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
    M5.Display.fillRect(7, 7, 2, 2, TFT_WHITE);
    M5.Display.display();
}

volatile bool pm1IrqTriggered = false;

void ARDUINO_ISR_ATTR pm1_irq_handler()
{
    pm1IrqTriggered = true;
}

void loop(void)
{
    M5.update();

    if (pm1IrqTriggered) {
        pm1IrqTriggered = false;

        uint16_t status = 0;
        imu.getInterruptStatus(&status);
        Serial.printf("BMI270 interrupt status: 0x%04X\n", status);

        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(4, 4, 8, 8, TFT_WHITE);
        M5.Display.display();
    }

    if (M5.BtnA.wasPressed()) {
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();
        int8_t ret = imu.enableFeature(BMI2_ANY_MOTION);
        // Optional
        // bmi2_sens_config config;
        // config.type = BMI2_ANY_MOTION;
        // ret |= imu.getConfig(&config);
        // config.cfg.any_motion.threshold = 0xE0;  // 1LSB equals to 0.48mg. Default is 83mg. Lower is more sensitive
        // config.cfg.any_motion.duration = 0x0A;   // 1LSB equals 20ms. Default is 100ms.
        // ret |= imu.setConfig(config);

        bmi2_int_pin_config intPinConfig;
        intPinConfig.pin_type = BMI2_INT1;
        intPinConfig.int_latch = BMI2_INT_NON_LATCH;
        intPinConfig.pin_cfg[0].lvl = BMI2_INT_ACTIVE_LOW;  // Active low
        intPinConfig.pin_cfg[0].od = BMI2_INT_PUSH_PULL;
        intPinConfig.pin_cfg[0].output_en = BMI2_INT_OUTPUT_ENABLE;
        intPinConfig.pin_cfg[0].input_en = BMI2_INT_INPUT_DISABLE;
        ret |= imu.setInterruptPinConfig(intPinConfig);
        ret |= imu.mapInterruptToPin(BMI2_ANY_MOTION_INT, BMI2_INT1);
        if (!ret) {
            Serial.println("BMI270 AnyMotionInterrupt enabled successfully");
        } else {
            Serial.println("Failed to enable BMI270 AnyMotionInterrupt");
        }

        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(5, 5, 6, 6, TFT_WHITE);
        M5.Display.display();

        // Choose either of the two pieces of code below.
        pinMode(GPIO_NUM_2, INPUT_PULLUP);
        attachInterrupt(GPIO_NUM_2, pm1_irq_handler, FALLING);

        // esp_sleep_enable_ext1_wakeup(1ULL << GPIO_NUM_2, ESP_EXT1_WAKEUP_ANY_LOW);
        // rtc_gpio_pullup_en(GPIO_NUM_2);
        // Serial.println("Going to sleep now");
        // esp_deep_sleep_start();
    }
}

例程效果演示:

5. KEY 唤醒

KEY M5PM1 唤醒

电源切换至 L1 Mode,CoreMatrix 的 KEY1~KEY3 按键上拉和 M5PM1 处于供电状态。在配置 KEY3 唤醒功能后,M5PM1 进入休眠状态,同时保持 L1 的输出供电 (3V3_L1_EN),用于维持按键上拉和唤醒逻辑工作。

此时可通过按下 KEY3,触发 M5PM1 G2 下降沿唤醒信号,唤醒 M5PM1 重新启动。

M5PM1 唤醒后将重新运行 L0、L1 和 L3A 上电流程。ESP32-C61 重新执行初始化。

案例说明:设备开机后,单击按键 B(KEY2)配置 KEY3 唤醒与 M5PM1 L1 电源保持,M5PM1 进入休眠。此时按下 KEY3 即可触发 M5PM1 唤醒,ESP32-C61 重新上电。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
#include <M5PM1.h>
#include <M5Unified.h>

M5PM1 pm1;

void setup(void)
{
    M5.begin();
    Serial.begin(115200);

    if (pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K) != M5PM1_OK) {
        Serial.println("PM1 initialization failed");
        while (true) {
            delay(1000);
        }
    }

    Serial.println("PM1 initialization successful");
    M5.Display.setColorDepth(8);
    M5.Display.setRotation(1);
    M5.Display.setBrightness(128);
    M5.Display.fillScreen(TFT_BLACK);
    M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
    M5.Display.fillRect(7, 7, 2, 2, TFT_WHITE);
    M5.Display.display();

    pm1.gpioSetWakeEdge(M5PM1_GPIO_NUM_2, M5PM1_GPIO_WAKE_FALLING);
    pm1.gpioSetWakeEnable(M5PM1_GPIO_NUM_2, true);
    pm1.setLdoEnable(true);
    pm1.ldoSetPowerHold(true);
}

void loop(void)
{
    M5.update();
    if (M5.BtnB.wasPressed()) {
        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(5, 5, 6, 6, TFT_WHITE);
        M5.Display.display();
        Serial.println("KEY3 wakeup enabled");
        delay(1000);
        // Shutdown
        pm1.setLedEnLevel(true);
        pm1.shutdown();
    }
}

例程效果演示:

KEY 唤醒 ESP32-C61

M5PM1 的 G3 IRQ 引脚在电路上连接到了 ESP32-C61 的 G2 上,可以通过链式的唤醒信号实现 ESP32-C61 唤醒,实现步骤如下:

  1. 配置 M5PM1 G2 为输入模式,该引脚连接 KEY3 按键。
  2. 配置 M5PM1 G3 为 IRQ 输出信号引脚。当 G2 (KEY3 被按下) 引脚状态变化时,G3 IRQ 引脚将输出中断信号。
  3. 将 ESP32-C61 的 G2 配置为下降沿中断输入,用来检测 M5PM1 G3 IRQ;实际深度休眠代码在示例中保持注释。
  4. 按下 KEY3:触发按键唤醒信号 -> 触发 M5PM1 G3 IRQ -> 触发 ESP32-C61 G2 中断处理函数,CoreMatrix 点阵图案随之变化。

案例说明:设备开机后,单击按键 B(KEY2)配置 KEY3 对应的 M5PM1 G2 中断并注册 ESP32-C61 G2 下降沿中断。按下 KEY3 即可触发 GPIO 中断处理函数,点阵图案由待触发状态变为已触发状态。再次单击按键 B 可清除 M5PM1 IRQ 标志位并重新测试;若启用代码中注释的深度休眠部分,按下 KEY3 后会唤醒 ESP32-C61 并从 setup() 重新运行。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
#include <M5Unified.h>
#include <M5PM1.h>
#include "driver/rtc_io.h"

M5PM1 pm1;

void setup(void)
{
    M5.begin();
    Serial.begin(115200);

    // Initialize PM1
    m5pm1_err_t err = pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);

    if (err == M5PM1_OK) {
        Serial.println("PM1 initialization successful");
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();

        pm1.irqSetGpioMaskAll(M5PM1_IRQ_MASK_ENABLE);
        pm1.irqSetSysMaskAll(M5PM1_IRQ_MASK_ENABLE);
        pm1.irqSetBtnMaskAll(M5PM1_IRQ_MASK_ENABLE);

        pm1.irqSetGpioMask(M5PM1_IRQ_GPIO2, M5PM1_IRQ_MASK_DISABLE);
        pm1.gpioSetMode(M5PM1_GPIO_NUM_2, M5PM1_GPIO_MODE_INPUT);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_2, M5PM1_GPIO_PULL_UP);

        pm1.gpioSetMode(M5PM1_GPIO_NUM_3, M5PM1_GPIO_MODE_OUTPUT);
        pm1.gpioSetDrive(M5PM1_GPIO_NUM_3, M5PM1_GPIO_DRIVE_PUSHPULL);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_3, M5PM1_GPIO_PULL_UP);
        pm1.gpioSetOutput(M5PM1_GPIO_NUM_3, true);
        pm1.gpioSetFunc(M5PM1_GPIO_NUM_3, M5PM1_GPIO_FUNC_IRQ);
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", err);
    }

    M5.Display.setColorDepth(8);
    M5.Display.setRotation(1);
    M5.Display.setBrightness(128);
    M5.Display.fillScreen(TFT_BLACK);
    M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
    M5.Display.fillRect(7, 7, 2, 2, TFT_WHITE);
    M5.Display.display();
}

volatile bool pm1IrqTriggered = false;

void ARDUINO_ISR_ATTR pm1_irq_handler()
{
    pm1IrqTriggered = true;
}

void loop(void)
{
    M5.update();

    if (pm1IrqTriggered) {
        pm1IrqTriggered = false;
        Serial.println("PM1 IRQ triggered");

        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(4, 4, 8, 8, TFT_WHITE);
        M5.Display.display();
    }

    if (M5.BtnB.wasPressed()) {
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();
        Serial.println("KEY3 IRQ enabled successfully");

        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(5, 5, 6, 6, TFT_WHITE);
        M5.Display.display();

        // Choose either of the two pieces of code below.
        pinMode(GPIO_NUM_2, INPUT_PULLUP);
        attachInterrupt(GPIO_NUM_2, pm1_irq_handler, FALLING);

        // esp_sleep_enable_ext1_wakeup(1ULL << GPIO_NUM_2, ESP_EXT1_WAKEUP_ANY_LOW);
        // rtc_gpio_pullup_en(GPIO_NUM_2);
        // Serial.println("Going to sleep now");
        // esp_deep_sleep_start();
    }
}

例程效果演示:

6. M5IOE1 外设控制

CoreMatrix 的 M5IOE1 负责 L3B 级外设的电源使能和状态读取。通过 M5IOE1 API 可以独立控制 LED 点阵、microSD、蜂鸣器和 Grove 5V,并读取充电与插卡状态。

M5IOE1 引脚 CoreMatrix 功能
PYG1 microSD 电源使能
PYG2 蜂鸣器电源使能
PYG3 充电电流档位
PYG4 LED 点阵电源使能
PYG5 Grove 5V 输出使能
PYG8 充电状态输入
PYG10 microSD 检测输入
PYG11 蜂鸣器 PWM 输出

下面示例直接初始化 M5IOE1,开启 LED 点阵、microSD 和 Grove 5V 电源,关闭蜂鸣器电源,并读取充电状态与 microSD 检测状态。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
#include <M5Unified.h>
#include <M5IOE1.h>

M5IOE1 ioe1;

#define IOE_TF_EN     M5IOE1_PIN_1
#define IOE_BUZZER_EN M5IOE1_PIN_2
#define IOE_LEDS_EN   M5IOE1_PIN_4
#define IOE_GROVE_EN  M5IOE1_PIN_5
#define IOE_CHG_STAT  M5IOE1_PIN_8
#define IOE_TF_DET    M5IOE1_PIN_10

void setup()
{
  auto cfg = M5.config();
  cfg.serial_baudrate = 115200;
  M5.begin(cfg);

  M5.Display.setColorDepth(8);
  M5.Display.setRotation(1);
  M5.Display.setBrightness(128);

  const m5ioe1_err_t err = ioe1.begin(&M5.In_I2C, M5IOE1_DEFAULT_ADDR_2, M5IOE1_I2C_FREQ_100K);
  if (err != M5IOE1_OK) {
    Serial.printf("M5IOE1 init failed: %d\n", err);
    while (true) delay(1000);
  }

  ioe1.pinMode(IOE_TF_EN, OUTPUT);
  ioe1.pinMode(IOE_BUZZER_EN, OUTPUT);
  ioe1.pinMode(IOE_LEDS_EN, OUTPUT);
  ioe1.pinMode(IOE_GROVE_EN, OUTPUT);
  ioe1.pinMode(IOE_CHG_STAT, INPUT);
  ioe1.pinMode(IOE_TF_DET, INPUT_PULLUP);

  ioe1.digitalWrite(IOE_TF_EN, HIGH);
  ioe1.digitalWrite(IOE_LEDS_EN, HIGH);
  ioe1.digitalWrite(IOE_GROVE_EN, HIGH);
  ioe1.digitalWrite(IOE_BUZZER_EN, LOW);

  Serial.println("CoreMatrix L3B rails enabled");
}

void loop()
{
  M5.update();

  const int chargeState = ioe1.digitalRead(IOE_CHG_STAT);
  const int tfState = ioe1.digitalRead(IOE_TF_DET);

  Serial.printf("CHG_STAT: %d, TF_DET: %d\n", chargeState, tfState);
  delay(1000);
}

成功烧录后,程序会每秒通过串口输出 CHG_STAT 和 TF_DET 状态。CHG_STAT 为低电平时表示正在充电,TF_DET 为低电平时表示已插入 microSD 卡。

Page Tools
PDF
On This Page