如下图所示为ST培训资料的PPT中进行定义的坐标系,图中可以看出和常见的坐标系定义不同的主要有两点:
// Clarke变换问题不是很大,主要注意两点:
// 1.Clark变换中前面×了系数2/3
// 2.建立的坐标系alpha和常见的一样,但是beta坐标系是朝下的,所以和一般的结果差一个负号
alphabeta_t mc_clark(ab_t input)
{
alphabeta_t output;
int32_t a_divSQRT3_tmp, b_divSQRT3_tmp ;
int32_t wbeta_tmp;
int16_t hbeta_tmp;
/* qIalpha = qIas*/
output.alpha = input.a; // 注意这里的clarke变换×了系数2/3
a_divSQRT3_tmp = divSQRT_3 * ( int32_t )input.a;
b_divSQRT3_tmp = divSQRT_3 * ( int32_t )input.b;
/*qIbeta = -(2*qIbs+qIas)/sqrt(3)*/ // 这里是负的,也就是beta轴是朝下的
wbeta_tmp = ( -( a_divSQRT3_tmp ) - ( b_divSQRT3_tmp ) -
( b_divSQRT3_tmp ) ) >> 15;
// 这里>>15是因为上面是两个Q15格式的数据进行运算,结果是Q30格式,需要重新转换为Q15格式
/* Check saturation of Ibeta */
if ( wbeta_tmp > INT16_MAX ) // INT16_MAX = 32767
{
hbeta_tmp = INT16_MAX;
}
else if ( wbeta_tmp < ( -32768 ) )
{
hbeta_tmp = ( -32768 );
}
else
{
hbeta_tmp = ( int16_t )( wbeta_tmp );
}
output.beta = hbeta_tmp;
// 这里的限幅是干什么的?把-32768限幅成-32767?
if ( output.beta == ( int16_t )( -32768 ) )
{
output.beta = -32767;
}
return ( output );
}
Park(Rev-Park)变换应该是ST的电机库比较难理解的一点,因为程序的运算结果和常见的推导结果完全相反(交换了dq轴的结果)。这里和1.前言中说的ST的坐标定义有关。有关坐标定义,可以参照 FOC中坐标系和电角度的定义 。
如果按照这个坐标系定义,又会出现一个难以理解的问题,因为常见的电角度定义为d轴和alpha轴的夹角,为什么这里定义为q轴和alpha轴的夹角也可以呢?这样结果不会出错吗?
实际上,解答这个问题还是要从FOC的核心思想上来解答。FOC使用Clarke变换和Park变换是为了什么?就是为了生成超前转子(d轴,注意不论电角度怎么定义,dq轴定义总是不变的)90度的磁场,或者说超前转子90度的电流。那么电角度theta有什么用呢?其实它仅仅是为了指示当前转子在什么方向,从而生成一个超前转子90度的磁场,因为abc轴和alpha/beta轴都是固定的,而只有知道要生成的磁场相对这些固定的坐标轴在什么位置,才能进行在旋转坐标轴和固定坐标轴之间相互转换。所以说,电角度可以随便定义,它可以是q轴和alpha的夹角,也可以是d轴和alpha的夹角,甚至可以是d轴和(alpha轴与beta轴)的角平分线的夹角,因为电角度存在的意义只是为了指示当前转子在什么方向,然后生成一个超前转子90度的磁场,利用电角度,就可以把这个磁场分解到固定的坐标轴上去,然后控制PWM的生成。
那么为什么常见的定义是d轴或者q轴和alpha轴的夹角呢?其实很简单,就是为了Park和反Park变换的方便,因为此时只需要在dq轴和alpha/beta轴之间相互投影即可,而投影的时候×的系数就是theta的三角函数值。由此看来,这个夹角定义成d轴或者q轴和beta轴的夹角也是可以的,它同样方便计算。但是如果定义成和其他轴(比如alpha和beta角平分线)的夹角,那么投影计算的时候就不好算了。
总之记住:**所有的变换最终结果都是为了生成超前转子90度的磁场,而电角度只是为了提供转子的位置,方便磁场在各个坐标系之间进行变换。**这也是FOC的核心思想!
参考解答
为什么要进行电角度校准呢?也很简单,因为电机初始停在任意的一个位置,此时磁编码器是有一个读数的(而且极大的概率不是0)。但是从上面常见的坐标系的定义可以直到,电角度的零度是q轴或者d轴与alpha轴对齐的时候定义为电角度0度,此时编码器读出来的角度再×极对数就是偏置电角度。后面读取电角度的时候,只需要把编码器实际读出来的机械角度转换为电角度,然后再减去极对数,就是真正的电角度了。这里也可以发现,由于P对极的电机在初始对齐的时候可以有P个位置,也就对应有P个偏置电机度,实际上使用哪个偏置电角度都可以。(详见另一篇笔记)
为什么给-90度电角度就能进行电角度预校准?答案还是和FOC的核心思想有关,那就是FOC控制要生成一个超前转子90度的磁场。
如下图所示,电角度定义为d轴和alpha轴的夹角,此时为了校准电角度,也就是找到电角度0度的位置,那么要让d轴和alpha轴对齐,这个时候的电角度就是0度。为了让d轴和alpha轴对齐,那么就要生成alpha轴方向的磁场。由于是使用FOC控制,其核心就是控制生成一个超前转子90度的磁场,那么此时虚拟的转子应该在滞后alpha轴90度的方向,也就是在校准电角度的时候角度的反馈不是编码器来的,而是人为给定的,因为目的就是要生成一个想要的固定方向的磁场,从而吸引转子到这个方向,从而对齐电角度0度。所以此时虚拟的转子在图示位置,d轴也在这个位置,那么电角度是d轴和alpha轴的夹角,由图可知就是-90度。
如下图所示,电角度定义为q轴和alpha轴的夹角,此时为了校准电角度,也就是找到电角度0度的位置,那么要让q轴和alpha轴对齐,这个时候的电角度就是0度。为了让q轴和alpha轴对齐,那么就要生成beta轴方向的磁场。由于是使用FOC控制,其核心就是控制生成一个超前转子90度的磁场,那么此时虚拟的转子应该在滞后beta轴90度的方向,也就是在校准电角度的时候角度的反馈不是编码器来的,而是人为给定的,因为目的就是要生成一个想要的固定方向的磁场,从而吸引转子到这个方向,从而对齐电角度0度。所以此时虚拟的转子在图示位置,d轴也在这个位置,此时的q轴在生成的FOC磁场的方向,那么电角度是q轴和alpha轴的夹角,由图可知电角度就是-90度。
// Park变化问题比较大,目前还没有弄明白
// 解决!终于明白了这里的Park变换为什么不对了!
// 坐标系的定义:https://www.cnblogs.com/neriq/p/14800876.html
qd_t mc_park(alphabeta_t input, int16_t theta)
{
qd_t output;
int32_t d_tmp_1, d_tmp_2, q_tmp_1, q_tmp_2;
trig_components local_vector_components;
int32_t wqd_tmp;
int16_t hqd_tmp;
// 计算输入的角度的sin和cos值
local_vector_components = mc_trig_functions(theta);
/*No overflow guaranteed 无溢出保证*/
q_tmp_1 = input.alpha * ( int32_t )local_vector_components.h_cos;
/*No overflow guaranteed 无溢出保证*/
q_tmp_2 = input.beta * ( int32_t )local_vector_components.h_sin;
/*Iq component in Q1.15 Format */
wqd_tmp = ( q_tmp_1 - q_tmp_2 ) >> 15; // 这里就是在算iq
// 注意这里>>15为还是因为前面的结果是两个Q15格式的运算,结果是Q30格式,需要重新转换为Q15格式
/* Check saturation of Iq */
if ( wqd_tmp > INT16_MAX )
{
hqd_tmp = INT16_MAX;
}
else if ( wqd_tmp < ( -32768 ) )
{
hqd_tmp = ( -32768 );
}
else
{
hqd_tmp = ( int16_t )( wqd_tmp );
}
output.q = hqd_tmp;
if ( output.q == ( int16_t )( -32768 ) )
{
output.q = -32767;
}
/*No overflow guaranteed*/
d_tmp_1 = input.alpha * ( int32_t )local_vector_components.h_sin;
/*No overflow guaranteed*/
d_tmp_2 = input.beta * ( int32_t )local_vector_components.h_cos;
wqd_tmp = ( d_tmp_1 + d_tmp_2 ) >> 15;
/* Check saturation of Id */
if ( wqd_tmp > INT16_MAX )
{
hqd_tmp = INT16_MAX;
}
else if ( wqd_tmp < ( -32768 ) )
{
hqd_tmp = ( -32768 );
}
else
{
hqd_tmp = ( int16_t )( wqd_tmp );
}
output.d = hqd_tmp;
if ( output.d == ( int16_t )( -32768 ) )
{
output.d = -32767;
}
return (output);
}
/**
* @brief 反park变换:将同步旋转坐标系下的vqd,变换为静止坐标系的v_alpha,v_beta
* @param volt_input: vqd
* @param theta: 电角度值
*/
alphabeta_t mc_rev_park(qd_t volt_input, int16_t theta)
{
int32_t q_v_alpha_tmp1, q_v_alpha_tmp2, q_v_beta_tmp1, q_v_beta_tmp2;
trig_components local_vector_components;
alphabeta_t volt_output;
local_vector_components = mc_trig_functions(theta);
q_v_alpha_tmp1 = volt_input.q * ( int32_t )local_vector_components.h_cos;
q_v_alpha_tmp2 = volt_input.d * ( int32_t )local_vector_components.h_sin;
volt_output.alpha = ( int16_t )( ( ( q_v_alpha_tmp1 ) + ( q_v_alpha_tmp2 ) ) >> 15 );
q_v_beta_tmp1 = volt_input.q * ( int32_t )local_vector_components.h_sin;
q_v_beta_tmp2 = volt_input.d * ( int32_t )local_vector_components.h_cos;
volt_output.beta = ( int16_t )( ( q_v_beta_tmp2 - q_v_beta_tmp1 ) >> 15 );
return (volt_output);
}