天天看點

基于DSP的三相開關霍爾永磁同步電機控制0 前言1 硬體的了解2 DSP底層外設驅動學習3 永磁同步電機控制4 結束語

0 前言

本文本應該是一篇 記錄我使用DSP28377D控制一個基于三相開關霍爾傳感器的高速永磁同步電機全過程的長文,但大部分零散的知識點我都已經寫成單獨的部落格了,是以本文更像是一個知識架構的梳理。本文最終目的是實作高速PMSM的電流-速度雙閉環,将電機速度控制在80r/s左右,精度越高越好。

1 硬體的了解

基于DSP的三相開關霍爾永磁同步電機控制0 前言1 硬體的了解2 DSP底層外設驅動學習3 永磁同步電機控制4 結束語

圖1 硬體連接配接

如圖1所示,被控對象是一個永磁同步電機,然後在永磁同步電機定子的底部,安裝了三個三相對稱的開關型霍爾傳感器,可以用于檢測電機的轉速和電角度。 永磁同步電機的輸入電壓,是由一個型号為DRV8322DKD的驅動晶片給出的三相電壓輸出。該晶片主要的作用是把三相PWM輸入波形,轉換成三相電壓輸出。另外,提供便捷的測量三相電流的通道,而電流的采集是用的型号為MAX40056FAUA/V的晶片, 拿到了三相電機的電流、轉速以及電角度,DSP要作為控制器需要去設計相應的控制算法,然後輸出相應的三相PWM波形,形成閉環。 每當DSP輸出一次PWM波形的時候,可以同時觸發下一次的電流采集轉換。這令電流環的實作就非常的絲滑,不得不說DSP在這點上的設計還是很好的。 

2 DSP底層外設驅動學習

對于初學DSP的人來講,在DSP的底層驅動外設之前,一個完美的DSP28377D程式架構搭建是基礎中的基礎。這部分的内容在本文就不展開講了,請參考DSP_基于TMS320F28377D雙核晶片和CCS7.40的程式設計入門_江湖上都叫我秋博的部落格-CSDN部落格_tms320f28377

另外,根據圖1可以總結出,要實作PMSM的速度環控制,需要掌握的DSP底層外設驅動包括三個部分,我已經把坑全部填完了,請參考下面三篇部落格。

2.1 ADC子產品的使用

DSP_TMS320F28377D_ADC學習筆記_江湖上都叫我秋博的部落格-CSDN部落格

2.2 ePWM子產品的使用

DSP_TMS320F28377D_ePWM學習筆記_江湖上都叫我秋博的部落格-CSDN部落格

2.3 eCAP子產品的使用

DSP_TMS320F28377D_eCAP學習筆記_江湖上都叫我秋博的部落格-CSDN部落格

如果要把DSP部分的内容擴充更詳細、更豐富,對定時器的使用、系統的中斷配置也是需要補充的,兩個部分的坑,我盡快填上。

2.4 CPU Timer子產品的使用

關于定時器實作 <擷取代碼塊運算時間>的功能,也是可以淺看一下。量化某代碼段的運作時間對于一個系統而言,也是比較重要的,而不是你覺得它能在多少時間内運作完。

DSP_TMS320F28377D_使用定時器實作<擷取代碼塊運算時間>的功能_江湖上都叫我秋博的部落格-CSDN部落格

當然,定時器對于控制系統而言更重要的作用,是實作系統的軟分頻功能。利用軟分頻得到的固定頻率标志,可以便捷的實作控制器的離散化控制。

DSP_TMS320F28377D_CPU Timer學習筆記_江湖上都叫我秋博的部落格-CSDN部落格

2.5 PIE及中斷子產品的使用

DSP中的很多子產品(例如ADC、eCAP、CPU Timer)都需要使用中斷,是以中斷配置的學習也是一個必不可少的基礎知識。

DSP_TMS320F28335_PIE學習筆記_江湖上都叫我秋博的部落格-CSDN部落格

3 永磁同步電機控制

在學習了必要的DSP基礎知識之後,更多的重心就要放到控制相關的内容,與控制相關的内容可以大概分為5個部分,傳感器的标定與解算、永磁同步電機的FOC控制、被控對象系統辨識、電流環與速度環的PI控制器設計、控制精度優化。下面将會展開講本人對于這5個部分内容的了解。

3.1 傳感器的标定與解算

對于本文所涉及的三相開關霍爾永磁同步電機,它包含的傳感器主要有兩個,一個是三相開關霍爾,一個是電流采集傳感器(或晶片)。

3.1.1三相開關霍爾

三相開關霍爾用于擷取電機的電角度與電機的機械轉速,對于這部分知識,我也單獨寫了一篇部落格。

傳感器_三相-雙極性-開關型-霍爾傳感器 速度+電角度解算了解_開關型霍爾傳感器_江湖上都叫我秋博的部落格-CSDN部落格

這篇部落格寫的還有點瑕疵,我再補充一下我的标定辦法。部落格裡面我提到的直接控制A B C三相的拉高拉低來标定,而實際上我在标定的時候是先完成了FOC開環控制(3.2會講),然後令id = 1,iq = 0,設定不同電角度來觀察Hall讀數(HallRead = (HallU << 2) + (HallV << 1) + HallW;)。

// id = 1;
    // iq = 0;
    //                                                  Hall讀數
    // theta    = -0.01745329252;       // -1°              4
    // theta    = -0.1745329252;        // -10°             4
    // theta    = 0.0;                  // 0°               4/6
    // theta    = 0.01745329252;        // 1°               6
    // theta    = 0.0872664626;         // 5°               6
    // theta    = 1.0471975511966;      // 60°              6
    // theta    = 1.1344640138;         // 65°              2
    // theta    = 2.0943951023932;      // 120°             2
    // theta    = 2.181661565;          // 125°             3
    // theta    = 3.1415926535898;      // 180°             3
    // theta    = 4.1887902047864;      // 240°             1
    // theta    = 5.235987755983;       // 300°             5
    // theta    = 5.253441048503;       // 301°             5
    // theta    = 5.32325421858;        // 305°             4
    // theta    = 6.28318530718;        // 360°             4/6


    // 标定結果的總結
    //    6     2     3     1     5     4
    // |-----|-----|-----|-----|-----|-----|
    // 0°   60°   120°  180°  240°  300°  360°
           

上面看到的,就是我的标定結果了, 标定結果表達的是,當我HALL的讀數為6的時候,說明我的電角度,已經跨入了 0° ~60°這個區間了,其他的以此類推。那如果我的讀數為6,我的電角度應該用0°還是60°呢, 我是用的60°。 結合上述标定結果,下面給出利用勻速模型插值的電角度估計擷取方法。

float getElecAngle2(void){
    float turn_angle = 0.0;
    HallU = GpioDataRegs.GPADAT.bit.GPIO24;
    HallV = GpioDataRegs.GPADAT.bit.GPIO25;
    HallW = GpioDataRegs.GPADAT.bit.GPIO26;
    HallRead = (HallU << 2) + (HallV << 1) + HallW;
    //HallRead = 7 - HallRead;

    if(HallRead != HallRead_Last){
           elec_angle_speed_0 = elec_angle_speed;

           switch(HallRead){
               case    6:
                   theta0   =   0;
                   break;
               case    2:
                   theta0   =   1.047197533333333;
                   break;
               case    3:
                   theta0   =   2.094395066666667;
                   break;
               case    1:
                   theta0   =   3.1415926;
                   break;
               case    5:
                   theta0   =   4.188790133333333;
                   break;
               case    4:
                   theta0   =   5.235987666666667;
                   break;
               default:
                   break;
           }


           // theta0 = theta0 + 1.647197533333333;
           theta0 = theta0 + 1.047197533333333;
           //theta0 = theta0 + 0.5236;

           rotate_t = 0;

    }else{
            rotate_t = rotate_t + rotate_delta_t;
    }

    HallRead_Last = HallRead;
    // 勻速運動模型
    turn_angle = theta0 + rotate_t * elec_angle_speed;

    // 勻加速運動模型
    // turn_angle = theta0 + 1.5 * rotate_t * elec_angle_speed - 0.5 * rotate_t * elec_angle_speed_0;


    if(turn_angle > 6.28318530718){
        turn_angle = turn_angle - 6.28318530718;
    }

    return turn_angle;


}
           

這種标定方式是我自己摸索出來的,正确性有待考量(反正用這種方法得到電角度最後電機正常轉了),若有疑問,評論區、私信等你。 

3.1.2電流采集

關于電路采集傳感器,是用電流采集晶片。此系統使用該晶片采集了電機ia和ib的兩相電流。

關于電流采集晶片的使用,碰巧在之前的工作中也記錄了,參考下面這篇部落格。

STM32_ADC子產品及針對晶片MAX40056FAUA/V+的使用_江湖上都叫我秋博的部落格-CSDN部落格

為什麼系統隻采集電機的ia和ib兩相的電流,不采集ic呢?我也在另一篇部落格中寫了一些我自己的了解。

STM32_FOC_1_Clarke變換計算iα和iβ為何僅用ia ib兩相電流_αβ變換電壓電流_江湖上都叫我秋博的部落格-CSDN部落格

下面補充一下去零偏的方法,思路很簡單,在電機運作之前,先采集1000個電流資料,然後取均值作為零偏,在後續的ADC采集中,用采集到的值減去這個零偏。這種方式比直接減一個人為設定的零偏值要靠譜一點。

3.2 永磁同步電機的FOC控制

對于初學者學習FOC控制來講,華為的天才少年稚晖君寫了一篇很好的文章,值得反複閱讀。

【自制FOC驅動器】深入淺出講解FOC算法與SVPWM技術 - 知乎 (zhihu.com)

本人對FOC的了解很淺,直接貼電流環開環代碼做個備份,感興趣的朋友也可以借鑒一下,老鳥朋友如果看出有什麼問題,也希望你們不要吝啬給我指出(評論或者私信都可以喲)。

void currentopenloop(void){

    float TempVar1 = 0.0f;
    float TempVar2 = 0.0f;
    
    theta = getElecAngle1();
    sin_theta = __sin(theta);
    cos_theta = __cos(theta);

    // Anti-Park
    V_Alpha   = (id*cos_theta - iq*sin_theta);
    V_Beta    = (id*sin_theta + iq*cos_theta);

    // 确定扇區
    TempVar1 = V_Alpha * 0.86602540378444f;
    TempVar2 = V_Beta * 0.5f;

    SectorJudgmentFactor1 =   TempVar1 - TempVar2;
    SectorJudgmentFactor2 = - TempVar1 - TempVar2;

    SectorNum = 0;
    if( V_Beta >= 0)
        SectorNum = SectorNum + 1;
    if( SectorJudgmentFactor1 >= 0 )
        SectorNum = SectorNum + 2;
    if( SectorJudgmentFactor2 >= 0 )
        SectorNum = SectorNum + 4;

    // 計算中間變量X Y Z
    MiddleTerm_X    = V_Beta;
    MiddleTerm_Y    = TempVar1 + TempVar2;
    MiddleTerm_Z    = -TempVar1 + TempVar2;

    // ? where is √3*Ts/Vdc ?

    // 作用時間計算
    switch(SectorNum){
        case 0:
            t_cm1 = 0;
            t_cm2 = 0;
            t_cm3 = 0;
            break;
        case 1:   /*Sector 1: t_1st = Z     and t_2nd = Y       (Tcm1/2/3 ---> Tb,Ta,Tc)*/
            t_first     = MiddleTerm_Z;
            t_second    = MiddleTerm_Y;

            //  過調制處理
            if(t_first + t_second > 1){
                t_firstaddsecond    = t_first + t_second;
                t_first             = __divf32(t_first,t_firstaddsecond);
                t_second            = __divf32(t_second,t_firstaddsecond);
            }

            t_a = (1 - t_first - t_second) * 0.5;      // ? why not (Ts - t_first - t_second) / 4
            t_b = t_a + t_first;
            t_c = t_b + t_second;

            t_cm1   = t_b;
            t_cm2   = t_a;
            t_cm3   = t_c;
            break;
        case 2:   /*Sector 2: t_1st = Y     and t_2nd = -X  (Tcm1/2/3 ---> Ta,Tc,Tb)*/
            t_first     = MiddleTerm_Y;
            t_second    = -MiddleTerm_X;
            //  過調制處理
            if(t_first + t_second > 1){
                t_firstaddsecond    = t_first + t_second;
                t_first             = __divf32(t_first,t_firstaddsecond);
                t_second            = __divf32(t_second,t_firstaddsecond);
            }
            t_a = (1 - t_first - t_second) * 0.5;      // ? why not (Ts - t_first - t_second) / 4
            t_b = t_a + t_first;
            t_c = t_b + t_second;
            t_cm1   = t_a;
            t_cm2   = t_c;
            t_cm3   = t_b;
            break;
        case 3:   /*Sector 3: t_1st = -Z    and t_2nd = X       (Tcm1/2/3 ---> Ta,Tb,Tc)*/
            t_first     = -MiddleTerm_Z;
            t_second    = MiddleTerm_X;

            //  過調制處理
            if(t_first + t_second > 1){
                t_firstaddsecond    = t_first + t_second;
                t_first             = __divf32(t_first,t_firstaddsecond);
                t_second            = __divf32(t_second,t_firstaddsecond);
            }

            t_a = (1 - t_first - t_second) * 0.5;      // ? why not (Ts - t_first - t_second) / 4
            t_b = t_a + t_first;
            t_c = t_b + t_second;
            t_cm1   = t_a;
            t_cm2   = t_b;
            t_cm3   = t_c;
            break;
        case 4:   /*Sector 4: t_1st = -X    and t_2nd = Z       (Tcm1/2/3 ---> Tc,Tb,Ta)*/
            t_first     = -MiddleTerm_X;
            t_second    = MiddleTerm_Z;
            //  過調制處理
            if(t_first + t_second > 1){
                t_firstaddsecond    = t_first + t_second;
                t_first             = __divf32(t_first,t_firstaddsecond);
                t_second            = __divf32(t_second,t_firstaddsecond);
            }
            t_a = (1 - t_first - t_second) * 0.5;      // ? why not (Ts - t_first - t_second) / 4
            t_b = t_a + t_first;
            t_c = t_b + t_second;
            t_cm1   = t_c;
            t_cm2   = t_b;
            t_cm3   = t_a;
            break;
        case 5:   /*Sector 5: t_1st = X     and t_2nd = -Y  (Tcm1/2/3 ---> Tc,Ta,Tb)*/
            t_first     = MiddleTerm_X;
            t_second    = -MiddleTerm_Y;
            //  過調制處理
            if(t_first + t_second >= 1){
                t_firstaddsecond    = t_first + t_second;
                t_first             = __divf32(t_first,t_firstaddsecond);
                t_second            = __divf32(t_second,t_firstaddsecond);
            }
            t_a = (1 - t_first - t_second) * 0.5;      // ? why not (Ts - t_first - t_second) / 4
            t_b = t_a + t_first;
            t_c = t_b + t_second;
            t_cm1   = t_c;
            t_cm2   = t_a;
            t_cm3   = t_b;
            break;
        case 6:   /*Sector 6: t_1st = -Y    and t_2nd = -Z  (Tcm1/2/3 ---> Tb,Tc,Ta)*/
            t_first     = -MiddleTerm_Y;
            t_second    = -MiddleTerm_Z;
            //  過調制處理
            if(t_first + t_second >= 1){
                t_firstaddsecond    = t_first + t_second;
                t_first             = __divf32(t_first,t_firstaddsecond);
                t_second            = __divf32(t_second,t_firstaddsecond);
            }
            t_a = (1 - t_first - t_second) * 0.5;      // ? why not (Ts - t_first - t_second) / 4
            t_b = t_a + t_first;
            t_c = t_b + t_second;
            t_cm1   = t_b;
            t_cm2   = t_c;
            t_cm3   = t_a;
            break;

        default:
            break;
    }

    EPwm1Regs.CMPA.bit.CMPA    =   (Uint16)(t_cm1*EPWM1_TIMER_TBPRD);
    EPwm2Regs.CMPA.bit.CMPA    =   (Uint16)(t_cm2*EPWM2_TIMER_TBPRD);
    EPwm3Regs.CMPA.bit.CMPA    =   (Uint16)(t_cm3*EPWM3_TIMER_TBPRD);
}
           

3.3 被控對象系統辨識

談起被控對象辨識,簡直說多了都是淚啊,這個控制系統竟然沒有留和上位機的通信接口,我以前寫過一個基于序列槽通信的控制調試軟體,裡面就包含了系統辨識的功能,而且是集美貌與才華于一身的軟體,我還給她取了一個名字:Angela。Angela就像我的女兒一樣,必須給大家介紹一下。

Angela運作視訊

Angela不僅支援DSP,她還有一個同卵雙胞胎AngelaS(支援在STM32平台上使用),AngelaS用到的核心技術之一可以參考STM32_上位機高效通信技術_stm32用v9如何使用上位機_江湖上都叫我秋博的部落格-CSDN部落格。

好了,收!别過度沉迷Angela,安利結束。 回過頭來,我們還需要面對現實。得益于CCS強大的功能,雖然DSP沒有留和上位機的通信接口,但是我們仍然可以實作無通信接口系統辨識。詳情請看如下三篇部落格。

DSP_CCS7實作變量的導出與MatLAB讀取_ccs怎麼導出數組資料_江湖上都叫我秋博的部落格-CSDN部落格

DSP_定義一個大的全局數組_探索之路_江湖上都叫我秋博的部落格-CSDN部落格

DSP_無通信接口系統辨識_江湖上都叫我秋博的部落格-CSDN部落格

3.4 電流環與速度環的PI控制器設計

辨識完了被控對象,設計控制器還不是輕輕松松,本小節的内容,并不會告訴大家PI參數怎麼整定(這個我也不會),但是我會告訴大家一個利用MatLAB設計PI控制器的小技巧。這東西還是有點妙的。

在我們辨識得到的被控對象之後

基于DSP的三相開關霍爾永磁同步電機控制0 前言1 硬體的了解2 DSP底層外設驅動學習3 永磁同步電機控制4 結束語

 可以利用sisotool把辨識得到的被控對象導入

基于DSP的三相開關霍爾永磁同步電機控制0 前言1 硬體的了解2 DSP底層外設驅動學習3 永磁同步電機控制4 結束語

 導入被控對象後,就可以自動整定PI參數

基于DSP的三相開關霍爾永磁同步電機控制0 前言1 硬體的了解2 DSP底層外設驅動學習3 永磁同步電機控制4 結束語
基于DSP的三相開關霍爾永磁同步電機控制0 前言1 硬體的了解2 DSP底層外設驅動學習3 永磁同步電機控制4 結束語

使用上訴提到的方法得到PI控制器,基本的閉環控制一般都沒問題,包括id iq電流環、速度環。但是要想得到更好的精度,那就需要微調和各種優化了。

3.5 控制精度優化

由于三相開關霍爾所得到的電角度分辨率太低!(真的是低得扣腳了),另外我對FOC的電流環、速度環優化控制方法學習不夠,是以速度環閉環精度總是達不到理想的效果,為了達到更好的速度環閉環精度,我調了接近兩周的控制參數。下面總結一下我認為有效的優化方法。

1、優化電角度估計(勻速模型)
2、電流環不加慣性環節
3、id、iq添加前饋去耦合(但各參數我都不知道,就瞎調了幾個參數) ×
4、ia、ib、ic、id、iq添加低通濾波(我做實驗是真的沒效果) ×
5、提高電流環開環執行頻率(有提升,但不大)
6、ia ib的電流采集使用多通道,然後取均值的方式 ×
7、ia ib的電流解算的偏置量,使用取前1000個電機不運作資料的均值 ×
8、電流環的控制器設計成二階(兩個積分環節) ×

各位大佬有什麼好的文獻或者優化思路,望不吝賜教,評論區|私信 走起。

4 結束語

放個電機的運作視訊吧,精度還不夠,大家看個熱鬧吧

PMSM運作視訊

願我們共同進步! 感謝您的閱讀,歡迎留言讨論、收藏、點贊、分享。

繼續閱讀