From 372895fa082d5f2a2a95f4e8d55ef8a4a8a15ad2 Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Thu, 29 Jan 2026 15:58:58 -0500 Subject: [PATCH] initial game template --- README.md | 72 +++- REFACTORING_PLAN.md | 438 ------------------- TEMPLATE_USAGE.md | 543 ++++++++++++++++++++++++ basic1.cpp | 613 ++++++++++++++++++++------- display/low_level_display_epaper.cpp | 4 +- 5 files changed, 1076 insertions(+), 594 deletions(-) delete mode 100644 REFACTORING_PLAN.md create mode 100644 TEMPLATE_USAGE.md diff --git a/README.md b/README.md index 9ae9160..312038a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # RP2350 TFT Display with Touch and SD Card Demo -A modular embedded application for RP2350 microcontrollers featuring display, touch, and SD card support with hardware abstraction layers. +A modular embedded application for RP2350 microcontrollers featuring display, touch, and SD card support with hardware abstraction layers. **Now includes a reactive game template architecture!** ## Features +- **Reactive Game Template** - Event-driven architecture optimized for e-ink displays and power efficiency - **Display Abstraction Layer** - Support for multiple display types (ST7796, ST7789, E-Paper) - **Touch Abstraction Layer** - Extensible touch controller support (FT6336U) - **SD Card with FatFS** - File system support with board-aware initialization @@ -12,6 +13,75 @@ A modular embedded application for RP2350 microcontrollers featuring display, to - **Automated Build Scripts** - Build for one board or all boards with single commands - **Hardware Abstraction** - Factory pattern for displays and touch controllers +## 🎮 Reactive Game Template + +The project now uses a **clean, event-driven architecture** perfect for building games and interactive applications: + +### Key Features +- ⚡ **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 +- 🧩 **Modular**: Clear separation of input processing, game logic, and rendering + +### Architecture Highlights + +``` +Interrupt → Set Flag → Wake CPU → Process Input → Update Game → Draw → Refresh → Sleep +``` + +**Before (Polling Loop):** +```cpp +while(1) { + if (touch_interrupt_flag) { + // Read touch data + // Process coordinates + // Draw directly + // Handle gestures inline + refresh_screen(); + } +} +``` + +**After (Reactive Template):** +```cpp +while(1) { + __wfi(); // Sleep until interrupt + + 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); + } +} +``` + +### Creating Your Own Game + +1. **Modify GameState** - Define your game variables +2. **Implement game_init()** - Set initial values +3. **Implement game_update()** - Handle input and update state +4. **Implement game_draw()** - Render your game graphics + +The reactive loop and input system work automatically! + +**📖 [Read the Full Template Usage Guide](TEMPLATE_USAGE.md)** for detailed examples and patterns. + +### Example Game (Included) + +The template includes a **Button Navigation Game** demonstrating: +- Hardware button input handling (KEY0 switches focus, KEY1 clicks) +- GUI component usage (buttons, radio buttons, status bars) +- State management (click counters, focus tracking) +- Visual feedback (filled buttons show focus) +- E-ink optimized refreshes + +Perfect starting point for your own game! + ## Supported Hardware Configurations ### Available Board Configurations diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md deleted file mode 100644 index dd4e462..0000000 --- a/REFACTORING_PLAN.md +++ /dev/null @@ -1,438 +0,0 @@ -# Reactive Game Template Refactoring Plan - -**Goal**: Transform `basic1.cpp` into a clean, event-driven reactive game template where: -- Display only updates when input is received (ideal for e-ink displays) -- All drawing happens inside dedicated functions -- Game state is separate from input handling -- Clear structure for developers to customize game logic - ---- - -## Phase 1: Define Core Structures and Types - -### ☐ Step 1.1: Create InputEvent structure -**Location**: After includes, before interrupt handlers -**Action**: Define a unified `InputEvent` structure to normalize all input types (touch, button, gesture) -```cpp -// Input event types -enum InputType { - INPUT_NONE = 0, - INPUT_TOUCH_DOWN, - INPUT_TOUCH_MOVE, - INPUT_TOUCH_UP, - INPUT_BUTTON_0, - INPUT_BUTTON_1, - INPUT_GESTURE -}; - -// Unified input event structure -struct InputEvent { - InputType type; - int16_t x; - int16_t y; - uint8_t gesture_code; // For gesture events - uint8_t button_id; // For button events - uint8_t pressure; // Touch pressure/weight - bool valid; // Set to true if event is valid -}; -``` - -### ☐ Step 1.2: Create GameState structure -**Location**: After InputEvent definition -**Action**: Define a `GameState` structure to hold all game-specific data (example for drawing game) -```cpp -// Game state - customize this for your game -struct GameState { - // Drawing game state - int16_t last_x; - int16_t last_y; - bool is_drawing; - - // General game state - uint32_t score; - bool game_over; - uint32_t frame_count; - - // Statistics - uint32_t touch_success_count; - uint32_t touch_fail_count; -}; -``` - -### ☐ Step 1.3: Create GameConfig structure -**Location**: After GameState definition -**Action**: Define configurable parameters for the game -```cpp -// Game configuration - adjust these for your game -struct GameConfig { - uint32_t touch_debounce_ms; // Touch polling rate - uint32_t button_debounce_ms; // Button debounce delay - bool enable_gestures; // Enable gesture recognition - bool enable_continuous_draw; // Allow continuous drawing while touched - bool debug_verbose; // Print debug messages -}; -``` - ---- - -## Phase 2: Refactor Input System - -### ☐ Step 2.1: Simplify touch interrupt handler -**Location**: `touch_interrupt_handler()` function (lines ~52-83) -**Action**: Remove all processing logic, keep only flag setting -- Remove `touch->read_touch()` call from ISR -- Remove printf statements from ISR -- Keep only `touch_interrupt_flag` and `touch_event_down` flag setting - -### ☐ Step 2.2: Simplify button interrupt handler -**Location**: `button_interrupt_handler()` function (lines ~98-113) -**Action**: Remove printf from ISR, keep only flag setting -- Remove `printf()` calls from interrupt handler -- Keep only flag setting logic - -### ☐ Step 2.3: Create process_touch_input() function -**Location**: After interrupt handlers, before refresh_screen() -**Action**: Create dedicated function to process touch events -```cpp -/** - * @brief Process touch input and convert to InputEvent - * - * Reads touch data from controller and creates appropriate InputEvent. - * Handles debouncing and filtering internally. - * - * @param config Game configuration - * @param last_time Pointer to last touch time for debouncing - * @return InputEvent structure (valid=false if no valid input) - */ -InputEvent process_touch_input(const GameConfig& config, uint32_t* last_time); -``` - -### ☐ Step 2.4: Create process_button_input() function -**Location**: After process_touch_input() -**Action**: Create dedicated function to process button events -```cpp -/** - * @brief Process button input and convert to InputEvent - * - * Checks button flags and verifies button state with debouncing. - * Clears flags after processing. - * - * @param config Game configuration - * @return InputEvent structure (valid=false if no valid input) - */ -InputEvent process_button_input(const GameConfig& config); -``` - -### ☐ Step 2.5: Create get_gesture_name() helper function -**Location**: After process_button_input() -**Action**: Extract gesture name lookup into helper function -```cpp -/** - * @brief Get human-readable gesture name - * - * @param gesture_code Gesture code from touch controller - * @return Constant string with gesture name - */ -const char* get_gesture_name(uint8_t gesture_code); -``` - ---- - -## Phase 3: Create Game Logic Functions - -### ☐ Step 3.1: Create game_init() function -**Location**: After input processing functions -**Action**: Initialize game state -```cpp -/** - * @brief Initialize game state - * - * Called once at startup to set initial game values. - * Customize this for your game. - * - * @param state Pointer to GameState to initialize - */ -void game_init(GameState* state); -``` - -### ☐ Step 3.2: Create game_update() function -**Location**: After game_init() -**Action**: Update game state based on input -```cpp -/** - * @brief Update game state based on input event - * - * This is where your game logic goes. - * Called whenever an input event occurs. - * - * @param state Pointer to GameState to update - * @param input Input event to process - * @param config Game configuration - * @return true if screen needs refresh (drawing occurred) - */ -bool game_update(GameState* state, const InputEvent& input, const GameConfig& config); -``` - -### ☐ Step 3.3: Create game_draw() function -**Location**: After game_update() -**Action**: Draw game visuals based on state -```cpp -/** - * @brief Draw game graphics to screen buffer - * - * All drawing operations go here. - * Called only when game_update() returns true. - * - * @param state Pointer to current GameState - * @param renderer Renderer for drawing primitives - * @param gui GUI system for widgets (optional) - */ -void game_draw(const GameState* state, LowLevelRenderer* renderer, LowLevelGUI* gui); -``` - ---- - -## Phase 4: Refactor Main Function - -### ☐ Step 4.1: Move global variables into main() -**Location**: main() function, beginning -**Action**: Move state variables into main as local variables -- Convert `last_x`, `last_y`, `was_touched` into GameState -- Convert `touch_fail_count`, `touch_success_count` into GameState -- Keep only interrupt flags as globals - -### ☐ Step 4.2: Create and initialize GameConfig -**Location**: main() function, after display init -**Action**: Create GameConfig instance with sensible defaults -```cpp -GameConfig config = { - .touch_debounce_ms = 10, - .button_debounce_ms = 50, - .enable_gestures = true, - .enable_continuous_draw = true, - .debug_verbose = false -}; -``` - -### ☐ Step 4.3: Create and initialize GameState -**Location**: main() function, after GameConfig -**Action**: Create GameState and call game_init() -```cpp -GameState game_state; -game_init(&game_state); -``` - -### ☐ Step 4.4: Call game_draw() for initial screen -**Location**: main() function, replace GUI setup code -**Action**: Replace hardcoded GUI drawing with game_draw() call -- Remove lines creating windows, status bars, gauges -- Call `game_draw(&game_state, &renderer, &gui);` -- Keep initial refresh_screen() call - -### ☐ Step 4.5: Refactor main loop with reactive pattern -**Location**: main() while loop (lines ~321-423) -**Action**: Replace polling logic with clean reactive pattern -```cpp -// Reactive game loop pattern: -while (1) { - __wfi(); // Sleep until interrupt - - InputEvent input = {0}; - bool needs_refresh = false; - - // 1. Process button input first (higher priority) - input = process_button_input(config); - if (input.valid) { - needs_refresh = game_update(&game_state, input, config); - } - - // 2. Process touch input - if (!input.valid) { - input = process_touch_input(config, &last_touch_time); - if (input.valid) { - needs_refresh = game_update(&game_state, input, config); - } - } - - // 3. Draw and refresh only if needed - if (needs_refresh) { - game_draw(&game_state, &renderer, &gui); - refresh_screen(bit_buffer, display); - } -} -``` - ---- - -## Phase 5: Implement Function Bodies - -### ☐ Step 5.1: Implement process_touch_input() -**Action**: Move touch reading logic from main loop into this function -- Check `touch_interrupt_flag`, return invalid event if not set -- Implement debouncing using `last_time` parameter -- Read `TouchData` from touch controller -- Convert to `InputEvent` structure -- Handle `touch_event_down` flag for TOUCH_DOWN vs TOUCH_MOVE vs TOUCH_UP -- Return populated InputEvent - -### ☐ Step 5.2: Implement process_button_input() -**Action**: Move button handling logic from main loop into this function -- Check button flags (`button_key0_pressed`, `button_key1_pressed`) -- Implement debouncing with `sleep_ms()` and `gpio_get()` verification -- Clear flags after processing -- Return InputEvent with button_id set - -### ☐ Step 5.3: Implement get_gesture_name() -**Action**: Move gesture switch statement into this function -- Simple switch on gesture_code -- Return const char* string - -### ☐ Step 5.4: Implement game_init() -**Action**: Initialize GameState fields -```cpp -void game_init(GameState* state) { - state->last_x = -1; - state->last_y = -1; - state->is_drawing = false; - state->score = 0; - state->game_over = false; - state->frame_count = 0; - state->touch_success_count = 0; - state->touch_fail_count = 0; -} -``` - -### ☐ Step 5.5: Implement game_update() -**Action**: Move game logic (drawing lines) into this function -- Handle different InputEvent types -- Update GameState based on input -- Return true if drawing occurred (needs refresh) -- Example: For drawing game, draw line from last_x/y to current x/y - -### ☐ Step 5.6: Implement game_draw() -**Action**: Create initial UI or game graphics -- Can draw GUI elements (windows, gauges, etc.) -- Or draw game-specific graphics -- For drawing game, no additional drawing needed (done in game_update) -- Can add score display, game over screen, etc. - ---- - -## Phase 6: Documentation and Comments - -### ☐ Step 6.1: Update file header comment -**Action**: Change description to reflect template nature -- Update title to "Reactive Game Template" -- Add description of architecture -- Add usage instructions - -### ☐ Step 6.2: Add section separator comments -**Action**: Add clear comment blocks separating each section -```cpp -// ============================================================================ -// INPUT EVENT STRUCTURES -// ============================================================================ - -// ============================================================================ -// GAME STATE AND CONFIGURATION -// ============================================================================ - -// ============================================================================ -// INTERRUPT HANDLERS (Keep these minimal!) -// ============================================================================ - -// ============================================================================ -// INPUT PROCESSING -// ============================================================================ - -// ============================================================================ -// GAME LOGIC (Customize this section for your game!) -// ============================================================================ - -// ============================================================================ -// MAIN PROGRAM -// ============================================================================ -``` - -### ☐ Step 6.3: Add customization guide comments -**Action**: Add comments explaining what to modify for new games -```cpp -// ============================================================================ -// HOW TO CREATE YOUR OWN GAME: -// ============================================================================ -// 1. Modify GameState structure with your game variables -// 2. Implement game_init() to set initial values -// 3. Implement game_update() to handle input and update state -// 4. Implement game_draw() to render your game graphics -// 5. Adjust GameConfig for your game's needs -// 6. The reactive loop and input system work automatically! -// ============================================================================ -``` - ---- - -## Phase 7: Testing and Validation - -### ☐ Step 7.1: Verify compilation -**Action**: Build the project and fix any compilation errors -- Run `./build_and_flash.sh` -- Fix any syntax errors or missing definitions - -### ☐ Step 7.2: Test on hardware (if available) -**Action**: Flash to device and test input/drawing -- Verify touch input works -- Verify buttons work (on e-ink board) -- Verify display only updates on input - -### ☐ Step 7.3: Create example game variations -**Action**: Create commented-out example implementations in the file -- Add example game_update() for different game types -- Add example game_draw() for different visualizations -- Keep default drawing game as primary example - ---- - -## Phase 8: Create Template Documentation - -### ☐ Step 8.1: Create TEMPLATE_USAGE.md -**Action**: Write comprehensive guide for using the template -- Architecture overview -- How to customize for different games -- Example game ideas (snake, pong, tic-tac-toe, etc.) -- Input handling best practices -- E-ink optimization tips - -### ☐ Step 8.2: Update README.md -**Action**: Add section about the reactive game template -- Link to TEMPLATE_USAGE.md -- Highlight key features -- Show before/after code structure - ---- - -## Completion Checklist - -- [ ] All Phase 1 steps completed (Core Structures) -- [ ] All Phase 2 steps completed (Input System) -- [ ] All Phase 3 steps completed (Game Logic) -- [ ] All Phase 4 steps completed (Main Function) -- [ ] All Phase 5 steps completed (Function Bodies) -- [ ] All Phase 6 steps completed (Documentation) -- [ ] All Phase 7 steps completed (Testing) -- [ ] All Phase 8 steps completed (Template Documentation) - ---- - -## Notes for Implementation - -**Work one step at a time**: Each checkbox should be completed and verified before moving to the next. - -**Test incrementally**: After each phase, verify the code still compiles. - -**Keep it simple**: The template should be easy to understand and modify. - -**Optimize for e-ink**: Minimize refreshes, only redraw on input. - -**Power efficiency**: Use `__wfi()` to sleep between inputs. - -**Flexibility**: Template should work on all board types (TFT with touch, e-ink with buttons, etc.) diff --git a/TEMPLATE_USAGE.md b/TEMPLATE_USAGE.md new file mode 100644 index 0000000..c04f473 --- /dev/null +++ b/TEMPLATE_USAGE.md @@ -0,0 +1,543 @@ +# Reactive Game Template - Usage Guide + +## Overview + +This template provides a clean, event-driven architecture for building games and interactive applications on Raspberry Pi Pico (RP2350) with displays. It's designed to be efficient, power-conscious, and optimized for e-ink displays. + +## Architecture + +### Event-Driven Design + +``` +Interrupt (Touch/Button) → Set Flag → Main Loop Wakes → Process Input → Update Game State → Refresh Display → Sleep +``` + +The template follows a reactive pattern: +1. **Sleep**: CPU waits for interrupts using `__wfi()` (very power efficient) +2. **Wake**: Interrupt handler sets a flag and wakes CPU +3. **Process**: Main loop processes the input event +4. **Update**: Game logic updates state based on input +5. **Render**: Display refreshes only if needed +6. **Repeat**: Back to sleep + +### Key Components + +#### 1. Input Event System +- **InputEvent Structure**: Unified representation of all inputs +- **InputType Enum**: Touch (down/move/up), buttons, gestures +- **Interrupt Handlers**: Minimal ISRs that only set flags +- **Processing Functions**: `process_touch_input()` and `process_button_input()` + +#### 2. Game State Management +- **GameState Structure**: All game-specific data in one place +- **GameConfig Structure**: Configurable parameters (debounce, features, debug) +- **No Global Variables**: State is passed to functions explicitly + +#### 3. Game Logic Functions +- **game_init()**: Initialize game state at startup +- **game_update()**: Handle input events and update state +- **game_draw()**: Render UI and game graphics + +## Creating Your Own Game + +### Step 1: Define Your Game State + +Modify the `GameState` structure to hold your game-specific data: + +```cpp +struct GameState { + // Example for a Snake game + int snake_x[100]; + int snake_y[100]; + int snake_length; + int food_x; + int food_y; + int direction; + uint32_t score; + bool game_over; +}; +``` + +### Step 2: Initialize Your Game + +Implement `game_init()` to set initial values: + +```cpp +void game_init(GameState* state) { + // Snake starts in center + state->snake_x[0] = V_WIDTH / 2; + state->snake_y[0] = V_HEIGHT / 2; + state->snake_length = 3; + state->direction = 0; // Right + state->score = 0; + state->game_over = false; + // Place first food + place_random_food(state); +} +``` + +### Step 3: Handle Input + +Implement `game_update()` to respond to input events: + +```cpp +bool game_update(GameState* state, const InputEvent& input, const GameConfig& config, LowLevelRenderer* renderer) { + bool needs_refresh = false; + + switch (input.type) { + case INPUT_BUTTON_0: + // Turn left + state->direction = (state->direction + 3) % 4; + needs_refresh = true; + break; + + case INPUT_BUTTON_1: + // Turn right + state->direction = (state->direction + 1) % 4; + needs_refresh = true; + break; + + case INPUT_TOUCH_DOWN: + // Touch to restart if game over + if (state->game_over) { + game_init(state); + needs_refresh = true; + } + break; + } + + // Update snake position + update_snake_position(state); + check_collisions(state); + + return needs_refresh; +} +``` + +### Step 4: Draw Your Game + +Implement `game_draw()` to render graphics: + +```cpp +void game_draw(const GameState* state, LowLevelRenderer* renderer, LowLevelGUI* gui) { + // Draw game board + LowLevelWindow *w1 = gui->draw_new_window(10, 10, V_WIDTH - 20, V_HEIGHT - 20, "Snake Game"); + + // Draw snake + for (int i = 0; i < state->snake_length; i++) { + renderer->draw_filled_rectangle(state->snake_x[i], state->snake_y[i], 10, 10, true, 1); + } + + // Draw food + renderer->draw_filled_circle(state->food_x, state->food_y, 5, true); + + // Draw score + char score_text[20]; + snprintf(score_text, sizeof(score_text), "Score: %d", state->score); + renderer->draw_string(20, 30, score_text, true); + + // Game over message + if (state->game_over) { + renderer->draw_string(V_WIDTH/2 - 40, V_HEIGHT/2, "GAME OVER", true); + renderer->draw_string(V_WIDTH/2 - 50, V_HEIGHT/2 + 20, "Touch to restart", true); + } +} +``` + +### Step 5: Adjust Configuration + +Modify `GameConfig` in main() for your game's needs: + +```cpp +GameConfig config = { + .touch_debounce_ms = 50, // Slower for menu navigation + .button_debounce_ms = 100, // Longer for game controls + .enable_gestures = false, // Not needed for snake + .enable_continuous_draw = false, + .debug_verbose = true // Enable during development +}; +``` + +## Example Game Ideas + +### 1. Tic-Tac-Toe +- **Input**: Touch to place X/O, buttons to switch players +- **State**: 3x3 grid, current player, win condition +- **Drawing**: Grid lines, X and O symbols +- **Ideal for**: E-ink displays (few updates) + +### 2. Pong +- **Input**: Buttons to move paddle up/down +- **State**: Paddle positions, ball position/velocity, score +- **Drawing**: Paddles, ball, center line, score +- **Note**: May need timer-based updates for ball movement + +### 3. Memory Card Game +- **Input**: Touch cards to flip, buttons to navigate +- **State**: Card positions, flipped states, matches found +- **Drawing**: Card grid, symbols when flipped +- **Ideal for**: E-ink displays (turn-based) + +### 4. Calculator +- **Input**: Touch for number buttons, physical buttons for operations +- **State**: Current value, operation mode, history +- **Drawing**: Display, button grid +- **Perfect for**: E-ink displays (minimal updates) + +### 5. Drawing Board (Current Example) +- **Input**: Touch to draw, buttons to clear/undo +- **State**: Last position, stroke history +- **Drawing**: Lines following touch movement +- **Ideal for**: TFT displays (frequent updates) + +## Input Handling Best Practices + +### Touch Input +```cpp +case INPUT_TOUCH_DOWN: + // First touch - capture position + state->start_x = input.x; + state->start_y = input.y; + break; + +case INPUT_TOUCH_MOVE: + // Continuous drawing/dragging + if (config.enable_continuous_draw) { + draw_line(state->last_x, state->last_y, input.x, input.y); + } + break; + +case INPUT_TOUCH_UP: + // Touch released - finalize action + calculate_gesture(state->start_x, state->start_y, input.x, input.y); + break; +``` + +### Button Input +```cpp +case INPUT_BUTTON_0: + // First button - navigation/cancel + navigate_menu_prev(state); + break; + +case INPUT_BUTTON_1: + // Second button - selection/confirm + select_menu_item(state); + break; +``` + +### Gesture Input +```cpp +case INPUT_GESTURE: + switch(input.gesture_code) { + case 0x10: // Move Up + scroll_up(state); + break; + case 0x18: // Move Down + scroll_down(state); + break; + case 0x48: // Zoom In + increase_scale(state); + break; + } + break; +``` + +## E-Ink Display Optimization + +### Minimize Refreshes +- Only return `true` from `game_update()` when display actually changes +- Batch updates: collect multiple changes before refreshing +- Use partial refresh when available + +### Visual Design Tips +1. **High Contrast**: Use solid blacks and whites +2. **Clear Shapes**: Avoid thin lines (use 2px minimum) +3. **Large Text**: Use readable fonts (5x5 or larger) +4. **Simple Graphics**: Minimize complex patterns +5. **Static Elements**: Redraw only changed areas when possible + +### Example Pattern +```cpp +bool game_update(GameState* state, const InputEvent& input, const GameConfig& config, LowLevelRenderer* renderer) { + bool needs_refresh = false; + + // Collect all changes + if (input.type == INPUT_BUTTON_0) { + state->menu_index++; + needs_refresh = true; + } + + // Only redraw if something changed + return needs_refresh; +} +``` + +## Power Efficiency + +### Sleep Between Events +The template uses `__wfi()` to put CPU to sleep: +```cpp +while (1) { + __wfi(); // CPU sleeps here until interrupt + // Process input... +} +``` + +### Reduce Polling +- Use interrupt-driven input (already implemented) +- Avoid tight loops checking hardware +- Let ISRs wake the CPU only when needed + +### Display Power +```cpp +// For e-ink: Turn off display after inactivity +if (time_since_last_input > SLEEP_TIMEOUT) { + display->sleep(); +} +``` + +## GUI Components Available + +The template includes a full GUI system with these components: + +```cpp +// Windows +gui->draw_new_window(x, y, width, height, "Title"); + +// Buttons +gui->draw_button(window, x, y, "Label", pressed, rounded); + +// Checkboxes +gui->draw_checkbox(window, x, y, "Label", checked); + +// Radio Buttons +gui->draw_radio_button(window, x, y, "Label", selected); + +// Sliders +gui->draw_slider(window, x, y, width, height, position, "Label"); + +// Status Bars +gui->draw_status_bar(window, x, y, width, "Label", "Sublabel", percentage, "Value"); + +// Gauges +gui->draw_circular_gauge(window, x, y, width, "Label", percentage); + +// Text Boxes +gui->draw_textbox(window, x, y, width, height, "Content", focused); + +// Tabs +gui->draw_tab(window, x, y, width, height, "Label", selected); + +// Notifications +gui->draw_notification(window, x, y, width, "Time", "Message"); + +// Clock +gui->draw_large_clock(window, x, y, "12:30"); + +// Calendar +gui->draw_calendar(window, x, y, month, year); +``` + +## Debugging Tips + +### Enable Verbose Mode +```cpp +GameConfig config = { + .debug_verbose = true // Print debug messages +}; +``` + +### Monitor Input Events +```cpp +if (config.debug_verbose) { + printf("Input: type=%d x=%d y=%d\n", input.type, input.x, input.y); +} +``` + +### Check State Changes +```cpp +if (config.debug_verbose) { + printf("Score: %d, Lives: %d\n", state->score, state->lives); +} +``` + +### Serial Monitor +Connect via USB and monitor output: +```bash +screen /dev/cu.usbmodem101 +``` + +## Multi-Board Support + +The template works across different board configurations: + +- **Pico 2 with TFT + Touch**: Full interactive drawing +- **Pico 2 with E-ink + Buttons**: Button-based navigation +- **Feather boards**: Various display combinations + +Board-specific configuration is handled automatically through `board_config.h`. + +## Advanced Patterns + +### Timer-Based Updates +For games needing periodic updates (not just reactive): + +```cpp +// In main(), before loop: +uint32_t last_game_tick = 0; +const uint32_t TICK_INTERVAL_MS = 100; + +// In main loop: +uint32_t now = to_ms_since_boot(get_absolute_time()); +bool needs_tick = (now - last_game_tick >= TICK_INTERVAL_MS); + +if (needs_tick) { + // Update game logic (physics, AI, etc.) + update_game_tick(&game_state); + last_game_tick = now; + refresh_screen(bit_buffer, display); +} +``` + +### Animation +```cpp +// Smooth movement over multiple frames +void animate_sprite(GameState* state) { + state->sprite_x += state->velocity_x; + state->sprite_y += state->velocity_y; + state->frame_count++; +} +``` + +### State Machines +```cpp +enum GameMode { + MODE_MENU, + MODE_PLAYING, + MODE_PAUSED, + MODE_GAME_OVER +}; + +struct GameState { + GameMode mode; + // ... other fields +}; + +bool game_update(GameState* state, const InputEvent& input, ...) { + switch(state->mode) { + case MODE_MENU: + return handle_menu_input(state, input); + case MODE_PLAYING: + return handle_game_input(state, input); + case MODE_PAUSED: + return handle_pause_input(state, input); + case MODE_GAME_OVER: + return handle_gameover_input(state, input); + } +} +``` + +## Common Pitfalls + +### 1. Forgetting to Return True +```cpp +// Wrong: +bool game_update(...) { + state->score++; + // No return - screen won't refresh! +} + +// Right: +bool game_update(...) { + state->score++; + return true; // Signal refresh needed +} +``` + +### 2. Drawing in game_update() +```cpp +// Wrong: +bool game_update(...) { + renderer->draw_line(...); // Drawing here! + return true; +} + +// Right: +bool game_update(...) { + state->line_end_x = input.x; // Update state only + return true; +} +// Drawing happens in main loop when refresh is needed +``` + +### 3. Blocking in ISR +```cpp +// Wrong: +void touch_interrupt_handler(...) { + touch->read_touch(&data); // Slow I2C operation in ISR! + printf("Touch!\n"); // Serial output in ISR! +} + +// Right: +void touch_interrupt_handler(...) { + touch_interrupt_flag = true; // Just set flag +} +``` + +### 4. Not Clearing Buffer Before Redraw +```cpp +// When redrawing entire UI: +memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8); // Clear first +game_draw(&game_state, &renderer, &gui); // Then draw +``` + +## Performance Considerations + +### Memory Usage +- Frame buffer: `V_WIDTH * V_HEIGHT / 8` bytes (e.g., 13KB for 296x128) +- Keep GameState small for fast copying +- Use `uint8_t` instead of `int` where possible + +### CPU Usage +- Interrupt-driven design minimizes CPU usage +- `__wfi()` puts CPU to sleep between events +- Typical power draw: < 1mA while sleeping + +### Display Refresh Times +- E-ink: 1-4 seconds for full refresh +- TFT: < 50ms for full screen +- Partial updates much faster + +## Next Steps + +1. **Start Simple**: Begin with basic button navigation +2. **Add Features**: Gradually add game mechanics +3. **Test on Hardware**: Verify on your target board +4. **Optimize**: Tune debounce, refresh strategy for your needs +5. **Polish**: Add animations, sounds, save states + +## Example Projects + +Check out these example implementations: +- **Button Game** (current): Focus switching and click counting +- **Drawing Board**: Touch-based freehand drawing +- **Snake Game**: Classic snake with button controls +- **Calculator**: Touch-based number pad with operations + +## Resources + +- **Board Configs**: `board_configs/` directory +- **Display Drivers**: `display/` directory +- **Font Files**: `fonts/` directory +- **Refactoring Plan**: `REFACTORING_PLAN.md` - Implementation details + +## Support + +For issues or questions: +1. Check serial output with `screen /dev/cu.usbmodem101` +2. Enable `debug_verbose` in GameConfig +3. Review the refactoring plan for architecture details +4. Test with minimal game logic first + +Happy coding! 🎮 diff --git a/basic1.cpp b/basic1.cpp index 496bb0b..b2954d7 100644 --- a/basic1.cpp +++ b/basic1.cpp @@ -3,7 +3,35 @@ * * SPDX-License-Identifier: Apache-2.0 * - * 4.0" TFT ST7796 with Touch Screen and SD Card Demo + * ============================================================================ + * REACTIVE GAME TEMPLATE - Event-Driven Architecture for RP2350 + * ============================================================================ + * + * This template provides a clean, reactive architecture for building games + * and interactive applications on Raspberry Pi Pico with displays. + * + * KEY FEATURES: + * - 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 + * - Modular: Clear separation of input, game logic, and rendering + * + * ARCHITECTURE: + * 1. Interrupt handlers set flags (kept minimal) + * 2. Main loop processes input events + * 3. Game logic updates state based on events + * 4. Screen refreshes only when changes occur + * + * HOW TO CREATE YOUR OWN GAME: + * ============================================================================ + * 1. Modify GameState structure with your game variables + * 2. Implement game_init() to set initial values + * 3. Implement game_update() to handle input and update state + * 4. Implement game_draw() to render your game graphics + * 5. Adjust GameConfig for your game's needs + * 6. The reactive loop and input system work automatically! + * ============================================================================ */ #include "pico/stdlib.h" @@ -26,6 +54,72 @@ bi_decl(bi_program_description("4.0\" TFT ST7796 with Touch and SD Card Demo")); bi_decl(bi_program_version_string("0.1")); bi_decl(bi_program_build_date_string(__DATE__)); +// ============================================================================ +// INPUT EVENT STRUCTURES +// ============================================================================ + +// Input event types +enum InputType { + INPUT_NONE = 0, + INPUT_TOUCH_DOWN, + INPUT_TOUCH_MOVE, + INPUT_TOUCH_UP, + INPUT_BUTTON_0, + INPUT_BUTTON_1, + INPUT_GESTURE +}; + +// Unified input event structure +struct InputEvent { + InputType type; + int16_t x; + int16_t y; + uint8_t gesture_code; // For gesture events + uint8_t button_id; // For button events + uint8_t pressure; // Touch pressure/weight + bool valid; // Set to true if event is valid +}; + +// ============================================================================ +// GAME STATE AND CONFIGURATION +// ============================================================================ + +// Game state - customize this for your game +struct GameState { + // Drawing game state + int16_t last_x; + int16_t last_y; + bool is_drawing; + + // General game state + uint32_t score; + bool game_over; + uint32_t frame_count; + + // UI state + uint8_t progress_value; // Progress bar value (0-100) + uint8_t focused_button; // Which button has focus (0 or 1) + uint32_t button1_clicks; // Count clicks on button 1 + uint32_t button2_clicks; // Count clicks on button 2 + + // Statistics + uint32_t touch_success_count; + uint32_t touch_fail_count; +}; + +// Game configuration - adjust these for your game +struct GameConfig { + uint32_t touch_debounce_ms; // Touch polling rate + uint32_t button_debounce_ms; // Button debounce delay + bool enable_gestures; // Enable gesture recognition + bool enable_continuous_draw; // Allow continuous drawing while touched + bool debug_verbose; // Print debug messages +}; + +// ============================================================================ +// INTERRUPT HANDLERS (Keep these minimal!) +// ============================================================================ + // Touch interrupt handling volatile bool touch_interrupt_flag = false; volatile bool touch_event_down = false; @@ -54,42 +148,12 @@ void touch_interrupt_handler(uint gpio, uint32_t events) { // Main loop will handle the actual touch reading touch_interrupt_flag = true; - // Optional: track which edge triggered (for debugging) + // Track which edge triggered (down vs up) if (events & GPIO_IRQ_EDGE_FALL) { touch_event_down = true; - printf("Touch DOWN event detected\n"); } if (events & GPIO_IRQ_EDGE_RISE) { touch_event_down = false; - printf("Touch UP event detected\n"); - } - TouchData touch_data; - touch->read_touch(&touch_data); - - - int16_t x = touch_data.points[0].x; - int16_t y = touch_data.points[0].y; - uint8_t event = touch_data.points[0].event; - uint8_t id = touch_data.points[0].id; - uint8_t weight = touch_data.points[0].pressure; - uint8_t gesture = touch_data.gesture; - - // Display detailed touch information including weight and gesture - printf("Touch: X=%d Y=%d Event=%d ID=%d Weight=%d\n", - x, y, event, id, weight); - - // Display gesture if detected (non-zero) - if (gesture != 0) { - const char* gesture_name = "Unknown"; - switch(gesture) { - case 0x10: gesture_name = "Move Up"; break; - case 0x14: gesture_name = "Move Right"; break; - case 0x18: gesture_name = "Move Down"; break; - case 0x1C: gesture_name = "Move Left"; break; - case 0x48: gesture_name = "Zoom In"; break; - case 0x49: gesture_name = "Zoom Out"; break; - } - printf(" Gesture=0x%02X (%s)\n", gesture, gesture_name); } } @@ -110,12 +174,10 @@ void button_interrupt_handler(uint gpio, uint32_t events) { if (events & GPIO_IRQ_EDGE_FALL) { if (gpio == BUTTON_KEY0_PIN) { button_key0_pressed = true; - printf("Button KEY0 pressed\n"); } #ifdef BUTTON_KEY1_PIN else if (gpio == BUTTON_KEY1_PIN) { button_key1_pressed = true; - printf("Button KEY1 pressed\n"); } #endif } @@ -145,6 +207,322 @@ void refresh_screen(const uint8_t *buffer, LowLevelDisplay* display) { display->refresh(); } +// ============================================================================ +// INPUT PROCESSING +// ============================================================================ + +/** + * @brief Get human-readable gesture name + * + * @param gesture_code Gesture code from touch controller + * @return Constant string with gesture name + */ +const char* get_gesture_name(uint8_t gesture_code) { + switch(gesture_code) { + case 0x10: return "Move Up"; + case 0x14: return "Move Right"; + case 0x18: return "Move Down"; + case 0x1C: return "Move Left"; + case 0x48: return "Zoom In"; + case 0x49: return "Zoom Out"; + default: return "Unknown"; + } +} + +/** + * @brief Process touch input and convert to InputEvent + * + * Reads touch data from controller and creates appropriate InputEvent. + * Handles debouncing and filtering internally. + * + * @param config Game configuration + * @param last_time Pointer to last touch time for debouncing + * @return InputEvent structure (valid=false if no valid input) + */ +InputEvent process_touch_input(const GameConfig& config, uint32_t* last_time) { + InputEvent event = {INPUT_NONE, 0, 0, 0, 0, 0, false}; + + // Check if touch interrupt flag is set + if (!touch_interrupt_flag) { + return event; // No touch event + } + + // Don't clear the flag yet - we may still be processing continuous touch + + // Check if touch is active + if (!touch_event_down) { + // Touch released + touch_interrupt_flag = false; + event.type = INPUT_TOUCH_UP; + event.valid = true; + return event; + } + + // Touch is down - check debounce timing + uint32_t now = to_ms_since_boot(get_absolute_time()); + if (now - *last_time < config.touch_debounce_ms) { + return event; // Too soon, skip + } + + // Read touch data + TouchData touch_data; + if (!touch || !touch->read_touch(&touch_data)) { + return event; // Read failed + } + + // Populate event structure + event.x = touch_data.points[0].x; + event.y = touch_data.points[0].y; + event.pressure = touch_data.points[0].pressure; + event.gesture_code = touch_data.gesture; + event.valid = true; + + // Determine event type + if (*last_time == 0) { + event.type = INPUT_TOUCH_DOWN; + } else { + event.type = INPUT_TOUCH_MOVE; + } + + // Handle gesture events + if (config.enable_gestures && touch_data.gesture != 0) { + event.type = INPUT_GESTURE; + if (config.debug_verbose) { + printf("Gesture: 0x%02X (%s)\n", event.gesture_code, get_gesture_name(event.gesture_code)); + } + } + + *last_time = now; + return event; +} + +/** + * @brief Process button input and convert to InputEvent + * + * Checks button flags and verifies button state with debouncing. + * Clears flags after processing. + * + * @param config Game configuration + * @return InputEvent structure (valid=false if no valid input) + */ +InputEvent process_button_input(const GameConfig& config) { + InputEvent event = {INPUT_NONE, 0, 0, 0, 0, 0, false}; + +#ifdef BUTTON_KEY0_PIN + // Check KEY0 + if (button_key0_pressed) { + button_key0_pressed = false; + sleep_ms(config.button_debounce_ms); + + if (gpio_get(BUTTON_KEY0_PIN) == 0) { // Verify still pressed + event.type = INPUT_BUTTON_0; + event.button_id = 0; + event.valid = true; + + if (config.debug_verbose) { + printf("Button KEY0 action triggered\n"); + } + return event; + } + } + +#ifdef BUTTON_KEY1_PIN + // Check KEY1 + if (button_key1_pressed) { + button_key1_pressed = false; + sleep_ms(config.button_debounce_ms); + + if (gpio_get(BUTTON_KEY1_PIN) == 0) { // Verify still pressed + event.type = INPUT_BUTTON_1; + event.button_id = 1; + event.valid = true; + + if (config.debug_verbose) { + printf("Button KEY1 action triggered\n"); + } + return event; + } + } +#endif +#endif + + return event; +} + +// ============================================================================ +// GAME LOGIC (Customize this section for your game!) +// ============================================================================ + +/** + * @brief Initialize game state + * + * Called once at startup to set initial game values. + * Customize this for your game. + * + * @param state Pointer to GameState to initialize + */ +void game_init(GameState* state) { + state->last_x = -1; + state->last_y = -1; + state->is_drawing = false; + state->score = 0; + state->game_over = false; + state->frame_count = 0; + state->progress_value = 50; // Start at 50% + state->focused_button = 0; // Start with first button focused + state->button1_clicks = 0; + state->button2_clicks = 0; + state->touch_success_count = 0; + state->touch_fail_count = 0; +} + +/** + * @brief Update game state based on input event + * + * This is where your game logic goes. + * Called whenever an input event occurs. + * + * @param state Pointer to GameState to update + * @param input Input event to process + * @param config Game configuration + * @param renderer Renderer for drawing operations + * @return true if screen needs refresh (drawing occurred) + */ +bool game_update(GameState* state, const InputEvent& input, const GameConfig& config, LowLevelRenderer* renderer) { + bool needs_refresh = false; + + switch (input.type) { + case INPUT_TOUCH_DOWN: + // Start new drawing stroke + state->last_x = input.x; + state->last_y = input.y; + state->is_drawing = true; + state->touch_success_count++; + break; + + case INPUT_TOUCH_MOVE: + // Continue drawing stroke + if (config.enable_continuous_draw && state->is_drawing) { + if (state->last_x >= 0 && state->last_y >= 0) { + // Draw line from last position + renderer->draw_line(state->last_x, state->last_y, input.x, input.y, true); + needs_refresh = true; + } + state->last_x = input.x; + state->last_y = input.y; + state->touch_success_count++; + } + break; + + case INPUT_TOUCH_UP: + // End drawing stroke + state->is_drawing = false; + state->last_x = -1; + state->last_y = -1; + needs_refresh = true; // Final refresh to show complete stroke + break; + + case INPUT_BUTTON_0: + // KEY0: Switch focus between buttons + state->focused_button = (state->focused_button == 0) ? 1 : 0; + needs_refresh = true; + if (config.debug_verbose) { + printf("Focus switched to button %d\n", state->focused_button); + } + break; + + case INPUT_BUTTON_1: + // KEY1: Activate the focused button + if (state->focused_button == 0) { + state->button1_clicks++; + if (config.debug_verbose) { + printf("Button 1 clicked! Total: %d\n", state->button1_clicks); + } + } else { + state->button2_clicks++; + if (config.debug_verbose) { + printf("Button 2 clicked! Total: %d\n", state->button2_clicks); + } + } + needs_refresh = true; + break; + + case INPUT_GESTURE: + // Handle gesture + if (config.debug_verbose) { + printf("Gesture detected: %s\n", get_gesture_name(input.gesture_code)); + } + // Add gesture-specific actions here + break; + + default: + break; + } + + state->frame_count++; + return needs_refresh; +} + +/** + * @brief Draw game graphics to screen buffer + * + * All initial UI drawing operations go here. + * Called once at startup to create the initial screen. + * + * @param state Pointer to current GameState + * @param renderer Renderer for drawing primitives + * @param gui GUI system for widgets (optional) + */ +void game_draw(const GameState* state, LowLevelRenderer* renderer, LowLevelGUI* gui) { + // Draw main window + LowLevelWindow *w1 = gui->draw_new_window(10, 10, V_WIDTH - 20, V_HEIGHT - 20, "Button Game"); + + // Draw instructions using text + renderer->set_font(&font_5x5_obj); + renderer->draw_string(20, 50, "KEY0: Switch Focus", true); + renderer->draw_string(20, 65, "KEY1: Click Button", true); + + // Create button labels with click counts + char btn1_label[30]; + snprintf(btn1_label, sizeof(btn1_label), "BTN 1 (%d)", state->button1_clicks); + + char btn2_label[30]; + snprintf(btn2_label, sizeof(btn2_label), "BTN 2 (%d)", state->button2_clicks); + + // Draw Button 1 using GUI button element + // pressed=true shows it's focused/selected + gui->draw_button(w1, 10, 90, btn1_label, state->focused_button == 0, true); + + // Draw Button 2 using GUI button element + gui->draw_button(w1, 10, 140, btn2_label, state->focused_button == 1, true); + + // Draw status indicators using GUI elements + // Show which button is focused + if (state->focused_button == 0) { + gui->draw_radio_button(w1, 200, 100, "Active", true); + } else { + gui->draw_radio_button(w1, 200, 100, "Active", false); + } + + if (state->focused_button == 1) { + gui->draw_radio_button(w1, 200, 150, "Active", true); + } else { + gui->draw_radio_button(w1, 200, 150, "Active", false); + } + + // Show total interactions with a status bar + uint32_t total_clicks = state->button1_clicks + state->button2_clicks; + int percentage = (total_clicks > 0) ? ((state->button1_clicks * 100) / total_clicks) : 50; + + char total_str[20]; + snprintf(total_str, sizeof(total_str), "%d", total_clicks); + gui->draw_status_bar(w1, 10, 200, 270, "TOTAL CLICKS", "BTN1 vs BTN2 Ratio", percentage, total_str); +} + +// ============================================================================ +// MAIN PROGRAM +// ============================================================================ + @@ -191,13 +569,26 @@ int main() // Now clear to black for drawing display->clear(false); // Clear to black + // Initialize renderer and GUI system LowLevelRenderer renderer(bit_buffer, V_WIDTH, V_HEIGHT); renderer.set_font(&font_5x5_obj); LowLevelGUI gui = LowLevelGUI(&renderer, font_BMplain_obj); - LowLevelWindow *w1 = gui.draw_new_window(15, 15, V_WIDTH - 30, V_HEIGHT - 30, "Main Window"); - gui.draw_status_bar(w1, 10, 40, 200, - "PANELS", "Weekly Average Charge", 65, "190KWH"); - gui.draw_circular_gauge(w1, 10, 100 - 10, 200, "SYSTEM EFF.", 68); + + // Initialize game configuration + GameConfig config = { + .touch_debounce_ms = 10, + .button_debounce_ms = 50, + .enable_gestures = true, + .enable_continuous_draw = true, + .debug_verbose = false + }; + + // Initialize game state + GameState game_state; + game_init(&game_state); + + // Draw initial game graphics + game_draw(&game_state, &renderer, &gui); // Refresh the screen with the rendered GUI refresh_screen(bit_buffer, display); @@ -260,131 +651,47 @@ int main() // printf("SD Card initialization failed or no card present\n"); // } - // Main loop - handle touch events - int last_x = -1, last_y = -1; + // ======================================================================== + // REACTIVE GAME LOOP + // ======================================================================== + // The loop sleeps until an interrupt occurs, then: + // 1. Process input (button or touch) + // 2. Update game state based on input + // 3. Redraw only if game_update() indicates changes occurred + // This is ideal for e-ink displays (minimal refreshes) and power efficiency + // ======================================================================== - // Touch debouncing uint32_t last_touch_time = 0; - const uint32_t debounce_ms = 10; // Poll touch every 10ms (100 times per second) - bool was_touched = false; - int touch_fail_count = 0; - int touch_success_count = 0; while (1) { // Sleep until interrupt wakes us up (very power efficient!) - // Te(); // Wait For Event - CPU sleeps until interrupt or evenurs - __wfi(); // Wait For Interrupt - CPU sleeps until any interrupt + __wfi(); // Wait For Interrupt - CPU sleeps until any interrupt occurs -#ifdef BUTTON_KEY0_PIN - // Handle button presses with debouncing - if (button_key0_pressed) { - button_key0_pressed = false; - sleep_ms(50); // Debounce delay - if (gpio_get(BUTTON_KEY0_PIN) == 0) { // Verify button still pressed - printf("Button KEY0 action triggered\n"); - // TODO: Add your KEY0 action here (e.g., focus next widget) + InputEvent input = {INPUT_NONE, 0, 0, 0, 0, 0, false}; + bool needs_refresh = false; + + // 1. Process button input first (higher priority) + input = process_button_input(config); + if (input.valid) { + needs_refresh = game_update(&game_state, input, config, &renderer); + } + + // 2. Process touch input (if no button was pressed) + if (!input.valid) { + input = process_touch_input(config, &last_touch_time); + if (input.valid) { + needs_refresh = game_update(&game_state, input, config, &renderer); } } - if (button_key1_pressed) { - button_key1_pressed = false; - sleep_ms(50); // Debounce delay - if (gpio_get(BUTTON_KEY1_PIN) == 0) { // Verify button still pressed - printf("Button KEY1 action triggered\n"); - // TODO: Add your KEY1 action here (e.g., activate focused widget) + // 3. Redraw and refresh screen only if needed + if (needs_refresh) { + // For button presses or touch release, redraw entire UI + if (input.type == INPUT_BUTTON_0 || input.type == INPUT_BUTTON_1 || input.type == INPUT_TOUCH_UP) { + // Clear buffer and redraw entire UI with updated state + memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8); + game_draw(&game_state, &renderer, &gui); } - } -#endif - - // Check if our touch interrupt flag was set - if (!touch_interrupt_flag) { - continue; // Woken by different interrupt, go back to sleep - } - - // Clear the flag - touch_interrupt_flag = false; - - while(touch_event_down){ - uint32_t now = to_ms_since_boot(get_absolute_time()); - - // Check if enough time has passed since last touch check (debounce) - if (now - last_touch_time < debounce_ms) { - //continue; - } - //printf("Touch interrupt event detected (event_down=%d)\n", touch_event_down); - - // Touch interrupt occurred - read the data - // is_touched() will check INT pin and confirm via I2C if needed - //if (touch && touch->is_touched()) { - // Now read full touch data via I2C (already confirmed by INT pin) - TouchData touch_data; - if (!touch->read_touch(&touch_data)) { - // Read failed or no actual touch data - touch_fail_count++; - //was_touched = false; - //last_x = -1; - //last_y = -1; - //last_touch_time = now; - continue; - } - - touch_success_count++; - - int16_t x = touch_data.points[0].x; - int16_t y = touch_data.points[0].y; - uint8_t event = touch_data.points[0].event; - uint8_t id = touch_data.points[0].id; - uint8_t weight = touch_data.points[0].pressure; - uint8_t gesture = touch_data.gesture; - - // Display detailed touch information including weight and gesture - // printf("Touch: X=%d Y=%d Event=%d ID=%d Weight=%d", - // x, y, event, id, weight); - - // Display gesture if detected (non-zero) - if (gesture != 0) { - const char* gesture_name = "Unknown"; - switch(gesture) { - case 0x10: gesture_name = "Move Up"; break; - case 0x14: gesture_name = "Move Right"; break; - case 0x18: gesture_name = "Move Down"; break; - case 0x1C: gesture_name = "Move Left"; break; - case 0x48: gesture_name = "Zoom In"; break; - case 0x49: gesture_name = "Zoom Out"; break; - } - printf(" Gesture=0x%02X (%s)", gesture, gesture_name); - } - - // printf(" [Success:%d Fail:%d]\n", touch_success_count, touch_fail_count); - - - // Check if touch is in title area to clear screen - - // Draw line from last position (for smooth drawing) - if (last_x >= 0 && last_y >= 0) { - int dx = abs(x - last_x); - int dy = abs(y - last_y); - // Only draw line if movement is reasonable (filter noise) - //if (dx < 50 && dy < 50) { - renderer.draw_line(last_x, last_y, x, y, true); - - //} - } - - last_x = x; - last_y = y; - - was_touched = true; - last_touch_time = now; - //} else { - // INT pin triggered but no touch data (likely release event) - - //} - } - if (was_touched) { - last_x = -1; - last_y = -1; - was_touched = false; refresh_screen(bit_buffer, display); } } diff --git a/display/low_level_display_epaper.cpp b/display/low_level_display_epaper.cpp index 4020f0c..5d350df 100644 --- a/display/low_level_display_epaper.cpp +++ b/display/low_level_display_epaper.cpp @@ -93,13 +93,13 @@ void LowLevelDisplayEPaper::refresh() { return; } - printf("Refreshing e-paper display (partial refresh)...\n"); + // printf("Refreshing e-paper display (partial refresh)...\n"); // Use partial refresh for fast updates (~1 second instead of ~15 seconds) // Partial refresh updates only the changed pixels EPD_4IN2_V2_PartialDisplay(framebuffer, 0, 0, EPD_4IN2_V2_WIDTH, EPD_4IN2_V2_HEIGHT); - printf("E-paper partial refresh complete\n"); + // printf("E-paper partial refresh complete\n"); dirty = false; }