2025-03-14 7:10 AM
Hello,
I designed a BLDC motor driver for myself and I am currently in the software phase.
I am using the 1st, 2nd and 3rd channels of the TIM8 timer for 3 phases, but I do not have any information about how to create SPWM in stm32 or how to trigger these 3 phases and their configuration. I wrote a sample code from the internet, but the motor draws very high current and rotates with vibration.
#define PI 3.14159265359
uint16_t SINE_A = 0;
uint16_t SINE_B = 120;
uint16_t SINE_C = 240;
void generateSPWM()
{
// Add 1 so the rotation will continue 1 by 1
SINE_A = (SINE_A + 1) % 360; // Sine Wave A
SINE_B = (SINE_A + 120) % 360; // Sine Wave B, 120 derece faz farkı
SINE_C = (SINE_A + 240) % 360; // Sine Wave C, 240 derece faz farkı
// Calculate the PWM values for creating a sine wave (SPWM)
int SINE_A_PWM = (int)(sin((double)SINE_A * PI / 180) * 499.5 + 499.5); // Convert to PWM range
int SINE_B_PWM = (int)(sin((double)SINE_B * PI / 180) * 499.5 + 499.5); // Convert to PWM range
int SINE_C_PWM = (int)(sin((double)SINE_C * PI / 180) * 499.5 + 499.5); // Convert to PWM range
// Set the PWM duty cycles for each phase
// PWM değerini daha düşük bir oranda kullan
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, SINE_A_PWM * 0.2); // %40 Duty Cycle
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, SINE_B_PWM * 0.2); // %40 Duty Cycle
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, SINE_C_PWM * 0.2); // %40 Duty Cycle
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM8)
{
generateSPWM(); // Generate SPWM on Timer overflow
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM1_Init();
MX_SPI3_Init();
MX_TIM2_Init();
MX_ADC1_Init();
MX_I2C2_Init();
MX_TIM8_Init();
MX_USART1_UART_Init();
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
HAL_GPIO_WritePin(GPIOB, RUNL_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET); // Enable Half1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET); // Enable Half2
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_3);
// Start PWM signals
HAL_TIM_Base_Start_IT(&htim8);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while(1){
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/* USER CODE END 3 */
}
2025-03-14 7:18 AM
You can improve the SPWM Implementation by editing your code.
#include "math.h"
#include "stdint.h"
#define PI 3.14159265359
#define PWM_MAX 999 // Assume TIM8 ARR = 999 for 100% duty cycle
#define TABLE_SIZE 360
uint16_t sineTable[TABLE_SIZE]; // Precomputed sine lookup table
uint16_t stepIndex = 0; // Angle step for sine wave generation
void generateSineTable() {
for (uint16_t i = 0; i < TABLE_SIZE; i++) {
sineTable[i] = (uint16_t)((sin((double)i * PI / 180) * (PWM_MAX / 2)) + (PWM_MAX / 2));
}
}
void generateSPWM() {
// Update phase shift
stepIndex = (stepIndex + 1) % TABLE_SIZE;
// Get sine wave values for three phases with 120° phase shift
uint16_t SINE_A_PWM = sineTable[stepIndex];
uint16_t SINE_B_PWM = sineTable[(stepIndex + 120) % TABLE_SIZE];
uint16_t SINE_C_PWM = sineTable[(stepIndex + 240) % TABLE_SIZE];
// Set the PWM duty cycles for each phase
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, SINE_A_PWM);
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, SINE_B_PWM);
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, SINE_C_PWM);
}
// Timer Interrupt Handler
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM8) {
generateSPWM(); // Generate SPWM on Timer overflow
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM8_Init();
// Generate sine lookup table
generateSineTable();
// Enable all motor drive pins
HAL_GPIO_WritePin(GPIOB, RUNL_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET); // Enable Half1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET); // Enable Half2
// Start PWM signals
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_3);
HAL_TIM_Base_Start_IT(&htim8); // Start Timer Interrupt
while(1) {
// Infinite loop
}
}
2025-03-14 9:34 AM
Hello,
First of all, thank you for your answer. I tried this code, it works better than the previous one, but I have some problems.
I have an esc driver, when I connect it to it, it rotates at a higher speed and more stable at the same currents, while on my own PCB board it continues to rotate a little more vibrating and slowly, and it also heats up more, I probably need to optimize it a little more, how do you think I can optimize it?
Last Code :
#define PI 3.14159265359
#define PWM_MAX 999 // Assume TIM8 ARR = 999 for 100% duty cycle
#define TABLE_SIZE 360
uint16_t sineTable[TABLE_SIZE]; // Precomputed sine lookup table
uint16_t stepIndex = 0; // Angle step for sine wave generation
void generateSineTable() {
for (uint16_t i = 0; i < TABLE_SIZE; i++) {
sineTable[i] = (uint16_t)((sin((double)i * PI / 180) * (PWM_MAX / 2)) + (PWM_MAX / 2));
}
}
void generateSPWM() {
// Update phase shift
stepIndex = (stepIndex + 1) % TABLE_SIZE;
// Get sine wave values for three phases with 120° phase shift
uint16_t SINE_A_PWM = sineTable[stepIndex];
uint16_t SINE_B_PWM = sineTable[(stepIndex + 120) % TABLE_SIZE];
uint16_t SINE_C_PWM = sineTable[(stepIndex + 240) % TABLE_SIZE];
// Set the PWM duty cycles for each phase
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, SINE_A_PWM * 0.1);
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, SINE_B_PWM * 0.1);
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, SINE_C_PWM * 0.1);
}
// Timer Interrupt Handler
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM8) {
generateSPWM(); // Generate SPWM on Timer overflow
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM1_Init();
MX_SPI3_Init();
MX_TIM2_Init();
MX_ADC1_Init();
MX_I2C2_Init();
MX_TIM8_Init();
MX_USART1_UART_Init();
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
generateSineTable();
HAL_GPIO_WritePin(GPIOB, RUNL_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET); // Enable Half1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET); // Enable Half2
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_3);
// Start PWM signals
HAL_TIM_Base_Start_IT(&htim8);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while(1){
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/* USER CODE END 3 */
}
2025-03-14 9:30 PM
Hello,
I have a doubt about that multiplication to set the timer output compare:
SINE_x_PWM * 0.1
The input needs to be an integer while you are multiplying that sine value by 0.1. It could be a cast problem. So check if you have the intended input value to:
__HAL_TIM_SET_COMPARE()
2025-03-15 2:04 AM
Hello,
I wrote the 0.1 multiplication there on purpose because the motor draws extremely high current while rotating, and since I don't have an oscilloscope to understand that my microcontroller is actually creating spwm, I am currently testing my motor by trial and error.