From 6f647e4f500f8cbfc3e206d6f0b6eb96333f726a Mon Sep 17 00:00:00 2001 From: Tom Magnier Date: Tue, 27 Jan 2026 11:26:19 +0100 Subject: [PATCH 1/3] HD108 : fix end frame transmission --- src/internal/methods/Hd108GenericMethod.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/internal/methods/Hd108GenericMethod.h b/src/internal/methods/Hd108GenericMethod.h index 6b9cbe5c..dc37d3e1 100644 --- a/src/internal/methods/Hd108GenericMethod.h +++ b/src/internal/methods/Hd108GenericMethod.h @@ -41,6 +41,7 @@ template class Hd108MethodBase Hd108MethodBase(uint8_t pinClock, uint8_t pinData, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : _sizeData(pixelCount * elementSize + settingsSize), + _sizeEndFrame((pixelCount + 7) / 8), // ceiling of pixel count divided by 8 (one clock per bit) _wire(pinClock, pinData) { _data = static_cast(malloc(_sizeData)); @@ -77,20 +78,19 @@ template class Hd108MethodBase } void Update(bool) - { - const uint8_t startFrame[16] = { 0x00 }; - const uint8_t endFrame[4] = { 0xff }; - + { _wire.beginTransaction(); - // start frame - _wire.transmitBytes(startFrame, sizeof(startFrame)); + //start frame : 128bits (16bytes) with DIN = 0 + for (int i = 0; i < 16; i++) + _wire.transmitByte(0x00); // data _wire.transmitBytes(_data, _sizeData); - // end frame - _wire.transmitBytes(endFrame, sizeof(endFrame)); + //end frame : at least one bit per LED ; no less than 8 bytes + for (size_t i = 0; i < max(_sizeEndFrame,(size_t)8); i++) + _wire.transmitByte(0x00); _wire.endTransaction(); } @@ -123,6 +123,7 @@ template class Hd108MethodBase private: const size_t _sizeData; // Size of '_data' buffer below + const size_t _sizeEndFrame; T_TWOWIRE _wire; uint8_t* _data; // Holds LED color values From b5b5d205fb2ec086679fc7faecbab96565e12102 Mon Sep 17 00:00:00 2001 From: Tom Magnier Date: Tue, 27 Jan 2026 16:36:54 +0100 Subject: [PATCH 2/3] PARLIO method for HD108 LEDs --- src/internal/NeoMethods.h | 5 +- src/internal/methods/Hd108Esp32ParlioMethod.h | 203 ++++++++++++++++++ 2 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 src/internal/methods/Hd108Esp32ParlioMethod.h diff --git a/src/internal/NeoMethods.h b/src/internal/NeoMethods.h index cc241306..54151a0a 100644 --- a/src/internal/NeoMethods.h +++ b/src/internal/NeoMethods.h @@ -63,9 +63,12 @@ License along with NeoPixel. If not, see #include "methods/DotStarEsp32DmaSpiMethod.h" #include "methods/NeoEsp32I2sXMethod.h" #include "methods/NeoEsp32LcdXMethod.h" +#endif - +#if defined(SOC_PARLIO_SUPPORTED) +#include "methods/Hd108Esp32ParlioMethod.h" #endif + #include "methods/NeoEspBitBangMethod.h" #elif defined(ARDUINO_ARCH_NRF52840) // must be before __arm__ diff --git a/src/internal/methods/Hd108Esp32ParlioMethod.h b/src/internal/methods/Hd108Esp32ParlioMethod.h new file mode 100644 index 00000000..57df9a40 --- /dev/null +++ b/src/internal/methods/Hd108Esp32ParlioMethod.h @@ -0,0 +1,203 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for HD108 using PARLIO peripheral on compatible ESP32 chips + +Written by Tom Magnier, adapted from DotStarEsp32DmaSpiMethod + +I invest time and resources providing this open source code, +please support me by donating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +// Available on ESP32-C5 / C6 / H2 / H4 / P4 +#if defined(ARDUINO_ARCH_ESP32) && defined(SOC_PARLIO_SUPPORTED) + +#pragma once + +/* General Reference documentation for the APIs used in this implementation +LOW LEVEL: (what is actually used) +DOCS: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/parlio/parlio_tx.html +*/ + +extern "C" +{ +#include +} + +template class Hd108Esp32ParlioMethodBase +{ +public: + typedef typename T_SPEED::SettingsObject SettingsObject; + + Hd108Esp32ParlioMethodBase(uint8_t clockPin, uint8_t dataPin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + _clockPin((gpio_num_t)clockPin), + _dataPin((gpio_num_t)dataPin), + _sizeStartFrame(16), // 128 bits (16 bytes) + _sizePixelData(pixelCount * elementSize + settingsSize), + _sizeEndFrame((pixelCount + 7) / 8 < 8 ? 8 : (pixelCount + 7) / 8) // one clock per bit, no less than 8 bytes + { + + _bufferSize = _sizeStartFrame + _sizePixelData + _sizeEndFrame; + + // must have a 4 byte aligned buffer for DMA + uint32_t alignment = _bufferSize % 4; + if (alignment) + { + _bufferSize += 4 - alignment; + } + + _data = static_cast(malloc(_bufferSize)); + _dmadata = static_cast(heap_caps_malloc(_bufferSize, MALLOC_CAP_DMA)); + + // data cleared later in Begin() + } + + ~Hd108Esp32ParlioMethodBase() + { + if (_parlio_tx_handle) + { + deinitParlio(); + } + free(_data); + heap_caps_free(_dmadata); + } + + bool IsReadyToUpdate() const + { + esp_err_t ret = parlio_tx_unit_wait_all_done(_parlio_tx_handle, 0); + + return (ret == ESP_OK); + } + + void Initialize() + { + memset(_data, 0x00, _sizeStartFrame); //Init start frame + memset(_data + _sizeStartFrame + _sizePixelData, 0x00, _bufferSize - (_sizeStartFrame + _sizePixelData)); //Init end frame + + initParlio(); + } + + void Update(bool) + { + while(!IsReadyToUpdate()) + portYIELD(); + + memcpy(_dmadata, _data, _bufferSize); + + parlio_transmit_config_t transmit_config = { + .idle_value = 0x00, // All data lines are low in idle state + }; + ESP_ERROR_CHECK(parlio_tx_unit_transmit(_parlio_tx_handle, _dmadata, _bufferSize * 8, &transmit_config)); + } + + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + + bool SwapBuffers() + { + return false; + } + + uint8_t* getData() const + { + return _data + _sizeStartFrame; + }; + + size_t getDataSize() const + { + return _sizePixelData; + }; + + void applySettings([[maybe_unused]] const SettingsObject& settings) + { + _speed.applySettings(settings); + if (_parlio_tx_handle) + { + deinitParlio(); + initParlio(); + } + } + +private: + void initParlio() + { + parlio_tx_unit_config_t config = { + .clk_src = PARLIO_CLK_SRC_DEFAULT, // Select the default clock source + .clk_in_gpio_num = (gpio_num_t)-1, // Don't use external clock source + .output_clk_freq_hz = _speed.Clock, // Output clock frequency + .data_width = 1, // Data width is 1 bit (single data pin) + .data_gpio_nums = { + _dataPin + }, + .clk_out_gpio_num = _clockPin, // Clock pin + .trans_queue_depth = 8, // Transaction queue depth (max count of pending transactions) + .max_transfer_size = _bufferSize, // Maximum transfer size (number of bytes per transaction) + .sample_edge = PARLIO_SAMPLE_EDGE_POS, // Sample data on the rising edge of the clock + .bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB, //Data endianness : MSB first + .flags = { + //.clk_gate_en = 1, // NOT SUPPORTED ON C6 - Disable clock when not transmitting + }, + }; + ESP_LOGD("PARLIO", "Initializing PARLIO with clock freq %d Hz, source %d", config.output_clk_freq_hz, config.clk_src); + + // Create TX unit instance + ESP_ERROR_CHECK(parlio_new_tx_unit(&config, &_parlio_tx_handle)); + // Enable TX unit + ESP_ERROR_CHECK(parlio_tx_unit_enable(_parlio_tx_handle)); + } + + void deinitParlio() + { + while (!IsReadyToUpdate()) + portYIELD(); + + ESP_ERROR_CHECK(parlio_tx_unit_disable(_parlio_tx_handle)); + ESP_ERROR_CHECK(parlio_del_tx_unit(_parlio_tx_handle)); + _parlio_tx_handle = NULL; + } + + const size_t _sizeStartFrame; + const size_t _sizePixelData; // Size of '_data' buffer below, minus (_sizeStartFrame + _sizeEndFrame) + const size_t _sizeEndFrame; + + size_t _bufferSize; + uint8_t* _data; // Holds start/end frames and LED color values + uint8_t* _dmadata; // Holds start/end frames and LED color values + + parlio_tx_unit_handle_t _parlio_tx_handle = NULL; + T_SPEED _speed; + gpio_num_t _clockPin; + gpio_num_t _dataPin; +}; + + +typedef Hd108Esp32ParlioMethodBase Hd108Esp32Parlio40MhzMethod; +typedef Hd108Esp32ParlioMethodBase Hd108Esp32Parlio20MhzMethod; +typedef Hd108Esp32ParlioMethodBase Hd108Esp32Parlio10MhzMethod; +typedef Hd108Esp32ParlioMethodBase Hd108Esp32Parlio5MhzMethod; +typedef Hd108Esp32ParlioMethodBase Hd108Esp32Parlio2MhzMethod; +typedef Hd108Esp32ParlioMethodBase Hd108Esp32Parlio1MhzMethod; +typedef Hd108Esp32ParlioMethodBase Hd108Esp32Parlio500KhzMethod; +typedef Hd108Esp32ParlioMethodBase Hd108Esp32ParlioHzMethod; + +typedef Hd108Esp32Parlio10MhzMethod Hd108Esp32ParlioMethod; + +#endif // defined(ARDUINO_ARCH_ESP32) && defined(SOC_PARLIO_SUPPORTED) \ No newline at end of file From a1471282834423d1cbce4a9f9520e83a9a892076 Mon Sep 17 00:00:00 2001 From: Tom Magnier Date: Fri, 13 Feb 2026 14:19:22 +0100 Subject: [PATCH 3/3] HD018 PARLIO : fix PARLIO config Without setting valid_gpio_num to -1, GPIO0 will be setup for the valid signal --- src/internal/methods/Hd108Esp32ParlioMethod.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/internal/methods/Hd108Esp32ParlioMethod.h b/src/internal/methods/Hd108Esp32ParlioMethod.h index 57df9a40..ef4e688d 100644 --- a/src/internal/methods/Hd108Esp32ParlioMethod.h +++ b/src/internal/methods/Hd108Esp32ParlioMethod.h @@ -148,6 +148,7 @@ template class Hd108Esp32ParlioMethodBase _dataPin }, .clk_out_gpio_num = _clockPin, // Clock pin + .valid_gpio_num = (gpio_num_t)-1, // Don't use valid signal .trans_queue_depth = 8, // Transaction queue depth (max count of pending transactions) .max_transfer_size = _bufferSize, // Maximum transfer size (number of bytes per transaction) .sample_edge = PARLIO_SAMPLE_EDGE_POS, // Sample data on the rising edge of the clock