espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.37k stars 7.21k forks source link

LEDC output unreliable when quickly calling ledc_stop, ledc_channel_config, ledc_set_duty, and ledc_update_duty (IDFGH-5176) #6952

Open andycarle opened 3 years ago

andycarle commented 3 years ago

Environment

Problem Description

The LEDC PWM module produces unreliable output when quickly calling ledc_stop, ledc_channel_config, ledc_set_duty, and ledc_update_duty in sequence. When these ESP-IDF functions are called quickly in this order, the LEDC PWM module sometimes outputs a single short low pulse followed by a steady logical 1 instead of the duty cycle set by ledc_set_duty. If a brief delay is introducted between ledc_stop and the remaining LEDC API calls, it works as expected.

Note: this issue only seems to occur with the ESP32 configured for 240 MHz

Consider this example, modified from ledc_example.main.c.

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include "esp_err.h"

#define TEST_DELAY false

static const ledc_timer_config_t ledc_timer = {
    .duty_resolution = LEDC_TIMER_10_BIT,
    .freq_hz         = 10000,
    .speed_mode      = LEDC_HIGH_SPEED_MODE,
    .timer_num       = LEDC_TIMER_0,
    .clk_cfg         = LEDC_AUTO_CLK,
};

void app_main(void)
{
    int duties[3] = {256, 512, 768};
    int onDuty = 0;

    ledc_timer_config(&ledc_timer);

    while (1) {    
        ledc_channel_config_t ledc_channel = {
            .channel    = 0,
            .duty       = 1024,
            .gpio_num   = 27,
            .speed_mode = LEDC_HIGH_SPEED_MODE,
            .hpoint     = 0,
            .timer_sel  = LEDC_TIMER_0,
            .intr_type  = 0
        };

        ledc_channel_config(&ledc_channel);

        ledc_set_duty(LEDC_HIGH_SPEED_MODE, 0, duties[onDuty]);
        ledc_update_duty(LEDC_HIGH_SPEED_MODE, 0);

        onDuty++;
        onDuty %= 3;

        vTaskDelay(100 / portTICK_PERIOD_MS);
        ledc_stop(LEDC_HIGH_SPEED_MODE, 0, 1);
    #if TEST_DELAY
        vTaskDelay(10 / portTICK_PERIOD_MS);
    #endif
    }
}

If TEST_DELAY is defined to true this example works as expected and this correct signal is output on GPIO27:

image

If TEST_DELAY is defined to false this incorrect signal is output on GPIO27:

image

Are there known restrictions around how quickly you can stop and re-start the LEDC module on a pin? Or perhaps restrictions on how long to wait between a call to ledc_channel_config and ledc_set_duty? If so, where can I find that guidance?

Expected Behavior

GPIO27 outputs a nearly constant PWM signal at 10 kHz that cycles between three PWM duties (25%, 50%, 75%) with small gaps between each duty cycle phase.

Actual Behavior

GPIO27 outputs the correct PWM signal for 100 ms and then starts putting out one low pulse and then solid logic level 1 on each subsequent PWM configuration when quickly stopped and restarted. It works as expected if an artificial delay is introduced with vTaskDelay.

Steps to reproduce

  1. Replace the source code of ledc_example.main.c with the code snippet above.
  2. Run idf.py menuconfig and configure the ESP32 for 240 MHz (Component config -> ESP32-Specific -> CPU Frequency -> 240 MHz and save.
  3. idf.py build flash monitor
  4. Observe the output of GPIO27 using a logic analyzer / oscilloscope or an LED.

Other items if possible

sdkconfig and elf.zip

Thank you for your assistance,  - Andy

HermenB commented 1 year ago

Thank you Andy for reporting this bug! Although it seems you never got a response, your report helped me to get my signal generator behave bug free. (I just dialed down the cpu-freq to 160 MHZ, and it works flawlessly now!)

But looking closer at your code, you don't want ledc_channel_config(&ledc_channel); in the while-loop. Better to configure the channel once (ie before the while loop) and only change duty repeatedly.

And as for the slight delay, the cpu runs @240MHz, the LEDC_HIGH_SPEED_MODE is 80MHz, so you need to give it a few clock-cycles to execute the instructions, and apparently 1ms (240cycles) is enough. Have you tried less?

Anyway, thanks again, regards Hermen

ForgetCSX commented 1 week ago

@HermenB hi HermenB I have a similar requirement. I need to constantly change the input and output modes of io. When io is output, I need to configure it as pwm output, but when changing the io output mode, I do not see the deinitialization of the LEDC peripheral. Interfaces, LEDC timers and channels do not need to be de-initialized. You only need to stop LEDC and then you can directly initialize other peripherals connected to this IO?