四、手写STM32 FOC记录-----FOC + SVPWM
FOC控制算法框图:

咋一看FOC控制框图可能会发现不知道从哪入手,可以回想一下直流有刷电机的学习过程,首先是让电机转起来,然后进行速度控制,再进一步进行位置控制,同样我们在FOC学习过程中依然可以这样做,我们首先将位置环和速度环甚至是电流环去掉,然后就剩下SVPWM,既然只是让电机转起来那么电流检测也不需要了,我们就直接给电压,开环运行,这时候控制框架就能简化成下图所示。

逐一实现框图中的每一个模块,首先是Park逆变换,将DQ坐标系转换成αβ坐标系,如下:

用C语言代码实现它:

/**********************************************************************************************************
park逆变换,输入Uq、Ud得到Ualpha、Ubeta
Uα = Ud · cosθ - Uq · sinθ
Uβ = Ud · sinθ + Uq · cosθ
**********************************************************************************************************/
void inverseParkTransform(DQ_Def *dq, AlphaBeta_Def *alphaBeta, float angle)
{
float cosAngle = cos(angle);
float sinAngle = sin(angle);
alphaBeta->alpha = dq->d * cosAngle - dq->q * sinAngle;
alphaBeta->beta = dq->d * sinAngle + dq->q * cosAngle;
}
接下来便是FOC控制最复杂也是最难理解的一部分SVPWM,不介绍SVPWM原理,网上关于SVPWM的资料超级多,需要的时候自行查阅。这里只针对我的理解,结合我编写的代码来说明具体的实现过程。
第一步,计算u1、u2、u3。通过park逆变换得到的alpha,beta,计算得出u1、u2和u3,我理解这个其实是克拉克逆变换

第二步,扇区判断。根据u1、u2和u3的正负情况确定所处的扇区。根据U1,U2,U3的符号计算N=4C+2B+A,再结合扇区表判断所处的扇区,N的取值和所对应的扇区关系如下表:

第三步,计算基本矢量电压作用时间(占空比),根据扇区确定相邻两个基本矢量电压及其作用时间,然后对作用时间进行等比例缩小处理,使得总的作用时间等于Ts采样时间,或总的占空比等于1,这样计算出来的数据ta,tb,tc就是占空比,正好与PWM的输出对应。这部分相对较复杂,这里不做描述,请查阅专业的资料。推荐一篇博客:https://blog.csdn.net/weixin_42887190/article/details/125464343

第四步,6路PWM输出,根据a,b,c三项占空比,计算PWM输出,驱动电机转动,这个很简单,就是用占空比乘以PWM周期,编写set_PWM_value函数如下:


void set_PWM_value(uint16_t pwm_u,uint16_t pwm_v,uint16_t pwm_w)
{
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm_u);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, pwm_v);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, pwm_w);
}
至此SVPWM完成,现将完整的SVPWM代码粘贴进来:
/**********************************************************************************************************
将坐标变换中的反Park变换得到的 Valpha 、Vbeta 转换六路PWM输出。
**********************************************************************************************************/
void SVPWM(AlphaBeta_Def *U_alphaBeta, SVPWM_Def *svpwm)
{
float sum;
float k_svpwm;
svpwm->Ts = 1.0f; // SVPWM的采样周期
svpwm->U_alpha = U_alphaBeta->alpha;
svpwm->U_beta = U_alphaBeta->beta;
// step1 计算u1、u2和u3
// 计算SVPWM算法中的三个控制电压u1、u2和u3
svpwm->u1 = U_alphaBeta->beta;
svpwm->u2 = -0.8660254f * U_alphaBeta->alpha - 0.5f * U_alphaBeta->beta; // sqrt(3)/2 ≈ 0.86603
svpwm->u3 = 0.8660254f * U_alphaBeta->alpha - 0.5f * U_alphaBeta->beta;
// step2:扇区判断
// 根据u1、u2和u3的正负情况确定所处的扇区
svpwm->sector = (svpwm->u1 > 0.0f) + ((svpwm->u2 > 0.0f) << 1) + ((svpwm->u3 > 0.0f) << 2); // N=4*C+2*B+A
// step3:计算基本矢量电压作用时间(占空比)
// 根据扇区的不同,计算对应的t_a、t_b和t_c的值,表示生成的三相电压的时间
switch (svpwm->sector)
{
case 5:
// 扇区5
svpwm->t4 = svpwm->u3;
svpwm->t6 = svpwm->u1;
sum = svpwm->t4 + svpwm->t6;
if (sum > svpwm->Ts)
{
k_svpwm = svpwm->Ts / sum; //
svpwm->t4 = k_svpwm * svpwm->t4;
svpwm->t6 = k_svpwm * svpwm->t6;
}
svpwm->t7 = (svpwm->Ts - svpwm->t4 - svpwm->t6) / 2;
svpwm->ta = svpwm->t4 + svpwm->t6 + svpwm->t7;
svpwm->tb = svpwm->t6 + svpwm->t7;
svpwm->tc = svpwm->t7;
break;
case 1:
// 扇区1
svpwm->t2 = -svpwm->u3;
svpwm->t6 = -svpwm->u2;
sum = svpwm->t2 + svpwm->t6;
if (sum > svpwm->Ts)
{
k_svpwm = svpwm->Ts / sum; // 计算缩放系数
svpwm->t2 = k_svpwm * svpwm->t2;
svpwm->t6 = k_svpwm * svpwm->t6;
}
svpwm->t7 = (svpwm->Ts - svpwm->t2 - svpwm->t6) / 2;
svpwm->ta = svpwm->t6 + svpwm->t7;
svpwm->tb = svpwm->t2 + svpwm->t6 + svpwm->t7;
svpwm->tc = svpwm->t7;
break;
case 3:
// 扇区3
svpwm->t2 = svpwm->u1;
svpwm->t3 = svpwm->u2;
sum = svpwm->t2 + svpwm->t3;
if (sum > svpwm->Ts)
{
k_svpwm = svpwm->Ts / sum; //
svpwm->t2 = k_svpwm * svpwm->t2;
svpwm->t3 = k_svpwm * svpwm->t3;
}
svpwm->t7 = (svpwm->Ts - svpwm->t2 - svpwm->t3) / 2;
svpwm->ta = svpwm->t7;
svpwm->tb = svpwm->t2 + svpwm->t3 + svpwm->t7;
svpwm->tc = svpwm->t3 + svpwm->t7;
break;
case 2:
// 扇区2
svpwm->t1 = -svpwm->u1;
svpwm->t3 = -svpwm->u3;
sum = svpwm->t1 + svpwm->t3;
if (sum > svpwm->Ts)
{
k_svpwm = svpwm->Ts / sum; //
svpwm->t1 = k_svpwm * svpwm->t1;
svpwm->t3 = k_svpwm * svpwm->t3;
}
svpwm->t7 = (svpwm->Ts - svpwm->t1 - svpwm->t3) / 2;
svpwm->ta = svpwm->t7;
svpwm->tb = svpwm->t3 + svpwm->t7;
svpwm->tc = svpwm->t1 + svpwm->t3 + svpwm->t7;
break;
case 6:
// 扇区6
svpwm->t1 = svpwm->u2;
svpwm->t5 = svpwm->u3;
sum = svpwm->t1 + svpwm->t5;
if (sum > svpwm->Ts)
{
k_svpwm = svpwm->Ts / sum; //
svpwm->t1 = k_svpwm * svpwm->t1;
svpwm->t5 = k_svpwm * svpwm->t5;
}
svpwm->t7 = (svpwm->Ts - svpwm->t1 - svpwm->t5) / 2;
svpwm->ta = svpwm->t5 + svpwm->t7;
svpwm->tb = svpwm->t7;
svpwm->tc = svpwm->t1 + svpwm->t5 + svpwm->t7;
break;
case 4:
// 扇区4
svpwm->t4 = -svpwm->u2;
svpwm->t5 = -svpwm->u1;
sum = svpwm->t4 + svpwm->t5;
if (sum > svpwm->Ts)
{
k_svpwm = svpwm->Ts / sum; //
svpwm->t4 = k_svpwm * svpwm->t4;
svpwm->t5 = k_svpwm * svpwm->t5;
}
svpwm->t7 = (svpwm->Ts - svpwm->t4 - svpwm->t5) / 2;
svpwm->ta = svpwm->t4 + svpwm->t5 + svpwm->t7;
svpwm->tb = svpwm->t7;
svpwm->tc = svpwm->t5 + svpwm->t7;
break;
default:
break;
}
// step4:6路PWM输出
set_PWM_value(PWM_PERIOD*svpwm->ta,PWM_PERIOD*svpwm->tb,PWM_PERIOD*svpwm->tc);
}
第五步,验证SVPWM输出是否正确。写完SVPWM函数,需要做验证,看看算法是否正确可用。编写svpwm_test函数,给定d,q值,由于暂时还没有完成电机编码器的角度获取,所以角度theta给以个固定增量,这样正常情况下,接上电机会转动起来。为了便于观察将ta,tb,tc三个数据扩大100倍放到vofa输出函数中,显示三条曲线。

void svpwm_test(void)
{
float theta = 0;
DQ_Def test_dq;
AlphaBeta_Def test_ab;
SVPWM_Def svpwm_out;
test_dq.d = 0.0f;
test_dq.q = 0.2f;
for (theta = 0; theta < 6.2831853f; theta += 0.275f)
{
inverseParkTransform(&test_dq,&test_ab,theta);
SVPWM(&test_ab,&svpwm_out);
vofa_JustFloat_output(100.0f*svpwm_out.ta,100.0f*svpwm_out.tb,100.0f*svpwm_out.tc,0.0f);
// HAL_Delay(1);
}
}
最后主函数调用如下:

接上硬件平台,编译下载,打开串口连接vofa+上位机,可以输出三条相位相差120°的马鞍波曲线,显示如下,如此就证明SVPWM成功实现。

同时,可以接上BLDC的UVW三项,可以看到电机会转动,修改test_dq.d和theta的增量,电机的转动速度会改变。
观看视频效果请转至https://www.bilibili.com/video/BV1pu4y1y7jK/?spm_id_from=333.999.0.0&vd_source=4c57166c6142504cc135c2135bf9844e
本文已将重要的代码贴到文章中了,涉及到的所有源码可以都开源,需要的小伙伴点赞关注后可以私信获取