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 4807b23..f696206 100644 --- a/main/display.cpp +++ b/main/display.cpp @@ -1,238 +1,205 @@ #include "display.h" -#include +#include -#include "esp_heap_caps.h" -#include "esp_log.h" #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 +#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 -static Hub75Driver *_matrix; -static uint8_t _brightness = (CONFIG_HUB75_BRIGHTNESS * 100) / 255; -static const char *TAG = "display"; - -#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 = DISPLAY_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 + ESP_LOGI(TAG, "Initializing display with swap_colors=%s", + swap_colors ? "true" : "false"); - mxconfig.min_refresh_rate = CONFIG_HUB75_MIN_REFRESH_RATE; - mxconfig.latch_blanking = CONFIG_HUB75_LATCH_BLANKING; + // 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}; - // Clock Phase -#ifdef CONFIG_HUB75_CLK_PHASE_INVERTED - mxconfig.clk_phase_inverted = true; +#if CONFIG_NO_INVERT_CLOCK_PHASE + bool invert_clock_phase = false; #else - mxconfig.clk_phase_inverted = false; + bool invert_clock_phase = true; #endif - mxconfig.brightness = CONFIG_HUB75_BRIGHTNESS; + 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 Hub75Driver(mxconfig); - - if (_matrix == NULL) { - ESP_LOGE(TAG, "Failed to allocate Hub75Driver object"); - return 1; - } + _matrix = new MatrixPanel_I2S_DMA(mxconfig); 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(DISPLAY_DEFAULT_BRIGHTNESS); return 0; } @@ -258,69 +225,68 @@ void display_set_brightness(uint8_t brightness_pct) { ESP_LOGI(TAG, "Setting brightness to %d%% (%d)", brightness_pct, brightness_8bit); - _matrix->set_brightness(brightness_8bit); - _matrix->clear(); + _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; - } - } - - _matrix->draw_pixels(0, 0, 128, 64, (uint8_t *)_scaled_buffer, - Hub75PixelFormat::RGB888_32, Hub75ColorOrder::BGR); - _matrix->flip_buffer(); - return; + scale = 2; // Scale up to 128x64 } #endif - // Default path: bulk transfer for native resolution - _matrix->draw_pixels(0, 0, width, height, pix, Hub75PixelFormat::RGB888_32, - Hub75ColorOrder::BGR); - _matrix->flip_buffer(); + 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->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 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) { - _matrix->fill(x, y, w, h, r, g, b); - // Note: No flip here, caller must flip + for (int iy = y; iy < y + h; iy++) { + for (int ix = x; ix < x + w; ix++) { + _matrix->drawPixelRGB888(ix, iy, r, g, b); + } + } } } -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) { if (_matrix == NULL || text == NULL) { @@ -350,18 +316,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 +341,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..1583ef6 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 volatile int32_t isAnimating; // Declare the variable #ifdef __cplusplus extern "C" { #endif @@ -16,7 +14,9 @@ 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, @@ -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/gfx.c b/main/gfx.c index 1e22b8c..5bc63d1 100644 --- a/main/gfx.c +++ b/main/gfx.c @@ -63,13 +63,20 @@ 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) { @@ -187,6 +194,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; } @@ -225,7 +233,7 @@ static void send_websocket_notification(int counter) { 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); } } @@ -239,16 +247,11 @@ int gfx_update(void *webp, size_t len, int32_t dwell_secs) { // 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; } @@ -257,8 +260,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"); @@ -274,7 +278,7 @@ int gfx_update(void *webp, size_t len, int32_t dwell_secs) { 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_LOGI(TAG, "Sent queued notification: %s", message); } } @@ -324,24 +328,29 @@ 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) 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; } @@ -350,30 +359,14 @@ void gfx_display_text(const char *text, int x, int y, uint8_t r, uint8_t g, 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) { + ESP_LOGI(TAG, "gfx_loop ENTERED"); void *webp = NULL; 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 (;;) { @@ -384,7 +377,7 @@ static void gfx_loop(void *args) { 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 +386,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, "Displaying image counter=%d", _state->counter); + if (webp) free(webp); webp = _state->buf; len = _state->len; dwell_secs = _state->dwell_secs; _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 && !_state->paused) isAnimating = 1; // Send websocket notification that we're now displaying this image send_websocket_notification(counter); @@ -413,15 +406,13 @@ 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, &isAnimating)) { 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 - if (webp && !is_static_asset(webp)) { - free(webp); - } + free(webp); webp = NULL; len = 0; } @@ -445,7 +436,7 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, 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); @@ -476,30 +467,24 @@ 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 && !_state->paused) { 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 && !_state->paused) { 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; } @@ -508,7 +493,7 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, 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 @@ -532,7 +517,25 @@ static int draw_webp(const uint8_t *buf, size_t len, int32_t dwell_secs, } WebPAnimDecoderDelete(decoder); - ESP_LOGD(TAG, "Setting isAnimating to 0"); - *isAnimating = 0; + // ESP_LOGI(TAG, "Setting isAnimating to 0"); + if (*isAnimating != -1) { + *isAnimating = 0; + } return 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) { + if (_state) { + isAnimating = 0; + _state->paused = false; + ESP_LOGI(TAG, "Graphics loop resumed"); + } +} diff --git a/main/idf_component.yml b/main/idf_component.yml index 23ee91a..1fba22b 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -1,8 +1,7 @@ dependencies: - espressif/esp_websocket_client: "1.6.1" - esp-hub75: - git: https://github.com/esphome-libs/esp-hub75.git - version: "v0.3.2" - path: components/hub75 + espressif/esp_websocket_client: "1.6.0" + 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/main/main.c b/main/main.c index 1953258..241e970 100644 --- a/main/main.c +++ b/main/main.c @@ -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; @@ -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,11 +769,14 @@ 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"); - isAnimating = 1; + // ESP_LOGD(TAG, "Setting isAnimating to 1"); + // Only start animation if OTA is not in progress + if (isAnimating != -1) { + isAnimating = 1; + } } wifi_health_check(); } 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"); } } 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 diff --git a/sdkconfig.defaults.matrixportal-s3 b/sdkconfig.defaults.matrixportal-s3 index 8d3c745..dc29ea7 100644 --- a/sdkconfig.defaults.matrixportal-s3 +++ b/sdkconfig.defaults.matrixportal-s3 @@ -1,5 +1,5 @@ CONFIG_BOARD_MATRIXPORTAL_S3=y CONFIG_IDF_TARGET="esp32s3" -CONFIG_HUB75_CLK_8MHZ=y CONFIG_SWAP_COLORS=y -CONFIG_HUB75_MIN_REFRESH_RATE=80 +CONFIG_NO_INVERT_CLOCK_PHASE=y + diff --git a/sdkconfig.defaults.matrixportal-s3-waveshare b/sdkconfig.defaults.matrixportal-s3-waveshare index 7ece154..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_HUB75_MIN_REFRESH_RATE=80 +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.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 diff --git a/sdkconfig.defaults.tronbyt-s3 b/sdkconfig.defaults.tronbyt-s3 index b4ec77e..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_HUB75_MIN_REFRESH_RATE=80 + + + +CONFIG_NO_INVERT_CLOCK_PHASE=y diff --git a/sdkconfig.defaults.tronbyt-s3-wide b/sdkconfig.defaults.tronbyt-s3-wide index d33a0f0..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_MIN_REFRESH_RATE=80 -CONFIG_HUB75_PANEL_WIDTH=128 -CONFIG_HUB75_PANEL_HEIGHT=64 +CONFIG_NO_INVERT_CLOCK_PHASE=y \ No newline at end of file