STM32 HRTIM 学习心得(3):SVPWM 输出

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 小结

开环控制的流程如下:

  1. 我们需要得到当前期望电机输出的力矩、磁阻和角度的大小、分别对应 iqi_qiq(vqv_qvq)、idi_did(vdv_dvd)和 θ\thetaθ;
  2. 我们将期望输出的 iqi_qiq 或 vqv_qvq、idi_did 或 vdv_dvd 和 θ\thetaθ,通过反 Park 变换成 UαU_\alphaUα 和 UβU_\betaUβ;
  3. 根据 UαU_\alphaUα 和 UβU_\betaUβ 的值,可以得到当前输出要作用在哪个扇区上,判断出需要控制的两个基本矢量;
  4. 根据 UαU_\alphaUα 和 UβU_\betaUβ 的值,得到两个基本矢量的作用时长;
  5. 确定电机的输出控制频率,计算出计数器的上限值 ;同时计算出 T1T2T3T_1 T_2 T_3T1T2T3 的值,然后根据当前输出所在的扇区,分别赋值为三相各自的比较器;

总体框图如下:

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

设计思路:

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

4.1 配置主定时器

配置主定时器的输出频率为 10KHz,用于同步复位三个定时器的计数器。

4.2 配置定时器工作在 up-down 模式

参考手册可以知道,如果把 Compare 设置成为 greater than :

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

以定时器 A 为例:

  1. 配置定时器 A 使能 TA1 和 TA2 的输出;
  2. 配置定时器 A 的定时器分频基准时钟为系统频率 170MHz;
  3. 配置定时器 A 计数器工作在 up-down 模式;
  4. 配置计数器为连续计数模式;
  1. 配置 Preload Enabled,我们更新比较器的时候,会先写入比较器的影子寄存器,但 Period 触发 Reset 或者 Roll-over 时候再写入到比较器里面;
  2. 设置在 TA1 和 TA2 插入一个死区;
  3. 设置 Reset 或者 Roll-over 时候,触发定时器内的计数器值更新(Period 或者比较器等);
  1. 设置 Reset Trigger 事件有 1 个,就是当 Master Timer 触发 period 事件时候复位计数器(用于三个定时器的计数器同步);
  2. 设置 Compare Unit 1 比较方式为 greater than comparsion;
  3. Compare Unit 2 不需要设置,因为开启死区后,已经能够自动为我们输出一个互补信号了;
  1. 设置 Dead Time 为 Enable,然后 Rising Value 和 Falling Value 为 170,即:死区时间为 1us;
  1. 设置 Output 1 的配置,配置当 Compare 1 事件触发时,使输出变为 active state;

  2. 设置默认的 inactive 输出为低电平;

  3. 不需要设置 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 &sector)
{
    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 控制电机

相关推荐
perseverance5211 小时前
MCU串口实现串行flash编程器功能
单片机·flash编程
421!11 小时前
C 语言学习笔记——11(函数指针与指针函数)
c语言·开发语言·笔记·单片机·学习
Redemption12 小时前
嵌软面试每日一阅----freeRTOS(三)
stm32·单片机·嵌入式硬件·mcu·物联网·面试·51单片机
篮子里的玫瑰12 小时前
STM32 GPIO八种输入输出模式深度解析:原理、区别与选型指南
stm32·单片机·嵌入式硬件
季鹏EthanJ12 小时前
STM32首次烧录选择erase sectors导致程序跑飞
stm32·单片机·嵌入式硬件·启动故障·erase·程序跑飞
DA022112 小时前
系统移植-STM32MP1_Buildroot根文件系统移植
stm32·单片机·嵌入式硬件·bsp·系统移植
cmpxr_12 小时前
【单片机】STM32晶振引脚不连晶振时的做法
stm32·单片机·嵌入式硬件
cmpxr_13 小时前
【单片机】STM32Fxx中RTC掉电不走
stm32·单片机
qq_1508419914 小时前
关于TTL单端到RS485差分
嵌入式硬件