ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于STM32F4的FOC串级PID电机控制实现

2026/8/31 12:01:23 拓冰建站 浏览量
基于STM32F4的FOC串级PID电机控制实现 在实际的电机控制项目中单纯靠速度环或位置环很难让电机在高速、重载或变负载场景下保持稳定。这是因为电机内部的电磁转矩和反电动势会随转速和负载变化如果没有对电流进行实时闭环控制速度环输出的控制量会被电机电气特性所扭曲。这就是为什么工业级伺服驱动都采用电流环、速度环、位置环三层串级结构而底层电流环必须基于**FOC磁场定向控制**算法才能实现高效、低纹波的转矩控制。本文以STM32F4系列MCU为核心配合增量式编码器作为位置和速度反馈详细讲解如何实现从电流环到位置环的完整串级PID控制。文章会先解释每一层控制环的作用和设计依据再给出硬件选型、驱动配置、PID参数整定方法最后提供完整的代码框架和调试指南。无论你是刚接触FOC的嵌入式工程师还是正在从方波驱动转向正弦波驱动的开发者都能从中找到可直接复用的工程思路。1. 先理解FOC和串级PID为什么是“标配”1.1 FOC解决什么问题直流无刷电机BLDC或永磁同步电机PMSM的定子绕组需要产生旋转磁场来拖动转子。如果只是简单地按六步换相法方波驱动给绕组通电电流波形是梯形的转矩脉动大高速时效率低。FOCField-Oriented Control的核心思想是将三相定子电流投影到与转子磁场同步旋转的坐标系dq轴上从而像控制直流电机一样独立控制磁通和转矩。在FOC中电流环是唯一直接与电机电气特性打交道的环节。它通过对Id励磁电流和Iq转矩电流的闭环调节让电机实际电流快速跟随目标电流。速度环和位置环输出的目标值最终都折算成Iq的给定值因此电流环的控制质量直接决定了整个系统的带宽和稳定性。1.2 串级结构为什么比单环更好假设我们只需要控制电机转到某个位置如果只用一个位置环直接输出PWM占空比会面临两个问题位置环输出需要同时克服负载转矩和电机惯性但负载变化时环路的响应速度不够快容易产生超调或震荡。没有电流内环的限流保护意外堵转时会瞬间烧毁MOS管或电机。串级结构将控制任务分层内环电流环响应最快负责电磁转矩的精确生成。中环速度环响应中等负责让转速快速跟随目标。外环位置环响应最慢负责最终的定位精度。每一层都只关心自己范围内的物理量内环的输出约束条件如电流限幅可以保护外环的调节器不会输出过大的控制量。这种结构在工程上被称为“级联控制”在电机驱动、无人机飞控、机器人关节控制中几乎是标准做法。1.3 三个环的典型带宽和采样频率在设计串级控制时一个基本原则是内环的采样频率至少是外环的5到10倍。否则内环来不及响应外环的变化级联效果会退化。控制环典型带宽采样频率建议输出目标电流环1kHz ~ 10kHz10kHz ~ 100kHz占空比PWM速度环100Hz ~ 1kHz1kHz ~ 10kHzIq参考值位置环10Hz ~ 100Hz100Hz ~ 1kHz速度参考值STM32F4主频168MHz配合硬件定时器和ADC完全可以在100kHz电流环频率下运行。实际项目中电流环通常放在PWM定时器更新中断或ADC采样完成中断中执行速度环和位置环可以放在较低优先级的中断或主循环任务中。2. 硬件平台与环境准备2.1 核心器件选型器件推荐型号作用MCUSTM32F405RGT6 / STM32F407VGT6主控自带FPU、高级定时器、双ADC电机400W以下PMSM或BLDC极对数4~8被控对象带霍尔或编码器接口编码器增量式编码器 2500线每圈10000脉冲提供位置和速度反馈驱动芯片DRV8301 / TMC4671 / 分立MOSIR2101三相逆变桥带电流检测电流采样两个下桥臂采样电阻 运放电路用于FOC的Clarke变换电源24V~48V直流电源功率≥电机额定功率2倍供电2.2 STM32F4的定时器与ADC配置FOC控制需要精确的PWM生成和同步ADC采样。典型配置如下使用TIM1或TIM8作为高级定时器输出三对互补PWM中心对齐模式频率20kHz~50kHz。配置ADC1和ADC2工作在注入组模式由TIM1的TRGO触发在PWM的中间点即上下计数器相等时采样两相电流。由于FOC需要三相电流但第三相电流可以通过IaIbIc0计算得到所以只需采样两相。配置示例HAL库// TIM1 中心对齐模式PWM频率20kHz TIM_HandleTypeDef htim1; htim1.Instance TIM1; htim1.Init.Prescaler 0; // 168MHz htim1.Init.Period 4200; // 168MHz / (4200 1) 40kHz但中心对齐实际频率20kHz htim1.Init.CounterMode TIM_COUNTERMODE_CENTERALIGNED1; htim1.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIMEx_PWMN_Init(htim1, TIM_CHANNEL_1, sConfigOC); // 三通道 // ADC1 注入组由TIM1 TRGO触发 ADC_HandleTypeDef hadc1; hadc1.Instance ADC1; hadc1.Init.ScanConvMode ENABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T1_TRGO; hadc1.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_RISING;2.3 编码器接口增量式编码器输出A、B两相差分信号Z信号用于零位校准。使用TIM2或TIM3的编码器模式TIM_Encoder_InitTypeDef sEncoder; sEncoder.EncoderMode TI_ENCODERMODE_TI12; // 四倍频 sEncoder.IC1Polarity TIM_ICPOLARITY_RISING; sEncoder.IC1Selection TIM_ICSELECTION_DIRECTTI; sEncoder.IC1Prescaler TIM_ICPSC_DIV1; sEncoder.IC1Filter 10; sEncoder.IC2Polarity TIM_ICPOLARITY_RISING; sEncoder.IC2Selection TIM_ICSELECTION_DIRECTTI; sEncoder.IC2Prescaler TIM_ICPSC_DIV1; sEncoder.IC2Filter 10; HAL_TIM_Encoder_Init(htim3, sEncoder);初始化后读取TIM3的计数器值即可得到位置脉冲数。速度可以通过计算两个采样时刻之间的脉冲差得到或者使用定时器捕获脉冲周期M法或T法。3. 电流环实现与PID参数3.1 FOC电流环流程电流环每执行一次需要完成以下步骤读取ADC采样值得到Ia和Ib计算Ic -Ia - Ib。对三相电流进行Clarke变换得到Iα和Iβ。根据当前转子电角度θ进行Park变换得到Id和Iq。计算Id误差和Iq误差分别送入PI控制器。PI控制器输出Vd和Vq进行反Park变换得到Vα和Vβ。使用SVPWM算法将Vα、Vβ转换为三路PWM占空比。其中电角度θ由编码器位置脉冲乘以极对数得到但需要做角度归一化0~2π和初始校准。3.2 电流环PI控制器参数整定电流环的PI参数通常采用工程设计法先忽略电阻和反电动势将电机视为一个电感负载然后根据期望的电流环带宽计算。对于PMSM电流环开环传递函数近似为G(s) Kp * (1 Ki/s) * (1/(R sL))。为了消除零点令Ki R/L则闭环传递函数变为一阶低通Gc(s) Kp/(sL Kp)。带宽ωb Kp/L。因此期望电流环带宽1000 rad/s约160Hz则Kp L * ωb。Ki R/L。实际项目中电阻R和电感L可以通过电机参数表或LCR表测量得到。但最终参数还需要在电机旋转时微调因为电感会随位置变化。常见做法是先给一个保守的Kp比如计算值的50%然后逐步增加直到电流波形出现轻微振荡再回调90%。Id和Iq的PI控制器可以共用一套参数但实际中Id环的Ki可以比Iq环小一些因为Id通常被控制为0不需要快速跟踪。3.3 电流环的代码实现伪代码void CurrentLoop_Handler(void) { // 1. 读取ADC float Ia ADC_GetValue(ADC_CH_Ia); float Ib ADC_GetValue(ADC_CH_Ib); float Ic -Ia - Ib; // 2. Clarke变换 float Ialpha Ia; float Ibeta (Ia 2.0f * Ib) / 1.73205f; // sqrt(3) // 3. 获取电角度 float theta motor.theta_elec; float sin_t sinf(theta); float cos_t cosf(theta); // 4. Park变换 float Id Ialpha * cos_t Ibeta * sin_t; float Iq -Ialpha * sin_t Ibeta * cos_t; // 5. 电流误差 float errId motor.Id_ref - Id; float errIq motor.Iq_ref - Iq; // 6. PI控制器离散化位置式 motor.Id_integral errId * motor.Ki_Id * DT_CURRENT; motor.Iq_integral errIq * motor.Ki_Iq * DT_CURRENT; // 积分限幅 motor.Id_integral CLAMP(motor.Id_integral, -V_MAX, V_MAX); motor.Iq_integral CLAMP(motor.Iq_integral, -V_MAX, V_MAX); float Vd motor.Kp_Id * errId motor.Id_integral; float Vq motor.Kp_Iq * errIq motor.Iq_integral; // 7. 反Park变换 float Valpha Vd * cos_t - Vq * sin_t; float Vbeta Vd * sin_t Vq * cos_t; // 8. SVPWM float t1, t2, t0; SVPWM_Calc(Valpha, Vbeta, t1, t2, t0); TIM1-CCR1 ...; // 更新三路PWM占空比 }注意电流环代码必须放在高优先级中断中且不能有其他阻塞操作。所有变量建议使用float类型STM32F4的FPU支持单精度浮点运算速度足够。4. 速度环与位置环的实现4.1 速度环的设计速度环的输入是外部给定的目标转速rpm和实际转速rpm输出是Iq参考值。实际转速通过编码器脉冲差分计算得到常用方法是M法测频法或T法测周法。M法适用于中高速通过固定时间窗口内的脉冲数计算速度。T法适用于低速通过测量两个脉冲的时间间隔。在FOC系统中通常使用M法因为电流环的采样频率已经很高可以每几个电流环周期计算一次速度。速度环的采样频率通常在1kHz左右即每1ms执行一次。PI参数的整定原则速度环带宽一般取电流环带宽的1/10到1/5。如果电流环带宽1000 rad/s则速度环带宽取100~200 rad/s。速度环的PI控制器与电流环类似但积分项通常需要附加**抗饱和Anti-windup**机制因为速度环输出限幅Iq_max会使得积分持续累积导致恢复时超调。4.2 位置环的设计位置环的输入是目标位置圈数或角度和当前位置由编码器累计得到输出是目标转速。位置环通常采用比例控制P或比例前馈控制很少使用积分项因为积分会导致位置过冲和稳态振荡。位置环的采样频率一般为100~200Hz即每5~10ms执行一次。比例系数Kp_pos的选取假设位置环带宽为10 rad/s则Kp_pos 10单位rad/s per rad。但实际中需要根据电机允许的最大加速度进行限幅。位置环输出的速度参考值要经过速度前馈补偿才能获得更好的动态响应。例如在机器人关节中目标位置是连续变化的如果不加前馈位置环单独工作的跟踪误差会很大。4.3 串级连接的整体逻辑void PositionLoop_Handler(void) { float pos_err motor.pos_ref - motor.pos_actual; motor.speed_ref CLAMP(Kp_Pos * pos_err, -SPEED_MAX, SPEED_MAX); } void SpeedLoop_Handler(void) { float speed_err motor.speed_ref - motor.speed_actual; motor.Iq_ref CLAMP(Kp_Speed * speed_err Ki_Speed * speed_integral, -IQ_MAX, IQ_MAX); // 积分限幅 } void CurrentLoop_Handler(void) { // 如上 }执行顺序先执行位置环更新速度参考再执行速度环更新Iq参考最后执行电流环。注意每个环的采样频率不同通常通过定时器或任务调度实现。5. 编码器接口与数据处理5.1 位置计算增量式编码器给出的脉冲数需要转换为机械角度。假设编码器每圈脉冲数为2500四倍频后为10000则每个脉冲对应的机械角度为360/10000 0.036度。电角度需要乘以极对数例如极对数为4则电角度分辨率 0.036 * 4 0.144度。在代码中用一个int32_t变量来累计脉冲数int32_t encoder_pulses TIM3-CNT; // 直接读取 // 处理溢出定时器计数模式为0~65535需要软件扩展 static int32_t encoder_prev 0; int32_t delta encoder_pulses - encoder_prev; if (delta 32768) delta - 65536; else if (delta -32768) delta 65536; encoder_prev encoder_pulses; motor.pos_pulses delta;5.2 速度计算速度计算可以使用M法每10ms读取一次脉冲数计算差值除以时间间隔再转换为rpm。static uint32_t last_time 0; static int32_t last_pulses 0; uint32_t now HAL_GetTick(); if (now - last_time 10) { int32_t delta_pulses motor.pos_pulses - last_pulses; float speed_rpm (float)delta_pulses / (encoder_resolution) * 60.0f / ((now - last_time) / 1000.0f); last_pulses motor.pos_pulses; last_time now; motor.speed_actual speed_rpm; }5.3 编码器初始校准FOC需要知道电机转子初始位置否则无法启动。如果编码器有Z信号可以在上电时让电机转动到Z脉冲位置然后记录该位置对应的电角度。如果没有Z信号可以采用高频注入法或预定位法给一个固定方向的电流让转子吸合到已知位置。预定位法简单可靠先给定一个Iq比如0.5A等待电机稳定然后记录当前编码器位置作为转子零位。6. 完整代码框架与关键函数6.1 项目文件结构project/ ├── Inc/ │ ├── foc.h │ ├── pid.h │ ├── encoder.h │ ├── motor.h │ └── main.h ├── Src/ │ ├── main.c │ ├── foc.c │ ├── pid.c │ ├── encoder.c │ ├── motor.c │ └── stm32f4xx_it.c └── Drivers/6.2 PID控制器封装一个通用的PID结构体支持位置式和增量式并带有积分限幅和输出限幅。typedef struct { float Kp; float Ki; float Kd; float integral; float prev_error; float output_max; float integral_max; float dt; // 采样周期 } PID_Controller; void PID_Init(PID_Controller *pid, float Kp, float Ki, float Kd, float dt, float out_max) { pid-Kp Kp; pid-Ki Ki; pid-Kd Kd; pid-integral 0; pid-prev_error 0; pid-output_max out_max; pid-integral_max out_max * 0.8f; // 积分限幅为输出限幅的80% pid-dt dt; } float PID_Update(PID_Controller *pid, float error) { pid-integral error * pid-Ki * pid-dt; // 积分限幅 if (pid-integral pid-integral_max) pid-integral pid-integral_max; if (pid-integral -pid-integral_max) pid-integral -pid-integral_max; float derivative (error - pid-prev_error) / pid-dt; float output pid-Kp * error pid-integral pid-Kd * derivative; // 输出限幅 if (output pid-output_max) output pid-output_max; if (output -pid-output_max) output -pid-output_max; pid-prev_error error; return output; }6.3 主循环框架void main(void) { // 初始化硬件 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM1_Init(); // PWM MX_ADC1_Init(); // 电流采样 MX_TIM3_Init(); // 编码器 MX_USART1_UART_Init(); // 调试串口 // 初始化电机参数 Motor_Init(motor); PID_Init(pid_current_d, Ld, R, 0.0f, 1e-4f, VD_MAX); // 具体参数需计算 PID_Init(pid_current_q, Lq, R, 0.0f, 1e-4f, VQ_MAX); PID_Init(pid_speed, Kp_sp, Ki_sp, 0.0f, 0.001f, IQ_MAX); PID_Init(pid_position, Kp_pos, 0.0f, 0.0f, 0.01f, SPEED_MAX); // 启动定时器中断 HAL_TIM_Base_Start_IT(htim1); // 电流环中断 HAL_TIM_Base_Start_IT(htim6); // 1ms定时器用于速度环 HAL_TIM_Base_Start_IT(htim7); // 10ms定时器用于位置环 while(1) { // 与上位机通信更新目标位置等 // 可以在这里处理非实时任务 } }6.4 中断服务函数// TIM1更新中断电流环20kHz void TIM1_UP_TIM10_IRQHandler(void) { HAL_TIM_IRQHandler(htim1); CurrentLoop_Handler(); } // TIM6中断速度环1kHz void TIM6_DAC_IRQHandler(void) { HAL_TIM_IRQHandler(htim6); SpeedLoop_Handler(); } // TIM7中断位置环100Hz void TIM7_IRQHandler(void) { HAL_TIM_IRQHandler(htim7); PositionLoop_Handler(); }7. 运行验证与调试方法7.1 开环测试在连接电机之前先用示波器观察PWM波形是否正确。中心对齐模式下上管和下管之间应有死区时间通常1~2us。然后接上电机不连接编码器给定一个固定的Vd、Vq例如Vd0, Vq2V电机应该能缓慢旋转。如果电机抖动或电流过大说明PWM或ADC采样有问题。7.2 电流环调试将速度环和位置环旁路直接给一个固定的Iq参考值例如0.5AId参考值为0。用示波器观察采样到的Ia、Ib波形应该呈现正弦波形状。如果波形畸变检查ADC采样时刻是否在PWM中心点。电流采样电阻的增益和偏置是否正确。电角度θ是否与转子实际位置同步。7.3 速度环调试速度环调试时给一个固定的目标转速例如500rpm观察实际转速的响应曲线。如果出现振荡先减小Kp再增加Ki。如果稳态误差大增加Ki并减小Kp。使用串口打印转速和Iq值可以绘制曲线进行分析。7.4 位置环调试位置环调试时目标位置从0度到90度阶跃观察电机是否快速定位且无超调。如果超调减小Kp_pos如果响应太慢适当增加Kp_pos但要注意可能引起电机啸叫。8. 常见问题与排查路径8.1 电机不转电流很大现象可能原因排查方法电机不转母线电流很大电角度错误检查编码器Z信号校准或预定位后确认电角度初始化电机抖动电流波形异常三相线序接反交换任意两相线序电机可以转但效率低Id没有控制为0检查Park变换角度符号确保Id闭环8.2 电流环啸叫电流环啸叫通常是带宽太高或运算精度问题。检查是否使用了过大的Kp导致系统不稳定电流采样是否引入了噪声可以在ADC前加RC滤波截止频率1/2采样频率。是否忘了积分限幅积分饱和会导致PI输出振荡。8.3 速度环低速时震荡低速时编码器脉冲间隔大M法测速精度低。可以采用T法测速或者结合观测器如滑模观测器来估计速度。另外速度环的Ki在低速时容易积分饱和考虑使用变积分方法当误差大时减少积分作用。8.4 位置环到达目标后不断微动位置环只使用P控制时如果存在静摩擦或负载稳态误差可能不为零。可以加入很小的积分项Ki_pos 0.1 * Kp_pos但必须配合积分限幅避免积分饱和导致过冲。或者采用死区控制在误差小于一定阈值时停止积分。9. 最佳实践与扩展方向9.1 代码层面的优化将电流环的三角函数sin/cos改为查表法减少计算时间。STM32F4虽然支持FPU但sinf仍需要几十个周期查表可以降到个位数周期。使用DMA传输ADC数据避免中断中等待。将SVPWM计算与PWM更新分离电流环中只计算占空比实际更新可以放在PWM定时器影子寄存器传递时。9.2 参数整定清单步骤操作检查项1测量电机相电阻和相电感使用LCR表记录R、Ld、Lq2计算电流环Kp、Ki根据带宽公式计算初始值取计算值50%3调试电流环给Iq0.5A观察电流波形是否正弦有无振荡4调试速度环给定目标转速500rpm观察阶跃响应调节Kp、Ki5调试位置环给定位置阶跃观察超调量和稳定时间6加入负载测试增加负载检查各环响应是否在预算内9.3 扩展方向无传感器FOC去掉编码器使用观测器如滑模观测器、扩展卡尔曼滤波器估计转子位置和速度。适合成本敏感或空间受限的场景。MTPA控制对于内置式永磁同步电机IPMSM在高速区利用磁阻转矩通过调整Id提高效率。弱磁控制当电机转速超过基速时通过减小Id来降低反电动势从而扩展转速范围。多电机同步控制在多轴运动系统中各电机的位置环可以通过交叉耦合或主从控制实现同步。9.4 生产环境注意事项电流环的代码必须放在最高优先级中断中且不能调用HAL_Delay等阻塞函数。在电机运行前先进行硬件故障检测检查母线电压、IGBT温度、编码器断线。加入看门狗防止程序跑飞导致电机失控。所有PI参数的积分限幅必须小于输出限幅避免积分饱和导致失控。串级PID控制看似复杂但一旦理解了每一层的物理意义和设计原则调试起来就像搭积木一样清晰。从电流环开始逐步向上叠加每一步都验证后再进入下一步这是最稳妥的工程方法。希望这篇文章能帮你从零开始搭建一套可用的FOC串级控制系统并在实际项目中少走弯路。