伺服电机FOC调试指南:三环PI整定 + 电角度零点对齐
电机通电后疯狂抖动,示波器上Id/Iq波形像心电图一样乱跳。或者电机明明在转,但力气小得可怜,效率低得离谱。如果你经历过这种场景,大概率是电角度零点没对齐,或者PI参数没调好。
这两个问题几乎是每个FOC开发者必经的"劫难"。网上的资料要么只讲理论公式不给代码,要么只贴代码不讲为什么这么设。本文给你一套完整的、可复现的工程化调参流程:先对齐电角度零点,再从内到外逐环整定PI参数。每一步都附带C代码和参数计算公式,拿去就能用。
FOC三环结构:为什么从内往外调?
FOC控制系统采用级联三环架构,从内到外依次是电流环、速度环、位置环。电流环控制电磁转矩,响应最快,带宽最高(通常1~5kHz)。速度环把目标转速转化为目标电流,带宽约为电流环的五分之一到十分之一。位置环最外层,把目标位置转化为目标速度,带宽最低。
下面这张框图展示了三环的级联关系和信号流向:

三环之间是嵌套关系:位置环的输出是速度环的参考输入,速度环的输出是电流环的参考输入。这意味着内环必须比外环快得多,外环才能把内环当作"已经调好的黑盒"来用。
所以调参顺序是铁律:先电流环,再速度环,最后位置环。如果电流环不稳定,速度环和位置环无论怎么调都不会好——地基没打好,楼是盖不起来的。
但在调电流环之前,还有一件更基础的事:电角度零点必须先对齐。因为FOC的Park变换依赖精确的转子电角度,零点不对,Id和Iq的解耦就是错的,后面所有的PI调参都建立在错误的基础上。
第一步:电角度零点对齐
为什么需要对齐?
FOC算法中,Park变换将静止坐标系(αβ)下的电流旋转到与转子同步的坐标系(dq)上。这个变换依赖一个关键量——转子电角度θe。
电角度和机械角度的关系是:θe = P × θm + θ_offset。其中P是极对数,θm是编码器读到的机械角度,θ_offset是电角度零点偏移。这个偏移量取决于编码器的安装位置,每台电机装配后都不一样,必须通过校准来确定。
如果θ_offset不准确,你以为自己在控制Iq(产生转矩的电流),实际上有一部分电流跑到Id上去了(产生磁化而非转矩),结果就是力气变小、效率变低、电机发热。偏差大到一定程度,电机甚至会抖动或堵转。
方法一:强制d轴定位法
这是最经典、最简单的校准方法。原理是:给电机施加一个已知方向的固定电压/电流矢量(对齐到电角度0°即d轴方向),转子在磁力作用下会主动对齐到该方向。此时读取编码器,反推出偏移量。
// 电角度零点校准 - 强制d轴定位法
// 适用平台:STM32 + 增量式/绝对式编码器
#define ALIGN_VOLTAGE 2.0f // 对齐电压幅值(V),需根据电机调整
#define ALIGN_TIME_MS 2000 // 等待对齐时间(ms)
#define POLE_PAIRS 7 // 电机极对数
float calibrate_zero_offset(void)
{
// 1. 设置对齐电角度为0°(d轴方向)
float theta_e_set = 0.0f;
// 2. 反Park变换:Vd=ALIGN_VOLTAGE, Vq=0, theta=0
// 得到 Vα = Vd*cos(0) - Vq*sin(0) = ALIGN_VOLTAGE
// Vβ = Vd*sin(0) + Vq*cos(0) = 0
float V_alpha = ALIGN_VOLTAGE * cosf(theta_e_set);
float V_beta = ALIGN_VOLTAGE * sinf(theta_e_set);
// 3. 输出到SVPWM,驱动电机
SVPWM_SetVoltage(V_alpha, V_beta);
// 4. 等待转子稳定对齐
HAL_Delay(ALIGN_TIME_MS);
// 5. 读取编码器当前机械角度
float theta_m = Encoder_GetAngle_Rad(); // 单位:rad
// 6. 计算电角度零点偏移
// 此时实际电角度应为0,所以:
// 0 = POLE_PAIRS * theta_m + offset
// offset = -POLE_PAIRS * theta_m
float offset = -((float)POLE_PAIRS) * theta_m;
// 7. 归一化到 [0, 2π)
offset = fmodf(offset, 2.0f * M_PI);
if (offset < 0) offset += 2.0f * M_PI;
// 8. 关闭输出
SVPWM_SetVoltage(0, 0);
return offset;
}
使用时的注意事项:
对齐电压不宜过大,否则转子会"弹过头"来回振荡;也不宜太小,否则无法克服摩擦力完成对齐。一般取额定电压的10~20%作为起点试探。等待时间要足够长,确保转子完全静止(通过编码器读数确认不再变化)。
方法二:正反转平均法(更精确)
强制定位法有一个固有误差来源:齿槽转矩(cogging torque)会让转子偏离真正的d轴位置。正反转平均法可以消除这个误差:
float calibrate_zero_offset_bidirectional(void)
{
// 正向对齐:电角度设为0°
SVPWM_SetVoltage(ALIGN_VOLTAGE, 0);
HAL_Delay(ALIGN_TIME_MS);
float theta_m_forward = Encoder_GetAngle_Rad();
// 反向对齐:电角度设为180°(π)
float V_alpha_rev = ALIGN_VOLTAGE * cosf(M_PI); // = -ALIGN_VOLTAGE
float V_beta_rev = ALIGN_VOLTAGE * sinf(M_PI); // = 0
SVPWM_SetVoltage(V_alpha_rev, V_beta_rev);
HAL_Delay(ALIGN_TIME_MS);
float theta_m_reverse = Encoder_GetAngle_Rad();
// 关闭输出
SVPWM_SetVoltage(0, 0);
// 正向偏移
float offset_fwd = -(float)POLE_PAIRS * theta_m_forward;
// 反向偏移(此时实际电角度应为π)
float offset_rev = M_PI - (float)POLE_PAIRS * theta_m_reverse;
// 取平均消除齿槽转矩影响
float offset = (offset_fwd + offset_rev) / 2.0f;
// 归一化
offset = fmodf(offset, 2.0f * M_PI);
if (offset < 0) offset += 2.0f * M_PI;
return offset;
}
校准验证
校准完成后必须验证。方法很简单:设定Vd=0,Vq=正值(比如1V),电机应当匀速正转(具体方向取决于你的坐标系定义)。用示波器或调试工具观察Id,它应该接近于0。如果Id明显不为0,说明偏移角有误;如果电机反转,说明偏移差了约180°(π弧度)。
第二步:电流环PI参数整定
电角度对齐完成后,正式进入三环调参。电流环是第一个要调的。
数学推导:为什么Kp=L×ωc,Ki=R×ωc?
PMSM在dq旋转坐标系下,忽略交叉耦合项后,q轴电压方程简化为:
Vq = R × Iq + Lq × dIq/dt
这是一个标准的一阶RL系统,传递函数为 G(s) = 1/(Ls + R)。我们用PI控制器 C(s) = Kp + Ki/s 来控制它。
开环传递函数 = C(s) × G(s) = (Kp×s + Ki) / [s × (Ls + R)]
如果我们令 Ki/Kp = R/L(即让PI的零点恰好对消被控对象的极点),开环传递函数简化为:
L_open(s) = Kp / (L × s)
这是一个纯积分器,闭环带宽就是 ωc = Kp/L,即 Kp = L × ωc。由零点对消条件 Ki/Kp = R/L,得到 Ki = R × ωc。
这就是极点对消法(也叫零极点配置法)的核心——用PI的零点对消电机RL环节的极点,使系统变成纯积分环节,带宽由Kp直接决定。
带宽如何选?
电流环带宽fc(Hz)的经验公式:fc = PWM频率 / (510)。假如你的PWM频率是20kHz,电流环带宽选24kHz比较合适。带宽越高响应越快,但对噪声和采样延时越敏感。
数值计算实例
以一台7极对PMSM为例:
| 参数 | 值 |
|---|---|
| 相电阻 Rs | 0.5 Ω |
| 相电感 Ls | 1.2 mH |
| PWM频率 | 20 kHz |
| 目标带宽 fc | 2000 Hz |
计算过程:
- ωc = 2π × 2000 = 12566 rad/s
- Kp = Ls × ωc = 0.0012 × 12566 = 15.08
- Ki = Rs × ωc = 0.5 × 12566 = 6283
离散化处理
实际代码中PI控制器是离散执行的,积分项需乘以采样周期Ts:
// 电流环PI控制器(dq轴通用)
typedef struct {
float Kp;
float Ki;
float integral;
float output_limit; // 输出限幅(对应母线电压限制)
float integral_limit; // 积分限幅(防饱和)
} PI_Controller_t;
float PI_Update(PI_Controller_t *pi, float ref, float fdb, float Ts)
{
float error = ref - fdb;
// 比例项
float p_out = pi->Kp * error;
// 积分项(带限幅)
pi->integral += pi->Ki * error * Ts;
if (pi->integral > pi->integral_limit) pi->integral = pi->integral_limit;
if (pi->integral < -pi->integral_limit) pi->integral = -pi->integral_limit;
// 总输出(带限幅)
float output = p_out + pi->integral;
if (output > pi->output_limit) output = pi->output_limit;
if (output < -pi->output_limit) output = -pi->output_limit;
return output;
}
// 初始化电流环PI
void CurrentLoop_Init(PI_Controller_t *pi_d, PI_Controller_t *pi_q)
{
float Ls = 0.0012f; // 电感 1.2mH
float Rs = 0.5f; // 电阻 0.5Ω
float fc = 2000.0f; // 目标带宽 2kHz
float wc = 2.0f * M_PI * fc;
pi_d->Kp = Ls * wc; // = 15.08
pi_d->Ki = Rs * wc; // = 6283
pi_d->integral = 0;
pi_d->output_limit = 12.0f; // 根据母线电压设定
pi_d->integral_limit = 10.0f;
// q轴参数相同(表贴式PMSM,Ld≈Lq)
*pi_q = *pi_d;
}
调试验证
给Iq设一个阶跃参考(比如从0跳到1A),用示波器或调试上位机观察响应波形:

- 理想响应:快速上升到目标值,无超调或极小超调(<5%),无持续振荡
- Kp偏大:响应快但有振荡或明显超调 → 减小Kp(减小带宽)
- Kp偏小:响应慢,上升时间长 → 增大Kp
- Ki偏大:低频振荡,积分饱和 → 减小Ki
- Ki偏小:稳态有残差 → 增大Ki
第三步:速度环PI参数整定
电流环调好后,把它当作一个"已知增益为1"的内部模块,开始调速度环。
参数计算
速度环控制的对象是电机的机械动态方程:J × dω/dt = Kt × Iq - TL
其中J是转动惯量,Kt是转矩系数,TL是负载转矩。忽略负载扰动,传递函数为 G_v(s) = Kt / (J×s)——一个纯积分环节。
速度环PI参数:
- Kp_v = J × ωv / Kt
- Ki_v ≈ Kp_v × ωv / 5
其中ωv为速度环带宽(rad/s),一般取电流环带宽的1/5~1/10。
不知道J怎么办?工程调参法
实际中转动惯量J往往不精确,这时可以用"先P后I"的工程方法:
- 设Ki=0,只用比例控制
- 逐步增大Kp_v,每次增大10~20%,观察阶跃响应
- 直到速度出现持续振荡,记录此时的Kp为Kp_osc
- 最终 Kp_v = Kp_osc × 0.6~0.7
- 逐步增大Ki_v,直到稳态速度误差消除,但不产生新的振荡
- 若超调过大,减小Ki_v
// 速度环PI初始化(工程经验值,需根据实际调整)
void SpeedLoop_Init(PI_Controller_t *pi_speed)
{
pi_speed->Kp = 0.5f; // 初始值,需调试确定
pi_speed->Ki = 5.0f; // 初始值,先设为0调Kp
pi_speed->integral = 0;
pi_speed->output_limit = 10.0f; // 限制最大电流指令(A)
pi_speed->integral_limit = 8.0f;
}
// 速度环调用(典型执行频率:1~5kHz)
float speed_ref = target_speed_rad_s;
float speed_fdb = encoder_get_speed_rad_s();
float Iq_ref = PI_Update(&pi_speed, speed_ref, speed_fdb, Ts_speed);
第四步:位置环参数整定
位置环是最外层,控制相对简单。多数伺服系统的位置环只用比例控制器(P控制),输出为目标速度:
// 位置环P控制器(带速度前馈)
float PositionLoop_Update(float pos_ref, float pos_fdb,
float vel_ff, float Kp_pos)
{
float pos_error = pos_ref - pos_fdb;
float speed_ref = Kp_pos * pos_error + vel_ff;
// 速度限幅
if (speed_ref > MAX_SPEED) speed_ref = MAX_SPEED;
if (speed_ref < -MAX_SPEED) speed_ref = -MAX_SPEED;
return speed_ref;
}
位置环Kp的物理意义就是带宽(单位rad/s)。若速度环带宽300Hz,位置环带宽取60100Hz(即Kp_pos ≈ 377628)。实际调试时从小值开始增大,观察位置阶跃响应,当末端出现振荡时回退20~30%。
速度前馈vel_ff可以显著改善跟踪精度,特别是在做轨迹规划时。它的值等于目标位置对时间的导数(即规划速度),让速度环不用等位置误差积累就能提前输出。
避坑清单
电角度校准的坑:
极对数P必须精确。如果你不确定电机的极对数,可以手动缓慢转动转子一圈,同时观察编码器电角度变化了几个完整周期——那就是极对数。极对数错一个,整个FOC都会乱。
电流环的坑:
高转速时反电动势(Back-EMF)会"吃掉"母线电压裕量。如果不做反电动势前馈补偿,高速段电流环会因为输出饱和而失控。前馈公式:Vd_ff = -ωe × Lq × Iq,Vq_ff = ωe × (Ld × Id + ψf)。
速度环的坑:
速度估算的噪声会被Kp直接放大,变成电流指令的噪声。如果编码器分辨率不高(比如1000线增量式),速度环带宽不宜设太高,或者需要对速度反馈做低通滤波。但滤波又会引入相位滞后,需要在噪声和响应速度之间权衡。
总结
FOC三环PI调参的核心方法论可以浓缩为四句话:
- 先校准电角度零点——一切的前提
- 电流环用公式算——Kp = L × ωc,Ki = R × ωc
- 速度环先P后I——先找到振荡临界点,再逐步加积分
- 位置环用P控制——带宽取速度环的三分之一到五分之一
公式给的是理论初值,实际工程中还需根据波形微调。但有了正确的方向和合理的初始参数,你已经解决了80%的问题。剩下的20%,就是示波器+耐心。
更多推荐
所有评论(0)