Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Designing a STM32-Based BLDC Motor Drive: Schematics and Firmware

Tech Sep 12 2

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.
Tags: STM32

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.