ledc causing WDT to reboot...

lehrian
Posts: 24
Joined: Wed May 12, 2021 3:07 am

ledc causing WDT to reboot...

Postby lehrian » Sat Apr 04, 2026 1:34 am

I have some firmware that has been in production for 4 or 5 years and is running on ESP-IDF 5.1.2. I'm in the process of updating it to run on 5.5.4 and everything went smoothly except for the ledc code.

There is a task that runs on core 1 that changes the LED when the controller enters different states that are set by tasks running on Core 0. e.g. BLE connected, Wifi connected...etc. The code can fade a single color via ledc just fine, say green or blue by itself. But when it attempts to mix 2 colors (blue and red) via ledc fade a WDT exception is being triggered.

Here is the configuration of the ledc timer, I'm using low speed mode.

Code: Select all

    // configure the timer for the PWM DML 4/2/2024
    ledc_timer_config_t ledc_timer = {
		.speed_mode = LEDC_LOW_SPEED_MODE,		// timer mode
		.duty_resolution = LEDC_TIMER_13_BIT,	// resolution of PWM duty
		.timer_num = LEDC_TIMER_1,				// timer index
		.freq_hz = 5000,						// frequency of PWM signal
		.clk_cfg = LEDC_AUTO_CLK,				// Auto select the source clock
    };
And here is the code that actually does the fading. As you see I'm following the guidance of the ledc_fade example and using xSemaphoreTake to take 3 semaphores, one for each color, and xSemaphoreGiveFromISR in the callback registered via ledc_cb_register to put them back. With the use of the semaphore there has never been an issue with changing the duty cycle before the previous one completes.

Code: Select all

void LEDTask::fadeLED(int r, int g, int b, int fadeTime){
	xSemaphoreTake(counting_sem, portMAX_DELAY);
	xSemaphoreTake(counting_sem, portMAX_DELAY);
	xSemaphoreTake(counting_sem, portMAX_DELAY);
	ledc_set_fade_with_time(this->ledc_channel[R_CHANNEL].speed_mode,this->ledc_channel[R_CHANNEL].channel, r, fadeTime);
	ledc_set_fade_with_time(this->ledc_channel[G_CHANNEL].speed_mode,this->ledc_channel[G_CHANNEL].channel, g, fadeTime);
	ledc_set_fade_with_time(this->ledc_channel[B_CHANNEL].speed_mode,this->ledc_channel[B_CHANNEL].channel, b, fadeTime);
	ledc_fade_start(this->ledc_channel[R_CHANNEL].speed_mode, this->ledc_channel[R_CHANNEL].channel, LEDC_FADE_NO_WAIT);
	ledc_fade_start(this->ledc_channel[G_CHANNEL].speed_mode, this->ledc_channel[G_CHANNEL].channel, LEDC_FADE_NO_WAIT);
	ledc_fade_start(this->ledc_channel[B_CHANNEL].speed_mode, this->ledc_channel[B_CHANNEL].channel, LEDC_FADE_NO_WAIT);
}
When the WDT is triggered the back trace shows Core 1 at:

Code: Select all

--- 0x40094bff: ledc_ll_set_duty_start at C:/Users/david/OneDrive/Documents/.espressif/v5.5.4/esp-idf/components/hal/esp32/include/hal/ledc_ll.h:487
Which is the empty while loop.

Code: Select all

static inline void ledc_ll_set_duty_start(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num)
{
    // wait until the last duty change took effect (duty_start bit will be self-cleared when duty update or fade is done)
    // this is necessary on ESP32 only, otherwise, internal logic might mess up (later targets with SOC_LEDC_SUPPORT_FADE_STOP allow to re-configure parameters while last update is still in progress)
    while (hw->channel_group[speed_mode].channel[channel_num].conf1.duty_start);
    hw->channel_group[speed_mode].channel[channel_num].conf1.duty_start = 1;
}
It seems that it is waiting for something to change with duty_start that isn't happening quickly enough and then Core 0 triggers the WTD (not sure why it is Core 0 that triggers it when the ledc code is running on Core 1). If I comment out the empty while call the code runs perfectly. The fact that I'm already waiting for the duty cycle to end before changing it seems to imply that possibly this is just a belt and suspenders situation, but having an empty while loop that waits for a condition to change before exiting is bad practice. It should at least have a vTaskDelay in the loop so it allows other tasks to run and not trigger a WTD event.

While debugging this I also learned that the fade length that I'm using seems to have some bearing on this. The LED fades in and out over the course of 4 seconds, 2 sec in and 2 sec out. If I shorten this to 200 ms, 100ms in and 100ms out it doesn't trigger the WDT. Perhaps that is because the WDT task length is 300ms by default.

All this being said, I have not yet been able to recreate my issue with the ledc_fade example. Though the example is very simple and runs on a single core. I will attempt to modify it to use a couple of tasks that simulate what my code is doing and see if I can recreate the error in an example simple enough to share.

Does anyone have any thoughts on the empty while loop? It wasn't there in 5.1.2 but does show up in 5.2.6 and later.

lehrian
Posts: 24
Joined: Wed May 12, 2021 3:07 am

Re: ledc causing WDT to reboot...

Postby lehrian » Mon Apr 06, 2026 5:28 pm

I have figured out a solution. If I put a 10ms delay (5ms didn't work) between taking the semaphores and calling the function:

Code: Select all

ledc_set_fade_with_time(this->ledc_channel[R_CHANNEL].speed_mode,this->ledc_channel[R_CHANNEL].channel, r, fadeTime);
It starts working again. As mentioned the exception occurs at:

Code: Select all

C:/.espressif/v5.5.4/esp-idf/components/hal/esp32/include/hal/ledc_ll.h:487
This method is an empty while loop who's criteria never becomes false. And while I wait for the semaphore to be given back via the callback on the fade function before I call ledc_set_fade_with_time that doesn't seem to be enough for this while loop.

Code: Select all

static inline void ledc_ll_set_duty_start(ledc_dev_t *hw, ledc_mode_t speed_mode, ledc_channel_t channel_num)
{
    // wait until the last duty change took effect (duty_start bit will be self-cleared when duty update or fade is done)
    // this is necessary on ESP32 only, otherwise, internal logic might mess up (later targets with SOC_LEDC_SUPPORT_FADE_STOP allow to re-configure parameters while last update is still in progress)
    while (hw->channel_group[speed_mode].channel[channel_num].conf1.duty_start);
    hw->channel_group[speed_mode].channel[channel_num].conf1.duty_start = 1;
}
Can anyone at Espressif enlighten me as to why this empty while loop exists and what it might be waiting for? I attempted to add a vTaskDelay to this while loop but because it is at the hal level this function is not available.

lehrian
Posts: 24
Joined: Wed May 12, 2021 3:07 am

Re: ledc causing WDT to reboot...

Postby lehrian » Tue Apr 07, 2026 2:06 am

So I figured out the issue. I was using the fade correctly but prior to starting the fade I was also turning all the LED off via

Code: Select all

	ledc_set_duty(ledc_channel[channel].speed_mode, ledc_channel[channel].channel, duty);
	ledc_update_duty(ledc_channel[channel].speed_mode, ledc_channel[channel].channel);
For some reason I have yet to determine this seems to work if my fade time is fairly short, but when it is extended beyond roughly 200ms setting the duty cycle while the ledc is still fading causes the WDT exception.

I do see in the code where it mentions that the ESP32 should not set the duty cycle during the fade cycle so I now know the reason why and can implement a proper fix.

Thanks for being my rubber ducky for these last couple of days :D ;)

Who is online

Users browsing this forum: ChatGPT-User and 8 guests