From 8b8d9d3a557f99b1d1c54712ce20f775e4f3dcae Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Mon, 6 Oct 2025 09:34:33 +0200 Subject: [PATCH] pause --- Firmware/main/src/hello_world_main.cpp | 196 ++++++++++++++++++------- 1 file changed, 145 insertions(+), 51 deletions(-) diff --git a/Firmware/main/src/hello_world_main.cpp b/Firmware/main/src/hello_world_main.cpp index e9d09d2..1416232 100644 --- a/Firmware/main/src/hello_world_main.cpp +++ b/Firmware/main/src/hello_world_main.cpp @@ -16,7 +16,7 @@ #include #include "esp_chip_info.h" #include "esp_flash.h" -#include "esp_pm.h" +#include "esp_pm.h" // power management (guard usage by CONFIG_PM_ENABLE) #include "esp_system.h" #include @@ -35,6 +35,7 @@ #include "i2c_global.hpp" // Battery monitor header (conditionally included) #include +#include "power_helper.hpp" namespace cfg { constexpr int BoardW = 10; @@ -128,8 +129,10 @@ struct PlatformClock final : IClock { return (uint32_t) ((uint64_t) t * 1000ULL / configTICK_RATE_HZ); } void sleep_ms(uint32_t ms) override { - if (ms) - vTaskDelay(pdMS_TO_TICKS(ms)); + // Pass a longer delay when slow mode is active, cap normal delay for responsiveness + int slow_ms = (int) ms; // allow full requested sleep when slow + int normal_ms = (int) std::min(ms, 30); // cap active-mode sleep to 30ms + PowerHelper::get().delay(slow_ms, normal_ms); } }; @@ -296,16 +299,20 @@ struct Font5x7 { case '-': return 28; case 'G': - return 29; // newly added glyph + return 29; + case 'P': + return 30; // added + case 'D': + return 31; // added // space handled specially in drawText default: return 255; } } // columns × 7 rows (bit0 = top, bit6 = bottom) - static const uint8_t data[30][5]; + static const uint8_t data[32][5]; }; -const uint8_t Font5x7::data[30][5] = { +const uint8_t Font5x7::data[32][5] = { /*0*/ {0b0111110, 0b1000001, 0b1000001, 0b1000001, 0b0111110}, /*1*/ {0b0000000, 0b1000010, 0b1111111, 0b1000000, 0b0000000}, /*2*/ {0b11100010, 0b10010001, 0b10001001, 0b10001001, 0b10000110}, @@ -335,7 +342,9 @@ const uint8_t Font5x7::data[30][5] = { /*.*/ {0b00000000, 0b00000000, 0b01100000, 0b01100000, 0b00000000}, // wider centered dot (2 cols) /*:*/ {0b00000000, 0b00000000, 0b00110110, 0b00110110, 0b00000000}, // beefier colon (two stacked 2x2 dots) /*-*/ {0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000}, - /*G*/ {0b01111110, 0b10000001, 0b10001001, 0b10001001, 0b01111010}, // refined G with inner bar & notch + /*G*/ {0b01111110, 0b10000001, 0b10001001, 0b10001001, 0b01111010}, + /*P*/ {0b11111111, 0b00010001, 0b00010001, 0b00010001, 0b00001110}, + /*D*/ {0b11111111, 0b10000001, 0b10000001, 0b01000010, 0b00111100}, }; // ───────────────────────────────────────────────────────────────────────────── @@ -350,7 +359,7 @@ public: } void render(const Board& b, int px, int py, int prot, int pidx, int score, int level, int lines, int nextIdx, - bool ghost) { + bool ghost, bool paused) { fb.clear(false); // white background drawBatteryOverlay(); @@ -404,6 +413,8 @@ public: if (cell_of(TET[nextIdx], 0, xx, yy)) drawCellFullPreview(nx + xx * p, ny + yy * p, nextIdx + 1); + if (paused) + drawPausedOverlay(); DispTools::get().draw_to_display(); } @@ -438,6 +449,30 @@ public: putPixel(ix0 + xx, iy0 + yy, true); } + void drawPausedOverlay() { + const char* txt = "PAUSED"; + int len = 6; + int w = len * 6 - 1; // width in pixels used by text + int h = 8; + int cx = (fb.width() - w) / 2; + int cy = (fb.height() - h) / 2 - 10; + // Background wipe (white) then border + int padX = 5, padY = 4; + for (int y = -padY; y < h + padY; ++y) + for (int x = -padX; x < w + padX; ++x) + fb.drawPixel(cx + x, cy + y, false); + // Solid border + for (int x = -padX; x < w + padX; ++x) { + fb.drawPixel(cx + x, cy - padY, true); + fb.drawPixel(cx + x, cy + h + padY - 1, true); + } + for (int y = -padY; y < h + padY; ++y) { + fb.drawPixel(cx - padX, cy + y, true); + fb.drawPixel(cx + w + padX - 1, cy + y, true); + } + drawText(cx, cy, txt); + } + private: IFramebuffer& fb; int ox = 0, oy = 0, bw = 0, bh = 0; @@ -733,51 +768,103 @@ public: Game(IFramebuffer& fb, IInput& in, IClock& clk) : fb(fb), input(in), clock(clk), renderer(fb) { nextPiece = bag.next(); spawn(); - lastFall = clock.millis(); - touchTime = lastFall; + lastFall = clock.millis(); + touchTime = lastFall; + lastOverlayUpd = lastFall; + dirty = true; } void step() { + const uint32_t now = clock.millis(); + overlayTick(now); if (!running) { - renderEndOnce(); + if (dirty) + paintHUD(); return; } - const uint32_t now = clock.millis(); - InputState st = input.readState(); - - if (st.back && !backPrev) + InputState st = input.readState(); + if (st.back && !backPrev) { paused = !paused; + PowerHelper::get().set_slow(paused); // engage/disengage slow mode + dirty = true; + } backPrev = st.back; if (paused) { - paintHUD(); + if (dirty) + paintHUD(); return; } - if (st.rotate && !rotPrev) - tryRotate(+1); + // Rotation + if (st.rotate && !rotPrev) { + if (tryRotate(+1)) + dirty = true; + } rotPrev = st.rotate; + // Horizontal movement + autorepeat handleHorizontal(st, now); + // Gravity / soft drop const int g = st.down ? cfg::DropFastMs : score.dropMs; if (elapsed_ms(lastFall, now) >= (uint32_t) g) { lastFall = now; - if (!tryMove(0, 1)) { + if (!tryMoveInternal(0, 1)) { if (!touchingGround) { touchingGround = true; touchTime = now; } else if (elapsed_ms(touchTime, now) >= (uint32_t) cfg::LockDelayMs) { lockAndAdvance(); - touchingGround = false; } - } else + } else { touchingGround = false; + } } - paintHUD(); + if (dirty) + paintHUD(); } + uint32_t recommendedSleepMs(uint32_t now) const { + if (dirty) + return 0; + if (paused || !running) { + uint32_t untilOverlay = + (lastOverlayUpd + overlayIntervalMs > now) ? (lastOverlayUpd + overlayIntervalMs - now) : 0; + uint32_t cap = paused ? 2000u : 500u; // allow longer idle while paused + if (untilOverlay > cap) + untilOverlay = cap; + return untilOverlay; + } + + // Estimate next gravity event + uint32_t g = score.dropMs; + uint32_t sinceFall = elapsed_ms(lastFall, now); + uint32_t untilDrop = (sinceFall >= g) ? 0 : (g - sinceFall); + if (touchingGround) { + uint32_t sinceTouch = elapsed_ms(touchTime, now); + uint32_t untilLock = (sinceTouch >= (uint32_t) cfg::LockDelayMs) ? 0 : (cfg::LockDelayMs - sinceTouch); + untilDrop = std::min(untilDrop, untilLock); + } + + // Overlay may force earlier wake + uint32_t untilOverlay = + (lastOverlayUpd + overlayIntervalMs > now) ? (lastOverlayUpd + overlayIntervalMs - now) : 0; + uint32_t sleep = std::min(untilDrop, untilOverlay); + + // Provide a max sleep to keep input latency reasonable + if (sleep > maxIdleSleepMs) + sleep = maxIdleSleepMs; + return sleep; + } + + bool isPaused() const { return paused; } + private: + // Power-aware overlay throttling + static constexpr uint32_t overlayIntervalMs = 500; // base interval (paused uses multiplier) + static constexpr uint32_t maxIdleSleepMs = 120; // cap to keep input responsive + IFramebuffer& fb; IInput& input; IClock& clock; @@ -790,28 +877,34 @@ private: uint32_t lHoldStart = 0, rHoldStart = 0, lLastRep = 0, rLastRep = 0, lastFall = 0, touchTime = 0; int current = 0, nextPiece = 0, px = 3, py = -2, rot = 0; + // Dirty rendering & overlay + bool dirty = false; + uint32_t lastOverlayUpd = 0; + + void overlayTick(uint32_t now) { + uint32_t interval = paused ? overlayIntervalMs * 8 : overlayIntervalMs; // 4s when paused + if (elapsed_ms(lastOverlayUpd, now) >= interval) { + lastOverlayUpd = now; + dirty = true; + } + } + void paintHUD() { - renderer.render(board, px, py, rot, current, score.score, score.level, score.lines, nextPiece, true); + renderer.render(board, px, py, rot, current, score.score, score.level, score.lines, nextPiece, true, paused); + dirty = false; } - void renderEndOnce() { - fb.clear(true); - DispTools::get().draw_to_display(); - clock.sleep_ms(120); - fb.clear(false); - DispTools::get().draw_to_display(); - clock.sleep_ms(120); - paintHUD(); - } - - bool tryMove(int dx, int dy) { + bool tryMoveInternal(int dx, int dy) { if (!board.collides(px + dx, py + dy, rot, current)) { px += dx; py += dy; + dirty = true; return true; } return false; } + bool tryMove(int dx, int dy) { return tryMoveInternal(dx, dy); } + bool tryRotate(int d) { int nr = (rot + d + 4) % 4; static const int kicks[][2] = {{0, 0}, {-1, 0}, {1, 0}, {-2, 0}, {2, 0}, {0, -1}}; @@ -819,7 +912,8 @@ private: if (!board.collides(px + k[0], py + k[1], nr, current)) { px += k[0]; py += k[1]; - rot = nr; + rot = nr; + dirty = true; return true; } return false; @@ -833,29 +927,30 @@ private: if (!lHeld) { lHeld = true; rHeld = false; - tryMove(-1, 0); + (void) tryMoveInternal(-1, 0); lHoldStart = now; lLastRep = now; } else { uint32_t s = elapsed_ms(lHoldStart, now), r = elapsed_ms(lLastRep, now); if (s >= (uint32_t) cfg::DAS_ms && r >= (uint32_t) cfg::ARR_ms) { - (void) tryMove(-1, 0); + (void) tryMoveInternal(-1, 0); lLastRep = now; } } } else lHeld = false; + if (st.right) { if (!rHeld) { rHeld = true; lHeld = false; - tryMove(+1, 0); + (void) tryMoveInternal(+1, 0); rHoldStart = now; rLastRep = now; } else { uint32_t s = elapsed_ms(rHoldStart, now), r = elapsed_ms(rLastRep, now); if (s >= (uint32_t) cfg::DAS_ms && r >= (uint32_t) cfg::ARR_ms) { - (void) tryMove(+1, 0); + (void) tryMoveInternal(+1, 0); rLastRep = now; } } @@ -864,6 +959,7 @@ private: } void lockAndAdvance() { board.lock(px, py, rot, current); + dirty = true; int c = board.clearLines(); if (c) { static const int pts[5] = {0, 100, 300, 500, 800}; @@ -883,6 +979,7 @@ private: px = 3; py = -2; rot = 0; + dirty = true; if (board.collides(px, py, rot, current)) running = false; } @@ -897,14 +994,13 @@ public: } ~App() { delete game; } void runForever() { - uint32_t last = clock.millis(); while (true) { - game->step(); uint32_t now = clock.millis(); - uint32_t dt = elapsed_ms(last, now); - if (dt < (uint32_t) cfg::FrameMs) - clock.sleep_ms((uint32_t) cfg::FrameMs - dt); - last = clock.millis(); + game->step(); + uint32_t sleepMs = game->recommendedSleepMs(now); + if (sleepMs) { + clock.sleep_ms(sleepMs); + } } } @@ -918,14 +1014,14 @@ private: // ───────────────────────────────────────────────────────────────────────────── // Entry extern "C" void app_main() { + // Configure dynamic frequency scaling & light sleep if enabled +#ifdef CONFIG_PM_ENABLE esp_pm_config_t pm_config = { .max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, .min_freq_mhz = 16, .light_sleep_enable = true}; ESP_ERROR_CHECK(esp_pm_configure(&pm_config)); - printf("Hello world!\n"); - // TODO: Where to put that? ESP_ERROR_CHECK(esp_sleep_enable_gpio_wakeup()); - // For some reason, calling it here hangs on startup, sometimes - // ESP_ERROR_CHECK(gpio_install_isr_service(0)); +#endif + PowerHelper::get(); Shutdowner::get(); Buttons::get(); @@ -936,8 +1032,6 @@ extern "C" void app_main() { I2cGlobal::get(); BatMon::get(); SpiGlobal::get(); - SMD::get(); - SMD::get().clear(); DispTools::get().clear(); static PlatformFramebuffer fb;