Skip to content

PWM (LEDc driver) doesn't work during light sleep #3534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
shraiwi opened this issue Dec 3, 2019 · 9 comments
Closed

PWM (LEDc driver) doesn't work during light sleep #3534

shraiwi opened this issue Dec 3, 2019 · 9 comments
Labels
Status: Stale Issue is stale stage (outdated/stuck)

Comments

@shraiwi
Copy link

shraiwi commented Dec 3, 2019

Hello!

I've been messing around with the ESP32 but I've run into an issue. I want to keep the PWM peripherals running when the ESP32 sleeps, but it seems that the LEDc module is paused during light and deep sleep. Is it possible to find a workaround where the LEDc module can run during light sleep?

#include <SPI.h>
#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI();
TFT_eSprite surface = TFT_eSprite(&tft);

#define FPS 15
#define BL_CHANNEL 1
#define POWERSYNC

uint32_t lt = 0;
uint32_t frameTime = 1000000 / FPS;
bool firstBoot = true;

void setup(void) {
  if (firstBoot) {
    ledcSetup(BL_CHANNEL, 5000, 8);
    ledcAttachPin(23, BL_CHANNEL);
    ledcWrite(BL_CHANNEL, 10);
    tft.init();
    tft.fillScreen(TFT_BLACK);
    surface.createSprite(70, 50);
    surface.fillSprite(TFT_BLACK);
    surface.setCursor(0, 0, 4);
    surface.setTextColor(TFT_WHITE, TFT_BLACK);
    surface.println("groovy!");
    firstBoot = false;
  }
}

void loop() {
  uint32_t dt = micros() - lt;
  lt = micros();
  uint32_t nextFrame = micros() + frameTime;
  surface.fillSprite(TFT_BLACK);
  surface.drawNumber(1e6/dt, 0, 0);
  surface.pushSprite(20, sin(millis()/500.0)*50+100);
#if defined(POWERSYNC)
  int32_t timeLeft = nextFrame - micros();
  if (timeLeft > 0) {
    esp_sleep_enable_timer_wakeup(timeLeft);
    esp_light_sleep_start(); // LEDs stop pwm-ing here
  }
#endif
}

I've found this on the ESP32 forums outlining a possible workaround

Thank you!

@shraiwi shraiwi changed the title LEDC driver doesn't work during light sleep PWM (LEDc driver) doesn't work during light sleep Dec 4, 2019
@stale
Copy link

stale bot commented Feb 2, 2020

[STALE_SET] This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions.

@stale stale bot added the Status: Stale Issue is stale stage (outdated/stuck) label Feb 2, 2020
@stale
Copy link

stale bot commented Feb 16, 2020

[STALE_DEL] This stale issue has been automatically closed. Thank you for your contributions.

@stale stale bot closed this as completed Feb 16, 2020
@raomin
Copy link

raomin commented Apr 17, 2023

I would be happy to know if you have found a solution @shraiwi?

@shraiwi
Copy link
Author

shraiwi commented Apr 17, 2023

In the current state, it looks like the LEDC won't work during light sleep. If you read the the ESP32 forums, it looks like there is a workaround but you'd likely have to implement that yourself, as it is not supported in the ESP32 Arduino Core.

@raomin
Copy link

raomin commented Apr 19, 2023

I did found the workaround, hoping for an easier way.

I got it working though.
Too bad arduino-esp32 do not let us choose the clk.cfg for the ledC implementation...

Anyway, this works:

#include <stdio.h>
#include <Arduino.h>
#include "freertos/task.h"
#include "driver/ledc.h"
#include "esp_err.h"
#include "esp_pm.h"
#include "esp_sleep.h"
#include "driver/uart.h"
#include "esp32/rom/uart.h"
#include "ulp_common.h"
#include "esp32/ulp.h"

#define LEDC_LS_TIMER LEDC_TIMER_0
#define LEDC_LS_MODE LEDC_LOW_SPEED_MODE
#define LEDC_LS_CH2_GPIO (4)
#define LEDC_LS_CH2_CHANNEL LEDC_CHANNEL_2

ledc_channel_config_t ledc_channel[1] = {
    {
        .gpio_num = LEDC_LS_CH2_GPIO,
        .speed_mode = LEDC_LS_MODE,
        .channel = LEDC_LS_CH2_CHANNEL,
        .timer_sel = LEDC_LS_TIMER,
        .duty = 0,
        .hpoint = 0,
    },
};

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

    ledc_timer_config_t ledc_timer = {
        .speed_mode = LEDC_LS_MODE,          // timer mode
        .duty_resolution = LEDC_TIMER_8_BIT, // resolution of PWM duty
        .timer_num = LEDC_LS_TIMER,          // timer index
        .freq_hz = 100,                      // frequency of PWM signal
        .clk_cfg = LEDC_USE_RTC8M_CLK,       // Force source clock to RTC8M
    };
    ledc_timer_config(&ledc_timer);
    ledc_channel_config(&ledc_channel[0]);
    printf("Frequency %u Hz\n", ledc_get_freq(LEDC_LS_MODE, LEDC_LS_TIMER));
}

void loop()
{

    // Fade up from 0 to 255
    for (int i = 0; i < 255; i++)
    {
        ledc_set_duty(ledc_channel[0].speed_mode, ledc_channel[0].channel, i);
        ledc_update_duty(ledc_channel[0].speed_mode, ledc_channel[0].channel);
        delay(10);
    }
    // stay at 100% for 5s
    delay(5000);

    // Set duty to 55/255 (21%)
    ledc_set_duty(ledc_channel[0].speed_mode, ledc_channel[0].channel, 55);
    ledc_update_duty(ledc_channel[0].speed_mode, ledc_channel[0].channel);

    printf("Sleep for 10s\n");
    esp_sleep_enable_timer_wakeup(10000000L);
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_AUTO);
    fflush(stdout);

    uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM);
    esp_light_sleep_start();
}

@zackees
Copy link

zackees commented May 16, 2023

I tried to compile this in platform = espressif32@=6..2.0 and it doesn't compile

@raomin
Copy link

raomin commented May 16, 2023

What is your error? For me it compiles fine with platform = [email protected]

@zackees
Copy link

zackees commented May 17, 2023

Hm... Funny. I just tried it again and it works fine. So operator error. These files are missing though:

// #include "ulp_common.h"
// #include "esp32/ulp.h"

@zackees
Copy link

zackees commented Feb 27, 2024

I got it to work. The esp32 documentation is missing the following key requirements:

  • ESP_ERROR_CHECK(esp_sleep_pd_config(ESP_PD_DOMAIN_RTC8M, ESP_PD_OPTION_ON));
  • ESP_ERROR_CHECK(gpio_sleep_sel_dis(PIN_LEDC)); // Needed for light sleep.

The following code is confirmed working on the v5.1 and v4.4 toolchain. I've named this thing psuedo i2s because reasons:

#include <math.h>
#include <Arduino.h>
#include <stdio.h>

#include "driver/ledc.h"
#include "esp_err.h"
#include "driver/ledc.h"

#include "defs.h"
#include "pseudo_i2s.h"

#include "esp32-hal-ledc.h"

#define LEDC_TIMER LEDC_TIMER_0
#define LEDC_MODE LEDC_LOW_SPEED_MODE
#define LEDC_CHANNEL LEDC_CHANNEL_0
#define LEDC_DUTY_RES LEDC_TIMER_1_BIT // Set duty resolution to 13 bits
#define LEDC_FREQUENCY (1024 * 1024)  // 1 mhz clock.
#define PIN_PSUEDO_I2S GPIO_NUM_6

// #define LEDC_CLOCK LEDC_USE_RC_FAST_CLK  // still clocks during light sleep.
#define LEDC_CLOCK LEDC_USE_RTC8M_CLK  // still clocks during light sleep.

namespace
{
enum {
  // use bith manipuation against the LEDC_TIMER_8_BIT to come to a max duty.
  // For example, if LEDC_TIMER_8_BIT is 8, then the max duty is 255.
  kMaxDuty = (1 << LEDC_DUTY_RES) - 1,
};
ledc_timer_config_t ledc_timer = {
    //.duty_resolution  = LEDC_TIMER_13_BIT, // resolution of PWM duty
    .speed_mode = LEDC_MODE,
    .duty_resolution = LEDC_DUTY_RES,
    .timer_num = LEDC_TIMER,
    .freq_hz = LEDC_FREQUENCY, // Set output frequency
    .clk_cfg = LEDC_CLOCK
  };

// Prepare and then apply the LEDC PWM channel configuration
ledc_channel_config_t ledc_channel = {
    .gpio_num = PIN_PSUEDO_I2S,
    .speed_mode = LEDC_MODE,
    .channel = LEDC_CHANNEL,
    .intr_type = LEDC_INTR_DISABLE,
    .timer_sel = LEDC_TIMER,
    .duty = 0,
    .hpoint = 0,
    .flags = {
      .output_invert = 1,
    }
};

} // namespace

void pseudo_i2s_start()
{
//rtc_clk_slow_freq_set(RTC_SLOW_FREQ_8MD256);
std::cout << "pseudo_i2s_start\n";
std::flush(std::cout);
ESP_ERROR_CHECK(esp_sleep_pd_config(ESP_PD_DOMAIN_RTC8M, ESP_PD_OPTION_ON));
ESP_ERROR_CHECK(gpio_sleep_sel_dis(PIN_PSUEDO_I2S)); // Needed for light sleep.
// Prepare and then apply the LEDC PWM timer configuration
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 1));
std::cout << "pseudo_i2s_start done\n";
std::flush(std::cout);
ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL));
std::cout << "pseudo_i2s_start done\n";
std::flush(std::cout);

}

void pseudo_i2s_stop()
{
ESP_ERROR_CHECK(ledc_stop(LEDC_MODE, LEDC_CHANNEL, 0));
pinMode(PIN_PSUEDO_I2S, INPUT);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Stale Issue is stale stage (outdated/stuck)
Projects
None yet
Development

No branches or pull requests

3 participants