Creating Burst PWM using MCPWM in ESP32

Dear All,

I would like to know how to generate BURST PWM in ESP32 using interrupts.

The typical required sequence is attached.
PWM_BURST.pdf (71.4 KB).

Presently, I have been have to generate BASIC PWM

#include "driver/mcpwm.h"                     
#define MOSFET1 22          //pin to trigger the MOSFET (Output) pin22
#define MOSFET2 23          //pin to trigger the MOSFET (Output) pin22
void setup() {
  // put your setup code here, to run once:
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, MOSFET1);
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, MOSFET2); 

  mcpwm_config_t pwm_config;
    pwm_config.frequency = 100000;    //frequency = 110Hz
    pwm_config.cmpr_a = 50;       //duty cycle of PWMxA = 60.0%
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);   //Configure PWM0A & PWM0B with above settings
  
  mcpwm_set_duty_type(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
  mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A,50);
}

void loop() {
 

}

How to include TIMER interrupt or PWM Interrupt . WHich would be best approach.

Thanks

Hi @sacd

The following ESP32 code outputs a burst of 300 PWM pulses at 1kHz, 30% duty-cycle on GPIO 27. The cycle is triggered every second:

// Output a burst of 300 pulses at 1kHz, 30% duty-cycle on GPIO pin 27
#include "soc/mcpwm_reg.h"
#include "soc/mcpwm_struct.h"
#include "driver/mcpwm.h"

#define GPIO_PWM0A_OUT 27

static void IRAM_ATTR isr_handler(void*)// arg)
{
  static volatile uint32_t counter;
 
  if (MCPWM0.int_st.timer0_tez_int_st & MCPWM0.int_ena.timer0_tez_int_ena)             //Check for interrupt on timer overflow
  {
    if (counter == 0)
    {
      counter++;   
      MCPWM0.channel[0].cmpr_value[0].val = 300;     // Set the counter compare for 30% duty-cycle
    }
    else if (counter < 300)                           // Have we counted up to 300?
    {
      counter++;                                      // Increment the counter and continue     
    }
    else
    {
      counter = 0;                                    // Reset the counter     
      MCPWM0.channel[0].cmpr_value[0].val = 0;        // Set the counter compare for 0% duty-cycle
      MCPWM0.int_ena.timer0_tez_int_ena = 0;          // Disable interrupt on TEZ event
    } 
    MCPWM0.int_clr.timer0_tez_int_clr = 1;           // Clear the interrupt flag       
  } 
}

void setup() {
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT);     // Initialise channel MCPWM0A on GPIO pin 27

  MCPWM0.clk_cfg.prescale = 9;                  // Set the 160MHz clock prescaler to 9 (160MHz/(9+1)=16MHz)
  MCPWM0.timer[0].period.prescale = 15;         // Set timer 0 prescaler to 8 (16MHz/(15+1))=1MHz)
  MCPWM0.timer[0].period.period = 999;          // Set the PWM period to 1kHz (1MHz/(999+1)=1kHz)  
     
  MCPWM0.channel[0].cmpr_value[0].val = 0;      // Set the counter compare for 0% duty-cycle
  MCPWM0.channel[0].generator[0].utez = 2;      // Set the PWM0A ouput to go high at the start of the timer period
  MCPWM0.channel[0].generator[0].utea = 1;      // Clear on compare match
  
  mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); //Set ISR Handler
  
  MCPWM0.timer[0].mode.mode = 1;                // Set timer 0 to increment
  MCPWM0.timer[0].mode.start = 2;               // Set timer 0 to free-run
}

void loop() 
{
   MCPWM0.int_ena.timer0_tez_int_ena = 1;       // Enable interrupt on TEZ event
   delay(1000);                                 // Wait for 1 second
}

Hi MartinL

Thanks for the reply. I tried the code but there are errors.

eg:
error: 'struct mcpwm_dev_t' has no member named 'channel'
MCPWM0.channel[0].cmpr_value[0].val = 0; // Set the counter compare for 0% duty-cycle
and so on.

Is there any other library/header file required.

Hi @sacd

My apologies for the delay in getting back, I was just trying to sort out why the code that I provided didn't even compile, as when I last ran it, admittedly a few years ago, it worked just fine.

It turns out the Espressif have completely rewritten their IDF library, presumably to accommodate their new ESP32 derivative devices. This unfortunately includes all the structures used to access the registers, rendering my previous example unusable.

Anyway, I started replacing the previous old structures with new ones thinking this would work, however it also turned out the Espressif have now turned the MCPWM module off by default, whereas previously it was turned on.

I wish that Espressif wouldn't make such substantial changes to their core code, but the fact is that they do.

I finally worked around the issues, here's the revised code, this time outputting 5, 1kHz pulses at 50% duty-cycle every second on GPIO27:

// Output a burst of 5 pulses at 1kHz, 50% duty-cycle on GPIO pin 27
#include "soc/mcpwm_struct.h"
#include "driver/mcpwm.h"

#define GPIO_PWM0A_OUT 27
#define PERIP_CLK_EN_REG (*(volatile uint32_t*)0x3FF000C0)    // Peripheral Clock Enable Register definition
#define PERIP_RST_EN_REG (*(volatile uint32_t*)0x3FF000C4)    // Peripheral Reset Enable Register definition

static void IRAM_ATTR isr_handler(void* arg)
{
  static volatile uint32_t counter = 0;

  if (MCPWM0.int_st.op0_tea_int_st)                   // Check for interrupt on timer compare matach
  {
    if (counter == 0)                                 // Start of pulse burst...
    {     
      counter++;   
      MCPWM0.operators[0].timestamp[0].gen = 500;     // Set the counter compare for 50% duty-cycle 
    }
    else if (counter <= 5)                             // Have we counted up to 5?
    {
      counter++;                                      // Increment the counter and continue     
    }
    else
    {
      counter = 0;                                    // Reset the counter     
      MCPWM0.operators[0].timestamp[0].gen = 0;       // Set the counter compare for 0% duty-cycle
      MCPWM0.int_ena.op0_tea_int_ena = 0;             // Disable interrupt on TEA event
    } 
    MCPWM0.int_clr.op0_tea_int_clr = 1;               // Clear the interrupt flag   
  } 
}

void setup() 
{
  PERIP_RST_EN_REG |= 1 << 17;                                // Reset the MCPWM0 peripheral             
  PERIP_RST_EN_REG &= ~(1 << 17);           
  PERIP_CLK_EN_REG |= 1 << 17;                                // Enable the MCPWM0 clock source
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT);     // Initialise channel MCPWM0A on GPIO pin 27

  MCPWM0.clk_cfg.clk_prescale = 9;                  // Set the 160MHz clock prescaler to 9 (160MHz/(9+1)=16MHz)
  MCPWM0.timer[0].timer_cfg0.timer_prescale = 15;   // Set timer 0 prescaler to 8 (16MHz/(15+1))=1MHz)
  MCPWM0.timer[0].timer_cfg0.timer_period = 999;    // Set the PWM period to 1kHz (1MHz/(999+1)=1kHz)
  
  MCPWM0.operators[0].timestamp[0].gen = 0;         // Set the counter compare for 0% duty-cycle
  MCPWM0.operators[0].generator[0].gen_utez = 2;    // Set the PWM0A ouput to go high at the start of the timer period
  MCPWM0.operators[0].generator[0].gen_utea = 1;    // Clear on compare match
  
  mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); //Set ISR Handler
  
  MCPWM0.timer[0].timer_cfg1.timer_mod = 1;         // Set timer 0 to increment
  MCPWM0.timer[0].timer_cfg1.timer_start = 2;       // Set timer 0 to free-run
}

void loop() 
{
   MCPWM0.int_clr.op0_tea_int_clr = 1;              // Clear the interrupt flag 
   MCPWM0.int_ena.op0_tea_int_ena = 1;              // Enable interrupt on TEA event
   delay(1000);                                     // Wait for 1 second
}

2 Likes

Thanks MartinL. This code was working without any compiler issues.

Can you give references for the MCPWM.operators[]... . I need to modify the code for including PWMxB and also complementary PWM with headband. my earlier code for dead band was based on this.

   mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);   //Configure PWM0A & PWM0B with above settings
  
  mcpwm_set_duty_type(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
  mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A,50);



mcpwm_set_duty_type(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, MCPWM_DUTY_MODE_1);
  mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B,50);

The code you gave for setting duty includes the following line

MCPWM0.operators[0].timestamp[0].gen

so if i can get MCPWM0.operators reference (on how to use it), it will help my use the code you have mentioned for my application.

Thanks
here duty is set using mcpwm_set_duty() function. In the code you have suggested, its using

Hi @sacd

The MCPWM operators are defined in the #include files: "soc/mcpwm_struct.h" and "driver/mcpwm.h". If you're using the Arduino IDE V2, it's possible to obtain the pathway to these files by hovering the cursor over them. On my machine they're located at:

C:\Users\Computer\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.11\tools\sdk\esp32\include\soc\esp32\include\soc\mcpwm_struct.h

C:\Users\Computer\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.11\tools\sdk\esp32\include\driver\include\driver\mcpwm.h

These definitions are used in conjunction with the ESP Technical Reference Manual Section 16 Motor Control PWM:

The code functions that you provide are an alternative way to program the MCPWM on-chip perhipheral using the ESP32-IDF API:

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/mcpwm.html

Microcontroller manufacturers often provide an Application Programming Interface (API) for their chips. This acts as an Hardware Abstraction Layer/Library (HAL), essentially allowing the programmer to simply call on a set of functions, thereby abstracting them from need to set the microcontroller's regsiters directly. These functions themselves act as a wrapper around the register level code.

The advantage is that the programmer doesn't require knowledge of the underlying registers, but instead just needs to know the requied API functions. The disadvantage however is that the programmer sacrifices the ability to directly control the microcontroller. The API usually covering the majority, but by no means all the possible features the microcontroller or its on-chip peripherals may have to offer.

Personally, I find it easier to read the datasheet/technical reference and just learn how to program the registers directly rather than memorising a set of API functions. Saying that some manufacturers nowadays make this quite difficult and it often requires having to trawl through obscure files in order to find where the registers are defined.

@sacd By the way, the Arduino IDE V2's intellisence is really useful when selecting the correct register definitions, since it provides a list of available options for each nested structure, as well as an explanation:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.