NeoPixelBus icon indicating copy to clipboard operation
NeoPixelBus copied to clipboard

Compatibility problem with arduino-esp32 v3.3.0 and ETH.h (W5500) with NeoEsp32LcdX16Ws2813Method

Open tresler opened this issue 4 months ago • 9 comments

Hello,

I am experiencing a spinlock collision on my ESP32-S3 when using Ethernet together with NeoPixelBus.

  • When I use only NeoPixelBus with NeoEsp32LcdX16Ws2813Method, everything works fine.
  • When I use only ETH.h with a W5500 module, everything also runs fine.
  • But when I use them together, the code fails on the first Show(); call with the following error:
assert failed: spinlock_acquire spinlock.h:142 (lock->count == 0)

Backtrace: 0x40375c75:0x3fcebd70 0x4037bb1d:0x3fcebd90 0x40382656:0x3fcebdb0 
0x4037c9ee:0x3fcebef0 0x4205141e:0x3fcebf20 0x420038c2:0x3fcebf40 
0x42009888:0x3fcebf80 0x4037c7b9:0x3fcebfa0

This happens on Arduino-ESP32 v3.3.0 (ESP-IDF v5). Could this be fixed in the NeoPixelBus library so that it works with Ethernet (W5500) on ESP32-S3?

Thank you!

tresler avatar Aug 21 '25 11:08 tresler

You will need to decode the backtrace for your built sketch. There are instructions online for doing this. It will provide the call stack that points to who's lock is the culprit and where in the code it is.

Makuna avatar Aug 21 '25 13:08 Makuna

I ran into some issues with v3.3.0 as well, namely even example NeoPixelBus code not compiling for the ESP32S3 Dev Module. Compiles fine with the previous version, v3.2.1. Are you seeing your bug if you roll back to 3.2.1 as well?

Here's the method they've dropped and the error I get:

[...]/NeoPixelBus_by_Makuna/src/internal/methods/NeoEsp32LcdXMethod.h:545:32: error: 'gpio_hal_iomux_func_sel' was not declared in this scope; did you mean 'gpio_hal_func_sel'?

obar avatar Aug 21 '25 20:08 obar

I ran into some issues with v3.3.0 as well, namely even example NeoPixelBus code not compiling for the ESP32S3 Dev Module. Compiles fine with the previous version, v3.2.1. Are you seeing your bug if you roll back to 3.2.1 as well?

Here's the method they've dropped and the error I get:

[...]/NeoPixelBus_by_Makuna/src/internal/methods/NeoEsp32LcdXMethod.h:545:32: error: 'gpio_hal_iomux_func_sel' was not declared in this scope; did you mean 'gpio_hal_func_sel'?

I was change NeoEsp32LcdXMethod.h file. At top I import #include "hal/gpio_ll.h" and in class NeoEsp32LcdMuxBus I change Initialize method.

    void Initialize(uint8_t pin, uint16_t nsBitSendTime, bool invert)
    {
        s_context.Construct(nsBitSendTime);
........
        uint8_t muxIdx = LCD_DATA_OUT0_IDX + _muxId;
        esp_rom_gpio_connect_out_signal(pin, muxIdx, invert, false);
        gpio_ll_func_sel(&GPIO, pin, PIN_FUNC_GPIO);
        //gpio_ll_func_sel((gpio_num_t)pin, PIN_FUNC_GPIO);
        //gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO);
        gpio_set_drive_capability((gpio_num_t)pin, (gpio_drive_cap_t)3);
    }

tresler avatar Aug 21 '25 22:08 tresler

You will need to decode the backtrace for your built sketch. There are instructions online for doing this. It will provide the call stack that points to who's lock is the culprit and where in the code it is.

0x40375d49:` panic_abort at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp_system/panic.c:469
0x4037db39: esp_system_abort at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp_system/port/esp_system_chip.c:87
0x403846fa: __assert_func at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/newlib/src/assert.c:80
0x4037ea42: spinlock_acquire at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp_hw_support/include/spinlock.h:106 (discriminator 1)
 (inlined by) xPortEnterCriticalTimeout at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:479 (discriminator 1)
0x420b34e6: xPortEnterCriticalTimeoutSafe at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/include/freertos/portmacro.h:581
 (inlined by) vPortEnterCriticalSafe at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/include/freertos/portmacro.h:588
 (inlined by) gdma_reset at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp_hw_support/dma/gdma.c:576
0x42002dcc: NeoEspLcdMonoBuffContext<NeoEspLcdMuxMap<unsigned short, NeoEspLcdMuxBusSize16Bit> >::StartWrite() at /home/tomi/Arduino/libraries/NeoPixelBus_by_Makuna/src/internal/methods/NeoEsp32LcdXMethod.h:477
 (inlined by) NeoEspLcdMonoBuffContext<NeoEspLcdMuxMap<unsigned short, NeoEspLcdMuxBusSize16Bit> >::StartWrite() at /home/tomi/Arduino/libraries/NeoPixelBus_by_Makuna/src/internal/methods/NeoEsp32LcdXMethod.h:471
 (inlined by) NeoEsp32LcdMuxBus<NeoEspLcdMonoBuffContext<NeoEspLcdMuxMap<unsigned short, NeoEspLcdMuxBusSize16Bit> > >::StartWrite() at /home/tomi/Arduino/libraries/NeoPixelBus_by_Makuna/src/internal/methods/NeoEsp32LcdXMethod.h:568
 (inlined by) NeoEsp32LcdXMethodBase<NeoBitsSpeedWs2812x, NeoEsp32LcdMuxBus<NeoEspLcdMonoBuffContext<NeoEspLcdMuxMap<unsigned short, NeoEspLcdMuxBusSize16Bit> > >, NeoBitsNotInverted>::Update(bool) at /home/tomi/Arduino/libraries/NeoPixelBus_by_Makuna/src/internal/methods/NeoEsp32LcdXMethod.h:649
 (inlined by) NeoPixelBus<NeoRgbFeature, NeoEsp32LcdXMethodBase<NeoBitsSpeedWs2812x, NeoEsp32LcdMuxBus<NeoEspLcdMonoBuffContext<NeoEspLcdMuxMap<unsigned short, NeoEspLcdMuxBusSize16Bit> > >, NeoBitsNotInverted> >::Show(bool) at /home/tomi/Arduino/libraries/NeoPixelBus_by_Makuna/src/NeoPixelBus.h:138
 (inlined by) loop() at /home/tomi/Arduino/project/adresovatelne_pasky-test-neopixelBus_ETH/adresovatelne_pasky-test-neopixelBus_ETH.ino:85
0x4200c168: loopTask(void*) at /home/tomi/.arduino15/packages/esp32/hardware/esp32/3.3.0/cores/esp32/main.cpp:74
0x4037e80d: vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:139

tresler avatar Aug 21 '25 22:08 tresler

Do you call Show() in more than one location?

The error is stating that in the API call gdma_reset() (not my code) it is trying to take a lock (spin lock).
The "web" generally states this is due to two possible situations. Stack corruption (increase your stack size) or general memory corruption (someone is writing past the end of their buffer). It's interesting that in most cases it is dealing with WiFi that it happens. Probably due to the buffers being expected to be copied or filled and incorrect size is being used (off by one?).

Makuna avatar Aug 22 '25 13:08 Makuna

No I just use minimal code:

#include <NeoPixelBrightnessBus.h>
#include <ETH.h>

const int stripPin[] = {21, 38, 39, 40, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 41, 42};
const int stripLength[] = {238, 198, 203, 214, 259, 246, 226, 231, 254, 227, 213, 256, 215, 223, 224, 236};

#define ETH_PHY_TYPE     ETH_PHY_W5500
#define ETH_PHY_SPI_FREQ_MHZ 10
#define ETH_PHY_ADDR        1
#define ETH_PHY_CS          10
#define ETH_PHY_IRQ         9
#define ETH_PHY_RST         14
#define ETH_PHY_SPI_HOST   SPI2_HOST
#define ETH_PHY_SPI_SCK     12
#define ETH_PHY_SPI_MISO    13
#define ETH_PHY_SPI_MOSI    11
#define USE_W5100 false
SPIClass * fspi = NULL;


NeoPixelBrightnessBus<NeoRgbFeature, NeoEsp32LcdX16Ws2813Method>* strips[16]; 

void setup() {
  Serial.begin(115200);

  Serial.println();
  Serial.println("Initializing...");

  //ETH setup
  fspi = new SPIClass(HSPI);
  fspi->begin(ETH_PHY_SPI_SCK, ETH_PHY_SPI_MISO, ETH_PHY_SPI_MOSI, ETH_PHY_CS);
  ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, *fspi, ETH_PHY_SPI_FREQ_MHZ);
  Serial.println("All network information: ");
  Serial.println(Network);

  for (int i = 0; i < 16; i++) {
    strips[i] = new NeoPixelBrightnessBus<NeoRgbFeature, NeoEsp32LcdX16Ws2813Method>(stripLength[i], stripPin[i]);
    strips[i]->Begin(); // Předpokládám, že metoda Begin() je potřebná pro inicializaci stripu
  }
  
  Serial.println();
  Serial.println("Running...");
}

int actualstrip = 0;
int a = 0;

void loop() {
    strips[actualstrip]->SetBrightness(255);
    strips[actualstrip]->SetPixelColor(a, RgbColor(255, 0, 0));      

    for (int i = 0; i < 16; i++) {
      strips[i]->Show();
    }

    a++;
    if (a > stripLength[actualstrip]) {
       a = 0;
       actualstrip++;
       if (actualstrip > 15) {
           actualstrip = 0;
       }
    }
}

tresler avatar Aug 22 '25 14:08 tresler

If you turn verbose debug output on, do you see anything else before the assert in the output? The spin lock is not within my code, it is inside a system call gdma_reset()

Did you run a memory calculation on 16 busses at over 200 pixels each? Thats a lot of memory. allocation failures should show up in the debug output when verbose is on, and they fail pretty silently.

front buffer size for each bus = 3 (RGB) * pixel count dma back buffer = largest front buffer size * cadence (3 or 4) * bus width (16 in your case) * 8

Something to try... Separate the allocation of the bus from the initialization so that all busses are allocated before the first bus::begin is called.

Makuna avatar Aug 25 '25 13:08 Makuna

If you turn verbose debug output on, do you see anything else before the assert in the output? The spin lock is not within my code, it is inside a system call gdma_reset()

Did you run a memory calculation on 16 busses at over 200 pixels each? Thats a lot of memory. allocation failures should show up in the debug output when verbose is on, and they fail pretty silently.

front buffer size for each bus = 3 (RGB) * pixel count dma back buffer = largest front buffer size * cadence (3 or 4) * bus width (16 in your case) * 8

Something to try... Separate the allocation of the bus from the initialization so that all busses are allocated before the first bus::begin is called.

Wow, I just separate the allocation and you are right. Now it is working with Ethernet (ETH.h and W5500).

  for (int i = 0; i < 16; i++) {
    strips[i] = new NeoPixelBrightnessBus<NeoRgbFeature, NeoEsp32LcdX16Ws2813Method>(stripLength[i], stripPin[i]);
  }
  delay(100);
  for (int j = 0; j < 16; j++) {
    strips[j]->Begin();
  }

tresler avatar Aug 27 '25 10:08 tresler

I am not sure this is documented anywhere, but it is often overlooked detail that all busses of the parallel methods must be allocated all before the first begin. Under the covers it collects a count and size data that is then used when the FIRST begin is called.

I will leave this issue open to track documenting this requirement.

Makuna avatar Aug 27 '25 14:08 Makuna