From 22d856551ab2ac396c691a58b6f51d1574d2beb3 Mon Sep 17 00:00:00 2001 From: Tavis Date: Sun, 25 Jan 2026 22:52:19 -1000 Subject: [PATCH 01/11] Revert esp-hub75 migration to restore previous display library --- main/display.cpp | 482 +++++++++++++++++++---------------------- main/display.h | 15 +- main/gfx.c | 351 +++++++++++++----------------- main/idf_component.yml | 7 +- sdkconfig.defaults | 115 +--------- 5 files changed, 380 insertions(+), 590 deletions(-) diff --git a/main/display.cpp b/main/display.cpp index 4807b23..edf655f 100644 --- a/main/display.cpp +++ b/main/display.cpp @@ -1,246 +1,213 @@ #include "display.h" - -#include - -#include "esp_heap_caps.h" -#include "esp_log.h" #include "font5x7.h" #include "nvs_settings.h" -static Hub75Driver *_matrix; -static uint8_t _brightness = (CONFIG_HUB75_BRIGHTNESS * 100) / 255; -static const char *TAG = "display"; +#include +#if CONFIG_BOARD_TIDBYT_GEN2 + #define R1 5 + #define G1 23 + #define BL1 4 + #define R2 2 + #define G2 22 + #define BL2 32 + + #define CH_A 25 + #define CH_B 21 + #define CH_C 26 + #define CH_D 19 + #define CH_E -1 // assign to pin 14 if using more than two panels + + #define LAT 18 + #define OE 27 + #define CLK 15 +#elif CONFIG_BOARD_TRONBYT_S3_WIDE + #define R1 4 + #define G1 5 + #define BL1 6 + #define R2 7 + #define G2 15 + #define BL2 16 + + #define CH_A 17 + #define CH_B 18 + #define CH_C 8 + #define CH_D 3 + #define CH_E 46 + #define LAT 9 + #define OE 10 + #define CLK 11 + + #define WIDTH 128 + #define HEIGHT 64 +#elif CONFIG_BOARD_TRONBYT_S3 + #define R1 4 + #define G1 6 + #define BL1 5 + #define R2 7 + #define G2 16 + #define BL2 15 + + #define CH_A 17 + #define CH_B 18 + #define CH_C 8 + #define CH_D 3 + #define CH_E -1 + + #define LAT 9 + #define OE 10 + #define CLK 11 +#elif CONFIG_BOARD_PIXOTICKER + #define R1 2 + #define G1 4 + #define BL1 15 + #define R2 16 + #define G2 17 + #define BL2 27 + #define CH_A 5 + #define CH_B 18 + #define CH_C 19 + #define CH_D 21 + #define CH_E 12 + #define CLK 22 + #define LAT 26 + #define OE 25 +#elif CONFIG_BOARD_MATRIXPORTAL_S3 +// R1, G1, B1, R2, G2, B2 +// uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; +// uint8_t addrPins[] = {45, 36, 48, 35, 21}; +// uint8_t clockPin = 2; +// uint8_t latchPin = 47; +// uint8_t oePin = 14; + #define R1 42 + #define R2 38 + #define CH_A 45 + #define CH_B 36 + #define CH_C 48 + #define CH_D 35 + #define CH_E 21 + #define CLK 2 + #define LAT 47 + #define OE 14 +#else // GEN1 from here down. + #define CH_A 26 + #define CH_B 5 + #define CH_C 25 + #define CH_D 18 + #define CH_E -1 // assign to pin 14 if using more than two panels + + #define LAT 19 + #define OE 32 + #define CLK 33 +#endif -#if CONFIG_HUB75_PANEL_WIDTH == 128 && CONFIG_HUB75_PANEL_HEIGHT == 64 -static uint32_t *_scaled_buffer = NULL; +#ifndef WIDTH +#define WIDTH 64 #endif -int display_initialize(void) { -#if CONFIG_HUB75_PANEL_WIDTH == 128 && CONFIG_HUB75_PANEL_HEIGHT == 64 - if (_scaled_buffer == NULL) { - _scaled_buffer = (uint32_t *)heap_caps_malloc( - 128 * 64 * 4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - if (_scaled_buffer == NULL) { - ESP_LOGE(TAG, "Failed to allocate scaled buffer in PSRAM"); - return 1; - } - } +#ifndef HEIGHT +#define HEIGHT 32 #endif +static MatrixPanel_I2S_DMA *_matrix; +static uint8_t _brightness = DEFAULT_BRIGHTNESS; +static const char *TAG = "display"; + +int display_initialize(void) { // Get swap_colors setting bool swap_colors = nvs_get_swap_colors(); // Initialize pin values based on hardware and swap_colors setting - ESP_LOGI(TAG, "Initializing display with swap_colors=%s", - swap_colors ? "true" : "false"); + int8_t pin_R1, pin_G1, pin_BL1, pin_R2, pin_G2, pin_BL2; - // Initialize the panel. - Hub75Config mxconfig; - mxconfig.panel_width = CONFIG_HUB75_PANEL_WIDTH; - mxconfig.panel_height = CONFIG_HUB75_PANEL_HEIGHT; - -#if CONFIG_BOARD_TIDBYT_GEN2 - mxconfig.pins.r1 = 5; - mxconfig.pins.g1 = 23; - mxconfig.pins.b1 = 4; - mxconfig.pins.r2 = 2; - mxconfig.pins.g2 = 22; - mxconfig.pins.b2 = 32; - mxconfig.pins.a = 25; - mxconfig.pins.b = 21; - mxconfig.pins.c = 26; - mxconfig.pins.d = 19; - mxconfig.pins.e = -1; // assign to pin 14 if using more than two panels - mxconfig.pins.lat = 18; - mxconfig.pins.oe = 27; - mxconfig.pins.clk = 15; - ESP_LOGI(TAG, "Board preset: Tidbyt Gen2"); -#elif CONFIG_BOARD_TRONBYT_S3_WIDE - mxconfig.pins.r1 = 4; - mxconfig.pins.g1 = 5; - mxconfig.pins.b1 = 6; - mxconfig.pins.r2 = 7; - mxconfig.pins.g2 = 15; - mxconfig.pins.b2 = 16; - mxconfig.pins.a = 17; - mxconfig.pins.b = 18; - mxconfig.pins.c = 8; - mxconfig.pins.d = 3; - mxconfig.pins.e = 46; - mxconfig.pins.lat = 9; - mxconfig.pins.oe = 10; - mxconfig.pins.clk = 11; - ESP_LOGI(TAG, "Board preset: Tronbyt S3 Wide"); -#elif CONFIG_BOARD_TRONBYT_S3 - mxconfig.pins.r1 = 4; - mxconfig.pins.g1 = 6; - mxconfig.pins.b1 = 5; - mxconfig.pins.r2 = 7; - mxconfig.pins.g2 = 16; - mxconfig.pins.b2 = 15; - mxconfig.pins.a = 17; - mxconfig.pins.b = 18; - mxconfig.pins.c = 8; - mxconfig.pins.d = 3; - mxconfig.pins.e = -1; - mxconfig.pins.lat = 9; - mxconfig.pins.oe = 10; - mxconfig.pins.clk = 11; - ESP_LOGI(TAG, "Board preset: Tronbyt S3"); -#elif CONFIG_BOARD_PIXOTICKER - mxconfig.pins.r1 = 2; - mxconfig.pins.g1 = 4; - mxconfig.pins.b1 = 15; - mxconfig.pins.r2 = 16; - mxconfig.pins.g2 = 17; - mxconfig.pins.b2 = 27; - mxconfig.pins.a = 5; - mxconfig.pins.b = 18; - mxconfig.pins.c = 19; - mxconfig.pins.d = 21; - mxconfig.pins.e = 12; - mxconfig.pins.lat = 26; - mxconfig.pins.oe = 25; - mxconfig.pins.clk = 22; - ESP_LOGI(TAG, "Board preset: Pixoticker"); -#elif CONFIG_BOARD_MATRIXPORTAL_S3 - mxconfig.pins.r1 = 42; - mxconfig.pins.r2 = 38; - mxconfig.pins.a = 45; - mxconfig.pins.b = 36; - mxconfig.pins.c = 48; - mxconfig.pins.d = 35; - mxconfig.pins.e = 21; - mxconfig.pins.lat = 47; - mxconfig.pins.oe = 14; - mxconfig.pins.clk = 2; +#if CONFIG_BOARD_MATRIXPORTAL_S3 + pin_R1 = R1; // R1 = 42 + pin_R2 = R2; // R2 = 38 if (swap_colors) { - mxconfig.pins.g1 = 41; - mxconfig.pins.b1 = 40; - mxconfig.pins.g2 = 39; - mxconfig.pins.b2 = 37; + // Swapped configuration for MATRIXPORTALS3 + pin_G1 = 40; + pin_BL1 = 41; + pin_G2 = 37; + pin_BL2 = 39; } else { - mxconfig.pins.g1 = 40; - mxconfig.pins.b1 = 41; - mxconfig.pins.g2 = 37; - mxconfig.pins.b2 = 39; + // Normal configuration for MATRIXPORTALS3 + pin_G1 = 41; + pin_BL1 = 40; + pin_G2 = 39; + pin_BL2 = 37; } - ESP_LOGI(TAG, "Board preset: MatrixPortal S3"); -#else // GEN1 from here down. - mxconfig.pins.a = 26; - mxconfig.pins.b = 5; - mxconfig.pins.c = 25; - mxconfig.pins.d = 18; - mxconfig.pins.e = -1; // assign to pin 14 if using more than two panels - mxconfig.pins.lat = 19; - mxconfig.pins.oe = 32; - mxconfig.pins.clk = 33; +#elif CONFIG_BOARD_TIDBYT_GEN2 || CONFIG_BOARD_TRONBYT_S3_WIDE || CONFIG_BOARD_TRONBYT_S3 || CONFIG_BOARD_PIXOTICKER + // These variants don't support color swapping, use fixed pins + pin_R1 = R1; + pin_G1 = G1; + pin_BL1 = BL1; + pin_R2 = R2; + pin_G2 = G2; + pin_BL2 = BL2; +#else // GEN1 if (swap_colors) { // Swapped configuration for GEN1 - mxconfig.pins.r1 = 21; - mxconfig.pins.g1 = 2; - mxconfig.pins.b1 = 22; - mxconfig.pins.r2 = 23; - mxconfig.pins.g2 = 4; - mxconfig.pins.b2 = 27; + pin_R1 = 21; + pin_G1 = 2; + pin_BL1 = 22; + pin_R2 = 23; + pin_G2 = 4; + pin_BL2 = 27; } else { // Normal configuration for GEN1 - mxconfig.pins.r1 = 2; - mxconfig.pins.g1 = 22; - mxconfig.pins.b1 = 21; - mxconfig.pins.r2 = 4; - mxconfig.pins.g2 = 27; - mxconfig.pins.b2 = 23; + pin_R1 = 2; + pin_G1 = 22; + pin_BL1 = 21; + pin_R2 = 4; + pin_G2 = 27; + pin_BL2 = 23; } - ESP_LOGI(TAG, "Board preset: Tidbyt Gen1"); -#endif - - // Scan Pattern -#if defined(CONFIG_HUB75_SCAN_1_32) - mxconfig.scan_pattern = Hub75ScanPattern::SCAN_1_32; -#elif defined(CONFIG_HUB75_SCAN_1_16) - mxconfig.scan_pattern = Hub75ScanPattern::SCAN_1_16; -#elif defined(CONFIG_HUB75_SCAN_1_8) - mxconfig.scan_pattern = Hub75ScanPattern::SCAN_1_8; -#endif - - // Scan wiring -#if defined(CONFIG_HUB75_WIRING_STANDARD) - mxconfig.scan_wiring = Hub75ScanWiring::STANDARD_TWO_SCAN; -#elif defined(CONFIG_HUB75_WIRING_FOUR_SCAN_16PX) - mxconfig.scan_wiring = Hub75ScanWiring::FOUR_SCAN_16PX_HIGH; -#elif defined(CONFIG_HUB75_WIRING_FOUR_SCAN_32PX) - mxconfig.scan_wiring = Hub75ScanWiring::FOUR_SCAN_32PX_HIGH; -#elif defined(CONFIG_HUB75_WIRING_FOUR_SCAN_64PX) - mxconfig.scan_wiring = Hub75ScanWiring::FOUR_SCAN_64PX_HIGH; -#endif - - // Shift Driver -#if defined(CONFIG_HUB75_DRIVER_GENERIC) - mxconfig.shift_driver = Hub75ShiftDriver::GENERIC; -#elif defined(CONFIG_HUB75_DRIVER_FM6126A) - mxconfig.shift_driver = Hub75ShiftDriver::FM6126A; -#elif defined(CONFIG_HUB75_DRIVER_FM6124) - mxconfig.shift_driver = Hub75ShiftDriver::FM6124; -#elif defined(CONFIG_HUB75_DRIVER_MBI5124) - mxconfig.shift_driver = Hub75ShiftDriver::MBI5124; -#elif defined(CONFIG_HUB75_DRIVER_DP3246) - mxconfig.shift_driver = Hub75ShiftDriver::DP3246; -#endif - -#if CONFIG_HUB75_DOUBLE_BUFFER - mxconfig.double_buffer = true; -#else - mxconfig.double_buffer = false; -#endif - - // Clock Speed -#if defined(CONFIG_HUB75_CLK_32MHZ) - mxconfig.output_clock_speed = Hub75ClockSpeed::HZ_32M; -#elif defined(CONFIG_HUB75_CLK_20MHZ) - mxconfig.output_clock_speed = Hub75ClockSpeed::HZ_20M; -#elif defined(CONFIG_HUB75_CLK_16MHZ) - mxconfig.output_clock_speed = Hub75ClockSpeed::HZ_16M; -#elif defined(CONFIG_HUB75_CLK_10MHZ) - mxconfig.output_clock_speed = Hub75ClockSpeed::HZ_10M; -#elif defined(CONFIG_HUB75_CLK_8MHZ) - mxconfig.output_clock_speed = Hub75ClockSpeed::HZ_8M; -#endif - - mxconfig.min_refresh_rate = CONFIG_HUB75_MIN_REFRESH_RATE; - mxconfig.latch_blanking = CONFIG_HUB75_LATCH_BLANKING; - - // Clock Phase -#ifdef CONFIG_HUB75_CLK_PHASE_INVERTED - mxconfig.clk_phase_inverted = true; -#else - mxconfig.clk_phase_inverted = false; #endif - mxconfig.brightness = CONFIG_HUB75_BRIGHTNESS; + ESP_LOGI(TAG, "Initializing display with swap_colors=%s", swap_colors ? "true" : "false"); - _matrix = new Hub75Driver(mxconfig); - - if (_matrix == NULL) { - ESP_LOGE(TAG, "Failed to allocate Hub75Driver object"); + // Initialize the panel. + HUB75_I2S_CFG::i2s_pins pins = {pin_R1, pin_G1, pin_BL1, pin_R2, pin_G2, pin_BL2, CH_A, + CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK}; + + #if CONFIG_NO_INVERT_CLOCK_PHASE + bool invert_clock_phase = false; + #else + bool invert_clock_phase = true; + #endif + + HUB75_I2S_CFG mxconfig(WIDTH, // width + HEIGHT, // height + 1, // chain length + pins, // pin mapping + HUB75_I2S_CFG::FM6126A, // driver chip + HUB75_I2S_CFG::TYPE138, // line driver + true, // double-buffering + HUB75_I2S_CFG::HZ_10M, // clock speed + 1, // latch blanking + invert_clock_phase // invert clock phase + ); + + _matrix = new MatrixPanel_I2S_DMA(mxconfig); + + if (_matrix == NULL) { // Should not happen with new if it throws std::bad_alloc + ESP_LOGE(TAG, "Failed to allocate MatrixPanel_I2S_DMA object"); return 1; } if (!_matrix->begin()) { - ESP_LOGE(TAG, "Hub75Driver begin() failed"); + ESP_LOGE(TAG, "MatrixPanel_I2S_DMA begin() failed"); delete _matrix; _matrix = NULL; return 1; } - display_set_brightness((CONFIG_HUB75_BRIGHTNESS * 100) / 255); + display_set_brightness(DEFAULT_BRIGHTNESS); return 0; } static inline uint8_t brightness_percent_to_8bit(uint8_t pct) { if (pct > 100) pct = 100; - return (uint8_t)(((uint32_t)pct * 230 + 50) / - 100); // 230 as MAX 8 BIT HARDCODED + return (uint8_t)(((uint32_t)pct * 230 + 50) / 100); // 230 as MAX 8 BIT HARDCODED } void display_set_brightness(uint8_t brightness_pct) { @@ -251,78 +218,65 @@ void display_set_brightness(uint8_t brightness_pct) { uint8_t max_brightness_8bit = MAX_BRIGHTNESS_8BIT; if (brightness_8bit > max_brightness_8bit) { brightness_8bit = max_brightness_8bit; - ESP_LOGI(TAG, "Clamping brightness to MAX_BRIGHTNESS (%d)", - MAX_BRIGHTNESS_8BIT); + ESP_LOGI(TAG, "Clamping brightness to MAX_BRIGHTNESS (%d)", MAX_BRIGHTNESS_8BIT); } #endif - ESP_LOGI(TAG, "Setting brightness to %d%% (%d)", brightness_pct, - brightness_8bit); - _matrix->set_brightness(brightness_8bit); - _matrix->clear(); + ESP_LOGI(TAG, "Setting brightness to %d%% (%d)", brightness_pct, brightness_8bit); + _matrix->setBrightness8(brightness_8bit); + _matrix->clearScreen(); _brightness = brightness_pct; } } void display_shutdown(void) { - _matrix->clear(); - _matrix->end(); + _matrix->clearScreen(); + _matrix->stopDMAoutput(); delete _matrix; _matrix = NULL; } -void display_draw(const uint8_t *pix, int width, int height) { -#if CONFIG_HUB75_PANEL_WIDTH == 128 && CONFIG_HUB75_PANEL_HEIGHT == 64 +void display_draw(const uint8_t *pix, int width, int height, + int channels, int ixR, int ixG, int ixB) { + int scale = 1; + #if CONFIG_BOARD_TRONBYT_S3_WIDE if (width == 64 && height == 32) { - // Optimize scale-by-2 drawing (specifically for 64x32 -> 128x64) - const uint32_t *src32 = (const uint32_t *)pix; - for (int y = 0; y < height; y++) { - uint32_t *dst_row1 = &_scaled_buffer[(y * 2) * 128]; - uint32_t *dst_row2 = &_scaled_buffer[(y * 2 + 1) * 128]; - for (int x = 0; x < width; x++) { - uint32_t pixel = src32[y * width + x]; - // Fill 2x2 block - dst_row1[x * 2] = pixel; - dst_row1[x * 2 + 1] = pixel; - dst_row2[x * 2] = pixel; - dst_row2[x * 2 + 1] = pixel; + scale = 2; // Scale up to 128x64 + } + #endif + + for (unsigned int i = 0; i < height; i++) { + for (unsigned int j = 0; j < width; j++) { + const uint8_t *p = &pix[(i * width + j) * channels]; + uint8_t r = p[ixR]; + uint8_t g = p[ixG]; + uint8_t b = p[ixB]; + + // Draw each pixel scaled up (2x2 pixels for each original pixel) + for (int sy = 0; sy < scale; sy++) { + for (int sx = 0; sx < scale; sx++) { + _matrix->drawPixelRGB888(j * scale + sx, i * scale + sy, r, g, b); + } } } - - _matrix->draw_pixels(0, 0, 128, 64, (uint8_t *)_scaled_buffer, - Hub75PixelFormat::RGB888_32, Hub75ColorOrder::BGR); - _matrix->flip_buffer(); - return; } -#endif - - // Default path: bulk transfer for native resolution - _matrix->draw_pixels(0, 0, width, height, pix, Hub75PixelFormat::RGB888_32, - Hub75ColorOrder::BGR); - _matrix->flip_buffer(); + _matrix->flipDMABuffer(); } -void display_clear(void) { _matrix->clear(); } +void display_clear(void) { _matrix->clearScreen(); } void display_draw_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) { if (_matrix != NULL) { - _matrix->set_pixel(x, y, r, g, b); - _matrix->flip_buffer(); + _matrix->drawPixelRGB888(x, y, r, g, b); + _matrix->flipDMABuffer(); } } -void display_fill_rect(int x, int y, int w, int h, uint8_t r, uint8_t g, - uint8_t b) { - if (_matrix != NULL) { - _matrix->fill(x, y, w, h, r, g, b); - // Note: No flip here, caller must flip - } +void draw_error_indicator_pixel(void) { + display_draw_pixel(0, 0, 100, 0, 0); } -void draw_error_indicator_pixel(void) { display_draw_pixel(0, 0, 100, 0, 0); } - -void display_text(const char *text, int x, int y, uint8_t r, uint8_t g, - uint8_t b, int scale) { +void display_text(const char* text, int x, int y, uint8_t r, uint8_t g, uint8_t b, int scale) { if (_matrix == NULL || text == NULL) { return; } @@ -336,12 +290,12 @@ void display_text(const char *text, int x, int y, uint8_t r, uint8_t g, // Check if character is in font range if (c < FONT5X7_FIRST_CHAR || c > FONT5X7_LAST_CHAR) { - c = ' '; // Replace unsupported characters with space + c = ' '; // Replace unsupported characters with space } // Get font data for this character int char_index = c - FONT5X7_FIRST_CHAR; - const uint8_t *char_data = font5x7[char_index]; + const uint8_t* char_data = font5x7[char_index]; // Draw each column of the character for (int col = 0; col < FONT5X7_CHAR_WIDTH; col++) { @@ -350,18 +304,16 @@ void display_text(const char *text, int x, int y, uint8_t r, uint8_t g, // Draw each row in the column for (int row = 0; row < FONT5X7_CHAR_HEIGHT; row++) { if (column_data & (1 << row)) { - int px = cursor_x + (col * scale); - int py = cursor_y + (row * scale); - - if (scale > 1) { - // Optimize scaled text using fill - _matrix->fill(px, py, scale, scale, r, g, b); - } else { - // Draw pixel(s) based on scale - // Check bounds - if (px >= 0 && px < CONFIG_HUB75_PANEL_WIDTH && py >= 0 && - py < CONFIG_HUB75_PANEL_HEIGHT) { - _matrix->set_pixel(px, py, r, g, b); + // Draw pixel(s) based on scale + for (int sy = 0; sy < scale; sy++) { + for (int sx = 0; sx < scale; sx++) { + int px = cursor_x + (col * scale) + sx; + int py = cursor_y + (row * scale) + sy; + + // Check bounds + if (px >= 0 && px < WIDTH && py >= 0 && py < HEIGHT) { + _matrix->drawPixelRGB888(px, py, r, g, b); + } } } } @@ -377,6 +329,6 @@ void display_text(const char *text, int x, int y, uint8_t r, uint8_t g, void display_flip(void) { if (_matrix != NULL) { - _matrix->flip_buffer(); + _matrix->flipDMABuffer(); } } diff --git a/main/display.h b/main/display.h index 039d912..7329812 100644 --- a/main/display.h +++ b/main/display.h @@ -3,12 +3,10 @@ #include #include -#include "sdkconfig.h" - #define DISPLAY_MAX_BRIGHTNESS 100 #define DISPLAY_MIN_BRIGHTNESS 0 - -extern volatile int32_t isAnimating; // Declare the variable +#define DISPLAY_DEFAULT_BRIGHTNESS 30 +extern int32_t isAnimating; // Declare the variable #ifdef __cplusplus extern "C" { #endif @@ -16,14 +14,13 @@ int display_initialize(void); void display_set_brightness(uint8_t brightness_pct); void display_shutdown(void); -void display_draw(const uint8_t* pix, int width, int height); +void display_draw(const uint8_t *pix, int width, int height, int channels, + int ixR, int ixG, int ixB); + void display_clear(void); void display_draw_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b); -void display_fill_rect(int x, int y, int w, int h, uint8_t r, uint8_t g, - uint8_t b); void draw_error_indicator_pixel(void); -void display_text(const char* text, int x, int y, uint8_t r, uint8_t g, - uint8_t b, int scale); +void display_text(const char* text, int x, int y, uint8_t r, uint8_t g, uint8_t b, int scale); void display_flip(void); // int32_t isAnimating = 0; // Initialize with a valid value diff --git a/main/gfx.c b/main/gfx.c index 1e22b8c..4c334a5 100644 --- a/main/gfx.c +++ b/main/gfx.c @@ -1,19 +1,18 @@ -#include #include -#include #include #include #include -#include #include #include #include +#include +#include -#include "assets.h" #include "display.h" #include "esp_timer.h" -#include "nvs_settings.h" +#include "assets.h" #include "version.h" +#include "nvs_settings.h" static const char *TAG = "gfx"; @@ -28,29 +27,16 @@ struct gfx_state { size_t len; int32_t dwell_secs; int counter; - int loaded_counter; // Counter that tracks which image has been loaded by gfx - // task - esp_websocket_client_handle_t - ws_handle; // Websocket handle for sending notifications - volatile bool paused; + int loaded_counter; // Counter that tracks which image has been loaded by gfx task + esp_websocket_client_handle_t ws_handle; // Websocket handle for sending notifications }; static struct gfx_state *_state = NULL; static void gfx_loop(void *arg); -static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, - int32_t *isAnimating); +static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, int32_t *isAnimating); static void send_websocket_notification(int counter); -static bool is_static_asset(const void *ptr) { - if (ptr == ASSET_BOOT_WEBP) return true; - if (ptr == ASSET_CONFIG_WEBP) return true; - if (ptr == ASSET_404_WEBP) return true; - if (ptr == ASSET_OVERSIZE_WEBP) return true; - if (ptr == ASSET_NOCONNECT_WEBP) return true; - return false; -} - int gfx_initialize(const char *img_url) { // Only initialize once if (_state) { @@ -63,20 +49,26 @@ int gfx_initialize(const char *img_url) { ESP_LOGI(TAG, "largest heap %d", heapl); // ESP_LOGI(TAG, "calling calloc"); // Initialize state - ESP_LOGI(TAG, "Using static buffer for boot animation"); + ESP_LOGI(TAG, "Allocating buffer of size: %d", ASSET_BOOT_WEBP_LEN); _state = calloc(1, sizeof(struct gfx_state)); _state->len = ASSET_BOOT_WEBP_LEN; - // Use static asset directly, no allocation or copy needed - _state->buf = (void *)ASSET_BOOT_WEBP; - _state->paused = false; - + ESP_LOGI(TAG,"calloc buff"); + _state->buf = calloc(1, ASSET_BOOT_WEBP_LEN); + ESP_LOGI(TAG, "done calloc, copying"); + if (_state->buf == NULL) { + ESP_LOGE("gfx", "Memory allocation failed!"); + return 1; + } + memcpy(_state->buf, ASSET_BOOT_WEBP, ASSET_BOOT_WEBP_LEN); + ESP_LOGI(TAG, "done, copying"); + _state->mutex = xSemaphoreCreateMutex(); if (_state->mutex == NULL) { ESP_LOGE(TAG, "Could not create gfx mutex"); return 1; } - ESP_LOGI(TAG, "done with gfx init"); + ESP_LOGI(TAG,"done with gfx init"); // Initialize the display if (display_initialize()) { @@ -85,92 +77,89 @@ int gfx_initialize(const char *img_url) { // Display version if not skipped if (!nvs_get_skip_display_version()) { - // Display version and image_url for 1 second - display_clear(); - char version_text[32]; - snprintf(version_text, sizeof(version_text), "v%s", FIRMWARE_VERSION); - - // Parse URL to extract host and last two path components - if (img_url != NULL && strlen(img_url) > 0) { - ESP_LOGI(TAG, "Full URL: %s", img_url); - char host_only[64] = {0}; - char last_two_components[32] = {0}; - - struct http_parser_url u; - http_parser_url_init(&u); - - if (http_parser_parse_url(img_url, strlen(img_url), 0, &u) == 0) { - if (u.field_set & (1 << UF_HOST)) { - size_t host_len = u.field_data[UF_HOST].len; - if (host_len >= sizeof(host_only)) host_len = sizeof(host_only) - 1; - memcpy(host_only, img_url + u.field_data[UF_HOST].off, host_len); - host_only[host_len] = '\0'; - } + // Display version and image_url for 1 second + display_clear(); + char version_text[32]; + snprintf(version_text, sizeof(version_text), "v%s", FIRMWARE_VERSION); + + // Parse URL to extract host and last two path components + if (img_url != NULL && strlen(img_url) > 0) { + ESP_LOGI(TAG, "Full URL: %s", img_url); + char host_only[64] = {0}; + char last_two_components[32] = {0}; + + struct http_parser_url u; + http_parser_url_init(&u); + + if (http_parser_parse_url(img_url, strlen(img_url), 0, &u) == 0) { + if (u.field_set & (1 << UF_HOST)) { + size_t host_len = u.field_data[UF_HOST].len; + if (host_len >= sizeof(host_only)) host_len = sizeof(host_only) - 1; + memcpy(host_only, img_url + u.field_data[UF_HOST].off, host_len); + host_only[host_len] = '\0'; + } - if (u.field_set & (1 << UF_PATH)) { - const char *path = img_url + u.field_data[UF_PATH].off; - size_t path_len = u.field_data[UF_PATH].len; - const char *last_slash = NULL; - const char *second_last_slash = NULL; - - for (size_t i = 0; i < path_len; i++) { - if (path[i] == '/') { - second_last_slash = last_slash; - last_slash = path + i; - } - } + if (u.field_set & (1 << UF_PATH)) { + const char *path = img_url + u.field_data[UF_PATH].off; + size_t path_len = u.field_data[UF_PATH].len; + const char *last_slash = NULL; + const char *second_last_slash = NULL; - if (second_last_slash != NULL) { - size_t len = (path + path_len) - second_last_slash; - if (len >= sizeof(last_two_components)) - len = sizeof(last_two_components) - 1; - memcpy(last_two_components, second_last_slash, len); - last_two_components[len] = '\0'; - } else { - size_t len = path_len; - if (len >= sizeof(last_two_components)) - len = sizeof(last_two_components) - 1; - memcpy(last_two_components, path, len); - last_two_components[len] = '\0'; + for (size_t i = 0; i < path_len; i++) { + if (path[i] == '/') { + second_last_slash = last_slash; + last_slash = path + i; } } - } - // Display host at the top, left-aligned - if (strlen(host_only) > 0) { - ESP_LOGI(TAG, "Displaying host: '%s' at y=0", host_only); - display_text(host_only, 0, 0, 255, 255, 255, 1); + if (second_last_slash != NULL) { + size_t len = (path + path_len) - second_last_slash; + if (len >= sizeof(last_two_components)) len = sizeof(last_two_components) - 1; + memcpy(last_two_components, second_last_slash, len); + last_two_components[len] = '\0'; + } else { + size_t len = path_len; + if (len >= sizeof(last_two_components)) len = sizeof(last_two_components) - 1; + memcpy(last_two_components, path, len); + last_two_components[len] = '\0'; + } } + } - // Display last 11 chars of path components in the middle, left-aligned - if (strlen(last_two_components) > 0) { - const char *display_path = last_two_components; - size_t path_len = strlen(last_two_components); + // Display host at the top, left-aligned + if (strlen(host_only) > 0) { + ESP_LOGI(TAG, "Displaying host: '%s' at y=0", host_only); + display_text(host_only, 0, 0, 255, 255, 255, 1); + } - // If longer than 11 chars, show only the last 11 - if (path_len > 11) { - display_path = last_two_components + (path_len - 11); - } + // Display last 11 chars of path components in the middle, left-aligned + if (strlen(last_two_components) > 0) { + const char* display_path = last_two_components; + size_t path_len = strlen(last_two_components); - ESP_LOGI(TAG, "Displaying path components: '%s' at y=10", display_path); - display_text(display_path, 0, 10, 255, 255, 255, 1); - } else { - ESP_LOGW(TAG, "No path components found to display"); + // If longer than 11 chars, show only the last 11 + if (path_len > 11) { + display_path = last_two_components + (path_len - 11); } + + ESP_LOGI(TAG, "Displaying path components: '%s' at y=10", display_path); + display_text(display_path, 0, 10, 255, 255, 255, 1); + } else { + ESP_LOGW(TAG, "No path components found to display"); } + } - // Display version at the bottom, centered - // Calculate x position to center text (approximately) - // Each character is 6 pixels wide (5 + 1 spacing) - int text_width = strlen(version_text) * 6; - int x = (64 - text_width) / 2; // Center on 64-pixel wide display - display_text(version_text, x, 24, 255, 255, 255, - 1); // White text, centered at bottom + // Display version at the bottom, centered + // Calculate x position to center text (approximately) + // Each character is 6 pixels wide (5 + 1 spacing) + int text_width = strlen(version_text) * 6; + int x = (64 - text_width) / 2; // Center on 64-pixel wide display + display_text(version_text, x, 24, 255, 255, 255, 1); // White text, centered at bottom - // Flip the buffer once to show all three text lines at the same time - display_flip(); + // Flip the buffer once to show all three text lines at the same time + display_flip(); - vTaskDelay(pdMS_TO_TICKS(2000)); + vTaskDelay(pdMS_TO_TICKS(2000)); } // Launch the graphics loop in separate task @@ -213,19 +202,20 @@ static void send_websocket_notification(int counter) { // Create JSON message: {"displaying": 42} char message[128]; - int len = snprintf(message, sizeof(message), "{\"displaying\":%d}", counter); + int len = snprintf(message, sizeof(message), + "{\"displaying\":%d}", + counter); if (len < 0 || len >= sizeof(message)) { ESP_LOGE(TAG, "Failed to format websocket notification message"); return; } - int sent = esp_websocket_client_send_text(_state->ws_handle, message, len, - portMAX_DELAY); + int sent = esp_websocket_client_send_text(_state->ws_handle, message, len, portMAX_DELAY); if (sent < 0) { ESP_LOGE(TAG, "Failed to send websocket notification"); } else { - ESP_LOGD(TAG, "Sent websocket notification: %s", message); + ESP_LOGI(TAG, "Sent websocket notification: %s", message); } } @@ -236,19 +226,10 @@ int gfx_update(void *webp, size_t len, int32_t dwell_secs) { } // If a new frame arrives before the previous one is consumed by the gfx task, - // free the old buffer here to prevent a memory leak (frame-dropping - // strategy). + // free the old buffer here to prevent a memory leak (frame-dropping strategy). if (_state->buf) { - if (!is_static_asset(_state->buf)) { - ESP_LOGW(TAG, - "Dropping queued image (counter %d) - new image arrived before " - "it was displayed", - _state->counter); - free(_state->buf); - } else { - ESP_LOGD(TAG, "Dropping queued static image (counter %d)", - _state->counter); - } + ESP_LOGW(TAG, "Dropping queued image (counter %d) - new image arrived before it was displayed", _state->counter); + free(_state->buf); _state->buf = NULL; } @@ -266,20 +247,16 @@ int gfx_update(void *webp, size_t len, int32_t dwell_secs) { } // Send "queued" notification immediately when image is queued - if (_state->ws_handle && - esp_websocket_client_is_connected(_state->ws_handle)) { + if (_state->ws_handle && esp_websocket_client_is_connected(_state->ws_handle)) { char message[64]; - int msg_len = - snprintf(message, sizeof(message), "{\"queued\":%d}", counter); + int msg_len = snprintf(message, sizeof(message), "{\"queued\":%d}", counter); if (msg_len > 0 && msg_len < sizeof(message)) { - esp_websocket_client_send_text(_state->ws_handle, message, msg_len, - portMAX_DELAY); - ESP_LOGD(TAG, "Sent queued notification: %s", message); + esp_websocket_client_send_text(_state->ws_handle, message, msg_len, portMAX_DELAY); + ESP_LOGI(TAG, "Sent queued notification: %s", message); } } - return counter; // Return the counter value (>= 0) so caller can wait for it - // to be loaded + return counter; // Return the counter value (>= 0) so caller can wait for it to be loaded } int gfx_get_loaded_counter(void) { @@ -300,8 +277,8 @@ int gfx_get_loaded_counter(void) { return loaded; } -int gfx_display_asset(const char *asset_type) { - const uint8_t *asset_data = NULL; +int gfx_display_asset(const char* asset_type) { + const uint8_t* asset_data = NULL; size_t asset_len = 0; // Determine which asset to display @@ -324,48 +301,34 @@ int gfx_display_asset(const char *asset_type) { } // Allocate heap memory and copy asset data - // Optimization: pass static pointer directly to gfx_update - // logic in gfx_update/gfx_loop ensures it is not freed. + uint8_t *asset_heap_copy = (uint8_t *)malloc(asset_len); + if (asset_heap_copy == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for %s asset copy", asset_type); + return 1; + } + + memcpy(asset_heap_copy, asset_data, asset_len); // Interrupt current animation to display asset immediately isAnimating = -1; // Display the asset with no dwell time (static display) - // We cast const away because gfx_update takes void*, but we know we won't - // write to it. - int result = gfx_update((void *)asset_data, asset_len, 0); + int result = gfx_update(asset_heap_copy, asset_len, 0); if (result < 0) { - // Only free if gfx_update failed to take ownership (returned negative - // error) + // Only free if gfx_update failed to take ownership (returned negative error) ESP_LOGE(TAG, "Failed to update graphics with %s asset", asset_type); + free(asset_heap_copy); return 1; } - // gfx_update now owns the asset buffer (returns counter >= 0 on success) + // gfx_update now owns the asset_heap_copy buffer (returns counter >= 0 on success) return 0; } -void gfx_display_text(const char *text, int x, int y, uint8_t r, uint8_t g, - uint8_t b, int scale) { +void gfx_display_text(const char* text, int x, int y, uint8_t r, uint8_t g, uint8_t b, int scale) { display_text(text, x, y, r, g, b, scale); } -void gfx_stop(void) { - if (_state) { - isAnimating = -1; // Signal current draw to stop - _state->paused = true; - ESP_LOGI(TAG, "Graphics loop paused"); - } -} - -void gfx_start(void) { - if (_state) { - isAnimating = 0; - _state->paused = false; - ESP_LOGI(TAG, "Graphics loop resumed"); - } -} - void gfx_shutdown(void) { display_shutdown(); } static void gfx_loop(void *args) { @@ -377,14 +340,9 @@ static void gfx_loop(void *args) { ESP_LOGI(TAG, "Graphics loop running on core %d", xPortGetCoreID()); for (;;) { - if (_state->paused) { - vTaskDelay(pdMS_TO_TICKS(100)); - continue; - } - if (pdTRUE != xSemaphoreTake(_state->mutex, portMAX_DELAY)) { ESP_LOGE(TAG, "Could not take gfx mutex"); - if (webp && !is_static_asset(webp)) { + if (webp) { free(webp); webp = NULL; } @@ -393,15 +351,15 @@ static void gfx_loop(void *args) { // If there's new data, take ownership of buffer if (counter != _state->counter) { - ESP_LOGD(TAG, "Loaded new webp"); - if (webp && !is_static_asset(webp)) free(webp); + ESP_LOGI(TAG, "Loaded new webp"); + if (webp) free(webp); webp = _state->buf; len = _state->len; dwell_secs = _state->dwell_secs; - _state->buf = NULL; // gfx_loop now owns the buffer + _state->buf = NULL; // gfx_loop now owns the buffer counter = _state->counter; _state->loaded_counter = counter; // Signal that we've loaded this image - if (*isAnimating == -1 && !_state->paused) *isAnimating = 1; + if (*isAnimating == -1) *isAnimating = 1; // Send websocket notification that we're now displaying this image send_websocket_notification(counter); @@ -419,9 +377,7 @@ static void gfx_loop(void *args) { vTaskDelay(pdMS_TO_TICKS(1 * 1000)); *isAnimating = 0; // Free the invalid buffer to prevent re-drawing it - if (webp && !is_static_asset(webp)) { - free(webp); - } + free(webp); webp = NULL; len = 0; } @@ -432,40 +388,39 @@ static void gfx_loop(void *args) { } } -static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, - int32_t *isAnimating) { +static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, int32_t *isAnimating) { // Set up WebP decoder // ESP_LOGI(TAG, "starting draw_webp"); int app_dwell_secs = dwell_secs; + int64_t dwell_us; - - if (app_dwell_secs <= 0) { - ESP_LOGW(TAG, "dwell_secs is 0. Looping one more time while we wait."); - dwell_us = 1 * 1000000; // default to 1s if it's zero so we loop again or - // show the image for 1 more second. + + if (app_dwell_secs <= 0 ) { + ESP_LOGW(TAG,"dwell_secs is 0. Looping one more time while we wait."); + dwell_us = 1 * 1000000; // default to 1s if it's zero so we loop again or show the image for 1 more second. } else { - ESP_LOGD(TAG, "dwell_secs: %d", app_dwell_secs); + ESP_LOGI(TAG, "dwell_secs : %d", app_dwell_secs); dwell_us = app_dwell_secs * 1000000; } // ESP_LOGI(TAG, "frame count: %d", animation.frame_count); - + WebPData webpData; WebPDataInit(&webpData); webpData.bytes = buf; webpData.size = len; - + WebPAnimDecoderOptions decoderOptions; WebPAnimDecoderOptionsInit(&decoderOptions); decoderOptions.color_mode = MODE_RGBA; - + WebPAnimDecoder *decoder = WebPAnimDecoderNew(&webpData, &decoderOptions); if (decoder == NULL) { ESP_LOGE(TAG, "Could not create WebP decoder"); draw_error_indicator_pixel(); return 1; } - + WebPAnimInfo animation; if (!WebPAnimDecoderGetInfo(decoder, &animation)) { ESP_LOGE(TAG, "Could not get WebP animation"); @@ -476,52 +431,44 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, // ESP_LOGI(TAG, "frame count: %d", animation.frame_count); int64_t start_us = esp_timer_get_time(); - while (esp_timer_get_time() - start_us < dwell_us && *isAnimating != -1 && - !_state->paused) { + while (esp_timer_get_time() - start_us < dwell_us && *isAnimating != -1) { int lastTimestamp = 0; int delay = 0; - TickType_t lastWakeTime = xTaskGetTickCount(); + TickType_t drawStartTick = xTaskGetTickCount(); // Draw each frame, and sleep for the delay - while (WebPAnimDecoderHasMoreFrames(decoder) && *isAnimating != -1 && - !_state->paused) { + while (WebPAnimDecoderHasMoreFrames(decoder) && *isAnimating != -1) { uint8_t *pix; int timestamp; WebPAnimDecoderGetNext(decoder, &pix, ×tamp); - if (delay > 0) { - // Wait for the previous frame's duration to expire. - // Since we decoded *during* this time, we only sleep for the remainder. - xTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(delay)); + xTaskDelayUntil(&drawStartTick, pdMS_TO_TICKS(delay)); } else { - // First frame or no delay: yield briefly to let other tasks run - vTaskDelay(pdMS_TO_TICKS(1)); - lastWakeTime = xTaskGetTickCount(); + vTaskDelay(10); // small delay for yield. } - - display_draw(pix, animation.canvas_width, animation.canvas_height); + drawStartTick = xTaskGetTickCount(); + display_draw(pix, animation.canvas_width, animation.canvas_height, 4, 0, + 1, 2); delay = timestamp - lastTimestamp; lastTimestamp = timestamp; } - + // reset decoder to start from the beginning WebPAnimDecoderReset(decoder); - + if (delay > 0) { - xTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(delay)); + xTaskDelayUntil(&drawStartTick, pdMS_TO_TICKS(delay)); } else { - vTaskDelay( - pdMS_TO_TICKS(100)); // Add a small fallback delay to yield CPU + vTaskDelay(pdMS_TO_TICKS(100)); // Add a small fallback delay to yield CPU } - + // In case of a single frame, sleep for app_dwell_secs if (animation.frame_count == 1) { - // For static images, we need to check isAnimating periodically during the - // dwell time Break the dwell time into 100ms chunks so we can respond to - // immediate commands + // For static images, we need to check isAnimating periodically during the dwell time + // Break the dwell time into 100ms chunks so we can respond to immediate commands int64_t static_start_us = esp_timer_get_time(); while (esp_timer_get_time() - static_start_us < dwell_us) { - if (*isAnimating == -1 || _state->paused) { + if (*isAnimating == -1) { // Immediate command received, break out of dwell time break; } @@ -532,7 +479,7 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, } WebPAnimDecoderDelete(decoder); - ESP_LOGD(TAG, "Setting isAnimating to 0"); + ESP_LOGI(TAG, "Setting isAnimating to 0"); *isAnimating = 0; return 0; } diff --git a/main/idf_component.yml b/main/idf_component.yml index 5300d6c..1fba22b 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -1,8 +1,7 @@ dependencies: espressif/esp_websocket_client: "1.6.0" - esp-hub75: - git: https://github.com/esphome-libs/esp-hub75.git - version: "v0.3.0" - path: components/hub75 + esp32-hub75-matrixpanel-dma: + git: https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git + version: "3.0.13" libwebp: git: https://github.com/tronbyt/libwebp.git diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 71ac753..ec9087a 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -1,138 +1,33 @@ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.5.2 Project Minimal Configuration - -# Ensures the binary is deterministic (bit-for-bit identical). +# CONFIG_APP_REPRODUCIBLE_BUILD=y - -# Length of the SHA256 hash of the ELF file stored in the app header. CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16 - -# Optimize for performance (-O2) instead of size (-Os). CONFIG_COMPILER_OPTIMIZATION_PERF=y - -# Configure the flasher for 8MB of flash memory. +CONFIG_ESP32_HUB75_USE_GFX=n CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y - -# Allow firmware updates over insecure HTTP. CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP=y - -# Interrupt Watchdog timeout. CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 - -# Increase stack size for the main application task. CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 - -# Reduce PHY TX power when brownout reset. CONFIG_ESP_PHY_REDUCE_TX_POWER=y - -# Enable TLS session tickets for faster reconnects. CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS=y - -# Enable 802.11R (Fast Transition) Support. -CONFIG_ESP_WIFI_11R_SUPPORT=y - -# Wifi sleep optimize when beacon lost -CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT=y - -# Place additional frequently called Wi-Fi library functions in IRAM. -# Costs 5 KB of IRAM but increases WiFi throughput. -CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y - -# Use static allocation for WiFi TX buffers (increases stability). -# If PSRAM is enabled, "Static" should be selected to guarantee enough WiFi TX buffers. CONFIG_ESP_WIFI_STATIC_TX_BUFFER=y - -# Set FreeRTOS tick rate to 1ms (standard for high precision). CONFIG_FREERTOS_HZ=1000 - -# Increase stack size for the internal FreeRTOS timer task. -# The default is too small. CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 - -# Enable double buffering for smooth animations. -CONFIG_HUB75_DOUBLE_BUFFER=y - -# Width of a single LED panel. -CONFIG_HUB75_PANEL_WIDTH=64 - -# Height of a single LED panel. -CONFIG_HUB75_PANEL_HEIGHT=32 - -# Use the driver for FM6126A shift registers. -CONFIG_HUB75_DRIVER_FM6126A=y - -# Insert blanking cycles during latching to prevent ghosting. -CONFIG_HUB75_LATCH_BLANKING=1 - -# Set the display frequency to 8 MHz -# Increasing this improves refresh rates and color accuracy, but WiFi -# stability suffers. See -# https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/discussions/258 -CONFIG_HUB75_CLK_8MHZ=y - -# Target SoC architecture. CONFIG_IDF_TARGET="esp32" - -# Logging -CONFIG_LOG_COLORS=n -CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM_FULL=y +CONFIG_LOG_COLORS=y CONFIG_LOG_VERSION_2=y - -# Max number of IP addresses returned per DNS query. CONFIG_LWIP_DNS_MAX_HOST_IP=4 - -# Enable NTP server acquisition via DHCP Option 42 -CONFIG_LWIP_DHCP_GET_NTP_SRV=y -CONFIG_LWIP_SNTP_MAX_SERVERS=3 - -# Enable IPv6 Stateless Address Autoconfiguration (SLAAC). CONFIG_LWIP_IPV6_AUTOCONFIG=y - -# Max DNS servers to accept via IPv6 Router Advertisements. CONFIG_LWIP_IPV6_RDNSS_MAX_DNS_SERVERS=2 - -# Allow checking for available data on a netconn. CONFIG_LWIP_SO_RCVBUF=y - -# Use esp_getaddrinfo() instead of lwip_getaddrinfo() for IPv4/IPv6 dual stacks. +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5744 +CONFIG_LWIP_TCP_WND_DEFAULT=5744 CONFIG_LWIP_USE_ESP_GETADDRINFO=y - -# Force mbedTLS to use PSRAM for large allocations (saves internal RAM). CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y - -# Enable TLS 1.3 support. CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y - -# Do not put math/fast functions in IRAM (saves internal RAM). CONFIG_NO_FAST_FUNCTIONS=y - -# Use a custom partition table layout. CONFIG_PARTITION_TABLE_CUSTOM=y - -# Path to the custom partition table. CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="boards/default_8mb.csv" - -# Enable support for external SPI RAM (PSRAM). CONFIG_SPIRAM=y - -# Try to allocate memories of WiFi and LWIP in SPIRAM firstly. If failed, allocate internal memory -CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y - -# Allow .bss segment placed in external memory -CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y - -# Allow .noinit segment placed in external memory -CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY=y - -# Maximum malloc() size, in bytes, to always put in internal memory -CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=128 - -# Errata fix for CAN/TWAI (disabled). CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=n - -# Application Defaults -CONFIG_REFRESH_INTERVAL_SECONDS=10 -CONFIG_ENABLE_AP_MODE=y -CONFIG_ENABLE_WIFI_POWER_SAVE=y -CONFIG_SKIP_DISPLAY_VERSION=n -CONFIG_PREFER_IPV6=n From 6e3864a40a49293cfeef7cdb76249a5f0da423e3 Mon Sep 17 00:00:00 2001 From: Tavis Date: Sun, 25 Jan 2026 23:28:07 -1000 Subject: [PATCH 02/11] ditry revert --- .gitignore | 1 + main/display.cpp | 229 +++++++++++++++++++++-------------------- main/display.h | 5 +- main/gfx.c | 260 +++++++++++++++++++++++++---------------------- main/main.c | 4 +- main/ota.c | 6 -- main/sntp.c | 2 +- 7 files changed, 266 insertions(+), 241 deletions(-) diff --git a/.gitignore b/.gitignore index 25320fb..8c55b6d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ sdkconfig.old secrets.cmake secrets.json secrets.json.injected* +.cache/clangd/* diff --git a/main/display.cpp b/main/display.cpp index edf655f..36f60bd 100644 --- a/main/display.cpp +++ b/main/display.cpp @@ -1,76 +1,77 @@ #include "display.h" -#include "font5x7.h" -#include "nvs_settings.h" #include + +#include "font5x7.h" +#include "nvs_settings.h" #if CONFIG_BOARD_TIDBYT_GEN2 - #define R1 5 - #define G1 23 - #define BL1 4 - #define R2 2 - #define G2 22 - #define BL2 32 - - #define CH_A 25 - #define CH_B 21 - #define CH_C 26 - #define CH_D 19 - #define CH_E -1 // assign to pin 14 if using more than two panels - - #define LAT 18 - #define OE 27 - #define CLK 15 +#define R1 5 +#define G1 23 +#define BL1 4 +#define R2 2 +#define G2 22 +#define BL2 32 + +#define CH_A 25 +#define CH_B 21 +#define CH_C 26 +#define CH_D 19 +#define CH_E -1 // assign to pin 14 if using more than two panels + +#define LAT 18 +#define OE 27 +#define CLK 15 #elif CONFIG_BOARD_TRONBYT_S3_WIDE - #define R1 4 - #define G1 5 - #define BL1 6 - #define R2 7 - #define G2 15 - #define BL2 16 - - #define CH_A 17 - #define CH_B 18 - #define CH_C 8 - #define CH_D 3 - #define CH_E 46 - #define LAT 9 - #define OE 10 - #define CLK 11 - - #define WIDTH 128 - #define HEIGHT 64 +#define R1 4 +#define G1 5 +#define BL1 6 +#define R2 7 +#define G2 15 +#define BL2 16 + +#define CH_A 17 +#define CH_B 18 +#define CH_C 8 +#define CH_D 3 +#define CH_E 46 +#define LAT 9 +#define OE 10 +#define CLK 11 + +#define WIDTH 128 +#define HEIGHT 64 #elif CONFIG_BOARD_TRONBYT_S3 - #define R1 4 - #define G1 6 - #define BL1 5 - #define R2 7 - #define G2 16 - #define BL2 15 - - #define CH_A 17 - #define CH_B 18 - #define CH_C 8 - #define CH_D 3 - #define CH_E -1 - - #define LAT 9 - #define OE 10 - #define CLK 11 +#define R1 4 +#define G1 6 +#define BL1 5 +#define R2 7 +#define G2 16 +#define BL2 15 + +#define CH_A 17 +#define CH_B 18 +#define CH_C 8 +#define CH_D 3 +#define CH_E -1 + +#define LAT 9 +#define OE 10 +#define CLK 11 #elif CONFIG_BOARD_PIXOTICKER - #define R1 2 - #define G1 4 - #define BL1 15 - #define R2 16 - #define G2 17 - #define BL2 27 - #define CH_A 5 - #define CH_B 18 - #define CH_C 19 - #define CH_D 21 - #define CH_E 12 - #define CLK 22 - #define LAT 26 - #define OE 25 +#define R1 2 +#define G1 4 +#define BL1 15 +#define R2 16 +#define G2 17 +#define BL2 27 +#define CH_A 5 +#define CH_B 18 +#define CH_C 19 +#define CH_D 21 +#define CH_E 12 +#define CLK 22 +#define LAT 26 +#define OE 25 #elif CONFIG_BOARD_MATRIXPORTAL_S3 // R1, G1, B1, R2, G2, B2 // uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; @@ -78,26 +79,26 @@ // uint8_t clockPin = 2; // uint8_t latchPin = 47; // uint8_t oePin = 14; - #define R1 42 - #define R2 38 - #define CH_A 45 - #define CH_B 36 - #define CH_C 48 - #define CH_D 35 - #define CH_E 21 - #define CLK 2 - #define LAT 47 - #define OE 14 -#else // GEN1 from here down. - #define CH_A 26 - #define CH_B 5 - #define CH_C 25 - #define CH_D 18 - #define CH_E -1 // assign to pin 14 if using more than two panels - - #define LAT 19 - #define OE 32 - #define CLK 33 +#define R1 42 +#define R2 38 +#define CH_A 45 +#define CH_B 36 +#define CH_C 48 +#define CH_D 35 +#define CH_E 21 +#define CLK 2 +#define LAT 47 +#define OE 14 +#else // GEN1 from here down. +#define CH_A 26 +#define CH_B 5 +#define CH_C 25 +#define CH_D 18 +#define CH_E -1 // assign to pin 14 if using more than two panels + +#define LAT 19 +#define OE 32 +#define CLK 33 #endif #ifndef WIDTH @@ -109,7 +110,7 @@ #endif static MatrixPanel_I2S_DMA *_matrix; -static uint8_t _brightness = DEFAULT_BRIGHTNESS; +static uint8_t _brightness = DISPLAY_DEFAULT_BRIGHTNESS; static const char *TAG = "display"; int display_initialize(void) { @@ -135,7 +136,8 @@ int display_initialize(void) { pin_G2 = 39; pin_BL2 = 37; } -#elif CONFIG_BOARD_TIDBYT_GEN2 || CONFIG_BOARD_TRONBYT_S3_WIDE || CONFIG_BOARD_TRONBYT_S3 || CONFIG_BOARD_PIXOTICKER +#elif CONFIG_BOARD_TIDBYT_GEN2 || CONFIG_BOARD_TRONBYT_S3_WIDE || \ + CONFIG_BOARD_TRONBYT_S3 || CONFIG_BOARD_PIXOTICKER // These variants don't support color swapping, use fixed pins pin_R1 = R1; pin_G1 = G1; @@ -143,7 +145,7 @@ int display_initialize(void) { pin_R2 = R2; pin_G2 = G2; pin_BL2 = BL2; -#else // GEN1 +#else // GEN1 if (swap_colors) { // Swapped configuration for GEN1 pin_R1 = 21; @@ -163,17 +165,19 @@ int display_initialize(void) { } #endif - ESP_LOGI(TAG, "Initializing display with swap_colors=%s", swap_colors ? "true" : "false"); + ESP_LOGI(TAG, "Initializing display with swap_colors=%s", + swap_colors ? "true" : "false"); // Initialize the panel. - HUB75_I2S_CFG::i2s_pins pins = {pin_R1, pin_G1, pin_BL1, pin_R2, pin_G2, pin_BL2, CH_A, - CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK}; + HUB75_I2S_CFG::i2s_pins pins = {pin_R1, pin_G1, pin_BL1, pin_R2, pin_G2, + pin_BL2, CH_A, CH_B, CH_C, CH_D, + CH_E, LAT, OE, CLK}; - #if CONFIG_NO_INVERT_CLOCK_PHASE +#if CONFIG_NO_INVERT_CLOCK_PHASE bool invert_clock_phase = false; - #else +#else bool invert_clock_phase = true; - #endif +#endif HUB75_I2S_CFG mxconfig(WIDTH, // width HEIGHT, // height @@ -189,7 +193,8 @@ int display_initialize(void) { _matrix = new MatrixPanel_I2S_DMA(mxconfig); - if (_matrix == NULL) { // Should not happen with new if it throws std::bad_alloc + if (_matrix == + NULL) { // Should not happen with new if it throws std::bad_alloc ESP_LOGE(TAG, "Failed to allocate MatrixPanel_I2S_DMA object"); return 1; } @@ -200,14 +205,15 @@ int display_initialize(void) { _matrix = NULL; return 1; } - display_set_brightness(DEFAULT_BRIGHTNESS); + display_set_brightness(DISPLAY_DEFAULT_BRIGHTNESS); return 0; } static inline uint8_t brightness_percent_to_8bit(uint8_t pct) { if (pct > 100) pct = 100; - return (uint8_t)(((uint32_t)pct * 230 + 50) / 100); // 230 as MAX 8 BIT HARDCODED + return (uint8_t)(((uint32_t)pct * 230 + 50) / + 100); // 230 as MAX 8 BIT HARDCODED } void display_set_brightness(uint8_t brightness_pct) { @@ -218,11 +224,13 @@ void display_set_brightness(uint8_t brightness_pct) { uint8_t max_brightness_8bit = MAX_BRIGHTNESS_8BIT; if (brightness_8bit > max_brightness_8bit) { brightness_8bit = max_brightness_8bit; - ESP_LOGI(TAG, "Clamping brightness to MAX_BRIGHTNESS (%d)", MAX_BRIGHTNESS_8BIT); + ESP_LOGI(TAG, "Clamping brightness to MAX_BRIGHTNESS (%d)", + MAX_BRIGHTNESS_8BIT); } #endif - ESP_LOGI(TAG, "Setting brightness to %d%% (%d)", brightness_pct, brightness_8bit); + ESP_LOGI(TAG, "Setting brightness to %d%% (%d)", brightness_pct, + brightness_8bit); _matrix->setBrightness8(brightness_8bit); _matrix->clearScreen(); _brightness = brightness_pct; @@ -236,14 +244,14 @@ void display_shutdown(void) { _matrix = NULL; } -void display_draw(const uint8_t *pix, int width, int height, - int channels, int ixR, int ixG, int ixB) { +void display_draw(const uint8_t *pix, int width, int height, int channels, + int ixR, int ixG, int ixB) { int scale = 1; - #if CONFIG_BOARD_TRONBYT_S3_WIDE +#if CONFIG_BOARD_TRONBYT_S3_WIDE if (width == 64 && height == 32) { - scale = 2; // Scale up to 128x64 + scale = 2; // Scale up to 128x64 } - #endif +#endif for (unsigned int i = 0; i < height; i++) { for (unsigned int j = 0; j < width; j++) { @@ -272,11 +280,10 @@ void display_draw_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) { } } -void draw_error_indicator_pixel(void) { - display_draw_pixel(0, 0, 100, 0, 0); -} +void draw_error_indicator_pixel(void) { display_draw_pixel(0, 0, 100, 0, 0); } -void display_text(const char* text, int x, int y, uint8_t r, uint8_t g, uint8_t b, int scale) { +void display_text(const char *text, int x, int y, uint8_t r, uint8_t g, + uint8_t b, int scale) { if (_matrix == NULL || text == NULL) { return; } @@ -290,12 +297,12 @@ void display_text(const char* text, int x, int y, uint8_t r, uint8_t g, uint8_t // Check if character is in font range if (c < FONT5X7_FIRST_CHAR || c > FONT5X7_LAST_CHAR) { - c = ' '; // Replace unsupported characters with space + c = ' '; // Replace unsupported characters with space } // Get font data for this character int char_index = c - FONT5X7_FIRST_CHAR; - const uint8_t* char_data = font5x7[char_index]; + const uint8_t *char_data = font5x7[char_index]; // Draw each column of the character for (int col = 0; col < FONT5X7_CHAR_WIDTH; col++) { diff --git a/main/display.h b/main/display.h index 7329812..d84320a 100644 --- a/main/display.h +++ b/main/display.h @@ -14,13 +14,14 @@ int display_initialize(void); void display_set_brightness(uint8_t brightness_pct); void display_shutdown(void); -void display_draw(const uint8_t *pix, int width, int height, int channels, +void display_draw(const uint8_t* pix, int width, int height, int channels, int ixR, int ixG, int ixB); void display_clear(void); void display_draw_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b); void draw_error_indicator_pixel(void); -void display_text(const char* text, int x, int y, uint8_t r, uint8_t g, uint8_t b, int scale); +void display_text(const char* text, int x, int y, uint8_t r, uint8_t g, + uint8_t b, int scale); void display_flip(void); // int32_t isAnimating = 0; // Initialize with a valid value diff --git a/main/gfx.c b/main/gfx.c index 4c334a5..fcf441d 100644 --- a/main/gfx.c +++ b/main/gfx.c @@ -1,18 +1,18 @@ #include +#include #include #include #include +#include #include #include #include -#include -#include +#include "assets.h" #include "display.h" #include "esp_timer.h" -#include "assets.h" -#include "version.h" #include "nvs_settings.h" +#include "version.h" static const char *TAG = "gfx"; @@ -27,14 +27,17 @@ struct gfx_state { size_t len; int32_t dwell_secs; int counter; - int loaded_counter; // Counter that tracks which image has been loaded by gfx task - esp_websocket_client_handle_t ws_handle; // Websocket handle for sending notifications + int loaded_counter; // Counter that tracks which image has been loaded by gfx + // task + esp_websocket_client_handle_t + ws_handle; // Websocket handle for sending notifications }; static struct gfx_state *_state = NULL; static void gfx_loop(void *arg); -static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, int32_t *isAnimating); +static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, + int32_t isAnimating); static void send_websocket_notification(int counter); int gfx_initialize(const char *img_url) { @@ -53,7 +56,7 @@ int gfx_initialize(const char *img_url) { _state = calloc(1, sizeof(struct gfx_state)); _state->len = ASSET_BOOT_WEBP_LEN; - ESP_LOGI(TAG,"calloc buff"); + ESP_LOGI(TAG, "calloc buff"); _state->buf = calloc(1, ASSET_BOOT_WEBP_LEN); ESP_LOGI(TAG, "done calloc, copying"); if (_state->buf == NULL) { @@ -62,13 +65,13 @@ int gfx_initialize(const char *img_url) { } memcpy(_state->buf, ASSET_BOOT_WEBP, ASSET_BOOT_WEBP_LEN); ESP_LOGI(TAG, "done, copying"); - + _state->mutex = xSemaphoreCreateMutex(); if (_state->mutex == NULL) { ESP_LOGE(TAG, "Could not create gfx mutex"); return 1; } - ESP_LOGI(TAG,"done with gfx init"); + ESP_LOGI(TAG, "done with gfx init"); // Initialize the display if (display_initialize()) { @@ -77,89 +80,92 @@ int gfx_initialize(const char *img_url) { // Display version if not skipped if (!nvs_get_skip_display_version()) { - // Display version and image_url for 1 second - display_clear(); - char version_text[32]; - snprintf(version_text, sizeof(version_text), "v%s", FIRMWARE_VERSION); - - // Parse URL to extract host and last two path components - if (img_url != NULL && strlen(img_url) > 0) { - ESP_LOGI(TAG, "Full URL: %s", img_url); - char host_only[64] = {0}; - char last_two_components[32] = {0}; - - struct http_parser_url u; - http_parser_url_init(&u); - - if (http_parser_parse_url(img_url, strlen(img_url), 0, &u) == 0) { - if (u.field_set & (1 << UF_HOST)) { - size_t host_len = u.field_data[UF_HOST].len; - if (host_len >= sizeof(host_only)) host_len = sizeof(host_only) - 1; - memcpy(host_only, img_url + u.field_data[UF_HOST].off, host_len); - host_only[host_len] = '\0'; - } + // Display version and image_url for 1 second + display_clear(); + char version_text[32]; + snprintf(version_text, sizeof(version_text), "v%s", FIRMWARE_VERSION); + + // Parse URL to extract host and last two path components + if (img_url != NULL && strlen(img_url) > 0) { + ESP_LOGI(TAG, "Full URL: %s", img_url); + char host_only[64] = {0}; + char last_two_components[32] = {0}; + + struct http_parser_url u; + http_parser_url_init(&u); + + if (http_parser_parse_url(img_url, strlen(img_url), 0, &u) == 0) { + if (u.field_set & (1 << UF_HOST)) { + size_t host_len = u.field_data[UF_HOST].len; + if (host_len >= sizeof(host_only)) host_len = sizeof(host_only) - 1; + memcpy(host_only, img_url + u.field_data[UF_HOST].off, host_len); + host_only[host_len] = '\0'; + } - if (u.field_set & (1 << UF_PATH)) { - const char *path = img_url + u.field_data[UF_PATH].off; - size_t path_len = u.field_data[UF_PATH].len; - const char *last_slash = NULL; - const char *second_last_slash = NULL; + if (u.field_set & (1 << UF_PATH)) { + const char *path = img_url + u.field_data[UF_PATH].off; + size_t path_len = u.field_data[UF_PATH].len; + const char *last_slash = NULL; + const char *second_last_slash = NULL; + + for (size_t i = 0; i < path_len; i++) { + if (path[i] == '/') { + second_last_slash = last_slash; + last_slash = path + i; + } + } - for (size_t i = 0; i < path_len; i++) { - if (path[i] == '/') { - second_last_slash = last_slash; - last_slash = path + i; + if (second_last_slash != NULL) { + size_t len = (path + path_len) - second_last_slash; + if (len >= sizeof(last_two_components)) + len = sizeof(last_two_components) - 1; + memcpy(last_two_components, second_last_slash, len); + last_two_components[len] = '\0'; + } else { + size_t len = path_len; + if (len >= sizeof(last_two_components)) + len = sizeof(last_two_components) - 1; + memcpy(last_two_components, path, len); + last_two_components[len] = '\0'; } } + } - if (second_last_slash != NULL) { - size_t len = (path + path_len) - second_last_slash; - if (len >= sizeof(last_two_components)) len = sizeof(last_two_components) - 1; - memcpy(last_two_components, second_last_slash, len); - last_two_components[len] = '\0'; - } else { - size_t len = path_len; - if (len >= sizeof(last_two_components)) len = sizeof(last_two_components) - 1; - memcpy(last_two_components, path, len); - last_two_components[len] = '\0'; - } + // Display host at the top, left-aligned + if (strlen(host_only) > 0) { + ESP_LOGI(TAG, "Displaying host: '%s' at y=0", host_only); + display_text(host_only, 0, 0, 255, 255, 255, 1); } - } - // Display host at the top, left-aligned - if (strlen(host_only) > 0) { - ESP_LOGI(TAG, "Displaying host: '%s' at y=0", host_only); - display_text(host_only, 0, 0, 255, 255, 255, 1); - } + // Display last 11 chars of path components in the middle, left-aligned + if (strlen(last_two_components) > 0) { + const char *display_path = last_two_components; + size_t path_len = strlen(last_two_components); - // Display last 11 chars of path components in the middle, left-aligned - if (strlen(last_two_components) > 0) { - const char* display_path = last_two_components; - size_t path_len = strlen(last_two_components); + // If longer than 11 chars, show only the last 11 + if (path_len > 11) { + display_path = last_two_components + (path_len - 11); + } - // If longer than 11 chars, show only the last 11 - if (path_len > 11) { - display_path = last_two_components + (path_len - 11); + ESP_LOGI(TAG, "Displaying path components: '%s' at y=10", display_path); + display_text(display_path, 0, 10, 255, 255, 255, 1); + } else { + ESP_LOGW(TAG, "No path components found to display"); } - - ESP_LOGI(TAG, "Displaying path components: '%s' at y=10", display_path); - display_text(display_path, 0, 10, 255, 255, 255, 1); - } else { - ESP_LOGW(TAG, "No path components found to display"); } - } - // Display version at the bottom, centered - // Calculate x position to center text (approximately) - // Each character is 6 pixels wide (5 + 1 spacing) - int text_width = strlen(version_text) * 6; - int x = (64 - text_width) / 2; // Center on 64-pixel wide display - display_text(version_text, x, 24, 255, 255, 255, 1); // White text, centered at bottom + // Display version at the bottom, centered + // Calculate x position to center text (approximately) + // Each character is 6 pixels wide (5 + 1 spacing) + int text_width = strlen(version_text) * 6; + int x = (64 - text_width) / 2; // Center on 64-pixel wide display + display_text(version_text, x, 24, 255, 255, 255, + 1); // White text, centered at bottom - // Flip the buffer once to show all three text lines at the same time - display_flip(); + // Flip the buffer once to show all three text lines at the same time + display_flip(); - vTaskDelay(pdMS_TO_TICKS(2000)); + vTaskDelay(pdMS_TO_TICKS(2000)); } // Launch the graphics loop in separate task @@ -202,16 +208,15 @@ static void send_websocket_notification(int counter) { // Create JSON message: {"displaying": 42} char message[128]; - int len = snprintf(message, sizeof(message), - "{\"displaying\":%d}", - counter); + int len = snprintf(message, sizeof(message), "{\"displaying\":%d}", counter); if (len < 0 || len >= sizeof(message)) { ESP_LOGE(TAG, "Failed to format websocket notification message"); return; } - int sent = esp_websocket_client_send_text(_state->ws_handle, message, len, portMAX_DELAY); + int sent = esp_websocket_client_send_text(_state->ws_handle, message, len, + portMAX_DELAY); if (sent < 0) { ESP_LOGE(TAG, "Failed to send websocket notification"); } else { @@ -226,9 +231,13 @@ int gfx_update(void *webp, size_t len, int32_t dwell_secs) { } // If a new frame arrives before the previous one is consumed by the gfx task, - // free the old buffer here to prevent a memory leak (frame-dropping strategy). + // free the old buffer here to prevent a memory leak (frame-dropping + // strategy). if (_state->buf) { - ESP_LOGW(TAG, "Dropping queued image (counter %d) - new image arrived before it was displayed", _state->counter); + ESP_LOGW(TAG, + "Dropping queued image (counter %d) - new image arrived before it " + "was displayed", + _state->counter); free(_state->buf); _state->buf = NULL; } @@ -247,16 +256,20 @@ int gfx_update(void *webp, size_t len, int32_t dwell_secs) { } // Send "queued" notification immediately when image is queued - if (_state->ws_handle && esp_websocket_client_is_connected(_state->ws_handle)) { + if (_state->ws_handle && + esp_websocket_client_is_connected(_state->ws_handle)) { char message[64]; - int msg_len = snprintf(message, sizeof(message), "{\"queued\":%d}", counter); + int msg_len = + snprintf(message, sizeof(message), "{\"queued\":%d}", counter); if (msg_len > 0 && msg_len < sizeof(message)) { - esp_websocket_client_send_text(_state->ws_handle, message, msg_len, portMAX_DELAY); + esp_websocket_client_send_text(_state->ws_handle, message, msg_len, + portMAX_DELAY); ESP_LOGI(TAG, "Sent queued notification: %s", message); } } - return counter; // Return the counter value (>= 0) so caller can wait for it to be loaded + return counter; // Return the counter value (>= 0) so caller can wait for it + // to be loaded } int gfx_get_loaded_counter(void) { @@ -277,8 +290,8 @@ int gfx_get_loaded_counter(void) { return loaded; } -int gfx_display_asset(const char* asset_type) { - const uint8_t* asset_data = NULL; +int gfx_display_asset(const char *asset_type) { + const uint8_t *asset_data = NULL; size_t asset_len = 0; // Determine which asset to display @@ -315,17 +328,20 @@ int gfx_display_asset(const char* asset_type) { // Display the asset with no dwell time (static display) int result = gfx_update(asset_heap_copy, asset_len, 0); if (result < 0) { - // Only free if gfx_update failed to take ownership (returned negative error) + // Only free if gfx_update failed to take ownership (returned negative + // error) ESP_LOGE(TAG, "Failed to update graphics with %s asset", asset_type); free(asset_heap_copy); return 1; } - // gfx_update now owns the asset_heap_copy buffer (returns counter >= 0 on success) + // gfx_update now owns the asset_heap_copy buffer (returns counter >= 0 on + // success) return 0; } -void gfx_display_text(const char* text, int x, int y, uint8_t r, uint8_t g, uint8_t b, int scale) { +void gfx_display_text(const char *text, int x, int y, uint8_t r, uint8_t g, + uint8_t b, int scale) { display_text(text, x, y, r, g, b, scale); } @@ -336,7 +352,6 @@ static void gfx_loop(void *args) { size_t len = 0; int32_t dwell_secs = 0; int counter = -1; - int32_t *isAnimating = (int32_t *)args; ESP_LOGI(TAG, "Graphics loop running on core %d", xPortGetCoreID()); for (;;) { @@ -356,10 +371,10 @@ static void gfx_loop(void *args) { webp = _state->buf; len = _state->len; dwell_secs = _state->dwell_secs; - _state->buf = NULL; // gfx_loop now owns the buffer + _state->buf = NULL; // gfx_loop now owns the buffer counter = _state->counter; _state->loaded_counter = counter; // Signal that we've loaded this image - if (*isAnimating == -1) *isAnimating = 1; + if (isAnimating == -1) isAnimating = 1; // Send websocket notification that we're now displaying this image send_websocket_notification(counter); @@ -375,7 +390,7 @@ static void gfx_loop(void *args) { ESP_LOGE(TAG, "Could not draw webp"); draw_error_indicator_pixel(); vTaskDelay(pdMS_TO_TICKS(1 * 1000)); - *isAnimating = 0; + isAnimating = 0; // Free the invalid buffer to prevent re-drawing it free(webp); webp = NULL; @@ -388,39 +403,40 @@ static void gfx_loop(void *args) { } } -static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, int32_t *isAnimating) { +static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, + int32_t isAnimating) { // Set up WebP decoder // ESP_LOGI(TAG, "starting draw_webp"); int app_dwell_secs = dwell_secs; - int64_t dwell_us; - - if (app_dwell_secs <= 0 ) { - ESP_LOGW(TAG,"dwell_secs is 0. Looping one more time while we wait."); - dwell_us = 1 * 1000000; // default to 1s if it's zero so we loop again or show the image for 1 more second. + + if (app_dwell_secs <= 0) { + ESP_LOGW(TAG, "dwell_secs is 0. Looping one more time while we wait."); + dwell_us = 1 * 1000000; // default to 1s if it's zero so we loop again or + // show the image for 1 more second. } else { ESP_LOGI(TAG, "dwell_secs : %d", app_dwell_secs); dwell_us = app_dwell_secs * 1000000; } // ESP_LOGI(TAG, "frame count: %d", animation.frame_count); - + WebPData webpData; WebPDataInit(&webpData); webpData.bytes = buf; webpData.size = len; - + WebPAnimDecoderOptions decoderOptions; WebPAnimDecoderOptionsInit(&decoderOptions); decoderOptions.color_mode = MODE_RGBA; - + WebPAnimDecoder *decoder = WebPAnimDecoderNew(&webpData, &decoderOptions); if (decoder == NULL) { ESP_LOGE(TAG, "Could not create WebP decoder"); draw_error_indicator_pixel(); return 1; } - + WebPAnimInfo animation; if (!WebPAnimDecoderGetInfo(decoder, &animation)) { ESP_LOGE(TAG, "Could not get WebP animation"); @@ -431,20 +447,20 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, int32_t // ESP_LOGI(TAG, "frame count: %d", animation.frame_count); int64_t start_us = esp_timer_get_time(); - while (esp_timer_get_time() - start_us < dwell_us && *isAnimating != -1) { + while (esp_timer_get_time() - start_us < dwell_us && isAnimating != -1) { int lastTimestamp = 0; int delay = 0; TickType_t drawStartTick = xTaskGetTickCount(); // Draw each frame, and sleep for the delay - while (WebPAnimDecoderHasMoreFrames(decoder) && *isAnimating != -1) { + while (WebPAnimDecoderHasMoreFrames(decoder) && isAnimating != -1) { uint8_t *pix; int timestamp; WebPAnimDecoderGetNext(decoder, &pix, ×tamp); if (delay > 0) { xTaskDelayUntil(&drawStartTick, pdMS_TO_TICKS(delay)); } else { - vTaskDelay(10); // small delay for yield. + vTaskDelay(10); // small delay for yield. } drawStartTick = xTaskGetTickCount(); display_draw(pix, animation.canvas_width, animation.canvas_height, 4, 0, @@ -452,23 +468,25 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, int32_t delay = timestamp - lastTimestamp; lastTimestamp = timestamp; } - + // reset decoder to start from the beginning WebPAnimDecoderReset(decoder); - + if (delay > 0) { xTaskDelayUntil(&drawStartTick, pdMS_TO_TICKS(delay)); } else { - vTaskDelay(pdMS_TO_TICKS(100)); // Add a small fallback delay to yield CPU + vTaskDelay( + pdMS_TO_TICKS(100)); // Add a small fallback delay to yield CPU } - + // In case of a single frame, sleep for app_dwell_secs if (animation.frame_count == 1) { - // For static images, we need to check isAnimating periodically during the dwell time - // Break the dwell time into 100ms chunks so we can respond to immediate commands + // For static images, we need to check isAnimating periodically during the + // dwell time Break the dwell time into 100ms chunks so we can respond to + // immediate commands int64_t static_start_us = esp_timer_get_time(); while (esp_timer_get_time() - static_start_us < dwell_us) { - if (*isAnimating == -1) { + if (isAnimating == -1) { // Immediate command received, break out of dwell time break; } @@ -480,6 +498,10 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, int32_t WebPAnimDecoderDelete(decoder); ESP_LOGI(TAG, "Setting isAnimating to 0"); - *isAnimating = 0; + isAnimating = 0; return 0; } + +void gfx_stop(void) { isAnimating = 0; } + +void gfx_start(void) { isAnimating = 1; } diff --git a/main/main.c b/main/main.c index 1953258..1faf85c 100644 --- a/main/main.c +++ b/main/main.c @@ -38,7 +38,7 @@ #endif static const char* TAG = "main"; -volatile int32_t isAnimating = 1; +int32_t isAnimating = 1; static int32_t app_dwell_secs = CONFIG_REFRESH_INTERVAL_SECONDS; // main buffer downloaded webp data static uint8_t* webp; @@ -700,7 +700,7 @@ void app_main(void) { for (;;) { uint8_t* webp; size_t len; - static uint8_t brightness_pct = (CONFIG_HUB75_BRIGHTNESS * 100) / 255; + static uint8_t brightness_pct = DISPLAY_DEFAULT_BRIGHTNESS; int status_code = 0; ESP_LOGI(TAG, "Fetching from URL: %s", image_url); char* ota_url = NULL; diff --git a/main/ota.c b/main/ota.c index 314cfbc..a650d03 100644 --- a/main/ota.c +++ b/main/ota.c @@ -290,12 +290,6 @@ void run_ota(const char *url) { int progress_width = (cur_len * bar_w) / total_len; if (progress_width != last_progress_width) { - // Draw background (dim) - display_fill_rect(bar_x, bar_y, bar_w, bar_h, 10, 10, 10); - // Draw progress (green) - if (progress_width > 0) { - display_fill_rect(bar_x, bar_y, progress_width, bar_h, 0, 255, 0); - } display_flip(); last_progress_width = progress_width; } diff --git a/main/sntp.c b/main/sntp.c index 76f6af4..2d425e7 100644 --- a/main/sntp.c +++ b/main/sntp.c @@ -28,7 +28,7 @@ void app_sntp_config(void) { } else { // 2. Use DHCP (if enabled) ESP_LOGI(TAG, "Using SNTP from DHCP (fallback: pool.ntp.org)"); - esp_sntp_servermode_dhcp(1); + sntp_servermode_dhcp(1); esp_sntp_setservername(0, "pool.ntp.org"); } } From 696894b97f008eca4246ef6ee7d0a5dea16cefcd Mon Sep 17 00:00:00 2001 From: Tavis Date: Wed, 28 Jan 2026 16:21:13 -1000 Subject: [PATCH 03/11] add back gfx logging lines. --- main/gfx.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/main/gfx.c b/main/gfx.c index fcf441d..841ef05 100644 --- a/main/gfx.c +++ b/main/gfx.c @@ -182,6 +182,7 @@ int gfx_initialize(const char *img_url) { ESP_LOGE(TAG, "Could not create gfx task"); return 1; } + ESP_LOGI(TAG, "Gfx task created successfully"); return 0; } @@ -247,8 +248,9 @@ int gfx_update(void *webp, size_t len, int32_t dwell_secs) { _state->len = len; _state->dwell_secs = dwell_secs; _state->counter++; - int counter = _state->counter; + ESP_LOGI(TAG, "Queued image counter=%d size=%zu dwell=%d", counter, len, + dwell_secs); if (pdTRUE != xSemaphoreGive(_state->mutex)) { ESP_LOGE(TAG, "Could not give gfx mutex"); @@ -348,6 +350,7 @@ void gfx_display_text(const char *text, int x, int y, uint8_t r, uint8_t g, void gfx_shutdown(void) { display_shutdown(); } static void gfx_loop(void *args) { + ESP_LOGI(TAG, "gfx_loop ENTERED"); void *webp = NULL; size_t len = 0; int32_t dwell_secs = 0; @@ -366,8 +369,8 @@ static void gfx_loop(void *args) { // If there's new data, take ownership of buffer if (counter != _state->counter) { - ESP_LOGI(TAG, "Loaded new webp"); - if (webp) free(webp); + ESP_LOGI(TAG, "Displaying image counter=%d", _state->counter); + if (webp && !is_static_asset(webp)) free(webp); webp = _state->buf; len = _state->len; dwell_secs = _state->dwell_secs; From 8f64bafa37b88254ce86ca4686fbf6521eaec64e Mon Sep 17 00:00:00 2001 From: Tavis Date: Wed, 28 Jan 2026 18:48:49 -1000 Subject: [PATCH 04/11] manual ota bar --- main/Kconfig.projbuild | 6 ++++++ main/display.cpp | 11 +++++++++++ main/display.h | 2 ++ main/gfx.c | 19 +++++++++++++------ main/main.c | 7 +++---- main/ota.c | 6 ++++++ sdkconfig.defaults.tidbyt-gen2 | 1 + 7 files changed, 42 insertions(+), 10 deletions(-) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index cca6533..47c8f05 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -12,6 +12,12 @@ menu "Application Configuration" help Swap RGB colors. + config NO_INVERT_CLOCK_PHASE + bool "No Invert Clock Phase" + default n + help + Do not invert clock phase. Required for some display panels (e.g., Tidbyt Gen2). + config REFRESH_INTERVAL_SECONDS int "Default Refresh Interval (seconds)" default 10 diff --git a/main/display.cpp b/main/display.cpp index 36f60bd..68ad2ca 100644 --- a/main/display.cpp +++ b/main/display.cpp @@ -282,6 +282,17 @@ void display_draw_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) { void draw_error_indicator_pixel(void) { display_draw_pixel(0, 0, 100, 0, 0); } +void display_fill_rect(int x, int y, int w, int h, uint8_t r, uint8_t g, + uint8_t b) { + if (_matrix != NULL) { + for (int iy = y; iy < y + h; iy++) { + for (int ix = x; ix < x + w; ix++) { + _matrix->drawPixelRGB888(ix, iy, r, g, b); + } + } + } +} + void display_text(const char *text, int x, int y, uint8_t r, uint8_t g, uint8_t b, int scale) { if (_matrix == NULL || text == NULL) { diff --git a/main/display.h b/main/display.h index d84320a..1c4976f 100644 --- a/main/display.h +++ b/main/display.h @@ -19,6 +19,8 @@ void display_draw(const uint8_t* pix, int width, int height, int channels, void display_clear(void); void display_draw_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b); +void display_fill_rect(int x, int y, int w, int h, uint8_t r, uint8_t g, + uint8_t b); void draw_error_indicator_pixel(void); void display_text(const char* text, int x, int y, uint8_t r, uint8_t g, uint8_t b, int scale); diff --git a/main/gfx.c b/main/gfx.c index 841ef05..bc948d8 100644 --- a/main/gfx.c +++ b/main/gfx.c @@ -36,10 +36,18 @@ struct gfx_state { static struct gfx_state *_state = NULL; static void gfx_loop(void *arg); -static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, - int32_t isAnimating); +static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs); static void send_websocket_notification(int counter); +static bool is_static_asset(const void *ptr) { + if (ptr == ASSET_BOOT_WEBP) return true; + if (ptr == ASSET_CONFIG_WEBP) return true; + if (ptr == ASSET_404_WEBP) return true; + if (ptr == ASSET_OVERSIZE_WEBP) return true; + if (ptr == ASSET_NOCONNECT_WEBP) return true; + return false; +} + int gfx_initialize(const char *img_url) { // Only initialize once if (_state) { @@ -389,7 +397,7 @@ static void gfx_loop(void *args) { } if (webp && len > 0) { - if (draw_webp(webp, len, dwell_secs, isAnimating)) { + if (draw_webp(webp, len, dwell_secs)) { ESP_LOGE(TAG, "Could not draw webp"); draw_error_indicator_pixel(); vTaskDelay(pdMS_TO_TICKS(1 * 1000)); @@ -406,8 +414,7 @@ static void gfx_loop(void *args) { } } -static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, - int32_t isAnimating) { +static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs) { // Set up WebP decoder // ESP_LOGI(TAG, "starting draw_webp"); int app_dwell_secs = dwell_secs; @@ -500,7 +507,7 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, } WebPAnimDecoderDelete(decoder); - ESP_LOGI(TAG, "Setting isAnimating to 0"); + // ESP_LOGI(TAG, "Setting isAnimating to 0"); isAnimating = 0; return 0; } diff --git a/main/main.c b/main/main.c index 1faf85c..67164ec 100644 --- a/main/main.c +++ b/main/main.c @@ -760,8 +760,7 @@ void app_main(void) { // Wait for gfx task to load the newly queued image before fetching the // next one This ensures the image has begun displaying before we fetch // again - ESP_LOGI(TAG, "Waiting for gfx task to load new image (counter=%d)", - queued_counter); + // ESP_LOGI(TAG, "Waiting for gfx task to load new image (counter=%d)", queued_counter); int timeout = 0; while (gfx_get_loaded_counter() != queued_counter && timeout < 20000) { vTaskDelay(pdMS_TO_TICKS(10)); @@ -770,10 +769,10 @@ void app_main(void) { if (timeout >= 20000) { ESP_LOGE(TAG, "Timeout waiting for gfx task to load image"); } else { - ESP_LOGI(TAG, "Gfx task loaded image after %d ms", timeout); + ESP_LOGI(TAG, "Gfx task loaded image counter %d ms", queued_counter); } - ESP_LOGD(TAG, "Setting isAnimating to 1"); + // ESP_LOGD(TAG, "Setting isAnimating to 1"); isAnimating = 1; } wifi_health_check(); diff --git a/main/ota.c b/main/ota.c index a650d03..314cfbc 100644 --- a/main/ota.c +++ b/main/ota.c @@ -290,6 +290,12 @@ void run_ota(const char *url) { int progress_width = (cur_len * bar_w) / total_len; if (progress_width != last_progress_width) { + // Draw background (dim) + display_fill_rect(bar_x, bar_y, bar_w, bar_h, 10, 10, 10); + // Draw progress (green) + if (progress_width > 0) { + display_fill_rect(bar_x, bar_y, progress_width, bar_h, 0, 255, 0); + } display_flip(); last_progress_width = progress_width; } diff --git a/sdkconfig.defaults.tidbyt-gen2 b/sdkconfig.defaults.tidbyt-gen2 index 41e1d1b..e9e199b 100644 --- a/sdkconfig.defaults.tidbyt-gen2 +++ b/sdkconfig.defaults.tidbyt-gen2 @@ -1 +1,2 @@ CONFIG_BOARD_TIDBYT_GEN2=y +CONFIG_NO_INVERT_CLOCK_PHASE=y From 1a17c90851aea5a7ad20f88a3f23d828f52dff80 Mon Sep 17 00:00:00 2001 From: Tavis Date: Mon, 2 Feb 2026 11:44:10 -0800 Subject: [PATCH 05/11] ota screen fix --- main/gfx.c | 43 +++++++++++++++++++++++++++++++++---------- main/main.c | 5 ++++- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/main/gfx.c b/main/gfx.c index bc948d8..b94f397 100644 --- a/main/gfx.c +++ b/main/gfx.c @@ -31,12 +31,14 @@ struct gfx_state { // task esp_websocket_client_handle_t ws_handle; // Websocket handle for sending notifications + volatile bool paused; }; static struct gfx_state *_state = NULL; static void gfx_loop(void *arg); -static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs); +static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, + int32_t *isAnimating); static void send_websocket_notification(int counter); static bool is_static_asset(const void *ptr) { @@ -64,6 +66,7 @@ int gfx_initialize(const char *img_url) { _state = calloc(1, sizeof(struct gfx_state)); _state->len = ASSET_BOOT_WEBP_LEN; + _state->paused = false; ESP_LOGI(TAG, "calloc buff"); _state->buf = calloc(1, ASSET_BOOT_WEBP_LEN); ESP_LOGI(TAG, "done calloc, copying"); @@ -366,6 +369,11 @@ static void gfx_loop(void *args) { ESP_LOGI(TAG, "Graphics loop running on core %d", xPortGetCoreID()); for (;;) { + if (_state->paused) { + vTaskDelay(pdMS_TO_TICKS(100)); + continue; + } + if (pdTRUE != xSemaphoreTake(_state->mutex, portMAX_DELAY)) { ESP_LOGE(TAG, "Could not take gfx mutex"); if (webp) { @@ -385,7 +393,7 @@ static void gfx_loop(void *args) { _state->buf = NULL; // gfx_loop now owns the buffer counter = _state->counter; _state->loaded_counter = counter; // Signal that we've loaded this image - if (isAnimating == -1) isAnimating = 1; + if (isAnimating == -1 && !_state->paused) isAnimating = 1; // Send websocket notification that we're now displaying this image send_websocket_notification(counter); @@ -397,7 +405,7 @@ static void gfx_loop(void *args) { } if (webp && len > 0) { - if (draw_webp(webp, len, dwell_secs)) { + if (draw_webp(webp, len, dwell_secs, &isAnimating)) { ESP_LOGE(TAG, "Could not draw webp"); draw_error_indicator_pixel(); vTaskDelay(pdMS_TO_TICKS(1 * 1000)); @@ -414,7 +422,8 @@ static void gfx_loop(void *args) { } } -static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs) { +static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, + int32_t *isAnimating) { // Set up WebP decoder // ESP_LOGI(TAG, "starting draw_webp"); int app_dwell_secs = dwell_secs; @@ -457,13 +466,13 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs) { // ESP_LOGI(TAG, "frame count: %d", animation.frame_count); int64_t start_us = esp_timer_get_time(); - while (esp_timer_get_time() - start_us < dwell_us && isAnimating != -1) { + while (esp_timer_get_time() - start_us < dwell_us && *isAnimating != -1 && !_state->paused) { int lastTimestamp = 0; int delay = 0; TickType_t drawStartTick = xTaskGetTickCount(); // Draw each frame, and sleep for the delay - while (WebPAnimDecoderHasMoreFrames(decoder) && isAnimating != -1) { + while (WebPAnimDecoderHasMoreFrames(decoder) && *isAnimating != -1 && !_state->paused) { uint8_t *pix; int timestamp; WebPAnimDecoderGetNext(decoder, &pix, ×tamp); @@ -496,7 +505,7 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs) { // immediate commands int64_t static_start_us = esp_timer_get_time(); while (esp_timer_get_time() - static_start_us < dwell_us) { - if (isAnimating == -1) { + if (*isAnimating == -1 || _state->paused) { // Immediate command received, break out of dwell time break; } @@ -508,10 +517,24 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs) { WebPAnimDecoderDelete(decoder); // ESP_LOGI(TAG, "Setting isAnimating to 0"); - isAnimating = 0; + if (*isAnimating != -1) { + *isAnimating = 0; + } return 0; } -void gfx_stop(void) { isAnimating = 0; } +void gfx_stop(void) { + if (_state) { + isAnimating = -1; // Signal current draw to stop + _state->paused = true; + ESP_LOGI(TAG, "Graphics loop paused"); + } +} -void gfx_start(void) { isAnimating = 1; } +void gfx_start(void) { + if (_state) { + isAnimating = 0; + _state->paused = false; + ESP_LOGI(TAG, "Graphics loop resumed"); + } +} diff --git a/main/main.c b/main/main.c index 67164ec..d3d6da7 100644 --- a/main/main.c +++ b/main/main.c @@ -773,7 +773,10 @@ void app_main(void) { } // ESP_LOGD(TAG, "Setting isAnimating to 1"); - isAnimating = 1; + // Only start animation if OTA is not in progress + if (isAnimating != -1) { + isAnimating = 1; + } } wifi_health_check(); } From 4279484f414fbe6241f6bc732e241ded9f43fdea Mon Sep 17 00:00:00 2001 From: Chris Chambers Date: Fri, 30 Jan 2026 21:50:34 -0600 Subject: [PATCH 06/11] fix: Move matrixportal-s3 devices back to 8MHZ for stability (#126) --- sdkconfig.defaults.matrixportal-s3 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdkconfig.defaults.matrixportal-s3 b/sdkconfig.defaults.matrixportal-s3 index 84d323e..8d3c745 100644 --- a/sdkconfig.defaults.matrixportal-s3 +++ b/sdkconfig.defaults.matrixportal-s3 @@ -1,4 +1,5 @@ CONFIG_BOARD_MATRIXPORTAL_S3=y CONFIG_IDF_TARGET="esp32s3" -CONFIG_HUB75_CLK_32MHZ=y +CONFIG_HUB75_CLK_8MHZ=y +CONFIG_SWAP_COLORS=y CONFIG_HUB75_MIN_REFRESH_RATE=80 From 2b90fcc1ee807080156d3ef1ea8847672a359abe Mon Sep 17 00:00:00 2001 From: Tavis Date: Tue, 3 Feb 2026 23:16:27 -0800 Subject: [PATCH 07/11] minor cleanup --- main/display.cpp | 6 ------ main/display.h | 1 - main/main.c | 2 +- sdkconfig.defaults.matrixportal-s3 | 3 +-- sdkconfig.defaults.matrixportal-s3-waveshare | 2 +- sdkconfig.defaults.tronbyt-s3 | 2 +- sdkconfig.defaults.tronbyt-s3-wide | 2 +- 7 files changed, 5 insertions(+), 13 deletions(-) diff --git a/main/display.cpp b/main/display.cpp index 68ad2ca..f696206 100644 --- a/main/display.cpp +++ b/main/display.cpp @@ -193,12 +193,6 @@ int display_initialize(void) { _matrix = new MatrixPanel_I2S_DMA(mxconfig); - if (_matrix == - NULL) { // Should not happen with new if it throws std::bad_alloc - ESP_LOGE(TAG, "Failed to allocate MatrixPanel_I2S_DMA object"); - return 1; - } - if (!_matrix->begin()) { ESP_LOGE(TAG, "MatrixPanel_I2S_DMA begin() failed"); delete _matrix; diff --git a/main/display.h b/main/display.h index 1c4976f..7a1aad9 100644 --- a/main/display.h +++ b/main/display.h @@ -25,7 +25,6 @@ void draw_error_indicator_pixel(void); void display_text(const char* text, int x, int y, uint8_t r, uint8_t g, uint8_t b, int scale); void display_flip(void); -// int32_t isAnimating = 0; // Initialize with a valid value #ifdef __cplusplus } diff --git a/main/main.c b/main/main.c index d3d6da7..241e970 100644 --- a/main/main.c +++ b/main/main.c @@ -38,7 +38,7 @@ #endif static const char* TAG = "main"; -int32_t isAnimating = 1; +volatile int32_t isAnimating = 1; static int32_t app_dwell_secs = CONFIG_REFRESH_INTERVAL_SECONDS; // main buffer downloaded webp data static uint8_t* webp; diff --git a/sdkconfig.defaults.matrixportal-s3 b/sdkconfig.defaults.matrixportal-s3 index 8d3c745..3748314 100644 --- a/sdkconfig.defaults.matrixportal-s3 +++ b/sdkconfig.defaults.matrixportal-s3 @@ -1,5 +1,4 @@ CONFIG_BOARD_MATRIXPORTAL_S3=y CONFIG_IDF_TARGET="esp32s3" -CONFIG_HUB75_CLK_8MHZ=y CONFIG_SWAP_COLORS=y -CONFIG_HUB75_MIN_REFRESH_RATE=80 + diff --git a/sdkconfig.defaults.matrixportal-s3-waveshare b/sdkconfig.defaults.matrixportal-s3-waveshare index 7ece154..956dc6e 100644 --- a/sdkconfig.defaults.matrixportal-s3-waveshare +++ b/sdkconfig.defaults.matrixportal-s3-waveshare @@ -4,4 +4,4 @@ CONFIG_HTTP_BUFFER_SIZE_DEFAULT=100000 CONFIG_IDF_TARGET="esp32s3" CONFIG_SWAP_COLORS=y CONFIG_HUB75_CLK_32MHZ=y -CONFIG_HUB75_MIN_REFRESH_RATE=80 + diff --git a/sdkconfig.defaults.tronbyt-s3 b/sdkconfig.defaults.tronbyt-s3 index b4ec77e..9018de3 100644 --- a/sdkconfig.defaults.tronbyt-s3 +++ b/sdkconfig.defaults.tronbyt-s3 @@ -8,4 +8,4 @@ CONFIG_SPIRAM_SPEED_40M=y CONFIG_SPIRAM_USE_MALLOC=y CONFIG_HUB75_BIT_DEPTH_10=y CONFIG_HUB75_CLK_32MHZ=y -CONFIG_HUB75_MIN_REFRESH_RATE=80 + diff --git a/sdkconfig.defaults.tronbyt-s3-wide b/sdkconfig.defaults.tronbyt-s3-wide index d33a0f0..994ea03 100644 --- a/sdkconfig.defaults.tronbyt-s3-wide +++ b/sdkconfig.defaults.tronbyt-s3-wide @@ -7,6 +7,6 @@ CONFIG_SPIRAM_MODE_OCT=y CONFIG_SPIRAM_SPEED_40M=y CONFIG_SPIRAM_USE_MALLOC=y CONFIG_HUB75_CLK_32MHZ=y -CONFIG_HUB75_MIN_REFRESH_RATE=80 + CONFIG_HUB75_PANEL_WIDTH=128 CONFIG_HUB75_PANEL_HEIGHT=64 From a2ccc1ce22dac24acc3418dca6ba293fca161db8 Mon Sep 17 00:00:00 2001 From: Tavis Date: Tue, 3 Feb 2026 23:41:09 -0800 Subject: [PATCH 08/11] set isAnimating back volatile --- main/display.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/display.h b/main/display.h index 7a1aad9..1583ef6 100644 --- a/main/display.h +++ b/main/display.h @@ -6,7 +6,7 @@ #define DISPLAY_MAX_BRIGHTNESS 100 #define DISPLAY_MIN_BRIGHTNESS 0 #define DISPLAY_DEFAULT_BRIGHTNESS 30 -extern int32_t isAnimating; // Declare the variable +extern volatile int32_t isAnimating; // Declare the variable #ifdef __cplusplus extern "C" { #endif From 9ad4e4e3a682a3cf50b9fabd19023e58423d6afb Mon Sep 17 00:00:00 2001 From: Tavis Date: Wed, 4 Feb 2026 11:28:08 -1000 Subject: [PATCH 09/11] remove is_static_check Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- main/gfx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/gfx.c b/main/gfx.c index b94f397..098690e 100644 --- a/main/gfx.c +++ b/main/gfx.c @@ -386,7 +386,7 @@ static void gfx_loop(void *args) { // If there's new data, take ownership of buffer if (counter != _state->counter) { ESP_LOGI(TAG, "Displaying image counter=%d", _state->counter); - if (webp && !is_static_asset(webp)) free(webp); + if (webp) free(webp); webp = _state->buf; len = _state->len; dwell_secs = _state->dwell_secs; From cb52d3a1a268ba7bc5bd495c219d3bcec246f2ba Mon Sep 17 00:00:00 2001 From: Tavis Date: Wed, 4 Feb 2026 11:39:00 -1000 Subject: [PATCH 10/11] add back heap_caps --- main/gfx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/main/gfx.c b/main/gfx.c index 098690e..5bc63d1 100644 --- a/main/gfx.c +++ b/main/gfx.c @@ -1,3 +1,4 @@ +#include #include #include #include From 224033c1aa49584304fbe701b6e46b0a1a8f907e Mon Sep 17 00:00:00 2001 From: Tavis Date: Wed, 4 Feb 2026 14:23:33 -1000 Subject: [PATCH 11/11] add back no invert clock phase in sdkconfigs --- sdkconfig.defaults.matrixportal-s3 | 1 + sdkconfig.defaults.matrixportal-s3-waveshare | 2 +- sdkconfig.defaults.tidbyt-gen1 | 1 - sdkconfig.defaults.tronbyt-s3 | 5 +++-- sdkconfig.defaults.tronbyt-s3-wide | 5 +---- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/sdkconfig.defaults.matrixportal-s3 b/sdkconfig.defaults.matrixportal-s3 index 3748314..dc29ea7 100644 --- a/sdkconfig.defaults.matrixportal-s3 +++ b/sdkconfig.defaults.matrixportal-s3 @@ -1,4 +1,5 @@ CONFIG_BOARD_MATRIXPORTAL_S3=y CONFIG_IDF_TARGET="esp32s3" CONFIG_SWAP_COLORS=y +CONFIG_NO_INVERT_CLOCK_PHASE=y diff --git a/sdkconfig.defaults.matrixportal-s3-waveshare b/sdkconfig.defaults.matrixportal-s3-waveshare index 956dc6e..5be4503 100644 --- a/sdkconfig.defaults.matrixportal-s3-waveshare +++ b/sdkconfig.defaults.matrixportal-s3-waveshare @@ -3,5 +3,5 @@ CONFIG_HTTP_BUFFER_SIZE_MAX=220000 CONFIG_HTTP_BUFFER_SIZE_DEFAULT=100000 CONFIG_IDF_TARGET="esp32s3" CONFIG_SWAP_COLORS=y -CONFIG_HUB75_CLK_32MHZ=y +CONFIG_NO_INVERT_CLOCK_PHASE=y diff --git a/sdkconfig.defaults.tidbyt-gen1 b/sdkconfig.defaults.tidbyt-gen1 index 5020a19..773a99b 100644 --- a/sdkconfig.defaults.tidbyt-gen1 +++ b/sdkconfig.defaults.tidbyt-gen1 @@ -1,2 +1 @@ CONFIG_BOARD_TIDBYT_GEN1=y -CONFIG_HUB75_CLK_PHASE_INVERTED=y diff --git a/sdkconfig.defaults.tronbyt-s3 b/sdkconfig.defaults.tronbyt-s3 index 9018de3..0742d40 100644 --- a/sdkconfig.defaults.tronbyt-s3 +++ b/sdkconfig.defaults.tronbyt-s3 @@ -6,6 +6,7 @@ CONFIG_IDF_TARGET="esp32s3" CONFIG_SPIRAM_MODE_OCT=y CONFIG_SPIRAM_SPEED_40M=y CONFIG_SPIRAM_USE_MALLOC=y -CONFIG_HUB75_BIT_DEPTH_10=y -CONFIG_HUB75_CLK_32MHZ=y + + +CONFIG_NO_INVERT_CLOCK_PHASE=y diff --git a/sdkconfig.defaults.tronbyt-s3-wide b/sdkconfig.defaults.tronbyt-s3-wide index 994ea03..ca0d554 100644 --- a/sdkconfig.defaults.tronbyt-s3-wide +++ b/sdkconfig.defaults.tronbyt-s3-wide @@ -6,7 +6,4 @@ CONFIG_IDF_TARGET="esp32s3" CONFIG_SPIRAM_MODE_OCT=y CONFIG_SPIRAM_SPEED_40M=y CONFIG_SPIRAM_USE_MALLOC=y -CONFIG_HUB75_CLK_32MHZ=y - -CONFIG_HUB75_PANEL_WIDTH=128 -CONFIG_HUB75_PANEL_HEIGHT=64 +CONFIG_NO_INVERT_CLOCK_PHASE=y \ No newline at end of file