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
}
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.
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
}
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.
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:
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: