From 07824112fc2a8c4a54113fbba728dc75f753ffe5 Mon Sep 17 00:00:00 2001 From: CQ793 <2609616396@qq.com> Date: Tue, 7 Jul 2026 17:32:37 +0800 Subject: [PATCH 1/2] feat/idf-sm5368-support --- components/hub75/Kconfig | 27 ++++++++++++ components/hub75/README.md | 17 ++++++++ components/hub75/include/hub75_types.h | 17 ++++++++ .../hub75/src/platforms/gdma/gdma_dma.cpp | 41 ++++++++++++++----- .../hub75/src/platforms/i2s/i2s_dma.cpp | 5 +++ .../hub75/src/platforms/parlio/parlio_dma.cpp | 5 +++ examples/common/board_config.h | 9 ++++ 7 files changed, 110 insertions(+), 11 deletions(-) diff --git a/components/hub75/Kconfig b/components/hub75/Kconfig index 665d726..fddbeed 100644 --- a/components/hub75/Kconfig +++ b/components/hub75/Kconfig @@ -213,6 +213,33 @@ menu "HUB75 RGB LED Matrix Driver" bool "DP3246" endchoice + choice HUB75_ROW_DECODER + prompt "Row Decoder" + default HUB75_ROW_DECODER_BINARY + help + Row selection / row addressing mode used by the panel. + + Most HUB75 panels use standard binary ABCDE row addressing. + Some panels use SM5368-style row logic where A/B/C are used as + row clock, BK, and row data instead of binary row address bits. + + Note: SM5368 row decoding is currently implemented only on the + ESP32-S3 GDMA backend. + + config HUB75_ROW_DECODER_BINARY + bool "Binary ABCDE (Most Panels)" + help + Standard HUB75 row addressing using A/B/C/D/E as a binary + row number. + + config HUB75_ROW_DECODER_SM5368 + bool "SM5368 Shift Row Decoder" + help + SM5368-style row selection where A=row clock, B=BK, and + C=row data. Use this for panels that require shift-style + row selection instead of binary row addressing. + endchoice + choice HUB75_BIT_DEPTH_CHOICE prompt "Bit Depth" # Backward compatibility: honor deprecated DEPTH_* option names diff --git a/components/hub75/README.md b/components/hub75/README.md index ec7195e..71e2d53 100644 --- a/components/hub75/README.md +++ b/components/hub75/README.md @@ -69,6 +69,7 @@ void app_main() { config.panel_height = 64; config.scan_wiring = Hub75ScanWiring::STANDARD_TWO_SCAN; // Most panels config.shift_driver = Hub75ShiftDriver::FM6126A; // Or GENERIC + config.row_decoder = Hub75RowDecoder::BINARY; // Default row addressing // Set GPIO pins config.pins.r1 = 25; @@ -114,6 +115,12 @@ void app_main() { - `bool begin()` - Initialize hardware and start continuous refresh loop - `void end()` - Stop refresh and cleanup resources +**Row Decoder Modes:** +- `Hub75RowDecoder::BINARY` - Standard HUB75 row addressing using A/B/C/D/E as a binary row number +- `Hub75RowDecoder::SM5368` - SM5368-style row selection using A=row clock, B=BK, C=row data + +`SM5368` is currently implemented on the ESP32-S3 GDMA backend only. + ### Drawing - `void draw_pixels(x, y, w, h, buffer, format, color_order, big_endian)` - Bulk pixel write from buffer (most efficient for images/sprites) @@ -196,6 +203,15 @@ config.panel_height = 64; config.shift_driver = Hub75ShiftDriver::FM6126A; // Try this if GENERIC doesn't work ``` +**SM5368 row decoder panel (ESP32-S3 GDMA):** +```cpp +Hub75Config config{}; +config.panel_width = 64; +config.panel_height = 32; +config.scan_wiring = Hub75ScanWiring::SCAN_1_8_32PX_FULL; +config.row_decoder = Hub75RowDecoder::SM5368; +``` + **Two panels side-by-side:** ```cpp config.layout_cols = 2; // Two panels horizontally @@ -260,6 +276,7 @@ Supports ESP32, ESP32-S2, ESP32-S3, and ESP32-P4 with platform-specific optimiza - **Black screen** → Try `shift_driver = Hub75ShiftDriver::FM6126A` (most modern panels) - **Wrong colors** → Check R1/G1/B1/R2/G2/B2 pin mapping - **Scrambled display** → Try `FOUR_SCAN_32PX_HIGH` scan wiring for 32px panels with 1/8 scan +- **Rows shift/jump incorrectly** → If panel uses SM5368-style row logic, try `row_decoder = Hub75RowDecoder::SM5368` on ESP32-S3 GDMA **For complete debugging guide** (ghosting, flickering, multi-panel issues, platform-specific problems, error messages), see **[Troubleshooting Guide](../../docs/TROUBLESHOOTING.md)**. diff --git a/components/hub75/include/hub75_types.h b/components/hub75/include/hub75_types.h index d8d74f4..fe85304 100644 --- a/components/hub75/include/hub75_types.h +++ b/components/hub75/include/hub75_types.h @@ -86,6 +86,19 @@ enum class Hub75ShiftDriver { DP3246 // DP3246 (special timing requirements) }; +/** + * @brief Row decoder / row addressing mode + * + * This controls how the panel row-select pins are interpreted by the backend. + * Most HUB75 panels use binary ABCDE row addressing. + * Some panels use SM5368-style shift row selection where A/B/C act as + * row clock, BK, and row data respectively. + */ +enum class Hub75RowDecoder { + BINARY, // Standard ABCDE binary row addressing (default) + SM5368, // A=row_clk, B=BK, C=row_data +}; + /** * @brief Multi-panel physical layout/wiring pattern * @@ -171,6 +184,9 @@ struct Hub75Config { // Shift driver chip type (determines initialization sequence) Hub75ShiftDriver shift_driver = Hub75ShiftDriver::GENERIC; + // Row decoder / row addressing mode + Hub75RowDecoder row_decoder = Hub75RowDecoder::BINARY; + // ======================================== // Multi-Panel Physical Layout // ======================================== @@ -231,6 +247,7 @@ struct Hub75Config { using ScanPattern [[deprecated("Use Hub75ScanWiring instead")]] = Hub75ScanWiring; using ShiftDriver [[deprecated("Use Hub75ShiftDriver instead")]] = Hub75ShiftDriver; +using RowDecoder [[deprecated("Use Hub75RowDecoder instead")]] = Hub75RowDecoder; using PanelLayout [[deprecated("Use Hub75PanelLayout instead")]] = Hub75PanelLayout; #ifdef __cplusplus diff --git a/components/hub75/src/platforms/gdma/gdma_dma.cpp b/components/hub75/src/platforms/gdma/gdma_dma.cpp index e0afed3..d9fcf92 100644 --- a/components/hub75/src/platforms/gdma/gdma_dma.cpp +++ b/components/hub75/src/platforms/gdma/gdma_dma.cpp @@ -65,6 +65,9 @@ enum HUB75WordBits : uint16_t { // Address field (not individual bits) constexpr int ADDR_SHIFT = 6; constexpr uint16_t ADDR_MASK = 0x1F; // 5-bit address (0-31) +constexpr uint16_t SM5368_CLK_MASK = 1u << (ADDR_SHIFT + 0); +constexpr uint16_t SM5368_BK_MASK = 1u << (ADDR_SHIFT + 1); +constexpr uint16_t SM5368_DATA_MASK = 1u << (ADDR_SHIFT + 2); // Combined RGB masks constexpr uint16_t RGB_UPPER_MASK = (1 << R1_BIT) | (1 << G1_BIT) | (1 << B1_BIT); @@ -217,6 +220,7 @@ bool GdmaDma::init() { layout_rows_, virtual_width_, virtual_height_); ESP_LOGI(TAG, "DMA config: %dx%d (width x rows), four-scan: %s", dma_width_, num_rows_, is_four_scan_wiring(scan_wiring_) ? "yes" : "no"); + ESP_LOGI(TAG, "Row decoder: %s", config_.row_decoder == Hub75RowDecoder::SM5368 ? "SM5368" : "binary"); ESP_LOGI(TAG, "LCD_CAM + GDMA initialized successfully"); ESP_LOGI(TAG, "Clock: %.2f MHz (requested %u MHz)", actual_clock_hz_ / 1000000.0f, @@ -863,7 +867,8 @@ void GdmaDma::initialize_buffer_internal(RowBitPlaneBuffer *buffers) { } for (int row = 0; row < num_rows_; row++) { - uint16_t row_addr = row & ADDR_MASK; + const uint16_t row_addr = row & ADDR_MASK; + const bool is_sm5368 = config_.row_decoder == Hub75RowDecoder::SM5368; for (int bit = 0; bit < bit_depth_; bit++) { uint16_t *buf = (uint16_t *) (buffers[row].data + (bit * dma_width_ * 2)); @@ -889,20 +894,34 @@ void GdmaDma::initialize_buffer_internal(RowBitPlaneBuffer *buffers) { // This prevents corruption when transitioning from row 31 (last) to row 0 (first). // Without wrap-around, the address would change from 31→0 during row 31's LAT settling, // causing ghosting on row 0. - uint16_t addr_for_buffer; - if (bit == 0) { - // LSB bit plane uses previous row (wraps row 0 to last row) - addr_for_buffer = ((row == 0 ? num_rows_ : row) - 1) & ADDR_MASK; - ESP_LOGD(TAG, "Row %d Bit 0: Using previous row address 0x%02X (current: 0x%02X)", row, addr_for_buffer, - row_addr); - } else { - // All other bit planes use current row - addr_for_buffer = row_addr; + uint16_t addr_for_buffer = row_addr; + if (!is_sm5368) { + if (bit == 0) { + // LSB bit plane uses previous row (wraps row 0 to last row) + addr_for_buffer = ((row == 0 ? num_rows_ : row) - 1) & ADDR_MASK; + ESP_LOGD(TAG, "Row %d Bit 0: Using previous row address 0x%02X (current: 0x%02X)", row, addr_for_buffer, + row_addr); + } else { + // All other bit planes use current row + addr_for_buffer = row_addr; + } } // Fill all pixels with control bits (RGB=0, row address, OE=HIGH) for (uint16_t x = 0; x < dma_width_; x++) { - buf[x] = (addr_for_buffer << ADDR_SHIFT) | (1 << OE_BIT); + buf[x] = (1 << OE_BIT); + if (!is_sm5368) { + buf[x] |= (addr_for_buffer << ADDR_SHIFT); + } + } + + // SM5368 panels use shift-style row selection instead of binary ABCDE address lines. + // Encode the row pulse at the tail of bit-plane 0 so the panel advances row selection + // during the LAT settling window while leaving other bit planes unchanged. + if (is_sm5368 && bit == 0 && dma_width_ >= 2) { + const uint16_t row_data = (row == 0) ? SM5368_DATA_MASK : 0; + buf[dma_width_ - 2] |= row_data | SM5368_BK_MASK; + buf[dma_width_ - 1] |= row_data | SM5368_BK_MASK | SM5368_CLK_MASK; } // Set LAT bit on last pixel diff --git a/components/hub75/src/platforms/i2s/i2s_dma.cpp b/components/hub75/src/platforms/i2s/i2s_dma.cpp index 7e8b411..6c0f05b 100644 --- a/components/hub75/src/platforms/i2s/i2s_dma.cpp +++ b/components/hub75/src/platforms/i2s/i2s_dma.cpp @@ -147,6 +147,11 @@ bool I2sDma::init() { ESP_LOGI(TAG, "Pin config: A=%d B=%d C=%d D=%d E=%d LAT=%d OE=%d CLK=%d", config_.pins.a, config_.pins.b, config_.pins.c, config_.pins.d, config_.pins.e, config_.pins.lat, config_.pins.oe, config_.pins.clk); + if (config_.row_decoder != Hub75RowDecoder::BINARY) { + ESP_LOGE(TAG, "Row decoder mode is not supported on I2S backend yet"); + return false; + } + // Get I2S device #if defined(CONFIG_IDF_TARGET_ESP32S2) i2s_dev_ = &I2S0; diff --git a/components/hub75/src/platforms/parlio/parlio_dma.cpp b/components/hub75/src/platforms/parlio/parlio_dma.cpp index 11afc53..f2a8e66 100644 --- a/components/hub75/src/platforms/parlio/parlio_dma.cpp +++ b/components/hub75/src/platforms/parlio/parlio_dma.cpp @@ -116,6 +116,11 @@ bool ParlioDma::init() { is_four_scan_wiring(scan_wiring_) ? "yes" : "no"); ESP_LOGI(TAG, "Bit depth: %d", bit_depth_); + if (config_.row_decoder != Hub75RowDecoder::BINARY) { + ESP_LOGE(TAG, "Row decoder mode is not supported on PARLIO backend yet"); + return false; + } + // Calculate BCM timings first calculate_bcm_timings(); diff --git a/examples/common/board_config.h b/examples/common/board_config.h index c6d0667..3d7a9c9 100644 --- a/examples/common/board_config.h +++ b/examples/common/board_config.h @@ -28,6 +28,8 @@ static inline Hub75Config getMenuConfigSettings() { config.scan_wiring = Hub75ScanWiring::SCAN_1_4_16PX_HIGH; #elif defined(CONFIG_HUB75_WIRING_SCAN_1_8_32PX) config.scan_wiring = Hub75ScanWiring::SCAN_1_8_32PX_HIGH; +#elif defined(CONFIG_HUB75_WIRING_SCAN_1_8_32PX_FULL) + config.scan_wiring = Hub75ScanWiring::SCAN_1_8_32PX_FULL; #elif defined(CONFIG_HUB75_WIRING_SCAN_1_8_40PX) config.scan_wiring = Hub75ScanWiring::SCAN_1_8_40PX_HIGH; #elif defined(CONFIG_HUB75_WIRING_SCAN_1_8_64PX) @@ -47,6 +49,13 @@ static inline Hub75Config getMenuConfigSettings() { config.shift_driver = Hub75ShiftDriver::DP3246; #endif + // Row decoder +#if defined(CONFIG_HUB75_ROW_DECODER_SM5368) + config.row_decoder = Hub75RowDecoder::SM5368; +#else + config.row_decoder = Hub75RowDecoder::BINARY; +#endif + // Pin configuration (board preset or custom) #if defined(CONFIG_HUB75_BOARD_ADAFRUIT_MATRIX_PORTAL_S3) // Adafruit Matrix Portal S3 From 1ca3551eed0475c5e4e2f54e1613c300b44e8bcf Mon Sep 17 00:00:00 2001 From: CQ793 <2609616396@qq.com> Date: Tue, 7 Jul 2026 18:18:26 +0800 Subject: [PATCH 2/2] Content: fixed format --- components/hub75/include/hub75_types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/hub75/include/hub75_types.h b/components/hub75/include/hub75_types.h index fe85304..338306b 100644 --- a/components/hub75/include/hub75_types.h +++ b/components/hub75/include/hub75_types.h @@ -95,8 +95,8 @@ enum class Hub75ShiftDriver { * row clock, BK, and row data respectively. */ enum class Hub75RowDecoder { - BINARY, // Standard ABCDE binary row addressing (default) - SM5368, // A=row_clk, B=BK, C=row_data + BINARY, // Standard ABCDE binary row addressing (default) + SM5368, // A=row_clk, B=BK, C=row_data }; /**