ESP32无感滑模FOC详细学习文档
基于灯哥DengFOC V4例程4:无感滑模完整控制例程
适合2208电机空载情况,其他电机需要自行调整参数
目录
- 无感FOC基础理论
- 滑模观测器(SMO)原理
- 代码架构分析
- 核心模块详解
- 调参指南
- 常见问题与调试
1. 无感FOC基础理论
1.1 什么是无感FOC?
FOC(Field Oriented Control,磁场定向控制)是一种高效的电机控制方法。无感FOC是指不使用位置传感器(如编码器、霍尔传感器),而是通过观测器算法估算转子位置和速度的控制方式。
1.2 为什么需要无感FOC?
| 有感FOC |
无感FOC |
| 需要额外传感器 |
无需传感器,成本低 |
| 启动可靠 |
启动需要特殊处理 |
| 位置精度高 |
位置有估算误差 |
| 适合高精度应用 |
适合成本敏感应用 |
1.3 PMSM电机数学模型
无感FOC的核心是利用电机的反电动势来估算转子位置。
电压方程(α-β坐标系)
1 2
| Uα = Rs·Iα + Ls·(dIα/dt) + Eα Uβ = Rs·Iβ + Ls·(dIβ/dt) + Eβ
|
其中:
Uα, Uβ:定子电压
Iα, Iβ:定子电流
Rs:相电阻
Ls:相电感
Eα, Eβ:反电动势(关键!)
反电动势与转子位置的关系
1 2
| Eα = -Ke·ω·sin(θ) Eβ = Ke·ω·cos(θ)
|
其中:
关键结论:如果我们能准确估算出反电动势,就能计算出转子位置!
1.4 坐标变换关系
1 2
| 三相静止坐标系(ABC) → 两相静止坐标系(α-β) → 两相旋转坐标系(d-q) (Clarke变换) (Park变换)
|
Clarke变换(ABC → αβ):
1 2
| Iα = Ia Iβ = (1/√3)·Ia + (2/√3)·Ib
|
Park变换(αβ → dq):
1 2
| Id = Iα·cos(θ) + Iβ·sin(θ) Iq = Iβ·cos(θ) - Iα·sin(θ)
|
Park逆变换(dq → αβ):
1 2
| Iα = Id·cos(θ) - Iq·sin(θ) Iβ = Id·sin(θ) + Iq·cos(θ)
|
2. 滑模观测器(SMO)原理
2.1 滑模控制基本思想
滑模控制是一种非线性控制方法,其核心思想是设计一个滑模面,使系统状态在有限时间内到达并保持在滑模面上。
2.2 滑模观测器结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ┌─────────────────┐ Uα,Uβ ──────→│ 观测器模型 │────→ 估算电流 Îα,Îβ │ (电机数学模型) │ └─────────────────┘ │ ↓ ┌─────────────────┐ 实际电流Iα,Iβ ──→│ 滑模面计算 │────→ 电流误差 │ e = Î - I │ └─────────────────┘ │ ↓ ┌─────────────────┐ 电流误差 ─────→│ 滑模控制律 │────→ 反电动势 Eα,Eβ │ u = K·sat(e) │ └─────────────────┘ │ ↓ ┌─────────────────┐ 反电动势Eα,Eβ ──→│ 锁相环(PLL) │────→ 估算角度θ̂,速度ω̂ │ │ └─────────────────┘
|
2.3 详细数学推导
步骤1:构造观测器模型
1 2 3
| d(Îα)/dt = -(Rs/Ls)·Îα + (1/Ls)·(Uα - Eα) d(Îβ)/dt = -(Rs/Ls)·Îβ + (1/Ls)·(Uβ - Eβ)
|
步骤2:定义滑模面(电流误差)
1 2
| sα = Îα - Iα sβ = Îβ - Iβ
|
步骤3:设计滑模控制律
使用等速趋近律估算反电动势:
1 2
| Eα = h·sat(sα, ε) Eβ = h·sat(sβ, ε)
|
其中:
饱和函数:
1 2 3
| sat(e, ε) = { 1, e > ε { e/ε, -ε ≤ e ≤ ε { -1, e < ε
|
步骤4:低通滤波
由于滑模输出的反电动势含有高频抖振,需要滤波:
1 2
| Eα_flt = k·Eα_flt + (1-k)·Eα Eβ_flt = k·Eβ_flt + (1-k)·Eβ
|
步骤5:锁相环(PLL)提取角度
1 2 3
| θe = -Eα_flt·cos(θ̂) - Eβ_flt·sin(θ̂) ω̂ = Kp·θe + Ki·∫θe·dt θ̂ = θ̂ + ω̂·Ts
|
2.4 为什么用滑模观测器?
| 优点 |
缺点 |
| 对参数变化鲁棒性强 |
存在抖振问题 |
| 算法简单,计算量小 |
需要低通滤波 |
| 动态响应快 |
需要调节滑模增益 |
3. 代码架构分析
3.1 文件结构
1 2 3 4 5 6 7 8
| DengFOC_SMO_full_control/ ├── DengFOC_SMO_full_control.ino # 主程序 ├── DengFOC.h/cpp # FOC核心控制类 ├── SMO.h/cpp # 滑模观测器类 ⭐核心 ├── InlineCurrent.h/cpp # 电流采样模块 ├── pid.h/cpp # PID控制器 ├── lowpass_filter.h/cpp # 低通滤波器 ├── AS5600.h/cpp # 编码器(仅用于启动参考)
|
3.2 主程序流程图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| ┌─────────────────────────────────────────┐ │ setup() │ │ 1. 初始化串口、PWM、ADC │ │ 2. 初始化电流传感器 │ │ 3. 设置控制模式(有感/无感) │ │ 4. 配置PID参数 │ └─────────────────────────────────────────┘ │ ↓ ┌─────────────────────────────────────────┐ │ loop() │ │ ┌─────────────────────────────────┐ │ │ │ 1. runFOC() │ │ │ │ - 采样电流 │ │ │ │ - 计算变换 │ │ │ │ - SMO观测器更新 │ │ │ └─────────────────────────────────┘ │ │ ┌─────────────────────────────────┐ │ │ │ 2. VF_start() │ │ │ │ - 开环启动 │ │ │ │ - 达到速度后切换闭环 │ │ │ └─────────────────────────────────┘ │ │ ┌─────────────────────────────────┐ │ │ │ 3. 控制函数 │ │ │ │ - 电压控制 │ │ │ │ - 电流控制 │ │ │ │ - 速度控制 │ │ │ └─────────────────────────────────┘ │ └─────────────────────────────────────────┘
|
4. 核心模块详解
4.1 主程序 (DengFOC_SMO_full_control.ino)
完整代码解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include "DengFOC.h" #include "SMO.h"
void setup() { Serial.begin(115200); DFOC_enable();
DFOC_Vbus(12);
Ctrl_Mode(SENSELESS);
DFOC_M0_SET_CURRENT_PID(3, 200, 0, 100000); DFOC_M1_SET_CURRENT_PID(3, 200, 0, 100000);
DFOC_M0_SET_VEL_PID(0.02, 0.5, 0, 100000, 6); DFOC_M1_SET_VEL_PID(0.02, 0.5, 0, 100000, 6);
First_Target(90); }
void loop() { runFOC();
if (VF_start(0, CCW) == 1) { DFOC_M0_SMO_CUR_setSpeed(serial_motor_target()); }
serialReceiveUserCommand(); }
|
关键点说明
- 控制模式选择:
Ctrl_Mode(SENSELESS) 是切换到无感模式的关键
- PID参数:需要根据具体电机调整
- VF启动:无感FOC必须先开环启动,达到一定速度后才能切换闭环
4.2 SMO滑模观测器 (SMO.cpp) ⭐核心模块
类结构定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| struct SMO_params { float Rs; float Ls; float h; float PLL_kp; float PLL_ki; float VF_acc; float VF_max_vel; float VF_uq_delta; };
class SMO { private: float Rs; float Ls;
float Ualpha, Ubeta; float Ialpha, Ibeta; float Est_Ialpha, Est_Ibeta; float Ialpha_Err, Ibeta_Err;
float h; float Ealpha, Ebeta; float Ealpha_flt, Ebeta_flt;
float Est_Theta; float Est_speed; float PLL_kp, PLL_ki;
uint8_t VF_flag; float VF_acc; float VF_max_vel;
public: SMO(SMO_params para); void SMO_closeloop(Cur_vol_s Cur_vol); float sat(float err, float limits); void SMO_position_estimate(); void PLL_calc(float alpha, float beta); void _VF_start(int num, int dir); };
|
核心函数1:滑模闭环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void SMO::SMO_closeloop(Cur_vol_s Cur_vol) { now_time = micros(); Ts = (now_time - last_time) * 1e-6f; last_time = now_time; if (Ts < 0 || Ts > 5e-3f) Ts = 1e-3f;
Ialpha = Cur_vol.I_alpha; Ibeta = Cur_vol.I_beta;
Ualpha = -sin(Est_Theta) * Cur_vol.Uq; Ubeta = cos(Est_Theta) * Cur_vol.Uq;
SMO_position_estimate(); }
|
关键点:
- 采样周期Ts对算法稳定性很重要
- 这里假设Ud=0,即只控制q轴电流(产生转矩)
核心函数2:位置估算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void SMO::SMO_position_estimate() { Est_Ialpha += Ts * (-Rs / Ls * Est_Ialpha + 1 / Ls * (Ualpha - Ealpha)); Est_Ibeta += Ts * (-Rs / Ls * Est_Ibeta + 1 / Ls * (Ubeta - Ebeta));
Ialpha_Err = Est_Ialpha - Ialpha; Ibeta_Err = Est_Ibeta - Ibeta;
Ealpha = h * sat(Ialpha_Err, 0.5f); Ebeta = h * sat(Ibeta_Err, 0.5f);
Ealpha_flt = 0.1 * Ealpha_flt + 0.9 * Ealpha; Ebeta_flt = 0.1 * Ebeta_flt + 0.9 * Ebeta;
if (_dir >= 0) PLL_calc(Ealpha_flt, Ebeta_flt); else PLL_calc(-Ealpha_flt, -Ebeta_flt); }
|
图解观测器原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ┌─────────────────────────────────────────┐ │ 电机实际运行 │ │ Uα,Uβ → 电机 → Iα,Iβ (实际电流) │ └─────────────────────────────────────────┘ │ ↓ 对比 ┌─────────────────────────────────────────┐ │ 观测器模型 │ │ Uα,Uβ → 模型 → Îα,Îβ (估算电流) │ └─────────────────────────────────────────┘ │ ↓ 误差 ┌─────────────────────────────────────────┐ │ 滑模控制 │ │ e = Î - I → E = h·sat(e) (反电动势) │ └─────────────────────────────────────────┘
|
核心函数3:饱和函数
1 2 3 4 5
| float SMO::sat(float err, float limits) { if (err > limits) return 1; else if (err < -limits) return -1; else return err / limits; }
|
为什么用饱和函数不用符号函数?
符号函数 sign(x) 会导致严重的抖振现象,饱和函数可以平滑过渡。
核心函数4:锁相环(PLL)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void SMO::PLL_calc(float alpha, float beta) { Theta_err = -1 * alpha * cos(Est_Theta) + beta * sin(Est_Theta) * -1;
i_err += Ts * PLL_ki * Theta_err; Est_speed = PLL_kp * Theta_err + i_err;
Est_speed_F = Est_speed_F * 0.9 + Est_speed * 0.1;
Est_Theta += Ts * Est_speed; Est_Theta = _normalizeAngle(Est_Theta); }
|
PLL原理图:
1 2 3 4 5 6 7
| 反电动势(Eα, Eβ) → [相位误差计算] → θe ↓ [PI控制器] → ω̂ (估算速度) ↓ [积分器] → θ̂ (估算角度) │ └──→ 反馈到相位误差计算
|
核心函数5:VF启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| void SMO::_VF_start(int num, int dir) { _dir = dir;
if (VF_flag == 0) { init_time = micros(); now_us = micros(); VF_flag = 2; }
if ((now_us - init_time) * 1e-3f >= 3000) { VF_flag = 1; }
if (VF_flag == 2) { now_us = micros(); float Ts = (now_us - open_loop_timestamp) * 1e-6f; if (Ts <= 0 || Ts > 0.01f) Ts = 1e-3f;
if (Target_Vel_openloop < VF_max_vel) { Target_Vel_openloop = Target_Vel_openloop + VF_acc; } else { Target_Vel_openloop = VF_max_vel; }
shaft_angle = _normalizeAngle(shaft_angle + dir * Target_Vel_openloop * Ts);
if (num == 0) { M0_setTorque(2.0f + Target_Vel_openloop * VF_uq_delta, 0, shaft_angle); } else if (num == 1) { M1_setTorque(2.0f + Target_Vel_openloop * VF_uq_delta, 0, shaft_angle); } open_loop_timestamp = now_us; } }
|
VF启动原理:
- 开环强制电机转动
- 速度按设定加速度逐渐增加
- 当速度足够高(反电动势足够大)时,切换到闭环控制
4.3 FOC核心控制 (DengFOC.cpp)
Clarke变换 + Park变换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Cur_vol_s cal_Iq_Id(float current_a, float current_b, float angle_el) { Cur_vol_s r_Cur_vol;
r_Cur_vol.I_alpha = current_a; r_Cur_vol.I_beta = _1_SQRT3 * current_a + _2_SQRT3 * current_b;
float ct = cos(angle_el); float st = sin(angle_el); r_Cur_vol.I_d = r_Cur_vol.I_alpha * ct + r_Cur_vol.I_beta * st; r_Cur_vol.I_q = r_Cur_vol.I_beta * ct - r_Cur_vol.I_alpha * st;
return r_Cur_vol; }
|
SVPWM实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| void M0_setTorque(float Uq, float Ud, float angle_el) { Cur_vol_M0.Uq = Uq; Cur_vol_M0.Ud = Ud;
float Uout; if (Ud) { Uout = _sqrt(Ud * Ud + Uq * Uq) / voltage_power_supply; angle_el = _normalizeAngle(angle_el + atan2(Uq, Ud)); } else { Uout = Uq / voltage_power_supply; angle_el = _normalizeAngle(angle_el + _PI_2); }
int sector = floor(angle_el / _PI_3) + 1;
float T1 = _SQRT3 * sin(sector * _PI_3 - angle_el) * Uout; float T2 = _SQRT3 * sin(angle_el - (sector - 1.0f) * _PI_3) * Uout; float T0 = 1 - T1 - T2;
float Ta, Tb, Tc; switch (sector) { case 1: Ta = T1 + T2 + T0 / 2; Tb = T2 + T0 / 2; Tc = T0 / 2; break; }
float Ua = Ta * voltage_power_supply; float Ub = Tb * voltage_power_supply; float Uc = Tc * voltage_power_supply; M0_setPwm(Ua, Ub, Uc); }
|
runFOC核心循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void runFOC() { CS_M0.getPhaseCurrents(); CS_M1.getPhaseCurrents();
if (ctrl_mode == SENSELESS) { Cur_vol_M0 = cal_Iq_Id(CS_M0.current_a, CS_M0.current_b, SMO_M0.Est_Theta); Cur_vol_M1 = cal_Iq_Id(CS_M1.current_a, CS_M1.current_b, SMO_M1.Est_Theta);
SMO_M0.SMO_closeloop(Cur_vol_M0); SMO_M1.SMO_closeloop(Cur_vol_M1); } else { Cur_vol_M0 = cal_Iq_Id(CS_M0.current_a, CS_M0.current_b, S0_electricalAngle()); Cur_vol_M1 = cal_Iq_Id(CS_M1.current_a, CS_M1.current_b, S1_electricalAngle()); } }
|
4.4 电流采样模块 (InlineCurrent.cpp)
类结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class CurrSense { public: CurrSense(int Mot_Num); void init(); void getPhaseCurrents();
float current_a, current_b, current_c;
private: int pinA, pinB, pinC; float offset_ia, offset_ib, offset_ic; float _shunt_resistor; float amp_gain; float volts_to_amps_ratio; float gain_a, gain_b, gain_c; };
|
ADC电流读取
1 2 3 4 5 6 7 8 9 10 11 12 13
| void CurrSense::getPhaseCurrents() { float voltage_a = readADCVoltageInline(pinA); float voltage_b = readADCVoltageInline(pinB);
float voltage_a_offset = voltage_a - offset_ia; float voltage_b_offset = voltage_b - offset_ib;
current_a = voltage_a_offset * gain_a; current_b = voltage_b_offset * gain_b; }
|
电流计算公式:
1
| I = (V_adc - V_offset) × (1 / R_shunt / Gain_opamp)
|
其中:
V_adc:ADC采样电压
V_offset:零电流时的ADC电压(偏移电压)
R_shunt:采样电阻(本例0.01Ω)
Gain_opamp:运放增益(本例50倍)
零点校准
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void CurrSense::calibrateOffsets() { const int calibration_rounds = 1000; offset_ia = 0; offset_ib = 0;
for (int i = 0; i < calibration_rounds; i++) { offset_ia += readADCVoltageInline(pinA); offset_ib += readADCVoltageInline(pinB); delay(1); }
offset_ia = offset_ia / calibration_rounds; offset_ib = offset_ib / calibration_rounds; }
|
4.5 PID控制器 (pid.cpp)
PID实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class PIDController { public: float P; float I; float D; float output_ramp; float limit;
float operator() (float error);
private: float error_prev; float output_prev; float integral_prev; unsigned long timestamp_prev; };
|
PID计算(抗饱和设计)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| float PIDController::operator() (float error) { unsigned long timestamp_now = micros(); float Ts = (timestamp_now - timestamp_prev) * 1e-6f; if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
float proportional = P * error;
float integral = integral_prev + I * Ts * 0.5f * (error + error_prev); integral = _constrain(integral, -limit, limit);
float derivative = D * (error - error_prev) / Ts;
float output = proportional + integral + derivative; output = _constrain(output, -limit, limit);
if(output_ramp > 0) { float output_rate = (output - output_prev) / Ts; if (output_rate > output_ramp) output = output_prev + output_ramp * Ts; else if (output_rate < -output_ramp) output = output_prev - output_ramp * Ts; }
integral_prev = integral; output_prev = output; error_prev = error; timestamp_prev = timestamp_now;
return output; }
|
4.6 低通滤波器 (lowpass_filter.cpp)
一阶低通滤波器
1 2 3 4 5 6 7 8 9 10 11
| class LowPassFilter { public: LowPassFilter(float Tf);
float operator() (float x);
private: float Tf; float y_prev; unsigned long timestamp_prev; };
|
滤波算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| float LowPassFilter::operator() (float x) { unsigned long timestamp = micros(); float dt = (timestamp - timestamp_prev) * 1e-6f;
if (dt < 0.0f) dt = 1e-3f; else if(dt > 0.3f) { y_prev = x; timestamp_prev = timestamp; return x; }
float alpha = Tf / (Tf + dt);
float y = alpha * y_prev + (1.0f - alpha) * x;
y_prev = y; timestamp_prev = timestamp;
return y; }
|
滤波公式:
1 2 3
| y[n] = α·y[n-1] + (1-α)·x[n]
其中:α = Tf / (Tf + dt)
|
Tf越大,滤波效果越强,但响应越慢
Tf越小,响应越快,但滤波效果越弱
5. 调参指南
5.1 电机参数测量
在开始调参前,需要测量以下电机参数:
相电阻 Rs 测量方法
- 使用万用表测量任意两相之间的电阻
- 相电阻 = 测量值 / 2
- 例如:测得AB相电阻为16.5Ω,则 Rs = 8.25Ω
相电感 Ls 测量方法
- 使用电桥表测量任意两相之间的电感
- 相电感 = 测量值 / 2
- 例如:测得AB相电感为8.5mH,则 Ls = 4.25mH
如果没有电桥表,可以使用以下方法估算:
1 2 3 4 5 6
| 对于普通无刷电机: Ls ≈ (0.5 ~ 2) mH (根据电机大小)
小型云台电机(如2208):约 4-5 mH 中型电机:约 1-2 mH 大型电机:约 0.5-1 mH
|
5.2 参数初始化
在 DengFOC.cpp 中设置电机参数:
1 2 3 4 5 6 7 8 9 10
| SMO_params paras{ paras.Rs = 8.25f, paras.Ls = 0.004256f, paras.h = 0.3f, paras.PLL_kp = 521.0f, paras.PLL_ki = 40000.0f, paras.VF_acc = 0.3f, paras.VF_max_vel = 210, paras.VF_uq_delta = 0.006f, };
|
5.3 调参步骤
第一步:确认硬件连接
1 2 3 4 5 6 7 8
| M0_pwmA = 32; M0_pwmB = 33; M0_pwmC = 25;
|
第二步:测试有感模式(可选)
如果手头有编码器,先测试有感模式确保硬件正常:
1 2 3 4 5 6 7 8 9 10
| void setup() { Ctrl_Mode(SENSE); DFOC_M0_alignSensor(7, 1); }
void loop() { runFOC(); DFOC_M0_vol_setTorque(serial_motor_target()); }
|
第三步:调节滑模增益 h
滑模增益是最关键的参数。
调节方法:
| 现象 |
原因 |
调节 |
| 电机不转/抖动严重 |
h太小 |
增大h(×1.5) |
| 高频噪音大 |
h太大 |
减小h(×0.8) |
| 估算角度不准 |
h不合适 |
微调h |
推荐范围:
- 小型电机(2208):h = 0.2 ~ 0.5
- 中型电机:h = 0.5 ~ 1.0
- 大型电机:h = 1.0 ~ 2.0
第四步:调节PLL参数
PLL参数影响角度估算的平滑度和响应速度。
调节方法:
- 先调节
PLL_kp(决定响应速度)
- 再调节
PLL_ki(决定稳态精度)
经验公式:
1 2 3 4 5 6
| PLL_kp ≈ (2 × ζ × ωn) PLL_ki ≈ (ωn × ωn)
其中: ζ = 0.707(阻尼比) ωn = 100 ~ 500(自然频率,rad/s)
|
推荐值(2208电机):
1 2
| PLL_kp = 521.0f; PLL_ki = 40000.0f;
|
调试技巧:
- 速度波动大:增大PLL_kp,或减小PLL_ki
- 角度跟踪慢:增大PLL_kp
- 角度抖动:减小PLL_kp和PLL_ki
第五步:调节电流环PID
1
| DFOC_M0_SET_CURRENT_PID(P, I, D, ramp);
|
调节顺序:P → I → D(一般D=0)
初始值:
1 2 3
| P = 1.0 ~ 3.0 I = 100 ~ 300 D = 0
|
调节方法:
- I=0,从小到大调节P,直到电流有震荡
- P取震荡值的50%
- 从小到大调节I,消除稳态误差
- D一般设为0
第六步:调节速度环PID
1
| DFOC_M0_SET_VEL_PID(P, I, D, ramp, limit);
|
初始值:
1 2 3 4
| P = 0.02 I = 0.5 D = 0 limit = 6
|
调节方法:
- I=0,增加P直到速度震荡
- P取震荡值的50%
- 增加I消除稳态误差
- 调节limit防止电压饱和
第七步:调节VF启动参数
1 2 3
| VF_acc = 0.3f; VF_max_vel = 210; VF_uq_delta = 0.006f;
|
调节方法:
| 现象 |
原因 |
调节 |
| 启动时堵转 |
加速度太大 |
减小VF_acc |
| 启动时间太长 |
加速度太小 |
增大VF_acc |
| 切换闭环时抖动 |
切换速度太低 |
增大VF_max_vel |
| 切换后电流大 |
初始电压太高 |
减小VF_uq_delta |
5.4 调参参考表
2208云台电机(参考)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Rs = 8.25 Ω Ls = 4.256 mH
h = 0.3 PLL_kp = 521 PLL_ki = 40000
current_P = 3 current_I = 200
vel_P = 0.02 vel_I = 0.5 vel_limit = 6
VF_acc = 0.3 VF_max_vel = 210 VF_uq_delta = 0.006
|
大功率电机(如2804)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Rs = 2.5 Ω Ls = 0.5 mH
h = 0.8 PLL_kp = 800 PLL_ki = 60000
current_P = 2 current_I = 150
vel_P = 0.05 vel_I = 1.0 vel_limit = 8
VF_acc = 0.5 VF_max_vel = 150 VF_uq_delta = 0.008
|
5.5 调试工具
串口监控
1 2 3 4 5 6 7
| Serial.printf("%f,%f,%f,%f\n", SMO_M0.Est_Theta, S0_electricalAngle(), DFOC_M0_Current(), SMO_M0.Est_speed );
|
使用串口绘图器
Arduino IDE → 工具 → 串口绘图器
可以实时观察:
6. 常见问题与调试
6.1 电机不转
可能原因:
相序错误
参数完全错误
电压太低
电流采样方向错误
- 解决:修改
InlineCurrent.cpp中的gain_a和gain_b符号
6.2 电机抖动严重
可能原因:
滑模增益h太小
PLL参数不合适
电流采样噪声大
- 解决:
- 检查硬件接线
- 增加低通滤波时间常数
- 检查ADC零点校准是否正确
6.3 角度估算不准
可能原因:
低速时反电动势太小
电机参数不准确
滤波参数不合适
- 解决:调整反电动势滤波系数(代码中0.1和0.9)
6.4 只能单向转动
可能原因:
方向判断逻辑错误
PLL参数在反向时不稳定
6.5 启动困难
可能原因:
VF启动加速度太大
初始电压不够
- 解决:增大
M0_setTorque()的第一个参数(初始电压)
负载太重
附录A:重要公式汇总
A.1 电机模型
1 2 3 4 5
| Uα = Rs·Iα + Ls·(dIα/dt) + Eα Uβ = Rs·Iβ + Ls·(dIβ/dt) + Eβ
Eα = -Ke·ω·sin(θ) Eβ = Ke·ω·cos(θ)
|
A.2 坐标变换
1 2 3 4 5 6 7 8 9 10 11
| // Clarke Iα = Ia Iβ = (1/√3)·Ia + (2/√3)·Ib
// Park Id = Iα·cos(θ) + Iβ·sin(θ) Iq = Iβ·cos(θ) - Iα·sin(θ)
// Park逆变换 Iα = Id·cos(θ) - Iq·sin(θ) Iβ = Id·sin(θ) + Iq·cos(θ)
|
A.3 滑模观测器
1 2 3 4 5 6 7 8 9 10 11
| // 观测器 d(Îα)/dt = -(Rs/Ls)·Îα + (1/Ls)·(Uα - Eα) d(Îβ)/dt = -(Rs/Ls)·Îβ + (1/Ls)·(Uβ - Eβ)
// 滑模面 sα = Îα - Iα sβ = Îβ - Iβ
// 控制律 Eα = h·sat(sα) Eβ = h·sat(sβ)
|
A.4 锁相环
1 2 3
| θe = -Eα·cos(θ̂) - Eβ·sin(θ̂) ω̂ = Kp·θe + Ki·∫θe·dt θ̂ = ∫ω̂·dt
|
附录B:代码修改速查
B.1 修改电机引脚
1 2 3 4 5 6 7 8 9
| int M0_pwmA = 32; int M0_pwmB = 33; int M0_pwmC = 25;
pinA = 39; pinB = 36;
|
B.2 修改电机参数
1 2 3 4 5 6 7
| SMO_params paras{ paras.Rs = 你的电阻值, paras.Ls = 你的电感值, paras.h = 滑模增益, };
|
B.3 修改PID参数
1 2 3
| DFOC_M0_SET_CURRENT_PID(P, I, D, ramp); DFOC_M0_SET_VEL_PID(P, I, D, ramp, limit);
|
附录C:参考资料
滑模控制理论
FOC控制
PMSM电机建模
文档版本:v1.0
最后更新:2025年
适用硬件:DengFOC V4
原作者:灯哥开源
文档整理:基于例程4代码分析