Designing a STM32-Based BLDC Motor Drive: Schematics and Firmware
1. Core Architecture
Driving a brushless DC (BLDC) motor with an STM32 microcontroller (e.g., STM32F103, STM32F407) relies on generating complementary PWM signals via advanced timers to control a three-phase inverter bridge. Rotor position is determined using Hall-effect sensors or back-EMF detection. The firmware implements either field-oriented control (FOC) or six-step commutation for precise speed and torque regulation. The following sections detail the hardware schematic design and software implementation.
2. Hardware Schematic Design
The drive system comprises an STM32 main controller, a three-phase inverter, Hall sensor interface, current sensing circuitry, and power management. Each module is described below.
2.1 Three-Phase Inverter (Power Stage)
Topology: A full-bridge inverter using six MOSFETs (or IGBTs) divided into high-side (UH, VH, WH) and low-side (UL, VL, WL) switches. These are driven by complementary PWM outputs from STM32’s advanced timer (e.g., TIM1).
Key Components:
- MOSFET: Use fast-switching, low on-resistance devices such as IRFS3607 (100V, 360A).
- Gate Driver: IR2110S half-bridge driver amplifies STM32 logic signals to drive MOSFET gates and provides programmable dead time to prevent shoot-through.
- Bootstrap Circuit: A capacitor (100nF) between VB and VS pins generates the high-side gate voltage ( >10V).
Schematic Details:
- High-side driver: IR2110S HIN connects to STM32 PWM output (e.g., TIM1_CH1); LIN is grounded.
- Low-side driver: IR2110S LIN connects to complementary PWM (e.g., TIM1_CH1N); HIN is grounded.
- Freewheeling diodes (e.g., FR107) across each MOSFET clamp back-EMF from the motor windings.
2.2 Hall Sensor Interface (Position Sensing)
Topology: Three Hall-effect sensors (e.g., A1120) mounted near the rotor output signals H1, H2, H3. These connect to STM32’s general-purpose timer (e.g., TIM2) configured for input capture.
Key Design Points:
- Sensor Power: Use a 5V regulator (LM1117-5.0) to supply the Hall sensors.
- Signal Conditioning: RC low-pass filter (1kΩ + 100nF) removes high-frequency noise.
- Timer Configuration: TIM2 operates in input capture mode to catch rising edges, enabling rotor position calculation for six-step commutation.
2.3 Current Sensing (Closed-Loop Control)
Topology: Single shunt resistor (0.01Ω, 2W) placed in the negative DC bus. The voltage drop is amplified by a op-amp (LM358) and fed to an STM32 ADC channel (e.g., PA0).
Key Design Points:
- Shunt Resistor: Choose a low-inductance, precision constantan wire resistor to minimize noise.
- Amplifier: Differential amplifier with gain of 10 maps the voltage to 0–3.3V ADC range.
- Filtering: A 100nF ceramic capacitor at the op-amp output suppresses high-frequency transients.
2.4 Power Management
Input Supply: 24V DC (common for BLDC motors) stepped down to 12V (for gate drivers) via LM2596, then to 3.3V (for STM32) via AMS1117-3.3.
Protection:
- Input fuse (5A) prevents overcurrent damage.
- TVS diode (P6KE15CA) across the input absorbs surges.
- Decoupling capacitors: 100nF ceramic + 10μF electrolytic at each STM32 VDD pin.
3. Firmware Implementation
The software stack includes initialization (HAL library), PWM generation, Hall signal processing, FOC algorithm, and fault protection. The code below targets STM32F103 using HAL.
3.1 Initialization (Clock, GPIO, Timer, ADC)
#include "stm32f10x.h"
#include "stm32f10x_hal.h"
// Pin definitions
#define PWM_UH_PIN GPIO_PIN_8 // PA8 – TIM1_CH1
#define PWM_UH_PORT GPIOA
#define HALL_1_PIN GPIO_PIN_0 // PA0 – Hall H1
#define HALL_1_PORT GPIOA
// Global variables
TIM_HandleTypeDef htim1;
ADC_HandleTypeDef hadc1;
volatile uint8_t hall_position = 0;
int main(void) {
HAL_Init();
SystemClock_Config(); // 72 MHz
GPIO_Init(); // Configure GPIOs
TIM1_Init_PWM(); // TIM1 for PWM output
ADC1_Init(); // ADC for current sensing
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_ADC_Start_IT(&hadc1, ADC_CHANNEL_0);
while(1) {
// Main loop: execute FOC or commutation logic
}
}
void TIM1_Init_PWM(void) {
TIM_OC_InitTypeDef ocConfig = {0};
TIM_MasterConfigTypeDef masterConfig = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = 71; // 1 MHz timer clock (72 MHz / 72)
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 1999; // 5 kHz PWM (1 MHz / 2000)
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
HAL_TIM_PWM_Init(&htim1);
// Channel 1 configuration
ocConfig.OCMode = TIM_OCMODE_PWM1;
ocConfig.Pulse = 1000; // 50% duty cycle
ocConfig.OCPolarity = TIM_OCPOLARITY_HIGH;
ocConfig.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim1, &ocConfig, TIM_CHANNEL_1);
// Dead time insertion (1.5 μs)
TIM_BDTRInitTypeDef bdtrConfig = {0};
bdtrConfig.DeadTime = 0x0F; // 1.5 μs (each unit = 0.1 μs)
bdtrConfig.LockLevel = TIM_LOCKLEVEL_OFF;
bdtrConfig.DeadTimeCompensation = TIM_DEADTIMECOMPENSATION_DISABLE;
HAL_TIMEx_ConfigDeadTime(&htim1, &bdtrConfig);
// Master mode configuration
masterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
masterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim1, &masterConfig);
}
void ADC1_Init(void) {
ADC_ChannelConfTypeDef chConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
HAL_ADC_Init(&hadc1);
chConfig.Channel = ADC_CHANNEL_0;
chConfig.Rank = 1;
chConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
HAL_ADC_ConfigChannel(&hadc1, &chConfig);
}
void GPIO_Init(void) {
GPIO_InitTypeDef gpioConfig = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
// PWM output pin (PA8) as alternate function push-pull
gpioConfig.Pin = PWM_UH_PIN;
gpioConfig.Mode = GPIO_MODE_AF_PP;
gpioConfig.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(PWM_UH_PORT, &gpioConfig);
// Hall sensor input (PA0) as floating input
gpioConfig.Pin = HALL_1_PIN;
gpioConfig.Mode = GPIO_MODE_INPUT;
gpioConfig.Pull = GPIO_NOPULL;
HAL_GPIO_Init(HALL_1_PORT, &gpioConfig);
}
3.2 Hall Signal Processing (Rotor Position)
Three Hall signals are read to determine rotor sector for six-step commutation.
// Read combined Hall state
uint8_t GetHallState(void) {
uint8_t h1 = HAL_GPIO_ReadPin(HALL_1_PORT, HALL_1_PIN);
uint8_t h2 = HAL_GPIO_ReadPin(HALL_2_PORT, HALL_2_PIN);
uint8_t h3 = HAL_GPIO_ReadPin(HALL_3_PORT, HALL_3_PIN);
return (h1 << 2) | (h2 << 1) | h3;
}
// TIM2 interrupt for Hall capture
void TIM2_IRQHandler(void) {
HAL_TIM_IRQHandler(&htim2);
}
// Hall capture callback
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance != TIM2) return;
hall_position = GetHallState();
// Six-step commutation logic
switch (hall_position) {
case 0b001: // Sector 1
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_2);
break;
case 0b011: // Sector 2
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
break;
// ... other sectors
default: break;
}
}
3.3 FOC Algorithm (Field-Oriented Control)
FOC uses Clarke and Park transforms to decouple torque and flux. SVPWM generates the phase voltages.
// Clarke transform (3-phase to 2-phase)
typedef struct { float alpha; float beta; } Clarke;
Clarke ClarkeTransform(float iA, float iB, float iC) {
Clarke clk;
clk.alpha = iA;
clk.beta = (iB - iC) * 0.57735f; // 1/√3
return clk;
}
// Park transform (stationary to rotating)
typedef struct { float d; float q; } Park;
Park ParkTransform(Clarke clk, float theta) {
Park prk;
prk.d = clk.alpha * cosf(theta) + clk.beta * sinf(theta);
prk.q = -clk.alpha * sinf(theta) + clk.beta * cosf(theta);
return prk;
}
// SVPWM generation
void SVPWM(float vd, float vq, float vdc, float theta) {
// Inverse Park (rotating to stationary)
float uAlpha = vd * cosf(theta) - vq * sinf(theta);
float uBeta = vd * sinf(theta) + vq * cosf(theta);
// Normalize to [0,1] and scale to PWM period
float tA = (uAlpha * 0.5f + 0.5f) * vdc;
float tB = ( (-0.5f * uAlpha + 0.8660f * uBeta) * 0.5f + 0.5f ) * vdc;
float tC = ( (-0.5f * uAlpha - 0.8660f * uBeta) * 0.5f + 0.5f ) * vdc;
TIM1->CCR1 = (uint32_t)(tA * (TIM1->ARR + 1));
TIM1->CCR2 = (uint32_t)(tB * (TIM1->ARR + 1));
TIM1->CCR3 = (uint32_t)(tC * (TIM1->ARR + 1));
}
3.4 Fault Protection (Overcurrent, Overvoltage, Undervoltage)
The ADC interrupt monitors the shunt current. If the value exceeds the threshold (e.g., 5A), PWM outputs are disabled.
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc->Instance != ADC1) return;
uint16_t adcVal = HAL_ADC_GetValue(hadc);
float current = (adcVal * 3.3f / 4096.0f) * (100.0f / 0.01f); // Scale to amperes
if (current > 5.0f) {
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_2);
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_3);
HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET); // Fault LED on
}
}
4. Optimizaton Guidelines
- PWM Frequency: Use 5–20 kHz. Lower values reduce switching losses; higher values reduce curent ripple.
- Dead Time: Set based on MOSFET turn-on/off delays. A typical value is 1–2 μs to avoid shoot-through.
- Current Sensing: Employ differential amplification with RC filtering to improve SNR.
- PI Controller Tuning: Start with Kp=0.1, Ki=0.05 and adjust for desired transient response and steady-state accuracy.
5. Debugging and Validation
- Hardware: Measure supply rails (12V, 3.3V) with a multimeter. Use an oscilloscope to verify PWM waveforms (5 kHz, 50% duty) and dead time.
- Firmware: Use a debugger (e.g., Keil MDK) to inspect Hall state variable and ADC readings.
- Performance: Measure motor speed (via encoder) and torque (via sensor). Target speed error <1%, torque error <5% under closed-loop FOC.