【永磁同步电机】SVPWM控制算法+Matlab/Simulink仿真详解
ST电机库v5.4.4源代码分析(3): α、β方向的电流值与三相PWM 波形的联系
电机的本质和扭矩的概念
电机是由定子和转子组成,可以看成两个磁铁,转子是永磁铁,定子是电磁铁。
转子的磁场大小是固定的,定子的磁场大小可以由绕组中的电流大小来控制。
当转子和定子没有夹角时,不会有扭矩的存在,转子不会动,如下:
把外面的定子磁场扭转一定角度,根据磁铁异性相吸的原理,内部的转子会跟着旋转,这个时候就存在扭矩了。
扭矩的大小怎么衡量呢?
所有的电机,扭矩的大小正比于内外两个磁场的叉乘,也就是两个磁场围成的平行四边形的面积。
于是当两个磁场重合,平行四边形面积为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计算出:
扇区号 | A | B | C | N=A+2B+4C |
---|---|---|---|---|
Ⅰ | 1 | 1 | 0 | 3 |
Ⅱ | 1 | 0 | 0 | 1 |
Ⅲ | 1 | 0 | 1 | 5 |
Ⅳ | 0 | 0 | 1 | 4 |
Ⅴ | 0 | 1 | 1 | 6 |
Ⅵ | 0 | 1 | 0 | 2 |
扇区作用时间计算
以 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计算。