电机通电后疯狂抖动,示波器上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为例:

参数值
相电阻 Rs0.5 Ω
相电感 Ls1.2 mH
PWM频率20 kHz
目标带宽 fc2000 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"的工程方法:

  1. 设Ki=0,只用比例控制
  2. 逐步增大Kp_v,每次增大10~20%,观察阶跃响应
  3. 直到速度出现持续振荡,记录此时的Kp为Kp_osc
  4. 最终 Kp_v = Kp_osc × 0.6~0.7
  5. 逐步增大Ki_v,直到稳态速度误差消除,但不产生新的振荡
  6. 若超调过大,减小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调参的核心方法论可以浓缩为四句话:

  1. 先校准电角度零点——一切的前提
  2. 电流环用公式算——Kp = L × ωc,Ki = R × ωc
  3. 速度环先P后I——先找到振荡临界点,再逐步加积分
  4. 位置环用P控制——带宽取速度环的三分之一到五分之一

公式给的是理论初值,实际工程中还需根据波形微调。但有了正确的方向和合理的初始参数,你已经解决了80%的问题。剩下的20%,就是示波器+耐心。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐