#include "epaper_manager.h" #include "wifi_manager.h" #include "tcp_debug.h" #include "pico/stdlib.h" #include "pico/multicore.h" #include "pico/flash.h" #include "pico/util/queue.h" #include #include #include // e-Paper library includes extern "C" { #include "DEV_Config.h" #include "EPD_4in2_V2.h" #include "GUI_Paint.h" } // e-Paper display buffers static UBYTE *g_epd_image = NULL; // Black image buffer // Entry list for display #define MAX_ENTRIES 10 #define ENTRY_LENGTH 64 typedef struct { char entries[MAX_ENTRIES][ENTRY_LENGTH]; int count; } EntryList; static EntryList g_entry_list = {{}, 0}; static bool g_force_full_refresh = false; // Flag to force full refresh when list is full // Synchronization flag for multicore initialization static volatile bool g_display_ready = false; // Queue for display messages (replacing raw FIFO to avoid conflict with flash_safe_execute) static queue_t g_display_queue; #define QUEUE_LENGTH 8 // Display update message structure for inter-core communication typedef struct { EntryList entries; bool use_partial; // Use partial refresh instead of full refresh UWORD xstart; // Partial refresh region UWORD ystart; UWORD xend; UWORD yend; UBYTE *raw_image; // Pointer to raw image data (if not NULL, display this instead of text) } DisplayMessage; static void init_epaper_display() { printf("[Core 1] Initializing 4.2\" e-Paper display (V2)...\r\n"); // Initialize the hardware if (DEV_Module_Init() != 0) { printf("[Core 1] Failed to initialize e-Paper hardware!\r\n"); return; } // Initialize the display printf("[Core 1] EPD_4IN2_V2_Init_Fast()\r\n"); EPD_4IN2_V2_Init(); printf("[Core 1] EPD_4IN2_V2_Clear()\r\n"); EPD_4IN2_V2_Clear(); // Create image buffers for black content // 4.2" display: 400x300, 8 pixels per byte UWORD imagesize = ((EPD_4IN2_V2_WIDTH % 8 == 0)? (EPD_4IN2_V2_WIDTH / 8 ): (EPD_4IN2_V2_WIDTH / 8 + 1)) * EPD_4IN2_V2_HEIGHT; g_epd_image = (UBYTE *)malloc(imagesize); if (g_epd_image == NULL) { printf("[Core 1] Failed to allocate memory for e-Paper image buffers!\r\n"); return; } // Setup paint buffer for black content printf("[Core 1] White background\r\n"); Paint_NewImage(g_epd_image, EPD_4IN2_V2_WIDTH, EPD_4IN2_V2_HEIGHT, 0, WHITE); Paint_SelectImage(g_epd_image); Paint_Clear(WHITE); printf("[Core 1] Trying to connect to wifi\r\n"); bool connected = wifi_try_auto_connect(); printf("[Core 1] Connected: %d\r\n", connected); if (connected) { tcp_debug_init(); } Paint_SelectImage(g_epd_image); printf("[Core 1] Drawing header\r\n"); if (connected) { char header_text[64]; const char* ip = wifi_get_ip(); snprintf(header_text, sizeof(header_text), "IP: %s", ip ? ip : "Unknown"); Paint_DrawString_EN(10, 10, header_text, &Font24, WHITE, BLACK); } else { Paint_DrawString_EN(10, 10, "What's new today:", &Font24, WHITE, BLACK); } // Display the image EPD_4IN2_V2_PartialDisplay(g_epd_image, 0, 0, EPD_4IN2_V2_WIDTH, 40); // EPD_4IN2_V2_Display_Fast(g_epd_image); printf("[Core 1] e-Paper display ready!\r\n"); } /** * Core 1 function: Runs display initialization and update handling */ static void core1_display_init() { // Initialize flash safe execute support for this core flash_safe_execute_core_init(); // Initialize WiFi on Core 1 so IRQs are handled here wifi_init(); init_epaper_display(); g_display_ready = true; printf("[Core 1] Display ready, waiting for update messages...\n"); // Initialize for partial refresh on second call // EPD_4IN2_V2_Init_Fast(Seconds_1S); // Already done in init // Core 1 main loop: handle display updates via Queue while (true) { DisplayMessage *msg; // Check if there's a message in the queue if (queue_try_remove(&g_display_queue, &msg)) { // Drain Queue to get the latest message DisplayMessage *next_msg; while (queue_try_remove(&g_display_queue, &next_msg)) { // printf("[Core 1] Skipping intermediate update\n"); free(msg); // Free the stale message msg = next_msg; } // printf("[Core 1] Updating display with %d entries\n", msg->entries.count); // Update the e-Paper display if (msg->raw_image != NULL) { printf("[Core 1] Displaying raw image...\n"); EPD_4IN2_V2_Display(msg->raw_image); free(msg->raw_image); // Free the image buffer allocated by Core 0 } else if (g_epd_image != NULL) { // For partial refresh, only clear the text area UWORD y_start = msg->ystart; UWORD y_end = msg->yend; Paint_SelectImage(g_epd_image); Paint_Clear(WHITE); // Calculate offset for partial refresh // UBYTE *image_ptr = g_epd_image + (msg->ystart * (EPD_4IN2_V2_WIDTH / 8)); if(!msg->use_partial){ // for loop 5 times display white partial Paint_Clear(WHITE); int i = 0; for(i = 0; i < 1; i++){ EPD_4IN2_V2_PartialDisplay(g_epd_image, 0, 0, 400, 300); } } // Draw all entries starting below header UWORD y_pos = 0; // paint only the last 10 entries int start_index = msg->entries.count > 10 ? msg->entries.count - 10 : 0; for (int i = start_index; i < msg->entries.count; i++) { if (y_pos + 25 < 300) { //Don't draw beyond screen Paint_DrawString_EN(20, 50 + i*25, msg->entries.entries[i], &Font16, WHITE, BLACK); y_pos += 25; // Space between entries } else { printf("[Core 1] Skipping entry to avoid overflow\n"); } } // Use partial or full refresh // printf("[Core 1] Using partial refresh\n"); EPD_4IN2_V2_PartialDisplay(g_epd_image, 0, 0, 400, 300); } // Free the message (it was allocated by core 0) free(msg); } sleep_ms(10); } } void epaper_start_background_thread() { // Initialize the queue before starting the thread queue_init(&g_display_queue, sizeof(DisplayMessage*), QUEUE_LENGTH); printf("[Core 0] Launching e-Paper display init on core 1...\n"); multicore_launch_core1(core1_display_init); } void epaper_send_update(const char *entry, bool finish_line) { if (!g_display_ready) return; // Don't send if display isn't ready // printf("[Core 0] Preparing display update: '%s' (finish_line=%d)\n", entry, finish_line); // Check if list is at capacity if (g_entry_list.count >= MAX_ENTRIES) { // List is full - clear all entries and start fresh printf("[Core 0] List is FULL - clearing all entries and starting fresh\n"); g_entry_list.count = 0; g_force_full_refresh = true; } // Update the current entry (at g_entry_list.count) strncpy(g_entry_list.entries[g_entry_list.count], entry, ENTRY_LENGTH - 1); g_entry_list.entries[g_entry_list.count][ENTRY_LENGTH - 1] = '\0'; // Allocate message structure DisplayMessage *msg = (DisplayMessage *)malloc(sizeof(DisplayMessage)); if (msg == NULL) { printf("[Core 0] Failed to allocate display message!\n"); return; } // Copy entry list to message msg->entries = g_entry_list; msg->raw_image = NULL; // Not an image update // We want to show the current line being edited, so we treat count as count + 1 for display purposes msg->entries.count = g_entry_list.count + 1; // Decide whether to use partial or full refresh msg->use_partial = !g_force_full_refresh; if (g_force_full_refresh) { printf("[Core 0] Forcing full refresh this update\n"); g_force_full_refresh = false; // Reset flag after setting it in message // Full text area refresh msg->xstart = 0; msg->ystart = 50; // Start below the header msg->xend = EPD_4IN2_V2_WIDTH; // Full width msg->yend = EPD_4IN2_V2_HEIGHT; // Full height from header onwards } else { // Partial refresh of ONLY the current line msg->xstart = 0; msg->ystart = 50 + (g_entry_list.count * 25); msg->xend = EPD_4IN2_V2_WIDTH; msg->yend = msg->ystart + 25; } // Send message pointer to core 1 via Queue if (queue_try_add(&g_display_queue, &msg)) { // printf("[Core 0] Display update sent to core 1\n"); } else { static int full_count = 0; if (full_count < 5) { printf("[Core 0] Queue full, skipping display update\n"); full_count++; } free(msg); } // If finishing the line, increment count for next time if (finish_line) { g_entry_list.count++; } } void epaper_clear() { g_entry_list.count = 0; memset(g_entry_list.entries, 0, sizeof(g_entry_list.entries)); g_force_full_refresh = true; epaper_send_update("", false); } void epaper_force_refresh() { g_force_full_refresh = true; epaper_send_update("", false); } void epaper_display_full_image(const unsigned char* image_data, unsigned int len) { if (!g_display_ready) return; // Allocate message structure DisplayMessage *msg = (DisplayMessage *)malloc(sizeof(DisplayMessage)); if (msg == NULL) { printf("[Core 0] Failed to allocate display message for image!\n"); return; } // We are passing ownership of the image buffer to the message/Core 1 // The caller should have allocated image_data with malloc msg->raw_image = (UBYTE*)image_data; msg->use_partial = false; // Full refresh for images // Send message pointer to core 1 via Queue if (queue_try_add(&g_display_queue, &msg)) { printf("[Core 0] Image update sent to core 1\n"); } else { printf("[Core 0] Queue full, skipping image update\n"); free(msg); // Note: We don't free image_data here because the caller might expect it to be consumed or not. // Ideally, if we fail to send, we should free it if we took ownership. // Let's assume we take ownership. free((void*)image_data); } }