diff --git a/CMakeLists.txt b/CMakeLists.txt index 31344a2..a194035 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,9 @@ if (TARGET tinyusb_host) hello_usb.cpp display.cpp commands/echo.cpp + keyboard_input.cpp + command_processor.cpp + epaper_manager.cpp ) add_subdirectory(pico-ssd1306 commands) diff --git a/command_processor.cpp b/command_processor.cpp new file mode 100644 index 0000000..80104cf --- /dev/null +++ b/command_processor.cpp @@ -0,0 +1,19 @@ +#include "command_processor.h" +#include +#include + +CommandAction parse_command(const char* input) { + if (input[0] != '/') { + return CMD_NONE; + } + + if (strcmp(input, "/refresh") == 0) { + return CMD_REFRESH; + } else if (strcmp(input, "/clear") == 0) { + return CMD_CLEAR; + } else if (strcmp(input, "/wifisetup") == 0) { + return CMD_WIFI; + } + + return CMD_UNKNOWN; +} diff --git a/command_processor.h b/command_processor.h new file mode 100644 index 0000000..f427759 --- /dev/null +++ b/command_processor.h @@ -0,0 +1,18 @@ +#ifndef COMMAND_PROCESSOR_H +#define COMMAND_PROCESSOR_H + +enum CommandAction { + CMD_NONE, + CMD_REFRESH, + CMD_CLEAR, + CMD_WIFI, + CMD_UNKNOWN +}; + +/** + * Parses a string to check if it is a command. + * Returns the corresponding CommandAction. + */ +CommandAction parse_command(const char* input); + +#endif // COMMAND_PROCESSOR_H diff --git a/epaper_manager.cpp b/epaper_manager.cpp new file mode 100644 index 0000000..5feba44 --- /dev/null +++ b/epaper_manager.cpp @@ -0,0 +1,247 @@ +#include "epaper_manager.h" +#include "pico/stdlib.h" +#include "pico/multicore.h" +#include +#include +#include + +// e-Paper library includes +extern "C" { + #include "DEV_Config.h" + #include "EPD_7in5b_V2.h" + #include "GUI_Paint.h" +} + +// e-Paper display buffers +static UBYTE *g_epd_image = NULL; // Black image buffer +static UBYTE *g_epd_red = NULL; // Red image buffer + +// Entry list for display +#define MAX_ENTRIES 15 +#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; + +// 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; +} DisplayMessage; + +static void init_epaper_display() { + printf("Initializing 7.5\" e-Paper display (B V2)...\r\n"); + + // Initialize the hardware + if (DEV_Module_Init() != 0) { + printf("Failed to initialize e-Paper hardware!\r\n"); + return; + } + + // Initialize the display + printf("EPD_7IN5B_V2_Init()\r\n"); + EPD_7IN5B_V2_Init_Fast(); + printf("EPD_7IN5B_V2_Clear()\r\n"); + + // Create image buffers for black and red content + // 7.5" display: 800x480, 8 pixels per byte, so (800/8) * 480 = 48000 bytes each + UWORD imagesize = (EPD_7IN5B_V2_WIDTH / 8) * EPD_7IN5B_V2_HEIGHT; + + g_epd_image = (UBYTE *)malloc(imagesize); + g_epd_red = (UBYTE *)malloc(imagesize); + + if (g_epd_image == NULL || g_epd_red == NULL) { + printf("Failed to allocate memory for e-Paper image buffers!\r\n"); + return; + } + + // Initialize red buffer to all white (0xFF = no red pixels) + for (UWORD i = 0; i < imagesize; i++) { + g_epd_red[i] = 0xFF; + } + + // Setup paint buffer for black content + printf("White background\r\n"); + Paint_NewImage(g_epd_image, EPD_7IN5B_V2_WIDTH, EPD_7IN5B_V2_HEIGHT, 0, WHITE); + Paint_SelectImage(g_epd_image); + Paint_Clear(WHITE); + + // Draw header + EPD_7IN5B_V2_Init_Part(); + EPD_7IN5B_V2_Display_Partial(g_epd_image, 0, 0, EPD_7IN5B_V2_WIDTH, EPD_7IN5B_V2_HEIGHT); + EPD_7IN5B_V2_Display_Partial(g_epd_image, 0, 0, EPD_7IN5B_V2_WIDTH, EPD_7IN5B_V2_HEIGHT); + EPD_7IN5B_V2_Display_Partial(g_epd_image, 0, 0, EPD_7IN5B_V2_WIDTH, EPD_7IN5B_V2_HEIGHT); + + Paint_SelectImage(g_epd_red); + printf("Drawing header\r\n"); + Paint_DrawString_EN(10, 10, "What's new today:", &Font24, WHITE, RED); + + // Display the image with both black and red buffers + EPD_7IN5B_V2_Display_Partial(g_epd_red, 0, 0, EPD_7IN5B_V2_WIDTH, EPD_7IN5B_V2_HEIGHT); + + printf("e-Paper display ready!\r\n"); +} + +/** + * Core 1 function: Runs display initialization and update handling + */ +static void core1_display_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_7IN5B_V2_Init_Part(); + + // Core 1 main loop: handle display updates via FIFO + while (true) { + // Check if there's a message from core 0 + if (multicore_fifo_rvalid()) { + // Read the message pointer + uint32_t msg_addr = multicore_fifo_pop_blocking(); + DisplayMessage *msg = (DisplayMessage *)msg_addr; + + // Drain FIFO to get the latest message + while (multicore_fifo_rvalid()) { + printf("[Core 1] Skipping intermediate update\n"); + free(msg); // Free the stale message + msg_addr = multicore_fifo_pop_blocking(); + msg = (DisplayMessage *)msg_addr; + } + + printf("[Core 1] Updating display with %d entries\n", msg->entries.count); + + // Update the e-Paper display + if (g_epd_image != NULL && g_epd_red != 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_7IN5B_V2_WIDTH / 8)); + + if(!msg->use_partial){ + // for loop 5 times display white partial + int i = 0; + for(i = 0; i < 1; i++){ + EPD_7IN5B_V2_Display_Partial(image_ptr, msg->xstart, msg->ystart, msg->xend, msg->yend); + } + } + + // 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 < 480) { //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("Skipping entry to avoid overflow\n"); + } + } + // Use partial or full refresh + printf("[Core 1] Using partial refresh\n"); + EPD_7IN5B_V2_Display_Partial(image_ptr, msg->xstart, msg->ystart, msg->xend, msg->yend); + } + + // Free the message (it was allocated by core 0) + free(msg); + } + + sleep_ms(10); + } +} + +void epaper_start_background_thread() { + printf("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 + + // 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("Failed to allocate display message!\n"); + return; + } + + // Copy entry list to message + msg->entries = g_entry_list; + // 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 = 800; // Full width + msg->yend = 480; // 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 = 800; + msg->yend = msg->ystart + 25; + } + + // Send message pointer to core 1 if FIFO has space + if (multicore_fifo_wready()) { + multicore_fifo_push_blocking((uint32_t)msg); + } else { + printf("[Core 0] FIFO full, skipping display update\n"); + 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); +} diff --git a/epaper_manager.h b/epaper_manager.h new file mode 100644 index 0000000..48132b6 --- /dev/null +++ b/epaper_manager.h @@ -0,0 +1,31 @@ +#ifndef EPAPER_MANAGER_H +#define EPAPER_MANAGER_H + +#include + +/** + * Launches the e-Paper display handling loop on Core 1. + * This function returns immediately after launching the core. + */ +void epaper_start_background_thread(); + +/** + * Sends an update to the e-Paper display. + * @param entry The text content of the current line. + * @param finish_line If true, the line is committed (moved to the list of static lines). + * If false, it updates the current line being typed. + */ +void epaper_send_update(const char *entry, bool finish_line); + +/** + * Clears all text entries and refreshes the display to white. + */ +void epaper_clear(); + +/** + * Forces a full refresh of the display on the next update. + * Useful for clearing ghosting. + */ +void epaper_force_refresh(); + +#endif // EPAPER_MANAGER_H diff --git a/hello_usb.cpp b/hello_usb.cpp index 91d5d61..e4ee0c9 100644 --- a/hello_usb.cpp +++ b/hello_usb.cpp @@ -13,243 +13,13 @@ #include "display.h" #include "commands/echo.h" - -// e-Paper library includes -extern "C" { - #include "DEV_Config.h" - #include "EPD_7in5b_V2.h" - #include "GUI_Paint.h" -} +#include "keyboard_input.h" +#include "command_processor.h" +#include "epaper_manager.h" // Holds last echoed line for display static char g_last_echo[128] = ""; -// e-Paper display buffers -static UBYTE *g_epd_image = NULL; // Black image buffer -static UBYTE *g_epd_red = NULL; // Red image buffer - -// Entry list for display -#define MAX_ENTRIES 15 -#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; - -// 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; -} DisplayMessage; - -void init_epaper_display() { - printf("Initializing 7.5\" e-Paper display (B V2)...\r\n"); - - // Initialize the hardware - if (DEV_Module_Init() != 0) { - printf("Failed to initialize e-Paper hardware!\r\n"); - return; - } - - // Initialize the display - printf("EPD_7IN5B_V2_Init()\r\n"); - EPD_7IN5B_V2_Init_Fast(); - printf("EPD_7IN5B_V2_Clear()\r\n"); - // EPD_7IN5B_V2_Clear(); - // DEV_Delay_ms(500); - - // Create image buffers for black and red content - // 7.5" display: 800x480, 8 pixels per byte, so (800/8) * 480 = 48000 bytes each - UWORD imagesize = (EPD_7IN5B_V2_WIDTH / 8) * EPD_7IN5B_V2_HEIGHT; - - g_epd_image = (UBYTE *)malloc(imagesize); - g_epd_red = (UBYTE *)malloc(imagesize); - - if (g_epd_image == NULL || g_epd_red == NULL) { - printf("Failed to allocate memory for e-Paper image buffers!\r\n"); - return; - } - - // Initialize red buffer to all white (0xFF = no red pixels) - for (UWORD i = 0; i < imagesize; i++) { - g_epd_red[i] = 0xFF; - } - - // Setup paint buffer for black content - printf("White background\r\n"); - Paint_NewImage(g_epd_image, EPD_7IN5B_V2_WIDTH, EPD_7IN5B_V2_HEIGHT, 0, WHITE); - Paint_SelectImage(g_epd_image); - Paint_Clear(WHITE); - - // Draw header - EPD_7IN5B_V2_Init_Part(); - EPD_7IN5B_V2_Display_Partial(g_epd_image, 0, 0, EPD_7IN5B_V2_WIDTH, EPD_7IN5B_V2_HEIGHT); - EPD_7IN5B_V2_Display_Partial(g_epd_image, 0, 0, EPD_7IN5B_V2_WIDTH, EPD_7IN5B_V2_HEIGHT); - EPD_7IN5B_V2_Display_Partial(g_epd_image, 0, 0, EPD_7IN5B_V2_WIDTH, EPD_7IN5B_V2_HEIGHT); - - Paint_SelectImage(g_epd_red); - printf("Drawing header\r\n"); - Paint_DrawString_EN(10, 10, "What's new today:", &Font24, WHITE, RED); - - // Display the image with both black and red buffers - EPD_7IN5B_V2_Display_Partial(g_epd_red, 0, 0, EPD_7IN5B_V2_WIDTH, EPD_7IN5B_V2_HEIGHT); - - printf("e-Paper display ready!\r\n"); -} - -/** - * Add an entry to the display list and send update to core 1 - * @param entry The text to display - * @param finish_line If true, commits the line (moves to next line). If false, updates current line. - */ -void send_display_update(const char *entry, bool finish_line) { - if (!g_display_ready) return; // Don't send if display isn't ready - - // 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("Failed to allocate display message!\n"); - return; - } - - // Copy entry list to message - msg->entries = g_entry_list; - // 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 = 800; // Full width - msg->yend = 480; // 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 = 800; - msg->yend = msg->ystart + 25; - } - - // Send message pointer to core 1 if FIFO has space - if (multicore_fifo_wready()) { - multicore_fifo_push_blocking((uint32_t)msg); - } else { - printf("[Core 0] FIFO full, skipping display update\n"); - free(msg); - } - - // If finishing the line, increment count for next time - if (finish_line) { - g_entry_list.count++; - } -} - -/** - * Core 1 function: Runs display initialization and update handling - */ -void core1_display_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_7IN5B_V2_Init_Part(); - - // Core 1 main loop: handle display updates via FIFO - while (true) { - // Check if there's a message from core 0 - if (multicore_fifo_rvalid()) { - // Read the message pointer - uint32_t msg_addr = multicore_fifo_pop_blocking(); - DisplayMessage *msg = (DisplayMessage *)msg_addr; - - // Drain FIFO to get the latest message - while (multicore_fifo_rvalid()) { - printf("[Core 1] Skipping intermediate update\n"); - free(msg); // Free the stale message - msg_addr = multicore_fifo_pop_blocking(); - msg = (DisplayMessage *)msg_addr; - } - - printf("[Core 1] Updating display with %d entries\n", msg->entries.count); - - // Update the e-Paper display - if (g_epd_image != NULL && g_epd_red != NULL) { - UWORD imagesize = (EPD_7IN5B_V2_WIDTH / 8) * EPD_7IN5B_V2_HEIGHT; - - // For partial refresh, only clear the text area - UWORD width = (EPD_7IN5B_V2_WIDTH / 8); - 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_7IN5B_V2_WIDTH / 8)); - - if(!msg->use_partial){ - // for loop 5 times display white partial - int i = 0; - for(i = 0; i < 1; i++){ - EPD_7IN5B_V2_Display_Partial(image_ptr, msg->xstart, msg->ystart, msg->xend, msg->yend); - } - } - - // 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 < 480) { //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("Skipping entry to avoid overflow\n"); - } - } - // Use partial or full refresh - printf("[Core 1] Using partial refresh\n"); - EPD_7IN5B_V2_Display_Partial(image_ptr, msg->xstart, msg->ystart, msg->xend, msg->yend); - } - - // Free the message (it was allocated by core 0) - free(msg); - } - - sleep_ms(10); - } -} - // Global DisplayManager pointer static DisplayManager* g_display_manager = nullptr; @@ -258,219 +28,84 @@ static DisplayManager* g_display_manager = nullptr; static char g_input_buffer[MAX_INPUT_LEN]; static int g_buffer_index = 0; -// ASCII code for Backspace (often sent as 0x08) -#define ASCII_BACKSPACE 8 - -static const uint8_t keycode2ascii[128][2] = { - {0, 0}, /* 0x00 */ - {0, 0}, /* 0x01 */ - {0, 0}, /* 0x02 */ - {0, 0}, /* 0x03 */ - {'a', 'A'}, /* 0x04 */ - {'b', 'B'}, /* 0x05 */ - {'c', 'C'}, /* 0x06 */ - {'d', 'D'}, /* 0x07 */ - {'e', 'E'}, /* 0x08 */ - {'f', 'F'}, /* 0x09 */ - {'g', 'G'}, /* 0x0a */ - {'h', 'H'}, /* 0x0b */ - {'i', 'I'}, /* 0x0c */ - {'j', 'J'}, /* 0x0d */ - {'k', 'K'}, /* 0x0e */ - {'l', 'L'}, /* 0x0f */ - {'m', 'M'}, /* 0x10 */ - {'n', 'N'}, /* 0x11 */ - {'o', 'O'}, /* 0x12 */ - {'p', 'P'}, /* 0x13 */ - {'q', 'Q'}, /* 0x14 */ - {'r', 'R'}, /* 0x15 */ - {'s', 'S'}, /* 0x16 */ - {'t', 'T'}, /* 0x17 */ - {'u', 'U'}, /* 0x18 */ - {'v', 'V'}, /* 0x19 */ - {'w', 'W'}, /* 0x1a */ - {'x', 'X'}, /* 0x1b */ - {'y', 'Y'}, /* 0x1c */ - {'z', 'Z'}, /* 0x1d */ - {'1', '!'}, /* 0x1e */ - {'2', '@'}, /* 0x1f */ - {'3', '#'}, /* 0x20 */ - {'4', '$'}, /* 0x21 */ - {'5', '%'}, /* 0x22 */ - {'6', '^'}, /* 0x23 */ - {'7', '&'}, /* 0x24 */ - {'8', '*'}, /* 0x25 */ - {'9', '('}, /* 0x26 */ - {'0', ')'}, /* 0x27 */ - {'\r', '\r'}, /* 0x28 ENTER */ - {'\x1b', '\x1b'}, /* 0x29 ESCAPE */ - {'\b', '\b'}, /* 0x2a BACKSPACE */ - {'\t', '\t'}, /* 0x2b TAB */ - {' ', ' '}, /* 0x2c SPACE */ - {'-', '_'}, /* 0x2d MINUS */ - {'=', '+'}, /* 0x2e EQUAL */ - {'[', '{'}, /* 0x2f BRACKET_LEFT */ - {']', '}'}, /* 0x30 BRACKET_RIGHT */ - {'\\', '|'}, /* 0x31 BACKSLASH */ - {'#', '~'}, /* 0x32 EUROPE_1 */ - {';', ':'}, /* 0x33 SEMICOLON */ - {'\'', '\"'}, /* 0x34 APOSTROPHE */ - {'`', '~'}, /* 0x35 GRAVE */ - {',', '<'}, /* 0x36 COMMA */ - {'.', '>'}, /* 0x37 PERIOD */ - {'/', '?'}, /* 0x38 SLASH */ - {0, 0}, /* 0x39 CAPS_LOCK */ - {0, 0}, /* 0x3a F1 */ - {0, 0}, /* 0x3b F2 */ - {0, 0}, /* 0x3c F3 */ - {0, 0}, /* 0x3d F4 */ - {0, 0}, /* 0x3e F5 */ - {0, 0}, /* 0x3f F6 */ - {0, 0}, /* 0x40 F7 */ - {0, 0}, /* 0x41 F8 */ - {0, 0}, /* 0x42 F9 */ - {0, 0}, /* 0x43 F10 */ - {0, 0}, /* 0x44 F11 */ - {0, 0}, /* 0x45 F12 */ - {0, 0}, /* 0x46 PRINT_SCREEN */ - {0, 0}, /* 0x47 SCROLL_LOCK */ - {0, 0}, /* 0x48 PAUSE */ - {0, 0}, /* 0x49 INSERT */ - {0, 0}, /* 0x4a HOME */ - {0, 0}, /* 0x4b PAGE_UP */ - {0, 0}, /* 0x4c DELETE */ - {0, 0}, /* 0x4d END */ - {0, 0}, /* 0x4e PAGE_DOWN */ - {0, 0}, /* 0x4f RIGHT_ARROW */ - {0, 0}, /* 0x50 LEFT_ARROW */ - {0, 0}, /* 0x51 DOWN_ARROW */ - {0, 0}, /* 0x52 UP_ARROW */ - {0, 0}, /* 0x53 NUM_LOCK */ - {'/', '/'}, /* 0x54 KP_DIVIDE */ - {'*', '*'}, /* 0x55 KP_MULTIPLY */ - {'-', '-'}, /* 0x56 KP_MINUS */ - {'+', '+'}, /* 0x57 KP_PLUS */ - {'\r', '\r'}, /* 0x58 KP_ENTER */ - {'1', '1'}, /* 0x59 KP_1 */ - {'2', '2'}, /* 0x5a KP_2 */ - {'3', '3'}, /* 0x5b KP_3 */ - {'4', '4'}, /* 0x5c KP_4 */ - {'5', '5'}, /* 0x5d KP_5 */ - {'6', '6'}, /* 0x5e KP_6 */ - {'7', '7'}, /* 0x5f KP_7 */ - {'8', '8'}, /* 0x60 KP_8 */ - {'9', '9'}, /* 0x61 KP_9 */ - {'0', '0'}, /* 0x62 KP_0 */ - {'.', '.'}, /* 0x63 KP_DECIMAL */ -}; - -static inline bool find_key_in_report(hid_keyboard_report_t const *report, uint8_t keycode) { - for(uint8_t i=0; i<6; i++) { - if (report->keycode[i] == keycode) return true; +static bool execute_command(CommandAction action) { + switch (action) { + case CMD_REFRESH: + printf("Command: /refresh\n"); + epaper_force_refresh(); + return true; + case CMD_CLEAR: + printf("Command: /clear\n"); + epaper_clear(); + return true; + case CMD_WIFI: + printf("Command: /wifisetup (Not implemented)\n"); + return true; + default: + return false; } - return false; -} - -static bool process_command(const char* input) { - if (input[0] != '/') return false; - - if (strcmp(input, "/refresh") == 0) { - printf("Command: /refresh\n"); - g_force_full_refresh = true; - send_display_update("", false); - return true; - } else if (strcmp(input, "/clear") == 0) { - printf("Command: /clear\n"); - g_entry_list.count = 0; - memset(g_entry_list.entries, 0, sizeof(g_entry_list.entries)); - g_force_full_refresh = true; - send_display_update("", false); - return true; - } else if (strcmp(input, "/wifisetup") == 0) { - printf("Command: /wifisetup (Not implemented)\n"); - return true; - } - - return false; } static void process_kbd_report(hid_keyboard_report_t const *report) { - static hid_keyboard_report_t prev_report = { 0, 0, {0} }; - - for(uint8_t i=0; i<6; i++) { - if (report->keycode[i]) { - if (find_key_in_report(&prev_report, report->keycode[i])) { - // exist in previous report means the current key is holding - } else { - // not existed in previous report means the current key is pressed - bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT); - uint8_t ch = keycode2ascii[report->keycode[i]][is_shift ? 1 : 0]; + KeyEvent event; + if (parse_keyboard_report(report, &event)) { + if (event.is_backspace) { + if (g_buffer_index > 0) { + printf("\b \b"); + g_buffer_index--; + g_input_buffer[g_buffer_index] = '\0'; - if (ch) { - bool is_enter = (ch == '\r' || ch == '\n'); - bool is_backspace = (ch == '\b' || ch == 127); - bool is_printable = !is_enter && !is_backspace; // Includes space - - if (is_backspace) { - if (g_buffer_index > 0) { - printf("\b \b"); - g_buffer_index--; - g_input_buffer[g_buffer_index] = '\0'; - - // Update OLED - if (g_display_manager) g_display_manager->refresh(g_input_buffer, g_last_echo); - - // Update e-Paper (in-place) - send_display_update(g_input_buffer, false); - } - } else if (is_enter) { - printf("\n"); - - if (process_command(g_input_buffer)) { - // Command handled, clear buffer but don't commit line - g_buffer_index = 0; - g_input_buffer[0] = '\0'; - - if (g_display_manager) { - g_display_manager->set_last_echo("Command Executed"); - g_display_manager->refresh("", "Command Executed"); - } - } else { - // Update e-Paper (commit line) - send_display_update(g_input_buffer, true); - - // Save to last echo - strncpy(g_last_echo, g_input_buffer, sizeof(g_last_echo) - 1); - g_last_echo[sizeof(g_last_echo) - 1] = '\0'; - - // Clear buffer - g_buffer_index = 0; - g_input_buffer[0] = '\0'; - - // Update OLED - if (g_display_manager) { - g_display_manager->set_last_echo(g_last_echo); - g_display_manager->refresh("", g_last_echo); - } - } - } else if (is_printable) { - printf("%c", ch); - if (g_buffer_index < MAX_INPUT_LEN - 1) { - g_input_buffer[g_buffer_index++] = ch; - g_input_buffer[g_buffer_index] = '\0'; - - // Update OLED - if (g_display_manager) g_display_manager->refresh(g_input_buffer, g_last_echo); - - // Update e-Paper on every keystroke - send_display_update(g_input_buffer, false); - } - } + // Update OLED + if (g_display_manager) g_display_manager->refresh(g_input_buffer, g_last_echo); + + // Update e-Paper (in-place) + epaper_send_update(g_input_buffer, false); + } + } else if (event.is_enter) { + printf("\n"); + + CommandAction action = parse_command(g_input_buffer); + if (execute_command(action)) { + // Command handled, clear buffer but don't commit line + g_buffer_index = 0; + g_input_buffer[0] = '\0'; + + if (g_display_manager) { + g_display_manager->set_last_echo("Command Executed"); + g_display_manager->refresh("", "Command Executed"); } + } else { + // Update e-Paper (commit line) + epaper_send_update(g_input_buffer, true); + + // Save to last echo + strncpy(g_last_echo, g_input_buffer, sizeof(g_last_echo) - 1); + g_last_echo[sizeof(g_last_echo) - 1] = '\0'; + + // Clear buffer + g_buffer_index = 0; + g_input_buffer[0] = '\0'; + + // Update OLED + if (g_display_manager) { + g_display_manager->set_last_echo(g_last_echo); + g_display_manager->refresh("", g_last_echo); + } + } + } else if (event.is_printable) { + printf("%c", event.ascii); + if (g_buffer_index < MAX_INPUT_LEN - 1) { + g_input_buffer[g_buffer_index++] = event.ascii; + g_input_buffer[g_buffer_index] = '\0'; + + // Update OLED + if (g_display_manager) g_display_manager->refresh(g_input_buffer, g_last_echo); + + // Update e-Paper on every keystroke + epaper_send_update(g_input_buffer, false); } } } - prev_report = *report; } //--------------------------------------------------------------------+ @@ -537,8 +172,7 @@ int main() { printf("System Booting...\n"); // Launch display initialization on core 1 - printf("Launching e-Paper display init on core 1...\n"); - multicore_launch_core1(core1_display_init); + epaper_start_background_thread(); DisplayManager display; display.init(); diff --git a/keyboard_input.cpp b/keyboard_input.cpp new file mode 100644 index 0000000..fc0242e --- /dev/null +++ b/keyboard_input.cpp @@ -0,0 +1,142 @@ +#include "keyboard_input.h" +#include + +static const uint8_t keycode2ascii[128][2] = { + {0, 0}, /* 0x00 */ + {0, 0}, /* 0x01 */ + {0, 0}, /* 0x02 */ + {0, 0}, /* 0x03 */ + {'a', 'A'}, /* 0x04 */ + {'b', 'B'}, /* 0x05 */ + {'c', 'C'}, /* 0x06 */ + {'d', 'D'}, /* 0x07 */ + {'e', 'E'}, /* 0x08 */ + {'f', 'F'}, /* 0x09 */ + {'g', 'G'}, /* 0x0a */ + {'h', 'H'}, /* 0x0b */ + {'i', 'I'}, /* 0x0c */ + {'j', 'J'}, /* 0x0d */ + {'k', 'K'}, /* 0x0e */ + {'l', 'L'}, /* 0x0f */ + {'m', 'M'}, /* 0x10 */ + {'n', 'N'}, /* 0x11 */ + {'o', 'O'}, /* 0x12 */ + {'p', 'P'}, /* 0x13 */ + {'q', 'Q'}, /* 0x14 */ + {'r', 'R'}, /* 0x15 */ + {'s', 'S'}, /* 0x16 */ + {'t', 'T'}, /* 0x17 */ + {'u', 'U'}, /* 0x18 */ + {'v', 'V'}, /* 0x19 */ + {'w', 'W'}, /* 0x1a */ + {'x', 'X'}, /* 0x1b */ + {'y', 'Y'}, /* 0x1c */ + {'z', 'Z'}, /* 0x1d */ + {'1', '!'}, /* 0x1e */ + {'2', '@'}, /* 0x1f */ + {'3', '#'}, /* 0x20 */ + {'4', '$'}, /* 0x21 */ + {'5', '%'}, /* 0x22 */ + {'6', '^'}, /* 0x23 */ + {'7', '&'}, /* 0x24 */ + {'8', '*'}, /* 0x25 */ + {'9', '('}, /* 0x26 */ + {'0', ')'}, /* 0x27 */ + {'\r', '\r'}, /* 0x28 ENTER */ + {'\x1b', '\x1b'}, /* 0x29 ESCAPE */ + {'\b', '\b'}, /* 0x2a BACKSPACE */ + {'\t', '\t'}, /* 0x2b TAB */ + {' ', ' '}, /* 0x2c SPACE */ + {'-', '_'}, /* 0x2d MINUS */ + {'=', '+'}, /* 0x2e EQUAL */ + {'[', '{'}, /* 0x2f BRACKET_LEFT */ + {']', '}'}, /* 0x30 BRACKET_RIGHT */ + {'\\', '|'}, /* 0x31 BACKSLASH */ + {'#', '~'}, /* 0x32 EUROPE_1 */ + {';', ':'}, /* 0x33 SEMICOLON */ + {'\'', '\"'}, /* 0x34 APOSTROPHE */ + {'`', '~'}, /* 0x35 GRAVE */ + {',', '<'}, /* 0x36 COMMA */ + {'.', '>'}, /* 0x37 PERIOD */ + {'/', '?'}, /* 0x38 SLASH */ + {0, 0}, /* 0x39 CAPS_LOCK */ + {0, 0}, /* 0x3a F1 */ + {0, 0}, /* 0x3b F2 */ + {0, 0}, /* 0x3c F3 */ + {0, 0}, /* 0x3d F4 */ + {0, 0}, /* 0x3e F5 */ + {0, 0}, /* 0x3f F6 */ + {0, 0}, /* 0x40 F7 */ + {0, 0}, /* 0x41 F8 */ + {0, 0}, /* 0x42 F9 */ + {0, 0}, /* 0x43 F10 */ + {0, 0}, /* 0x44 F11 */ + {0, 0}, /* 0x45 F12 */ + {0, 0}, /* 0x46 PRINT_SCREEN */ + {0, 0}, /* 0x47 SCROLL_LOCK */ + {0, 0}, /* 0x48 PAUSE */ + {0, 0}, /* 0x49 INSERT */ + {0, 0}, /* 0x4a HOME */ + {0, 0}, /* 0x4b PAGE_UP */ + {0, 0}, /* 0x4c DELETE */ + {0, 0}, /* 0x4d END */ + {0, 0}, /* 0x4e PAGE_DOWN */ + {0, 0}, /* 0x4f RIGHT_ARROW */ + {0, 0}, /* 0x50 LEFT_ARROW */ + {0, 0}, /* 0x51 DOWN_ARROW */ + {0, 0}, /* 0x52 UP_ARROW */ + {0, 0}, /* 0x53 NUM_LOCK */ + {'/', '/'}, /* 0x54 KP_DIVIDE */ + {'*', '*'}, /* 0x55 KP_MULTIPLY */ + {'-', '-'}, /* 0x56 KP_MINUS */ + {'+', '+'}, /* 0x57 KP_PLUS */ + {'\r', '\r'}, /* 0x58 KP_ENTER */ + {'1', '1'}, /* 0x59 KP_1 */ + {'2', '2'}, /* 0x5a KP_2 */ + {'3', '3'}, /* 0x5b KP_3 */ + {'4', '4'}, /* 0x5c KP_4 */ + {'5', '5'}, /* 0x5d KP_5 */ + {'6', '6'}, /* 0x5e KP_6 */ + {'7', '7'}, /* 0x5f KP_7 */ + {'8', '8'}, /* 0x60 KP_8 */ + {'9', '9'}, /* 0x61 KP_9 */ + {'0', '0'}, /* 0x62 KP_0 */ + {'.', '.'}, /* 0x63 KP_DECIMAL */ +}; + +static inline bool find_key_in_report(hid_keyboard_report_t const *report, uint8_t keycode) { + for(uint8_t i=0; i<6; i++) { + if (report->keycode[i] == keycode) return true; + } + return false; +} + +bool parse_keyboard_report(hid_keyboard_report_t const *report, KeyEvent* result) { + static hid_keyboard_report_t prev_report = { 0, 0, {0} }; + bool event_found = false; + + for(uint8_t i=0; i<6; i++) { + if (report->keycode[i]) { + if (find_key_in_report(&prev_report, report->keycode[i])) { + // exist in previous report means the current key is holding + } else { + // not existed in previous report means the current key is pressed + bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT); + uint8_t ch = keycode2ascii[report->keycode[i]][is_shift ? 1 : 0]; + + if (ch) { + result->ascii = ch; + result->is_enter = (ch == '\r' || ch == '\n'); + result->is_backspace = (ch == '\b' || ch == 127); + result->is_printable = !result->is_enter && !result->is_backspace; + event_found = true; + // We only process one key event per report for simplicity in this refactor + // Ideally we might want to queue them if multiple keys are pressed at once + break; + } + } + } + } + prev_report = *report; + return event_found; +} diff --git a/keyboard_input.h b/keyboard_input.h new file mode 100644 index 0000000..3531fc0 --- /dev/null +++ b/keyboard_input.h @@ -0,0 +1,19 @@ +#ifndef KEYBOARD_INPUT_H +#define KEYBOARD_INPUT_H + +#include "tusb.h" + +struct KeyEvent { + char ascii; + bool is_enter; + bool is_backspace; + bool is_printable; +}; + +/** + * Parses a raw HID keyboard report and returns true if a new key press was detected. + * The result is stored in the provided KeyEvent pointer. + */ +bool parse_keyboard_report(hid_keyboard_report_t const *report, KeyEvent* result); + +#endif // KEYBOARD_INPUT_H