STEVAL ESC01 SimpleFoc Support

Good Morning,

I’m trying to run SimpleFOC on the STEVAL-ESC01 drive, which works with the STM32F303 microcontroller in 6PWM mode.

I successfully ran the driver using STCubeIDE with the example code. However, when I try to run it using Arduino and SimpleFOC, issues begin.

The motor is not energized at all, and I don’t see anything on the oscilloscope.

SimpleFOC found the correct timer to use (TIM1) since I can see in the log: “STM32-DRV: Synchronising timers! Timer no. 1” from stm32_mcu.cpp.

Moreover, PeripheralPins from F303C(B-C)T variant seems to be correct according to the STEVAL-ESC01 board.

I also imported SystemClock_Config() and MX_TIM1_Init from the STCube example, but it doesn’t change anything.

Below is my code:

// Open loop motor control example
#include <SimpleFOC.h>
#include <SoftwareSerial.h>

#define PWM_PERIOD_CYCLES (uint16_t)((144 * (uint32_t)1000000u / ((uint32_t)(30000))) & 0xFFFE)
#define REP_COUNTER (uint16_t)((1 * 2u) - 1u)

#define DEADTIME_NS SW_DEADTIME_NS
#define DEAD_TIME_ADV_TIM_CLK_MHz (144 * 1)
#define DEAD_TIME_COUNTS (DEAD_TIME_ADV_TIM_CLK_MHz * 800 / 1000uL)

#define M1_PWM_WL_Pin GPIO_PIN_1
#define M1_PWM_WL_GPIO_Port GPIOB
#define M1_CURR_SHUNT_W_Pin GPIO_PIN_11
#define M1_CURR_SHUNT_W_GPIO_Port GPIOB
#define M1_BUS_VOLTAGE_Pin GPIO_PIN_13
#define M1_BUS_VOLTAGE_GPIO_Port GPIOB
#define M1_PWM_UH_Pin GPIO_PIN_8
#define M1_PWM_UH_GPIO_Port GPIOA
#define M1_PWM_VH_Pin GPIO_PIN_9
#define M1_PWM_VH_GPIO_Port GPIOA
#define M1_PWM_WH_Pin GPIO_PIN_10
#define M1_PWM_WH_GPIO_Port GPIOA
#define M1_PWM_UL_Pin GPIO_PIN_11
#define M1_PWM_UL_GPIO_Port GPIOA
#define M1_PWM_VL_Pin GPIO_PIN_12
#define M1_PWM_VL_GPIO_Port GPIOA



TIM_HandleTypeDef htim1;

UART_HandleTypeDef huart1;


SoftwareSerial mySerial(PB7, PB6);  // RX, TX




// BLDC motor & driver instance
// BLDCMotor motor = BLDCMotor(pole pair number);
BLDCMotor motor = BLDCMotor(7);
// BLDCDriver3PWM driver = BLDCDriver3PWM(pwmA, pwmB, pwmC, Enable(optional));
BLDCDriver6PWM driver = BLDCDriver6PWM(PA8, PA11, PA9, PA12, PA10, PB1);
//BLDCDriver3PWM driver = BLDCDriver3PWM(PA8, PA9,PA10);


// Stepper motor & driver instance
//StepperMotor motor = StepperMotor(50);
//StepperDriver4PWM driver = StepperDriver4PWM(9, 5, 10, 6,  8);


//target variable
float target_velocity = 10;

// instantiate the commander
Commander command = Commander(mySerial);
void doTarget(char* cmd) {
  command.scalar(&target_velocity, cmd);
}
void doLimit(char* cmd) {
  command.scalar(&motor.voltage_limit, cmd);
}
void SystemClock_Config(void) {
  RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
  RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };
  RCC_PeriphCLKInitTypeDef PeriphClkInit = { 0 };

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                                | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1 | RCC_PERIPHCLK_TIM1;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
  PeriphClkInit.Tim1ClockSelection = RCC_TIM1CLK_PLLCLK;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
    Error_Handler();
  }
}

void setup() {

  //Comment or Uncomment theses lines does not change anything. 
  //SystemClock_Config();
  //MX_TIM1_Init();

  mySerial.begin(9600);

  // enable more verbose output for debugging
  // comment out if not needed
  SimpleFOCDebug::enable(&mySerial);

  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 12;
  // limit the maximal dc voltage the driver can set
  // as a protection measure for the low-resistance motors
  // this value is fixed on startup
  driver.voltage_limit = 12;

  driver.pwm_frequency = 10000;
  driver.dead_zone = 0.05;

  driver.pwm_frequency = 15000;
  if (!driver.init()) {
    mySerial.println("Driver init failed!");
    return;
  }

  driver.enable();

  // link the motor and the driver
  motor.linkDriver(&driver);

  // limiting motor movements
  // limit the voltage to be set to the motor
  // start very low for high resistance motors
  // current = voltage / resistance, so try to be well under 1Amp
  motor.voltage_limit = 6;  // [V]


  // open loop control config
  motor.controller = MotionControlType::velocity_openloop;

  // motor.foc_modulation = FOCModulationType::Trapezoid_120;

  // init motor hardware
  if (!motor.init()) {
    mySerial.println("Motor init failed!");
    delay(10000);
    return;
  }

  // add target command T
  command.add('T', doTarget, "target velocity");
  command.add('L', doLimit, "voltage limit");

  mySerial.println("Motor ready!");
  mySerial.println("Set target velocity [rad/s]");
  _delay(1000);
}

void loop() {
  motor.move(target_velocity);
  // user communication
  command.run();
}

static void MX_TIM1_Init(void) {


  TIM_SlaveConfigTypeDef sSlaveConfig = { 0 };
  TIM_MasterConfigTypeDef sMasterConfig = { 0 };
  TIM_OC_InitTypeDef sConfigOC = { 0 };
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = { 0 };


  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;
  htim1.Init.Period = ((PWM_PERIOD_CYCLES) / 2);
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV2;
  htim1.Init.RepetitionCounter = (REP_COUNTER);
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK) {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK) {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;
  sSlaveConfig.InputTrigger = TIM_TS_ITR1;
  if (HAL_TIM_SlaveConfigSynchro(&htim1, &sSlaveConfig) != HAL_OK) {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC4REF;
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_SET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK) {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM2;
  sConfigOC.Pulse = (((PWM_PERIOD_CYCLES) / 2) - (1));
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK) {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_1;
  sBreakDeadTimeConfig.DeadTime = ((DEAD_TIME_COUNTS) / 2);
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.BreakFilter = 0;
  sBreakDeadTimeConfig.Break2State = TIM_BREAK2_ENABLE;
  sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
  sBreakDeadTimeConfig.Break2Filter = 3;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK) {
    Error_Handler();
  }

  HAL_TIM_MspPostInit(&htim1);
}

void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim) {
  GPIO_InitTypeDef GPIO_InitStruct = { 0 };
  if (htim->Instance == TIM1) {


    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pin = M1_PWM_WL_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF6_TIM1;
    HAL_GPIO_Init(M1_PWM_WL_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = M1_PWM_UH_Pin | M1_PWM_VH_Pin | M1_PWM_WH_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF6_TIM1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = M1_PWM_UL_Pin | M1_PWM_VL_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF6_TIM1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}

Right now, I’m a bit confused, and I don’t know where I should search.

Any help is welcome!

Thanks in advance to this super community!

Have a good day

Don’t use MX_TIM1_Init, this is already being initialized by SimpleFOC.

I don’t see obvious issues.

motor.voltage_limit = 6;

This will be used as target voltage in openloop mode, it’s too much.

That doesn’t mean TIM1 is used, but the number of timers is 1. So no need to synchronize several timers together (code)


Good Morning,

Thank you, Candas1, for your feedback!

The issue (after hours of research) was that for STEVAL ESC01, we need to define SIMPLEFOC_PWM_LOWSIDE_ACTIVE_HIGH as false in hardware_api.h (here). In fact, MOSFET is active when the low-side PWM is LOW

I will now work on CAN, closed loop, and current sensing.

I plan to propose a pull request at the end so that the board can be officially supported by SimpleFOC.

I will update my progress here.

Here the actual code that is working :

// Open loop motor control example
#include <SimpleFOC.h>
#include <SoftwareSerial.h>


#define PWM_PERIOD_CYCLES (uint16_t)((144 * (uint32_t)1000000u / ((uint32_t)(30000))) & 0xFFFE)

SoftwareSerial mySerial(PB7, PB6);  // RX, TX

BLDCMotor motor = BLDCMotor(7);
BLDCDriver6PWM driver = BLDCDriver6PWM(PA8, PA11, PA9, PA12, PA10, PB1);

//target variable
float target_velocity = 5;

// instantiate the commander
Commander command = Commander(mySerial);
void doTarget(char* cmd) {
  command.scalar(&target_velocity, cmd);
}
void doLimit(char* cmd) {
  command.scalar(&motor.voltage_limit, cmd);
}

void setup() {

    SystemClock_Config();

  _delay(1000);

  mySerial.begin(9600);
  mySerial.println(PWM_PERIOD_CYCLES);

  // enable more verbose output for debugging
  // comment out if not needed
  SimpleFOCDebug::enable(&mySerial);

  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 12;
  // limit the maximal dc voltage the driver can set
  // as a protection measure for the low-resistance motors
  // this value is fixed on startup
  driver.voltage_limit = 12;
  driver.pwm_frequency = 30000;

  driver.dead_zone = 0.043;

  if (!driver.init()) {
    mySerial.println("Driver init failed!");
    return;
  }

  driver.enable();

  // link the motor and the driver
  motor.linkDriver(&driver);


  motor.voltage_limit = 0.5;  // [V]
  motor.controller = MotionControlType::velocity_openloop;
 // motor.foc_modulation = FOCModulationType::SpaceVectorPWM;

  // init motor hardware
  if (!motor.init()) {
    mySerial.println("Motor init failed!");
    delay(10000);
    return;
  }

  // add target command T
  command.add('T', doTarget, "target velocity");
  command.add('L', doLimit, "voltage limit");

  mySerial.println("Motor ready!");
  mySerial.println("Set target velocity [rad/s]");
  _delay(1000);
}

void loop() {
  motor.move(target_velocity);
  // user communication
  command.run();
}

void SystemClock_Config(void) {
  RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
  RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };
  RCC_PeriphCLKInitTypeDef PeriphClkInit = { 0 };

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                                | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1 | RCC_PERIPHCLK_TIM1;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
  PeriphClkInit.Tim1ClockSelection = RCC_TIM1CLK_PLLCLK;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
    Error_Handler();
  }

    SystemCoreClockUpdate();

}


Have a good Christmas holiday.

1 Like

About current sensing, I spend a lot of time on a unified stm32 driver but I haven’t released it yet. Maybe you want to try it on stm32f3 after closed loop is working.

Hello Candas,

I just finished CAN communication and closed-loop (speed and position). I will begin current sensing.

I will be happy to try your work! Keep me in touch.

Have a good day.

1 Like

Sorry for the delay.
This is the fork, and here I explain what is different

I will probably have to add F3 here to test.

In case you need extra adc values to sample, this is how it’s handled with this fork.

Thanks !

I will have a look and keep you in touch !

Have a great day.