用STM32F103给无刷电机做‘大脑’手把手教你实现FOC算法附完整代码1. 项目准备与硬件搭建当你第一次拿到STM32F103开发板和无刷电机时可能会被复杂的接线和算法吓到。别担心我们从最基础的硬件连接开始。我用的是一块俗称蓝色药丸的STM32F103C8T6核心板搭配一个50W的无刷电机和三相驱动板。硬件清单STM32F103C8T6开发板72MHz主频带3个高级定时器三相无刷电机驱动板我用的是基于IR2104的方案12V电源适配器电流采样电阻0.01Ω/5W逻辑分析仪可选用于调试PWM波形接线时最容易出错的是电机三相线与驱动板的对应关系。我的经验是先用万用表测量电机三相绕组的电阻确保U/V/W相正确对应驱动板输出。驱动板的使能信号要接STM32的GPIOPWM输入接TIM1的CH1/CH2/CH3通道。注意首次上电前务必检查所有接线特别是电源极性。我曾因为接反电源烧毁过驱动芯片。2. CubeMX基础配置打开STM32CubeMX选择STM32F103C8T6芯片首先配置时钟树// 时钟配置在System Core RCC中设置 HSE时钟8MHz PLL倍频x9 系统时钟72MHz APB1分频/2 (36MHz) APB2分频/1 (72MHz)接下来配置关键外设定时器TIM1生成PWM时钟源内部时钟通道1/2/3PWM Generation CH1/CH2/CH3PWM模式PWM模式1预分频0计数周期719910kHz PWM频率死区时间500ns根据驱动芯片规格调整ADC配置电流采样// 在Analog ADC1中设置 采样时间239.5周期 通道0/1规则组对应电流采样 触发源TIM1_TRGO 连续转换模式禁用 扫描模式启用生成代码前记得在Project Manager中勾选Generate peripheral initialization as a pair of .c/.h files。3. FOC算法实现FOC的核心在于三个关键变换我们先从Clarke变换开始// foc.c #include foc.h #include arm_math.h // 使用CMSIS-DSP库加速计算 // Clarke变换三相静止→两相静止 void Clarke_Transform(float Ia, float Ib, float *Ialpha, float *Ibeta) { *Ialpha Ia; *Ibeta (Ia 2*Ib) * ONE_BY_SQRT3; // 1/√3 ≈ 0.577 }Park变换需要转子角度信息我用的是霍尔传感器获取的机械角度// 电角度计算假设电机极对数为4 float Get_Electrical_Angle(float mech_angle) { return mech_angle * POLE_PAIRS; } // Park变换两相静止→两相旋转 void Park_Transform(float Ialpha, float Ibeta, float theta, float *Id, float *Iq) { float cos_theta arm_cos_f32(theta); float sin_theta arm_sin_f32(theta); *Id Ialpha*cos_theta Ibeta*sin_theta; *Iq -Ialpha*sin_theta Ibeta*cos_theta; }电流环PI控制器实现typedef struct { float Kp, Ki; float integral; float limit; } PI_Controller; float PI_Update(PI_Controller *pi, float error) { pi-integral error; // 积分限幅 if(pi-integral pi-limit) pi-integral pi-limit; else if(pi-integral -pi-limit) pi-integral -pi-limit; return pi-Kp * error pi-Ki * pi-integral; }4. SVPWM实现技巧SVPWM是将电压矢量转换为PWM占空比的关键环节。我优化过的扇区判断算法// 判断电压矢量所在扇区1-6 uint8_t SVM_Sector(float Ualpha, float Ubeta) { float Uref1 Ubeta; float Uref2 (SQRT3*Ualpha - Ubeta)/2; float Uref3 (-SQRT3*Ualpha - Ubeta)/2; return (Uref10 ? 1:0) | (Uref20 ? 2:0) | (Uref30 ? 4:0); }矢量作用时间计算以扇区1为例void SVM_CalcTime(uint8_t sector, float Ualpha, float Ubeta, float Udc, uint32_t PWM_period, float *T1, float *T2) { float sqrt3_T SQRT3 * PWM_period / Udc; switch(sector) { case 1: // Sector I *T1 ( SQRT3*Ualpha - Ubeta) * sqrt3_T; *T2 ( 2*Ubeta) * sqrt3_T; break; // 其他扇区类似... } // 时间限幅 if(*T1 *T2 PWM_period) { float scale PWM_period / (*T1 *T2); *T1 * scale; *T2 * scale; } }5. 调试与优化调试FOC系统时我习惯先用串口打印关键变量// 在main.c中添加调试输出 printf(Ia%.2f, Ib%.2f, Iq%.2f, Speed%.1f\r\n, currents.Ia, currents.Ib, currents.Iq, motor_speed);用逻辑分析仪捕获的PWM波形应该呈现如下特征三相互补对称死区时间清晰可见占空比随负载变化常见问题排查电机抖动不转检查霍尔传感器接线和极对数设置电流采样异常确认运放增益和ADC基准电压PWM无输出验证TIM1的MOE主输出使能是否开启6. 完整工程结构我的项目目录结构如下/FOC_Controller │── /Drivers │── /Core │ ├── Src/main.c │ └── Inc/main.h │── /FOC │ ├── Src/foc.c # FOC算法实现 │ ├── Inc/foc.h │ └── Src/svm.c # SVPWM实现 │── /BSP │ ├── Src/adc.c # 电流采样 │ └── Src/hall.c # 位置检测关键初始化代码// main.c int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM1_Init(); MX_ADC1_Init(); // 启动PWM HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_1); // 同样启动CH2/CH3... // 启用TIM1中断PWM周期中断 HAL_TIM_Base_Start_IT(htim1); while(1) { // 主循环处理速度指令等 } }7. 进阶优化方向当基础FOC运行稳定后可以尝试以下优化无传感器启动// 初始位置检测 void AlignRotor(void) { // 施加固定方向的电压矢量 SetPWM_Duty(0.5, 0.0, 0.5); HAL_Delay(100); // 此时转子会对齐到特定位置 }参数自整定注入小信号测量电机电阻通过阶跃响应辨识电感自动计算PI参数代码优化技巧使用CMSIS-DSP库加速三角函数运算将Park变换等频繁调用的函数放在RAM中执行采用Q15格式定点数运算适合无FPU的MCU我在实际项目中发现合理调整电流环采样时机能显著改善性能。最佳采样点是在PWM周期中间此时电流纹波最小。
用STM32F103给无刷电机做‘大脑’:手把手教你实现FOC算法(附完整代码)
发布时间:2026/6/26 11:34:17
用STM32F103给无刷电机做‘大脑’手把手教你实现FOC算法附完整代码1. 项目准备与硬件搭建当你第一次拿到STM32F103开发板和无刷电机时可能会被复杂的接线和算法吓到。别担心我们从最基础的硬件连接开始。我用的是一块俗称蓝色药丸的STM32F103C8T6核心板搭配一个50W的无刷电机和三相驱动板。硬件清单STM32F103C8T6开发板72MHz主频带3个高级定时器三相无刷电机驱动板我用的是基于IR2104的方案12V电源适配器电流采样电阻0.01Ω/5W逻辑分析仪可选用于调试PWM波形接线时最容易出错的是电机三相线与驱动板的对应关系。我的经验是先用万用表测量电机三相绕组的电阻确保U/V/W相正确对应驱动板输出。驱动板的使能信号要接STM32的GPIOPWM输入接TIM1的CH1/CH2/CH3通道。注意首次上电前务必检查所有接线特别是电源极性。我曾因为接反电源烧毁过驱动芯片。2. CubeMX基础配置打开STM32CubeMX选择STM32F103C8T6芯片首先配置时钟树// 时钟配置在System Core RCC中设置 HSE时钟8MHz PLL倍频x9 系统时钟72MHz APB1分频/2 (36MHz) APB2分频/1 (72MHz)接下来配置关键外设定时器TIM1生成PWM时钟源内部时钟通道1/2/3PWM Generation CH1/CH2/CH3PWM模式PWM模式1预分频0计数周期719910kHz PWM频率死区时间500ns根据驱动芯片规格调整ADC配置电流采样// 在Analog ADC1中设置 采样时间239.5周期 通道0/1规则组对应电流采样 触发源TIM1_TRGO 连续转换模式禁用 扫描模式启用生成代码前记得在Project Manager中勾选Generate peripheral initialization as a pair of .c/.h files。3. FOC算法实现FOC的核心在于三个关键变换我们先从Clarke变换开始// foc.c #include foc.h #include arm_math.h // 使用CMSIS-DSP库加速计算 // Clarke变换三相静止→两相静止 void Clarke_Transform(float Ia, float Ib, float *Ialpha, float *Ibeta) { *Ialpha Ia; *Ibeta (Ia 2*Ib) * ONE_BY_SQRT3; // 1/√3 ≈ 0.577 }Park变换需要转子角度信息我用的是霍尔传感器获取的机械角度// 电角度计算假设电机极对数为4 float Get_Electrical_Angle(float mech_angle) { return mech_angle * POLE_PAIRS; } // Park变换两相静止→两相旋转 void Park_Transform(float Ialpha, float Ibeta, float theta, float *Id, float *Iq) { float cos_theta arm_cos_f32(theta); float sin_theta arm_sin_f32(theta); *Id Ialpha*cos_theta Ibeta*sin_theta; *Iq -Ialpha*sin_theta Ibeta*cos_theta; }电流环PI控制器实现typedef struct { float Kp, Ki; float integral; float limit; } PI_Controller; float PI_Update(PI_Controller *pi, float error) { pi-integral error; // 积分限幅 if(pi-integral pi-limit) pi-integral pi-limit; else if(pi-integral -pi-limit) pi-integral -pi-limit; return pi-Kp * error pi-Ki * pi-integral; }4. SVPWM实现技巧SVPWM是将电压矢量转换为PWM占空比的关键环节。我优化过的扇区判断算法// 判断电压矢量所在扇区1-6 uint8_t SVM_Sector(float Ualpha, float Ubeta) { float Uref1 Ubeta; float Uref2 (SQRT3*Ualpha - Ubeta)/2; float Uref3 (-SQRT3*Ualpha - Ubeta)/2; return (Uref10 ? 1:0) | (Uref20 ? 2:0) | (Uref30 ? 4:0); }矢量作用时间计算以扇区1为例void SVM_CalcTime(uint8_t sector, float Ualpha, float Ubeta, float Udc, uint32_t PWM_period, float *T1, float *T2) { float sqrt3_T SQRT3 * PWM_period / Udc; switch(sector) { case 1: // Sector I *T1 ( SQRT3*Ualpha - Ubeta) * sqrt3_T; *T2 ( 2*Ubeta) * sqrt3_T; break; // 其他扇区类似... } // 时间限幅 if(*T1 *T2 PWM_period) { float scale PWM_period / (*T1 *T2); *T1 * scale; *T2 * scale; } }5. 调试与优化调试FOC系统时我习惯先用串口打印关键变量// 在main.c中添加调试输出 printf(Ia%.2f, Ib%.2f, Iq%.2f, Speed%.1f\r\n, currents.Ia, currents.Ib, currents.Iq, motor_speed);用逻辑分析仪捕获的PWM波形应该呈现如下特征三相互补对称死区时间清晰可见占空比随负载变化常见问题排查电机抖动不转检查霍尔传感器接线和极对数设置电流采样异常确认运放增益和ADC基准电压PWM无输出验证TIM1的MOE主输出使能是否开启6. 完整工程结构我的项目目录结构如下/FOC_Controller │── /Drivers │── /Core │ ├── Src/main.c │ └── Inc/main.h │── /FOC │ ├── Src/foc.c # FOC算法实现 │ ├── Inc/foc.h │ └── Src/svm.c # SVPWM实现 │── /BSP │ ├── Src/adc.c # 电流采样 │ └── Src/hall.c # 位置检测关键初始化代码// main.c int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM1_Init(); MX_ADC1_Init(); // 启动PWM HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_1); // 同样启动CH2/CH3... // 启用TIM1中断PWM周期中断 HAL_TIM_Base_Start_IT(htim1); while(1) { // 主循环处理速度指令等 } }7. 进阶优化方向当基础FOC运行稳定后可以尝试以下优化无传感器启动// 初始位置检测 void AlignRotor(void) { // 施加固定方向的电压矢量 SetPWM_Duty(0.5, 0.0, 0.5); HAL_Delay(100); // 此时转子会对齐到特定位置 }参数自整定注入小信号测量电机电阻通过阶跃响应辨识电感自动计算PI参数代码优化技巧使用CMSIS-DSP库加速三角函数运算将Park变换等频繁调用的函数放在RAM中执行采用Q15格式定点数运算适合无FPU的MCU我在实际项目中发现合理调整电流环采样时机能显著改善性能。最佳采样点是在PWM周期中间此时电流纹波最小。