ESP32无感滑模FOC深度学习文档

基于灯哥DengFOC V4例程4:无感滑模完整控制例程
包含滑模观测器、锁相环、SVPWM深度解析


目录

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

1. 无感FOC基础理论

1.1 什么是无感FOC?

FOC(Field Oriented Control,磁场定向控制)是一种高效的电机控制方法。无感FOC是指不使用位置传感器(如编码器、霍尔传感器),而是通过观测器算法估算转子位置和速度的控制方式。

1.2 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.3 无感FOC控制框图

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
47
48
49
50
51
52
53
54
55
56
57
58
59
┌─────────────────────────────────────────────────────────────────┐
│ 无感FOC控制框图 │
└─────────────────────────────────────────────────────────────────┘

目标速度/目标电流


┌──────────┐
│ 速度环 │ → Id_ref (通常为0), Iq_ref
│ PID │
└──────────┘


┌──────────┐
│ 电流环 │ → Ud, Uq
│ PID │
└──────────┘


┌──────────────────────────────────────────┐
│ Park逆变换 (dq→αβ) │
│ Uα = Ud·cos(θ̂) - Uq·sin(θ̂) │
│ Uβ = Ud·sin(θ̂) + Uq·cos(θ̂) │
└──────────────────────────────────────────┘


┌──────────────────────────────────────────┐
│ SVPWM调制 │
│ 生成三相PWM占空比 │
└──────────────────────────────────────────┘


┌──────────────────────────────────────────┐
│ 电机 │
│ 产生实际电流 Ia, Ib │
└──────────────────────────────────────────┘


┌──────────────────────────────────────────┐
│ Clarke变换 (ABC→αβ) │
│ Iα = Ia, Iβ = (Ia+2Ib)/√3 │
└──────────────────────────────────────────┘


┌──────────────────────────────────────────┐
│ Park变换 (αβ→dq) │
│ 使用估算角度θ̂进行变换 │
│ Id = Iα·cos(θ̂) + Iβ·sin(θ̂) │
│ Iq = Iβ·cos(θ̂) - Iα·sin(θ̂) │
└──────────────────────────────────────────┘


┌──────────────────────────────────────────┐
│ 滑模观测器 (SMO) │
│ 输入: Uα,Uβ, Iα,Iβ │
│ 输出: 估算角度θ̂, 估算速度ω̂ │
└──────────────────────────────────────────┘

└──→ 反馈到Park变换和Park逆变换

2. 滑模观测器(SMO)深度解析

2.1 滑模控制理论基础

2.1.1 什么是滑模控制?

滑模控制(Sliding Mode Control)是一种非线性控制策略,其核心思想是:

  1. 设计滑模面:定义一个切换函数 s(x) = 0
  2. 设计控制律:使系统状态在有限时间内到达滑模面
  3. 保持滑动:一旦到达滑模面,控制律使系统状态保持在滑模面上运动

2.1.2 滑模面的物理意义

在电机控制中,我们定义滑模面为电流误差

1
2
sα = Îα - Iα  (α轴电流误差)
sβ = Îβ - Iβ (β轴电流误差)

滑模面的物理意义

  • 当 s = 0 时,估算电流等于实际电流
  • 当 s ≠ 0 时,存在误差,需要通过控制律消除

2.1.3 滑模存在条件

滑模存在的充分条件(李雅普诺夫稳定性):

1
2
V = (1/2)·s²  (定义能量函数)
dV/dt = s·(ds/dt) < 0 (能量必须减小)

2.2 滑模观测器完整推导

2.2.1 电机数学模型(连续域)

在α-β坐标系下,PMSM电机的电压方程为:

1
2
dIα/dt = -(Rs/Ls)·Iα + (1/Ls)·Uα - (1/Ls)·Eα
dIβ/dt = -(Rs/Ls)·Iβ + (1/Ls)·Uβ - (1/Ls)·Eβ

整理得:

1
dI/dt = A·I + B·U - B·E

其中:

1
2
3
4
5
I = [Iα, Iβ]ᵀ     (电流向量)
U = [Uα, Uβ]ᵀ (电压向量)
E = [Eα, Eβ]ᵀ (反电动势向量)
A = -(Rs/Ls)·I (系统矩阵)
B = (1/Ls)·I (输入矩阵)

2.2.2 构造观测器

我们构造一个与实际电机并行的观测器模型:

1
2
dÎα/dt = -(Rs/Ls)·Îα + (1/Ls)·Uα - (1/Ls)·Êα
dÎβ/dt = -(Rs/Ls)·Îβ + (1/Ls)·Uβ - (1/Ls)·Êβ

关键:观测器中的 Êα, Êβ 是通过滑模控制律估算的,而不是实际测量值!

2.2.3 定义电流误差(滑模面)

1
2
eα = Îα - Iα
eβ = Îβ - Iβ

用向量形式:

1
e = Î - I

2.2.4 误差动力学方程

用观测器方程减去实际方程:

1
2
3
4
5
deα/dt = dÎα/dt - dIα/dt
= -(Rs/Ls)·(Îα - Iα) - (1/Ls)·(Êα - Eα)
= -(Rs/Ls)·eα - (1/Ls)·(Êα - Eα)

deβ/dt = -(Rs/Ls)·eβ - (1/Ls)·(Êβ - Eβ)

2.2.5 设计滑模控制律

我们的目标是:使 e = 0,即估算电流等于实际电流。

使用等速趋近律设计控制律:

1
2
Êα = h·sat(eα, ε)
Êβ = h·sat(eβ, ε)

其中:

  • h:滑模增益(必须足够大以保证滑模存在)
  • sat():饱和函数(替代符号函数以减少抖振)

2.2.6 为什么控制律能估算反电动势?

当系统进入滑动模态后(e = 0,de/dt = 0),从误差动力学方程:

1
0 = -(Rs/Ls)·0 - (1/Ls)·(Êα - Eα)

解得:

1
2
Êα = Eα
Êβ = Eβ

结论:在滑模状态下,估算的反电动势收敛于真实的反电动势!

2.3 饱和函数 vs 符号函数

2.3.1 符号函数(传统滑模)

1
2
3
sign(x) = { +1,  x > 0
{ 0, x = 0
{ -1, x < 0

问题:在滑模面附近产生高频抖振(chattering)

2.3.2 饱和函数(改进滑模)

1
2
3
sat(x, ε) = { +1,      x > ε
{ x/ε, -ε ≤ x ≤ ε
{ -1, x < -ε

优势:在边界层内线性变化,减少抖振

2.3.3 代码实现

1
2
3
4
5
6
// SMO.cpp中的实现
float SMO::sat(float err, float limits) {
if (err > limits) return 1; // 超过上界
else if (err < -limits) return -1; // 超过下界
else return err / limits; // 线性区
}

使用示例

1
2
Ealpha = h * sat(Ialpha_Err, 0.5f);  // limits = 0.5
Ebeta = h * sat(Ibeta_Err, 0.5f);

2.4 滑模增益h的选择

2.4.1 理论分析

滑模存在的条件:

1
h > |Eα|_max  且  h > |Eβ|_max

即滑模增益必须大于最大反电动势。

2.4.2 工程经验

电机类型 推荐h值 说明
小型云台电机(2208) 0.2 ~ 0.5 电感大,反电动势小
中型电机 0.5 ~ 1.0 平衡值
大型电机 1.0 ~ 2.0 电感小,反电动势大

2.4.3 h值调试技巧

1
2
3
4
5
6
7
8
9
10
11
现象1:电机不转,电流振荡
原因:h太小,滑模条件不满足
解决:h × 1.5,逐步增大

现象2:高频噪音,估算角度抖动
原因:h太大,过度反应
解决:h × 0.8,逐步减小

现象3:低速性能差
原因:低速时反电动势小,h相对太大
解决:使用自适应h或分段h

2.5 滑模观测器完整代码解析

2.5.1 数据结构定义

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
// SMO.h
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参数

// 时间变量
uint32_t now_time;
uint32_t last_time;
float Ts; // 采样周期

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); // 锁相环计算
};

2.5.2 核心函数1:滑模闭环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// SMO.cpp
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(只控制q轴电流产生转矩)
// Uα = Ud·cos(θ) - Uq·sin(θ) = -Uq·sin(θ)
// Uβ = Ud·sin(θ) + Uq·cos(θ) = Uq·cos(θ)
Ualpha = -sin(Est_Theta) * Cur_vol.Uq;
Ubeta = cos(Est_Theta) * Cur_vol.Uq;

// ========== 步骤4:执行位置估算 ==========
SMO_position_estimate();
}

代码说明

  1. 采样周期Ts必须准确,影响欧拉积分精度
  2. Ud=0是最大转矩/安比特性的控制策略
  3. 使用估算角度Est_Theta进行Park逆变换

2.5.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
// SMO.cpp
void SMO::SMO_position_estimate() {
// ========== 步骤1:估算电流(欧拉法离散化) ==========
// 连续域:dÎ/dt = -(Rs/Ls)·Î + (1/Ls)·(U - E)
// 离散化:Î[k+1] = Î[k] + Ts·[-(Rs/Ls)·Î[k] + (1/Ls)·(U[k] - E[k])]

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:滑模控制律(估算反电动势) ==========
// Ê = h·sat(e)
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. 滤波系数0.1/0.9可根据需要调整
  3. 方向判断确保正反转都能正常工作

2.5.4 核心函数3:饱和函数

1
2
3
4
5
6
// SMO.cpp
float SMO::sat(float err, float limits) {
if (err > limits) return 1; // 饱和区:输出+1
else if (err < -limits) return -1; // 饱和区:输出-1
else return err / limits; // 线性区:归一化输出
}

函数特性

1
2
3
4
5
6
7
8
9
10
11
输出
1 | ┌─────────
| /
| /
| /
0 |-------+--------→ 误差
| /
| /
| /
-1 ─────────┘
-limits +limits

2.6 滑模观测器信号流图

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
┌────────────────────────────────────────────────────────────────┐
│ 滑模观测器完整信号流 │
└────────────────────────────────────────────────────────────────┘

实际电机 观测器模型
┌─────────┐ ┌─────────┐
Uα→ │ dIα/dt │ │ dÎα/dt │←─── Êα
│ = f(I,U,E)│ │ = f(Î,U,Ê)│
└─────────┘ └─────────┘
│ │
↓ Iα ↓ Îα
└──────────┬──────────┘

┌──────────┐
│ eα = Îα-Iα│ ← 滑模面
└──────────┘


┌──────────┐
│ sat(eα,ε) │
└──────────┘


┌──────────┐
│ × h │
└──────────┘

↓ Êα
┌───────────────┐
│ 低通滤波 │
└───────────────┘

↓ Eα_flt
┌───────────────┐
│ PLL │
└───────────────┘


θ̂ (估算角度)

2.7 滑模观测器稳定性分析

2.7.1 李雅普诺夫函数

定义能量函数:

1
V = (1/2)·(eα² + eβ²)

2.7.2 能量变化率

1
2
3
4
5
dV/dt = eα·(deα/dt) + eβ·(deβ/dt)
= eα·[-(Rs/Ls)·eα - (1/Ls)·(h·sat(eα) - Eα)]
+ eβ·[-(Rs/Ls)·eβ - (1/Ls)·(h·sat(eβ) - Eβ)]
= -(Rs/Ls)·(eα² + eβ²) - (1/Ls)·[eα·(h·sat(eα) - Eα)
+ eβ·(h·sat(eβ) - Eβ)]

2.7.3 滑模存在条件

当 |e| > ε(在饱和区)时,sat(e) = sign(e),则:

1
e·(h·sat(e) - E) = h·|e| - e·E

如果 h > |E|_max,则:

1
e·(h·sat(e) - E) > h·|e| - |e|·|E|_max = |e|·(h - |E|_max) > 0

因此:

1
dV/dt = -(Rs/Ls)·|e|² - (1/Ls)·|e|·(h - |E|_max) < 0

结论:当 h > |E|_max 时,系统全局渐近稳定!


3. 锁相环(PLL)深度解析

3.1 PLL基本原理

3.1.1 什么是锁相环?

锁相环(Phase-Locked Loop,PLL)是一种相位反馈控制系统,能够:

  1. 跟踪输入信号的相位
  2. 输出与输入信号同频同相的信号
  3. 同时输出频率信息

3.1.2 为什么需要PLL?

在无感FOC中,我们从滑模观测器得到反电动势:

1
2
Eα = -Ke·ω·sin(θ)
Eβ = Ke·ω·cos(θ)

问题:如何从Eα、Eβ中提取角度θ?

方法1:直接计算

1
θ = atan2(-Eα, Eβ)

缺点:atan2计算量大,且对噪声敏感

方法2:PLL(推荐)
优点

  • 天然滤波作用
  • 同时获得角度和速度
  • 对噪声鲁棒

3.2 PLL数学模型

3.2.1 相位误差计算

利用三角恒等式:

1
2
3
4
-Eα·cos(θ̂) - Eβ·sin(θ̂)
= -(-Ke·ω·sin(θ))·cos(θ̂) - (Ke·ω·cos(θ))·sin(θ̂)
= Ke·ω·[sin(θ)·cos(θ̂) - cos(θ)·sin(θ̂)]
= Ke·ω·sin(θ - θ̂) ← 正弦定理

当角度误差较小时:

1
sin(θ - θ̂) ≈ θ - θ̂

因此:

1
相位误差 ∝ (θ - θ̂)

3.2.2 PLL控制框图

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
┌────────────────────────────────────────────────────────────┐
│ PLL控制框图 │
└────────────────────────────────────────────────────────────┘

Eα_flt, Eβ_flt


┌──────────────────┐
│ 相位误差计算 │
│ θe = -Eα·cos(θ̂) │
│ -Eβ·sin(θ̂) │
└──────────────────┘

↓ θe (相位误差)
┌──────────────────┐
│ PI控制器 │
│ ω̂ = Kp·θe + │
│ Ki·∫θe·dt │
└──────────────────┘

↓ ω̂ (估算速度)
┌──────────────────┐
│ 积分器 │
│ θ̂ = θ̂ + ω̂·Ts │
└──────────────────┘

↓ θ̂ (估算角度)
└───→ 反馈到相位误差计算

3.2.3 PLL传递函数

开环传递函数:

1
G(s) = (Kp·s + Ki) / s²

闭环传递函数(特征方程):

1
2
1 + G(s) = 0
s² + Kp·s + Ki = 0

这是典型的二阶系统

3.2.4 与二阶系统对应

标准二阶系统:

1
s² + 2·ζ·ωn·s + ωn² = 0

对应关系:

1
2
ωn² = Ki        (自然频率平方)
2·ζ·ωn = Kp (阻尼比相关)

因此:

1
2
ωn = √Ki
ζ = Kp / (2·√Ki)

3.2.5 PLL参数设计

步骤1:选择自然频率ωn

  • ωn越大,响应越快,但滤波效果差
  • 推荐:ωn = 100~500 rad/s

步骤2:选择阻尼比ζ

  • ζ = 0.707(临界阻尼,无超调)
  • ζ = 1.0(过阻尼,响应慢但平滑)

步骤3:计算Kp、Ki

1
2
Ki = ωn²
Kp = 2·ζ·ωn

示例(ωn = 200, ζ = 0.707):

1
2
Ki = 200² = 40000
Kp = 2 × 0.707 × 200 = 282.8 ≈ 300

3.3 PLL代码实现

3.3.1 PLL计算函数

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
// SMO.cpp
void SMO::PLL_calc(float alpha, float beta) {
// ========== 步骤1:计算相位误差 ==========
// 公式:θe = -Eα·cos(θ̂) - Eβ·sin(θ̂)
// 推导:当θ̂=θ时,θe=0
Theta_err = -1 * alpha * cos(Est_Theta) + beta * sin(Est_Theta) * -1;

// 简化写法:
// Theta_err = -alpha * cos(Est_Theta) - beta * sin(Est_Theta);

// ========== 步骤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;

// ========== 步骤5:角度归一化到[0, 2π] ==========
Est_Theta = _normalizeAngle(Est_Theta);
}

3.3.2 关键点说明

1. 相位误差计算详解

1
Theta_err = -alpha * cos(Est_Theta) - beta * sin(Est_Theta);

展开:

1
Theta_err = -Eα·cos(θ̂) - Eβ·sin(θ̂)

假设实际角度为θ,代入反电动势公式:

1
2
3
= -(-Ke·ω·sin(θ))·cos(θ̂) - (Ke·ω·cos(θ))·sin(θ̂)
= Ke·ω·[sin(θ)·cos(θ̂) - cos(θ)·sin(θ̂)]
= Ke·ω·sin(θ - θ̂) ← 正弦差公式

物理意义

  • 当 θ̂ = θ 时,Theta_err = 0
  • 当 θ̂ < θ 时,Theta_err > 0
  • 当 θ̂ > θ 时,Theta_err < 0

2. PI控制器详解

1
2
i_err += Ts * PLL_ki * Theta_err;  // 积分累加
Est_speed = PLL_kp * Theta_err + i_err;

比例项

1
ω_p = Kp·θe

  • 快速响应相位误差
  • 决定响应速度

积分项

1
ω_i = Ki·∫θe·dt

  • 消除稳态误差
  • 保证最终θ̂ = θ

3. 角度积分

1
Est_Theta += Ts * Est_speed;

物理意义:

1
θ̂(k+1) = θ̂(k) + ω̂(k)·Ts

这是速度积分得到角度的基本公式。

3.4 PLL动态响应分析

3.4.1 阶跃响应

设初始角度误差为Δθ,则:

1
θ̂(t) = θ(t) - Δθ·e^(-ζ·ωn·t)·[cos(ωd·t) + (ζ/√(1-ζ²))·sin(ωd·t)]

其中:

1
ωd = ωn·√(1-ζ²)  (阻尼振荡频率)

3.4.2 响应特性

阻尼比ζ 响应特性 超调量 调节时间
ζ < 0.7 欠阻尼 有超调
ζ = 0.707 临界阻尼 4.3% 最短
ζ > 1 过阻尼

推荐:ζ = 0.707~1.0

3.5 PLL调试技巧

3.5.1 参数调节方法

方法1:经验公式

1
2
Ki = ωn² = (200~500)² = 40000~250000
Kp = 2·ζ·√Ki = 1.414·√Ki

方法2:逐步调试

1
2
3
4
5
1. 设置Ki = 0,只调节Kp
2. 增大Kp直到速度开始振荡
3. Kp取振荡值的50%
4. 增加Ki消除稳态误差
5. 微调Kp和Ki

3.5.2 常见问题诊断

现象 原因 解决方案
角度跟踪慢 Kp太小 增大Kp
角度振荡 Kp太大 减小Kp
稳态误差大 Ki太小 增大Ki
速度估计噪声大 需要滤波 增加速度滤波
负载变化丢步 动态响应慢 增大Kp和Ki

3.6 PLL完整信号流

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
47
48
49
50
51
┌────────────────────────────────────────────────────────────────┐
│ PLL完整信号流图 │
└────────────────────────────────────────────────────────────────┘

Eα_flt = -Ke·ω·sin(θ) Eβ_flt = Ke·ω·cos(θ)
│ │
└──────────┬───────────────────┘

┌──────────────────────┐
│ 相位误差计算 │
│ θe = -Eα·cos(θ̂) │
│ -Eβ·sin(θ̂) │
│ = Ke·ω·sin(θ-θ̂) │
└──────────────────────┘

↓ θe
┌──────────────────────┐
│ PI控制器 │
│ ┌─────────────────┐ │
│ │ P项: Kp·θe │ │
│ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ I项: ∫Ki·θe·dt │ │
│ └─────────────────┘ │
└──────────────────────┘

↓ ω̂ (电角速度 rad/s)
┌──────────────────────┐
│ 速度低通滤波(可选) │
│ ω̂_f = k·ω̂_f + (1-k)·ω̂│
└──────────────────────┘


┌──────────────────────┐
│ 积分器 │
│ θ̂(k+1) = θ̂(k) + ω̂·Ts │
└──────────────────────┘

↓ θ̂ (电角度 rad)
┌──────────────────────┐
│ 角度归一化 │
│ θ̂ = θ̂ % (2·π) │
└──────────────────────┘


┌──────────────────────┐
│ 机械角度转换 │
│ θ_mech = θ̂ / PP │
└──────────────────────┘

└──→ 反馈到相位误差计算

4. SVPWM深度解析

4.1 SVPWM基本原理

4.1.1 什么是SVPWM?

SVPWM(Space Vector Pulse Width Modulation,空间矢量脉宽调制)是一种:

  • 利用三相电压空间矢量
  • 在复平面内合成任意方向电压矢量
  • 实现电机高效控制的方法

4.1.2 为什么用SVPWM?

方法 电压利用率 谐波 实现复杂度
SPWM(正弦PWM) 0.866 较大 简单
SVPWM 1.0 中等

4.2 三相逆变器基本原理

4.2.1 逆变器拓扑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
       Vdc

┌─────┴─────┐
│ │
┌─┴─┐ ┌─┴─┐ ┌─┴─┐
│ S1│ │ S3│ │ S5│
└─┬─┘ └─┬─┘ └─┬─┘
│ A相 │ B相 │ C相
┌─┴─┐ ┌─┴─┐ ┌─┴─┐
│ S2│ │ S4│ │ S6│
└─┬─┘ └─┬─┘ └─┬─┘
│ │ │
└─────┬─────┴─────┬─────┘
│ │
GND 电机

4.2.2 开关状态

每相有2种状态:上管导通(1)或下管导通(0)

三相共有 2³ = 8 种开关状态:

状态 S1 S3 S5 A B C 电压矢量 类型
0 0 0 0 0 0 0 零矢量 零矢量
1 1 0 0 Vdc 0 0 U1 基本矢量
2 1 1 0 Vdc Vdc 0 U2 基本矢量
3 0 1 0 0 Vdc 0 U3 基本矢量
4 0 1 1 0 Vdc Vdc U4 基本矢量
5 0 0 1 0 0 Vdc U5 基本矢量
6 1 0 1 Vdc 0 Vdc U6 基本矢量
7 1 1 1 Vdc Vdc Vdc 零矢量 零矢量

4.3 空间矢量图

4.3.1 α-β坐标系下的基本矢量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
      β轴


U3(010) │ U2(110)
\ │ /
\ │ /
\ │ /
\ │ /
┼────────→ α轴
/ │ \
/ │ \
/ │ \
U4(011)/ │U1(100)

六个基本矢量将平面分为6个扇区:

1
2
3
4
5
6
7
8
        扇区3
┌───────────┐
│ │
扇区2│ │ │扇区4
│ ● │
│ │
└───────────┘
扇区6 扇区5 扇区1

4.3.2 基本矢量坐标

矢量 开关状态 幅值
U0 000 0 0 0
U1 100 2/3·Vdc 0 2/3·Vdc
U2 110 1/3·Vdc √3/3·Vdc 2/3·Vdc
U3 010 -1/3·Vdc √3/3·Vdc 2/3·Vdc
U4 011 -2/3·Vdc 0 2/3·Vdc
U5 001 -1/3·Vdc -√3/3·Vdc 2/3·Vdc
U6 101 1/3·Vdc -√3/3·Vdc 2/3·Vdc
U7 111 0 0 0

重要结论:所有基本矢量幅值相等 = 2/3·Vdc

4.4 SVPWM算法推导

4.4.1 目标

给定任意目标电压矢量 Uref,求:

  1. 所在扇区
  2. 相邻两个基本矢量的作用时间
  3. 零矢量作用时间

4.4.2 扇区判断

计算中间变量:

1
2
3
V1 = Uβ
V2 = (√3·Uα - Uβ) / 2
V3 = (-√3·Uα - Uβ) / 2

扇区判断:

1
sector = 1 + sign(V1) + 2·sign(V2) + 4·sign(V3)

其中:

1
2
sign(x) = { 1,  x ≥ 0
{ 0, x < 0

简化判断(使用角度)

1
sector = floor(angle / (π/3)) + 1;

4.4.3 作用时间计算

在扇区I中,目标矢量由U1和U2合成:

1
Uref = (T1/Tpwm)·U1 + (T2/Tpwm)·U2 + (T0/Tpwm)·U0

约束条件:

1
T1 + T2 + T0 = Tpwm

在α-β坐标系分解:

1
2
Uα·Tpwm = T1·|U1| + T2·|U2|·cos(60°)
Uβ·Tpwm = T2·|U2|·sin(60°)

解得:

1
2
3
T1 = √3·Tpwm·(Uα/Vdc - Uβ/(√3·Vdc))
T2 = 2·Tpwm·(Uβ/(√3·Vdc))
T0 = Tpwm - T1 - T2

通用公式(适用于所有扇区)

定义归一化时间:

1
2
T1 = √3·sin(θk·π/3 - θ)·(|Uref|/Vdc)·Tpwm
T2 = √3·sin(θ - (θk-1)·π/3)·(|Uref|/Vdc)·Tpwm

其中:

  • θk:扇区结束角度
  • θ:目标电压角度
  • |Uref|:目标电压幅值

4.5 七段式SVPWM

4.5.1 为什么用七段式?

目标:减少开关损耗,使每次只切换一相

4.5.2 扇区I的七段式

1
2
3
4
5
6
7
8
时间轴:  0   T0/4   T0/4+T1/2   T0/4+T1/2+T2   Tpwm
│ │ │ │ │
│ │ │ │ │
状态: U0 U1 U2 U7 U0
│ │ │ │ │
000 100 110 111 000

开关次数: 0 1 2 3 4

各相状态

1
2
3
4
U0(000)  U1(100)  U2(110)  U7(111)
A: 0 → 1 → 1 → 1 → 0
B: 0 → 0 → 1 → 1 → 0
C: 0 → 0 → 0 → 1 → 0

4.5.3 扇区I的占空比计算

1
2
3
Ta = T1 + T2 + T0/4
Tb = T2 + T0/4
Tc = T0/4

归一化到[0,1]

1
2
3
Da = Ta / Tpwm
Db = Tb / Tpwm
Dc = Tc / Tpwm

4.5.6 各扇区占空比表

扇区 Ta Tb Tc
I(0°~60°) T1+T2+T0/2 T2+T0/2 T0/2
II(60°~120°) T1+T0/2 T1+T2+T0/2 T0/2
III(120°~180°) T0/2 T1+T2+T0/2 T2+T0/2
IV(180°~240°) T0/2 T1+T0/2 T1+T2+T0/2
V(240°~300°) T2+T0/2 T0/2 T1+T2+T0/2
VI(300°~360°) T1+T2+T0/2 T0/2 T1+T0/2

4.6 SVPWM代码实现

4.6.1 完整代码(带详细注释)

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// DengFOC.cpp
void M0_setTorque(float Uq, float Ud, float angle_el) {
// ============================================================
// 步骤1:保存电压值
// ============================================================
Cur_vol_M0.Uq = Uq;
Cur_vol_M0.Ud = Ud;

// ============================================================
// 步骤2:计算合成电压矢量的幅值和角度
// ============================================================

float Uout; // 电压幅值(归一化)
float voltage_angle; // 电压矢量角度

if (Ud != 0) {
// 如果有d轴电压,计算合成矢量
// |U| = √(Ud² + Uq²)
Uout = _sqrt(Ud * Ud + Uq * Uq) / voltage_power_supply;

// 合成矢量角度 = 原始角度 + atan2(Uq, Ud)
voltage_angle = _normalizeAngle(angle_el + atan2(Uq, Ud));
} else {
// 如果Ud=0,只有q轴电压(常见情况)
Uout = Uq / voltage_power_supply;

// q轴电压超前d轴90°
voltage_angle = _normalizeAngle(angle_el + _PI_2);
}

// ============================================================
// 步骤3:确定扇区
// ============================================================

// 将[0, 2π]分为6个扇区,每个扇区60°(π/3)
int sector = floor(voltage_angle / _PI_3) + 1;

// ============================================================
// 步骤4:计算相邻矢量的作用时间
// ============================================================

// T1: 第一个基本矢量作用时间
// T2: 第二个基本矢量作用时间
// T0: 零矢量作用时间

float T1 = _SQRT3 * sin(sector * _PI_3 - voltage_angle) * Uout;
float T2 = _SQRT3 * sin(voltage_angle - (sector - 1.0f) * _PI_3) * Uout;
float T0 = 1 - T1 - T2;

// ============================================================
// 步骤5:根据扇区分配各相占空比
// ============================================================

float Ta, Tb, Tc;

switch (sector) {
case 1: // 扇区I: U1(100)和U2(110)
Ta = T1 + T2 + T0 / 2; // A相作用时间最长
Tb = T2 + T0 / 2; // B相次之
Tc = T0 / 2; // C相最短(零矢量)
break;

case 2: // 扇区II: U3(010)和U2(110)
Ta = T1 + T0 / 2;
Tb = T1 + T2 + T0 / 2; // B相作用时间最长
Tc = T0 / 2;
break;

case 3: // 扇区III: U3(010)和U4(011)
Ta = T0 / 2;
Tb = T1 + T2 + T0 / 2; // B相作用时间最长
Tc = T2 + T0 / 2;
break;

case 4: // 扇区IV: U5(001)和U4(011)
Ta = T0 / 2;
Tb = T1 + T0 / 2;
Tc = T1 + T2 + T0 / 2; // C相作用时间最长
break;

case 5: // 扇区V: U5(001)和U6(101)
Ta = T2 + T0 / 2;
Tb = T0 / 2;
Tc = T1 + T2 + T0 / 2; // C相作用时间最长
break;

case 6: // 扇区VI: U1(100)和U6(101)
Ta = T1 + T2 + T0 / 2; // A相作用时间最长
Tb = T0 / 2;
Tc = T1 + T0 / 2;
break;

default: // 异常情况
Ta = 0;
Tb = 0;
Tc = 0;
}

// ============================================================
// 步骤6:转换为实际电压值
// ============================================================

float Ua = Ta * voltage_power_supply;
float Ub = Tb * voltage_power_supply;
float Uc = Tc * voltage_power_supply;

// ============================================================
// 步骤7:输出PWM
// ============================================================

M0_setPwm(Ua, Ub, Uc);
}

4.6.2 PWM输出函数

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
// DengFOC.cpp
void M0_setPwm(float Ua, float Ub, float Uc) {
// ============================================================
// 步骤1:限幅
// ============================================================

Ua = _constrain(Ua, 0.0f, voltage_power_supply);
Ub = _constrain(Ub, 0.0f, voltage_power_supply);
Uc = _constrain(Uc, 0.0f, voltage_power_supply);

// ============================================================
// 步骤2:计算占空比
// ============================================================

float dc_a = _constrain(Ua / voltage_power_supply, 0.0f, 1.0f);
float dc_b = _constrain(Ub / voltage_power_supply, 0.0f, 1.0f);
float dc_c = _constrain(Uc / voltage_power_supply, 0.0f, 1.0f);

// ============================================================
// 步骤3:写入PWM寄存器
// ============================================================

ledcWrite(0, dc_a * 255); // A相PWM,通道0
ledcWrite(1, dc_b * 255); // B相PWM,通道1
ledcWrite(2, dc_c * 255); // C相PWM,通道2
}

4.7 SVPWM详细图解

4.7.1 电压矢量合成(扇区I为例)

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
                    β轴




U3(010) │ U2(110)
\ │ /
\ │ /
\ │ /
\ │ /
U4───────┼─────────U1(100)
(011) / │ \
/ │ \
/ │ \
/ │ \
U5(001)/ │ \ U6(101)


◉─────────────┼─────────────→ α轴
Uref │


目标矢量Uref在扇区I,由U1和U2合成:

Uref = (T1/Tpwm)·U1 + (T2/Tpwm)·U2

4.7.2 七段式开关序列(扇区I)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
         T0/4      T1/2         T2         T1/2      T0/4
←─────────→←──────────→←────────────→←──────────→←─────────→
┌──────────┬────────────┬─────────────┬───────────┬──────────┐
│ │ │ │ │ │
│ U0 │ U1 │ U2 │ U7 │ U0 │
│ (000) │ (100) │ (110) │ (111) │ (000) │
│ │ │ │ │ │
└──────────┴────────────┴─────────────┴───────────┴──────────┘

A相: 0 ──────── 1 ─────────── 1 ──────────── 1 ───────── 0
B相: 0 ──────── 0 ─────────── 1 ──────────── 1 ───────── 0
C相: 0 ──────── 0 ─────────── 0 ──────────── 1 ───────── 0

开关次数: 1次 1次 1次 1次

总开关次数: 4次(最小)

4.8 SVPWM与SPWM对比

4.8.1 电压利用率

SPWM

1
2
最大相电压幅值 = Vdc/2
最大线电压幅值 = √3·Vdc/2 ≈ 0.866·Vdc

SVPWM

1
2
最大相电压幅值 = Vdc/√3
最大线电压幅值 = Vdc

结论:SVPWM电压利用率高约15.5%

4.8.2 谐波特性

SPWM:谐波集中在载波频率附近
SVPWM:谐波分布更均匀,幅值更小

4.8.3 实现复杂度

方法 计算量 存储需求 实时性
SPWM
SVPWM

5. 代码架构分析

5.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 # 编码器(调试用)

5.2 主程序流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// .ino
void loop() {
// ========== 核心FOC循环 ==========
runFOC(); // 采样电流 → Clarke变换 → SMO估算 → Park变换

// ========== VF启动 ==========
if (VF_start(0, CCW) == 1) {
// 启动完成,进入闭环控制
DFOC_M0_SMO_CUR_setSpeed(serial_motor_target());
}

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

5.3 runFOC核心循环详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// DengFOC.cpp
void runFOC() {
// ========== 步骤1:采样电流 ==========
CS_M0.getPhaseCurrents(); // 读取Ia, Ib
CS_M1.getPhaseCurrents();

if (ctrl_mode == SENSELESS) {
// ========== 步骤2:无感模式 ==========

// 2.1 Clarke变换: ABC → αβ
Cur_vol_M0 = cal_Iq_Id(CS_M0.current_a, CS_M0.current_b, SMO_M0.Est_Theta);

// 2.2 Park变换: αβ → dq (使用估算角度)
// 在cal_Iq_Id中完成

// 2.3 滑模观测器更新
SMO_M0.SMO_closeloop(Cur_vol_M0); // 输出新的Est_Theta

} else {
// ========== 步骤3:有感模式 ==========
Cur_vol_M0 = cal_Iq_Id(CS_M0.current_a, CS_M0.current_b, S0_electricalAngle());
}
}

6. 核心模块详解

6.1 坐标变换模块

Clarke变换(ABC → αβ)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// DengFOC.cpp
Cur_vol_s cal_Iq_Id(float current_a, float current_b, float angle_el) {
Cur_vol_s r_Cur_vol;

// Clarke变换
r_Cur_vol.I_alpha = current_a; // Iα = Ia
r_Cur_vol.I_beta = _1_SQRT3 * current_a + _2_SQRT3 * current_b; // Iβ = (Ia+2Ib)/√3

// Park变换
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; // Id = Iα·cos(θ) + Iβ·sin(θ)
r_Cur_vol.I_q = r_Cur_vol.I_beta * ct - r_Cur_vol.I_alpha * st; // Iq = Iβ·cos(θ) - Iα·sin(θ)

return r_Cur_vol;
}

6.2 电流采样模块

1
2
3
4
5
6
7
8
9
10
// InlineCurrent.cpp
void CurrSense::getPhaseCurrents() {
// 读取ADC并转换为电压
float voltage_a = readADCVoltageInline(pinA);
float voltage_b = readADCVoltageInline(pinB);

// 减去零点偏移并转换为电流
current_a = (voltage_a - offset_ia) * gain_a;
current_b = (voltage_b - offset_ib) * gain_b;
}

6.3 PID控制模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// pid.cpp
float PIDController::operator()(float error) {
// 计算时间间隔
float Ts = (timestamp_now - timestamp_prev) * 1e-6f;

// P项
float proportional = P * error;

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

// D项
float derivative = D * (error - error_prev) / Ts;

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

return output;
}

7. 调参指南

7.1 电机参数测量

参数 测量方法 典型值(2208)
Rs 万用表测两相/2 8.25Ω
Ls 电桥表测两相/2 4.256mH
极对数 查规格书或实测 7

7.2 SMO参数调试

滑模增益h

1
2
3
4
5
现象:电机不转
→ 增大h(×1.5)

现象:高频噪音
→ 减小h(×0.8)

PLL参数

1
2
3
4
5
6
7
// 经验公式
Ki = ωn² (ωn = 200~500)
Kp = 2·ζ·√Ki (ζ = 0.707)

// 示例
Ki = 40000
Kp = 521

7.3 完整调参流程

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
1. 硬件检查
├─ 确认相序
├─ 确认电流采样方向
└─ 确认PWM输出

2. 有感测试
├─ 切换到SENSE模式
├─ 校准编码器
└─ 验证电机能正常转动

3. SMO参数调试
├─ 设置h=0.3
├─ 设置PLL_kp=521, PLL_ki=40000
├─ 调试h直到稳定
└─ 微调PLL参数

4. 电流环调试
├─ P=1~3, I=100~300
└─ 增大P到震荡点×0.5

5. 速度环调试
├─ P=0.02, I=0.5
└─ 根据响应调整

6. VF启动调试
├─ 调整加速度VF_acc
├─ 调整最大速度VF_max_vel
└─ 调整电压增量VF_uq_delta

8. 常见问题与调试

8.1 问题诊断表

现象 可能原因 排查方法
电机不转 h太小 增大h到1.0测试
抖动严重 PLL参数 减小PLL_kp
只能单向 _dir错误 检查方向设置
启动失败 VF参数 减小VF_acc
角度漂移 滤波太强 减小滤波系数

8.2 调试技巧

串口监控

1
2
3
Serial.printf("Est_Theta=%.2f, Real_Theta=%.2f, Iq=%.2f, speed=%.2f\n",
SMO_M0.Est_Theta, S0_electricalAngle(),
DFOC_M0_Current(), SMO_M0.Est_speed);

使用串口绘图器

Arduino IDE → 工具 → 串口绘图器


附录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
dÎα/dt = -(Rs/Ls)·Îα + (1/Ls)·(Uα - Êα)
dÎβ/dt = -(Rs/Ls)·Îβ + (1/Ls)·(Uβ - Êβ)

sα = Îα - Iα
sβ = Îβ - Iβ

Êα = h·sat(sα)
Êβ = h·sat(sβ)

A.3 PLL

1
2
3
4
5
6
θe = -Eα·cos(θ̂) - Eβ·sin(θ̂)
ω̂ = Kp·θe + Ki·∫θe·dt
θ̂ = ∫ω̂·dt

Ki = ωn²
Kp = 2·ζ·ωn

A.4 SVPWM

1
2
3
T1 = √3·sin(θk·π/3 - θ)·(|Uref|/Vdc)·Tpwm
T2 = √3·sin(θ - (θk-1)·π/3)·(|Uref|/Vdc)·Tpwm
T0 = Tpwm - T1 - T2

文档版本:v2.0
最后更新:2025年
适用硬件:DengFOC V4
原作者:灯哥开源
文档整理:深度学习版