从电机本质到park变换再到SVPWM,SVPWM代码实现

参考:
空间电压矢量调制 SVPWM 技术

彻底吃透SVPWM如此简单

【永磁同步电机】SVPWM控制算法+Matlab/Simulink仿真详解

ST电机库v5.4.4源代码分析(3): α、β方向的电流值与三相PWM 波形的联系

MC SDK 5.x 中增加位置环

电机的本质和扭矩的概念

电机是由定子和转子组成,可以看成两个磁铁,转子是永磁铁,定子是电磁铁。
转子的磁场大小是固定的,定子的磁场大小可以由绕组中的电流大小来控制。
当转子和定子没有夹角时,不会有扭矩的存在,转子不会动,如下:
在这里插入图片描述
把外面的定子磁场扭转一定角度,根据磁铁异性相吸的原理,内部的转子会跟着旋转,这个时候就存在扭矩了。
在这里插入图片描述
扭矩的大小怎么衡量呢?
所有的电机,扭矩的大小正比于内外两个磁场的叉乘,也就是两个磁场围成的平行四边形的面积。
于是当两个磁场重合,平行四边形面积为0,此时扭矩为0。当两个磁场呈90度,平行四边形面积最大,扭矩也最大。
在这里插入图片描述
到此,我们对电机的控制就变成了对平行四边形面积的控制
我们希望用同样的电流大小来得到最大的扭矩,因此我们需要编码器来测量转子的位置,然后定子磁场与转子磁场相差90度。
在这里插入图片描述
在foc程序中:
假如电机旋转角速度为ω=2πf,即旋转一周所需的时间为T=1/ f;
开关管载波频率是 fs ,即每隔1/fs时间计算一次foc。频率比为 R=fs / f。
相当于把旋转平面等切割成R个小增量,定子的磁场是R边形。每次计算foc的时候用编码器测量转子位置,然后超前90度。

假如R=4,也就是转子转一圈,我们改变4次定子磁场的位置,每次改变时跟转子磁场夹角90度。

引出d轴和q轴

把上面的两个磁场叉乘的平行四边形重新组合下,组合前后面积不变。
在这里插入图片描述
这个时候扭矩的大小就可以用平行于内磁场的磁场大小(我们称为d轴,direct,也称直轴。其大小等于内磁场+外磁场在内磁场方向上的分量)乘上垂直于内磁场的大小(我们称为q轴,quadrature,也称交轴。其大小等于外磁场在内磁场垂直方向上的分量)。
d轴是平行于转子磁场方向的,夹角为0,因此d轴上面施加磁场只能影响转子的磁场,并不能得到扭矩。
因此我们说:d轴控制转子磁通,q轴控制电机的转矩输出,一般的使用中我们只需要控制电机的转矩输出即可,可以直接把d轴设置为0。

引出反park变换

从前面知道,我们控制电机其实是:
用编码器测量转子的电角度,然后根据转子的电角度(内磁场的角度)用电机绕组产生d、q轴大小的外磁场。
如下图,如果转子的电角度在θ1,则我们就要在θ1处产生d、q轴大小的外磁场。如果转子的电角度在θ2,则我们就要在θ2处产生d、q轴大小的外磁场。
在这里插入图片描述
我们把角度θ1的情况单独提出来,把它移到原点去,然后把x、y轴重命名为α,β:
在这里插入图片描述
根据三角函数的关系,我们可以把q、d轴的大小分解到α,β轴上:
在这里插入图片描述
这个就是反park变换了。

如果d轴等于0:
在这里插入图片描述
从上面式子知道:α、β轴是互差90度的。α、β轴的大小随正弦规律变化。

从前面我们知道:q、d轴是360度旋转的磁场。q、d轴大小在旋转过程目前还是个定值。

他们俩现在是可以变换的,是不是意味着:互差90度的大小随正弦规律变化的两个磁场,可以产生大小恒定的旋转磁场。

如下图,α、β轴值按正弦规律变化,他们的矢量合成,角度从0度增加到360度,且长度是恒定的。
在这里插入图片描述
其实就是矩形的边长和对角线长的关系。

α、β轴的值是SVPWM算法的输入:
在这里插入图片描述
α、β轴的值是互差90度的大小随正弦规律的值,输入给SVPWM算法,SVPWM算法就会输出大小恒定的旋转磁场。

SVPWM算法

α、β轴的值输入给SVPWM算法,SVPWM算法输出PWM控制6个MOS管的开关。
通过6个MOS管的开关来输出大小恒定的旋转磁场。
在这里插入图片描述

由于逆变器三相桥臂共有6个开关管,定义开关函数Sx(x=a、b、c) :
在这里插入图片描述
(Sa、Sb、Sc)的全部可能组合共有八个,包括 6个非零矢量Ul(001)、U2(010)、U3(011)、U4(100)、U5(101)、U6(110)、和两个零矢量 U0(000)、U7(111),下面以其中一种开关组合为例分析,假设Sx(x=a、b、c)=(100),此时等效电路如图:
在这里插入图片描述
相电压可以表示为:(相电压是每相相对于电机中间连接点的电压)
在这里插入图片描述
同理可得其他开关状态的相电压。另外线电压是两相之间的电压差,如Uab=Ua-Ub。

电机的三相是差了120度的,因此如果要把三相电压相加就需要用矢量表示:
在这里插入图片描述
注:为了使合成空间矢量在静止三相坐标轴上的投影和分矢量相等,这里引入了2/3。当成系数即可,不用深究。

在这里插入图片描述
把上面的8个电压空间矢量按照Uout的相位关系放在扇区图中:
在这里插入图片描述
上图中,6个非零矢量幅值相同,相邻的矢量间隔60度。两个零矢量幅值为零,位于中心。360度每隔60度分成6个扇区。

前面说过电机转子就是永磁铁,用定子产生跟转子超前90度的磁铁,转子就会跟着转起来。对应到上面图,假如要产生磁铁Uref,它的角度位于扇区I,它相邻是U6、U4。
在这里插入图片描述

Uref就可以矢量分解成U6、U4,也就是说,可以用U6、U4来产生我们想要的Uref。那U6、U4各出多少力呢?

假设载波频率是fs,那么总时间就是一个周期Ts,那么T4、T6、零电压矢量T7/T0的作用时间用下面来计算:

在这里插入图片描述
在 SVPWM 中,适当选择零矢量,可减少开关次数,减少开关损耗。
当 U4(100)切换至 U0(000)时,只需改变 A 相上下一对切换开关,若由 U4(100)切换至 U7(111)则需改变 B、C 相上下两对切换开关,增加了一倍的切换损失。
因此要改变电压向量 U4(100)、U2(010)、U1(001)的大小,需配合零电压向量 U0(000),而要改变 U6(110)、U3(011)、U5(100), 需配合零电压向量 U7(111)。
这样通过在不同区间内安排不同的开关切换顺序, 就可以获得对称的输出波形,其它各扇区的开关切换顺序如表 2-2 所示。
在这里插入图片描述

以第Ⅰ扇区为例,其所产生的三相波调制波形在一个载波周期时间Ts内如图 2-11 所示,图中电压向量出现的先后顺序为 U0、U4、U6、U7、U6、U4、U0
在这里插入图片描述
前面说过:

假如电机旋转角速度为ω=2πf,即旋转一周所需的时间为T=1/ f;
开关管载波频率是 fs ,即每隔1/fs时间计算一次foc。频率比为 R=fs / f。
相当于把旋转平面等切割成R个小增量,定子的磁场是R边形。每次计算foc的时候用编码器测量转子位置,然后超前90度。
假如R=4,也就是转子转一圈,我们改变4次定子磁场的位置,每次改变时跟转子磁场夹角90度。

结合SVPWM后,就变成了:
假如电机旋转角速度为ω=2πf,即旋转一周所需的时间为T=1/ f;
开关管载波频率是 fs ,即每隔Ts时间计算一次foc。
因此电机转一圈,可以计算R=fs / f 次,每次计算foc就相当于移动定子磁铁的位置。
在这里插入图片描述
计算foc时,会使用编码器测量转子的角度,然后定子超前90度。
假如定子超前90度后处于扇区I,那么就控制6个MOS管以下面的顺序来开关,T0、T4、T6、T7的时间用上面的公式计算。
在这里插入图片描述
电机转一圈,我们移动了定子磁铁R次,因此R越大,R边形越接近圆形。
在这里插入图片描述

5 段式 SVPWM
对7段而言,发波对称,谐波含量较小,但是每个开关周期有6次开关切换,为了进一步减少开关次数,采用每相开关在每个扇区状态维持不变的序列安排,使得每个开关周期只有3次开关切换,但是会增大谐波含量。
在这里插入图片描述
5 段式 SVPWM 的首尾少了T0,当从扇区I到扇区II时,就会出现100变成010,有两组开关发生切换,大大增大开关噪声。

合成矢量 Uref 所处扇区 N 的判断

在这里插入图片描述
α、β轴的值是互差90度的大小随正弦规律的值,输入给SVPWM算法。

SVPWM算法的第一步是判断由 Uα和 Uβ所决定的空间电压矢量(Uref)所处的扇区。
在这里插入图片描述
定义3个式子:
在这里插入图片描述

把式子画到α、β坐标轴上:
在这里插入图片描述

上图中蓝色是U2大于0区域,红色是U3大于0区域。

再定义3个变量A、B、C,取值:
U1>0,A=1 else A=0
U2>0,B=1 else B=0
U3>0,C=1 else C=0

扇区即可用N=A+2B+4C计算出:

扇区号ABCN=A+2B+4C
1103
1001
1015
0014
0116
0102

扇区作用时间计算

以 Uref 处在第Ⅰ扇区时进行分析
在这里插入图片描述
把Uref 分解到α、β轴上(下式Ud就是Udc,即母线电压)
Uref在U4上分量大小是(T4/Ts)U4,(T4/Ts)U4在β轴上分量是0,在α轴上分量是(T4/Ts)U4
Uref在U6上分量大小是(T6/Ts)U6,(T6/Ts)U6在β轴上分量是(T6/Ts)U6*sinπ/3,在α轴上分量是(T6/Ts)U6*cosπ/3
在这里插入图片描述
把电压矢量U4(100)和U6(110)的Uout式子带入,分别是2/3Udc和2/3Udc*cos(π/3),然后展开:
在这里插入图片描述
把上式整理后得到:
在这里插入图片描述

上面计算扇区用到的U1、U2、U3:
在这里插入图片描述
同理可求得Uref在其它扇区中各矢量的作用时间,结果如表2-4所示。表中两个非零矢量作用时间的比例系数为K =√3Ts/Udc 。
在这里插入图片描述

也有地方是先计算出3个中间变量X、Y、Z,再根据表来计算扇区作用时间的。跟上表意思相同。
在这里插入图片描述
在这里插入图片描述
Tfirst 代表任意扇区内逆时针数第一个非零电压向量持续的时间, Tsecond代表同一扇区第二个非零电压向量持续的时间。在第一个扇区,显然 Tfirst 和 Tsecond 分别是 T4 和 T6。

当非零矢量的和大于Ts,说明过调制了,需要按照比例缩小处理下,比如第I扇区:
在这里插入图片描述

确定切换时间点

计算出了扇区作用时间,接下来就是给定时器赋值了。还是来看扇区I,PWM波形如下:
在这里插入图片描述

可以看到:
在这里插入图片描述
把每个扇区的第一个电压矢量设为Tx,第二个设为Ty,那就可以:
在这里插入图片描述
根据包含关系,tc>tb>ta

其他的扇区也可以列出来:

在这里插入图片描述

在这里插入图片描述

MATLAB代码

function [Tcm1,Tcm2,Tcm3,N] = SVPWM(Valpha,Vbeta,Udc,Tpwm,ARR)

%输出变量初始化
Tcm1 = 0;
Tcm2 = 0;
Tcm3 = 0;
N = 0;


%扇区计算
%N与扇区对应的关系
%  3   1   5    4  6  2
%  I  II  III  IV  V  VI

Vref1 = Vbeta;                      %U1
Vref2 = (sqrt(3)*Valpha-Vbeta)/2;   %U2
Vref3 = (-sqrt(3)*Valpha-Vbeta)/2;  %U3

%N=A+2*B+4*C
if(Vref1>0)  
    N = 1;
end

if(Vref2>0)
    N = N+2;
end

if(Vref3>0)
    N = N+4;
end


%扇区内合成矢量作用时间计算
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);

switch(N)
    case 1
        T1 = Z;T2 = Y;
    case 2
        T1 = Y;T2 = -X;
    case 3
        T1 = -Z;T2 = X;
    case 4
        T1 = -X;T2 = Z;
    case 5
        T1 = X;T2 = -Y;
    otherwise
        T1 = -Y;T2 = -Z;
end

%过调制处理
if(T1+T2>Tpwm)
    T1 = Tpwm*T1/(T1+T2);
    T2 = Tpwm*T2/(T1+T2);
else
    T1 = T1;
    T2 = T2;
end


%扇区内合成矢量切换点时间计算
%此处为7段式,两个零矢量000 111 111插在中间,000均分插在两端
ta = (Tpwm-(T1+T2))/4;
tb = ta+T1/2;
tc = tb+T2/2;


%输出调制信号
switch(N)
    case 1
        Tcm1 = tb;
        Tcm2 = ta;
        Tcm3 = tc;
    case 2
        Tcm1 = ta;
        Tcm2 = tc;
        Tcm3 = tb;
    case 3
        Tcm1 = ta;
        Tcm2 = tb;
        Tcm3 = tc;
    case 4
        Tcm1 = tc;
        Tcm2 = tb;
        Tcm3 = ta;
    case 5
        Tcm1 = tc;
        Tcm2 = ta;
        Tcm3 = tb;
    case 6
        Tcm1 = tb;
        Tcm2 = tc;
        Tcm3 = ta;
end

%调制信号处理,生成输入到MCU中的调制信号
Tcm1 = 2*Tcm1/Tpwm; 
Tcm2 = 2*Tcm2/Tpwm;
Tcm3 = 2*Tcm3/Tpwm;

Tcm1 = Tcm1*ARR; 
Tcm2 = Tcm2*ARR; 
Tcm3 = Tcm3*ARR;

end

在这里插入图片描述

C语言代码

ST的电机库v5.4.4,在mc_tasks.c中进行FOC的计算:

inline uint16_t FOC_CurrControllerM1(void)
{
  qd_t Iqd, Vqd;
  ab_t Iab;
  alphabeta_t Ialphabeta, Valphabeta;

  int16_t hElAngle;
  uint16_t hCodeError;
  SpeednPosFdbk_Handle_t *speedHandle;

  speedHandle = STC_GetSpeedSensor(pSTC[M1]);
  hElAngle = SPD_GetElAngle(speedHandle);
  PWMC_GetPhaseCurrents(pwmcHandle[M1], &Iab);          //相电流采集
  Ialphabeta = MCM_Clarke(Iab);                         //Ia,Ib通过Clark变换得到Ialpha和Ibeta
  Iqd = MCM_Park(Ialphabeta, hElAngle);                 //输入电角度、Ialpha和Ibeta,经过Park变换得到Iq、Id 
  Vqd.q = PI_Controller(pPIDIq[M1],                     //q轴PID
            (int32_t)(FOCVars[M1].Iqdref.q) - Iqd.q);

  Vqd.d = PI_Controller(pPIDId[M1],                     //d轴PID
            (int32_t)(FOCVars[M1].Iqdref.d) - Iqd.d);

  Vqd = Circle_Limitation(pCLM[M1], Vqd);               //把经过PID调整过的d、q值的合成矢量模值限制在最大值,如果超过了,那么等比例缩小d、q值 
  hElAngle += SPD_GetInstElSpeedDpp(speedHandle)*REV_PARK_ANGLE_COMPENSATION_FACTOR;
  Valphabeta = MCM_Rev_Park(Vqd, hElAngle);             //反Park变换,由Vq、Vd得到Valpha、Vbeta
  hCodeError = PWMC_SetPhaseVoltage(pwmcHandle[M1], Valphabeta);  //SVPWM
  FOCVars[M1].Vqd = Vqd;
  FOCVars[M1].Iab = Iab;
  FOCVars[M1].Ialphabeta = Ialphabeta;
  FOCVars[M1].Iqd = Iqd;
  FOCVars[M1].Valphabeta = Valphabeta;
  FOCVars[M1].hElAngle = hElAngle;
  return(hCodeError);
}

在这里插入图片描述
从上面的代码可以看出,完全是按照上图流程来进行处理的,最后把Valphabeta输入PWMC_SetPhaseVoltage函数,进行SVPWM计算:

__weak uint16_t PWMC_SetPhaseVoltage( PWMC_Handle_t * pHandle, alphabeta_t Valfa_beta )
{
  int32_t wX, wY, wZ, wUAlpha, wUBeta, wTimePhA, wTimePhB, wTimePhC;

  wUAlpha = Valfa_beta.alpha * ( int32_t )pHandle->hT_Sqrt3;  //α*2*sqrt(3)*Ts
  wUBeta = -( Valfa_beta.beta * ( int32_t )( pHandle->PWMperiod ) ) * 2;

  wX = wUBeta;
  wY = ( wUBeta + wUAlpha ) / 2;
  wZ = ( wUBeta - wUAlpha ) / 2;

  /* Sector calculation from wX, wY, wZ */
  if ( wY < 0 )
  {
    if ( wZ < 0 )
    {
      pHandle->Sector = SECTOR_5;
      wTimePhA = ( int32_t )( pHandle->PWMperiod ) / 4 + ( ( wY - wZ ) / ( int32_t )262144 );
      wTimePhB = wTimePhA + wZ / 131072;
      wTimePhC = wTimePhA - wY / 131072;
      pHandle->lowDuty = wTimePhC;
      pHandle->midDuty = wTimePhA;
      pHandle->highDuty = wTimePhB;
    }
    else /* wZ >= 0 */
      if ( wX <= 0 )
      {
        pHandle->Sector = SECTOR_4;
        wTimePhA = ( int32_t )( pHandle->PWMperiod ) / 4 + ( ( wX - wZ ) / ( int32_t )262144 );
        wTimePhB = wTimePhA + wZ / 131072;
        wTimePhC = wTimePhB - wX / 131072;
        pHandle->lowDuty = wTimePhC;
        pHandle->midDuty = wTimePhB;
        pHandle->highDuty = wTimePhA;
      }
      else /* wX > 0 */
      {
        pHandle->Sector = SECTOR_3;
        wTimePhA = ( int32_t )( pHandle->PWMperiod ) / 4 + ( ( wY - wX ) / ( int32_t )262144 );
        wTimePhC = wTimePhA - wY / 131072;
        wTimePhB = wTimePhC + wX / 131072;
        pHandle->lowDuty = wTimePhB;
        pHandle->midDuty = wTimePhC;
        pHandle->highDuty = wTimePhA;
      }
  }
  else /* wY > 0 */
  {
    if ( wZ >= 0 )
    {
      pHandle->Sector = SECTOR_2;
      wTimePhA = ( int32_t )( pHandle->PWMperiod ) / 4 + ( ( wY - wZ ) / ( int32_t )262144 );
      wTimePhB = wTimePhA + wZ / 131072;
      wTimePhC = wTimePhA - wY / 131072;
      pHandle->lowDuty = wTimePhB;
      pHandle->midDuty = wTimePhA;
      pHandle->highDuty = wTimePhC;
    }
    else /* wZ < 0 */
      if ( wX <= 0 )
      {
        pHandle->Sector = SECTOR_6;
        wTimePhA = ( int32_t )( pHandle->PWMperiod ) / 4 + ( ( wY - wX ) / ( int32_t )262144 );
        wTimePhC = wTimePhA - wY / 131072;
        wTimePhB = wTimePhC + wX / 131072;
        pHandle->lowDuty = wTimePhA;
        pHandle->midDuty = wTimePhC;
        pHandle->highDuty = wTimePhB;
      }
      else /* wX > 0 */
      {
        pHandle->Sector = SECTOR_1;
        wTimePhA = ( int32_t )( pHandle->PWMperiod ) / 4 + ( ( wX - wZ ) / ( int32_t )262144 );
        wTimePhB = wTimePhA + wZ / 131072;
        wTimePhC = wTimePhB - wX / 131072;
        pHandle->lowDuty = wTimePhA;
        pHandle->midDuty = wTimePhB;
        pHandle->highDuty = wTimePhC;
      }
  }

  pHandle->CntPhA = ( uint16_t )wTimePhA;
  pHandle->CntPhB = ( uint16_t )wTimePhB;
  pHandle->CntPhC = ( uint16_t )wTimePhC;

  if ( pHandle->DTTest == 1u )
  {
    /* Dead time compensation */
    if ( pHandle->Ia > 0 )
    {
      pHandle->CntPhA += pHandle->DTCompCnt;
    }
    else
    {
      pHandle->CntPhA -= pHandle->DTCompCnt;
    }

    if ( pHandle->Ib > 0 )
    {
      pHandle->CntPhB += pHandle->DTCompCnt;
    }
    else
    {
      pHandle->CntPhB -= pHandle->DTCompCnt;
    }

    if ( pHandle->Ic > 0 )
    {
      pHandle->CntPhC += pHandle->DTCompCnt;
    }
    else
    {
      pHandle->CntPhC -= pHandle->DTCompCnt;
    }
  }

  return ( pHandle->pFctSetADCSampPointSectX( pHandle ) );
}

扇区判断

第1部分,定义3个式子:
在这里插入图片描述

  wUAlpha = Valfa_beta.alpha * ( int32_t )pHandle->hT_Sqrt3;  //α*2*sqrt(3)*Ts
  wUBeta = -( Valfa_beta.beta * ( int32_t )( pHandle->PWMperiod ) ) * 2;  //-β*Ts*2

  wX = wUBeta;                    //-β*Ts*2
  wY = ( wUBeta + wUAlpha ) / 2;  //(α*2*sqrt(3)*Ts-β*Ts*2)/2 = (sqrt(3)*α-β)*Ts
  wZ = ( wUBeta - wUAlpha ) / 2;  //(-α*2*sqrt(3)*Ts-β*Ts*2)/2 = (-sqrt(3)*α-β)*Ts

pHandle->hT_Sqrt3的取值计算:

#define PWM_PERIOD_CYCLES (uint16_t)(ADV_TIM_CLK_MHz*\(uint32_t)1000000u/((uint32_t)(PWM_FREQUENCY)))
#define TIM_CLOCK_DIVIDER  1
#define ADV_TIM_CLK_MHz  168/TIM_CLOCK_DIVIDER
#define PWM_FREQUENCY   16000
#define SQRT3FACTOR (uint16_t) 0xDDB4 /* = (16384 * 1.732051 * 2)*/
									  
.hT_Sqrt3 = (PWM_PERIOD_CYCLES*SQRT3FACTOR)/16384u
	      = ((ADV_TIM_CLK_MHz*1000000/PWM_FREQUENCY)*(16384 * 1.732051 * 2))/16384
		  = (168*1000000/16000) * 1.732051 * 2
	      = (16M/16K)*sqrt(3)*2 = 2*sqrt(3)*Ts

ST的α、β轴跟我们上面讲的有点区别,它的β轴是上下颠倒的,跟我们上面讲的相比β轴上的值需要取负
在这里插入图片描述
上图式子相比源代码中wX、wY、wZ缩小了2*Ts,不影响与0的大小判断。从上图就可以知道代码中扇区的划分。

扇区作用时间计算

再来计算扇区作用时间,把扇区1的计算式子提出来:
在这里插入图片描述
由于β轴颠倒了,因此式子变为(β轴变量符号颠倒):
在这里插入图片描述

对应上面的代码,我们先定义3个变量uX、uY、uZ:
在这里插入图片描述
带入:
在这里插入图片描述
我们知道第1扇区的波形为:
在这里插入图片描述
定义taH、tbH、tcH分别为上图中A、B、C波形高电平阶段时间:
在这里插入图片描述

CntA、CntB、CntC是taH、tbH、tcH时间的一半,这个值会被直接赋值给定时器的比较寄存器。
找到上面扇区1作用时间的计算代码:

      else /* wX > 0 */
      {
        pHandle->Sector = SECTOR_1;
        wTimePhA = ( int32_t )( pHandle->PWMperiod ) / 4 + ( ( wX - wZ ) / ( int32_t )262144 );
        wTimePhB = wTimePhA + wZ / 131072;
        wTimePhC = wTimePhB - wX / 131072;
        pHandle->lowDuty = wTimePhA;
        pHandle->midDuty = wTimePhB;
        pHandle->highDuty = wTimePhC;
      }
  }

  pHandle->CntPhA = ( uint16_t )wTimePhA;
  pHandle->CntPhB = ( uint16_t )wTimePhB;
  pHandle->CntPhC = ( uint16_t )wTimePhC;

262144 = 8 * 32768
131072 = 4 * 32768
这里看到计算值缩小了32768倍,因为电流采样值是使用的Q15格式。

ADC的注入组是左对齐的,ADC寄存器转成实际电压值需要:

(ADC寄存器>>3)/4096*3.3=(ADC寄存器>>15)*3.3

从上面代码看,我们获取ADC寄存器值后左移了1位,相当于是Q15格式*2

后面是乘3.3还是乘2都没有关系,甚至于如果ADC采集值太小还可以再放大,只要测量零电流值和实际测量时倍数相同,ADC采样值不会过小或过大导致溢出就行。

为什么CntA赋值给CCRx

函数PWMC_SetPhaseVoltage的最后将计算出来的“wTimePhA”等值送入了“pHandle->CntPhA”中,接下来在R3_2_WriteTIMRegisters函数中,会把CntPhA赋值给定时器的比较寄存器CCRx中,用于控制PWM的占空比。

在这里插入图片描述
CntA是高电平时间的一半,为什么是高电平时间的一半赋值给CCRx呢?

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = ((TIM_CLOCK_DIVIDER) - 1);         //分频系数
  htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;  //中央对齐模式1,从0计数到 TIM_Period 然后开始减到0,循环
  htim1.Init.Period = ((PWM_PERIOD_CYCLES) / 2);            //计数周期 
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV2;
  
  sConfigOC.OCMode = TIM_OCMODE_PWM1;                       //配置为PWM模式1 
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;               //CHx有效电平的极性为高电平(高侧)
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;             //CHxN有效电平的极性为高电平(低侧) 
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;  

使用的PWM模式1,当CNT寄存器值小于CCR,通道就输出有效状态。因此示意波形如下:
在这里插入图片描述
CNT值从0计数到ARR,然后减到0,然后又增加到ARR。CCRx就是通道x的比较值,当CNT<CCRx,就输出有效电平。有效电平上面配置为高电平,因此当CNT<CCRx,就输出高电平。

在代码中Ts = pHandle->PWMperiod
在这里插入图片描述
pHandle->PWMperiod = PWM_PERIOD_CYCLES
在这里插入图片描述
从上面知道,单片机主频168M,载波频率16K,定时器不分频,PWM_PERIOD_CYCLES 是等于168M/16K的。

在定时器初始化时,ARR赋值为PWM_PERIOD_CYCLES / 2 ,即Ts/2
在这里插入图片描述

在这里插入图片描述

从扇区1的波形可知,CNT从ARR减到0,再增加到ARR才算完成一个Ts,因此定时器初始化时ARR要赋值为Ts/2
CntA值越大,越接近ARR值,高电平时间taH就越宽。高电平时间taH最宽就是等于Ts。
ARR就是高电平时间最大值的一半,因此CCRx也是高电平时间的一半。

电流采样

在这里插入图片描述
U相采样电阻两端电压差输入AMC1200,AMC1200固定放大8倍,然后经过运放偏置1.24V后输入给单片机采样。
采样电阻0.02R。

在这里插入图片描述
从上面代码可以知道,进行FOC计算时,调用R3_2_GetPhaseCurrents函数读取相电流,函数中首先获取ADC寄存器中的值并乘2,然后用偏置值减去它就得到了a相电流值。
偏置值是=相电流为0时的ADC寄存器值 * 2,这个在开机初始化时会进行校准。
单片机的ADC输入最大是3.3V。从上面的电路,当相电流为0,ADC输入电压是1.24V,相电流是有正有负的,因此ADC采样值就在1.24V上下以正弦波变化。当ADC输入变成0V得到最大相电流=1.24/8/0.02=7.75A
偏置电压选3.3V的一半1.65V是最好的,可以用到ADC的全部输入范围。

如果要深究R3_2_GetPhaseCurrents函数,要注意此函数是使用的三电阻采样,FOC运算只需要两相电流值,因此需要根据电压扇区来决定舍弃哪一相的电流值。如果使用的电流传感器来采样电流,程序会简单很多。
在这里插入图片描述
从ADC的配置可以知道,ADC采样的数据是左对齐的,同时又放大了2倍,相当于最大值可以到0xFFF0,最小值0。
当减去偏置电压后,最大值为INT16_MAX,最小值为-INT16_MAX。

电流环

比较好的 Ia 、 Ib波形
在这里插入图片描述

速度环

位置环

雅特力

在这里插入图片描述
从雅特力的这里知道,当位置差为不同值时要采用不同的PI参数,位置差越小积分增益越大。
当位置差比较小时应该取掉速度环,直接位置环+电流环,在ST的《MC SDK 5.x 中增加位置环》文档中也是这么处理的。
在这里插入图片描述
同时该文档还给了位置环PID参数的简单整定过程:
在这里插入图片描述

在这里插入图片描述
速度环1K,位置环200Hz。设置了1个定时器中断来实现速度环、位置环。中断频率1KHz,进入中断后读取编码器值获取到当前位置、当前转速。然后进入5次计算一次位置环,位置环计算后输出目标速度,目标速度和当前速度相减得到速度差,用来做速度环PID,计算出电流环q轴目标值。

在这里插入图片描述
在PWM更新中断中,首先获取转子角度,计算sin、cos值,计算当前电流值,和上面计算的电流环目标值相减计算电流环PID,结果反PARK变换后做SVPWM。

再来看看position_control_handler();函数
在这里插入图片描述
在函数中根据当前位置和目标位置做斜坡加减速曲线,给出斜坡目标位置和当前位置相减,计算位置环PID,输出目标速度。

迈信EP100

//--------------------------------------PWM参数---------------------------------
#define PWM_FREQ   ( (u16) 10000)                                             // 电流环100us中断的频率(Hz)
#define CKTIM	    ( (u32) 72000000uL )                                       // 系统时钟(Hz)
#define PWM_PRSC   ( (u8) 0 )                                                 // 定时器预分频
#define PWM_PERIOD ( (u16) (CKTIM / (u32)(2 * PWM_FREQ *(PWM_PRSC+1))))       // 定时器的周期 = PWM的半周期
#define REP_RATE   ( (u8) 1)                                                  // 重复计数器,REP_RATE+1=pwm半周期

//----------------------------------速度环参数--------------------------------------------
#define SPEED_LOOP_PRESCALER          5
#define SPEED_LOOP_FREQ               ( CURRENT_LOOP_FREQ/SPEED_LOOP_PRESCALER )  //10k/5=2k

电流环10K,速度环2K

PC1 IV V相电流 ADC1的11通道  由TIM8_TRGO事件触发转换
PC2 IW W相电流 ADC3的12通道  由TIM8_TRGO事件触发转换
PC0 AS 速度模拟指令  ADC2的10通道  由ADC_SoftwareStartConvCmd(ADC2, ENABLE)软件触发

PWM输出使用TIM8  CenterAligned1  PWM2
TIM8更新事件产生TRGO事件用来触发电流采样
CH1   PC6
CH2   PC7
CH3   PC8
CH1N   PA7
CH2N   PB0
CH3N   PB1
TIM8_BKIN   PA6   IPM故障输出关闭TIM8

如下是相电流采样函数

void phase_current_read(phase_current *v)  //V相、W相电流采样
{
     s16 B_dataQ15,A_dataQ15;
     
	 //死循环等待 V相、W相电流采集完成
     while(! ( ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) & ADC_GetFlagStatus(ADC3,ADC_FLAG_EOC) ) ){}
     
     A_dataQ15 = (ADC_GetConversionValue(ADC1))^0x8000;  //获取ADC寄存器值
     v->ImeasA =  _IQ15toIQ(A_dataQ15);
     B_dataQ15 = (ADC_GetConversionValue(ADC3))^0x8000;
     v->ImeasB =  _IQ15toIQ(B_dataQ15);
     
     if(v->Offset_OK == 0)  //初始化时,0值校准
     {          
          if(current_sense_times_tmp == 1024)
          {
               v->PhaseAOffset = (s32) v->ImeasAAcc >> 10;
               v->PhaseBOffset = (s32) v->ImeasBAcc >> 10;
               
               v->PhaseAOffset = _IQ15toIQ(v->PhaseAOffset);
               v->PhaseBOffset = _IQ15toIQ(v->PhaseBOffset);
               v->Offset_OK = 1;
          }
          else
          {
               v->ImeasAAcc += A_dataQ15;
               v->ImeasBAcc += B_dataQ15;
          }
          current_sense_times_tmp = current_sense_times_tmp + 1;
     }
     else if(v->Offset_OK == 1)  //校准完成,正常读值
     {
          v->ImeasA = v->ImeasA - v->PhaseAOffset;  //减去0电流值
          v->ImeasB = v->ImeasB - v->PhaseBOffset;
     }
     
     v->U = v->ImeasA + v->ImeasB;  
     v->V = -v->ImeasA;
     v->W = -v->ImeasB;
     
     ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
     ADC_ClearFlag(ADC3, ADC_FLAG_EOC);
     
     ADC_SoftwareStartConvCmd(ADC2, ENABLE);  //ADC2开始采样,软件触发
}

如下是模拟输入的采样函数,采集模拟值用于调速,比如电位器。

void as_read(as *v)  //读模拟输入,用于调速  
{
     s16 tmp;
     
     while( ! ( ADC_GetFlagStatus(ADC2, ADC_FLAG_EOC) ) ){}  //死循环等待模拟输入采样完成
     
     tmp = (ADC_GetConversionValue(ADC2))^0x8000;  //读ADC2数据寄存器
     
     if(v->Offset_OK == 0)  //初始化时,进行校准
     {
          if(as_sense_times_tmp == 1024)
          {
               v->ASOffset = (s32) v->ASAcc >> 10;
               v->Offset_OK = 1;
          }
          else
          {
               v->ASAcc += tmp;
          }
          as_sense_times_tmp = as_sense_times_tmp + 1;
     }
     else if(v->Offset_OK == 1)  //校准完成
     {
          v->ImeasAS = v->ASOffset - tmp;  //减去0电流值
     }
     
     ADC_ClearFlag(ADC2, ADC_FLAG_EOC);
}

相电流采集的ADC由TIM8的更新事件触发,在phase_current_read函数中会死循环等待采集完成,在此函数最后用ADC_SoftwareStartConvCmd(ADC2, ENABLE)软件触发模拟输入采集,as_read函数中也是死循环等待采集完成。

在这里插入图片描述
foc计算放在TIM8的更新中断中,首先读取编码器值用于计算当前位置、转速,然后读取相电流、模拟输入,相电流clarke、park变换,低通滤波,之后选择运动方式是位置控制、速度控制等,不管何种方式他的输出都是pid_speed.Out,作为电流环q轴的目标值,接下来进行电流环d轴、q轴的PI计算,然后park逆变换、svpwm。
在这里插入图片描述
速度控制方式这里,首先根据编码器值计算当前速度,然后获取到目标速度,然后计算速度环PID。(目标速度可以做斜坡加速曲线)
在这里插入图片描述
位置控制方式这里,读取脉冲值,获取脉冲增量、目标位置,得到位置差做前馈运算,输出目标速度,得到速度差做PID计算。

### 永磁同步电机 FOC 算法实现原理 #### 定义与背景 永磁同步电机(PMSM)由于具备高效率、高功率密度和优秀的动态响应特性,在多个行业中得到广泛的应用。为了充分发挥这些优点,精确的控制策略至关重要。场定向控制(Field-Oriented Control, FOC),也称为矢量控制,是一种能够有效提升电机性能的技术。 #### 坐标变换理论基础 在稳态条件下观察电机运行情况时,可以发现定子电流表现为恒定量,而磁通矢量则保持静止状态[^2]。这种现象使得可以通过特定的坐标转换来简化控制系统的设计。具体来说,通过引入两组不同的坐标系——固定于定子上的αβ坐标系和平移至转子位置处d-q坐标系之间的映射关系,实现了复杂交流系统的线性化处理,从而允许采用类似于直流电动机那样的简单反馈机制来进行实时调控操作。 #### 关键技术环节解析 - **Clark 变换 (Clarke Transformation)** 将三相静止abc坐标系中的电压或电流信号转化为二维笛卡尔平面内的新变量表示形式,即所谓的αβ0坐标系。此过程消除了原始数据中存在的不平衡因素影响,并为进一步实施Park变化提供了必要的前提条件。 - **Park 变换 (Park Transformation)** 把经过Clark变换成后的αβ平面上的数据继续沿逆时针方向旋转一定角度θ后投影到新的dq上完成最终的目标向量表达式构建工作;此时所获得的结果可以直接用于后续计算当中去。 - **PI 控制器设计** 对应于上述两个维度分别设立独立的比例积分(Proportional Integral)调节单元负责维持期望值与实际测量值得一致性水平,确保整个闭环体系稳定可靠地运作下去而不至于发散失衡。 - **SVPWM 脉冲宽度调制** 利用空间矢量合成的方法代替传统SPWM波形生成方式,可以在相同的硬件资源消耗下提供更优的电磁兼容性和更低谐波含量输出效果,进而改善整体驱动品质并延长使用寿命期限。 ```matlab % MATLAB/Simulink 中 SVPWM 的基本实现框架示意代码片段 function svpwm_modulation(v_alpha, v_beta) % 输入参数为 Clark 和 Park 变换之后的 dq 平面内分量 Vdc = 800; % DC bus voltage level设定 Tsw = 1e-6; % 开关周期定义 % 计算扇区编号 Sector Number determination sector = mod(floor((atan2(v_beta,v_alpha)/(pi/3))+7),6)+1; % 找出三个比较电平 Compare Level identification Va_star = ... ; % 需要根据当前所在象限决定具体的赋值逻辑 Vb_star = ... Vc_star = ... end ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qlexcel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值