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 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) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue