
简介一套基于STM32F407的FOC磁场定向控制位置闭环工程源码面向STM32F4系列单片机开发者用于解决直流无刷电机/永磁同步电机的高精度位置与速度控制问题。工程覆盖电机建模、d-q轴坐标变换、磁场定向、PID闭环调节、PWM逆变控制、死区补偿与过流过压等异常保护利用STM32F407内置浮点运算单元和硬件乘法器可高效执行复杂控制算法。代码已完成时钟、GPIO、ADC、DAC、PWM等外设初始化与中断服务程序编写编译后可直接运行。压缩包共1296个文件约42.5MB。文件以641个C源文件、347个头文件为主要源码另有汇编启动文件、Keil工程文件、CubeMX配置文件、链接脚本、ARM数学库及hex固件等覆盖源码阅读、重新编译、直接烧录和算法库依赖等多类需求。已有381人学习下载。通过这套工程开发者可以快速理解FOC完整流程、位置闭环框架和关键外设配置方式并基于其中完善的初始化与调试结构进一步优化控制策略或扩展应用功能是一份兼顾学习与工程落地的参考实现。1. 位置闭环 FOC 在 STM32F407 上的应用价值做云台、机械臂关节或数控滑台时电机往往不是转起来就行而是要快速转到一个指定角度并牢牢稳住。普通的六步换向加上速度环只能做到匀速或调速位置一旦受到外力扰动就会偏出去必须靠位置闭环来修正。STM32F407 是 F4 系列里最常用的一颗料168MHz 主频带 FPU跑 FOC 的三环电流环、速度环、位置环时电流环 20kHz、位置环 1kHz 还有相当余量这也是很多入门级伺服方案选它的原因。这篇文章围绕 FOC 位置闭环在 STM32F407 上的落地讲清楚三环级联的架构、定时器与 ADC 的配置方式、编码器反馈的接法以及调参和移植到其他 F4 型号时的注意事项。适合手里已经有电机驱动板、想从能转走向能定位的工程师也适合正在评估 F407 做位置伺服方案的开发者。2. FOC 位置闭环的控制架构三个环路如何级联2.1 电流环、速度环、位置环的带宽分配FOC 位置闭环不是单独一个位置环而是三个环路从内到外套在一起电流环最内层速度环在中间位置环最外层。位置环的输出不是 PWM 占空比而是速度给定速度环的输出是电流给定也就是 q 轴电流电流环输出 d/q 轴电压经过逆 Park 和 SVPWM 生成占空比。这个顺序不能乱原因是每个环路的带宽必须从内到外递减否则外环会给内环发一个它跟不上的指令系统容易振荡。常见执行频率是这样分配的环路典型执行频率带宽经验值输出量电流环10kHz ~ 20kHz带宽的 1/10 ~ 1/20d/q 轴电压速度环1kHz ~ 5kHz20Hz ~ 100Hzq 轴电流给定位置环500Hz ~ 1kHz2Hz ~ 20Hz速度给定位置环放在最外层更新频率最低但它决定系统的最终刚度。如果位置环给一个速度阶跃速度环要平滑跟踪电流环要在更短时间内把实际电流顶上去。三者频率相差太多会响应迟钝相差太少则环间耦合振荡。我一般先把电流环固定在 20kHz速度环取它的 1/10 即 2kHz位置环再取 1kHz保留分频余量便于调试。2.2 坐标变换与转子电角度的关系FOC 的核心是坐标变换把三相静止坐标系下的电流变换到随转子旋转的 d/q 坐标系。Clarke 变换把 ia/ib/ic 变成 iα/iβPark 变换再把 iα/iβ 变成 id/iq。这里面最关键的是转子电角度 θe它等于机械角度 θm 乘以极对数 Pn。对位置闭环而言还需要独立的机械角度做位置反馈不能直接拿电角度当位置环输入因为极对数关系会让位置环看到的角度和实际机械位置对不上。// 三相电流合成到 d/q 轴 float ia adc_val[0]; float ib adc_val[1]; float ic -ia - ib; float i_alpha ia; float i_beta (ia 2.0f * ib) * 0.577350269f; // 1/sqrt(3) float cos_theta arm_cos_f32(theta_e); float sin_theta arm_sin_f32(theta_e); float id i_alpha * cos_theta i_beta * sin_theta; float iq -i_alpha * sin_theta i_beta * cos_theta;参数说明theta_e是从编码器机械角度乘以极对数得到的电角度单位是弧度。arm_cos_f32是 CMSIS-DSP 提供的高效三角函数STM32F407 带 FPU用浮点算这三行代码在 20kHz 中断里只占很小一部分时间。实际工程中ic通常不采样而是用基尔霍夫定律算出来可以省一路运放和 ADC 通道。2.3 位置反馈选择增量式编码器与磁编码器位置闭环对角度反馈的分辨率和重复精度要求远高于速度环。霍尔信号一个电周期只有 6 个区段机械角度分辨率太粗低速时位置环会明显抖动。增量式编码器通过正交 A/B 信号在 STM32F4 定时器编码器模式下能达到 4 倍频分辨率1000 线的编码器配上 4 倍频就是 4000 个计数每圈。磁编码器如 AS5047 这类 SPI 输出 14 位绝对角度零位信息直接读出来不需要 Z 脉冲找原点硬件接线更少。选编码器时还要看机械安装位置。编码器装在电机尾部负载侧和装在输出轴负载端的区别很大装在电机轴上位置环控制的是电机角度负载侧有减速比 N 时程序里要把目标角度除以 N 再做控制装在负载端则位置环直接读负载角度但编码器分辨率会被放大 N 倍对安装精度要求更高。F407 的定时器编码器模式不能同时做 PWM 输出所以一般用 TIM2/3/4/5 做编码器输入TIM1/TIM8 做 PWM 输出两者互不干扰。3. 在 STM32F407 上搭建 FOC 位置闭环工程骨架3.1 高级定时器 TIM1 输出互补 PWM 与死区配置FOC 逆变器需要 6 路 PWM 驱动三相半桥STM32F407 的 TIM1 和 TIM8 是两个高级定时器可以直接输出互补 PWM 并带硬件死区插入。死区是防止上下桥臂直通的关键参数一般取 0.5μs 到 2μs具体看 MOS 管的关断延迟。PWM 频率我常用 20kHz此时定时器时钟 168MHzARR 值为 8400-1死区寄存器 DTG 设置取决于预分频倍率。// TIM1 PWM 频率 20kHz死区 1us TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; TIM_BDTRInitTypeDef TIM_BDTRInitStructure; TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_Period 8400 - 1; // 168MHz / 8400 20kHz TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_CenterAligned1; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); TIM_BDTRInitStructure.TIM_OSSRState TIM_OSSRState_Enable; TIM_BDTRInitStructure.TIM_OSSIState TIM_OSSIState_Disable; TIM_BDTRInitStructure.TIM_LOCKLevel TIM_LOCKLevel_OFF; TIM_BDTRInitStructure.TIM_DeadTime 168; // 168 / 168MHz 1us TIM_BDTRInitStructure.TIM_Break TIM_Break_Disable; TIM_BDTRInit(TIM1, TIM_BDTRInitStructure);参数说明TIM_CounterMode_CenterAligned1是中心对齐模式计数器先递增再递减PWM 波形左右对称电流采样点恰好落在计数值为 0 或 ARR 的位置这是 FOC 最常用的对齐方式。TIM_DeadTime为 168 对应死区时间 168 / 168MHz 1μs。如果驱动板用的是光耦隔离或栅极驱动芯片死区偏大一些更安全但要注意死区太大会让电流波形产生明显畸变。3.2 电流采样ADC 注入组与 PWM 触发同步相电流采样必须和 PWM 同步否则采样点落在开关噪声区读到的电流全是毛刺。常见做法是用 TIM1 的 TRGO 事件触发 ADC1 的注入组在中心对齐计数器的下溢点计数值 0启动采样。此时下桥臂导通电流经过采样电阻运放输出已经稳定。F407 上常用双电阻采样采集两相电流第三相用基尔霍夫定律计算。ADC_InitTypeDef ADC_InitStructure; ADC_CommonInitTypeDef ADC_CommonInitStructure; ADC_CommonInitStructure.ADC_Mode ADC_Mode_Independent; ADC_CommonInitStructure.ADC_Prescaler ADC_Prescaler_Div4; // 42MHz ADC_CommonInitStructure.ADC_DMAAccessMode ADC_DMAAccessMode_Disabled; ADC_CommonInitStructure.ADC_TwoSamplingDelay ADC_TwoSamplingDelay_5Cycles; ADC_CommonInit(ADC1, ADC_CommonInitStructure); ADC_InitStructure.ADC_Resolution ADC_Resolution_12b; ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigInjecConv_T1_TRGO; ADC_InitStructure.ADC_ExternalTrigConvEdge ADC_ExternalTrigConvEdge_Rising; ADC_InitStructure.ADC_InjectedScanConvMode ENABLE; ADC_Init(ADC1, ADC_InitStructure);参数说明ADC_ExternalTrigInjecConv_T1_TRGO表示注入组由 TIM1 的 TRGO 上升沿触发触发边沿设为 Rising 保证每次计数周期只采一次。ADC_ScanConvMode在注入组这里配置两路注入通道分别对应采样电阻两相电流。采样完成后在注入组中断里读取ADC1-JDR1和ADC1-JDR2再做零点偏移校准和增益换算。常见坑是 ADC 输入引脚上的 RC 滤波时间常数过大导致采样值滞后于实际电流表现为电流环高频振荡。3.3 编码器接口TIM 正交解码与 Z 脉冲处理增量式编码器 A/B 两路信号接到 F407 某个定时器的 CH1/CH2 引脚配置为编码器模式后定时器计数器会随电机正反转自动加减。速度环和位置环直接读计数器的值就能得到累计位置方向的符号由硬件自动处理。TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0F; // 输入滤波防抖动 TIM_ICInit(TIM2, TIM_ICInitStructure);参数说明TIM_EncoderMode_TI12让定时器同时检测 A、B 两路信号的边沿实现 4 倍频计数。TIM_ICFilter设置为 0x0F 开启输入滤波可滤掉编码器线缆引入的毛刺但滤波会引入延迟高转速下可能丢步需要根据实际转速调整。Z 脉冲可以用来找绝对零位F407 上 Z 信号接到另一个定时器的输入捕获或直接接外部中断在捕获中断里把位置计数器清零。3.4 三环任务在定时器中断里的调度FOC 控制主体在 PWM 中断里执行因为电流环必须紧随采样点延迟越短越好。位置环不需要 20kHz 这么高的频率用计数分频方式每 N 次中断执行一次。下面是一个完整的中断骨架。volatile uint16_t loop_divider 0; void TIM1_UP_TIM10_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update)) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); // 1. 读取 ADC 注入组采样值换算成实际电流 read_current(ia, ib); // 2. 读取编码器计数换算成机械角度和电角度 encoder_angle (int32_t)TIM2-CNT * angle_scale; set_theta_e(encoder_angle * pole_pairs); // 3. 电流环Clarke/Park PI 逆Park foc_current_loop(ia, ib, theta_e, id_ref, iq_ref); // 4. 速度环和位置环分频执行1kHz if (loop_divider 20) { loop_divider 0; speed (encoder_angle - last_angle) * speed_scale; last_angle encoder_angle; speed_loop(speed, speed_ref, iq_ref); position_loop(encoder_angle, position_ref, speed_ref); } // 5. 更新 TIM1 比较寄存器 set_pwm_duty(pwm_compare); } }参数说明loop_divider每次中断加 1到 20 时执行一次速度环和位置环这样电流环 20kHz、速度环和位置环 1kHz。实际工程里速度环可以单独用 2kHz 分频位置环再分频灵活度更高。speed_scale和angle_scale是把编码器计数值换算成 rad/s 和 rad 的缩放系数用浮点乘比做除法快168MHz 主频下能省就是省。这段代码里还没有做电流环的过流保护和使能逻辑工程化时要先判断 PWM 是否解锁再进环。4. FOC 位置闭环调试参数整定与常见坑4.1 电流环 PI 参数估算与整定电流环参数可以按电机定子电感和电阻估算。已知电机的 Ls 和 Rs设定期望电流环带宽PI 参数近似为float bandwidth 2000.0f; // 电流环带宽 2kHz float Ls 0.00012f; // 定子电感 120uH float Rs 0.5f; // 定子电阻 0.5ohm float kp_i Ls * bandwidth; float ki_i Rs * bandwidth;参数说明kp_i的单位是 V/Aki_i的单位是 V/(A·s)。这个估算法基于电机的一阶 RL 模型忽略反电动势和数字延迟实际调试时 Kp 和 Ki 通常要乘 0.5 到 0.8 的系数。如果电流环响应有超调先降 Ki如果高频啸叫降 Kp 或减小采样滤波。给一个经验表带宽目标Kp 估算Ki 估算适用场景1kHzLs × 1000Rs × 1000调试初期保守2kHzLs × 2000Rs × 2000常规伺服响应适中4kHzLs × 4000Rs × 4000高动态需低延迟采样注意不要把带宽设到 PWM 频率的一半以上奈奎斯特限制会导致环路不稳定。4.2 位置环 PD 控制与前馈补偿位置环一般用 P 控制就够比例增益高了会抖动低了会有跟随误差。实际设备里位置环输出是速度给定所以位置环增益 Kp_pos 的单位是(rad/s)/rad也就是1/s。如果负载惯量大纯 P 控制在匀速跟踪目标时会有固定滞后加上速度前馈能明显改善。float kp_pos 10.0f; // 位置环增益 10 (1/s) float kd_pos 0.02f; // 微分项阻尼机械振荡 float ff_vel 1.0f; // 前馈系数通常 0.8 ~ 1.0 float pos_err position_ref - encoder_angle; float vel_ref_ff derivative_of(position_ref); // 目标速度前馈 // 位置环输出到速度环 speed_ref kp_pos * pos_err kd_pos * position_err_dot ff_vel * vel_ref_ff;参数说明derivative_of是目标位置对时间求导实际代码里可以用前后两次目标位置之差乘频率算出。kd_pos这一项不是必须的只有在位置反馈噪声小、机械结构有弹性时才有明显效果增量式编码器在高频抖动下微分项会放大噪声反而让电流环跟着抖建议先不加。前馈系数建议从 0.8 开始调得太满会造成过冲因为速度环和电流环的跟踪本身有相位滞后前馈系数需要和滞后程度匹配。4.3 相序、编码器方向与过流这三类问题的定位FOC 调试中十有八九是先遇到电机啸叫或反转而不是环参数问题。最经典的坑是编码器方向与电机相序不匹配电机正转时电角度递增方向若与编码器计数递增方向相反电流环就会变成正反馈表现为电流一路增大触发过流。检查方法很简单让系统开环给一个固定占空比不要进电流环观察电角度和编码器计数是否同方向变化。现象可能原因检查手段上电即过流相序接反、编码器方向反开环给 5% 占空比对比电角度增量与编码器增量电流有低频振荡电流环 Ki 过大或采样点偏移用示波器看 d/q 轴电流降低 Ki 测试位置稳定但抖动位置环增益过高、机械间隙减小 Kp_pos加低通滤波高速丢位置编码器滤波过强、Z 脉冲干扰示波器测 A/B 波形降低 TIM_ICFilter 值排查顺序建议是先确认编码器方向和相序再看电流环能否稳住一个固定 q 轴电流然后看速度环阶跃响应最后才调位置环。跳过前面直接调位置环往往会把电流环问题掩盖在位置环振荡里难以定位。5. 位置闭环 FOC 的工程化初始定位、限位与跨型号移植5.1 转子初始位置检测与预定位FOC 启动时如果不让转子先对齐电角度是未知的电流环会给出错误的力矩方向常见表现是一上电电机猛抽一下或抖动后过流。最可靠的初始定位方法是预定位给定一个固定的 d 轴电流和零 q 轴电流让转子被定子磁场拉到已知角度。具体做法是在电角度 0 的位置给一个恒定的 id 电流持续 300ms 到 1s等待机械振荡收敛。// 预定位固定电角度 0id 给定 1Aiq 给定 0 theta_e 0.0f; id_ref 1.0f; iq_ref 0.0f; for (int i 0; i 500; i) { foc_current_loop(ia, ib, theta_e, id_ref, iq_ref); delay_ms(1); } encoder_angle 0.0f; // 预定位完成后把当前位置清零作为位置原点参数说明预定位的id_ref大小按电机额定电流的 10% 到 30% 取太小拉不动负载太大冲击过大。带减速箱时这个过程会带动负载移动机械上需要有让位空间。若使用绝对值磁编码器可以直接读到 0 到 360 度的绝对位置不需要预定位上电即知道电角度这是它比增量式编码器更省事的主要原因。5.2 软限位、硬限位与 Z 脉冲归零位置闭环的应用场景必然有限位需求。硬限位靠机械撞块或光电开关直接切断 PWM软限位在位置环里做当位置给定或当前位置接近边界时把速度给定限制在一个很小的安全值。增量式编码器丢失原点后要归零常见做法是先低速朝限位方向运行撞到限位开关后反转再找 Z 脉冲清零。if (encoder_angle soft_limit_min) { speed_ref 0.0f; position_ref soft_limit_min; }参数说明软限位不能只限制位置给定还需要把速度给定钳位到零否则位置环的积分项会把速度顶到边界上。Z 脉冲归零时限位开关的位置精度只要到一两圈以内即可最终零位由 Z 脉冲确定因为 Z 脉冲每圈固定且重复精度远高于限位开关。这里要注意 Z 脉冲在电机停转时也可能因为振动误触发代码里要先确认当前在限位开关区域内再接受 Z 脉冲清零。5.3 从 STM32F407 移植到 F411、F401、F446 的要点标题里写了支持 STM32F4 系列单片机实际移植时主要改三处定时器资源、ADC 通道和 DMA 请求映射。F407 有 TIM1/TIM8 两个高级定时器F401 和 F411 同样有但 F401 最高主频只有 84MHz电流环中断执行时间要控制在 20kHz 周期内就比较紧张建议把 PWM 降到 16kHz并且把电流环里的浮点运算精简或者把速度环降频。F446 可以跑到 180MHz资源相对宽裕移植时几乎不需要动中断代码。芯片型号主频PWM 定时器ADC 位数移植关注点STM32F407168MHzTIM1/TIM812bit基准平台STM32F40184MHzTIM1/TIM812bit降低电流环频率STM32F411100MHzTIM1/TIM812bit检查中断执行时间STM32F446180MHzTIM1/TIM812bit基本无需改动移植流程上先把system_stm32f4xx.c里的时钟配置换成目标芯片的 PLL 参数再用 CubeMX 重新生成 GPIO 和定时器的初始化把原来 F407 代码里涉及 DMA 的 ADC 通道映射改为目标芯片对应的 DMA 请求号。F4 系列外设寄存器结构体基本相同控制算法代码可以原样复用风险点集中在时钟频率不同导致的定时器分频系数和 ADC 采样时间变化改完先用开环测试确认 PWM 频率和死区符合预期再进闭环调试。本文还有配套的精品资源点击获取