欢迎光临散文网 会员登陆 & 注册

四、手写STM32 FOC记录-----FOC + SVPWM

2023-09-02 15:07 作者:茄子土豆地三鲜  | 我要投稿

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

        本文已将重要的代码贴到文章中了,涉及到的所有源码可以都开源,需要的小伙伴点赞关注后可以私信获取


四、手写STM32 FOC记录-----FOC + SVPWM的评论 (共 条)

分享到微博请遵守国家法律