ESP32无感滑模FOC详细学习文档

基于灯哥DengFOC V4例程4:无感滑模完整控制例程
适合2208电机空载情况,其他电机需要自行调整参数


目录

  1. 无感FOC基础理论
  2. 滑模观测器(SMO)原理
  3. 代码架构分析
  4. 核心模块详解
  5. 调参指南
  6. 常见问题与调试

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(θ)

其中:

  • Ke:反电动势常数
  • ω:电角速度
  • θ:电角度

关键结论:如果我们能准确估算出反电动势,就能计算出转子位置!

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β, ε)

其中:

  • h:滑模增益
  • sat():饱和函数(减少抖振)

饱和函数

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 // PI调节器
θ̂ = θ̂ + ω̂·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); // 设置供电电压12V

/**选择控制模式
* SENSE 有感(使用编码器)
* SENSELESS 无感(使用滑模观测器)
*/
Ctrl_Mode(SENSELESS); // ⭐关键:设置为无感模式

// 电流环PID参数(P=3, I=200)
DFOC_M0_SET_CURRENT_PID(3, 200, 0, 100000);
DFOC_M1_SET_CURRENT_PID(3, 200, 0, 100000);

// 速度环PID参数(P=0.02, I=0.5)
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:VF启动后的自动闭环目标值
* 建议初始值:
* 电压模式:4V
* 电流模式:0.5A
* 速度模式:90 rad/s
*/
First_Target(90);
}

void loop() {
runFOC(); // 核心FOC循环

// VF启动:开环加速到一定速度后切换闭环
if (VF_start(0, CCW) == 1) {
// 选择控制模式:
// DFOC_M0_SMO_VOL_setTorque(...); // 电压力矩(无电流闭环)
// DFOC_M0_SMO_CUR_setTorque(...); // 电流力矩
DFOC_M0_SMO_CUR_setSpeed(serial_motor_target()); // ⭐速度电流双闭环
}

// 接收串口命令
serialReceiveUserCommand();
}

关键点说明

  1. 控制模式选择Ctrl_Mode(SENSELESS) 是切换到无感模式的关键
  2. PID参数:需要根据具体电机调整
  3. 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; // 相电感 (H)
float h; // 滑模增益
float PLL_kp; // 锁相环比例系数
float PLL_ki; // 锁相环积分系数
float VF_acc; // VF启动加速度
float VF_max_vel; // VF启动最大速度
float VF_uq_delta; // VF启动电压增量
};

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; // PLL参数

// VF启动
uint8_t VF_flag; // VF启动状态标志
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); // VF启动
};

核心函数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) {
// 1. 计算采样周期
now_time = micros();
Ts = (now_time - last_time) * 1e-6f; // 转换为秒
last_time = now_time;
if (Ts < 0 || Ts > 5e-3f) Ts = 1e-3f; // 异常处理

// 2. 更新电流和电压
Ialpha = Cur_vol.I_alpha;
Ibeta = Cur_vol.I_beta;

// 3. Park逆变换:从dq到αβ(Ud=0)
Ualpha = -sin(Est_Theta) * Cur_vol.Uq;
Ubeta = cos(Est_Theta) * Cur_vol.Uq;

// 4. 执行位置估算
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() {
// 步骤1:估算电流(欧拉法离散化)
// dÎ/dt = -(Rs/Ls)·Î + (1/Ls)·(U - E)
Est_Ialpha += Ts * (-Rs / Ls * Est_Ialpha + 1 / Ls * (Ualpha - Ealpha));
Est_Ibeta += Ts * (-Rs / Ls * Est_Ibeta + 1 / Ls * (Ubeta - Ebeta));

// 步骤2:计算电流误差(滑模面)
Ialpha_Err = Est_Ialpha - Ialpha;
Ibeta_Err = Est_Ibeta - Ibeta;

// 步骤3:滑模控制律(估算反电动势)
Ealpha = h * sat(Ialpha_Err, 0.5f);
Ebeta = h * sat(Ibeta_Err, 0.5f);

// 步骤4:低通滤波
Ealpha_flt = 0.1 * Ealpha_flt + 0.9 * Ealpha; // 一阶滤波
Ebeta_flt = 0.1 * Ebeta_flt + 0.9 * Ebeta;

// 步骤5:锁相环提取角度
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; // 超过上界,输出+1
else if (err < -limits) return -1; // 超过下界,输出-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) {
// 步骤1:计算相位误差
// 原理:当估算角度准确时,误差应为0
Theta_err = -1 * alpha * cos(Est_Theta) + beta * sin(Est_Theta) * -1;

// 步骤2:PI控制器(位置式)
i_err += Ts * PLL_ki * Theta_err; // 积分项
Est_speed = PLL_kp * Theta_err + i_err; // 输出速度

// 步骤3:速度滤波
Est_speed_F = Est_speed_F * 0.9 + Est_speed * 0.1;

// 步骤4:积分得到角度
Est_Theta += Ts * Est_speed;
Est_Theta = _normalizeAngle(Est_Theta); // 归一化到[0, 2π]
}

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; // 进入启动阶段
}

// 启动完成判断(3秒后)
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启动原理

  1. 开环强制电机转动
  2. 速度按设定加速度逐渐增加
  3. 当速度足够高(反电动势足够大)时,切换到闭环控制

4.3 FOC核心控制 (DengFOC.cpp)

Clarke变换 + Park变换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 通过Ia,Ib计算Iq,Id
Cur_vol_s cal_Iq_Id(float current_a, float current_b, float angle_el) {
Cur_vol_s r_Cur_vol;

// Clarke变换:ABC → αβ
r_Cur_vol.I_alpha = current_a;
r_Cur_vol.I_beta = _1_SQRT3 * current_a + _2_SQRT3 * current_b;

// Park变换:αβ → dq
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;
// ... 其他扇区类似
}

// 转换为电压并输出PWM
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() {
// 1. 采样电流
CS_M0.getPhaseCurrents();
CS_M1.getPhaseCurrents();

if (ctrl_mode == SENSELESS) {
// 2. 无感模式:用估算角度进行变换
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);

// 3. 滑模观测器更新
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; // ADC引脚
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() {
// 1. 读取ADC值并转换为电压
float voltage_a = readADCVoltageInline(pinA);
float voltage_b = readADCVoltageInline(pinB);

// 2. 减去零点偏移
float voltage_a_offset = voltage_a - offset_ia;
float voltage_b_offset = voltage_b - offset_ib;

// 3. 转换为电流
current_a = voltage_a_offset * gain_a; // 单位:A
current_b = voltage_b_offset * gain_b; // 单位:A
}

电流计算公式

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;

// 采样1000次取平均
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) {
// 1. 计算时间间隔
unsigned long timestamp_now = micros();
float Ts = (timestamp_now - timestamp_prev) * 1e-6f;
if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;

// 2. P项(比例)
float proportional = P * error;

// 3. I项(积分,Tustin法)
float integral = integral_prev + I * Ts * 0.5f * (error + error_prev);
integral = _constrain(integral, -limit, limit); // 抗饱和

// 4. D项(微分)
float derivative = D * (error - error_prev) / Ts;

// 5. 合成输出
float output = proportional + integral + derivative;
output = _constrain(output, -limit, limit);

// 6. 输出变化率限制(平滑输出)
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;
}

// 7. 保存状态
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); // Tf: 时间常数

float operator() (float x); // 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) {
// 1. 计算时间间隔
unsigned long timestamp = micros();
float dt = (timestamp - timestamp_prev) * 1e-6f;

// 2. 异常处理
if (dt < 0.0f) dt = 1e-3f;
else if(dt > 0.3f) {
y_prev = x; // 时间太长,直接更新
timestamp_prev = timestamp;
return x;
}

// 3. 计算滤波系数
float alpha = Tf / (Tf + dt);

// 4. 滤波公式(一阶指数移动平均)
float y = alpha * y_prev + (1.0f - alpha) * x;

// 5. 更新状态
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 测量方法

  1. 使用万用表测量任意两相之间的电阻
  2. 相电阻 = 测量值 / 2
  3. 例如:测得AB相电阻为16.5Ω,则 Rs = 8.25Ω

相电感 Ls 测量方法

  1. 使用电桥表测量任意两相之间的电感
  2. 相电感 = 测量值 / 2
  3. 例如:测得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, // ⚠️ 根据实测修改 (H)
paras.h = 0.3f, // 滑模增益(需调节)
paras.PLL_kp = 521.0f, // PLL比例(需调节)
paras.PLL_ki = 40000.0f, // PLL积分(需调节)
paras.VF_acc = 0.3f, // VF启动加速度
paras.VF_max_vel = 210, // VF启动最大速度
paras.VF_uq_delta = 0.006f,// VF电压增量
};

5.3 调参步骤

第一步:确认硬件连接

1
2
3
4
5
6
7
8
// 检查以下配置
M0_pwmA = 32; // A相PWM引脚
M0_pwmB = 33; // B相PWM引脚
M0_pwmC = 25; // C相PWM引脚

// 电流采样引脚
// M0: pinA = 39, pinB = 36
// M1: pinA = 35, pinB = 34

第二步:测试有感模式(可选)

如果手头有编码器,先测试有感模式确保硬件正常:

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参数影响角度估算的平滑度和响应速度。

调节方法

  1. 先调节 PLL_kp(决定响应速度)
  2. 再调节 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

调节方法

  1. I=0,从小到大调节P,直到电流有震荡
  2. P取震荡值的50%
  3. 从小到大调节I,消除稳态误差
  4. 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 // 输出限幅(电压)

调节方法

  1. I=0,增加P直到速度震荡
  2. P取震荡值的50%
  3. 增加I消除稳态误差
  4. 调节limit防止电压饱和

第七步:调节VF启动参数

1
2
3
VF_acc = 0.3f;        // 加速度(rad/s²)
VF_max_vel = 210; // 最大速度(rad/s)
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

// SMO参数
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启动
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

// SMO参数
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启动
VF_acc = 0.5
VF_max_vel = 150
VF_uq_delta = 0.008

5.5 调试工具

串口监控

1
2
3
4
5
6
7
// 在runFOC()中添加打印
Serial.printf("%f,%f,%f,%f\n",
SMO_M0.Est_Theta, // 估算角度
S0_electricalAngle(), // 实际角度(对比用)
DFOC_M0_Current(), // Q轴电流
SMO_M0.Est_speed // 估算速度
);

使用串口绘图器

Arduino IDE → 工具 → 串口绘图器

可以实时观察:

  • 估算角度 vs 实际角度
  • 电流变化
  • 估算速度

6. 常见问题与调试

6.1 电机不转

可能原因

  1. 相序错误

    • 解决:交换任意两相线序
  2. 参数完全错误

    • 解决:重新测量Rs和Ls
  3. 电压太低

    • 解决:检查供电电压,VF启动时的电压是否足够
  4. 电流采样方向错误

    • 解决:修改InlineCurrent.cpp中的gain_again_b符号

6.2 电机抖动严重

可能原因

  1. 滑模增益h太小

    • 解决:增大h
  2. PLL参数不合适

    • 解决:减小PLL_kp,增大PLL_ki
  3. 电流采样噪声大

    • 解决:
      • 检查硬件接线
      • 增加低通滤波时间常数
      • 检查ADC零点校准是否正确

6.3 角度估算不准

可能原因

  1. 低速时反电动势太小

    • 解决:增大VF_max_vel,让切换速度更高
  2. 电机参数不准确

    • 解决:重新测量Rs和Ls
  3. 滤波参数不合适

    • 解决:调整反电动势滤波系数(代码中0.1和0.9)

6.4 只能单向转动

可能原因

  1. 方向判断逻辑错误

    • 解决:检查_dir参数设置
  2. PLL参数在反向时不稳定

    • 解决:降低PLL_kp

6.5 启动困难

可能原因

  1. VF启动加速度太大

    • 解决:减小VF_acc
  2. 初始电压不够

    • 解决:增大M0_setTorque()的第一个参数(初始电压)
  3. 负载太重

    • 解决:增大VF_uq_delta

附录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
// DengFOC.cpp
int M0_pwmA = 32;
int M0_pwmB = 33;
int M0_pwmC = 25;

// InlineCurrent.cpp
// M0电流采样引脚
pinA = 39;
pinB = 36;

B.2 修改电机参数

1
2
3
4
5
6
7
// DengFOC.cpp
SMO_params paras{
paras.Rs = 你的电阻值,
paras.Ls = 你的电感值,
paras.h = 滑模增益,
// ...
};

B.3 修改PID参数

1
2
3
// .ino文件
DFOC_M0_SET_CURRENT_PID(P, I, D, ramp);
DFOC_M0_SET_VEL_PID(P, I, D, ramp, limit);

附录C:参考资料

  1. 滑模控制理论

    • 《滑模变结构控制理论及应用》
  2. FOC控制

  3. PMSM电机建模

    • 《永磁同步电机控制系统》

文档版本:v1.0
最后更新:2025年
适用硬件:DengFOC V4
原作者:灯哥开源
文档整理:基于例程4代码分析