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

Arduino 上手教程

2. 设备开发 & 案例程序

5. 扩展模块

6. 应用案例

ToughC5 M5PM1 & M5IOE1 电源管理

1. 多级电源开关设计

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

M5PM1 / M5IOE1 驱动库

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

注意事项
ToughC5 的电源分级根据供电范围与主控运行状态进行区分,并非各级电源依次串联。L0 对应 RTC 的纽扣电池供电,L1 对应整机电池供电及 M5PM1 电源管理,L2 / L3A 对应主控及相关外设供电,两者通过 ESP32-C5 的深度休眠或工作状态区分。L3B 外设通过各自的控制引脚独立使能,不要求同时开启。

L0

此级电源条件下,RTC 由纽扣电池保持供电,用于维持时钟运行。纽扣电池电量未耗尽的情况下,该层电源将一直保持。

L1

此级电源条件下,整机电池接入,M5PM1 保持供电,用于管理整机电源,后续层级的主控及外设可按需开启。

L2 / L3A

此级电源条件下,将新启用 ESP32-C5、M5IOE1 拓展芯片、GROVE 输入 / 输出接口及触摸屏相关供电,M5PM1 和 RTC 的供电继续保持。该层级使用 M5PM1 的 DCDC 输出 3V3_L2 为 ESP32-C5 和 M5IOE1 供电,使用 LDO 输出 3V3_L2_LDO 为触摸屏供电,可通过以下 API 实现这两路电源的开关控制。

pm1.setDcdcEnable(true); // L2 ON
pm1.setDcdcEnable(false); // L2 OFF
pm1.setLdoEnable(true); // L2 LDO ON
pm1.setLdoEnable(false); // L2 LDO OFF

当 ESP32-C5 处于深度休眠状态时,电源处于 L2 级,ESP32-C5、M5IOE1 和触摸屏保持供电,M5BUS 3V3 尚未启用。当 ESP32-C5 处于工作状态时,电源处于 L3A 级,,M5BUS 3V3 启用。两者属于相同的供电层级,通过主控运行状态区分。ESP32-C5 可通过控制 M5PM1 进入休眠,关断自身的供电 (L2 -> L1/L0)。

L3B

L3B 层级控制 LCD、蜂鸣器、Grove 等外设的供电或相关配置,该层级基于 M5IOE1 IO 拓展芯片实现控制,允许独立控制该层级的一些外设。

M5IOE1 PYG1 PYG3 PYG4_ADC2 PYG5_ADC3 PYG6 PYG10_PWM4 PYG14
Charger PYB_CHG_PROG PYB_CHG_STAT
LCD PYB_LCD_RST PYB_LCD_EN
LCD BL PYB_BL_EN
microSD PYB_TF_EN PYB_TF_DET
  • PYB_CHG_PROG: 充电电流档位控制
  • PYB_CHG_STAT: 充电状态输入,低电平表示充电
  • PYB_LCD_RST: LCD 复位
  • PYB_LCD_EN: LCD 供电
  • PYB_BL_EN: LCD 背光使能
  • PYB_TF_EN: microSD 供电
  • PYB_TF_DET: microSD 检测,低电平表示检测到卡

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

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

2. M5PM1 休眠

手动休眠

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

pm1.shutdown();

在需要保留触摸唤醒等低功耗电路的使用场景中,应在 M5PM1 进入休眠模式前配置对应的电源保持状态和唤醒源。ToughC5 没有实体按键,休眠后的唤醒操作通过触摸屏完成,具体配置请参考下方的 M5PM1 唤醒小节。

I2C 空闲休眠

M5PM1 支持配置 I2C 空闲通信自动休眠状态,来降低整机功耗。进入休眠状态后,ESP32-C5 与 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;

定时开机 / 关机案例

案例说明:通过触摸屏幕左右两侧配置定时器。触摸左侧设置 10s 后重新开机并立即关机,触摸右侧设置 10s 后关机(可通过单击电源按键重新开机)。

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;

void setup(void)
{
    auto cfg = M5.config();
    cfg.clear_display = false;
    M5.begin(cfg);
    Serial.begin(115200);
    M5.Display.setRotation(1);
    M5.Display.setTextDatum(middle_center);
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);
    M5.Display.setFont(&fonts::FreeSansBold12pt7b);
    M5.Display.clear(TFT_WHITE);
    M5.Display.drawString("Timer Power Test", M5.Display.width() / 2, 30);
    M5.Display.setFont(&fonts::FreeSansBold9pt7b);
    M5.Display.drawString("Touch left: Power ON after 10s", M5.Display.width() / 2, 90);
    M5.Display.drawString("Touch right: Power OFF after 10s", M5.Display.width() / 2, 140);

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

void loop(void)
{
    M5.update();
    if (!M5.Touch.getCount()) {
        return;
    }

    const auto touch = M5.Touch.getDetail(0);
    if (!touch.wasClicked()) {
        return;
    }

    M5.Display.clear(TFT_WHITE);
    M5.Display.setTextDatum(middle_center);
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);
    M5.Display.setFont(&fonts::FreeSansBold12pt7b);
    if (touch.x < M5.Display.width() / 2) {
        M5.Display.drawString("Power ON after 10s", M5.Display.width() / 2, 120);
        Serial.println("Touch left: power on timer set for 10 seconds");
        const m5pm1_err_t timerErr = pm1.timerSet(10, M5PM1_TIM_ACTION_POWERON);
        Serial.printf("Power on timer result: %d\n", timerErr);
        delay(200);
        Serial.println("Requesting shutdown");
        pm1.shutdown();
    } else {
        M5.Display.drawString("Power OFF after 10s", M5.Display.width() / 2, 120);
        Serial.println("Touch right: power off timer set for 10 seconds");
        const m5pm1_err_t timerErr = pm1.timerSet(10, M5PM1_TIM_ACTION_POWEROFF);
        Serial.printf("Power off timer result: %d\n", timerErr);
    }

    delay(20);
}

4. RTC 唤醒

RTC M5PM1 唤醒

电源切换至 L1 Mode,ToughC5 的 RTC 和 M5PM1 处于供电状态。当配置 RTC 定时唤醒功能后,M5PM1 进入休眠状态,同时保持 RTC 所需的 L1 电源,用于维持 RTC 工作。

此时可通过配置 RTC 定时器唤醒,RTC 的 PM_RTC_INT 中断连接到 M5PM1 GPIO3,触发 M5PM1 唤醒,ESP32-C5 重新上电。

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

案例说明:设备开机后,触摸屏幕一次配置 RTC 定时器唤醒,M5PM1 进入休眠。5s 后触发 RTC 唤醒,M5PM1 恢复供电,ESP32-C5 重新上电。

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
#include <M5Unified.h>
#include <M5PM1.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_400K);

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

    M5.Display.setTextDatum(middle_center);
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);
    M5.Display.setFont(&fonts::FreeSansBold12pt7b);
    M5.Display.clear(TFT_WHITE);
    M5.Display.drawString("Touch to set RTC wakeup", M5.Display.width() / 2, 80);
    M5.Display.setFont(&fonts::FreeSansBold9pt7b);
    M5.Display.drawString("Wake up after 5 seconds", M5.Display.width() / 2, 130);
}

void loop(void)
{
    M5.update();
    if (M5.Touch.getCount() && M5.Touch.getDetail(0).wasClicked()) {
        M5.Rtc.clearIRQ();
        if (!M5.Rtc.setTimerIRQ(5000)) {
            M5.Display.clear(TFT_WHITE);
            M5.Display.drawString("RTC timer failed", M5.Display.width() / 2, 120);
            return;
        }
        Serial.println("RTC IRQ enabled successfully");
        M5.Display.clear(TFT_WHITE);
        M5.Display.drawString("5s later wakeup", M5.Display.width() / 2, 120);
        pm1.setLdoEnable(true);
        pm1.ldoSetPowerHold(true);
        delay(50);
        pm1.shutdown();
    }
}

触摸屏幕后,程序会清除 RTC 中断并设置 5 秒定时器,同时保持 M5PM1 的 L1 供电后关闭整机。定时器到期时,RTC 通过 PM_RTC_INT 触发 M5PM1 GPIO3 唤醒,M5PM1 恢复供电,ESP32-C5 重新执行初始化。

RTC 唤醒 ESP32-C5

RTC 唤醒 ESP32-C5 的配置步骤与 RTC 唤醒 M5PM1 类似,区别在于 RTC 中断不直接用于重新上电,而是依赖 RTC IRQ → M5PM1 GPIO3 → M5PM1 GPIO1 IRQout → ESP32-C5 G4 这条链路,实现步骤如下:

  1. 配置 M5PM1 GPIO3 为输入模式,该引脚连接 RTC 的 PM_RTC_INT 中断信号。
  2. 配置 M5PM1 GPIO1 为 IRQout 输出信号引脚。当 GPIO3 (RTC 产生中断信号) 引脚状态变化时,GPIO1 IRQout 引脚将输出中断信号。
  3. 配置 ESP32-C5 进入休眠,配置唤醒引脚为 G4。
  4. RTC 定时到期:触发 RTC 唤醒信号 -> 触发 M5PM1 GPIO1 IRQout -> 唤醒 ESP32-C5。

案例说明:设备开机后,触摸屏幕配置 5 秒 RTC 定时器。定时到期即可触发 GPIO 中断处理函数。若启用代码中注释的深度休眠部分,RTC 定时到期后会唤醒 ESP32-C5,并从 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
#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_400K);

    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_GPIO3, M5PM1_IRQ_MASK_DISABLE);
        pm1.gpioSetMode(M5PM1_GPIO_NUM_3, M5PM1_GPIO_MODE_INPUT);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_3, M5PM1_GPIO_PULL_UP);

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

    M5.Display.setTextDatum(middle_center);
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);
    M5.Display.setFont(&fonts::FreeSansBold12pt7b);
    M5.Display.clear(TFT_WHITE);
    M5.Display.drawString("Touch to set RTC IRQ", M5.Display.width() / 2, 80);
    M5.Rtc.clearIRQ();
}

volatile bool pm1IrqTriggered = false;

void ARDUINO_ISR_ATTR pm1_irq_handler() {
    pm1IrqTriggered = true;
}

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

    if (pm1IrqTriggered) {
        pm1IrqTriggered = false;
        M5.Rtc.clearIRQ();
        M5.Rtc.setTimerIRQ(0);
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();
        Serial.println("PM1 IRQ triggered");
        M5.Display.drawString("PM1 IRQ triggered", M5.Display.width() / 2, 130);
    }

    if (M5.Touch.getCount() && M5.Touch.getDetail(0).wasClicked()) {
        if (!M5.Rtc.setTimerIRQ(5000)) {
            M5.Display.clear(TFT_WHITE);
            M5.Display.drawString("RTC timer failed", M5.Display.width() / 2, 100);
            Serial.println("Failed to enable RTC IRQ");
            return;
        }
        Serial.println("RTC IRQ enabled successfully");
        M5.Display.clear(TFT_WHITE);
        M5.Display.drawString("Wait for RTC IRQ", M5.Display.width() / 2, 80);
        // Choose either of the two pieces of code below.
        pinMode(GPIO_NUM_4, INPUT_PULLUP);
        attachInterrupt(GPIO_NUM_4, pm1_irq_handler, FALLING);

        // esp_sleep_enable_ext0_wakeup(GPIO_NUM_4, 0);  // 0 = Low
        // rtc_gpio_pullup_en(GPIO_NUM_4);
        // Serial.println("Going to sleep now");
        // esp_deep_sleep_start();
    }
}

RTC 中断触发后,M5PM1 GPIO1 输出 IRQ 信号,ESP32-C5 G4 检测到低电平并从深度休眠中唤醒。唤醒后 ESP32-C5 会复位,程序从 setup() 开始执行。

5. TP 唤醒

TP M5PM1 唤醒

电源切换至 L2 / L3A Mode,ToughC5 的触摸电路和 M5PM1 处于供电状态。当配置触摸唤醒功能后,M5PM1 进入休眠状态,同时保持 3V3_L2_LDO 触摸电源,用于维持触摸检测。

此时可通过触摸屏幕产生唤醒信号,触摸中断 PM_TP_INT 连接到 M5PM1 GPIO0,触发 M5PM1 唤醒,ESP32-C5 重新上电。

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

案例说明:设备开机后,触摸屏幕一次配置触摸唤醒和 M5PM1 LDO 电源保持,M5PM1 进入休眠。再次触摸屏幕即可触发 TP 唤醒,M5PM1 恢复供电,ESP32-C5 重新上电。

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
#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_400K) != M5PM1_OK) {
        Serial.println("PM1 initialization failed");
        while (true) {
            delay(1000);
        }
    }

    Serial.println("PM1 initialization successful");
    M5.Display.setTextDatum(middle_center);
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);
    M5.Display.setFont(&fonts::FreeSansBold12pt7b);
    M5.Display.clear(TFT_WHITE);
    M5.Display.drawString("Touch to sleep", M5.Display.width() / 2, 120);

    pm1.gpioSetFunc(M5PM1_GPIO_NUM_0, M5PM1_GPIO_FUNC_WAKE);
    pm1.gpioSetWakeEdge(M5PM1_GPIO_NUM_0, M5PM1_GPIO_WAKE_FALLING);
    pm1.gpioSetWakeEnable(M5PM1_GPIO_NUM_0, true);
    pm1.setLdoEnable(true);
    pm1.ldoSetPowerHold(true);
}

void loop(void)
{
    M5.update();
    if (M5.Touch.getCount() && M5.Touch.getDetail(0).wasClicked()) {
        M5.Display.clear(TFT_WHITE);
        M5.Display.drawString("Touch wakeup enabled", M5.Display.width() / 2, 120);
        delay(200);
        pm1.shutdown();
    }
}

程序启动时配置 M5PM1 GPIO0 的唤醒功能并保持 3V3_L2_LDO 触摸电源,触摸屏幕后关闭整机。再次触摸屏幕时,PM_TP_INT 通过 M5PM1 GPIO0 触发唤醒,M5PM1 恢复供电,ESP32-C5 重新执行初始化。例程只演示 M5PM1 的唤醒配置,不负责读取触摸坐标。

TP 唤醒 ESP32-C5

TP 唤醒 ESP32-C5 的配置步骤与 RTC 唤醒 ESP32-C5 类似,区别在于唤醒源为触摸中断 PM_TP_INT,并依赖 TP IRQ → M5PM1 GPIO0 → M5PM1 GPIO1 IRQout → ESP32-C5 G4 这条链路。

案例说明:设备开机后,触摸屏幕一次配置 TP 中断并注册 G4 中断处理函数。再次触摸屏幕即可触发 GPIO 中断处理函数。若启用代码中注释的深度休眠部分,触摸屏幕后会唤醒 ESP32-C5,并从 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
#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_400K);

    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_GPIO0, M5PM1_IRQ_MASK_DISABLE);
        pm1.gpioSetMode(M5PM1_GPIO_NUM_0, M5PM1_GPIO_MODE_INPUT);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_0, M5PM1_GPIO_PULL_UP);

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

    M5.Display.setTextDatum(middle_center);
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);
    M5.Display.setFont(&fonts::FreeSansBold12pt7b);
    M5.Display.clear(TFT_WHITE);
    M5.Display.drawString("Touch to set TP IRQ", M5.Display.width() / 2, 80);
}

volatile bool pm1IrqTriggered = false;

void ARDUINO_ISR_ATTR pm1_irq_handler() {
    pm1IrqTriggered = true;
}

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

    if (pm1IrqTriggered) {
        pm1IrqTriggered = false;
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();
        Serial.println("PM1 IRQ triggered");
        M5.Display.drawString("PM1 IRQ triggered", M5.Display.width() / 2, 130);
    }

    if (M5.Touch.getCount() && M5.Touch.getDetail(0).wasClicked()) {
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();
        Serial.println("TP IRQ enabled successfully");
        M5.Display.clear(TFT_WHITE);
        M5.Display.drawString("Wait for TP IRQ", M5.Display.width() / 2, 80);
        // Choose either of the two pieces of code below.
        pinMode(GPIO_NUM_4, INPUT_PULLUP);
        attachInterrupt(GPIO_NUM_4, pm1_irq_handler, FALLING);

        // esp_sleep_enable_ext0_wakeup(GPIO_NUM_4, 0);  // 0 = Low
        // rtc_gpio_pullup_en(GPIO_NUM_4);
        // Serial.println("Going to sleep now");
        // esp_deep_sleep_start();
    }
}

触摸屏幕后,程序清除 M5PM1 中断标志并在 ESP32-C5 G4 注册下降沿中断。再次触摸屏幕时,TP 中断经 M5PM1 GPIO0 转发至 GPIO1 IRQ 输出,触发 G4 中断,程序在屏幕和串口显示触发结果。

6. M5IOE1 外设控制

ToughC5 的 M5IOE1 负责 L3B 级外设的部分供电及相关配置,如 LCD、背光和 microSD 等。通过 M5IOE1 的 API,可以独立控制这些外设,实现更灵活的电源管理。

ToughC5 的 M5IOE1 主要引脚如下:

M5IOE1 引脚 ToughC5 功能
M5IOE1_PIN_3 充电状态输入
M5IOE1_PIN_4 LCD 复位
M5IOE1_PIN_5 LCD 电源使能
M5IOE1_PIN_6 microSD 电源使能
M5IOE1_PIN_10 LCD 背光使能
M5IOE1_PIN_14 microSD 检测输入

下面示例直接初始化 M5IOE1,依次演示 LCD 复位、LCD 电源、背光和 microSD 电源控制,并读取充电状态与 TF 卡检测状态。PYB_TF_DET 为低有效,读取到低电平表示检测到 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 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
#include <Arduino.h>
#include <Wire.h>
#include <M5IOE1.h>

M5IOE1 ioe1;

#define IOE_CHG_STAT M5IOE1_PIN_3
#define IOE_LCD_RST  M5IOE1_PIN_4
#define IOE_LCD_EN   M5IOE1_PIN_5
#define IOE_TF_EN    M5IOE1_PIN_6
#define IOE_BL_EN    M5IOE1_PIN_10
#define IOE_TF_DET   M5IOE1_PIN_14

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

    // Initialize IOE1.
    const m5ioe1_err_t ioe1_err =
        ioe1.begin(&Wire, M5IOE1_DEFAULT_ADDR_2, LP_SDA, LP_SCL,
                   M5IOE1_I2C_FREQ_100K);
    if (ioe1_err == M5IOE1_OK) {
        Serial.println("IOE1 initialization successful");
    } else {
        Serial.printf("IOE1 initialization failed, error code: %d\n", ioe1_err);
        while (true) delay(1000);
    }

    ioe1.pinMode(IOE_CHG_STAT, INPUT);
    ioe1.pinMode(IOE_LCD_RST, OUTPUT);
    ioe1.pinMode(IOE_LCD_EN, OUTPUT);
    ioe1.pinMode(IOE_TF_EN, OUTPUT);
    ioe1.pinMode(IOE_BL_EN, OUTPUT);
    ioe1.pinMode(IOE_TF_DET, INPUT_PULLUP);

    // Default states: LCD on, backlight on, and microSD power on.
    ioe1.digitalWrite(IOE_LCD_EN, HIGH);
    ioe1.digitalWrite(IOE_TF_EN, HIGH);
    ioe1.digitalWrite(IOE_BL_EN, HIGH);
    ioe1.digitalWrite(IOE_LCD_RST, LOW);
    delay(10);
    ioe1.digitalWrite(IOE_LCD_RST, HIGH);

    Serial.println("IOE1 peripheral control test begin");
}

void loop(void)
{
    // LCD reset.
    Serial.println("LCD reset LOW");
    ioe1.digitalWrite(IOE_LCD_RST, LOW);
    delay(100);
    Serial.println("LCD reset HIGH");
    ioe1.digitalWrite(IOE_LCD_RST, HIGH);
    delay(1000);

    // LCD and backlight power.
    Serial.println("LCD and backlight power OFF");
    ioe1.digitalWrite(IOE_BL_EN, LOW);
    ioe1.digitalWrite(IOE_LCD_EN, LOW);
    delay(1000);
    Serial.println("LCD and backlight power ON");
    ioe1.digitalWrite(IOE_LCD_EN, HIGH);
    ioe1.digitalWrite(IOE_BL_EN, HIGH);
    delay(1000);

    // microSD power.
    Serial.println("microSD power OFF");
    ioe1.digitalWrite(IOE_TF_EN, LOW);
    delay(1000);
    Serial.println("microSD power ON");
    ioe1.digitalWrite(IOE_TF_EN, HIGH);
    delay(1000);

    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);
}

成功烧录后,程序会依次演示 LCD 复位、LCD 与背光供电、microSD 供电控制,并通过串口输出充电状态和 TF 卡检测状态。

Page Tools
PDF
On This Page