Arduino nano ESP32 mcpwm sync bldc motor control

Hello,

I am trying to control a BLDC, gimbal motor using 3 PWM signals, one for each phase, to obtain accurate positioning at low speeds. I use an Arduino Nano ESP32 (previously an Arduino Micro, which worked but is too slow). The 3 PWM signals (MCPWM) need to be synced; otherwise, the motor makes a high-pitched noise.

Here is a part of my code where this is handled:

#ifndef motor_h
#define motor_h

#include "Arduino.h"
#include "driver/mcpwm.h"
#include "config.h"

void motor_init(){

  // Initialize MCPWM GPIO
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, MOTOR_PIN_PWM_a);
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, MOTOR_PIN_PWM_b);
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM2A, MOTOR_PIN_PWM_c);

  // Configure MCPWM unit 0
  mcpwm_config_t pwm_config;
  pwm_config.frequency = 10000;    // Frequency = 10kHz
  pwm_config.cmpr_a = 50.0;        // Initial duty cycle of PWMxA = 50%
  pwm_config.cmpr_b = 50.0;         // Initial duty cycle of PWMxB = 0%
  pwm_config.counter_mode = MCPWM_UP_DOWN_COUNTER; // Up-down count mode
  pwm_config.duty_mode = MCPWM_DUTY_MODE_0;        // Active HIGH

  // Initialize PWM0A, PWM1A, and PWM2A with the same configuration
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config);
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_2, &pwm_config);

  mcpwm_sync_config_t sync_conf = {
    .sync_sig = MCPWM_SELECT_TIMER0_SYNC,
    .timer_val = 0,
    .count_direction = MCPWM_TIMER_DIRECTION_UP
  };
  mcpwm_sync_configure(MCPWM_UNIT_0, MCPWM_TIMER_0, &sync_conf);
  mcpwm_sync_configure(MCPWM_UNIT_0, MCPWM_TIMER_1, &sync_conf);
  mcpwm_sync_configure(MCPWM_UNIT_0, MCPWM_TIMER_2, &sync_conf);

  // Enable sync event for all timers to be the TEZ of timer0
  mcpwm_set_timer_sync_output(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_SWSYNC_SOURCE_TEZ);

  pinMode(MOTOR_PIN_ENABLE , OUTPUT);
}

void motor_write_pwm(float pwm_a,  float pwm_b, float pwm_c){
  // Update PWM duty cycles
  mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, pwm_a);
  mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_OPR_A, pwm_b);
  mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_2, MCPWM_OPR_A, pwm_c);
}

void motor_enable() {
  digitalWrite(MOTOR_PIN_ENABLE, HIGH);
  motor_write_pwm(0,0,0);
}

void motor_disable() {
  digitalWrite(MOTOR_PIN_ENABLE, LOW);
  motor_write_pwm(0,0,0);
}

void motor_turn_to_angle(float motor_angle,uint16_t voltage_peak){
  motor_angle = motor_angle * number_of_pole_pairs;
  // Calculate duty cycle for each phase
  float pwm_a = 100.0* (voltage_peak * sin(motor_angle) + voltage_power_input / 2) / voltage_power_input ;
  float pwm_b = 100.0* (voltage_peak * sin(motor_angle + PI*120/180) + voltage_power_input / 2) / voltage_power_input;
  float pwm_c = 100.0* (voltage_peak * sin(motor_angle + PI*240/180) + voltage_power_input / 2) / voltage_power_input;

  motor_write_pwm(pwm_a,pwm_b,pwm_c);

}

#endif

Has anyone an idea how to correctly sync these pwm signals?

Tanks for your help!

Kind regards,

Maarten

After some deep diving, this seems to work for me:

#ifndef motor_h
#define motor_h

#include "Arduino.h"
#include "driver/mcpwm.h"
#include "soc/mcpwm_reg.h"
#include "soc/mcpwm_struct.h"
#include "config.h"


void motor_init(){
  // Initialize MCPWM GPIO
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, MOTOR_PIN_PWM_a);
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, MOTOR_PIN_PWM_b);
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM2A, MOTOR_PIN_PWM_c);

  // Configure MCPWM unit 0
  mcpwm_config_t pwm_config;
  pwm_config.frequency = pwm_frequency*2;    // PWM frequency
  //pwm_config.cmpr_a = 50.0;        // Initial duty cycle of PWMxA = 50%
  //pwm_config.cmpr_b = 0.0;         // Initial duty cycle of PWMxB = 0%
  pwm_config.counter_mode = MCPWM_UP_DOWN_COUNTER; // Up-down count mode
  pwm_config.duty_mode = MCPWM_DUTY_MODE_0;        // Active HIGH

  // Initialize PWM0A, PWM1A, and PWM2A with the same configuration
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config);
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_2, &pwm_config);
  delayMicroseconds(10000);

  mcpwm_stop(MCPWM_UNIT_0, MCPWM_TIMER_0);
  mcpwm_stop(MCPWM_UNIT_0, MCPWM_TIMER_1);
  mcpwm_stop(MCPWM_UNIT_0, MCPWM_TIMER_2);

  // manual configuration due to the lack of config flexibility in mcpwm_init()
  MCPWM0.clk_cfg.clk_prescale = 0;
  // calculate prescaler and period
  // step 1: calculate the prescaler using the default pwm resolution
  // prescaler = bus_freq / (pwm_freq * default_pwm_res) - 1
  int prescaler = ceil((double) 160e6f / (double) 4096 / 2.0f / (double)pwm_frequency) - 1;
  // constrain prescaler
  prescaler = constrain(prescaler, 0, 128);
  // now calculate the real resolution timer period necessary (pwm resolution)
  // pwm_res = bus_freq / (pwm_freq * (prescaler + 1))
  int resolution_corrected = (double)160e6f / 2.0f / (double)pwm_frequency / (double)(prescaler + 1);
  // if pwm resolution too low lower the prescaler
  if(resolution_corrected < 3000 && prescaler > 0 )
    resolution_corrected = (double)160e6f / 2.0f / (double)pwm_frequency / (double)(--prescaler + 1);
  resolution_corrected = constrain(resolution_corrected, 3000, 8000);

  // set prescaler
  MCPWM0.timer[0].timer_cfg0.timer_prescale = prescaler;
  MCPWM0.timer[1].timer_cfg0.timer_prescale = prescaler;
  MCPWM0.timer[2].timer_cfg0.timer_prescale = prescaler;
  delayMicroseconds(1000);
  //set period
  MCPWM0.timer[0].timer_cfg0.timer_period = resolution_corrected;
  MCPWM0.timer[1].timer_cfg0.timer_period = resolution_corrected;
  MCPWM0.timer[2].timer_cfg0.timer_period = resolution_corrected;
  delayMicroseconds(1000);
  MCPWM0.timer[0].timer_cfg0.timer_period_upmethod = 0;
  MCPWM0.timer[1].timer_cfg0.timer_period_upmethod = 0;
  MCPWM0.timer[2].timer_cfg0.timer_period_upmethod = 0;
  delayMicroseconds(1000);
  //restart the timers
  mcpwm_start(MCPWM_UNIT_0, MCPWM_TIMER_0);
  mcpwm_start(MCPWM_UNIT_0, MCPWM_TIMER_1);
  mcpwm_start(MCPWM_UNIT_0, MCPWM_TIMER_2);
  delayMicroseconds(1000);

  mcpwm_sync_config_t sync_conf = {
    .sync_sig = MCPWM_SELECT_TIMER0_SYNC,
    .timer_val = 0,
    .count_direction = MCPWM_TIMER_DIRECTION_UP
  };
  mcpwm_sync_configure(MCPWM_UNIT_0, MCPWM_TIMER_0, &sync_conf);
  mcpwm_sync_configure(MCPWM_UNIT_0, MCPWM_TIMER_1, &sync_conf);
  mcpwm_sync_configure(MCPWM_UNIT_0, MCPWM_TIMER_2, &sync_conf);

  // Enable sync event for all timers to be the TEZ of timer0
  mcpwm_set_timer_sync_output(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_SWSYNC_SOURCE_TEZ);
  
  pinMode(MOTOR_PIN_ENABLE , OUTPUT);
}

void motor_write_pwm(float pwm_a,  float pwm_b, float pwm_c){
  // Update PWM duty cycles
  mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, pwm_a);
  mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_OPR_A, pwm_b);
  mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_2, MCPWM_OPR_A, pwm_c);
}

void motor_enable() {
  digitalWrite(MOTOR_PIN_ENABLE, HIGH);
  motor_write_pwm(50,50,50);
}

void motor_disable() {
  digitalWrite(MOTOR_PIN_ENABLE, LOW);
  motor_write_pwm(0,0,0);
}

void motor_turn_to_angle(float motor_angle,uint16_t voltage_peak){
  motor_angle = motor_angle * number_of_pole_pairs;
  // Calculate duty cycle for each phase
  float pwm_a = 100.0* (voltage_peak * sin(motor_angle) + voltage_power_input / 2) / voltage_power_input ;
  float pwm_b = 100.0* (voltage_peak * sin(motor_angle + PI*120/180) + voltage_power_input / 2) / voltage_power_input;
  float pwm_c = 100.0* (voltage_peak * sin(motor_angle + PI*240/180) + voltage_power_input / 2) / voltage_power_input;

  motor_write_pwm(pwm_a,pwm_b,pwm_c);

}

#endif