Refactor main loop touch flow and update docs
This commit is contained in:
18
README.md
18
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,8 +45,12 @@ while(1) {
|
||||
|
||||
**After (Reactive Template):**
|
||||
```cpp
|
||||
while(1) {
|
||||
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) {
|
||||
@@ -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
|
||||
|
||||
39
basic1.cpp
39
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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user