PlatformIO Community

ESP32 Change PWM Duty Cycle in Interrupt possible?

Hello, I’m using platfomio on vscode with Arduino Framework and try to change the duty cycle of a pwm channel in an Interrupt. Is there any possibility to do that? The ledcWrite function that is used typically to change a duty Cycle does not work because there is no IRAM_ATTR in the ledcWrite function and the functions below that.

The Project Compiles fine but the PWM doesn’t change in Interrupt.

Basic Example would look something like this:
main.cpp

#include <Arduino.h>
#define PWM_PIN 16
hw_timer_t * timer0 = NULL;
portMUX_TYPE timerMux0 = portMUX_INITIALIZER_UNLOCKED;
volatile uint32_t duty = 0;

void IRAM_ATTR TimerHandler0(void)
{
   ledcWrite(1, duty++);
}

void setup(){
   Serial.begin(115200);
   timer0 = timerBegin(0, 80, true); // 12,5 ns * 80 = 1000ns = 1us
   timerAttachInterrupt(timer0, &TimerHandler0, false); 
   timerAlarmWrite(timer0, 5000, true); // 1us * 5000 = 5ms
   timerAlarmEnable(timer0);
   pinMode(PWM_PIN, OUTPUT);
   ledcSetup(1, 4000, 8);
   ledcAttachPin(PWM_PIN, 1);
   ledcWrite(1, 0);
}
void loop()
{
}

The only thing I can think of is that you copy the implementation of ledcWrite (https://github.com/espressif/arduino-esp32/blob/46d5afb17fb91965632dc5fef237117e1fe947fc/cores/esp32/esp32-hal-ledc.c#L197-L223 for the 1.0.6 core), remove the mutex locks and mark it as IRAM. The function does only direct peripheral writes to LEDC as far as I can see, so it should work fine.

The alternative would be to as discussed in the previous topic to turn off the IRAM allocation flags in the timer implementation to allow the function to be run from flash (or mixed IRAM and flash for speed). But that disables the interrupt during flash operations and thus your application might not respond to critical input.

It works as you implied here is the new function myledcWrite that repaces ledcWrite:

void IRAM_ATTR myledcWrite(uint8_t chan, uint32_t duty)
{
    if (chan > 15)
    {
        return;
    }
    uint8_t group = (chan / 8), channel = (chan % 8);
    LEDC_CHAN(group, channel).duty.duty = duty << 4; //25 bit (21.4)
    if (duty)
    {
        LEDC_CHAN(group, channel).conf0.sig_out_en = 1; //This is the output enable control bit for channel
        LEDC_CHAN(group, channel).conf1.duty_start = 1; //When duty_num duty_cycle and duty_scale has been configured. these register won't take effect until set duty_start. this bit is automatically cleared by hardware.
        if (group)
        {
            LEDC_CHAN(group, channel).conf0.low_speed_update = 1;
        }
        else
        {
            LEDC_CHAN(group, channel).conf0.clk_en = 1;
        }
    }
    else
    {
        LEDC_CHAN(group, channel).conf0.sig_out_en = 0; //This is the output enable control bit for channel
        LEDC_CHAN(group, channel).conf1.duty_start = 0; //When duty_num duty_cycle and duty_scale has been configured. these register won't take effect until set duty_start. this bit is automatically cleared by hardware.
        if (group)
        {
            LEDC_CHAN(group, channel).conf0.low_speed_update = 1;
        }
        else
        {
            LEDC_CHAN(group, channel).conf0.clk_en = 0;
        }
    }
}