Skip to content

Instantly share code, notes, and snippets.

@fabiovila
Last active March 28, 2024 11:05
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fabiovila/77c544286e9b0260a5a09823b47ba39f to your computer and use it in GitHub Desktop.
Save fabiovila/77c544286e9b0260a5a09823b47ba39f to your computer and use it in GitHub Desktop.
How sync ESP32 Timers MCPWM?
// CHANGE the clock prescale in mcpwm.c file to make possible get 30000hz of pwm frequency (15Khz in center aligned mode)
#define MCPWM_BASE_CLK (2 * APB_CLK_FREQ) //2*APB_CLK_FREQ 160Mhz
#define MCPWM_CLK_PRESCL 1 //MCPWM clock prescale Original = 15 <---------------------
#define TIMER_CLK_PRESCALE 1 //MCPWM timer prescales Original = 9 <---------------------
#define MCPWM_CLK (MCPWM_BASE_CLK/(MCPWM_CLK_PRESCL +1))
#define MCPWM_PIN_IGNORE (-1)
#define OFFSET_FOR_GPIO_IDX_1 6
#define OFFSET_FOR_GPIO_IDX_2 75
mcpwm_config_t pwm_config;
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_PWM1A_OUT);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1B, GPIO_PWM1B_OUT);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM2A, GPIO_PWM2A_OUT);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM2B, GPIO_PWM2B_OUT);
Motor.TabelaLimite = 0;
// Center aligned pwm mode complementary mode
// 15Khz frequency
// 95% initial duty cibly
pwm_config.frequency = 30000; //frequency
pwm_config.cmpr_a = 95.0; // Duty em porcentagem
pwm_config.cmpr_b = pwm_config.cmpr_a;
pwm_config.counter_mode = MCPWM_UP_DOWN_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_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config); //Configure PWM1A & PWM1B with above settings
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_2, &pwm_config); //Configure PWM2A & PWM2B with above settings
// deadtime (see clock source changes in mcpwm.c file)
mcpwm_deadtime_enable(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, 80, 80); // 1us deadtime
mcpwm_deadtime_enable(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, 80, 80);
mcpwm_deadtime_enable(MCPWM_UNIT_0, MCPWM_TIMER_2, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, 80, 80);
// TEZ interrupts (TIMERS in ZERO before ascending)
MCPWM[MCPWM_UNIT_0]->int_ena.val = BIT(3) | BIT(4) | BIT(5); // /*A PWM timer X TEZ event will trigger this interrupt*/
mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); //Set ISR Handler
// ------ READ THIS
// mcpwm_sync_signal_t is defined only to SYNC0/1/2 that are for GPIO sync
// In order to sync Timer1 and Timer2 with Timer0
// Pass 1 to mcpwm_sync_signal_t sync_sig in mcpwm_sync_enable
// 1 is equal to "PWM timer0 sync_out" in PWM_TIMER_SYNCI_CFG_REG.PWM_TIMER1/2_SYNCISEL
// Phase (last parameter) is zero.
mcpwm_sync_enable(MCPWM_UNIT_0, MCPWM_TIMER_1, 1, 0);
mcpwm_sync_enable(MCPWM_UNIT_0, MCPWM_TIMER_2, 1, 0);
// Timer0 is our sync out. When it is equal to zero and before ascending sync out is triggered, so Timer1 and Timer2 stands in sync_in with Timer0.
// When Timer0 is zero Timer1 and Timer2 is forced to phase defined in last parameter in functions above (0).
// Make Timer0 sync out in TEZ
MCPWM[MCPWM_UNIT_0]->timer[MCPWM_TIMER_0].sync.out_sel = 1;
// So:
// [TIMER0](When in Zero)->[TIMER1=0]
// ->[TIMER2=0]
// Ref:
// esp32_technical_reference_manual_en.pdf around pages 439
// mcpwm.h
// mcpwm_struct.h
// try and error!!
@JohnMacrae
Copy link

Nice work Fabio. Is 30kHz the maximum PWM frequency achieveable?

@fabiovila
Copy link
Author

Nice work Fabio. Is 30kHz the maximum PWM frequency achieveable?

It's a good question, I haven't tested higher frequencies yet.

@creatormir
Copy link

Can anyone give an answer for higher frequencies?

@creatormir
Copy link

creatormir commented May 26, 2022

I tested
pwm_config.frequency = 600000; //frequency
pwm_config.cmpr_a = 50.0; //duty cycle of PWMxA
pwm_config.cmpr_b = 50.0; //duty cycle of PWMxb
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);

600kHz: fact 606kHz, duty ±0.4%
500kHz: fact 500kHz, duty ±0%
Other modes have not yet been tested.

@fabiovila
Copy link
Author

fabiovila commented May 26, 2022

Clock source of MCPWM is 160mhz. In theory, you can get 80mhz with no duty cycle, although GPIO max frequency be determinant here. I didn't find in data sheets and manual any information for max frequency of gpios. In general, modern mcus gpio max freq > 1mhz and < 1.5mhz

@Marcarleto
Copy link

Hi Fabio, do you have the full project's code? How or where do you set the associated gpio pins for the pwms out put and for wich unit?

@Marcarleto
Copy link

Is this in Arduino IDE?

@creatormir
Copy link

Marcarleto,
This is like the complete code of the project, it seems to be esp-idf: https://github.com/fabiovila/ESP32ACMotorInverter_Test/blob/master/main/motor.c

@Prithul0218
Copy link

@fabiovila

Hi. It seems like mcpwm.c is deprecated and now is replaced by mcpwm_prelude.c

Is there any chance you updated your code to be used with ESP-IDF 5.1? I'm struggling to generate 100kHz PWM while maintaining 8 bit resolution.

@JamshidJD
Copy link

JamshidJD commented Jun 22, 2023

I'm trying to use MCPWM_UP_DOWN_COUNTER
and my frequency range is 10K - 100K, i know i have to provide double the frequency to generate the center aligned output. but the factor of twice the frequency changes when i above 12K, for instance if i want to generate 100K i'm providing 200K as frequency but the output is 125K instead. I've also tried to make a loop to see the linearity but by decreasing the value 200K then next jumps comes at around 83K and so on...., this only happens in MCPWM_UP_DOWN_COUNTER if i am using the other mode its fine.
The code snippet is attached below, any help would be appreciated. thanks

mcpwm_config_t pwm_configA;
pwm_configA.frequency = 40000; // frequência = 500Hz,
pwm_configA.cmpr_a = 50; // Ciclo de trabalho (duty cycle) do PWMxA = 0
pwm_configA.cmpr_b = 50; // Ciclo de trabalho (duty cycle) do PWMxb = 0
pwm_configA.counter_mode = MCPWM_UP_DOWN_COUNTER; // Para MCPWM assimetrico
pwm_configA.duty_mode = MCPWM_DUTY_MODE_0; // Define ciclo de trabalho em nível alto

mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, Pulse_Pin_1);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, Pulse_Pin_2);
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_configA); // Define PWM0A & PWM0B com as configurações acima

int DutyCycle = 50;
mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, DutyCycle);
mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, 100 - DutyCycle); // Configura a porcentagem do PWM no Operador B (Ciclo de trabalho)
mcpwm_set_duty_type(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
mcpwm_set_duty_type(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, MCPWM_DUTY_MODE_1);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment