Powers off instead of sleeping
This commit is contained in:
parent
db29d89cbf
commit
5727751ea0
114
tama.c
114
tama.c
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue