diff --git a/CMakeLists.txt b/CMakeLists.txt index 837fe66..827d1b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,7 @@ target_include_directories(basic1 PRIVATE target_link_libraries(basic1 hardware_spi hardware_i2c + pico_multicore m ) diff --git a/basic1.cpp b/basic1.cpp index b2954d7..507b045 100644 --- a/basic1.cpp +++ b/basic1.cpp @@ -37,6 +37,7 @@ #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" #include @@ -54,6 +55,80 @@ 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__)); +// ============================================================================ +// DUAL-CORE DISPLAY REFRESH SYSTEM +// ============================================================================ + +// Shared variables for core communication +volatile bool refresh_requested = false; +volatile bool refresh_in_progress = false; +const uint8_t* volatile refresh_buffer = nullptr; +LowLevelDisplay* volatile refresh_display = nullptr; + +/** + * @brief Core 1 entry point - handles display refresh operations + * + * Runs on the second core, waiting for refresh requests. + * This keeps Core 0 responsive while display updates happen in background. + */ +void core1_entry() { + printf("Core 1 started - handling display refreshes\n"); + + while (1) { + // Wait for refresh request + if (refresh_requested && refresh_buffer && refresh_display) { + refresh_in_progress = true; + + // Get local copies for safe access + LowLevelDisplay* display = refresh_display; + const uint8_t* buffer = refresh_buffer; + + // Perform the refresh operation (may be slow for e-ink) + display->draw_buffer(buffer); + display->refresh(); + + // Clear flags + refresh_requested = false; + refresh_in_progress = false; + } + + // Small delay to avoid busy-waiting + sleep_us(100); + } +} + +/** + * @brief Request a screen refresh (non-blocking) + * + * Queues the refresh on Core 1, keeping Core 0 responsive. + * + * @param buffer Pointer to 1-bit framebuffer + * @param display Pointer to display abstraction + * @return true if refresh started, false if already in progress + */ +bool refresh_screen_async(const uint8_t *buffer, LowLevelDisplay* display) { + // Check if Core 1 is busy with previous refresh + if (refresh_in_progress) { + // Still refreshing previous frame, skip this one + return false; + } + + // Queue refresh on Core 1 + refresh_buffer = buffer; + refresh_display = display; + refresh_requested = true; + + return true; +} + +/** + * @brief Check if a refresh is currently in progress + * @return true if Core 1 is still refreshing + */ +bool is_refresh_in_progress() { + return refresh_in_progress; +} + // ============================================================================ // INPUT EVENT STRUCTURES // ============================================================================ @@ -86,25 +161,19 @@ struct InputEvent { // 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; + // Tic-Tac-Toe game state + uint8_t board[3][3]; // 0=empty, 1=X, 2=O + uint8_t current_player; // 1=X, 2=O + uint8_t winner; // 0=none, 1=X wins, 2=O wins, 3=tie + uint8_t selected_row; // Currently selected cell + uint8_t selected_col; 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 statistics + uint32_t x_wins; + uint32_t o_wins; + uint32_t ties; + uint32_t total_moves; }; // Game configuration - adjust these for your game @@ -151,9 +220,11 @@ void touch_interrupt_handler(uint gpio, uint32_t events) { // Track which edge triggered (down vs up) if (events & GPIO_IRQ_EDGE_FALL) { touch_event_down = true; + printf("INT: FALL\n"); } if (events & GPIO_IRQ_EDGE_RISE) { touch_event_down = false; + printf("INT: RISE\n"); } } @@ -247,14 +318,18 @@ InputEvent process_touch_input(const GameConfig& config, uint32_t* last_time) { return event; // No touch event } + printf("Processing touch: flag=%d, event_down=%d\n", touch_interrupt_flag, touch_event_down); + // 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 released - reset timing for next touch touch_interrupt_flag = false; + *last_time = 0; // Reset so next touch is treated as new touch-down event.type = INPUT_TOUCH_UP; event.valid = true; + printf("Touch UP\n"); return event; } @@ -267,9 +342,18 @@ InputEvent process_touch_input(const GameConfig& config, uint32_t* last_time) { // Read touch data TouchData touch_data; if (!touch || !touch->read_touch(&touch_data)) { + // Clear flag even if read failed to prevent getting stuck + touch_interrupt_flag = false; + printf("Touch read FAILED\n"); return event; // Read failed } + // Clear the interrupt flag after successfully reading touch data + // This allows the next touch interrupt to be detected + touch_interrupt_flag = false; + + printf("Touch DOWN at (%d,%d)\n", touch_data.points[0].x, touch_data.points[0].y); + // Populate event structure event.x = touch_data.points[0].x; event.y = touch_data.points[0].y; @@ -353,6 +437,63 @@ InputEvent process_button_input(const GameConfig& config) { // GAME LOGIC (Customize this section for your game!) // ============================================================================ +// Game board dimensions (used for both drawing and touch detection) +const int BOARD_SIZE = 200; +const int CELL_SIZE = BOARD_SIZE / 3; +const int BOARD_Y = 80; // Y position below title + +/** + * @brief Check if there's a winner on the board + * @return 0=no winner, 1=X wins, 2=O wins, 3=tie + */ +uint8_t check_winner(const GameState* state) { + // Check rows + for (int row = 0; row < 3; row++) { + if (state->board[row][0] != 0 && + state->board[row][0] == state->board[row][1] && + state->board[row][1] == state->board[row][2]) { + return state->board[row][0]; + } + } + + // Check columns + for (int col = 0; col < 3; col++) { + if (state->board[0][col] != 0 && + state->board[0][col] == state->board[1][col] && + state->board[1][col] == state->board[2][col]) { + return state->board[0][col]; + } + } + + // Check diagonals + if (state->board[0][0] != 0 && + state->board[0][0] == state->board[1][1] && + state->board[1][1] == state->board[2][2]) { + return state->board[0][0]; + } + + if (state->board[0][2] != 0 && + state->board[0][2] == state->board[1][1] && + state->board[1][1] == state->board[2][0]) { + return state->board[0][2]; + } + + // Check for tie (board full) + bool board_full = true; + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + if (state->board[row][col] == 0) { + board_full = false; + break; + } + } + if (!board_full) break; + } + + if (board_full) return 3; // Tie + return 0; // No winner yet +} + /** * @brief Initialize game state * @@ -362,18 +503,22 @@ InputEvent process_button_input(const GameConfig& config) { * @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; + // Clear the board + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + state->board[row][col] = 0; + } + } + + state->current_player = 1; // X starts + state->winner = 0; + state->selected_row = 1; // Start in center + state->selected_col = 1; 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; + state->total_moves = 0; + + // Keep win statistics across games + // state->x_wins, state->o_wins, state->ties remain unchanged } /** @@ -392,74 +537,124 @@ bool game_update(GameState* state, const InputEvent& input, const GameConfig& co 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_DOWN: { + // If game is over, restart on touch + if (state->game_over) { + game_init(state); + needs_refresh = true; + break; + } + + printf("Touch down at (%d,%d)\n", input.x, input.y); - 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; + // Calculate board position (must match game_draw!) + int board_x = V_WIDTH - BOARD_SIZE - 20; + + // Check if touch is within board + if (input.x >= board_x && input.x < board_x + BOARD_SIZE && + input.y >= BOARD_Y && input.y < BOARD_Y + BOARD_SIZE) { + + int touched_col = (input.x - board_x) / CELL_SIZE; + int touched_row = (input.y - BOARD_Y) / CELL_SIZE; + + // Clamp to valid range (safety check) + if (touched_row >= 0 && touched_row < 3 && touched_col >= 0 && touched_col < 3) { + // Place piece if cell is empty + if (state->board[touched_row][touched_col] == 0) { + state->board[touched_row][touched_col] = state->current_player; + state->total_moves++; + + // Check for winner + state->winner = check_winner(state); + if (state->winner != 0) { + state->game_over = true; + if (state->winner == 1) state->x_wins++; + else if (state->winner == 2) state->o_wins++; + else if (state->winner == 3) state->ties++; + } else { + // Switch player + state->current_player = (state->current_player == 1) ? 2 : 1; + } + + needs_refresh = true; + + if (config.debug_verbose) { + printf("Touch at [%d,%d] (pixel %d,%d) by player %d\n", + touched_row, touched_col, input.x, input.y, + state->current_player == 1 ? 2 : 1); + } + } else if (config.debug_verbose) { + printf("Cell [%d,%d] already occupied\n", touched_row, touched_col); + } } - state->last_x = input.x; - state->last_y = input.y; - state->touch_success_count++; + } else if (config.debug_verbose) { + printf("Touch outside board: %d,%d\n", input.x, input.y); } 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); + // KEY0: Move selection (for button-only boards) + if (!state->game_over) { + // Move to next cell, skipping occupied ones + int attempts = 0; + do { + state->selected_col++; + if (state->selected_col > 2) { + state->selected_col = 0; + state->selected_row++; + if (state->selected_row > 2) { + state->selected_row = 0; + } + } + attempts++; + // If we've tried all 9 cells, stop (game might be full) + if (attempts >= 9) break; + } while (state->board[state->selected_row][state->selected_col] != 0); + + needs_refresh = true; + if (config.debug_verbose) { + printf("Selection moved to [%d,%d]\n", state->selected_row, state->selected_col); + } + } else { + // Restart game + game_init(state); + needs_refresh = true; } 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); + // KEY1: Place piece at selected position + if (!state->game_over) { + if (state->board[state->selected_row][state->selected_col] == 0) { + state->board[state->selected_row][state->selected_col] = state->current_player; + state->total_moves++; + + // Check for winner + state->winner = check_winner(state); + if (state->winner != 0) { + state->game_over = true; + if (state->winner == 1) state->x_wins++; + else if (state->winner == 2) state->o_wins++; + else if (state->winner == 3) state->ties++; + } else { + // Switch player + state->current_player = (state->current_player == 1) ? 2 : 1; + } + + needs_refresh = true; + + if (config.debug_verbose) { + printf("Piece placed at [%d,%d]\n", state->selected_row, state->selected_col); + } } } - 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; } @@ -475,48 +670,99 @@ bool game_update(GameState* state, const InputEvent& input, const GameConfig& co */ 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"); + LowLevelWindow *w1 = gui->draw_new_window(10, 10, V_WIDTH - 20, V_HEIGHT - 20, "Tic-Tac-Toe"); - // 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); + // Draw current player or game result + if (state->game_over) { + if (state->winner == 1) { + renderer->draw_string(20, 40, "X WINS!", true); + } else if (state->winner == 2) { + renderer->draw_string(20, 40, "O WINS!", true); + } else { + renderer->draw_string(20, 40, "TIE GAME!", true); + } + renderer->draw_string(20, 55, "Touch or KEY0 to restart", true); } else { - gui->draw_radio_button(w1, 200, 100, "Active", false); + char turn_text[30]; + snprintf(turn_text, sizeof(turn_text), "Turn: %s", state->current_player == 1 ? "X" : "O"); + renderer->draw_string(20, 40, turn_text, true); + renderer->draw_string(20, 55, "Touch cell or use keys", true); } - if (state->focused_button == 1) { - gui->draw_radio_button(w1, 200, 150, "Active", true); - } else { - gui->draw_radio_button(w1, 200, 150, "Active", false); + // Draw game board (use same layout as touch detection!) + int board_x = V_WIDTH - BOARD_SIZE - 20; + + // Draw current turn indicator (large, on left side) + if (!state->game_over) { + int indicator_x = 60; + int indicator_y = V_HEIGHT / 2; + int piece_offset = CELL_SIZE / 4; + + if (state->current_player == 1) { + // Draw large X + renderer->draw_line(indicator_x - piece_offset, indicator_y - piece_offset, + indicator_x + piece_offset, indicator_y + piece_offset, true, 4); + renderer->draw_line(indicator_x + piece_offset, indicator_y - piece_offset, + indicator_x - piece_offset, indicator_y + piece_offset, true, 4); + } else { + // Draw large O + int piece_radius = CELL_SIZE / 4; + renderer->draw_circle(indicator_x, indicator_y, piece_radius, true); + renderer->draw_circle(indicator_x, indicator_y, piece_radius - 1, true); + renderer->draw_circle(indicator_x, indicator_y, piece_radius - 2, true); + renderer->draw_circle(indicator_x, indicator_y, piece_radius - 3, true); + } + } + // Draw grid lines + for (int i = 1; i < 3; i++) { + // Vertical lines + renderer->draw_line(board_x + i * CELL_SIZE, BOARD_Y, + board_x + i * CELL_SIZE, BOARD_Y + BOARD_SIZE, true, 2); + // Horizontal lines + renderer->draw_line(board_x, BOARD_Y + i * CELL_SIZE, + board_x + BOARD_SIZE, BOARD_Y + i * CELL_SIZE, true, 2); } - // 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; + // Draw outer border + renderer->draw_rectangle(board_x, BOARD_Y, BOARD_SIZE, BOARD_SIZE, true, 3); - 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); + // Draw X's and O's + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + int cell_x = board_x + col * CELL_SIZE; + int cell_y = BOARD_Y + row * CELL_SIZE; + int center_x = cell_x + CELL_SIZE / 2; + int center_y = cell_y + CELL_SIZE / 2; + + // Highlight selected cell (for button navigation) + if (!state->game_over && row == state->selected_row && col == state->selected_col) { + renderer->draw_rectangle(cell_x + 5, cell_y + 5, CELL_SIZE - 10, CELL_SIZE - 10, true, 1); + } + + if (state->board[row][col] == 1) { + // Draw X + int offset = CELL_SIZE / 4; + renderer->draw_line(center_x - offset, center_y - offset, + center_x + offset, center_y + offset, true, 3); + renderer->draw_line(center_x + offset, center_y - offset, + center_x - offset, center_y + offset, true, 3); + } else if (state->board[row][col] == 2) { + // Draw O + int radius = CELL_SIZE / 4; + renderer->draw_circle(center_x, center_y, radius, true); + renderer->draw_circle(center_x, center_y, radius - 1, true); + renderer->draw_circle(center_x, center_y, radius - 2, true); + } + } + } + + // Draw statistics at bottom + char stats[60]; + snprintf(stats, sizeof(stats), "X:%d O:%d Tie:%d Moves:%d", + state->x_wins, state->o_wins, state->ties, state->total_moves); + renderer->draw_string(20, V_HEIGHT - 40, stats, true); } // ============================================================================ @@ -534,6 +780,7 @@ int main() sleep_ms(5000); // Wait for USB connection (if present) printf("\n=== %s Demo ===\n", BOARD_NAME); + printf("Starting dual-core system...\n"); // Create display abstraction using factory method // The factory handles all board-specific configuration internally @@ -544,7 +791,7 @@ int main() return -1; } - printf("Initializing 4.0\" TFT with Touch and SD Card...\n"); + printf("Initializing display...\n"); // Initialize the display if (!display->init()) { @@ -553,6 +800,11 @@ int main() return -1; } + // Launch Core 1 for display refresh handling + printf("Launching Core 1 for display refresh...\n"); + multicore_launch_core1(core1_entry); + sleep_ms(100); // Give Core 1 time to start + // Do a full refresh with white screen first (removes ghosting on e-paper) printf("Performing initial full refresh to white...\n"); display->clear(true); // Clear to white @@ -577,7 +829,7 @@ int main() // Initialize game configuration GameConfig config = { .touch_debounce_ms = 10, - .button_debounce_ms = 50, + .button_debounce_ms = 20, .enable_gestures = true, .enable_continuous_draw = true, .debug_verbose = false @@ -585,13 +837,18 @@ int main() // Initialize game state GameState game_state; + // Initialize statistics (persists across game restarts) + game_state.x_wins = 0; + game_state.o_wins = 0; + game_state.ties = 0; 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); + // Refresh the screen with the rendered GUI (async on Core 1) + refresh_screen_async(bit_buffer, display); + printf("Initial screen refresh queued on Core 1\n"); // Initialize touch screen using abstraction touch = LowLevelTouch::create((TouchType)TOUCH_TYPE_SELECTED, V_WIDTH, V_HEIGHT, @@ -652,16 +909,23 @@ int main() // } // ======================================================================== - // REACTIVE GAME LOOP + // REACTIVE GAME LOOP WITH DUAL-CORE REFRESH // ======================================================================== + // 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: // 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 + // 3. Queue refresh on Core 1 (non-blocking) + // This keeps Core 0 responsive even during slow e-ink refreshes // ======================================================================== uint32_t last_touch_time = 0; + bool pending_refresh = false; // Track if we have a pending refresh + + printf("\nEntering reactive game loop (Core 0 - input & logic)\n"); + printf("Display refreshes handled by Core 1\n\n"); while (1) { // Sleep until interrupt wakes us up (very power efficient!) @@ -684,15 +948,25 @@ int main() } } - // 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); + // 3. Redraw and queue async refresh on Core 1 + if (needs_refresh || pending_refresh) { + // Clear buffer and redraw entire UI with updated state + memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8); + game_draw(&game_state, &renderer, &gui); + + // Request async refresh (non-blocking - handled by Core 1) + bool refresh_started = refresh_screen_async(bit_buffer, display); + + if (refresh_started) { + pending_refresh = false; // Refresh queued successfully + } else { + pending_refresh = true; // Core 1 busy, retry next iteration + if (config.debug_verbose) { + printf("Refresh pending - Core 1 still busy\n"); + } } - refresh_screen(bit_buffer, display); + + // Core 0 continues immediately, Core 1 handles the refresh } } diff --git a/board_config.h b/board_config.h index b725ad3..3875fae 100644 --- a/board_config.h +++ b/board_config.h @@ -16,9 +16,9 @@ // ============================================================================ // ---- SELECT YOUR BOARD HERE ---- -// #define BOARD_FEATHER_TFT // Feather RP2350 + 4.0" TFT ST7796 +#define BOARD_FEATHER_TFT // Feather RP2350 + 4.0" TFT ST7796 // #define BOARD_PICO2_TFT // Pico 2 + 4.0" TFT ST7796 -#define BOARD_PICO2_EINK // Pico 2 + E-Ink Display +//#define BOARD_PICO2_EINK // Pico 2 + E-Ink Display // -------------------------------- // ============================================================================ diff --git a/display/low_level_display_st7789.cpp b/display/low_level_display_st7789.cpp index 0deb9be..0c48c67 100644 --- a/display/low_level_display_st7789.cpp +++ b/display/low_level_display_st7789.cpp @@ -1,15 +1,19 @@ #include "low_level_display_st7789.h" #include +#include // Note: This is a placeholder implementation // You'll need to add the actual ST7789 driver code to lib/st7789/ LowLevelDisplayST7789::LowLevelDisplayST7789(const st7789_config* cfg, int w, int h) - : config(cfg), width(w), height(h), initialized(false) { + : config(cfg), width(w), height(h), initialized(false), rgb_buffer(nullptr) { } LowLevelDisplayST7789::~LowLevelDisplayST7789() { - // Cleanup if needed + if (rgb_buffer) { + free(rgb_buffer); + rgb_buffer = nullptr; + } } bool LowLevelDisplayST7789::init() { @@ -19,6 +23,15 @@ bool LowLevelDisplayST7789::init() { // TODO: Implement ST7789 initialization // st7789_init(config, width, height); + // + // TODO: Allocate RGB565 buffer once (reused for all draw operations) + // size_t buffer_size = width * height * sizeof(uint16_t); + // rgb_buffer = (uint16_t *)malloc(buffer_size); + // if (!rgb_buffer) { + // printf("Error: Failed to allocate %zu bytes for RGB buffer\n", buffer_size); + // return false; + // } + printf("ST7789 display initialized: %dx%d (stub)\n", width, height); initialized = true; return false; // Return false until actual driver is implemented @@ -36,6 +49,18 @@ void LowLevelDisplayST7789::draw_pixel(int x, int y, bool white) { void LowLevelDisplayST7789::draw_buffer(const uint8_t* bit_buffer) { // TODO: Implement - convert 1-bit to RGB565 and write + // IMPORTANT: Use the persistent rgb_buffer member, NOT malloc/free! + // Example: + // if (!bit_buffer || !rgb_buffer) return; + // for (int y = 0; y < height; y++) { + // for (int x = 0; x < width; x++) { + // int byte_index = (y * width + x) / 8; + // int bit_index = 7 - (x % 8); + // bool pixel_white = (bit_buffer[byte_index] >> bit_index) & 0x01; + // rgb_buffer[y * width + x] = pixel_white ? COLOR_WHITE : COLOR_BLACK; + // } + // } + // st7789_write(rgb_buffer, width * height); (void)bit_buffer; } diff --git a/display/low_level_display_st7789.h b/display/low_level_display_st7789.h index e02baa7..d6b68d6 100644 --- a/display/low_level_display_st7789.h +++ b/display/low_level_display_st7789.h @@ -20,6 +20,7 @@ private: int width; int height; bool initialized; + uint16_t* rgb_buffer; // Persistent buffer for 1-bit to RGB565 conversion public: LowLevelDisplayST7789(const st7789_config* cfg, int w, int h); diff --git a/display/low_level_display_st7796.cpp b/display/low_level_display_st7796.cpp index 4691856..35076e1 100644 --- a/display/low_level_display_st7796.cpp +++ b/display/low_level_display_st7796.cpp @@ -7,11 +7,14 @@ #define COLOR_WHITE 0xFFFF LowLevelDisplayST7796::LowLevelDisplayST7796(const st7796_config* cfg, int w, int h) - : config(cfg), width(w), height(h), initialized(false) { + : config(cfg), width(w), height(h), initialized(false), rgb_buffer(nullptr) { } LowLevelDisplayST7796::~LowLevelDisplayST7796() { - // Cleanup if needed + if (rgb_buffer) { + free(rgb_buffer); + rgb_buffer = nullptr; + } } bool LowLevelDisplayST7796::init() { @@ -20,8 +23,17 @@ bool LowLevelDisplayST7796::init() { } st7796_init(config, width, height); + + // Allocate RGB565 buffer once (reused for all draw operations) + size_t buffer_size = width * height * sizeof(uint16_t); + rgb_buffer = (uint16_t *)malloc(buffer_size); + if (!rgb_buffer) { + printf("Error: Failed to allocate %zu bytes for RGB buffer\n", buffer_size); + return false; + } + printf("ST7796 display initialized: %dx%d (RGB buffer: %zu bytes)\n", width, height, buffer_size); + initialized = true; - printf("ST7796 display initialized: %dx%d\n", width, height); return true; } @@ -34,16 +46,9 @@ void LowLevelDisplayST7796::draw_pixel(int x, int y, bool white) { } void LowLevelDisplayST7796::draw_buffer(const uint8_t* bit_buffer) { - if (!bit_buffer) return; + if (!bit_buffer || !rgb_buffer) return; - // Allocate RGB565 buffer for entire screen - uint16_t *rgb_buffer = (uint16_t *)malloc(width * height * sizeof(uint16_t)); - if (!rgb_buffer) { - printf("Error: Failed to allocate RGB buffer\n"); - return; - } - - // Convert 1-bit buffer to RGB565 + // Convert 1-bit buffer to RGB565 using persistent buffer for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int byte_index = (y * width + x) / 8; @@ -56,8 +61,6 @@ void LowLevelDisplayST7796::draw_buffer(const uint8_t* bit_buffer) { // Draw entire buffer at once st7796_set_cursor(0, 0); st7796_write(rgb_buffer, width * height); - - free(rgb_buffer); } void LowLevelDisplayST7796::refresh() { diff --git a/display/low_level_display_st7796.h b/display/low_level_display_st7796.h index 0d7a37b..2c6b330 100644 --- a/display/low_level_display_st7796.h +++ b/display/low_level_display_st7796.h @@ -10,6 +10,7 @@ private: int width; int height; bool initialized; + uint16_t* rgb_buffer; // Persistent buffer for 1-bit to RGB565 conversion public: LowLevelDisplayST7796(const st7796_config* cfg, int w, int h);