Skip to content

Instantly share code, notes, and snippets.

@MarsTechHAN
Created January 26, 2026 16:47
Show Gist options
  • Select an option

  • Save MarsTechHAN/ae1c02a4d60a63a84bb2ee4ac4fa2f1b to your computer and use it in GitHub Desktop.

Select an option

Save MarsTechHAN/ae1c02a4d60a63a84bb2ee4ac4fa2f1b to your computer and use it in GitHub Desktop.
#include <M5Unified.h>
#include <WiFi.h>
#include <esp_sleep.h>
#include <esp_pm.h>
#include <driver/rtc_io.h>
#include <sys/time.h>
#include "soc/esp32s3/rtc.h"
// Import M5PM1 from lib folder
#include "M5PM1.h"
// StickS3 Button GPIO definitions
#define BTN_A_GPIO GPIO_NUM_11
#define BTN_B_GPIO GPIO_NUM_12
// Create M5PM1 instance
M5PM1 pm1;
// Constants
const uint32_t WORK_CYCLE_DURATION = 15 * 60 * 1000; // 15 minutes
const uint32_t ALERT_BEFORE_END = 5 * 1000; // 5 seconds before end
const uint32_t SCREEN_ON_DURATION = 15 * 1000; // 15 seconds screen on
const uint32_t BLOCK_DURATION = 30 * 60 * 1000; // 30 minutes per block
const uint32_t REST_BLOCK_DURATION = 30 * 60 * 1000; // 30 minutes per rest triangle
// Colors - Professional and eye-friendly
const uint32_t COLOR_WORK_BG = 0x1B3A4B; // Deep blue for work
const uint32_t COLOR_WORK_TEXT = 0xE8F4F8; // Light cyan text
const uint32_t COLOR_WORK_ACCENT = 0x4FC3F7; // Bright blue accent
const uint32_t COLOR_REST_BG = 0x2E4D3D; // Deep green for rest
const uint32_t COLOR_REST_TEXT = 0xE8F5E9; // Light green text
const uint32_t COLOR_REST_ACCENT = 0x81C784; // Soft green accent
const uint32_t COLOR_BLOCK_EMPTY = 0x37474F; // Dark gray for empty blocks
const uint32_t COLOR_BLOCK_FILL = 0xFFB74D; // Warm orange for filled blocks
const uint32_t COLOR_TRIANGLE_EMPTY = 0x3D5A6B; // Dark teal for empty triangles
const uint32_t COLOR_TRIANGLE_FILL = 0x90CAF9; // Light blue for filled triangles
// State variables using RTC time
RTC_DATA_ATTR bool isWorkMode = true;
RTC_DATA_ATTR uint32_t totalWorkTime = 0; // Total work time in milliseconds
RTC_DATA_ATTR uint32_t totalRestTime = 0; // Total rest time in milliseconds
RTC_DATA_ATTR uint32_t savedCycleElapsed = 0; // Saved elapsed time before sleep
RTC_DATA_ATTR bool isCountdownPhase = false;
RTC_DATA_ATTR bool isTimeUp = false;
RTC_DATA_ATTR bool screenWasOn = false;
RTC_DATA_ATTR uint64_t rtcSleepStartUs = 0;
uint32_t wakeupTimeMs = 0; // Millis when device woke up
uint32_t cycleStartMillis = 0; // Millis when cycle started (relative to wakeup)
uint32_t screenOnMillis = 0; // Millis when screen turned on
bool needConfirmation = false;
bool buttonPressed = false;
bool awake = false;
uint32_t timeUpStartMs = 0;
uint32_t lastInvertToggleMs = 0;
bool invertDisplayOn = false;
// Sprite for double buffering
LGFX_Sprite canvas(&M5.Display);
// Battery monitoring
float batteryVoltage = 0.0;
int batteryPercent = 0;
uint32_t lastBatteryUpdate = 0;
const uint32_t BATTERY_UPDATE_INTERVAL = 5000; // Update every 5 seconds
// Get current elapsed time for cycle (accounts for sleep)
uint32_t getCurrentCycleElapsed() {
if (isTimeUp) {
return WORK_CYCLE_DURATION;
}
return savedCycleElapsed + (millis() - cycleStartMillis);
}
// Update battery status
void updateBatteryStatus() {
uint16_t voltage_mv = 0;
m5pm1_err_t result = pm1.readVbat(&voltage_mv);
if (result == M5PM1_OK && voltage_mv > 0) {
batteryVoltage = voltage_mv / 1000.0; // Convert to volts
// Calculate battery percentage based on typical Li-ion voltage curve
// 4.2V = 100%, 3.7V = 50%, 3.0V = 0%
if (batteryVoltage >= 4.2) {
batteryPercent = 100;
} else if (batteryVoltage >= 3.7) {
batteryPercent = 50 + (int)((batteryVoltage - 3.7) / 0.5 * 50);
} else if (batteryVoltage >= 3.0) {
batteryPercent = (int)((batteryVoltage - 3.0) / 0.7 * 50);
} else {
batteryPercent = 0;
}
// Clamp to 0-100 range
if (batteryPercent > 100) batteryPercent = 100;
if (batteryPercent < 0) batteryPercent = 0;
} else {
// Failed to read, try to use M5.Power if available
batteryVoltage = M5.Power.getBatteryVoltage() / 1000.0;
if (batteryVoltage > 0) {
// Calculate percentage based on voltage
if (batteryVoltage >= 4.2) {
batteryPercent = 100;
} else if (batteryVoltage >= 3.7) {
batteryPercent = 50 + (int)((batteryVoltage - 3.7) / 0.5 * 50);
} else if (batteryVoltage >= 3.0) {
batteryPercent = (int)((batteryVoltage - 3.0) / 0.7 * 50);
} else {
batteryPercent = 0;
}
if (batteryPercent > 100) batteryPercent = 100;
if (batteryPercent < 0) batteryPercent = 0;
}
}
}
// Draw battery icon in bottom right corner
void drawBatteryIcon(LGFX_Sprite* spr) {
const int iconWidth = 16;
const int iconHeight = 8;
const int tipWidth = 2;
const int tipHeight = 4;
const int margin = 2;
// Calculate position for bottom right (text first, then icon)
spr->setFont(&fonts::Font0);
spr->setTextSize(1);
char batteryText[16];
sprintf(batteryText, "%.2fV", batteryVoltage);
int textWidth = spr->textWidth(batteryText);
int totalWidth = iconWidth + tipWidth + 3 + textWidth; // icon + gap + text
int iconX = spr->width() - totalWidth - margin;
int iconY = spr->height() - iconHeight - margin;
// Battery body outline
spr->drawRect(iconX, iconY, iconWidth, iconHeight, COLOR_WORK_TEXT);
// Battery tip
spr->fillRect(iconX + iconWidth, iconY + (iconHeight - tipHeight) / 2, tipWidth, tipHeight, COLOR_WORK_TEXT);
// Battery fill based on percentage
uint32_t fillColor;
if (batteryPercent > 50) {
fillColor = 0x4CAF50; // Green
} else if (batteryPercent > 20) {
fillColor = 0xFFC107; // Amber
} else {
fillColor = 0xF44336; // Red
}
int fillWidth = (iconWidth - 4) * batteryPercent / 100;
if (fillWidth > 0) {
spr->fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4, fillColor);
}
// Draw voltage text to the right of icon
spr->setTextDatum(bottom_right);
spr->setTextColor(COLOR_WORK_TEXT);
spr->drawString(batteryText, spr->width() - margin, spr->height() - margin);
}
void disablePeripherals() {
// Disable WiFi and Bluetooth
WiFi.mode(WIFI_OFF);
btStop();
// Disable microphone and speaker
M5.Mic.end();
M5.Speaker.end();
// Set low power mode
setCpuFrequencyMhz(80); // Lower CPU frequency
// Configure M5PM1 power management for ultra low power
// Based on PMIC pinout:
// - PYG3_SPK_Pulse (pin 13) -> G3 (GPIO3) PWM function
// - PY_STATUS_LED (pin 5) -> LED_EN_PP
// Turn off speaker by setting GPIO3 to LOW (SPK_Pulse)
// GPIO3 corresponds to M5PM1_GPIO_NUM_3
pm1.gpioSetMode(M5PM1_GPIO_NUM_3, M5PM1_GPIO_MODE_OUTPUT);
pm1.gpioSetOutput(M5PM1_GPIO_NUM_3, LOW);
// Turn off status LED by setting LED_EN to LOW
pm1.setLedEnLevel(false);
// Keep LDO 3.3V enabled (required for system)
pm1.setLdoEnable(true);
// Keep DCDC 5V enabled (required for display and system)
pm1.setDcdcEnable(true);
// Disable BOOST/GROVE port if not needed
pm1.setBoostEnable(false);
// Optional: Configure charge current for battery safety
// pm1.setChargeEnable(true); // Keep charging enabled
// Configure RTC to use high-frequency clock for better accuracy
// ESP32-S3 uses internal RC oscillator by default, which is calibrated
// The RTC will continue running during deep sleep
}
void drawProgressBlocks(LGFX_Sprite* spr, uint32_t totalTime) {
const int screenWidth = spr->width();
const int blockSize = 12; // Square blocks
const int blockSpacing = 2;
const int blockY = 3;
const int maxBlocks = (screenWidth - 10) / (blockSize + blockSpacing);
uint32_t completedBlocks = totalTime / BLOCK_DURATION;
uint32_t currentBlockProgress = (totalTime % BLOCK_DURATION) * 100 / BLOCK_DURATION;
int startX = (screenWidth - (maxBlocks * (blockSize + blockSpacing) - blockSpacing)) / 2;
int x = startX;
for (int i = 0; i < maxBlocks; i++) {
if (i < completedBlocks) {
// Fully filled block
spr->fillRoundRect(x, blockY, blockSize, blockSize, 2, COLOR_BLOCK_FILL);
} else if (i == completedBlocks) {
// Partially filled block
spr->drawRoundRect(x, blockY, blockSize, blockSize, 2, COLOR_BLOCK_EMPTY);
int fillWidth = (blockSize - 2) * currentBlockProgress / 100;
if (fillWidth > 0) {
spr->fillRoundRect(x + 1, blockY + 1, fillWidth, blockSize - 2, 1, COLOR_BLOCK_FILL);
}
} else {
// Empty block
spr->drawRoundRect(x, blockY, blockSize, blockSize, 2, COLOR_BLOCK_EMPTY);
}
x += blockSize + blockSpacing;
}
}
void drawRestTriangles(LGFX_Sprite* spr, uint32_t totalRestTime) {
const int screenWidth = spr->width();
const int triangleWidth = 12;
const int triangleHeight = 10;
const int triangleSpacing = 2;
const int triangleY = 18; // Below the blocks
const int maxTriangles = (screenWidth - 10) / (triangleWidth + triangleSpacing);
uint32_t completedTriangles = totalRestTime / REST_BLOCK_DURATION;
uint32_t currentTriangleProgress = (totalRestTime % REST_BLOCK_DURATION) * 100 / REST_BLOCK_DURATION;
int startX = (screenWidth - (maxTriangles * (triangleWidth + triangleSpacing) - triangleSpacing)) / 2;
int x = startX;
for (int i = 0; i < maxTriangles; i++) {
int x0 = x + triangleWidth / 2;
int y0 = triangleY;
int x1 = x;
int y1 = triangleY + triangleHeight;
int x2 = x + triangleWidth;
int y2 = triangleY + triangleHeight;
if (i < completedTriangles) {
// Fully filled triangle
spr->fillTriangle(x0, y0, x1, y1, x2, y2, COLOR_TRIANGLE_FILL);
} else if (i == completedTriangles && currentTriangleProgress > 0) {
// Partially filled triangle
spr->drawTriangle(x0, y0, x1, y1, x2, y2, COLOR_TRIANGLE_EMPTY);
// Fill from bottom up
int fillHeight = triangleHeight * currentTriangleProgress / 100;
if (fillHeight > 0) {
int cutY = y1 - fillHeight;
// Calculate the width at the cut line
float widthRatio = (float)fillHeight / triangleHeight;
int cutWidth = triangleWidth * widthRatio;
int cutX1 = x0 - cutWidth / 2;
int cutX2 = x0 + cutWidth / 2;
spr->fillTriangle(cutX1, cutY, cutX2, cutY, x0, y1, COLOR_TRIANGLE_FILL);
spr->fillTriangle(x1, y1, cutX1, cutY, x0, y1, COLOR_TRIANGLE_FILL);
spr->fillTriangle(x2, y2, cutX2, cutY, x0, y1, COLOR_TRIANGLE_FILL);
}
} else {
// Empty triangle
spr->drawTriangle(x0, y0, x1, y1, x2, y2, COLOR_TRIANGLE_EMPTY);
}
x += triangleWidth + triangleSpacing;
}
}
void drawTime(LGFX_Sprite* spr, uint32_t remainingMs) {
int minutes = remainingMs / 60000;
int seconds = (remainingMs % 60000) / 1000;
spr->setTextDatum(middle_center);
spr->setTextSize(1);
// Create time string
char timeStr[10];
sprintf(timeStr, "%02d:%02d", minutes, seconds);
// Use large font for better visibility
spr->setFont(&fonts::FreeSansBold24pt7b);
spr->setTextColor(isWorkMode ? COLOR_WORK_TEXT : COLOR_REST_TEXT);
int centerX = spr->width() / 2;
int centerY = spr->height() / 2 + 5;
spr->drawString(timeStr, centerX, centerY);
// Draw mode indicator
spr->setFont(&fonts::FreeSans9pt7b);
const char* modeText = isWorkMode ? "WORK" : "REST";
uint32_t modeColor = isWorkMode ? COLOR_WORK_ACCENT : COLOR_REST_ACCENT;
spr->setTextColor(modeColor);
spr->drawString(modeText, centerX, centerY + 32);
}
void drawCountdownPrompt(LGFX_Sprite* spr) {
spr->setFont(&fonts::FreeSans9pt7b);
spr->setTextColor(isWorkMode ? COLOR_WORK_TEXT : COLOR_REST_TEXT);
spr->setTextDatum(bottom_center);
int centerX = spr->width() / 2;
int bottomY = spr->height() - 6;
spr->drawString("Press Btn A", centerX, bottomY);
}
void drawTimeUpMessage(LGFX_Sprite* spr) {
spr->setTextColor(isWorkMode ? COLOR_WORK_TEXT : COLOR_REST_TEXT);
spr->setFont(&fonts::FreeSansBold18pt7b);
spr->setTextDatum(top_center);
spr->drawString("Time's Up!", spr->width() / 2, 6);
spr->setFont(&fonts::FreeSans9pt7b);
spr->setTextDatum(bottom_center);
spr->drawString("Press Btn A", spr->width() / 2, spr->height() - 6);
}
void updateDisplay() {
uint32_t bgColor = isWorkMode ? COLOR_WORK_BG : COLOR_REST_BG;
// Draw to sprite buffer
canvas.fillScreen(bgColor);
// Calculate current cycle elapsed time
uint32_t currentCycleElapsed = getCurrentCycleElapsed();
if (isWorkMode && currentCycleElapsed > WORK_CYCLE_DURATION) {
currentCycleElapsed = WORK_CYCLE_DURATION;
}
if (isWorkMode) {
drawProgressBlocks(&canvas, totalWorkTime + currentCycleElapsed);
drawRestTriangles(&canvas, totalRestTime);
if (isTimeUp) {
drawTime(&canvas, 0);
drawTimeUpMessage(&canvas);
} else if (isCountdownPhase) {
uint32_t remaining = (currentCycleElapsed >= WORK_CYCLE_DURATION)
? 0
: (WORK_CYCLE_DURATION - currentCycleElapsed);
drawTime(&canvas, remaining);
drawCountdownPrompt(&canvas);
} else {
uint32_t remaining = (currentCycleElapsed >= WORK_CYCLE_DURATION)
? 0
: (WORK_CYCLE_DURATION - currentCycleElapsed);
drawTime(&canvas, remaining);
}
} else {
// Rest mode - show work progress and rest triangles
drawProgressBlocks(&canvas, totalWorkTime);
drawRestTriangles(&canvas, totalRestTime + currentCycleElapsed);
canvas.setTextDatum(middle_center);
canvas.setFont(&fonts::FreeSansBold18pt7b);
canvas.setTextColor(COLOR_REST_TEXT);
canvas.drawString("Rest Mode", canvas.width() / 2, canvas.height() / 2);
canvas.setFont(&fonts::FreeSans9pt7b);
int minutes = currentCycleElapsed / 60000;
int seconds = (currentCycleElapsed % 60000) / 1000;
char timeStr[20];
sprintf(timeStr, "%02d:%02d", minutes, seconds);
canvas.drawString(timeStr, canvas.width() / 2, canvas.height() / 2 + 25);
}
// Draw battery icon on top of everything
drawBatteryIcon(&canvas);
// Push sprite to display - no flicker
canvas.pushSprite(0, 0);
}
void enterDeepSleep() {
// Save current elapsed time before sleep
if (!isTimeUp && !isCountdownPhase) {
savedCycleElapsed = getCurrentCycleElapsed();
}
rtcSleepStartUs = esp_rtc_get_time_us();
M5.Display.setBrightness(0);
M5.Display.sleep();
// Configure wake up sources for StickS3
esp_sleep_enable_ext1_wakeup((1ULL << BTN_A_GPIO) | (1ULL << BTN_B_GPIO), ESP_EXT1_WAKEUP_ANY_LOW);
// Set timer wakeup for work mode (only if not in countdown/time up)
if (isWorkMode && !isTimeUp && !isCountdownPhase) {
uint32_t alertAtMs = (WORK_CYCLE_DURATION > ALERT_BEFORE_END)
? (WORK_CYCLE_DURATION - ALERT_BEFORE_END)
: 0;
if (savedCycleElapsed < alertAtMs && alertAtMs > 1000) {
uint32_t wakeAfterMs = alertAtMs - savedCycleElapsed;
// Ensure timer is reasonable (between 1s and 1 hour)
if (wakeAfterMs >= 1000 && wakeAfterMs <= 3600000) {
esp_sleep_enable_timer_wakeup((uint64_t)wakeAfterMs * 1000ULL);
}
}
}
esp_deep_sleep_start();
}
void startNewCycle() {
savedCycleElapsed = 0;
cycleStartMillis = millis();
isCountdownPhase = false;
isTimeUp = false;
needConfirmation = false;
timeUpStartMs = 0;
lastInvertToggleMs = 0;
invertDisplayOn = false;
}
void setup() {
auto cfg = M5.config();
cfg.internal_mic = false;
cfg.internal_spk = false;
M5.begin(cfg);
// Initialize M5PM1 power management chip with M5Unified's I2C
// PM1 uses I2C address 0x6E by default
if (pm1.begin(&Wire, M5PM1_DEFAULT_ADDR) != M5PM1_OK) {
// PM1 initialization failed, continue anyway
}
// Wait for PM1 to be ready
delay(100);
M5.Display.setRotation(1);
M5.Display.setBrightness(100);
M5.Display.wakeup();
// Create sprite buffer for flicker-free drawing
canvas.createSprite(M5.Display.width(), M5.Display.height());
disablePeripherals();
// Check if this is first boot
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
if (wakeup_reason == ESP_SLEEP_WAKEUP_UNDEFINED) {
// First boot - initialize everything and clear RTC residual data
isWorkMode = true;
totalWorkTime = 0;
totalRestTime = 0;
savedCycleElapsed = 0;
isCountdownPhase = false;
isTimeUp = false;
screenWasOn = true;
awake = true;
rtcSleepStartUs = 0;
timeUpStartMs = 0;
lastInvertToggleMs = 0;
invertDisplayOn = false;
} else {
// Waking from sleep
uint64_t rtcNowUs = esp_rtc_get_time_us();
// Validate RTC time to prevent overflow
if (rtcSleepStartUs > 0 && rtcNowUs > rtcSleepStartUs && (rtcNowUs - rtcSleepStartUs) < 3600000000ULL) {
uint32_t sleepDeltaMs = (uint32_t)((rtcNowUs - rtcSleepStartUs) / 1000);
savedCycleElapsed += sleepDeltaMs;
// Clamp to cycle duration
if (isWorkMode && savedCycleElapsed > WORK_CYCLE_DURATION) {
savedCycleElapsed = WORK_CYCLE_DURATION;
}
}
uint32_t alertStartElapsed = (WORK_CYCLE_DURATION > ALERT_BEFORE_END)
? (WORK_CYCLE_DURATION - ALERT_BEFORE_END)
: 0;
if (isWorkMode && savedCycleElapsed >= WORK_CYCLE_DURATION) {
savedCycleElapsed = WORK_CYCLE_DURATION;
isTimeUp = true;
isCountdownPhase = false;
} else if (isWorkMode && savedCycleElapsed >= alertStartElapsed) {
isCountdownPhase = true;
isTimeUp = false;
} else {
isCountdownPhase = false;
isTimeUp = false;
}
if (isCountdownPhase || isTimeUp) {
needConfirmation = true;
awake = false;
M5.Display.setBrightness(255);
M5.Display.wakeup();
}
awake = false;
screenWasOn = true;
}
// Reset millis-based timers
wakeupTimeMs = millis();
cycleStartMillis = millis();
screenOnMillis = millis();
// Initialize battery status
updateBatteryStatus();
lastBatteryUpdate = millis();
updateDisplay();
}
void loop() {
M5.update();
uint32_t currentCycleElapsed = getCurrentCycleElapsed();
bool cycleResetThisLoop = false;
// Handle button presses
if (M5.BtnA.wasPressed()) {
if (isWorkMode && isTimeUp) {
totalWorkTime += WORK_CYCLE_DURATION;
startNewCycle();
awake = true;
screenWasOn = true;
screenOnMillis = millis();
updateDisplay();
cycleResetThisLoop = true;
} else if (!awake) {
// First press after wake - just acknowledge
awake = true;
screenWasOn = true; // Enable screen updates
screenOnMillis = millis();
updateDisplay();
} else {
screenOnMillis = millis();
}
}
if (M5.BtnB.wasPressed()) {
if (!awake) {
awake = true;
screenWasOn = true; // Enable screen updates
screenOnMillis = millis();
updateDisplay();
} else {
// Toggle work/rest mode
if (isWorkMode) {
// Switching to rest - save current work progress
totalWorkTime += currentCycleElapsed;
isWorkMode = false;
savedCycleElapsed = 0;
cycleStartMillis = millis();
isCountdownPhase = false;
isTimeUp = false;
} else {
// Switching back to work - save rest time
totalRestTime += currentCycleElapsed;
isWorkMode = true;
startNewCycle();
cycleResetThisLoop = true;
}
updateDisplay();
screenOnMillis = millis();
}
}
// Check for alert phase in work mode
if (cycleResetThisLoop) {
currentCycleElapsed = getCurrentCycleElapsed();
}
if (isWorkMode && !cycleResetThisLoop) {
uint32_t alertStartElapsed = (WORK_CYCLE_DURATION > ALERT_BEFORE_END)
? (WORK_CYCLE_DURATION - ALERT_BEFORE_END)
: 0;
if (!isTimeUp && currentCycleElapsed >= WORK_CYCLE_DURATION) {
isTimeUp = true;
isCountdownPhase = false;
needConfirmation = true;
awake = false;
screenWasOn = true;
M5.Display.wakeup();
M5.Display.setBrightness(255);
updateDisplay();
screenOnMillis = millis();
timeUpStartMs = millis();
} else if (!isCountdownPhase && !isTimeUp && currentCycleElapsed >= alertStartElapsed) {
isCountdownPhase = true;
needConfirmation = true;
awake = false;
screenWasOn = true;
M5.Display.wakeup();
M5.Display.setBrightness(255);
updateDisplay();
screenOnMillis = millis();
}
// Update display every second during work
static uint32_t lastUpdate = 0;
if (millis() - lastUpdate >= 1000) {
lastUpdate = millis();
if (screenWasOn || isCountdownPhase || isTimeUp) {
updateDisplay();
}
}
} else {
// Rest mode - update display every second if screen is on
static uint32_t lastRestUpdate = 0;
if (millis() - lastRestUpdate >= 1000) {
lastRestUpdate = millis();
if (screenWasOn) {
updateDisplay();
}
}
}
// Update battery status periodically
if (millis() - lastBatteryUpdate >= BATTERY_UPDATE_INTERVAL) {
updateBatteryStatus();
lastBatteryUpdate = millis();
if (screenWasOn || isCountdownPhase || isTimeUp) {
updateDisplay();
}
}
// Handle screen timeout
if (!isCountdownPhase && !isTimeUp && millis() - screenOnMillis >= SCREEN_ON_DURATION) {
screenWasOn = false; // Will be set to true on next wake
enterDeepSleep();
}
// Flash invert after timeout in time-up phase
if (isTimeUp) {
if (timeUpStartMs == 0) {
timeUpStartMs = millis();
}
if (millis() - timeUpStartMs >= SCREEN_ON_DURATION) {
if (millis() - lastInvertToggleMs >= 500) {
invertDisplayOn = !invertDisplayOn;
M5.Display.invertDisplay(invertDisplayOn);
lastInvertToggleMs = millis();
}
}
} else {
if (invertDisplayOn) {
M5.Display.invertDisplay(false);
invertDisplayOn = false;
}
timeUpStartMs = 0;
lastInvertToggleMs = 0;
}
delay(50);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment