简介:STM32F407 FOC位置闭环电机驱动资源,主要面向需要实现精确位置控制的STM32F4系列开发者,适用于BLDC或PMSM电机应用场景。资源基于STM32F407硬件平台,系统讲解磁场定向控制算法,涵盖电机建模、坐标变换、PID环路调节、死区补偿及异常保护等关键环节,代码已完成时钟、GPIO、ADC、PWM等外设初始化,可直接编译运行。压缩包共1296个文件,以C源文件(641个)、头文件(347个)和汇编文件(78个)为主,辅以工程配置文件、链接脚本及hex/axf等编译产物,整体大小42.5MB,目录结构清晰,便于检索与二次开发。已有381人学习下载。通过学习该资源,开发者可掌握FOC核心原理与工程实现方法,理解位置环与速度环的参数整定思路,并能基于现成工程快速构建自己的电机控制原型,是嵌入式与电机控制领域实用的学习与开发资料。
1. 为什么工程上把 STM32F407 FOC 位置闭环当通用起点
STM32F407 在 FOC 里之所以被反复提起,不是因为性能天花板高,而是因为它把“做位置闭环最需要的资源”都放在了同一颗芯片上:168MHz Cortex-M4F 带 FPU,算 Clark/Park 和电流环 PI 有余力;TIM1/TIM8 可以直接输出带死区的互补 PWM;ADC 能由高级定时器触发,配合中央对齐计数完成三相电流采样;通用定时器还能进编码器接口,一条增量编码器通道不需要额外接处理电路。对做云台、桌面机械臂、小型自动化滑台或门机控制的人来说,这套组合意味着可以从电流环一路做上去,一直到角度到位,整个链路不用换主控。
位置闭环和单纯速度闭环的区别在于,外环控制对象是角度误差,输出的是速度指令,再由速度环和电流环把它变成输出电压。这里最容易被新手忽略的不是 PID 怎么调,而是三个环的采样频率和反馈来源必须分清楚。STM32F407 上跑 10kHz 电流环、1kHz 速度环、200Hz 位置环仍然有可观余量,所以它成了 FOC 入门教程里最常见的平台之一,也确实是 STM32F4 系列里性价比和可移植性都更稳的选型。
2. 位置闭环的 FOC 控制架构:三层环路与坐标系变换
2.1 为什么位置闭环一定要三层环路
位置闭环不是把目标角度直接丢给 FOC,而是沿着“位置 → 速度 → 电流 → 电压”逐层传递。位置环的带宽最低,它只负责回答“该向哪个方向以多快速度走”;速度环负责抵消负载和摩擦扰动,转速误差由它修正;电流环最内层,保证定子电流跟随 id/iq 指令,是整个系统频响最高的一环。
三环级联有个实际意义:每一环的饱和限幅本身就是在保护下一环。位置环输出被限速,速度环输出被限流,电流环输出被限制在母线电压以内。这样在调试时不需要担心某个环节超调后直接烧管。常见频率安排为电流环 10kHz 或 16kHz,速度环 1kHz,位置环 200Hz 到 500Hz。如果再往上提速度环频率,容易把编码器差分得到的速度噪声放大。
| 控制环 | 反馈量 | 输出量 | 典型执行频率 |
|---|---|---|---|
| 位置环 | 编码器角度 | 速度指令 | 200Hz ~ 500Hz |
| 速度环 | 编码器微分速度 | iq 电流指令 | 1kHz |
| 电流环 | 采样相电流 | vd/vq 电压 | 10kHz ~ 16kHz |
这里的频率划分不是硬规定,但顺序不能反过来。位置环输出直接给电流环的话,相当于用一个慢得不行的信号去驱动一个很快的电流环,系统抖动和到位过冲都会变得很难收敛。
2.2 从电流到磁场的 Clark/Park 变换
FOC 的核心是把三相交流电流变成同步旋转坐标系下的 id 和 iq。id 对应磁通方向电流,iq 对应力矩方向电流。对表贴式 PMSM,最省事的控制策略是让 id 收敛到 0,iq 专门负责出力。
等幅值变换下,Clark 变换可以写成:
iα = ia iβ = (ia + 2 * ib) / √3得到两相静止坐标系后,再用转子电角度 θ 做 Park 变换。下面这段是直接放到电流环中断里执行的代码:
void clarke_park(float ia, float ib, float theta, float *id, float *iq) { float i_alpha = ia; float i_beta = (ia + 2.0f * ia) * 0.577350269f; // 1/sqrt(3) float c = cosf(theta); float s = sinf(theta); *id = i_alpha * c + i_beta * s; *iq = -i_alpha * s + i_beta * c; }这里相电流ic不用单独采样,按基尔霍夫电流定律用ic = -ia - ib计算即可。theta是电角度,不是机械角度,它由编码器位置乘上极对数得到。代码里的投影方向如果反转,会出现负反馈变成了正反馈,最典型的表现就是一上电就飞车。
SVPWM 则是在电流环算出 vd/vq 之后做一次反 Park,再把相电压折算成三个占空比:
void set_svpwm(float vd, float vq, float theta, float vbus) { float c = cosf(theta); float s = sinf(theta); float v_alpha = vd * c - vq * s; float v_beta = vd * s + vq * c; float ua = v_alpha; float ub = -0.5f * v_alpha + 0.8660254f * v_beta; float uc = -0.5f * v_alpha - 0.8660254f * v_beta; // 注入零序分量,等效 SVPWM float offset = 0.5f * (MAX3(ua, ub, uc) + MIN3(ua, ub, uc)); ua -= offset; ub -= offset; uc -= offset; uint32_t arr = __HAL_TIM_GET_AUTORELOAD(&htim1); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, (uint32_t)(arr * (0.5f + ua / vbus))); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, (uint32_t)(arr * (0.5f + ub / vbus))); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, (uint32_t)(arr * (0.5f + uc / vbus))); }中心对齐模式下,比较寄存器相对自动重载值的比例就是占空比。0.5 加u/vbus相当于把负半周电压抬到 PWM 能表示的区间。这里有几点必须确认:vbus要带电阻分压后的实际值;占空比写回后要等下一次更新事件生效,不要在同一中断里反复改写溢出时间。
2.3 位置反馈:编码器、磁编码器与无感 FOC 的边界
位置闭环对角度反馈的实时性要求远高于稳态精度。增量编码器便宜且接口简单,但上电不知道绝对位置,必须回零或预定位。绝对磁编码器如 AS5047 一类,可以直接读出 0 到 360 度机械角,适合做需要断电保存位置的结构,但 SPI 读取会挤占电流环时间,通常要把位置读取放在速度环前一个节拍,再做一点补偿。
| 反馈类型 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 增量编码器 | 接口简单、实时性高 | 上电无绝对角、需回零 | 云台、小车、常规位置闭环 |
| 绝对磁编码器 | 上电即有绝对位置 | SPI 读取延迟、价格贵 | 机械臂关节、需要断电记忆 |
| 无感 FOC | 省掉编码器 | 低速带载能力弱、启动复杂 | 风机、泵类、不要求极端低速 |
所谓位置闭环支持无感方案也能做,但低速时转子初始位置检测和堵转检测会比带编码器困难很多,不建议第一次做位置闭环就上无感。STM32F407 的位置闭环调试,默认还是从带编码器的方案入手,把外环参数、电流波形和堵转保护都验证过之后,再考虑剥掉位置传感器。
3. STM32F407 的可复现硬件与定时器分配方案
3.1 外设分配:TIM1 出 PWM,通用定时器进编码器,ADC 抓相电流
我一般会把 TIM1 作为主 PWM 定时器,因为 STM32F407 上只有 TIM1 和 TIM8 是带互补输出和死区的高级控制定时器。TIM1 的三组互补通道可以直接驱动三相全桥,死区时间放在寄存器里配置,比用软件延迟插入死区可靠得多。
编码器接口用 TIM2 到 TIM5 里的一个通用定时器,配置成编码器模式后,计数器直接跟随 AB 相脉冲变化。以 TIM4 为例,它对应 PB6/PB7,和 TIM1 的 PWM 引脚不冲突,接线时也更顺。
| 功能 | 外设 | 常用引脚 | 说明 |
|---|---|---|---|
| 三相互补 PWM | TIM1 CH1/CH2/CH3 | PA8/PA9/PA10 | 互补停在 PB13/PB14/PB15 |
| 编码器 AB 相 | TIM4 CH1/CH2 | PB6/PB7 | 配置为 TI12 编码器模式 |
| 相电流 A | ADC1_IN0 | PA0 | 经运放或检流电阻放大 |
| 相电流 B | ADC1_IN1 | PA1 | 和 A 同步采样 |
| 母线电压 | ADC1_IN2 | PA2 | 过采样后折算 vbus |
电流采样用的是 ADC1 的两个通道一起采样,F407 的 ADC 支持双 ADC 同步模式,能用同一个 TIM1 触发源同时转换两路电流。如果手上只有单电阻或双电阻采样方案,这个表换成 GPIO 和定时器触发关系也要跟着改,但主 PWM 定时器不用换。
3.2 三相电流采样窗口和死区设置
使用三电阻采样时,采样点不能落在上下桥臂都切换的死区附近,否则相电流可能还没建立起来。常见做法是把电流采样点放在中心对齐计数器的顶点或底点附近,利用 PWM 输出高电平中间段采样。STM32F407 的 ADC 最快转换时间可以做到很窄,但要给采样保持电容留时间,分频系数不能只追求快。
死区大小和电机驱动板上的栅极驱动延迟有关。IGBT 驱动通常需要 1 到 2 微秒死区,而 MOSFET 驱动常见 500ns 以下。F407 的 TIM1 死区寄存器用TIM_BreakDeadTimeConfig配置,参数单位是定时器时钟周期。168MHz 定时器时钟下,死区寄存器值为 84 表示 500ns。
还有一个常见问题:采样点靠近 PWM 边界时,电流重构会受到占空比限制。低速大电流或高速反电动势很大的工况下,某一相占空比接近极限,检流电阻上的窗口会被压缩。调试时如果发现电流波形在扇区边界出现毛刺,优先检查触发相位,而不是急着换 PI 参数。
3.3 初始化:PWM、编码器接口的代码怎么写
PWM 初始化的核心是把 TIM1 配成中心对齐模式,周期 4200 对应 20kHz。STM32F407 定时器时钟 168MHz,中心对齐模式下实际 PWM 频率按 168MHz 除以两倍周期计算,所以Period = 4200 - 1正好是 20kHz。
htim1.Instance = TIM1; htim1.Init.Prescaler = 0; htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1; htim1.Init.Period = 4200 - 1; // 20kHz PWM htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; HAL_TIM_PWM_Init(&htim1);中心对齐模式选择最重要。CENTERALIGNED1 表示计数器向下计数时产生比较事件,CENTERALIGNED2 表示向上计数时产生,不同模式会影响 ADC 触发点。建议选 CENTERALIGNED1,并把 ADC 触发配置成计数器上溢或下溢事件,这样采样点在 PWM 中心,电流最平稳。
编码器接口的初始化并不复杂,关键是使能 TIM4 的编码器模式后,计数器值就代表位置。配置如下:
TIM_Encoder_InitTypeDef enc = {0}; enc.EncoderMode = TIM_ENCODERMODE_TI12; // AB 相双边沿计数,4 倍频 enc.IC1Polarity = TIM_ICPOLARITY_RISING; enc.IC2Polarity = TIM_ICPOLARITY_RISING; enc.IC1Filter = 0x0f; enc.IC2Filter = 0x0f; HAL_TIM_Encoder_Init(&htim4, &enc); HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);TIM_ENCODERMODE_TI12会让定时器在 A 相和 B 相的每个边沿都计数一次,四倍频效果。滤波器设为 0x0f 是为了滤掉电机带来的共模干扰,但如果 PWM 和编码线距离太近,这个值还要加大。编码器模式下的计数器默认是 16 位,用一个int16_t接收差值做速度计算,就能规避掉溢出判断。
4. FOC 位置闭环整定顺序:电流环、速度环、位置环
4.1 电流环 PI 从电机参数算起
电流环是三层闭环里最容易整定的,因为它面对的是电气时间常数。表贴式电机的定子等效模型由相电阻 R 和相电感 L 决定,电流环闭环带宽 f_c 通常取 PWM 频率的 1/10 左右。20kHz PWM 下,电流环带宽可以往 1kHz 到 2kHz 试探。
电流环 PI 初始值按下面公式估算:
Kp_i = L * 2 * π * fc Ki_i = Kp_i * R / L这里的 Kp 单位是 V/A,Ki 单位是 V/(A·s)。实际数字控制在中断里做累加时,积分项写integral += Ki * err * dt,不要再把 Ki 去掉 dt 重新折算。
| 环路 | Kp 参考 | Ki 参考 | 判断方法 |
|---|---|---|---|
| 电流环 | L * 2 * π * fc | Kp * R / L | 阶跃时电流无振铃,id 稳定回到 0 |
| 速度环 | 0.01 ~ 0.05 A/rpm | 0.1 ~ 1 A/(rpm·s) | 匀速段基本无静差 |
| 位置环 | 5 ~ 30 rpm/° | 0 | 到位快、无明显超调 |
实际电机如果标称参数不准确,可以用 LCR 电桥直接测相线间电感,再除以 2 得到一相电感。没有仪表的场合,可以先把 Ki 设得很小,Kp 往上加,直到电流波形出现高频振铃,再退回 70%。电流环调不好时,后面所有环都会抖。
4.2 速度环的抗饱和与加速度限制
速度环输入是位置环给的速度指令,输出是 iq 电流指令。速度环本身是带饱和的,iq 指令必须限制在峰值电流以内,否则启动瞬间电流会打满并触发过流保护。我一般把 iq 指令限幅放在速度环积分器之前,积分器输出也做同样限幅,防止积分饱和。
位置环输出速度指令还要额外限幅。最大速度不一定要给到电机能跑的上限,先按机械结构和减速比安全值设。机械臂关节如果减速比 100:1,输出端速度可能只要几十转每分钟就够了;但电机本身高速端可能要几千转,限幅不加会把位置环增益调高的后果直接放大成撞击限位。
速度反馈不要直接用编码器计数的原始差分。20kHz 或 1kHz 下,每拍差分值只有几个脉冲,噪声很大。先做一阶低通,再用低通后的速度参与 PI 计算。低通系数 0.2 到 0.3 通常够用,系数太小会让速度环相位滞后,位置闭环时容易振荡。
4.3 位置环的刚度、阻尼和前馈
位置环先用纯 P 控制,不要直接加积分。位置环加积分容易在摩擦较大的场合引起极限环震荡。P 系数越大,静态刚度越高,但会让速度环饱和更快,出现明显超调。给位置环加阻尼的常见做法不是加 D,而是把速度环做得更硬,让位置误差转化成速度指令后能立刻得到响应。
下面这段是三个闭环在同一个时刻各自触发时的级联逻辑,把位置环、速度环、电流环算清楚:
if (pos_tick) { float pos_err = target_pos - current_pos; // 位置环不积分,稳态误差靠速度环前馈消除 speed_ref = CLAMP(KP_POS * pos_err, -SPEED_LIMIT, SPEED_LIMIT); } if (speed_tick) { speed_est = lpf_apply(&speed_lpf, encoder_speed_rpm); float err = speed_ref - speed_est; integ_speed = CLAMP(integ_speed + KI_SPD * err * SPD_PERIOD, -CURRENT_LIMIT, CURRENT_LIMIT); iq_ref = CLAMP(KP_SPD * err + integ_speed, -CURRENT_LIMIT, CURRENT_LIMIT); } if (cur_tick) { // 读取ia/ib,做Clark/Park,得到 id_now/iq_now float iq_err = iq_ref - iq_now; integ_iq = CLAMP(integ_iq + KI_CUR * iq_err * CUR_PERIOD, -V_LIMIT, V_LIMIT); float vq = KP_CUR * iq_err + integ_iq; // id 环路同理 set_svpwm(vd, vq, elec_angle, VBUS); }三个环路共用一套级联数据,但只有在对应的pos_tick、speed_tick、cur_tick标志位到来时才更新,不要在 while 循环里按顺序一次性全跑一遍。位置环每 5ms 执行一次,速度环每 1ms 执行一次,电流环每 0.1ms 执行一次,这个节奏就能满足大部分 FOC 位置闭环需求。
5. 把位置闭环跑稳:零位标定、堵转检测、波形调测
5.1 编码器零位与电角度零点的标定流程
用增量编码器做位置闭环,第一件要做的是找电角度零点。如果电角度和编码器位置没有对齐,FOC 得到的 id/iq 是错的,电流环就会失控。
简单做法是给电机施加固定 vd 电压,让转子预定位到 A 相轴线方向,然后读取编码器计数值作为零点。STM32F407 里不需要额外硬件,只要在初始化后先不开速度环,直接写一个固定电压矢量,等转子停稳即可。
// FOC 初始化后,先让转子预定位 set_pwm_voltage(1.5f, 0.0f); // 向 d 轴注入电压,转子会转到零点 HAL_Delay(500); zero_count = __HAL_TIM_GET_COUNTER(&htim4);zero_count保存的是机械角度零点对应的编码器计数值。运行时,电角度由(encoder_count - zero_count) * 极对数换算。注意如果电机带减速器,零点标定要在输出轴和电机轴之间解耦之后再做,否则减速比会让零点偏移一个很大的分度值。
5.2 堵转检测不能只靠位置误差
位置闭环最容易出的保护问题是堵转。位置误差大不一定是堵转,可能是速度环还在加速阶段,也可能是目标位置太远但速度饱和。堵转更可靠的判据是“速度指令很大,但实际速度和电流同时异常”。
常见实现是同时看两个条件:速度接近 0,iq 电流超过正常负载阈值,并且持续一段可配置的时间。下面的逻辑把两个条件放在同一个积分里:
if ((fabsf(speed_est) < 1.0f) && (fabsf(iq_now) > 2.0f)) { stall_acc += fabsf(pos_err) * CUR_PERIOD; if (stall_acc > 0.5f) { disable_inverter(); } } else { stall_acc = 0.0f; }阈值 2A 和累积位置误差 0.5 都要根据电机额定电流调整。启动瞬间电流本来就会短暂超过阈值,所以必须有一个时间窗口,不能一看到大电流就停机。堵转检测在无感 FOC 场景里更敏感,因为低速下没有编码器速度信号,只能靠反电动势估计,带编码器时做位置闭环反而不需要太激进的阈值。
5.3 用 DAC 把 FOC 波形搬出来看
串口打印电流环数据只能看到离线曲线,看不到突变瞬间。STM32F407 内部有两个 DAC 通道,可以把 iq、id、位置误差这类变量直接映射到引脚上,用示波器观察。最简单的做法是让电流环每拍更新一次 DAC 输出:
DAC_SetChannel1Data(DAC_ALIGN_12B_R, (uint32_t)((iq_now + CURRENT_SPAN) / (2.0f * CURRENT_SPAN) * 4095.0f));CURRENT_SPAN是电流量程的一半,比如电流范围 -5A 到 5A,它取 5.0f。DAC 输出范围是 0 到 3.3V,所以先做偏移再缩放。DAC1 默认在 PA4,DAC2 在 PA5,如果这两个引脚同时又被 ADC 或 GPIO 占用,需要先检查原理图。示波器上观察到的 FOC 波形如果出现以下现象,排查方向很明确:
| 波形现象 | 排查点 |
|---|---|
| id 不在 0 附近 | 电角度零点偏了、Park 符号反了 |
| iq 在固定频率正弦振荡 | 速度环带宽太高或速度滤波滞后 |
| 电流在扇区边界毛刺 | 电流采样窗口太靠近死区、三电阻重构失败 |
| 位置到位后低速抖动 | 死区补偿未做、位置环 P 过大 |
6. 位置控制“手感”提升:速度前馈、梯形规划与死区补偿
6.1 用梯形速度规划替代纯比例位置环
纯比例位置环在大范围运动时,速度会先高速冲,再靠误差不断减速,到位终点会有一截刹车过程。更好的做法是在位置环外面做一层速度规划,让位置环只负责修正规划值和实际位置的偏差。梯形规划代码量不大,可以直接放在 200Hz 位置更新周期里:
float plan_trapezoid(float target_pos, float current_pos, float *prev_vref, float dt, float accel, float vmax) { float dist = target_pos - current_pos; float dir = (dist > 0.0f) ? 1.0f : -1.0f; float vref = *prev_vref + dir * accel * dt; if (vref > vmax) vref = vmax; float vmax_for_stop = sqrtf(2.0f * accel * fabsf(dist)); if (vref > vmax_for_stop) vref = vmax_for_stop; *prev_vref = vref; return vref * dir; }这个函数生成的vref作为速度前馈,最终给速度环的速度指令是vref + KP_POS * pos_err。这样位置环的 P 系数不需要太高,速度环也不会被一个过大的阶跃速度打到饱和。加减速度accel根据负载实际调,负载越大,accel 越小。
6.2 把“上一拍 iq”接到下一拍做电流前馈
位置闭环和速度环调试稳定后,可以再往 iq 参考里加一个前馈项:把上一拍电流环算出的vq或当前速度环输出的一小部分叠加到下一拍。这么做对减小惯量较大的系统滞后有帮助,但要注意前馈系数不能等于 1,否则会掩盖速度环本身的问题。
更稳妥的是在梯形规划里把当前加速度求出来,再乘上负载惯量和力矩常数的比值,得到电流前馈iq_ff = J * accel / Kt。这个做法对变负载不适用,只在定惯量场景下效果明显。第一次做位置闭环时先不加,等波形看清楚了再加,一次只加入一个变量。
6.3 零位漂移如何处理
使用增量编码器时,每次上电必须先回零,否则机械零点会漂。回零的方式有两种:一是限位开关,二是预定位。预定位虽然快,但会让系统在上电瞬间动一下,不适合有空间干涉的装置。绝对磁编码器不存在这个问题,但磁编码器安装偏心会在旋转一周内产生位置误差,导致低速下 iq 出现周期性波动。
处理磁编码器误差最直接的办法是做多点标定。转一圈取 12 或 24 个参考点,把误差表存在 Flash 里,运行时对编码器角度做查表修正。最后在调试时把 PWM 最小占空比从 2% 开始加,逐象限去调死区补偿的方向和脉宽,低速跟位精度会比直接加大位置环 P 更有效。
本文还有配套的精品资源,点击获取