LED controller PWM timer compare values

User avatar
dg9ngf
Posts: 65
Joined: Mon Sep 16, 2019 6:49 pm
Location: Germany
Contact:

Re: LED controller PWM timer compare values

Postby dg9ngf » Tue Sep 13, 2022 5:42 pm

Thanks for the explanations. Somehow I can't get this to work. Whatever I do, either the inverted channels or now all channels flash up for an instant when booting. I could clearly see that only the inverted channels had flashed up before I applied your suggestions now. After trying the generic and then the LED-controller-integrated methods, and reverting everything, now all channels flash up at boot, apparently one after the other (but really quick). I don't know why. I even unplugged the power to reset everything.

After boot and initialisation (and the flashing), everything works normally. I set the LED channels according to user input, with every second channel set to PWM_MAXDUTY minus the actual value. The LED brightness is as expected. It's only the initialisation that is jerky.

Here's a part of my current code, for the initialisation:

Code: Select all

	// Configure LED timers
	ledc_timer_config_t ledc_timer = {
		.duty_resolution = PWM_RESOLUTION,
		.freq_hz = 2500,
		.speed_mode = LEDC_HIGH_SPEED_MODE,
		.timer_num = LEDC_TIMER_0,
		.clk_cfg = LEDC_AUTO_CLK,
	};
	ledc_timer_config(&ledc_timer);

	// Configure LED channels
	gpio_reset_pin(LED0_GPIO);
	gpio_set_level(LED0_GPIO, 0);
	gpio_set_direction(LED0_GPIO, GPIO_MODE_OUTPUT);

	ledc_channel_config_t ledc_channel = {
		.channel = LEDC_CHANNEL_0,
		.gpio_num = LED0_GPIO,
		.duty = 0,
		.flags.output_invert = 0,
		.speed_mode = LEDC_HIGH_SPEED_MODE,
		.hpoint = 0,
		.timer_sel = LEDC_TIMER_0};
	ledc_channel_config(&ledc_channel);

	// Every second channel is inverted (2, 4, 6, 8) to distribute the power load over time
	if (CHANNELS >= 2)
	{
		gpio_reset_pin(LED1_GPIO);
		gpio_set_level(LED1_GPIO, 0);
		gpio_set_direction(LED1_GPIO, GPIO_MODE_OUTPUT);

		ledc_channel.channel = LEDC_CHANNEL_1;
		ledc_channel.gpio_num = LED1_GPIO;
		ledc_channel.duty = PWM_MAXDUTY;
		// new way:
		ledc_channel.flags.output_invert = 1;
		ledc_channel_config(&ledc_channel);
		// old way:
		//GPIO.func_out_sel_cfg[LED1_GPIO].inv_sel = 1;

		// generic way:
		//gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[LED1_GPIO], PIN_FUNC_GPIO);
		//gpio_set_direction(LED1_GPIO, GPIO_MODE_OUTPUT);
		//esp_rom_gpio_connect_out_signal(LED1_GPIO, LEDC_HS_SIG_OUT1_IDX, true, false);
	}
	// Repeated for LED2...7

User avatar
dg9ngf
Posts: 65
Joined: Mon Sep 16, 2019 6:49 pm
Location: Germany
Contact:

Re: LED controller PWM timer compare values

Postby dg9ngf » Tue Sep 13, 2022 9:45 pm

Okay, I think I found the problem. This works for now:

Code: Select all

	// Configure LED timers
	ledc_timer_config_t ledc_timer = {
		.duty_resolution = LEDC_TIMER_14_BIT,
		.freq_hz = 2500,
		.speed_mode = LEDC_HIGH_SPEED_MODE,
		.timer_num = LEDC_TIMER_0,
		.clk_cfg = LEDC_APB_CLK,
	};
	ledc_timer_config(&ledc_timer);

	ledc_channel_config_t ledc_channel = {
		.channel = LEDC_CHANNEL_0,
		.gpio_num = LED0_GPIO,
		.duty = 0,
		.speed_mode = LEDC_HIGH_SPEED_MODE,
		.hpoint = 0,
		.timer_sel = LEDC_TIMER_0};
	ledc_channel_config(&ledc_channel);

	// Every second channel is inverted (2, 4, 6, 8) to distribute the power load over time
	if (CHANNELS >= 2)
	{
		ledc_channel.channel = LEDC_CHANNEL_1;
		ledc_channel.gpio_num = LED1_GPIO;
		ledc_channel.duty = PWM_MAXDUTY;
		ledc_channel_config(&ledc_channel);
		gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[LED1_GPIO], PIN_FUNC_GPIO);
		gpio_set_direction(LED1_GPIO, GPIO_MODE_OUTPUT);
	}
	// ...

	// This wait is the critical thing:
	vTaskDelay(50 / portTICK_PERIOD_MS);
	if (CHANNELS >= 2)
	{
		esp_rom_gpio_connect_out_signal(LED1_GPIO, LEDC_HS_SIG_OUT1_IDX, true, false);
	}
	// ...
The wait is important. 1 ms is too short, 10 ms is unstable, 20 ms seems okay, 50 ms or more is definitely enough. Waiting once for all inverted channels is also okay.

User avatar
dg9ngf
Posts: 65
Joined: Mon Sep 16, 2019 6:49 pm
Location: Germany
Contact:

Re: LED controller PWM timer compare values

Postby dg9ngf » Sun May 11, 2025 11:31 am

A quick update after the flashing had returned after a compiler update.

Two delays are needed to avoid different lengths of flashes. The sequence now is:

1. ledc_timer_config
2. ledc_channel_config (always with .duty = 0)
3. For inverted channels: Disconnect the GPIO pin with gpio_hal_iomux_func_sel(PIN_FUNC_GPIO) and gpio_set_direction(GPIO_MODE_OUTPUT)
4. Repeat for all channels
5. Wait 20 ms
6. For inverted channels: Set PWM duty to PWM_MAXDUTY with ledc_set_duty and ledc_update_duty
7. Wait 20 ms
8. For inverted channels: Reconnect GPIO to timer with esp_rom_gpio_connect_out_signal and invert option

Extracted code (only first 2 channels):

Code: Select all

// Pins
#define LED0_GPIO 32
#define LED1_GPIO 33
// Channels count
#define CHANNELS 8
// PWM frequency range
#define MIN_LED_FREQ 5   // Hz (minimum supported by LEDC with current settings)
#define MAX_LED_FREQ 2500   // Hz (for 14 bit resolution)
// Invert the second channel of a dual-white setup to better spread the on-time over both channels
// and reduce peak current for the power supply
#define LED_INVERT_CH

#define PWM_RESOLUTION LEDC_TIMER_14_BIT
#define PWM_MAXDUTY 16384   // 2^14

#define initLedChannel(ledc_channel, ch, gpio) \
{ \
	ledc_channel.channel = ch; \
	ledc_channel.gpio_num = gpio; \
	ledc_channel_config(&ledc_channel); \
}

#define disconnectGpio(gpio) \
{ \
	gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO); \
	gpio_set_direction(gpio, GPIO_MODE_OUTPUT); \
}

#define setMaxDuty(ch) \
{ \
	ledc_set_duty(LEDC_HIGH_SPEED_MODE, ch, PWM_MAXDUTY); \
	ledc_update_duty(LEDC_HIGH_SPEED_MODE, ch); \
}

// Initializes the use of the LED PWM functions by setting up the TCA timer.
void initLed()
{
	// Configure LED timers
	ledc_timer_config_t ledc_timer = {
		.duty_resolution = PWM_RESOLUTION,
		.freq_hz = MAX_LED_FREQ,
		.speed_mode = LEDC_HIGH_SPEED_MODE,
		.timer_num = LEDC_TIMER_0,
		.clk_cfg = LEDC_APB_CLK
	};
	ledc_timer_config(&ledc_timer);

	// Configure LED channels
	ledc_channel_config_t ledc_channel = {
		.duty = 0,
		.speed_mode = LEDC_HIGH_SPEED_MODE,
		.hpoint = 0,
		.timer_sel = LEDC_TIMER_0
	};

	initLedChannel(ledc_channel, LEDC_CHANNEL_0, LED0_GPIO);

#if CHANNELS >= 2
	initLedChannel(ledc_channel, LEDC_CHANNEL_1, LED1_GPIO);
#ifdef LED_INVERT_CH
	// Every second channel is inverted (2, 4, 6, 8) to distribute the power load over time.
	// Disconnect the timer from the GPIO pin again while setting up the inversion to prevent
	// the inverted channels flashing up briefly at boot. They will be reconnected below after
	// waiting the time the hardware needs to apply the configuration.
	disconnectGpio(LED1_GPIO);
#endif
#endif

#ifdef LED_INVERT_CH
	// Wait until pins were disconnected (could cause 10 ms flash of output otherwise)
	vTaskDelay(pdMS_TO_TICKS(20));

#if CHANNELS >= 2
	setMaxDuty(LEDC_CHANNEL_1);
#endif

	// Wait until PWM duty was updated (could cause 10 ms flash of output otherwise)
	vTaskDelay(pdMS_TO_TICKS(20));

#if CHANNELS >= 2
	esp_rom_gpio_connect_out_signal(LED1_GPIO, LEDC_HS_SIG_OUT1_IDX, true, false);
#endif
#endif
}

Who is online

Users browsing this forum: Baidu [Spider], Google [Bot] and 2 guests