Updating frequency at PWM pin

Hi! to all, I want to blink a LED using ESP32 PWM pin at frequency 5 Hz (which can be updated in loop section when required) and duty cycle 50%. PWM generation part of my program is here-


#include <Arduino.h>
#include <driver/ledc.h>

int freq = 0; 
int channel = 0; 
int resolution = 8;

const int pwmChannel = 0; // LEDC channel for PWM
const int pwmPin = 26; // PWM output pin
const int sig_duty = 1; // cyclacity of 1/2 or 50% 1 is 50% or 1/2^1


void setup() {
   
   ledcSetup(channel, freq, resolution);  
   ledcAttachPin(pwmPin, channel);
    
    // Configure LEDC PWM
  ledcSetup(pwmChannel, 0, 8); // LEDC channel 0, frequency 0Hz, 8-bit resolution
  ledcAttachPin(pwmPin, pwmChannel); // Attach LEDC channel to pin
}

void loop() {
    // Update LEDC PWM frequency
  ledcWriteTone(pwmChannel, 5);

    }
  

This code is getting compile but LED connected on pin 26 is not blinking at all. Please help to find out the problem with code. Thanks in advance for any help.

This blinks:

// Forum: https://forum.arduino.cc/t/updating-frequency-at-pwm-pin/1247368
// This Wokwi project: https://wokwi.com/projects/395088728070574081

const int pwmChannel = 1; 
const int pwmPin = 26;

void setup() 
{
   pinMode(pwmPin, OUTPUT);

   // Is the frequency used ?
   // Can the frequency be anything ?
   ledcSetup(pwmChannel, 32, 8);
   ledcAttachPin(pwmPin, pwmChannel);
}

void loop() 
{
  ledcWriteTone(pwmChannel, 5);
  delay(1000);
}

Try it in Wokwi simulation:

I did not try to find why your sketch does not work, and this one does. I used normal code and removed everything that was in the way. I think that the delay(1000); in the loop() makes it work.

Thanks for your reply and solution but my code is a part of a long program and blocking code will cause hindrance in execution. Also, I have to take trials by changing duty cycle by hard code.

Then you have to find another way for the ledcWriteTone(). If the frequency of the led is changed only once every few seconds, then only call ledcWriteTone() when the frequency is changed.

If the ledcWriteTone() was a perfect function, then it would allow to be called that often and it would smoothly transit from one frequency to another. But it is not perfect. I think that the timer for the PWM is reset every time the function is called.

Under the Arduino layer is a FreeRTOS layer. You could create a task to blink the led.

With millis() it is easy to blink the led 5 times per second. That is the most simple solution.

OK, Thanks. I am changing frequency using HMI. Every time new frequency is received from HMI it get updated at pin26 but duty cycle is remaining same even on changing value in const int sig_duty = 1; // cyclacity of 1/2 or 50% 1 is 50% or 1/2^1. Can you please help to change duty cycle in hard code.

To avail unblocking feature, you can create sketch using FreeRTOS for concurrent exeecution of PWM task along with other tasks of your project.

The ledcWriteTone() has a duty cycle of 50% : https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/ledc.html#ledcwritetone
Use the ledc in a normal way, to use it as a PWM with a duty cycle.

Do you want to make a dimmed blinking led with 50% duty cycle ? Or a blinking led that is either fully on or fully off with variable duty cycle ? Or a dimmed blinking led that blinks with a variable duty cycle ?

There are many tutorials and examples and libraries for ledc. Even the ledc itself has functions that are rarely used, such as a automatic fade. There is even a "ledc_update_duty()" function, I never heard of that before.

Thanks for reply. I will choose final duty cycle (e.g. 10% or 15% which gives better results) after trials and will hard code the same and this will not be changed during running the code. Frequency changes when ever data is received from HMI. I am using sig_duty = 1; // cyclacity of 1/2 or 50% 1 is 50% or 1/2^1 as shown in the code above but that is not working.

Thanks for reply. Can you please suggest example code.

Check if the following solution serves your purpose while FressRTOS based multi-tasking version is being prepared.

// the number of the LED pin
const int ledPin = 26;  // 26 corresponds to GPIO2

// setting PWM properties
const int freq = 5; //Hz
const int ledChannel = 0;
const int resolution = 8;

void setup()
{
  Serial.begin(115200);
  Serial.println("Hello");
  // configure LED PWM functionalitites
  ledcSetup(ledChannel, freq, resolution);

  // attach the channel to the GPIO to be controlled
  ledcAttachPin(ledPin, ledChannel);
  
  byte dutyCycle = 128;
  ledcWrite(ledChannel, dutyCycle);//LED blinks at 5 Hz with 50% dutyCycle
}

void loop()
{

}
1 Like

Thanks for reply. Using this code LED blinks at freq = 5 and above. I can change duty cycle by replacing 128 in byte dutyCycle = 128;
Can you please suggest how to update freq while code is running if freq = a decimal number received from HMI. Also, can you please help to visualize above code using Wokwi serial plotter.

As a HMI device, which one of the following yo want to use?
A potentiometer
Input Box of Serial Monitor

I do not practice simulation, like Wokwi.

OK thanks.
On using ledc_set_freq() funtion to update frequency compiler is throwing error as - Compilation error: invalid conversion from 'int' to 'ledc_mode_t' [-fpermissive]

My code is as under-

// Event Occurs when response comes from HMI
void onHMIEvent(String address, int lastByte, String message, String response){  
  Serial.println("OnEvent : [ A : " + address + " | D : "+ String(lastByte, HEX)+ " | M : "+message+" | R : "+response+ " ]"); 
  String myString = ""+ String(lastByte, DEC)+ "";
  int myInt = myString.toInt();
  Serial.println(myInt);
  ledc_set_freq(myInt)
   ledcWrite(Channel, 10);
   Serial.print(" Frequency: ");  
  Serial.println(myInt);  // Print current frequency

Please help to find out the reason of error.
Thanks in advance.

You should receive a value for the frequency of the PWM signal from the InputBox of Serial Monitor and then pass it to the following function:

 ledcSetup(ledChannel, freq, resolution);

First of all thanks for your help.
I have re-arranged my code lines as suggested by you. Now code is as

 void onHMIEvent(String address, int lastByte, String message, String response){  
  Serial.println("OnEvent : [ A : " + address + " | D : "+ String(lastByte, HEX)+ " | M : "+message+" | R : "+response+ " ]"); 
  String myString = ""+ String(lastByte, DEC)+ "";
  int myInt = myString.toInt();
  Serial.println(myInt);
  ledcSetup(Channel, myInt, resolution);  
    ledcAttachPin(26, Channel); 
     ledcWrite(Channel, 10);
   Serial.print(" Frequency: ");  
  Serial.println(myInt);  // Print current frequency 
  delay(10); // Adjust delay as needed for responsiveness

Now facing a new problem, I am getting current frequency printed in serial monitor on increasing/decreasing it from HMI. But in actual, on increasing frequency it is increasing on PWM pin but not decreasing on decreasing, although serial monitor prints decreased value. It means that frequency is changing in upward direction but not in downward direction.
Also, serial monitor is printing an error.

OnEvent : [ A : 52005200 | D : 0 | M : | R : 5a a5 83 52 00 01 00 05 5a a5 83 52 00 01 00 00 ]
0
E (57427) ledc: freq_hz=0 duty_resolution=8
Frequency: 0
Please help to resolve.

The following steps could be carried out to dynamically change the frequency of the PWM signal from the InputBox of Serial Monitor (Fig-1):
1. Upload the following sketch into ESP32 Dev Board.

// the number of the LED pin
const int ledPin = 26;  // 26 corresponds to GPIO2

// setting PWM properties
const int freq = 5; //Hz
const int ledChannel = 0;
const int resolution = 8;
char myData[10];

void setup()
{
  Serial.begin(115200);
  Serial.println("Hello");
  // configure LED PWM functionalitites
  ledcSetup(ledChannel, freq, resolution);

  // attach the channel to the GPIO to be controlled
  ledcAttachPin(ledPin, ledChannel);
  byte dutyCycle = 128;
  ledcWrite(ledChannel, dutyCycle);//50% dutyCycle;
}

void loop()
{
  byte n = Serial.available();
  if (n != 0)
  {
    byte m = Serial.readBytesUntil('\n', myData, sizeof myData - 1);
    myData[m] = '\0'; //null charcater
    byte myFreq = atoi(myData);
    ledcSetup(ledChannel, myFreq, resolution);
  }
}

2. Check that LED is blinking at 5 Hz with 50% duty cycle.
3. Open Serial Monitor (SM) with Newline option and Bd = 115200.
4. Enter 10 in the InputBox and click on the Send Button of SM.
5. Check that LED blinks at 10 Hz.
6. If you wish, you can also change the duty cycle from the InputBox of SM.


Figure-1:

You may consider changing the Title to: Updating frequency at PWM pin of ESP32

Thanks. Now I can increase or decrease frequency using your code of declaration part. But frequency is not going below 5. On sending a digit below 5 or 0 previous value remains as it is and serial monitor shows error as- E (37639) ledc: freq_hz=0 duty_resolution=8.

I also faced this frequency limitation. The reason/remedy is not known to me.

This seems like what you have been looking for. Doesn't need a PWM pin. Non-blocking.. Etc etc.

OK. Thanks, I will write logic to generate 0 Hz at frequency below 5 till some solution found.