1. 前言
此篇,实现通过 SVPWM 完成对永磁无刷电机的开环控制。
2. FOC 开环控制简单概述
在电机控制中,如果我们期望控制电机处于任意角度的角度,可以通过 FOC 算法实现,而且其中用于控制单片机引脚输出对应 PWM 的方波,用于控制电机 H 桥开断,是由其中的 SVPWM 模块来实现的。
2.1 8个基本矢量

对于 FOC 控制来说,我们通过三相 ABC 控制电机内部的磁场,因而有八个基本实现,其中 2 个是零矢量,分别为 ABC 上桥臂都闭合或者 ABC 上桥臂都打开,如果把 ABC 对应上桥臂打开定义为 1,关断定义为 0,则两个零矢量分别可定义为 000 和 111。
此外,还有三个桥臂分别打开关闭的组合类型:100、110、010、011、001、101。对应如下的电压(电流)矢量图形:

6个非零矢量,将整个平面划分为了 6 个扇区。通过控制任意相邻两个矢量的长度,我们就可以实现让合成矢量处于扇区内的任意一个方向。
2.2 电机的模型
如果对电机进行 d-q 旋转坐标系建模

可得到形式永磁同步电机电磁转矩表达式 :
Te=32p[λpmiq+(Ld−Lq)idiq] T_e = \frac{3}{2}p[\lambda_{pm}i_q + (L_d - L_q)i_di_q] Te=23p[λpmiq+(Ld−Lq)idiq]
其中:
-
永磁转矩项 :32pλpmiq\frac{3}{2}p\lambda_{pm}i_q23pλpmiq
由永磁磁场与电枢反应磁场(q轴电流产生)相互作用产生,与 iqi_qiq 成正比,是表贴式永磁电机(Ld=LqL_d = L_qLd=Lq)的主要转矩来源。
-
磁阻转矩项 :32p(Ld−Lq)idiq\frac{3}{2}p(L_d - L_q)i_di_q23p(Ld−Lq)idiq
我们通过 iqi_qiq 来控制电机的转矩和转动,对于嵌入式的电机当其高速旋转时,我们可能还需控制 idi_did 来调节磁阻。
通过反 Park 变换,我们可以把期望作用于电机上的 iqi_qiq 和 idi_did (会随着电机转动而改变,可视为旋转坐标系)转为静止坐标上的 iβi_\betaiβ 和 iαi_\alphaiα 来描述。
三相 ABC 的三相电压和电流也可以通过 Clark 变换成两相坐标系 αβ\alpha \betaαβ 坐标系来描述,进而实现能够通过调整 ABC 三相输出情况,进而间接控制电机的旋转。
对于电机来说,我们可以定义旋转坐标系 dq 与静止坐标系的夹角为电机的旋转角度 θ\thetaθ,借助反 Park 变换可以将期望的 dq 值和 θ\thetaθ 转换为 αβ\alpha\betaαβ 下的 UαU_\alphaUα 和 UβU_\betaUβ,进而就能够实现电机的开环控制。
2.3 矢量作用时长
如果把控制电机工作的时间按照 TsT_sTs 为刻度进行划分,即可把 TsT_sTs 定义为周期,在一个周期 TsT_sTs 内,可以由三部分时长组成:
Ts=Tx+Ty+Tn T_s = T_x + T_y + T_n Ts=Tx+Ty+Tn
其中:
- TxT_xTx 和 TyT_yTy 为相邻两个非零矢量,例如:扇区一,对应图上的 U4U_4U4 和 U6U_6U6;
- TnT_nTn:为零矢量;
即,通过零矢量、两个基本矢量就可以控制电机处于任意角度。
对应任意扇区的作用时长 TxT_xTx 和 TyT_yTy 与 UαU_\alphaUα 和 UβU_\betaUβ 的电压输出大小,可以总结成如下三个表达式:
X=3TsUdcUβY=3TsUdc(32Uα+12Uβ)Z=3TsUdc(−32Uα+12Uβ) \begin{align} X &= \frac{\sqrt{3}T_s}{U_{dc}}U_\beta\\ Y &= \frac{\sqrt{3}T_s}{U_{dc}}(\frac{\sqrt{3}}{2}U_\alpha + \frac{1}{2}U_\beta)\\ Z &= \frac{\sqrt{3}T_s}{U_{dc}}(-\frac{\sqrt{3}}{2}U_\alpha + \frac{1}{2}U_\beta) \end{align} XYZ=Udc3 TsUβ=Udc3 Ts(23 Uα+21Uβ)=Udc3 Ts(−23 Uα+21Uβ)
对于具体某个扇区的 TxT_xTx 和 TyT_yTy 取值如下:
| 扇区 | TxT_xTx | TyT_yTy |
|---|---|---|
| 扇区一 | −Z-Z−Z | XXX |
| 扇区二 | ZZZ | YYY |
| 扇区三 | XXX | −Y-Y−Y |
| 扇区四 | −X-X−X | ZZZ |
| 扇区五 | −Y-Y−Y | −Z-Z−Z |
| 扇区六 | YYY | −X-X−X |
2.4 扇区判断
如何判断当前处于哪个扇区呢?也可以总结成如下的三个表达式:
{U1=UβU2=32Uα−12UβU3=−32Uα−12Uβ \begin{cases} U_1 = U_\beta\\ U_2 = \frac{\sqrt{3}}{2}U_\alpha - \frac{1}{2}U_\beta\\ U_3 = -\frac{\sqrt{3}}{2}U_\alpha - \frac{1}{2}U_\beta \end{cases} ⎩ ⎨ ⎧U1=UβU2=23 Uα−21UβU3=−23 Uα−21Uβ
借助三者的函数曲线图像:

其中黄色为 U1U_1U1、蓝色为 U2U_2U2、红色为 U3U_3U3。
借助三者的函数曲线,我们可以很容易得到:
- 在扇区一:U1>0U_1 \gt 0U1>0、U2>0U_2 \gt 0U2>0、U3<0U_3 \lt 0U3<0;
- 在扇区二:U1>0U_1 \gt 0U1>0、U2<0U_2 \lt 0U2<0、U3<0U_3 \lt 0U3<0;
- 在扇区三:U1>0U_1 \gt 0U1>0、U2<0U_2 \lt 0U2<0、U3>0U_3 \gt 0U3>0;
- 在扇区四:U1<0U_1 \lt 0U1<0、U2<0U_2 \lt 0U2<0、U3>0U_3 \gt 0U3>0;
- 在扇区五:U1<0U_1 \lt 0U1<0、U2>0U_2 \gt 0U2>0、U3>0U_3 \gt 0U3>0;
- 在扇区六:U1<0U_1 \lt 0U1<0、U2>0U_2 \gt 0U2>0、U3<0U_3 \lt 0U3<0;
定义符号 A、B、C,它们的取值条件如下:
- 当 U1>0U_1 \gt 0U1>0 时,A=1A = 1A=1;当 U1<0U_1 \lt 0U1<0 时,A=0A = 0A=0;
- 当 U2>0U_2 \gt 0U2>0 时,B=1B = 1B=1;当 U2<0U_2 \lt 0U2<0 时,B=0B = 0B=0;
- 当 U3>0U_3 \gt 0U3>0 时,C=1C = 1C=1;当 U3<0U_3 \lt 0U3<0 时,C=0C = 0C=0;
定义符号 N,它的取值表达式为:N=4C+2B+AN = 4C+2B+AN=4C+2B+A;
综合上述的所有内容,我们可以得到一张,可以用于判断扇区位置的表格:
| 扇区 | C | B | A | N |
|---|---|---|---|---|
| 一 | 0 | 1 | 1 | 3 |
| 二 | 0 | 0 | 1 | 1 |
| 三 | 1 | 0 | 1 | 5 |
| 四 | 1 | 0 | 0 | 4 |
| 五 | 1 | 1 | 0 | 6 |
| 六 | 0 | 1 | 0 | 2 |
2.5 控制流程
对应任意一个扇区,我们都有一个固定的控制顺序,目的是避免单次同时控制多个 MOS 管,尽可能减少导通损耗。
以扇区一为例:
0
1
2
3
4
5
100
110
111
000
先 A 相导通、BC 相断开(状态 100);接着导通 B 相,C 相仍然断开(状态 110);接着三相同时导通;再逆过来,C 相先断开(状态 110);再断开 B 相(状态 100);最后三相全断开(状态 000);接着 先 A 相导通(状态 100);以此反复。这样就是在一个扇区内的 ABC 三相 MOS 的控制流程。
在实际应用,我们需要通过单片机的定时器来输出 PWM,观察控制流程可以发现 MOS 的导通从 000→111000 \rightarrow 111000→111,又从 111→000111 \rightarrow 000111→000 刚好具有对称的特性,如果定时器采用中心对称的计数方式,那么我们就可以只控制前部分的逻辑,由单片机的硬件自动帮我们完成后部分的控制逻辑。
例如:
定义 OCREF A 为 A 相的比较器,OCREF B 为 B 相的比较器,计数器递增到达 OCREF A 时,导通 A 相 MOS 管,实现状态从 000→100000 \rightarrow 100000→100;计数器继续递增到达 OCREF B 时,导通 B 相 MOS 管,实现状态从 100→110100 \rightarrow 110100→110;同理,可以再加一个 OCREF C 来实现 C 相的控制。
当计数器到达顶点后开始递减,以此会断开 C 相的 MOS、B 相的 MOS,最后是 A 相的 MOS,进而自动实现从 111→000111 \rightarrow 000111→000 状态的切换。

2.6 定时器计数器和比较器取值
由于我们可以采用中心对齐的方式来设置定时器,因此定时器的计数器取值就取决于我们期望的输出频率计算的值,再除以 2 即可。
例如:采用频率为 10KHz 的 PWM 周期来驱动电机。采用 STM32G474 这块单片机,其系统主频的最大 170MHz,如果 HRTIM 基于主频时钟,这时我们设置定时器计数器的值为 8500 即可实现 10KHz 的输出频率:

比较器的取值我们定义 A 相的取值为 TaT_aTa,同理 BC 分别为 TbT_bTb 和 TcT_cTc。由于在不同扇区,我们要控制基本矢量的流程也不一样,因此当电机处于不同的扇区时,这三相的取值大小会有不同,总结下来,可写成下面的表达式:
{T1=Ts−Tx−Ty4T2=Ts+Tx−Ty4T3=Ts+Tx+Ty4 \begin{cases} T_1 = \frac{T_s - T_x - T_y}{4}\\ T_2 = \frac{T_s + T_x - T_y}{4}\\ T_3 = \frac{T_s + T_x + T_y}{4} \end{cases} ⎩ ⎨ ⎧T1=4Ts−Tx−TyT2=4Ts+Tx−TyT3=4Ts+Tx+Ty
其中:
- T1T_1T1:是第一个比较器的值,可能是比较器 A/B/C 的任一一个;
- T2T_2T2:是第二个比较器的值;
- T3T_3T3:是第三个比较器的值;
不同的扇区 ABC 的比较器分别的取值,如下表:
| 扇区 | TaT_aTa | TbT_bTb | TcT_cTc |
|---|---|---|---|
| 一 | T1T_1T1 | T2T_2T2 | T3T_3T3 |
| 二 | T2T_2T2 | T1T_1T1 | T3T_3T3 |
| 三 | T3T_3T3 | T1T_1T1 | T3T_3T3 |
| 四 | T3T_3T3 | T2T_2T2 | T1T_1T1 |
| 五 | T2T_2T2 | T3T_3T3 | T1T_1T1 |
| 六 | T1T_1T1 | T3T_3T3 | T2T_2T2 |
2.7 小结
开环控制的流程如下:
- 我们需要得到当前期望电机输出的力矩、磁阻和角度的大小、分别对应 iqi_qiq(vqv_qvq)、idi_did(vdv_dvd)和 θ\thetaθ;
- 我们将期望输出的 iqi_qiq 或 vqv_qvq、idi_did 或 vdv_dvd 和 θ\thetaθ,通过反 Park 变换成 UαU_\alphaUα 和 UβU_\betaUβ;
- 根据 UαU_\alphaUα 和 UβU_\betaUβ 的值,可以得到当前输出要作用在哪个扇区上,判断出需要控制的两个基本矢量;
- 根据 UαU_\alphaUα 和 UβU_\betaUβ 的值,得到两个基本矢量的作用时长;
- 确定电机的输出控制频率,计算出计数器的上限值 ;同时计算出 T1T2T3T_1 T_2 T_3T1T2T3 的值,然后根据当前输出所在的扇区,分别赋值为三相各自的比较器;
3. Simulink 仿真 SVPWM 算法
总体框图如下:

TpwmT_{pwm}Tpwm 是 PWM 的输出"频率",这里模型设置的步长是 1e-6 即 0.00001 秒运行一次,即该模型的一个 PWM 的周期为 18000×1e−6=0.18s18000 \times 1e-6 = 0.18s18000×1e−6=0.18s,这取值并没有太大所谓,主要是定义 TsT_sTs 的时长。

设置期望的电机旋转角度 θ\thetaθ 设置如下,输出 0 到 2π2\pi2π,旋转的速度为 0.1s 一圈:

FOC 算法的框图如下:

输入进来后,先对 θ\thetaθ 和 vqvdv_q v_dvqvd 进行 反 Park 变换,得到对应的 UβUαU_\beta U_\alphaUβUα 的值,然后输入到 SVPWM 算法中,计算得到对应的定时器三个比较器的取值。将三个比较器的值输出。
其中 SVPWM 的实现如下:
matlab
function [Tcmp1, Tcmp2, Tcmp3, Sector] = fcn(Valpha, Vbeta, Udc, Tpwm)
Sector = single(0);
Tcmp1 = single(0);
Tcmp2 = single(0);
Tcmp3 = single(0);
N = single(0);
%======================== Parameters Statement ==========================%
Vref1 = Vbeta; % A
Vref2 = (sqrt(3)*Valpha - Vbeta)/2; % B
Vref3 = (-sqrt(3)*Valpha - Vbeta)/2; % C
%========================= Sector Calculation ===========================%
% N = 4C + 2B + A
if (Vref1 > 0)
N = single(1);
end
if (Vref2 > 0)
N = N + single(2);
end
if (Vref3 > 0)
N = N + single(4);
end
switch (N)
case 1 % 扇区二
Sector = single(2);
case 2 % 扇区六
Sector = single(6);
case 3 % 扇区一
Sector = single(1);
case 4 % 扇区四
Sector = single(4);
case 5 % 扇区三
Sector = single(3);
otherwise % 扇区五
Sector = single(5);
end
%========================= X Y Z Calculation ===========================%
X = sqrt(3)*Vbeta*Tpwm/Udc;
Y = Tpwm/Udc*(3/2*Valpha+sqrt(3)/2*Vbeta);
Z = Tpwm/Udc*(-3/2*Valpha+sqrt(3)/2*Vbeta);
%======================= Duty Ratio Calculation ========================%
% 根据当前所在的扇区
% 确定对应扇区的两个基本矢量 Tx Ty 的作用时长
switch (N)
case 1 % 扇区二
Tx = Z; Ty = Y;
case 2 % 扇区六
Tx = Y; Ty = -X;
case 3 % 扇区一
Tx = -Z; Ty = X;
case 4 % 扇区四
Tx = -X; Ty = Z;
case 5 % 扇区三
Tx = X; Ty = -Y;
otherwise % 扇区五
Tx = -Y; Ty = -Z;
end
if Tx + Ty > Tpwm
Tx = Tx/(Tx+Ty);
Ty = Ty/(Tx+Ty);
else
Tx = Tx;
Ty = Ty;
end
T1 = single((Tpwm - (Tx+Ty))/4.0);
T2 = single(Tx + T1 / 2);
T3 = single(Ty + T2 / 2);
%======================= Duty Ratio Calculation ========================%
switch (N)
case 1 % 扇区二
Tcmp1 = T2;
Tcmp2 = T1;
Tcmp3 = T3;
case 2 % 扇区六
Tcmp1 = T1;
Tcmp2 = T3;
Tcmp3 = T2;
case 3 % 扇区一
Tcmp1 = T1;
Tcmp2 = T3;
Tcmp3 = T2;
case 4 % 扇区四
Tcmp1 = T2;
Tcmp2 = T3;
Tcmp3 = T1;
case 5 % 扇区三
Tcmp1 = T3;
Tcmp2 = T1;
Tcmp3 = T2;
otherwise % 扇区五
Tcmp1 = T3;
Tcmp2 = T2;
Tcmp3 = T1;
end
end % function
SVPWM 输出接到一个模拟的定时器中,定时器的计数器采用中心对称的计数方式,设置数值为 9000 等于 1/21/21/2 的 TpwmT_{pwm}Tpwm:

通过一个减法器,让当前计数器的值减去比较器的值,结果输入到一个 Relay 模块,如果计算出的值大于 0,则 Relay 输出 True、反之 Relay 输出 False,该输出值通过 Mux 合并输出。

截取最终输出的波形其中一小段查看,可以看到随着 θ\thetaθ 的递增,A 相的占空比逐渐增大、C 相的占空比逐渐减小,而 B 相保存不变。对应是扇区六的控制,说明当前正在控制电机从 U5U_5U5 逐渐转动到 U4U_4U4,从扇区六向扇区一移动:

4. 配置 HTRIM 输出 SVPWM
设计思路:
- 配置三路定时器 A\B\C 工作在 up-down 模式,采用中心对称生成互补的 PWM 波;
- 配置主定时器用于同步 A\B\C 三个定时器的 PWM 输出;
- 配置 A\B\C 三路的输出一个带死区的互补 PWM;
4.1 配置主定时器

配置主定时器的输出频率为 10KHz,用于同步复位三个定时器的计数器。
4.2 配置定时器工作在 up-down 模式
参考手册可以知道,如果把 Compare 设置成为 greater than :

可以实现在计数器达到 CMP 值(比较器)时输出高电平、在计数器值小于 CMP 值后自动拉低输出为低电平,正好可以符合我们的需求。
以定时器 A 为例:

- 配置定时器 A 使能 TA1 和 TA2 的输出;
- 配置定时器 A 的定时器分频基准时钟为系统频率 170MHz;
- 配置定时器 A 计数器工作在 up-down 模式;
- 配置计数器为连续计数模式;

- 配置 Preload Enabled,我们更新比较器的时候,会先写入比较器的影子寄存器,但 Period 触发 Reset 或者 Roll-over 时候再写入到比较器里面;
- 设置在 TA1 和 TA2 插入一个死区;
- 设置 Reset 或者 Roll-over 时候,触发定时器内的计数器值更新(Period 或者比较器等);

- 设置 Reset Trigger 事件有 1 个,就是当 Master Timer 触发 period 事件时候复位计数器(用于三个定时器的计数器同步);
- 设置 Compare Unit 1 比较方式为 greater than comparsion;
- Compare Unit 2 不需要设置,因为开启死区后,已经能够自动为我们输出一个互补信号了;

- 设置 Dead Time 为 Enable,然后 Rising Value 和 Falling Value 为 170,即:死区时间为 1us;

-
设置 Output 1 的配置,配置当 Compare 1 事件触发时,使输出变为 active state;
-
设置默认的 inactive 输出为低电平;
-
不需要设置 Output 2;
生成代码,烧录后即可看到一个对互补输出。

同理配置 Timer B 和 Timer C,怎么判断输出是符合我们前面设计的中心对称呢?我这样是捕获三个定时器的高电平输出,然后分别配置每个定时器的输出不同的占空比,如果它们的高电平能够中心对称就说明,输出是符合我们设计是中心对称输出的。
代码设计如下:
h
struct timer_data
{
uint16_t period1;
uint16_t period2;
uint16_t period3;
uint16_t tcmp1;
uint16_t tcmp2;
uint16_t tcmp3;
};
c
timer_data g_timer_data = {8500, 8500, 8500, 7500, 1250, 4250};
static void svpwm_compare1_update(HRTIM_HandleTypeDef *hhrtim, uint16_t TimerIdx)
{
__HAL_HRTIM_SetPeriod(hhrtim, TimerIdx, g_timer_data.period1);
__HAL_HRTIM_SETCOMPARE(hhrtim, TimerIdx, HRTIM_COMPAREUNIT_1, g_timer_data.tcmp1);
}
static void svpwm_compare2_update(HRTIM_HandleTypeDef *hhrtim, uint16_t TimerIdx)
{
__HAL_HRTIM_SetPeriod(hhrtim, TimerIdx, g_timer_data.period2);
__HAL_HRTIM_SETCOMPARE(hhrtim, TimerIdx, HRTIM_COMPAREUNIT_1, g_timer_data.tcmp2);
}
static void svpwm_compare3_update(HRTIM_HandleTypeDef *hhrtim, uint16_t TimerIdx)
{
__HAL_HRTIM_SetPeriod(hhrtim, TimerIdx, g_timer_data.period3);
__HAL_HRTIM_SETCOMPARE(hhrtim, TimerIdx, HRTIM_COMPAREUNIT_1, g_timer_data.tcmp3);
}
void svpwm_init()
{
svpwm_compare1_update(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A);
svpwm_compare2_update(&hhrtim1, HRTIM_TIMERINDEX_TIMER_B);
svpwm_compare3_update(&hhrtim1, HRTIM_TIMERINDEX_TIMER_C);
HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TA1);
HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TA2);
HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TB1);
HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TB2);
HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TC1);
HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TC2);
// 实际并没有开启 DMA,这里设置成 DMA 方式启动不会有影响的
HAL_HRTIM_WaveformCountStart_DMA(&hhrtim1, HRTIM_TIMERID_TIMER_A);
HAL_HRTIM_WaveformCountStart_DMA(&hhrtim1, HRTIM_TIMERID_TIMER_B);
HAL_HRTIM_WaveformCountStart_DMA(&hhrtim1, HRTIM_TIMERID_TIMER_C);
}
烧录后,捕获三个定时器的 TA1 通道输出:

由此,可见配置没问题。
4.2 SVPWM 实现
SVPWM 算法代码实现如下:
c
void svpwm_calculate(float valpha, float vbeta, float Udc, float ts, float &tcmp1, float &tcmp2, float &tcmp3, uint8_t §or)
{
static const float sqrt3 = std::sqrt(3);
float vref1 = vbeta;
float vref2 = (sqrt3 * valpha - vbeta) / 2.0f;
float vref3 = (-sqrt3 * valpha - vbeta) / 2.0f;
uint8_t n = 0;
n += (vref1 > 0 ? 1 : 0);
n += (vref2 > 0 ? 2 : 0);
n += (vref3 > 0 ? 4 : 0);
switch (n) {
case 1: sector = 2; break;
case 2: sector = 6; break;
case 3: sector = 1; break;
case 4: sector = 4; break;
case 5: sector = 3; break;
default: sector = 5; break;
}
// X Y Z Calculation
float x = sqrt3 * vbeta * ts / Udc;
float y = ts / Udc * (3.0f / 2.0f * valpha + sqrt3 / 2.0f * vbeta);
float z = ts / Udc * (-3.0f / 2.0f * valpha + sqrt3 / 2.0f * vbeta);
// Duty Ratio Calculation
// 根据当前所在的扇区
// 确定对应扇区的两个基本矢量 Tx Ty 的作用时长
float tx = 0.0f;
float ty = 0.0f;
switch (sector) {
case 1: tx = -z; ty = x; break;
case 2: tx = z; ty = y; break;
case 3: tx = x; ty = -y; break;
case 4: tx = -x; ty = z; break;
case 5: tx = -y; ty = -z; break;
default: tx = y; ty = -x; break;
}
/* Over-modulation: scale tx,ty so tx+ty == ts (same units as before), keep direction. */
const float tx_ty = tx + ty;
if (tx_ty > ts && tx_ty > 0.0f) {
const float scale = ts / tx_ty;
tx *= scale;
ty *= scale;
}
// 计算比较器的三个基本取值
float t1 = (ts - (tx_ty)) / 4.0f;
float t2 = t1 + tx / 2;
float t3 = t2 + ty / 2;
switch (sector) {
case 1: tcmp1 = t1; tcmp2 = t2; tcmp3 = t3; break;
case 2: tcmp1 = t2; tcmp2 = t1; tcmp3 = t3; break;
case 3: tcmp1 = t3; tcmp2 = t1; tcmp3 = t2; break;
case 4: tcmp1 = t3; tcmp2 = t2; tcmp3 = t1; break;
case 5: tcmp1 = t2; tcmp2 = t3; tcmp3 = t1; break;
default: tcmp1 = t1; tcmp2 = t3; tcmp3 = t2; break;
}
}
4.3 Vofa+ 观测 SVPWM 输出
配置 Vofa+ 观察我们实现的 SVPWM 是否生成了正确的马鞍波。这里我采用的 UART + DMA,使用 DMA 自动传输将 s_vofaFrame 传输给串口,波特率配置为 921600。


vofa 下位机实现代码:
c
enum VofaDataIdx : uint8_t
{
VOFADATA_IDX_TCMP1 = 0, /*!< 比较器1的值 */
VOFADATA_IDX_TCMP2, /*!< 比较器2的值 */
VOFADATA_IDX_TCMP3, /*!< 比较器3的值 */
VOFADATA_IDX_SECTOR, /*!< 扇区 */
VOFADATA_IDX_THETA, /*!< 角度 */
VOFADATA_IDX_COUNT, /*!< 数据长度 */
};
struct VofaFrame
{
float fdata[VOFADATA_IDX_COUNT];
unsigned char tail[4]{0x00, 0x00, 0x80, 0x7f};
};
extern UART_HandleTypeDef huart2;
static VofaFrame s_vofaFrame;
VofaService::VofaService(Context &context)
: _context(context),
_focService(_context.focService()),
_sensorService(_context.sensorService())
{
}
VofaService::~VofaService()
{
}
void VofaService::init()
{
HAL_UART_Transmit_DMA(&huart2, (uint8_t *)&s_vofaFrame, sizeof(s_vofaFrame));
}
void VofaService::yield()
{
s_vofaFrame.fdata[VOFADATA_IDX_TCMP1] = _focService._tcmp1;
s_vofaFrame.fdata[VOFADATA_IDX_TCMP2] = _focService._tcmp2;
s_vofaFrame.fdata[VOFADATA_IDX_TCMP3] = _focService._tcmp3;
s_vofaFrame.fdata[VOFADATA_IDX_SECTOR] = static_cast<float>(_focService._sector);
s_vofaFrame.fdata[VOFADATA_IDX_THETA] = _focService._theta;
}
可以观察到波形为正确的马鞍波:

4.4 控制电机
