Powers off instead of sleeping

This commit is contained in:
Carl Vargklint 2026-07-01 00:31:50 +02:00
parent db29d89cbf
commit 5727751ea0
6 changed files with 4126 additions and 3754 deletions

BIN
tama.bin

Binary file not shown.

114
tama.c
View File

@ -37,6 +37,7 @@
#define FRAME_MS 60 // main loop period
#define TICK_FRAMES 250 // stat update every ~15 s (250 * 60 ms)
#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:
// 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
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 sel_species = 0; // highlighted species on the welcome screen
// screen-off-on-inactivity options, in frames (0 = never sleep the display)
static const uint16_t screen_timeout_opts[] = { 250, 500, 1000, 0 };
static const char *const screen_timeout_names[] = { "15s", "30s", "60s", "Off" };
#define TIMEOUT_OPT_COUNT 4
// screen-off-on-inactivity options, in frames (0 = never sleep the display).
// Shorter timeouts blank the OLED sooner, which is the single biggest power
// win on a CR2032 — the panel draws far more than the dozing MCU.
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
// display power state
@ -571,6 +574,88 @@ static Buttons read_buttons(void)
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
static void gpio_setup(void)
{
@ -625,6 +710,7 @@ int main(void)
{
SystemInit();
gpio_setup();
lowpower_setup(); // button-wake EXTI + periodic-check timer
Delay_Ms(120); // let the OLED rail settle
if (ssd1306_i2c_init()) { // non-zero == failure
@ -639,6 +725,16 @@ int main(void)
Buttons prev = {0, 0, 0};
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();
uint8_t ok_edge = b.ok && !prev.ok;
uint8_t left_edge = b.left && !prev.left;
@ -648,13 +744,7 @@ int main(void)
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) {
case SCR_SELECT:
if (left_edge) {

BIN
tama.elf

Binary file not shown.

1386
tama.hex

File diff suppressed because it is too large Load Diff

5571
tama.lst

File diff suppressed because it is too large Load Diff

809
tama.map

File diff suppressed because it is too large Load Diff