diff --git a/README.md b/README.md index 312038a..50714f9 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ The project now uses a **clean, event-driven architecture** perfect for building - ⚡ **Event-Driven**: Display only updates when input is received - 🔋 **Power Efficient**: Uses `__wfi()` to sleep between inputs (< 1mA idle) - 📄 **E-ink Optimized**: Minimizes screen refreshes for e-paper displays -- 🎯 **Interrupt-Driven**: Touch and button handling via hardware interrupts +- 🎯 **Hybrid Input**: IRQ wake-up plus active-touch sampling for smoother drag/move handling - 🧩 **Modular**: Clear separation of input processing, game logic, and rendering ### Architecture Highlights ``` -Interrupt → Set Flag → Wake CPU → Process Input → Update Game → Draw → Refresh → Sleep +IRQ/Timer → Set Flag → Wake CPU → Process Input → Update Game → Draw → Refresh → Sleep ``` **Before (Polling Loop):** @@ -45,14 +45,18 @@ while(1) { **After (Reactive Template):** ```cpp -while(1) { - __wfi(); // Sleep until interrupt - +while (1) { + bool stay_awake = pending_refresh || touch_event_down || (last_touch_time != 0); + if (!stay_awake) { + __wfi(); // Sleep until interrupt + if (!has_pending_wake_source()) continue; // Ignore unrelated wake-ups + } + InputEvent input = process_button_input(config); if (!input.valid) { input = process_touch_input(config, &last_touch_time); } - + if (input.valid && game_update(&game_state, input, config, &renderer)) { game_draw(&game_state, &renderer, &gui); refresh_screen(bit_buffer, display); @@ -534,7 +538,8 @@ The typical development workflow: **Interrupt not triggering:** - Verify `TOUCH_INT_PIN` is correctly defined - Check INT pin is pulled high (internal pull-up or external resistor) -- Confirm touch controller is configured for interrupt mode +- Confirm touch controller is configured for trigger/interrupt mode +- Touch processing uses hybrid mode: IRQ wake-up plus active-session sampling while touch is down ### SD Card Issues @@ -579,7 +584,7 @@ The code includes `printf()` statements for debugging touch, display, and SD car For battery-powered projects: - E-Paper displays use almost no power when idle -- Use `__wfi()` (Wait For Interrupt) to sleep between inputs +- Use `__wfi()` (Wait For Interrupt) to sleep between event bursts - Disable TFT backlight when idle - Lower SPI/I2C baud rates to reduce power consumption @@ -607,7 +612,8 @@ For battery-powered projects: ### Touch Features - Multi-touch support (up to 2 points) - Coordinate transformation -- Touch debouncing +- Hybrid IRQ + active-session sampling +- Touch debouncing with release hysteresis - Event types (press, lift, contact) ### SD Card Features diff --git a/basic1.cpp b/basic1.cpp index 455bde6..b2caf45 100644 --- a/basic1.cpp +++ b/basic1.cpp @@ -14,7 +14,7 @@ * - Event-driven: Display only updates when input is received * - Power efficient: Uses __wfi() to sleep between inputs * - E-ink optimized: Minimizes screen refreshes - * - Interrupt-driven: Touch and button handling via interrupts + * - Hybrid input handling: IRQ wake-up plus active-touch sampling * - Modular: Clear separation of input, game logic, and rendering * * ARCHITECTURE: @@ -36,7 +36,6 @@ #include "pico/stdlib.h" #include "pico/binary_info.h" -#include "hardware/sync.h" #include "pico/multicore.h" #include "board_config.h" // Board-specific pin configuration #include "sd_card.h" @@ -171,7 +170,6 @@ static uint32_t last_interaction_time = 0; // Last time user interacted static bool is_idle_2min_triggered = false; // Flag for 2min trigger static bool is_idle_10min_triggered = false; // Flag for 10min trigger static volatile bool dim_check_flag = false; // Flag set by timer to check dimming -static LowLevelDisplay* global_display = nullptr; // Global display pointer for timer callback /** * @brief Update last interaction time and notify display driver @@ -269,6 +267,18 @@ static inline bool has_pending_wake_source() { return false; } +/** + * @brief Returns true when the currently selected game needs frame ticks. + */ +static inline bool game_wants_frame_updates(GameLauncher& launcher) { + if (!launcher.is_game_selected()) { + return false; + } + + Game* game = launcher.get_selected_game(); + return game && game->wants_frame_updates(); +} + /** * @brief Touch interrupt callback handler * @@ -282,6 +292,8 @@ static inline bool has_pending_wake_source() { * @param events Event mask (GPIO_IRQ_EDGE_FALL and/or GPIO_IRQ_EDGE_RISE) */ void touch_interrupt_handler(uint gpio, uint32_t events) { + (void)gpio; + // Track which edge triggered (down vs up). // Keep ISR minimal: do not log/print from interrupt context. if (events & GPIO_IRQ_EDGE_FALL) { @@ -325,9 +337,6 @@ void button_interrupt_handler(uint gpio, uint32_t events) { const int V_WIDTH = DISPLAY_WIDTH; const int V_HEIGHT = DISPLAY_HEIGHT; -// Touch indicator settings -#define TOUCH_RADIUS 10 - uint8_t bit_buffer[V_WIDTH * V_HEIGHT / 8]; /** @@ -427,7 +436,7 @@ int main() if (touch) { printf("Touch initialized successfully\n"); - // Set up interrupt-driven touch detection + // Set up touch IRQ wake-up (InputManager handles active-touch sampling) printf("Setting up touch interrupt callback...\n"); touch->set_interrupt_callback(touch_interrupt_handler); printf("Touch interrupt enabled on INT pin (falling and rising edges)\n"); @@ -549,10 +558,11 @@ int main() // Core 0 (this loop): Handles input and game logic - stays responsive // Core 1: Handles display refresh - can take 1-2 seconds for e-ink // - // The loop sleeps until an interrupt occurs, then: + // The loop primarily sleeps on __wfi(), and wakes to: // 1. Process input (button or touch) // 2. Update game state based on input // 3. Queue refresh on Core 1 (non-blocking) + // While touch is active or a game needs frame ticks, the loop stays awake. // This keeps Core 0 responsive even during slow e-ink refreshes // ======================================================================== @@ -561,8 +571,6 @@ int main() // Initialize last interaction time to current time last_interaction_time = to_ms_since_boot(get_absolute_time()); - global_display = display; - // Set up repeating alarm to periodically check dimming status // This wakes the CPU from __wfi() every DIM_CHECK_INTERVAL_MS add_alarm_in_ms(DIM_CHECK_INTERVAL_MS, dim_check_alarm_callback, nullptr, true); @@ -612,12 +620,7 @@ int main() if (touch_event_down) stay_awake = true; // Keep sampling while finger is down if (last_touch_time != 0) stay_awake = true; // Keep sampling during active touch session - if (launcher.is_game_selected()) { - Game* g = launcher.get_selected_game(); - if (g && g->wants_frame_updates()) { - stay_awake = true; - } - } + if (game_wants_frame_updates(launcher)) stay_awake = true; if (!stay_awake) { // Sleep until interrupt wakes us up (very power efficient!) @@ -722,10 +725,10 @@ int main() } } - if (launcher.is_game_selected()) { + if (game_wants_frame_updates(launcher)) { // No input, but check if game wants continuous updates current_game = launcher.get_selected_game(); - if (current_game->wants_frame_updates()) { + if (current_game) { // Only send frame tick if we're ready to draw the next frame if (!is_refresh_in_progress()) { InputEvent frame_tick = {INPUT_FRAME_TICK, 0, 0, 0, 0, 0, true}; diff --git a/lib/input_manager.cpp b/lib/input_manager.cpp index e5f0781..04e576e 100644 --- a/lib/input_manager.cpp +++ b/lib/input_manager.cpp @@ -10,7 +10,6 @@ // External interrupt flags from basic1.cpp extern volatile bool touch_interrupt_flag; -extern volatile bool touch_event_down; extern volatile bool button_key0_pressed; extern volatile bool button_key1_pressed; diff --git a/lib/input_manager.h b/lib/input_manager.h index 8215785..68137cd 100644 --- a/lib/input_manager.h +++ b/lib/input_manager.h @@ -37,9 +37,9 @@ public: InputManager(LowLevelTouch* touch, const GameConfig* config); /** - * @brief Process touch input from controller - * @param last_time Pointer to last touch timestamp for debouncing - * @return InputEvent (valid=false if no valid input) + * @brief Process touch input using hybrid IRQ + active-session sampling. + * @param last_time Pointer to last touch timestamp (0 means no active touch session) + * @return InputEvent (valid=false if no state change/update is emitted) */ InputEvent process_touch_input(uint32_t* last_time); @@ -97,6 +97,8 @@ public: private: LowLevelTouch* touch; const GameConfig* config; + // Consecutive touch_count==0 samples while a touch session is active. + // Used to suppress false TOUCH_UP events from transient touch controller jitter. uint8_t no_touch_samples = 0; // Virtual button regions