Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
daniel23
Published © LGPL

ESP32 Generate two 180° phase shifted PWMs

How-to generate two complementary PWMs phase shifted by 180° with the MCPWM or with the LEDC device of the ESP32

IntermediateProtip1 hour2,816
ESP32 Generate two 180° phase shifted PWMs

Things used in this project

Hardware components

ESP32-WROOM-32 ESP32 Dev Kit V1
×1
Single Turn Potentiometer- 100k ohms
Single Turn Potentiometer- 100k ohms
×2
3 mm LED: Green
3 mm LED: Green
×1
3 mm LED: Red
3 mm LED: Red
×1
Resistor 1k ohm
Resistor 1k ohm
×2
Breadboard (generic)
Breadboard (generic)
×1
Jumper wires (generic)
Jumper wires (generic)
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

PWM_schema.png

Code

Test_MCPWM

Arduino
Example of generating 180° phase-shifted and non-overlapping PWM with the MCPWM device
//         *********************************************************************
//         *                       ESP32 / ESP-WROOM-32                        *
//         *      Complementary and symmetrical PWM - 180 phase shifted       *
//         *   using the MCPWM module (Motor Control Pulse-Width Modulation)   *
//         *   Daniel Engel                                      09/10/2023    *
//         *********************************************************************
//
/* 
This is a demonstration of complementary symmetrical PWM with an ESP32. I wrote it 
to optimally use color-tunable LEDs with a voltage and current limited power supply.

A PWM wave is generated where each output can run from 0 to maximum time in opposite
manner (when one grows, the other gets smaller). TIMER 0 is used counting up and 
down so that two centered symmetrical PWM waveforms are generated. The second GPIO
pin is inverted to create the 180 phase shift. This is done in the setup with the
line: "GPIO.func_out_sel_cfg[LED_3000K].inv_sel = 1;"

The calculation of the PWM parameters is done in such a way that there is no overlap
between the high states of the two output pins.

References from which I drew inspiration:

https://forum.arduino.cc/t/esp32-mcpwm/608899
https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/api-reference/peripherals/mcpwm.html
https://github.com/ul-gh/esp32_ps_pwm/tree/master

Boards I had installed in arduino IDE (maybe only "espressif/arduino-esp32" is needed):
		=> espressif/arduino-esp32 v2.0.12		(https://github.com/espressif/arduino-esp32)
		=> Arduino ESP32 Boards by Arduino 2.0.12
  Successfully compiled for "ESP32 DEVKIT V1 DOIT" board.
*/

#include "driver/mcpwm.h"  // "arduino-esp32-master" v2.0.14   (https://github.com/espressif/arduino-esp32/)

#define LED_5700K 13        // LED_5700K pin
#define LED_3000K 12        // LED_3000K pin  (this output pin has to be inverted in setup)
int frequency = 12000;  // chosen to have a nice display on the oscilloscope... 
// the actual frequency is half this value (the counter counts up then down)
mcpwm_config_t pwm_config;  // initialize "pwm_config" structure

const int colorpotPin = 34;
const int brightpotPin = 35;
int colorPotValue = 0;
int brightPotValue = 0;
int colorPotValueMap = 0;
int brightPotValueMap = 0;  // percentage of the sum of the high states LED_5700K and LED_3000K
int valPwm_3000K = 0;       // PWM value for the 3000K LED
int valPwm_5700K = 0;       // PWM value for the 5700K LED

void setup() {
  Serial.begin(115200);
  Serial.setTimeout(500);
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, LED_5700K);  // initializes gpio "LED_5700K" for MCPWM
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, LED_3000K);  // initializes gpio "LED_3000K" for MCPWM
  GPIO.func_out_sel_cfg[LED_3000K].inv_sel = 1;       // <= this line inverts the corresponding output
  pwm_config.frequency = frequency;
  pwm_config.cmpr_a = 0;            // Duty cycle of PWMxA
  pwm_config.cmpr_b = (100);        // Note: the duty cycle of PWMxB is inverted (from where "100-x")
  pwm_config.counter_mode = MCPWM_UP_DOWN_COUNTER;       // creates symetrical vaweforms
  pwm_config.duty_mode = MCPWM_DUTY_MODE_0;              //
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);  // Configure PWM0A & PWM0B with settings
}

void loop() {
  readInputsValues();
  valPwm_5700K = (brightPotValueMap * colorPotValueMap) / 100;
  valPwm_3000K = brightPotValueMap - valPwm_5700K;
  pwm_config.cmpr_a = valPwm_3000K;          // Duty cycle of PWMxA (HIGH level)
  pwm_config.cmpr_b = (100 - valPwm_5700K);  // The duty cycle of PWMxB is inverted (100-x)
  updatePWM();
  // printValues(); // uncomment if you want display on the serial console...
  delay(10);
}

void readInputsValues() {
  colorPotValue = analogRead(colorpotPin);
  colorPotValueMap = map(colorPotValue, 0, 4095, 0, 100);
  brightPotValue = analogRead(brightpotPin);
  brightPotValueMap = map(brightPotValue, 0, 4095, 0, 100);
}

void updatePWM() {
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);  // Configure PWM0A & PWM0B with settings
}

void printValues() {
  Serial.print("  color : ");
  Serial.print(colorPotValueMap);
  Serial.print("  bright : ");
  Serial.print(brightPotValueMap);

  Serial.print("  |||  LED_5700K : ");
  Serial.print(valPwm_5700K);
  Serial.print("  LED_3000K : ");
  Serial.println(valPwm_3000K);
  delay(500);
}

TEST_LEDC_pwm

Arduino
Example of generating 180° phase-shifted and non-overlapping PWM with the LEDC device
//         ****************************************************
//         *              ESP32 / ESP-WROOM-32                *
//         *         Two PWM - 180 phase shifted             *
//         *   using the LEDC peripheral  (LED Control)       *
//         *   Daniel Engel                   14/10/2023      *
//         ****************************************************
//
/* 
This demonstrates the use of the LEDC device to create two PWMs 180 out of phase.
I wrote it as an alternative to the MCPWM to optimally use color-tunable LEDs 
with a voltage and current limited power supply.

Two potentiometers provide an analog voltage between 0 and 3.3V. The first
on DIO34 allows to change the color (cyclical ratio between the two PWMs).
The second on DIO35 allows to change the total brightness.

The calculations are a little more complicated than with the MCPWM device because
we also have to calculate the startpoint (phase) of the second PWM signal.

Boards I had installed in arduino IDE (maybe only "espressif" is needed):
		=> espressif/arduino-esp32 2.0.12		(https://github.com/espressif/arduino-esp32)
		=> Arduino ESP32 Boards by Arduino 2.0.12
  Successfully compiled for "ESP32 DEVKIT V1 DOIT" board.

I was inspired by the WEB page which describes a 3-phase PWM:
   https://wokwi.com/projects/334722465700774482
 
*/

#include <Arduino.h>
#include <driver/ledc.h>
#include <pwmWrite.h>  // https://github.com/Dlloydev/ESP32-ESP32S2-AnalogWrite

const byte pwmPin[] = { 13, 12 };
const uint32_t frequency = 8000;
const byte resolution = 10;  // select a resolution of 10 bits
const int colorpotPin = 34;
const int brightpotPin = 35;
int colorPotValue = 0;
int colorPotValueMap = 0;  // mapped value to work with % of color
int brightPotValue = 0;
int brightPotValueMap = 0;  // mapped value to work with % of brightness
int bright = 0;             // intermediate value to return to the full clock count
int LED_5700K = 0;
int LED_3000K = 0;
int deadTime = 0;

// If you are not used to deal with tables, note that the
// first element of the arrays has index 0 (and not 1 !)
uint16_t duty[2] = { 0, 0 };
uint16_t phase[2] = { 0, 512 };

Pwm pwm = Pwm();


void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("Demonstration of Two 180 phase shifted PWM using the LEDC");
  Serial.println();
}

void loop() {
  colorPotValue = analogRead(colorpotPin);
  colorPotValueMap = map(colorPotValue, 0, 4095, 0, 100);
  brightPotValue = analogRead(brightpotPin);
  brightPotValueMap = map(brightPotValue, 0, 4095, 0, 100);

  bright = ((pow(2, resolution) - 1) * brightPotValueMap) / 100;
  // bright = (1023 * brightPotValueMap) / 100;   // the numerical resolution of the line above
  LED_5700K = (bright * colorPotValueMap) / 100;
  LED_3000K = bright - LED_5700K;

  // Calculate time between falling edge of first PWM and rising edge of second PWM
  deadTime = (pow(2, resolution) - bright) / 2;
  // deadTime = (1024 - bright) / 2;    // the numerical resolution of the line above

  duty[0] = LED_5700K;
  duty[1] = LED_3000K;
  phase[0] = 0;
  phase[1] = LED_5700K + deadTime;

  //  pwm.pause();   // I don't know why this line was in the wokwi.com/projects
  //  This doesn't seem to be necessary!

  for (int i = 0; i < 2; i++) {
    pwm.write(pwmPin[i], (duty[i]), frequency, resolution, (phase[i]));
  }
  pwm.resume();
  // printValues(); // uncomment if you want display on the serial console... 
  // pwm.printDebug();
  delay(10);
}
void printValues() {
  // Serial.print("  colorPot : ");
  // Serial.print(colorPotValue);
  // Serial.print("  brightpot : ");
  // Serial.print(brightPotValue);

  Serial.print("  color : ");
  Serial.print(colorPotValueMap);
  Serial.print("  bright : ");
  Serial.print(brightPotValueMap);

  Serial.print("  |||  LED_5700K : ");
  Serial.print(LED_5700K);
  Serial.print("  LED_3000K : ");
  Serial.print(LED_3000K);
  Serial.print("  phase : ");
  Serial.println(phase[1]);

  delay(500);
}

Credits

daniel23
8 projects • 10 followers

Comments