Powers off instead of sleeping

This commit is contained in:
2026-07-01 00:31:50 +02:00
parent db29d89cbf
commit 5727751ea0
6 changed files with 4126 additions and 3754 deletions
BIN
View File
Binary file not shown.
+102 -12
View File
@@ -37,6 +37,7 @@
#define FRAME_MS 60 // main loop period #define FRAME_MS 60 // main loop period
#define TICK_FRAMES 250 // stat update every ~15 s (250 * 60 ms) #define TICK_FRAMES 250 // stat update every ~15 s (250 * 60 ms)
#define ANIM_FRAMES 12 // how long an action reaction is shown #define ANIM_FRAMES 12 // how long an action reaction is shown
#define TICK_MS (TICK_FRAMES * FRAME_MS) // 15000 ms: also the screen-off check period
// With the values below an untended pet survives roughly half an hour: // With the values below an untended pet survives roughly half an hour:
// a need empties in ~20 min, after which health bleeds down over ~10 more. // a need empties in ~20 min, after which health bleeds down over ~10 more.
@@ -87,14 +88,16 @@ static uint8_t evo_anim; // frames remaining for an evolution banner
// settings // settings
static uint8_t sound_on = 1; static uint8_t sound_on = 1;
static uint8_t timeout_idx = 1; // index into screen_timeout_opts (default 30 s) static uint8_t timeout_idx = 1; // index into screen_timeout_opts (default 10 s)
static uint8_t set_sel = 0; // highlighted row on the settings screen static uint8_t set_sel = 0; // highlighted row on the settings screen
static uint8_t sel_species = 0; // highlighted species on the welcome screen static uint8_t sel_species = 0; // highlighted species on the welcome screen
// screen-off-on-inactivity options, in frames (0 = never sleep the display) // screen-off-on-inactivity options, in frames (0 = never sleep the display).
static const uint16_t screen_timeout_opts[] = { 250, 500, 1000, 0 }; // Shorter timeouts blank the OLED sooner, which is the single biggest power
static const char *const screen_timeout_names[] = { "15s", "30s", "60s", "Off" }; // win on a CR2032 — the panel draws far more than the dozing MCU.
#define TIMEOUT_OPT_COUNT 4 static const uint16_t screen_timeout_opts[] = { 83, 167, 250, 500, 1000, 0 };
static const char *const screen_timeout_names[] = { "5s", "10s", "15s", "30s", "60s", "Off" };
#define TIMEOUT_OPT_COUNT 6
#define SETTINGS_ROWS 3 // screen-off, sound, back #define SETTINGS_ROWS 3 // screen-off, sound, back
// display power state // display power state
@@ -571,6 +574,88 @@ static Buttons read_buttons(void)
return b; return b;
} }
// ----------------------------------------------------------------- low power
// Two wake sources let the core idle in WFI light-sleep while the OLED is off:
// * a button press (EXTI falling edge) -> light the screen back up
// * a periodic TIM2 overflow (~once per game tick) -> run a quick check
// WFI only gates the CPU clock, so SRAM/registers (and the whole Pet) survive
// untouched; full standby would wipe them and force a reboot/splash on wake.
static volatile uint8_t exti_wake; // a button interrupt fired
static volatile uint8_t tick_wake; // the periodic check timer fired
// OK=PC0->EXTI0, Right=PD5->EXTI5, Left=PD6->EXTI6 all share this vector.
void EXTI7_0_IRQHandler(void) INTERRUPT_DECORATOR;
void EXTI7_0_IRQHandler(void)
{
EXTI->INTFR = EXTI_Line0 | EXTI_Line5 | EXTI_Line6; // clear pending
exti_wake = 1;
}
void TIM2_IRQHandler(void) INTERRUPT_DECORATOR;
void TIM2_IRQHandler(void)
{
TIM2->INTFR = (uint16_t)~TIM_FLAG_Update; // clear update flag
tick_wake = 1;
}
static void lowpower_setup(void)
{
// --- button wake: falling-edge EXTI on the three button pins ---
// (AFIO + GPIO clocks were already enabled by funGpioInitAll.)
AFIO->EXTICR |= AFIO_EXTICR_EXTI0_PC | // OK on PC0
AFIO_EXTICR_EXTI5_PD | // Right on PD5
AFIO_EXTICR_EXTI6_PD; // Left on PD6
EXTI->INTENR |= EXTI_Line0 | EXTI_Line5 | EXTI_Line6;
EXTI->FTENR |= EXTI_Line0 | EXTI_Line5 | EXTI_Line6; // press == falling edge
NVIC_EnableIRQ(EXTI7_0_IRQn);
// --- periodic wake: TIM2 update IRQ, one overflow per game tick ---
RCC->APB1PCENR |= RCC_APB1Periph_TIM2;
TIM2->PSC = 23999; // 24 MHz / 24000 = 1 kHz (1 ms/count)
TIM2->ATRLR = TICK_MS - 1; // overflow once per game tick
TIM2->SWEVGR = TIM_PSCReloadMode_Immediate; // latch PSC/ARR now
TIM2->INTFR = (uint16_t)~TIM_FLAG_Update;
TIM2->DMAINTENR |= TIM_UIE; // enable update interrupt
NVIC_EnableIRQ(TIM2_IRQn);
// the counter itself stays disabled until we actually enter low-power sleep
}
// Doze in WFI until a button or the periodic timer wakes us. Returns only once
// the display has been switched back on (button press, or the pet dying while
// we slept); otherwise it keeps running background ticks and sleeping again.
static void enter_low_power(void)
{
for (;;) {
exti_wake = 0;
tick_wake = 0;
TIM2->CNT = 0;
TIM2->INTFR = (uint16_t)~TIM_FLAG_Update;
TIM2->CTLR1 |= TIM_CEN; // arm the periodic wake
__WFI(); // core clock gated until an IRQ
TIM2->CTLR1 &= ~TIM_CEN; // halt it while we're awake
if (exti_wake) { // a button: bring the screen back
ssd1306_cmd(SSD1306_DISPLAYON);
disp_on = 1;
idle_frames = 0;
tick_acc = 0;
return;
}
// otherwise it was the periodic check: advance the pet a tick. This
// still chirps low-need reminders (and the death tune) over the buzzer.
if (pet.alive) {
game_tick();
if (!pet.alive) { // it died in its sleep: show the grave
ssd1306_cmd(SSD1306_DISPLAYON);
disp_on = 1;
idle_frames = 0;
return;
}
}
}
}
// ----------------------------------------------------------------- setup // ----------------------------------------------------------------- setup
static void gpio_setup(void) static void gpio_setup(void)
{ {
@@ -625,6 +710,7 @@ int main(void)
{ {
SystemInit(); SystemInit();
gpio_setup(); gpio_setup();
lowpower_setup(); // button-wake EXTI + periodic-check timer
Delay_Ms(120); // let the OLED rail settle Delay_Ms(120); // let the OLED rail settle
if (ssd1306_i2c_init()) { // non-zero == failure if (ssd1306_i2c_init()) { // non-zero == failure
@@ -639,6 +725,16 @@ int main(void)
Buttons prev = {0, 0, 0}; Buttons prev = {0, 0, 0};
while (1) { while (1) {
if (!disp_on) {
// Display is off: hand the core to WFI light-sleep, waking only
// for button presses or the periodic background check. It returns
// once the screen is lit again; swallow whatever press woke us so
// it just wakes the display rather than also firing an action.
enter_low_power();
prev = read_buttons();
continue;
}
Buttons b = read_buttons(); Buttons b = read_buttons();
uint8_t ok_edge = b.ok && !prev.ok; uint8_t ok_edge = b.ok && !prev.ok;
uint8_t left_edge = b.left && !prev.left; uint8_t left_edge = b.left && !prev.left;
@@ -648,13 +744,7 @@ int main(void)
if (any_edge) idle_frames = 0; if (any_edge) idle_frames = 0;
if (!disp_on) { {
// screen is asleep: the first press only wakes it (not an action)
if (any_edge) {
ssd1306_cmd(SSD1306_DISPLAYON);
disp_on = 1;
}
} else {
switch (screen) { switch (screen) {
case SCR_SELECT: case SCR_SELECT:
if (left_edge) { if (left_edge) {
BIN
View File
Binary file not shown.
+705 -681
View File
File diff suppressed because it is too large Load Diff
+2911 -2660
View File
File diff suppressed because it is too large Load Diff
+408 -401
View File
File diff suppressed because it is too large Load Diff