From 446fe3a670bad108e8122b303faedbcf88875240 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 30 Jun 2025 14:28:49 -0400 Subject: [PATCH 01/14] Initial import of ESP32 RMT high-priority driver --- src/internal/NeoMethods.h | 1 + src/internal/methods/NeoEsp32RmtHI.S | 271 +++++++++ src/internal/methods/NeoEsp32RmtHIMethod.cpp | 312 ++++++++++ src/internal/methods/NeoEsp32RmtHIMethod.h | 562 +++++++++++++++++++ src/internal/methods/NeoEsp32RmtMethod.h | 5 + 5 files changed, 1151 insertions(+) create mode 100644 src/internal/methods/NeoEsp32RmtHI.S create mode 100644 src/internal/methods/NeoEsp32RmtHIMethod.cpp create mode 100644 src/internal/methods/NeoEsp32RmtHIMethod.h diff --git a/src/internal/NeoMethods.h b/src/internal/NeoMethods.h index cc241306..4bd2443a 100644 --- a/src/internal/NeoMethods.h +++ b/src/internal/NeoMethods.h @@ -60,6 +60,7 @@ License along with NeoPixel. If not, see #if !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32H2) #include "methods/NeoEsp32I2sMethod.h" #include "methods/NeoEsp32RmtMethod.h" +#include "methods/NeoEsp32RmtHIMethod.h" #include "methods/DotStarEsp32DmaSpiMethod.h" #include "methods/NeoEsp32I2sXMethod.h" #include "methods/NeoEsp32LcdXMethod.h" diff --git a/src/internal/methods/NeoEsp32RmtHI.S b/src/internal/methods/NeoEsp32RmtHI.S new file mode 100644 index 00000000..e06df7ac --- /dev/null +++ b/src/internal/methods/NeoEsp32RmtHI.S @@ -0,0 +1,271 @@ +/* RMT ISR shim + * Bridges from a high-level interrupt to the C++ code. + * + * This code is largely derived from Espressif's 'hli_vector.S' Bluetooth ISR. + * + */ + +/* RISC-V CPUs not supported (yet?) */ +#if defined(__XTENSA__) + +#include +#include +#include +#include "freertos/xtensa_context.h" +#include "sdkconfig.h" +#include "soc/soc.h" + +/* + Select interrupt + - Base ESP32: could be 4 or 5, depends on platform config + - S2: 5 + - S3: 5 +*/ + +#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 + +#if CONFIG_BTDM_CTRL_HLI +/* Bluetooth is using vector 4. Use NMI (!!) */ +#define RFI_X XCHAL_NMILEVEL +#define xt_highintx xt_nmi + +#else /* !CONFIG_BTDM_CTRL_HLI */ +/* Use level 4 */ +#define RFI_X 4 +#define xt_highintx xt_highint4 + +#endif /* CONFIG_BTDM_CTRL_HLI */ + +#else /* !CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */ + +/* Use level 5 */ +#define RFI_X 5 +#define xt_highintx xt_highint5 + +#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */ + +// Other registers +#define EPC_X (EPC + RFI_X) +#define EXCSAVE_X (EXCSAVE + RFI_X) + +// The sp mnemonic is used all over in ESP's assembly, though I'm not sure where it's expected to be defined? +#define sp a1 + +/* Interrupt stack size, for C code. */ +#define RMT_INTR_STACK_SIZE 512 + +/* Save area for the CPU state: + * - 64 words for the general purpose registers + * - 7 words for some of the special registers: + * - WINDOWBASE, WINDOWSTART — only WINDOWSTART is truly needed + * - SAR, LBEG, LEND, LCOUNT — since the C code might use these + * - EPC1 — since the C code might cause window overflow exceptions + * This is not laid out as standard exception frame structure + * for simplicity of the save/restore code. + */ +#define REG_FILE_SIZE (64 * 4) +#define SPECREG_OFFSET REG_FILE_SIZE +#define SPECREG_SIZE (7 * 4) +#define REG_SAVE_AREA_SIZE (SPECREG_OFFSET + SPECREG_SIZE) + + .data +_rmt_intr_stack: + .space RMT_INTR_STACK_SIZE +_rmt_save_ctx: + .space REG_SAVE_AREA_SIZE + + .section .iram1,"ax" + .global xt_highintx + .type xt_highintx,@function + .align 4 + +xt_highintx: + + movi a0, _rmt_save_ctx + /* save 4 lower registers */ + s32i a1, a0, 4 + s32i a2, a0, 8 + s32i a3, a0, 12 + rsr a2, EXCSAVE_X /* holds the value of a0 */ + s32i a2, a0, 0 + + /* Save special registers */ + addi a0, a0, SPECREG_OFFSET + rsr a2, WINDOWBASE + s32i a2, a0, 0 + rsr a2, WINDOWSTART + s32i a2, a0, 4 + rsr a2, SAR + s32i a2, a0, 8 + rsr a2, LBEG + s32i a2, a0, 12 + rsr a2, LEND + s32i a2, a0, 16 + rsr a2, LCOUNT + s32i a2, a0, 20 + rsr a2, EPC1 + s32i a2, a0, 24 + + /* disable exception mode, window overflow */ + movi a0, PS_INTLEVEL(RFI_X) | PS_EXCM + wsr a0, PS + rsync + + /* Save the remaining physical registers. + * 4 registers are already saved, which leaves 60 registers to save. + * (FIXME: consider the case when the CPU is configured with physical 32 registers) + * These 60 registers are saved in 5 iterations, 12 registers at a time. + */ + movi a1, 5 + movi a3, _rmt_save_ctx + 4 * 4 + + /* This is repeated 5 times, each time the window is shifted by 12 registers. + * We come here with a1 = downcounter, a3 = save pointer, a2 and a0 unused. + */ +1: + s32i a4, a3, 0 + s32i a5, a3, 4 + s32i a6, a3, 8 + s32i a7, a3, 12 + s32i a8, a3, 16 + s32i a9, a3, 20 + s32i a10, a3, 24 + s32i a11, a3, 28 + s32i a12, a3, 32 + s32i a13, a3, 36 + s32i a14, a3, 40 + s32i a15, a3, 44 + + /* We are about to rotate the window, so that a12-a15 will become the new a0-a3. + * Copy a0-a3 to a12-15 to still have access to these values. + * At the same time we can decrement the counter and adjust the save area pointer + */ + + /* a0 is constant (_rmt_save_ctx), no need to copy */ + addi a13, a1, -1 /* copy and decrement the downcounter */ + /* a2 is scratch so no need to copy */ + addi a15, a3, 48 /* copy and adjust the save area pointer */ + beqz a13, 2f /* have saved all registers ? */ + rotw 3 /* rotate the window and go back */ + j 1b + + /* the loop is complete */ +2: + rotw 4 /* this brings us back to the original window */ + /* a0 still points to _rmt_save_ctx */ + + /* Can clear WINDOWSTART now, all registers are saved */ + rsr a2, WINDOWBASE + /* WINDOWSTART = (1 << WINDOWBASE) */ + movi a3, 1 + ssl a2 + sll a3, a3 + wsr a3, WINDOWSTART + +_highint4_stack_switch: + movi a0, 0 + movi sp, _rmt_intr_stack + RMT_INTR_STACK_SIZE - 16 + s32e a0, sp, -12 /* For GDB: set null SP */ + s32e a0, sp, -16 /* For GDB: set null PC */ + movi a0, _highint4_stack_switch /* For GDB: cosmetics, for the frame where stack switch happened */ + + /* Set up PS for C, disable all interrupts except NMI and debug, and clear EXCM. */ + movi a6, PS_INTLEVEL(RFI_X) | PS_UM | PS_WOE + wsr a6, PS + rsync + + /* Call C handler */ + mov a6, sp + call4 NeoEsp32RmtMethodIsr + + l32e sp, sp, -12 /* switch back to the original stack */ + + /* Done with C handler; re-enable exception mode, disabling window overflow */ + movi a2, PS_INTLEVEL(RFI_X) | PS_EXCM /* TOCHECK */ + wsr a2, PS + rsync + + /* Restore the special registers. + * WINDOWSTART will be restored near the end. + */ + movi a0, _rmt_save_ctx + SPECREG_OFFSET + l32i a2, a0, 8 + wsr a2, SAR + l32i a2, a0, 12 + wsr a2, LBEG + l32i a2, a0, 16 + wsr a2, LEND + l32i a2, a0, 20 + wsr a2, LCOUNT + l32i a2, a0, 24 + wsr a2, EPC1 + + /* Restoring the physical registers. + * This is the reverse to the saving process above. + */ + + /* Rotate back to the final window, then start loading 12 registers at a time, + * in 5 iterations. + * Again, a1 is the downcounter and a3 is the save area pointer. + * After each rotation, a1 and a3 are copied from a13 and a15. + * To simplify the loop, we put the initial values into a13 and a15. + */ + rotw -4 + movi a15, _rmt_save_ctx + 64 * 4 /* point to the end of the save area */ + movi a13, 5 + +1: + /* Copy a1 and a3 from their previous location, + * at the same time decrementing and adjusting the save area pointer. + */ + addi a1, a13, -1 + addi a3, a15, -48 + + /* Load 12 registers */ + l32i a4, a3, 0 + l32i a5, a3, 4 + l32i a6, a3, 8 + l32i a7, a3, 12 + l32i a8, a3, 16 + l32i a9, a3, 20 + l32i a10, a3, 24 + l32i a11, a3, 28 /* ensure PS and EPC written */ + l32i a12, a3, 32 + l32i a13, a3, 36 + l32i a14, a3, 40 + l32i a15, a3, 44 + + /* Done with the loop? */ + beqz a1, 2f + /* If no, rotate the window and repeat */ + rotw -3 + j 1b + +2: + /* Done with the loop. Only 4 registers (a0-a3 in the original window) remain + * to be restored. Also need to restore WINDOWSTART, since all the general + * registers are now in place. + */ + movi a0, _rmt_save_ctx + + l32i a2, a0, SPECREG_OFFSET + 4 + wsr a2, WINDOWSTART + + l32i a1, a0, 4 + l32i a2, a0, 8 + l32i a3, a0, 12 + rsr a0, EXCSAVE_X /* holds the value of a0 before the interrupt handler */ + + /* Return from the interrupt, restoring PS from EPS_X */ + rfi RFI_X + + +/* The linker has no reason to link in this file; all symbols it exports are already defined + (weakly!) in the default int handler. Define a symbol here so we can use it to have the + linker inspect this anyway. */ + + .global ld_include_hli_vectors_rmt +ld_include_hli_vectors_rmt: + + +#endif \ No newline at end of file diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp new file mode 100644 index 00000000..838b6708 --- /dev/null +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -0,0 +1,312 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Esp32. + +A BIG thanks to Andreas Merkle for the investigation and implementation of +a workaround to the GCC bug that drops method attributes from template methods + +Written by Michael C. Miller. + +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 +. +-------------------------------------------------------------------------*/ + +#include + +#if defined(ARDUINO_ARCH_ESP32) && defined(__XTENSA__) + +#include "../NeoSettings.h" +#include "../NeoBusChannel.h" +#include "NeoEsp32RmtHIMethod.h" +#include "hal/cpu_hal.h" +#include "hal/rmt_ll.h" +#include "hal/interrupt_controller_hal.h" + +#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 +#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL4 +#else +#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL5 +#endif + +// RMT driver implementation +struct NeoEsp32RmtDriverState { + rmt_item32_t rmtBit0, rmtBit1; + uint32_t resetDuration; + + const byte* txDataStart; // data array + const byte* txDataEnd; // one past end + const byte* txDataCurrent; // current location + size_t rmtOffset; + + uint32_t txMaxCycles; + uint32_t lastTxInterruptCycleCount; + + size_t overruns; + struct overrun_info { + uint32_t last, current, index, isr; + } overrun_debug[32]; +}; + +static intr_handle_t isrHandle = nullptr; +static NeoEsp32RmtDriverState** driverState = nullptr; +constexpr size_t rmtBatchSize = RMT_MEM_ITEM_NUM / 2; + +// Ensure the assembly code is linked by requiring a unique symbol from it +extern "C" int ld_include_hli_vectors_rmt; +const void* RmtForceAssemblyLink = &ld_include_hli_vectors_rmt; + +// Fill the RMT buffer memory +static inline void IRAM_ATTR RmtFillBuffer(uint8_t channel, NeoEsp32RmtDriverState& state, size_t reserve) { + // We assume that (rmtToWrite % 8) == 0 + size_t rmtToWrite = rmtBatchSize - reserve; + rmt_item32_t* dest =(rmt_item32_t*) &RMTMEM.chan[channel].data32[state.rmtOffset + reserve]; // write directly in to RMT memory + const byte* psrc = state.txDataCurrent; + const byte* end = state.txDataEnd; + + if (psrc != end) { + // No lookups in the main loop + const rmt_item32_t rmtBit0 = state.rmtBit0; + const rmt_item32_t rmtBit1 = state.rmtBit1; + + while (rmtToWrite > 0) { + uint8_t data = *psrc; + for (uint8_t bit = 0; bit < 8; bit++) + { + *dest = (data & 0x80) ? rmtBit1 : rmtBit0; + dest++; + data <<= 1; + } + rmtToWrite -= 8; + psrc++; + + if (psrc == end) { + break; + } + } + + state.txDataCurrent = psrc; + } + + if (rmtToWrite > 0) { + // Add end event + *dest = rmt_item32_t {{{ .duration0 = 0, .level0 = state.rmtBit0.level1, .duration1 = 0, .level1 = 0 }}}; + } + + state.rmtOffset ^= rmtBatchSize; +} + +static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtDriverState& state) { + // Reset context state + state.rmtOffset = 0; + state.lastTxInterruptCycleCount = 0; + + // Fill the first part of the buffer with a reset event + // Use 8 words to stay aligned with the buffer fill + rmt_item32_t fill = {{{ .duration0 = 1, .level0 = state.rmtBit0.level1, .duration1 = 1, .level1 = state.rmtBit0.level1 }}}; + rmt_item32_t* dest = (rmt_item32_t*) &RMTMEM.chan[channel].data32[0]; + for (auto i = 0; i < 7; ++i) dest[i] = fill; + fill.duration1 = state.resetDuration - 17; + dest[7] = fill; + + // Fill the remaining buffer with real data + RmtFillBuffer(channel, state, 8); + RmtFillBuffer(channel, state, 0); + + // Start operation + rmt_ll_tx_reset_pointer(&RMT, channel); + rmt_ll_tx_start(&RMT, channel); +} + + +extern "C" void IRAM_ATTR NeoEsp32RmtMethodIsr(void *arg) { + uint32_t isr_ccount = cpu_hal_get_cycle_count(); + + // Tx threshold interrupt + uint32_t status = rmt_ll_get_tx_thres_interrupt_status(&RMT); + while (status) { + uint8_t channel = __builtin_ffs(status) - 1; + if (driverState[channel]) { + NeoEsp32RmtDriverState& state = *driverState[channel]; + uint32_t ccount = cpu_hal_get_cycle_count(); + if ((state.lastTxInterruptCycleCount == 0) || ((ccount - state.lastTxInterruptCycleCount) < state.txMaxCycles)) { + // Normal case + RmtFillBuffer(channel, state, 0); + state.lastTxInterruptCycleCount = cpu_hal_get_cycle_count(); + } else { + // Overrun + // Reset RMT + rmt_ll_tx_stop(&RMT, channel); + + if (state.overruns < 32) { + auto& ord = state.overrun_debug[state.overruns]; + ord.last = state.lastTxInterruptCycleCount; + ord.current = ccount; + ord.index = state.txDataCurrent - state.txDataStart; + ord.isr = isr_ccount; + } + // Reset the context + state.txDataCurrent = state.txDataStart; + state.overruns++; + RmtStartWrite(channel, state); + } + rmt_ll_clear_tx_thres_interrupt(&RMT, channel); + } + status = rmt_ll_get_tx_thres_interrupt_status(&RMT); + } +}; + + +esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t reset) { + esp_err_t err = ESP_OK; + if (!driverState) { + // First time init + driverState = reinterpret_cast(heap_caps_calloc(RMT_CHANNEL_MAX, sizeof(NeoEsp32RmtDriverState*), MALLOC_CAP_INTERNAL)); + if (!driverState) return ESP_ERR_NO_MEM; + + /* TODO: Ensure no RMT interrupts are pending ? */ + // Ensure our driver is linked + + + // Bind interrupt handler + // For "high level" interrupts, we have to allocate separately from binding the handler +#if !defined(CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5) || !defined(CONFIG_BTDM_CTRL_HLI) + err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, nullptr, nullptr, &isrHandle); + if (err != ESP_OK) { + Serial.printf("Couldn't assign RMT IRQ: %d\n", err); + heap_caps_free(driverState); + driverState = nullptr; + return err; + } +#else + // We must use the NMI as Levels 4 and 5 are already in use. Hope nobody minds. + // 14 is the magic number of the NMI destiatno. + intr_matrix_set(cpu_hal_get_core_id(), ETS_RMT_INTR_SOURCE, 14); +#endif + } + + if (driverState[channel] != nullptr) { + return ESP_ERR_INVALID_STATE; // already in use + } + + NeoEsp32RmtDriverState* state = reinterpret_cast(heap_caps_calloc(1, sizeof(NeoEsp32RmtDriverState), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); + if (state == nullptr) { + return ESP_ERR_NO_MEM; + } + + state->rmtBit0.val = rmtBit0; + state->rmtBit1.val = rmtBit1; + state->resetDuration = reset; + + // Overrun detection threshold + state->txMaxCycles = ((2 * rmtBatchSize) * NeoEsp32RmtSpeed::CyclesPerSample(rmtBit0)) - 1000; + + // Initialize hardware + rmt_ll_tx_stop(&RMT, channel); + rmt_ll_tx_enable_loop(&RMT, channel, false); + rmt_ll_tx_set_limit(&RMT, channel, rmtBatchSize); + + driverState[channel] = state; + + rmt_ll_enable_tx_thres_interrupt(&RMT, channel, true); + + Serial.printf("RMT %d install\n", channel); + return err; +} + +esp_err_t NeoEsp32RmtHiMethodDriver::Uninstall(rmt_channel_t channel) { + if (!driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; + + NeoEsp32RmtDriverState* state = driverState[channel]; + + esp_err_t result = WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS); + if (result == ESP_OK) { // TODO - can we allow this to fail??? + rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false); + driverState[channel] = nullptr; + heap_caps_free(state); + + // TODO: turn off the driver ISR and release global state if none are left + Serial.printf("RMT %d uninstall\n", channel); + }; + return result; +} + +esp_err_t NeoEsp32RmtHiMethodDriver::Write(rmt_channel_t channel, const uint8_t *src, size_t src_size) { + if (!driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; + + NeoEsp32RmtDriverState& state = *driverState[channel]; + esp_err_t result = WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS); + if (result == ESP_OK) { + if (state.overruns) { + Serial.printf("RMT %d handled overruns %d -- threshold %d\n", channel, state.overruns, state.txMaxCycles); + for(unsigned i = 0; i < state.overruns; ++i) { + if (i >= 32) break; + auto& ord = state.overrun_debug[i]; + Serial.printf("RMT %d: %u - %u %u -> %u %u @= %d\n", channel, ord.last, ord.isr, ord.current, ord.current - ord.last, ord.current - ord.isr, ord.index); + } + state.overruns = 0; + } + + state.txDataStart = src; + state.txDataCurrent = src; + state.txDataEnd = src + src_size; + state.overruns = 0; + RmtStartWrite(channel, state); + + Serial.printf("RMT %d write begin, %d/%d sent, status %08X\n", channel, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, rmt_ll_tx_get_channel_status(&RMT, channel)); + } + return result; +} + +esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickType_t wait_time) { + if (!driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; + + NeoEsp32RmtDriverState& state = *driverState[channel]; + // yield-wait until wait_time + unsigned loop_count = 0; + uint32_t start_time = cpu_hal_get_cycle_count(); + esp_err_t rv = ESP_OK; + uint32_t status; + while(1) { + status = rmt_ll_tx_get_channel_status(&RMT, channel); + if ((state.txDataCurrent == state.txDataEnd) && ((status & 0x07000000) == 0)) break; + if (wait_time == 0) { rv = ESP_ERR_TIMEOUT; break; }; + + ++loop_count; + + TickType_t sleep = std::min(wait_time, 5U); + vTaskDelay(sleep); + wait_time -= sleep; + }; + + if (loop_count > 0) { + uint32_t end_time = cpu_hal_get_cycle_count(); + + Serial.printf("RMT %d wait %d:%d: %u, %d/%d sent, status %08X, overruns %d\n", channel, loop_count, end_time - start_time, rv, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, status, state.overruns); + for(unsigned i = 0; i < state.overruns; ++i) { + if (i >= 32) break; + auto& ord = state.overrun_debug[i]; + Serial.printf("RMT %d: %u - %u %u -> %u %u @= %d\n", channel, ord.last, ord.isr, ord.current, ord.current - ord.last, ord.current - ord.isr, ord.index); + } + } + return rv; +} + + + +#endif \ No newline at end of file diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.h b/src/internal/methods/NeoEsp32RmtHIMethod.h new file mode 100644 index 00000000..e0b6b9b5 --- /dev/null +++ b/src/internal/methods/NeoEsp32RmtHIMethod.h @@ -0,0 +1,562 @@ +/*------------------------------------------------------------------------- +NeoPixel driver for ESP32 RMTs using High-priority Interrupt + +(NB. This cannot be mixed with the non-HI driver.) + +Written by Will M. Miles. + +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 +. +-------------------------------------------------------------------------*/ + +#pragma once + +#if defined(ARDUINO_ARCH_ESP32) && defined(__XTENSA__) + +// Use the NeoEspRmtSpeed types from the driver-based implementation +#include "NeoEsp32RmtMethod.h" + + +namespace NeoEsp32RmtHiMethodDriver { + // Install the driver for a specific channel, specifying timing properties + esp_err_t Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t resetDuration); + + // Remove the driver on a specific channel + esp_err_t Uninstall(rmt_channel_t channel); + + // Write a buffer of data to a specific channel. + // Buffer reference is held until write completes. + esp_err_t Write(rmt_channel_t channel, const uint8_t *src, size_t src_size); + + // Wait until transaction is complete. + esp_err_t WaitForTxDone(rmt_channel_t channel, TickType_t wait_time); +}; + +template class NeoEsp32RmtHIMethodBase +{ +public: + typedef NeoNoSettings SettingsObject; + + NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + _sizeData(pixelCount * elementSize + settingsSize), + _pin(pin) + { + construct(); + } + + NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize, NeoBusChannel channel) : + _sizeData(pixelCount* elementSize + settingsSize), + _pin(pin), + _channel(channel) + { + construct(); + } + + ~NeoEsp32RmtHIMethodBase() + { + // wait until the last send finishes before destructing everything + // arbitrary time out of 10 seconds + ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS)); + + ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Uninstall(_channel.RmtChannelNumber)); + + gpio_matrix_out(_pin, SIG_GPIO_OUT_IDX, false, false); + pinMode(_pin, INPUT); + + free(_dataEditing); + free(_dataSending); + } + + bool IsReadyToUpdate() const + { + return (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT_SILENT_TIMEOUT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 0))); + } + + void Initialize() + { + rmt_config_t config = {}; + + config.rmt_mode = RMT_MODE_TX; + config.channel = _channel.RmtChannelNumber; + config.gpio_num = static_cast(_pin); + config.mem_block_num = 1; + config.tx_config.loop_en = false; + + config.tx_config.idle_output_en = true; + config.tx_config.idle_level = T_SPEED::IdleLevel; + + config.tx_config.carrier_en = false; + config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; + + config.clk_div = T_SPEED::RmtClockDivider; + + ESP_ERROR_CHECK(rmt_config(&config)); // Uses ESP library + ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Install(_channel.RmtChannelNumber, T_SPEED::RmtBit0, T_SPEED::RmtBit1, T_SPEED::RmtDurationReset)); + } + + void Update(bool maintainBufferConsistency) + { + // wait for not actively sending data + // this will time out at 10 seconds, an arbitrarily long period of time + // and do nothing if this happens + if (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS))) + { + // now start the RMT transmit with the editing buffer before we swap + ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::Write(_channel.RmtChannelNumber, _dataEditing, _sizeData)); + + if (maintainBufferConsistency) + { + // copy editing to sending, + // this maintains the contract that "colors present before will + // be the same after", otherwise GetPixelColor will be inconsistent + memcpy(_dataSending, _dataEditing, _sizeData); + } + + // swap so the user can modify without affecting the async operation + std::swap(_dataSending, _dataEditing); + } + } + + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + + bool SwapBuffers() + { + std::swap(_dataSending, _dataEditing); + return true; + } + + uint8_t* getData() const + { + return _dataEditing; + }; + + size_t getDataSize() const + { + return _sizeData; + } + + void applySettings([[maybe_unused]] const SettingsObject& settings) + { + } + +private: + const size_t _sizeData; // Size of '_data*' buffers + const uint8_t _pin; // output pin number + const T_CHANNEL _channel; // holds instance for multi channel support + + // Holds data stream which include LED color values and other settings as needed + uint8_t* _dataEditing; // exposed for get and set + uint8_t* _dataSending; // used for async send using RMT + + + void construct() + { + _dataEditing = static_cast(malloc(_sizeData)); + // data cleared later in Begin() + + _dataSending = static_cast(malloc(_sizeData)); + // no need to initialize it, it gets overwritten on every send + } +}; + +// normal +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2811Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2812xMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2816Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2805Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINSk6812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1814Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1829Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1914Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINApa106Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTx1812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINGs1903Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN800KbpsMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN400KbpsMethod; +typedef NeoEsp32RmtNWs2805Method NeoEsp32RmtHINWs2814Method; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2811Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2812xMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2816Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2805Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Sk6812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1814Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1829Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1914Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Apa106Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tx1812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Gs1903Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0800KbpsMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0400KbpsMethod; +typedef NeoEsp32Rmt0Ws2805Method NeoEsp32RmtHI0Ws2814Method; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2811Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2812xMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2816Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2805Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Sk6812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1814Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1829Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1914Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Apa106Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tx1812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Gs1903Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1800KbpsMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1400KbpsMethod; +typedef NeoEsp32Rmt1Ws2805Method NeoEsp32RmtHI1Ws2814Method; + +#if !defined(CONFIG_IDF_TARGET_ESP32C3) + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2811Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2812xMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2816Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2805Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Sk6812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1814Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1829Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1914Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Apa106Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tx1812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Gs1903Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2800KbpsMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2400KbpsMethod; +typedef NeoEsp32Rmt2Ws2805Method NeoEsp32RmtHI2Ws2814Method; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2811Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2812xMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2816Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2805Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Sk6812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1814Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1829Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1914Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Apa106Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tx1812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Gs1903Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3800KbpsMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3400KbpsMethod; +typedef NeoEsp32Rmt3Ws2805Method NeoEsp32RmtHI3Ws2814Method; + +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3) + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2811Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2812xMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2816Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2805Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Sk6812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1814Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1829Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1914Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Apa106Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tx1812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Gs1903Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4800KbpsMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4400KbpsMethod; +typedef NeoEsp32Rmt4Ws2805Method NeoEsp32RmtHI4Ws2814Method; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2811Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2812xMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2816Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2805Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Sk6812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1814Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1829Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1914Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Apa106Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tx1812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Gs1903Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5800KbpsMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5400KbpsMethod; +typedef NeoEsp32Rmt5Ws2805Method NeoEsp32RmtHI5Ws2814Method; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2811Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2812xMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2816Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2805Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Sk6812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1814Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1829Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1914Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Apa106Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tx1812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Gs1903Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6800KbpsMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6400KbpsMethod; +typedef NeoEsp32Rmt6Ws2805Method NeoEsp32RmtHI6Ws2814Method; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2811Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2812xMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2816Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2805Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Sk6812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1814Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1829Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1914Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Apa106Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tx1812Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Gs1903Method; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7800KbpsMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7400KbpsMethod; +typedef NeoEsp32Rmt7Ws2805Method NeoEsp32RmtHI7Ws2814Method; + +#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3) +#endif // !defined(CONFIG_IDF_TARGET_ESP32C3) + +// inverted +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2811InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2812xInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2816InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2805InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINSk6812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1814InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1829InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1914InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINApa106InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTx1812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINGs1903InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN800KbpsInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN400KbpsInvertedMethod; +typedef NeoEsp32RmtNWs2805InvertedMethod NeoEsp32RmtHINWs2814InvertedMethod; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2811InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2812xInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2816InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2805InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Sk6812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1814InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1829InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1914InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Apa106InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tx1812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Gs1903InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0800KbpsInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0400KbpsInvertedMethod; +typedef NeoEsp32Rmt0Ws2805InvertedMethod NeoEsp32RmtHI0Ws2814InvertedMethod; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2811InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2812xInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2816InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2805InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Sk6812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1814InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1829InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1914InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Apa106InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tx1812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Gs1903InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1800KbpsInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1400KbpsInvertedMethod; +typedef NeoEsp32Rmt1Ws2805InvertedMethod NeoEsp32RmtHI1Ws2814InvertedMethod; + +#if !defined(CONFIG_IDF_TARGET_ESP32C3) + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2811InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2812xInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2816InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2805InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Sk6812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1814InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1829InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1914InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Apa106InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tx1812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Gs1903InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2800KbpsInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2400KbpsInvertedMethod; +typedef NeoEsp32Rmt2Ws2805InvertedMethod NeoEsp32RmtHI2Ws2814InvertedMethod; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2811InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2812xInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2805InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2816InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Sk6812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1814InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1829InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1914InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Apa106InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tx1812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Gs1903InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3800KbpsInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3400KbpsInvertedMethod; +typedef NeoEsp32Rmt3Ws2805InvertedMethod NeoEsp32RmtHI3Ws2814InvertedMethod; + +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3) + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2811InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2812xInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2816InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2805InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Sk6812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1814InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1829InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1914InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Apa106InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tx1812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Gs1903InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4800KbpsInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4400KbpsInvertedMethod; +typedef NeoEsp32Rmt4Ws2805InvertedMethod NeoEsp32RmtHI4Ws2814InvertedMethod; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2811InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2812xInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2816InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2805InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Sk6812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1814InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1829InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1914InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Apa106InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tx1812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Gs1903InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5800KbpsInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5400KbpsInvertedMethod; +typedef NeoEsp32Rmt5Ws2805InvertedMethod NeoEsp32RmtHI5Ws2814InvertedMethod; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2811InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2812xInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2816InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2805InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Sk6812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1814InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1829InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1914InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Apa106InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tx1812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Gs1903InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6800KbpsInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6400KbpsInvertedMethod; +typedef NeoEsp32Rmt6Ws2805InvertedMethod NeoEsp32RmtHI6Ws2814InvertedMethod; + +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2811InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2812xInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2816InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2805InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Sk6812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1814InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1829InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1914InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Apa106InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tx1812InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Gs1903InvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7800KbpsInvertedMethod; +typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7400KbpsInvertedMethod; +typedef NeoEsp32Rmt7Ws2805InvertedMethod NeoEsp32RmtHI7Ws2814InvertedMethod; + +#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3) +#endif // !defined(CONFIG_IDF_TARGET_ESP32C3) + + +#if defined(NEOPIXEL_ESP32_RMT_DEFAULT) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) + +// Normally I2s method is the default, defining NEOPIXEL_ESP32_RMT_DEFAULT +// will switch to use RMT as the default method +// The ESP32S2, ESP32S3, and ESP32C3 will always defualt to RMT + +#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) + +// RMT channel 1 method is the default method for ESP32S2, ESP32S3, and ESP32C3 +typedef NeoEsp32Rmt1Ws2812xMethod NeoWs2813Method; +typedef NeoEsp32Rmt1Ws2812xMethod NeoWs2812xMethod; +typedef NeoEsp32Rmt1800KbpsMethod NeoWs2812Method; +typedef NeoEsp32Rmt1Ws2812xMethod NeoWs2811Method; +typedef NeoEsp32Rmt1Ws2812xMethod NeoWs2816Method; +typedef NeoEsp32Rmt1Ws2805Method NeoWs2805Method; +typedef NeoEsp32Rmt1Ws2814Method NeoWs2814Method; +typedef NeoEsp32Rmt1Sk6812Method NeoSk6812Method; +typedef NeoEsp32Rmt1Tm1814Method NeoTm1814Method; +typedef NeoEsp32Rmt1Tm1829Method NeoTm1829Method; +typedef NeoEsp32Rmt1Tm1914Method NeoTm1914Method; +typedef NeoEsp32Rmt1Sk6812Method NeoLc8812Method; +typedef NeoEsp32Rmt1Apa106Method NeoApa106Method; +typedef NeoEsp32Rmt1Tx1812Method NeoTx1812Method; +typedef NeoEsp32Rmt1Gs1903Method NeoGs1903Method; + +typedef NeoEsp32Rmt1Ws2812xMethod Neo800KbpsMethod; +typedef NeoEsp32Rmt1400KbpsMethod Neo400KbpsMethod; + +typedef NeoEsp32Rmt1Ws2812xInvertedMethod NeoWs2813InvertedMethod; +typedef NeoEsp32Rmt1Ws2812xInvertedMethod NeoWs2812xInvertedMethod; +typedef NeoEsp32Rmt1Ws2812xInvertedMethod NeoWs2811InvertedMethod; +typedef NeoEsp32Rmt1800KbpsInvertedMethod NeoWs2812InvertedMethod; +typedef NeoEsp32Rmt1Ws2812xInvertedMethod NeoWs2816InvertedMethod; +typedef NeoEsp32Rmt1Ws2805InvertedMethod NeoWs2805InvertedMethod; +typedef NeoEsp32Rmt1Ws2814InvertedMethod NeoWs2814InvertedMethod; +typedef NeoEsp32Rmt1Sk6812InvertedMethod NeoSk6812InvertedMethod; +typedef NeoEsp32Rmt1Tm1814InvertedMethod NeoTm1814InvertedMethod; +typedef NeoEsp32Rmt1Tm1829InvertedMethod NeoTm1829InvertedMethod; +typedef NeoEsp32Rmt1Tm1914InvertedMethod NeoTm1914InvertedMethod; +typedef NeoEsp32Rmt1Sk6812InvertedMethod NeoLc8812InvertedMethod; +typedef NeoEsp32Rmt1Apa106InvertedMethod NeoApa106InvertedMethod; +typedef NeoEsp32Rmt1Tx1812InvertedMethod NeoTx1812InvertedMethod; +typedef NeoEsp32Rmt1Gs1903InvertedMethod NeoGs1903InvertedMethod; + +typedef NeoEsp32Rmt1Ws2812xInvertedMethod Neo800KbpsInvertedMethod; +typedef NeoEsp32Rmt1400KbpsInvertedMethod Neo400KbpsInvertedMethod; + +#else // defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) + +// RMT channel 6 method is the default method for Esp32 +typedef NeoEsp32Rmt6Ws2812xMethod NeoWs2813Method; +typedef NeoEsp32Rmt6Ws2812xMethod NeoWs2812xMethod; +typedef NeoEsp32Rmt6800KbpsMethod NeoWs2812Method; +typedef NeoEsp32Rmt6Ws2812xMethod NeoWs2811Method; +typedef NeoEsp32Rmt6Ws2812xMethod NeoWs2816Method; +typedef NeoEsp32Rmt6Ws2805Method NeoWs2805Method; +typedef NeoEsp32Rmt6Ws2814Method NeoWs2814Method; +typedef NeoEsp32Rmt6Sk6812Method NeoSk6812Method; +typedef NeoEsp32Rmt6Tm1814Method NeoTm1814Method; +typedef NeoEsp32Rmt6Tm1829Method NeoTm1829Method; +typedef NeoEsp32Rmt6Tm1914Method NeoTm1914Method; +typedef NeoEsp32Rmt6Sk6812Method NeoLc8812Method; +typedef NeoEsp32Rmt6Apa106Method NeoApa106Method; +typedef NeoEsp32Rmt6Tx1812Method NeoTx1812Method; +typedef NeoEsp32Rmt6Gs1903Method NeoGs1903Method; + +typedef NeoEsp32Rmt6Ws2812xMethod Neo800KbpsMethod; +typedef NeoEsp32Rmt6400KbpsMethod Neo400KbpsMethod; + +typedef NeoEsp32Rmt6Ws2812xInvertedMethod NeoWs2813InvertedMethod; +typedef NeoEsp32Rmt6Ws2812xInvertedMethod NeoWs2812xInvertedMethod; +typedef NeoEsp32Rmt6Ws2812xInvertedMethod NeoWs2811InvertedMethod; +typedef NeoEsp32Rmt6800KbpsInvertedMethod NeoWs2812InvertedMethod; +typedef NeoEsp32Rmt6Ws2812xInvertedMethod NeoWs2816InvertedMethod; +typedef NeoEsp32Rmt6Ws2805InvertedMethod NeoWs2805InvertedMethod; +typedef NeoEsp32Rmt6Ws2814InvertedMethod NeoWs2814InvertedMethod; +typedef NeoEsp32Rmt6Sk6812InvertedMethod NeoSk6812InvertedMethod; +typedef NeoEsp32Rmt6Tm1814InvertedMethod NeoTm1814InvertedMethod; +typedef NeoEsp32Rmt6Tm1829InvertedMethod NeoTm1829InvertedMethod; +typedef NeoEsp32Rmt6Tm1914InvertedMethod NeoTm1914InvertedMethod; +typedef NeoEsp32Rmt6Sk6812InvertedMethod NeoLc8812InvertedMethod; +typedef NeoEsp32Rmt6Apa106InvertedMethod NeoApa106InvertedMethod; +typedef NeoEsp32Rmt6Tx1812InvertedMethod NeoTx1812InvertedMethod; +typedef NeoEsp32Rmt6Gs1903InvertedMethod NeoGs1903InvertedMethod; + +typedef NeoEsp32Rmt6Ws2812xInvertedMethod Neo800KbpsInvertedMethod; +typedef NeoEsp32Rmt6400KbpsInvertedMethod Neo400KbpsInvertedMethod; + +#endif // defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) + +#endif // defined(NEOPIXEL_ESP32_RMT_DEFAULT) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) + +#endif diff --git a/src/internal/methods/NeoEsp32RmtMethod.h b/src/internal/methods/NeoEsp32RmtMethod.h index 208b1ac4..4ee65317 100644 --- a/src/internal/methods/NeoEsp32RmtMethod.h +++ b/src/internal/methods/NeoEsp32RmtMethod.h @@ -73,6 +73,7 @@ class NeoEsp32RmtSpeed const static uint32_t NsPerSecond = 1000000000L; const static uint32_t RmtTicksPerSecond = (RmtCpu / RmtClockDivider); const static uint32_t NsPerRmtTick = (NsPerSecond / RmtTicksPerSecond); // about 25 + constexpr static uint32_t CyclesPerRmtTick = (RmtClockDivider * F_CPU / RmtCpu); static void IRAM_ATTR _translate(const void* src, rmt_item32_t* dest, @@ -84,6 +85,10 @@ class NeoEsp32RmtSpeed const uint32_t rmtBit1, const uint16_t rmtDurationReset); +public: + inline constexpr static uint32_t CyclesPerSample(uint32_t val) { + return ((val & 0x7FFF) + ((val>>16) & 0x7FFF)) * CyclesPerRmtTick; + } }; class NeoEsp32RmtSpeedBase : public NeoEsp32RmtSpeed From d1f0f14aea0f8f6379d487ff237ff9c84deef210 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Tue, 1 Jul 2025 18:44:31 -0400 Subject: [PATCH 02/14] RmtHI: Attempt to add loop safety Alas, it's no longer efficient enough to operate. --- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 48 ++++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index 838b6708..332e0d6c 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -135,13 +135,13 @@ static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtDriverState& sta extern "C" void IRAM_ATTR NeoEsp32RmtMethodIsr(void *arg) { - uint32_t isr_ccount = cpu_hal_get_cycle_count(); - // Tx threshold interrupt + uint32_t status_processed = 0; uint32_t status = rmt_ll_get_tx_thres_interrupt_status(&RMT); while (status) { uint8_t channel = __builtin_ffs(status) - 1; - if (driverState[channel]) { + + if (driverState[channel] && ((status_processed & (1<= 32) break; - auto& ord = state.overrun_debug[i]; - Serial.printf("RMT %d: %u - %u %u -> %u %u @= %d\n", channel, ord.last, ord.isr, ord.current, ord.current - ord.last, ord.current - ord.isr, ord.index); - } - state.overruns = 0; + + if (state.overruns) { + Serial.printf("RMT %d handled overruns %d -- threshold %d\n", channel, state.overruns, state.txMaxCycles); + for(unsigned i = 0; i < state.overruns; ++i) { + if (i >= 32) break; + auto& ord = state.overrun_debug[i]; + Serial.printf("RMT %d: %u - %u %u -> %u %u @= %d\n", channel, ord.last, ord.isr, ord.current, ord.current - ord.last, ord.current - ord.isr, ord.index); } + state.overruns = 0; + } + if (result == ESP_OK) { state.txDataStart = src; state.txDataCurrent = src; state.txDataEnd = src + src_size; @@ -279,12 +285,11 @@ esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickTy NeoEsp32RmtDriverState& state = *driverState[channel]; // yield-wait until wait_time unsigned loop_count = 0; - uint32_t start_time = cpu_hal_get_cycle_count(); esp_err_t rv = ESP_OK; uint32_t status; while(1) { status = rmt_ll_tx_get_channel_status(&RMT, channel); - if ((state.txDataCurrent == state.txDataEnd) && ((status & 0x07000000) == 0)) break; + if ((state.txDataCurrent == state.txDataEnd) && ((status & 0x07000000) == 0)) break; /* Stopped state while not at end could mean we caught it restarting on an error */ if (wait_time == 0) { rv = ESP_ERR_TIMEOUT; break; }; ++loop_count; @@ -295,14 +300,7 @@ esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickTy }; if (loop_count > 0) { - uint32_t end_time = cpu_hal_get_cycle_count(); - - Serial.printf("RMT %d wait %d:%d: %u, %d/%d sent, status %08X, overruns %d\n", channel, loop_count, end_time - start_time, rv, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, status, state.overruns); - for(unsigned i = 0; i < state.overruns; ++i) { - if (i >= 32) break; - auto& ord = state.overrun_debug[i]; - Serial.printf("RMT %d: %u - %u %u -> %u %u @= %d\n", channel, ord.last, ord.isr, ord.current, ord.current - ord.last, ord.current - ord.isr, ord.index); - } + Serial.printf("RMT %d wait %d: %u, %d/%d sent, status %08X, overruns %d\n", channel, loop_count, rv, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, status, state.overruns); } return rv; } From bf9e70d5a2f2487ca68abe8b2b9d6414f52fcc48 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 2 Jul 2025 22:53:11 -0400 Subject: [PATCH 03/14] NeoESP32RmtHIMethod: Get to stable - Strip down ISR to the essentials - Hook the Bluetooth driver instead of NMI in case of conflict --- src/internal/methods/NeoEsp32RmtHI.S | 11 +- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 196 ++++++++----------- 2 files changed, 81 insertions(+), 126 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHI.S b/src/internal/methods/NeoEsp32RmtHI.S index e06df7ac..e3f1e6b1 100644 --- a/src/internal/methods/NeoEsp32RmtHI.S +++ b/src/internal/methods/NeoEsp32RmtHI.S @@ -6,7 +6,8 @@ */ /* RISC-V CPUs not supported (yet?) */ -#if defined(__XTENSA__) +/* If the Bluetooth driver has hooked the high-priority interrupt, we piggyback on it and don't need this. */ +#if defined(__XTENSA__) && !defined(CONFIG_BTDM_CTRL_HLI) #include #include @@ -24,18 +25,10 @@ #if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 -#if CONFIG_BTDM_CTRL_HLI -/* Bluetooth is using vector 4. Use NMI (!!) */ -#define RFI_X XCHAL_NMILEVEL -#define xt_highintx xt_nmi - -#else /* !CONFIG_BTDM_CTRL_HLI */ /* Use level 4 */ #define RFI_X 4 #define xt_highintx xt_highint4 -#endif /* CONFIG_BTDM_CTRL_HLI */ - #else /* !CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */ /* Use level 5 */ diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index 332e0d6c..28a83444 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -38,6 +38,11 @@ License along with NeoPixel. If not, see #include "hal/rmt_ll.h" #include "hal/interrupt_controller_hal.h" +#if defined(CONFIG_BTDM_CTRL_HLI) +// Espressif's bluetooth driver offers a helpful sharing layer +extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32_t intr_reg, uint32_t intr_mask); +#endif + #if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 #define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL4 #else @@ -45,50 +50,39 @@ License along with NeoPixel. If not, see #endif // RMT driver implementation -struct NeoEsp32RmtDriverState { - rmt_item32_t rmtBit0, rmtBit1; +struct NeoEsp32RmtHIChannelState { + uint32_t rmtBit0, rmtBit1; uint32_t resetDuration; const byte* txDataStart; // data array const byte* txDataEnd; // one past end const byte* txDataCurrent; // current location size_t rmtOffset; - - uint32_t txMaxCycles; - uint32_t lastTxInterruptCycleCount; - - size_t overruns; - struct overrun_info { - uint32_t last, current, index, isr; - } overrun_debug[32]; }; +// Global variables static intr_handle_t isrHandle = nullptr; -static NeoEsp32RmtDriverState** driverState = nullptr; +static NeoEsp32RmtHIChannelState** driverState = nullptr; constexpr size_t rmtBatchSize = RMT_MEM_ITEM_NUM / 2; -// Ensure the assembly code is linked by requiring a unique symbol from it -extern "C" int ld_include_hli_vectors_rmt; -const void* RmtForceAssemblyLink = &ld_include_hli_vectors_rmt; // Fill the RMT buffer memory -static inline void IRAM_ATTR RmtFillBuffer(uint8_t channel, NeoEsp32RmtDriverState& state, size_t reserve) { +// This is implemented using many arguments instead of passing the structure object to ensure we do only one lookup +// All the arguments are passed in registers, so they don't need to be looked up again +static void IRAM_ATTR RmtFillBuffer(uint8_t channel, const byte** src_ptr, const byte* end, uint32_t bit0, uint32_t bit1, size_t* offset_ptr, size_t reserve) { // We assume that (rmtToWrite % 8) == 0 size_t rmtToWrite = rmtBatchSize - reserve; - rmt_item32_t* dest =(rmt_item32_t*) &RMTMEM.chan[channel].data32[state.rmtOffset + reserve]; // write directly in to RMT memory - const byte* psrc = state.txDataCurrent; - const byte* end = state.txDataEnd; + rmt_item32_t* dest =(rmt_item32_t*) &RMTMEM.chan[channel].data32[*offset_ptr + reserve]; // write directly in to RMT memory + const byte* psrc = *src_ptr; - if (psrc != end) { - // No lookups in the main loop - const rmt_item32_t rmtBit0 = state.rmtBit0; - const rmt_item32_t rmtBit1 = state.rmtBit1; + *offset_ptr ^= rmtBatchSize; + if (psrc != end) { while (rmtToWrite > 0) { uint8_t data = *psrc; for (uint8_t bit = 0; bit < 8; bit++) { - *dest = (data & 0x80) ? rmtBit1 : rmtBit0; + dest->val = (data & 0x80) ? bit1 : bit0; dest++; data <<= 1; } @@ -100,75 +94,51 @@ static inline void IRAM_ATTR RmtFillBuffer(uint8_t channel, NeoEsp32RmtDriverSta } } - state.txDataCurrent = psrc; + *src_ptr = psrc; } if (rmtToWrite > 0) { // Add end event - *dest = rmt_item32_t {{{ .duration0 = 0, .level0 = state.rmtBit0.level1, .duration1 = 0, .level1 = 0 }}}; + rmt_item32_t bit0_val = {{.val = bit0 }}; + *dest = rmt_item32_t {{{ .duration0 = 0, .level0 = bit0_val.level1, .duration1 = 0, .level1 = 0 }}}; } - - state.rmtOffset ^= rmtBatchSize; } -static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtDriverState& state) { +static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtHIChannelState& state) { // Reset context state state.rmtOffset = 0; - state.lastTxInterruptCycleCount = 0; // Fill the first part of the buffer with a reset event - // Use 8 words to stay aligned with the buffer fill - rmt_item32_t fill = {{{ .duration0 = 1, .level0 = state.rmtBit0.level1, .duration1 = 1, .level1 = state.rmtBit0.level1 }}}; + // FUTURE: we could do timing analysis with the last interrupt on this channel + // Use 8 words to stay aligned with the buffer fill logic + uint32_t idle_lvl = (rmt_item32_t {{.val = state.rmtBit0}}).level1; + rmt_item32_t fill = {{{ .duration0 = 1, .level0 = idle_lvl, .duration1 = 1, .level1 = idle_lvl }}}; rmt_item32_t* dest = (rmt_item32_t*) &RMTMEM.chan[channel].data32[0]; for (auto i = 0; i < 7; ++i) dest[i] = fill; fill.duration1 = state.resetDuration - 17; dest[7] = fill; // Fill the remaining buffer with real data - RmtFillBuffer(channel, state, 8); - RmtFillBuffer(channel, state, 0); + RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 8); + RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 0); // Start operation + rmt_ll_clear_tx_thres_interrupt(&RMT, channel); rmt_ll_tx_reset_pointer(&RMT, channel); rmt_ll_tx_start(&RMT, channel); } - extern "C" void IRAM_ATTR NeoEsp32RmtMethodIsr(void *arg) { // Tx threshold interrupt - uint32_t status_processed = 0; uint32_t status = rmt_ll_get_tx_thres_interrupt_status(&RMT); while (status) { - uint8_t channel = __builtin_ffs(status) - 1; - - if (driverState[channel] && ((status_processed & (1<(heap_caps_calloc(RMT_CHANNEL_MAX, sizeof(NeoEsp32RmtDriverState*), MALLOC_CAP_INTERNAL)); + driverState = reinterpret_cast(heap_caps_calloc(RMT_CHANNEL_MAX, sizeof(NeoEsp32RmtHIChannelState*), MALLOC_CAP_INTERNAL)); if (!driverState) return ESP_ERR_NO_MEM; - - /* TODO: Ensure no RMT interrupts are pending ? */ - // Ensure our driver is linked - - + // Bind interrupt handler - // For "high level" interrupts, we have to allocate separately from binding the handler -#if !defined(CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5) || !defined(CONFIG_BTDM_CTRL_HLI) - err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, nullptr, nullptr, &isrHandle); +#if defined(CONFIG_BTDM_CTRL_HLI) + // Bluetooth driver has taken the empty high-priority interrupt. Fortunately, it allows us to + // hook up another handler. + err = hli_intr_register(NeoEsp32RmtMethodIsr, nullptr, (uintptr_t) &RMT.int_st, 0xFF000000); + // 25 is the magic number of the bluetooth ISR on ESP32 - see soc/soc.h. + intr_matrix_set(cpu_hal_get_core_id(), ETS_RMT_INTR_SOURCE, 25); + intr_cntrl_ll_enable_interrupts(1<<25); +#else + // Our custom ISR is bound by the linker; passing a pointer to an address that source file here guarantees that we link it in + err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, nullptr, &ld_include_hli_vectors_rmt, &isrHandle); +#endif + if (err != ESP_OK) { Serial.printf("Couldn't assign RMT IRQ: %d\n", err); heap_caps_free(driverState); driverState = nullptr; return err; } -#else - // We must use the NMI as Levels 4 and 5 are already in use. Hope nobody minds. - // 14 is the magic number of the NMI destiatno. - intr_matrix_set(cpu_hal_get_core_id(), ETS_RMT_INTR_SOURCE, 14); -#endif } if (driverState[channel] != nullptr) { return ESP_ERR_INVALID_STATE; // already in use } - NeoEsp32RmtDriverState* state = reinterpret_cast(heap_caps_calloc(1, sizeof(NeoEsp32RmtDriverState), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); + NeoEsp32RmtHIChannelState* state = reinterpret_cast(heap_caps_calloc(1, sizeof(NeoEsp32RmtHIChannelState), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); if (state == nullptr) { return ESP_ERR_NO_MEM; } - state->rmtBit0.val = rmtBit0; - state->rmtBit1.val = rmtBit1; + // Store timing information + state->rmtBit0 = rmtBit0; + state->rmtBit1 = rmtBit1; state->resetDuration = reset; - // Overrun detection threshold - state->txMaxCycles = ((2 * rmtBatchSize) * NeoEsp32RmtSpeed::CyclesPerSample(rmtBit0)) - 1000; - // Initialize hardware rmt_ll_tx_stop(&RMT, channel); + rmt_ll_tx_reset_pointer(&RMT, channel); + rmt_ll_tx_reset_loop(&RMT, channel); + rmt_ll_enable_tx_err_interrupt(&RMT, channel, false); + rmt_ll_enable_tx_end_interrupt(&RMT, channel, false); + rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false); + rmt_ll_clear_tx_err_interrupt(&RMT, channel); + rmt_ll_clear_tx_end_interrupt(&RMT, channel); + rmt_ll_clear_tx_thres_interrupt(&RMT, channel); + rmt_ll_tx_enable_loop(&RMT, channel, false); rmt_ll_tx_set_limit(&RMT, channel, rmtBatchSize); @@ -230,51 +206,42 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmt rmt_ll_enable_tx_thres_interrupt(&RMT, channel, true); - Serial.printf("RMT %d install\n", channel); + //Serial.printf("RMT %d install\n", channel); return err; } esp_err_t NeoEsp32RmtHiMethodDriver::Uninstall(rmt_channel_t channel) { if (!driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; - NeoEsp32RmtDriverState* state = driverState[channel]; + NeoEsp32RmtHIChannelState* state = driverState[channel]; - esp_err_t result = WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS); - if (result == ESP_OK) { // TODO - can we allow this to fail??? - rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false); - driverState[channel] = nullptr; - heap_caps_free(state); + WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS); - // TODO: turn off the driver ISR and release global state if none are left - Serial.printf("RMT %d uninstall\n", channel); - }; - return result; + // Done or not, we're out of here + rmt_ll_tx_stop(&RMT, channel); + rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false); + driverState[channel] = nullptr; + heap_caps_free(state); + + // TODO: turn off the driver ISR and release global state if none are left + //Serial.printf("RMT %d uninstall\n", channel); + + return ESP_OK; } esp_err_t NeoEsp32RmtHiMethodDriver::Write(rmt_channel_t channel, const uint8_t *src, size_t src_size) { if (!driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; - NeoEsp32RmtDriverState& state = *driverState[channel]; + NeoEsp32RmtHIChannelState& state = *driverState[channel]; esp_err_t result = WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS); - if (state.overruns) { - Serial.printf("RMT %d handled overruns %d -- threshold %d\n", channel, state.overruns, state.txMaxCycles); - for(unsigned i = 0; i < state.overruns; ++i) { - if (i >= 32) break; - auto& ord = state.overrun_debug[i]; - Serial.printf("RMT %d: %u - %u %u -> %u %u @= %d\n", channel, ord.last, ord.isr, ord.current, ord.current - ord.last, ord.current - ord.isr, ord.index); - } - state.overruns = 0; - } - if (result == ESP_OK) { state.txDataStart = src; state.txDataCurrent = src; - state.txDataEnd = src + src_size; - state.overruns = 0; + state.txDataEnd = src + src_size; RmtStartWrite(channel, state); - Serial.printf("RMT %d write begin, %d/%d sent, status %08X\n", channel, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, rmt_ll_tx_get_channel_status(&RMT, channel)); + //Serial.printf("RMT %d write begin, %d/%d sent, status %08X\n", channel, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, rmt_ll_tx_get_channel_status(&RMT, channel)); } return result; } @@ -282,9 +249,8 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Write(rmt_channel_t channel, const uint8_t esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickType_t wait_time) { if (!driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; - NeoEsp32RmtDriverState& state = *driverState[channel]; + NeoEsp32RmtHIChannelState& state = *driverState[channel]; // yield-wait until wait_time - unsigned loop_count = 0; esp_err_t rv = ESP_OK; uint32_t status; while(1) { @@ -292,16 +258,12 @@ esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickTy if ((state.txDataCurrent == state.txDataEnd) && ((status & 0x07000000) == 0)) break; /* Stopped state while not at end could mean we caught it restarting on an error */ if (wait_time == 0) { rv = ESP_ERR_TIMEOUT; break; }; - ++loop_count; - TickType_t sleep = std::min(wait_time, 5U); vTaskDelay(sleep); wait_time -= sleep; }; - if (loop_count > 0) { - Serial.printf("RMT %d wait %d: %u, %d/%d sent, status %08X, overruns %d\n", channel, loop_count, rv, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, status, state.overruns); - } + //Serial.printf("RMT %d wait %d: %d/%d sent, status %08X\n", channel, rv, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, status); return rv; } From 116ae315b03eb3d51da986d98d6812336c52881e Mon Sep 17 00:00:00 2001 From: Will Miles Date: Thu, 3 Jul 2025 19:19:04 -0400 Subject: [PATCH 04/14] RmtHI: Fix building on more ESP platforms Fix S2, S3, and unsupported (ESP8266) builds. --- src/internal/methods/NeoEsp32RmtHI.S | 11 +++++++++-- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHI.S b/src/internal/methods/NeoEsp32RmtHI.S index e3f1e6b1..b3f359d9 100644 --- a/src/internal/methods/NeoEsp32RmtHI.S +++ b/src/internal/methods/NeoEsp32RmtHI.S @@ -7,7 +7,7 @@ /* RISC-V CPUs not supported (yet?) */ /* If the Bluetooth driver has hooked the high-priority interrupt, we piggyback on it and don't need this. */ -#if defined(__XTENSA__) && !defined(CONFIG_BTDM_CTRL_HLI) +#if defined(__XTENSA__) && defined(ESP32) #include #include @@ -16,6 +16,8 @@ #include "sdkconfig.h" #include "soc/soc.h" +#ifndef CONFIG_BTDM_CTRL_HLI + /* Select interrupt - Base ESP32: could be 4 or 5, depends on platform config @@ -90,12 +92,14 @@ xt_highintx: s32i a2, a0, 4 rsr a2, SAR s32i a2, a0, 8 + #if XCHAL_HAVE_LOOPS rsr a2, LBEG s32i a2, a0, 12 rsr a2, LEND s32i a2, a0, 16 rsr a2, LCOUNT s32i a2, a0, 20 + #endif rsr a2, EPC1 s32i a2, a0, 24 @@ -185,12 +189,14 @@ _highint4_stack_switch: l32i a2, a0, 8 wsr a2, SAR l32i a2, a0, 12 + #if XCHAL_HAVE_LOOPS wsr a2, LBEG l32i a2, a0, 16 wsr a2, LEND l32i a2, a0, 20 wsr a2, LCOUNT l32i a2, a0, 24 + #endif wsr a2, EPC1 /* Restoring the physical registers. @@ -261,4 +267,5 @@ _highint4_stack_switch: ld_include_hli_vectors_rmt: -#endif \ No newline at end of file +#endif // CONFIG_BTDM_CTRL_HLI +#endif // XTensa \ No newline at end of file diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index 28a83444..27ab13c1 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -37,10 +37,19 @@ License along with NeoPixel. If not, see #include "hal/cpu_hal.h" #include "hal/rmt_ll.h" #include "hal/interrupt_controller_hal.h" +#include "soc/soc.h" + #if defined(CONFIG_BTDM_CTRL_HLI) // Espressif's bluetooth driver offers a helpful sharing layer extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32_t intr_reg, uint32_t intr_mask); +#else /* !CONFIG_BTDM_CTRL_HLI*/ + +// Link our high-priority ISR handler +extern "C" int ld_include_hli_vectors_rmt[0]; // an object with an address, but no space + +#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) +#include "soc/periph_defs.h" #endif #if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 @@ -49,6 +58,9 @@ extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32 #define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL5 #endif +#endif /* CONFIG_BTDM_CTRL_HLI */ + + // RMT driver implementation struct NeoEsp32RmtHIChannelState { uint32_t rmtBit0, rmtBit1; From af0e9733661c5b8fb66aedbc4ea826e2542ae2b3 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 7 Jul 2025 22:24:41 -0400 Subject: [PATCH 05/14] RmtHI: Get working on IDF v3 --- src/internal/methods/NeoEsp32RmtHI.S | 8 +- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 193 +++++++++++++++++-- 2 files changed, 183 insertions(+), 18 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHI.S b/src/internal/methods/NeoEsp32RmtHI.S index b3f359d9..4f65bfe5 100644 --- a/src/internal/methods/NeoEsp32RmtHI.S +++ b/src/internal/methods/NeoEsp32RmtHI.S @@ -104,7 +104,7 @@ xt_highintx: s32i a2, a0, 24 /* disable exception mode, window overflow */ - movi a0, PS_INTLEVEL(RFI_X) | PS_EXCM + movi a0, PS_INTLEVEL(RFI_X+1) | PS_EXCM wsr a0, PS rsync @@ -159,12 +159,12 @@ xt_highintx: sll a3, a3 wsr a3, WINDOWSTART -_highint4_stack_switch: +_highint_stack_switch: movi a0, 0 movi sp, _rmt_intr_stack + RMT_INTR_STACK_SIZE - 16 s32e a0, sp, -12 /* For GDB: set null SP */ s32e a0, sp, -16 /* For GDB: set null PC */ - movi a0, _highint4_stack_switch /* For GDB: cosmetics, for the frame where stack switch happened */ + movi a0, _highint_stack_switch /* For GDB: cosmetics, for the frame where stack switch happened */ /* Set up PS for C, disable all interrupts except NMI and debug, and clear EXCM. */ movi a6, PS_INTLEVEL(RFI_X) | PS_UM | PS_WOE @@ -178,7 +178,7 @@ _highint4_stack_switch: l32e sp, sp, -12 /* switch back to the original stack */ /* Done with C handler; re-enable exception mode, disabling window overflow */ - movi a2, PS_INTLEVEL(RFI_X) | PS_EXCM /* TOCHECK */ + movi a2, PS_INTLEVEL(RFI_X+1) | PS_EXCM /* TOCHECK */ wsr a2, PS rsync diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index 27ab13c1..84e3cbce 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -34,19 +34,129 @@ License along with NeoPixel. If not, see #include "../NeoSettings.h" #include "../NeoBusChannel.h" #include "NeoEsp32RmtHIMethod.h" -#include "hal/cpu_hal.h" -#include "hal/rmt_ll.h" -#include "hal/interrupt_controller_hal.h" #include "soc/soc.h" +#include "soc/rmt_reg.h" + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) +#include "hal/rmt_ll.h" +#else +/* Shims for older core; we can safely assume original ESP32 */ +#include "soc/rmt_struct.h" + +__attribute__((always_inline)) +static inline void rmt_ll_tx_reset_pointer(rmt_dev_t *dev, uint32_t channel) +{ + dev->conf_ch[channel].conf1.mem_rd_rst = 1; + dev->conf_ch[channel].conf1.mem_rd_rst = 0; +} + +__attribute__((always_inline)) +static inline void rmt_ll_tx_start(rmt_dev_t *dev, uint32_t channel) +{ + dev->conf_ch[channel].conf1.tx_start = 1; +} + +__attribute__((always_inline)) +static inline void rmt_ll_tx_stop(rmt_dev_t *dev, uint32_t channel) +{ + RMTMEM.chan[channel].data32[0].val = 0; + dev->conf_ch[channel].conf1.tx_start = 0; + dev->conf_ch[channel].conf1.mem_rd_rst = 1; + dev->conf_ch[channel].conf1.mem_rd_rst = 0; +} + +__attribute__((always_inline)) +static inline void rmt_ll_tx_enable_pingpong(rmt_dev_t *dev, uint32_t channel, bool enable) +{ + dev->apb_conf.mem_tx_wrap_en = enable; +} + +__attribute__((always_inline)) +static inline void rmt_ll_tx_enable_loop(rmt_dev_t *dev, uint32_t channel, bool enable) +{ + dev->conf_ch[channel].conf1.tx_conti_mode = enable; +} + +__attribute__((always_inline)) +static inline uint32_t rmt_ll_tx_get_channel_status(rmt_dev_t *dev, uint32_t channel) +{ + return dev->status_ch[channel]; +} + +__attribute__((always_inline)) +static inline void rmt_ll_tx_set_limit(rmt_dev_t *dev, uint32_t channel, uint32_t limit) +{ + dev->tx_lim_ch[channel].limit = limit; +} + +__attribute__((always_inline)) +static inline void rmt_ll_enable_interrupt(rmt_dev_t *dev, uint32_t mask, bool enable) +{ + if (enable) { + dev->int_ena.val |= mask; + } else { + dev->int_ena.val &= ~mask; + } +} + +__attribute__((always_inline)) +static inline void rmt_ll_enable_tx_end_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable) +{ + dev->int_ena.val &= ~(1 << (channel * 3)); + dev->int_ena.val |= (enable << (channel * 3)); +} +__attribute__((always_inline)) +static inline void rmt_ll_enable_tx_err_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable) +{ + dev->int_ena.val &= ~(1 << (channel * 3 + 2)); + dev->int_ena.val |= (enable << (channel * 3 + 2)); +} + +__attribute__((always_inline)) +static inline void rmt_ll_enable_tx_thres_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable) +{ + dev->int_ena.val &= ~(1 << (channel + 24)); + dev->int_ena.val |= (enable << (channel + 24)); +} + +__attribute__((always_inline)) +static inline void rmt_ll_clear_tx_end_interrupt(rmt_dev_t *dev, uint32_t channel) +{ + dev->int_clr.val = (1 << (channel * 3)); +} + +__attribute__((always_inline)) +static inline void rmt_ll_clear_tx_err_interrupt(rmt_dev_t *dev, uint32_t channel) +{ + dev->int_clr.val = (1 << (channel * 3 + 2)); +} + +__attribute__((always_inline)) +static inline void rmt_ll_clear_tx_thres_interrupt(rmt_dev_t *dev, uint32_t channel) +{ + dev->int_clr.val = (1 << (channel + 24)); +} + + +__attribute__((always_inline)) +static inline uint32_t rmt_ll_get_tx_thres_interrupt_status(rmt_dev_t *dev) +{ + uint32_t status = dev->int_st.val; + return (status & 0xFF000000) >> 24; +} + +#endif #if defined(CONFIG_BTDM_CTRL_HLI) -// Espressif's bluetooth driver offers a helpful sharing layer +// Espressif's bluetooth driver offers a helpful sharing layer; bring in the interrupt management calls +#include "hal/interrupt_controller_hal.h" extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32_t intr_reg, uint32_t intr_mask); + #else /* !CONFIG_BTDM_CTRL_HLI*/ // Link our high-priority ISR handler -extern "C" int ld_include_hli_vectors_rmt[0]; // an object with an address, but no space +extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, but no space #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) #include "soc/periph_defs.h" @@ -73,10 +183,14 @@ struct NeoEsp32RmtHIChannelState { }; // Global variables +#ifndef CONFIG_BTDM_CTRL_HLI +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) // New IDF static intr_handle_t isrHandle = nullptr; +#endif +#endif /* CONFIG_BTDM_CTRL_HLI */ static NeoEsp32RmtHIChannelState** driverState = nullptr; constexpr size_t rmtBatchSize = RMT_MEM_ITEM_NUM / 2; - +static uint32_t isr_count = 0; // Fill the RMT buffer memory // This is implemented using many arguments instead of passing the structure object to ensure we do only one lookup @@ -142,6 +256,7 @@ static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtHIChannelState& extern "C" void IRAM_ATTR NeoEsp32RmtMethodIsr(void *arg) { // Tx threshold interrupt + ++isr_count; uint32_t status = rmt_ll_get_tx_thres_interrupt_status(&RMT); while (status) { uint8_t channel = __builtin_ffs(status) - 1; @@ -158,6 +273,42 @@ extern "C" void IRAM_ATTR NeoEsp32RmtMethodIsr(void *arg) { } }; +// Wrapper around the register analysis defines +// For all currently supported chips, this is constant for all channels; but this is not true of *all* ESP32 +static inline bool _RmtStatusIsTransmitting(rmt_channel_t channel, uint32_t status) { + uint32_t v; + switch(channel) { +#ifdef RMT_STATE_CH0 + case 0: v = (status >> RMT_STATE_CH0_S) & RMT_STATE_CH0_V; break; +#endif +#ifdef RMT_STATE_CH1 + case 1: v = (status >> RMT_STATE_CH1_S) & RMT_STATE_CH1_V; break; +#endif +#ifdef RMT_STATE_CH2 + case 2: v = (status >> RMT_STATE_CH2_S) & RMT_STATE_CH2_V; break; +#endif +#ifdef RMT_STATE_CH3 + case 3: v = (status >> RMT_STATE_CH3_S) & RMT_STATE_CH3_V; break; +#endif +#ifdef RMT_STATE_CH4 + case 4: v = (status >> RMT_STATE_CH4_S) & RMT_STATE_CH4_V; break; +#endif +#ifdef RMT_STATE_CH5 + case 5: v = (status >> RMT_STATE_CH5_S) & RMT_STATE_CH5_V; break; +#endif +#ifdef RMT_STATE_CH6 + case 6: v = (status >> RMT_STATE_CH6_S) & RMT_STATE_CH6_V; break; +#endif +#ifdef RMT_STATE_CH7 + case 7: v = (status >> RMT_STATE_CH7_S) & RMT_STATE_CH7_V; break; +#endif + default: v = 0; + } + + return v != 0; +} + + esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t reset) { esp_err_t err = ESP_OK; if (!driverState) { @@ -173,9 +324,19 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmt // 25 is the magic number of the bluetooth ISR on ESP32 - see soc/soc.h. intr_matrix_set(cpu_hal_get_core_id(), ETS_RMT_INTR_SOURCE, 25); intr_cntrl_ll_enable_interrupts(1<<25); -#else +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) // New IDF // Our custom ISR is bound by the linker; passing a pointer to an address that source file here guarantees that we link it in err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, nullptr, &ld_include_hli_vectors_rmt, &isrHandle); +#else // IDF 3 + // Old IDF doesn't allow us to register the interrupt; it's flagged as reserved. + // Ensure all interruptss are cleared first + RMT.int_ena.val = 0; + RMT.int_clr.val = 0xFFFFFFFF; + + static volatile const void* __attribute__((used)) pleaseLinkAssembly = (void*) ld_include_hli_vectors_rmt; + + intr_matrix_set(xPortGetCoreID(), ETS_RMT_INTR_SOURCE, 26); + ESP_INTR_ENABLE(26); #endif if (err != ESP_OK) { @@ -203,7 +364,6 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmt // Initialize hardware rmt_ll_tx_stop(&RMT, channel); rmt_ll_tx_reset_pointer(&RMT, channel); - rmt_ll_tx_reset_loop(&RMT, channel); rmt_ll_enable_tx_err_interrupt(&RMT, channel, false); rmt_ll_enable_tx_end_interrupt(&RMT, channel, false); rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false); @@ -212,6 +372,7 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmt rmt_ll_clear_tx_thres_interrupt(&RMT, channel); rmt_ll_tx_enable_loop(&RMT, channel, false); + rmt_ll_tx_enable_pingpong(&RMT, channel, true); rmt_ll_tx_set_limit(&RMT, channel, rmtBatchSize); driverState[channel] = state; @@ -250,10 +411,9 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Write(rmt_channel_t channel, const uint8_t if (result == ESP_OK) { state.txDataStart = src; state.txDataCurrent = src; - state.txDataEnd = src + src_size; + state.txDataEnd = src + src_size; RmtStartWrite(channel, state); - - //Serial.printf("RMT %d write begin, %d/%d sent, status %08X\n", channel, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, rmt_ll_tx_get_channel_status(&RMT, channel)); + //Serial.printf("RMT %d write begin, %d/%d sent, status %08X, ic %d\n", channel, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, rmt_ll_tx_get_channel_status(&RMT, channel), isr_count); } return result; } @@ -264,18 +424,23 @@ esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickTy NeoEsp32RmtHIChannelState& state = *driverState[channel]; // yield-wait until wait_time esp_err_t rv = ESP_OK; + //uint32_t loop_count = 0; uint32_t status; while(1) { status = rmt_ll_tx_get_channel_status(&RMT, channel); - if ((state.txDataCurrent == state.txDataEnd) && ((status & 0x07000000) == 0)) break; /* Stopped state while not at end could mean we caught it restarting on an error */ + if (!_RmtStatusIsTransmitting(channel, status)) break; if (wait_time == 0) { rv = ESP_ERR_TIMEOUT; break; }; + //++loop_count; TickType_t sleep = std::min(wait_time, 5U); vTaskDelay(sleep); wait_time -= sleep; }; - - //Serial.printf("RMT %d wait %d: %d/%d sent, status %08X\n", channel, rv, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, status); +/* + if (loop_count) { + Serial.printf("RMT %d wait %d: %d/%d sent, status %08X, ic %d\n", channel, rv, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, status, isr_count); + } +*/ return rv; } From 96b8be0edc3307d7e810c0df8300788b6589c150 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Tue, 8 Jul 2025 16:51:30 -0400 Subject: [PATCH 06/14] RmtHI: Fix loop regs --- src/internal/methods/NeoEsp32RmtHI.S | 4 ++-- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHI.S b/src/internal/methods/NeoEsp32RmtHI.S index 4f65bfe5..9d24fd68 100644 --- a/src/internal/methods/NeoEsp32RmtHI.S +++ b/src/internal/methods/NeoEsp32RmtHI.S @@ -188,15 +188,15 @@ _highint_stack_switch: movi a0, _rmt_save_ctx + SPECREG_OFFSET l32i a2, a0, 8 wsr a2, SAR - l32i a2, a0, 12 #if XCHAL_HAVE_LOOPS + l32i a2, a0, 12 wsr a2, LBEG l32i a2, a0, 16 wsr a2, LEND l32i a2, a0, 20 wsr a2, LCOUNT - l32i a2, a0, 24 #endif + l32i a2, a0, 24 wsr a2, EPC1 /* Restoring the physical registers. diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index 84e3cbce..cb646d90 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -326,7 +326,7 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmt intr_cntrl_ll_enable_interrupts(1<<25); #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) // New IDF // Our custom ISR is bound by the linker; passing a pointer to an address that source file here guarantees that we link it in - err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, nullptr, &ld_include_hli_vectors_rmt, &isrHandle); + err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, nullptr, (void*) &ld_include_hli_vectors_rmt, &isrHandle); #else // IDF 3 // Old IDF doesn't allow us to register the interrupt; it's flagged as reserved. // Ensure all interruptss are cleared first From e0d911c508bb4bcaacf746e24a973981417fa917 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Tue, 8 Jul 2025 22:09:28 -0400 Subject: [PATCH 07/14] RmtHI: Tune startup timing --- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index cb646d90..22c8fe66 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -226,7 +226,7 @@ static void IRAM_ATTR RmtFillBuffer(uint8_t channel, const byte** src_ptr, const if (rmtToWrite > 0) { // Add end event rmt_item32_t bit0_val = {{.val = bit0 }}; - *dest = rmt_item32_t {{{ .duration0 = 0, .level0 = bit0_val.level1, .duration1 = 0, .level1 = 0 }}}; + *dest = rmt_item32_t {{{ .duration0 = 0, .level0 = bit0_val.level1, .duration1 = 0, .level1 = bit0_val.level1 }}}; } } @@ -237,8 +237,8 @@ static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtHIChannelState& // Fill the first part of the buffer with a reset event // FUTURE: we could do timing analysis with the last interrupt on this channel // Use 8 words to stay aligned with the buffer fill logic - uint32_t idle_lvl = (rmt_item32_t {{.val = state.rmtBit0}}).level1; - rmt_item32_t fill = {{{ .duration0 = 1, .level0 = idle_lvl, .duration1 = 1, .level1 = idle_lvl }}}; + rmt_item32_t bit0_val = {{.val = state.rmtBit0 }}; + rmt_item32_t fill = {{{ .duration0 = 2, .level0 = bit0_val.level1, .duration1 = 2, .level1 = bit0_val.level1 }}}; rmt_item32_t* dest = (rmt_item32_t*) &RMTMEM.chan[channel].data32[0]; for (auto i = 0; i < 7; ++i) dest[i] = fill; fill.duration1 = state.resetDuration - 17; From afb117331783146a218581447acf27b8ed69d975 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Tue, 8 Jul 2025 23:34:35 -0400 Subject: [PATCH 08/14] RmtHI: Add license statement for borrowed code --- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index 22c8fe66..b53ba661 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -43,6 +43,22 @@ License along with NeoPixel. If not, see /* Shims for older core; we can safely assume original ESP32 */ #include "soc/rmt_struct.h" +// Selected RMT API functions borrowed from ESP-IDF v4.4.8 +// components/hal/esp32/include/hal/rmt_ll.h +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + __attribute__((always_inline)) static inline void rmt_ll_tx_reset_pointer(rmt_dev_t *dev, uint32_t channel) { From 1854fe9f4e1217f57acb8aa258f29950911063c5 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Thu, 17 Jul 2025 19:13:01 -0400 Subject: [PATCH 09/14] RMTHI: Increase reset time values Small count values seem to occasionally cause bit-shifts in the output. --- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index b53ba661..e0c017fd 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -254,10 +254,10 @@ static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtHIChannelState& // FUTURE: we could do timing analysis with the last interrupt on this channel // Use 8 words to stay aligned with the buffer fill logic rmt_item32_t bit0_val = {{.val = state.rmtBit0 }}; - rmt_item32_t fill = {{{ .duration0 = 2, .level0 = bit0_val.level1, .duration1 = 2, .level1 = bit0_val.level1 }}}; + rmt_item32_t fill = {{{ .duration0 = 100, .level0 = bit0_val.level1, .duration1 = 100, .level1 = bit0_val.level1 }}}; rmt_item32_t* dest = (rmt_item32_t*) &RMTMEM.chan[channel].data32[0]; for (auto i = 0; i < 7; ++i) dest[i] = fill; - fill.duration1 = state.resetDuration - 17; + fill.duration1 = state.resetDuration > 1400 ? (state.resetDuration - 1400) : 100; dest[7] = fill; // Fill the remaining buffer with real data From b5682e3b6b8ff4199a4119084c77814656ccb824 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Thu, 17 Jul 2025 19:13:40 -0400 Subject: [PATCH 10/14] RMTHI: Improve chip support Expand feature/bug detection of ESP platforms. --- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index e0c017fd..d48908c7 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -184,6 +184,12 @@ extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, bu #define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL5 #endif +// ESP-IDF v3 cannot enable high priority interrupts through the API at all; +// and ESP-IDF v4 cannot enable Level 5 due to incorrect interrupt descriptor tables +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) || ((ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)) && CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5) +#define NEOESP32_RMT_CAN_USE_INTR_ALLOC +#endif + #endif /* CONFIG_BTDM_CTRL_HLI */ @@ -199,11 +205,10 @@ struct NeoEsp32RmtHIChannelState { }; // Global variables -#ifndef CONFIG_BTDM_CTRL_HLI -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) // New IDF +#if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC) static intr_handle_t isrHandle = nullptr; #endif -#endif /* CONFIG_BTDM_CTRL_HLI */ + static NeoEsp32RmtHIChannelState** driverState = nullptr; constexpr size_t rmtBatchSize = RMT_MEM_ITEM_NUM / 2; static uint32_t isr_count = 0; @@ -340,17 +345,18 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmt // 25 is the magic number of the bluetooth ISR on ESP32 - see soc/soc.h. intr_matrix_set(cpu_hal_get_core_id(), ETS_RMT_INTR_SOURCE, 25); intr_cntrl_ll_enable_interrupts(1<<25); -#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) // New IDF +#elif defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC) // Our custom ISR is bound by the linker; passing a pointer to an address that source file here guarantees that we link it in err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, nullptr, (void*) &ld_include_hli_vectors_rmt, &isrHandle); -#else // IDF 3 - // Old IDF doesn't allow us to register the interrupt; it's flagged as reserved. +#else + // Broken IDF API does not allow us to reserve the interrupt; do it manually // Ensure all interruptss are cleared first RMT.int_ena.val = 0; RMT.int_clr.val = 0xFFFFFFFF; static volatile const void* __attribute__((used)) pleaseLinkAssembly = (void*) ld_include_hli_vectors_rmt; + // 26 is the interrupt id for the level 5 interrupt on Espressif XTensa cores intr_matrix_set(xPortGetCoreID(), ETS_RMT_INTR_SOURCE, 26); ESP_INTR_ENABLE(26); #endif From 2baf9120c7955b8ebbe350c92fe03d68c4d7e5b0 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Thu, 31 Jul 2025 23:03:00 -0400 Subject: [PATCH 11/14] RmtHI: Support RISCV (ESP32-C3) --- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 45 ++++++++++++++------ src/internal/methods/NeoEsp32RmtHIMethod.h | 2 +- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index d48908c7..1df81f56 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -29,7 +29,7 @@ License along with NeoPixel. If not, see #include -#if defined(ARDUINO_ARCH_ESP32) && defined(__XTENSA__) +#if defined(ARDUINO_ARCH_ESP32) #include "../NeoSettings.h" #include "../NeoBusChannel.h" @@ -37,6 +37,10 @@ License along with NeoPixel. If not, see #include "soc/soc.h" #include "soc/rmt_reg.h" +#ifdef __riscv +#include "riscv/interrupt.h" +#endif + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) #include "hal/rmt_ll.h" #else @@ -174,7 +178,7 @@ extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32 // Link our high-priority ISR handler extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, but no space -#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) +#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) #include "soc/periph_defs.h" #endif @@ -185,9 +189,17 @@ extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, bu #endif // ESP-IDF v3 cannot enable high priority interrupts through the API at all; -// and ESP-IDF v4 cannot enable Level 5 due to incorrect interrupt descriptor tables -#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) || ((ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)) && CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5) +// and ESP-IDF v4 on XTensa cannot enable Level 5 due to incorrect interrupt descriptor tables +#if !defined(__XTENSA__) || (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) || ((ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) && CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5)) #define NEOESP32_RMT_CAN_USE_INTR_ALLOC + +// XTensa cores require the assembly bridge +#ifdef __XTENSA__ +#define HI_IRQ_HANDLER_ARG ld_include_hli_vectors_rmt +#else +#define HI_IRQ_HANDLER_ARG nullptr +#endif + #endif #endif /* CONFIG_BTDM_CTRL_HLI */ @@ -337,6 +349,10 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmt driverState = reinterpret_cast(heap_caps_calloc(RMT_CHANNEL_MAX, sizeof(NeoEsp32RmtHIChannelState*), MALLOC_CAP_INTERNAL)); if (!driverState) return ESP_ERR_NO_MEM; + // Ensure all interrupts are cleared before binding + RMT.int_ena.val = 0; + RMT.int_clr.val = 0xFFFFFFFF; + // Bind interrupt handler #if defined(CONFIG_BTDM_CTRL_HLI) // Bluetooth driver has taken the empty high-priority interrupt. Fortunately, it allows us to @@ -346,14 +362,19 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmt intr_matrix_set(cpu_hal_get_core_id(), ETS_RMT_INTR_SOURCE, 25); intr_cntrl_ll_enable_interrupts(1<<25); #elif defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC) - // Our custom ISR is bound by the linker; passing a pointer to an address that source file here guarantees that we link it in - err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, nullptr, (void*) &ld_include_hli_vectors_rmt, &isrHandle); -#else + // Use the platform code to allocate the interrupt + // If we need the additional assembly bridge, we pass it as the "arg" to the IDF so it gets linked in + err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, nullptr, (void*) HI_IRQ_HANDLER_ARG, &isrHandle); + //err = ESP_ERR_NOT_FINISHED; + + #ifdef __riscv + if (err == ESP_OK) { + // We must bind the ISR to the CPU ourselves. Go right through the HAL because of type errors + intr_handler_set(esp_intr_get_intno(isrHandle), NeoEsp32RmtMethodIsr, nullptr); + } + #endif +#else // Broken IDF API does not allow us to reserve the interrupt; do it manually - // Ensure all interruptss are cleared first - RMT.int_ena.val = 0; - RMT.int_clr.val = 0xFFFFFFFF; - static volatile const void* __attribute__((used)) pleaseLinkAssembly = (void*) ld_include_hli_vectors_rmt; // 26 is the interrupt id for the level 5 interrupt on Espressif XTensa cores @@ -454,7 +475,7 @@ esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickTy if (wait_time == 0) { rv = ESP_ERR_TIMEOUT; break; }; //++loop_count; - TickType_t sleep = std::min(wait_time, 5U); + TickType_t sleep = std::min(wait_time, (TickType_t) 5); vTaskDelay(sleep); wait_time -= sleep; }; diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.h b/src/internal/methods/NeoEsp32RmtHIMethod.h index e0b6b9b5..45cfdd4b 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.h +++ b/src/internal/methods/NeoEsp32RmtHIMethod.h @@ -28,7 +28,7 @@ License along with NeoPixel. If not, see #pragma once -#if defined(ARDUINO_ARCH_ESP32) && defined(__XTENSA__) +#if defined(ARDUINO_ARCH_ESP32) // Use the NeoEspRmtSpeed types from the driver-based implementation #include "NeoEsp32RmtMethod.h" From 16242f10995afb04817e6c45fb299deab211e05d Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 4 Aug 2025 10:59:20 -0400 Subject: [PATCH 12/14] RmtHi: Cleanup --- src/internal/methods/NeoEsp32RmtHI.S | 13 ++-- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 73 ++++++++++++-------- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHI.S b/src/internal/methods/NeoEsp32RmtHI.S index 9d24fd68..19c5737e 100644 --- a/src/internal/methods/NeoEsp32RmtHI.S +++ b/src/internal/methods/NeoEsp32RmtHI.S @@ -5,9 +5,7 @@ * */ -/* RISC-V CPUs not supported (yet?) */ -/* If the Bluetooth driver has hooked the high-priority interrupt, we piggyback on it and don't need this. */ -#if defined(__XTENSA__) && defined(ESP32) +#if defined(__XTENSA__) && defined(ESP32) && !defined(CONFIG_BTDM_CTRL_HLI) #include #include @@ -16,30 +14,27 @@ #include "sdkconfig.h" #include "soc/soc.h" +/* If the Bluetooth driver has hooked the high-priority interrupt, we piggyback on it and don't need this. */ #ifndef CONFIG_BTDM_CTRL_HLI /* - Select interrupt + Select interrupt based on system check level - Base ESP32: could be 4 or 5, depends on platform config - S2: 5 - S3: 5 */ #if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 - /* Use level 4 */ #define RFI_X 4 #define xt_highintx xt_highint4 - #else /* !CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */ - /* Use level 5 */ #define RFI_X 5 #define xt_highintx xt_highint5 - #endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */ -// Other registers +// Register map, based on interrupt level #define EPC_X (EPC + RFI_X) #define EXCSAVE_X (EXCSAVE + RFI_X) diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index 1df81f56..92a44f3f 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -41,10 +41,11 @@ License along with NeoPixel. If not, see #include "riscv/interrupt.h" #endif + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) #include "hal/rmt_ll.h" #else -/* Shims for older core; we can safely assume original ESP32 */ +/* Shims for older ESP-IDF v3; we can safely assume original ESP32 */ #include "soc/rmt_struct.h" // Selected RMT API functions borrowed from ESP-IDF v4.4.8 @@ -165,9 +166,17 @@ static inline uint32_t rmt_ll_get_tx_thres_interrupt_status(rmt_dev_t *dev) uint32_t status = dev->int_st.val; return (status & 0xFF000000) >> 24; } - #endif + +// ********************************* +// Select method for binding interrupt +// +// - If the Bluetooth driver has registered a high-level interrupt, piggyback on that API +// - If we're on a modern core, allocate the interrupt with the API (old cores are bugged) +// - Otherwise use the low-level hardware API to manually bind the interrupt + + #if defined(CONFIG_BTDM_CTRL_HLI) // Espressif's bluetooth driver offers a helpful sharing layer; bring in the interrupt management calls #include "hal/interrupt_controller_hal.h" @@ -175,9 +184,6 @@ extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32 #else /* !CONFIG_BTDM_CTRL_HLI*/ -// Link our high-priority ISR handler -extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, but no space - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) #include "soc/periph_defs.h" #endif @@ -195,13 +201,19 @@ extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, bu // XTensa cores require the assembly bridge #ifdef __XTENSA__ +// Link our high-priority ISR handler +extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, but no space #define HI_IRQ_HANDLER_ARG ld_include_hli_vectors_rmt #else #define HI_IRQ_HANDLER_ARG nullptr #endif -#endif +#else +/* !CONFIG_BTDM_CTRL_HLI && !NEOESP32_RMT_CAN_USE_INTR_ALLOC */ +// This is the index of the LV5 interrupt vector - see interrupt descriptor table in idf components/hal/esp32/interrupt_descriptor_table.c +#define ESP32_LV5_IRQ_INDEX 26 +#endif /* NEOESP32_RMT_CAN_USE_INTR_ALLOC */ #endif /* CONFIG_BTDM_CTRL_HLI */ @@ -223,7 +235,6 @@ static intr_handle_t isrHandle = nullptr; static NeoEsp32RmtHIChannelState** driverState = nullptr; constexpr size_t rmtBatchSize = RMT_MEM_ITEM_NUM / 2; -static uint32_t isr_count = 0; // Fill the RMT buffer memory // This is implemented using many arguments instead of passing the structure object to ensure we do only one lookup @@ -289,7 +300,6 @@ static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtHIChannelState& extern "C" void IRAM_ATTR NeoEsp32RmtMethodIsr(void *arg) { // Tx threshold interrupt - ++isr_count; uint32_t status = rmt_ll_get_tx_thres_interrupt_status(&RMT); while (status) { uint8_t channel = __builtin_ffs(status) - 1; @@ -343,6 +353,11 @@ static inline bool _RmtStatusIsTransmitting(rmt_channel_t channel, uint32_t stat esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t reset) { + // Validate channel number + if (channel >= RMT_CHANNEL_MAX) { + return ESP_ERR_INVALID_ARG; + } + esp_err_t err = ESP_OK; if (!driverState) { // First time init @@ -376,14 +391,11 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmt #else // Broken IDF API does not allow us to reserve the interrupt; do it manually static volatile const void* __attribute__((used)) pleaseLinkAssembly = (void*) ld_include_hli_vectors_rmt; - - // 26 is the interrupt id for the level 5 interrupt on Espressif XTensa cores - intr_matrix_set(xPortGetCoreID(), ETS_RMT_INTR_SOURCE, 26); - ESP_INTR_ENABLE(26); + intr_matrix_set(xPortGetCoreID(), ETS_RMT_INTR_SOURCE, ESP32_LV5_IRQ_INDEX); + ESP_INTR_ENABLE(ESP32_LV5_IRQ_INDEX); #endif if (err != ESP_OK) { - Serial.printf("Couldn't assign RMT IRQ: %d\n", err); heap_caps_free(driverState); driverState = nullptr; return err; @@ -422,12 +434,11 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmt rmt_ll_enable_tx_thres_interrupt(&RMT, channel, true); - //Serial.printf("RMT %d install\n", channel); return err; } esp_err_t NeoEsp32RmtHiMethodDriver::Uninstall(rmt_channel_t channel) { - if (!driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; + if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; NeoEsp32RmtHIChannelState* state = driverState[channel]; @@ -439,14 +450,27 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Uninstall(rmt_channel_t channel) { driverState[channel] = nullptr; heap_caps_free(state); - // TODO: turn off the driver ISR and release global state if none are left - //Serial.printf("RMT %d uninstall\n", channel); +#if !defined(CONFIG_BTDM_CTRL_HLI) /* Cannot unbind from bluetooth ISR */ + // Turn off the driver ISR and release global state if none are left + for (uint8_t channelIndex = 0; channelIndex < RMT_CHANNEL_MAX; ++channelIndex) { + if (driverState[channelIndex]) return ESP_OK; // done + } + +#if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC) + esp_intr_free(&isrHandle); +#else + ESP_INTR_DISABLE(ESP32_LV5_IRQ_INDEX); +#endif + + heap_caps_free(driverState); + driverState = nullptr; +#endif /* !defined(CONFIG_BTDM_CTRL_HLI) */ return ESP_OK; } esp_err_t NeoEsp32RmtHiMethodDriver::Write(rmt_channel_t channel, const uint8_t *src, size_t src_size) { - if (!driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; + if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; NeoEsp32RmtHIChannelState& state = *driverState[channel]; esp_err_t result = WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS); @@ -456,37 +480,28 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Write(rmt_channel_t channel, const uint8_t state.txDataCurrent = src; state.txDataEnd = src + src_size; RmtStartWrite(channel, state); - //Serial.printf("RMT %d write begin, %d/%d sent, status %08X, ic %d\n", channel, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, rmt_ll_tx_get_channel_status(&RMT, channel), isr_count); } return result; } esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickType_t wait_time) { - if (!driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; + if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG; NeoEsp32RmtHIChannelState& state = *driverState[channel]; // yield-wait until wait_time esp_err_t rv = ESP_OK; - //uint32_t loop_count = 0; uint32_t status; while(1) { status = rmt_ll_tx_get_channel_status(&RMT, channel); if (!_RmtStatusIsTransmitting(channel, status)) break; if (wait_time == 0) { rv = ESP_ERR_TIMEOUT; break; }; - //++loop_count; TickType_t sleep = std::min(wait_time, (TickType_t) 5); vTaskDelay(sleep); wait_time -= sleep; }; -/* - if (loop_count) { - Serial.printf("RMT %d wait %d: %d/%d sent, status %08X, ic %d\n", channel, rv, state.txDataCurrent - state.txDataStart, state.txDataEnd - state.txDataStart, status, isr_count); - } -*/ + return rv; } - - #endif \ No newline at end of file From 988fe239e537f24ad93108d1e6099fd123b72b8c Mon Sep 17 00:00:00 2001 From: Will Miles Date: Tue, 5 Aug 2025 23:07:25 -0400 Subject: [PATCH 13/14] RmtHi: Tweak includes for PlatformIO The PlatformIO Library Dependency Finder on ESP8266 adds the "GDBStub" library to supply "xtensa/xtensa_context.h", even though the whole source file is ifdef'd out. Find another include that serves the same purpose but never appears on ESP8266 to avoid unnecessarily consuming flash space. --- src/internal/methods/NeoEsp32RmtHI.S | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHI.S b/src/internal/methods/NeoEsp32RmtHI.S index 19c5737e..0c60d2eb 100644 --- a/src/internal/methods/NeoEsp32RmtHI.S +++ b/src/internal/methods/NeoEsp32RmtHI.S @@ -7,10 +7,7 @@ #if defined(__XTENSA__) && defined(ESP32) && !defined(CONFIG_BTDM_CTRL_HLI) -#include -#include -#include -#include "freertos/xtensa_context.h" +#include #include "sdkconfig.h" #include "soc/soc.h" From fc4df7d77509249e8b3aa42f991f7d13e6f411de Mon Sep 17 00:00:00 2001 From: Will Miles Date: Tue, 5 Aug 2025 23:08:36 -0400 Subject: [PATCH 14/14] RmtHi: Fix references and linkage --- src/internal/methods/NeoEsp32RmtHIMethod.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/internal/methods/NeoEsp32RmtHIMethod.cpp b/src/internal/methods/NeoEsp32RmtHIMethod.cpp index 92a44f3f..bb4b3463 100644 --- a/src/internal/methods/NeoEsp32RmtHIMethod.cpp +++ b/src/internal/methods/NeoEsp32RmtHIMethod.cpp @@ -184,6 +184,9 @@ extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32 #else /* !CONFIG_BTDM_CTRL_HLI*/ +// Declare the our high-priority ISR handler +extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, but no space + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) #include "soc/periph_defs.h" #endif @@ -201,8 +204,6 @@ extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32 // XTensa cores require the assembly bridge #ifdef __XTENSA__ -// Link our high-priority ISR handler -extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, but no space #define HI_IRQ_HANDLER_ARG ld_include_hli_vectors_rmt #else #define HI_IRQ_HANDLER_ARG nullptr @@ -457,7 +458,7 @@ esp_err_t NeoEsp32RmtHiMethodDriver::Uninstall(rmt_channel_t channel) { } #if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC) - esp_intr_free(&isrHandle); + esp_intr_free(isrHandle); #else ESP_INTR_DISABLE(ESP32_LV5_IRQ_INDEX); #endif