1. 程式人生 > >STM32驅動直流電機的程式與電路設計(IR2110S自舉電路+H橋+高階定時器和死區PWM)

STM32驅動直流電機的程式與電路設計(IR2110S自舉電路+H橋+高階定時器和死區PWM)

本文介紹如何使用STM32F103微控制器,通過官方韌體庫,設定高階定時器TIM1輸出嵌入死區的互補PWM,來驅動直流電機的程式設計與電路設計。硬體電路採用IR2110S晶片作為mos管的驅動,驅動IRF840組成的H橋。IR2110S晶片使用中,有一個比較難理解的點——自舉電容,本文對其原理也有涉及。
電機驅動電路簡化原理圖
上圖是系統的簡化原理圖,左側是微控制器。中間是IR2110S晶片,為了方便講解,把晶片內部結構列出一些。右側是MOS管組成的H橋。其中M是直流電機,有正反轉。其中VCC是15V,MOTOR_VCC是24V,電壓可以改變,最大不超過500V。微控制器一般是3.3V或5V,無法直接驅動電機。可以藉助H橋來實現對直流電機的控制。
H橋由於形似H得名。
VT1,VT4導通,電機正轉
VT2,VT3導通,電機翻轉
VT1,VT3導通,短路,板子燒壞
VT2,VT4,導通短路。
所以,驅動電機的問題就變成了MOS管導通的問題。
實際電路中我選用了IRF840,這是N溝道的MOS管。N-MOS導通的條件:VGS大於一定值
對於IRF840,VGS>10V
IRF840資料手冊說明


所以MOS管導通的問題就變成了VGS>10V的問題

如果,VT1與VT3都獨立配置一個電源,獨立配置一套驅動,導通問題就變得簡單了。但是,電路設計會變得複雜。
我們使用1個驅動晶片IR2110S,一路驅動電源。
簡單介紹下IR2110S晶片
IR2110是獨立一橋臂雙通道,柵極驅動,高壓,高速微控制器專用功率器件整合驅動電路。2片IR2110就能構成H橋驅動電路。
IR2110S是3.3V版本
感興趣的可以自己來做個閱讀理解。
IR2110晶片簡介
簡單來說,IR2110是個3.3V控制10-20V的一個驅動。開關速度也很快,120ns
內部結構
IR2110內部結構
右上角的兩個MOS管,中間是非門連線,不會同時導通。
中間一系列怎麼變化,我也不是很清楚。
HIN是1,VM1導通,VM2截止。VB與HO連在一起
HIN是0,VM1截止,VM2導通。VS與HO連在一起
LIN是1,VM3導通,VM4截止。VCC與LO連在一起
LIN是0,VM3截止,VM4導通。LO與COM連在一起
先看下橋臂。左側下橋臂導通,很簡單:
LIN為高,VM3導通,VCC接在LO上(暫時忽略二極體壓降),VGS= VCC,導通
LIN輸入高電平

上橋臂導通的情況,先假設沒有電容。
VM1導通時,VCC接在HO上,為G極提供了接近15V的電壓。但是,VS的電壓是多少?不知道。
如果,VT3導通,VS就是0,VT1也導通了,燒壞。
如果VT4導通,VS通過電機接地(電機內部可以先等效為電阻)。但是VT1導通以後,VS接近24V,HO只有15V,VT1又截止了。電機還是不能工作。
我們面臨的問題是,上橋臂沒有地。怎麼辦?
VCC接在G上
這個時候,就需要自舉電容。
VT4導通,VS接地。電容一端是地,一端是15V,所以VCC通過D給C充電。
又因為VM1導通,所以C橫跨在GS上。所以,C可以作為電壓源,為GS供電。這是一個懸浮的電壓源。
VT1導通後,VS接近24V,不再是地。所以VCC15無法為G提供足夠的電壓。
自舉電容可以放電維持VT1工作,電容存有15V的電,可以保持MOS管的導通。由於電容兩極的壓差不能突變,而電容下邊變成了對地24V(暫不考慮MOS管壓降),所以這一瞬間,電容上邊的電壓是對地39V。這時,VCC無法為電容充電。由於二極體的存在,電容的電不會倒灌給VCC。
電容電量又是有限的,放電會導致電容的電壓降低。等到兩級壓差不到10V的時候,VT1又不工作了。並且,此時IR2110晶片內部的欠壓檢測邏輯就會工作,把HO拉到VS,讓VGS=0。
所以,自舉電容電壓小於10V之前,要充電。如果HIN一直是高電平,電容就沒有充電的機會,等到自舉電容的的電壓跌落到某個閾值以下,HO就變為低電平。
此時可以關掉VT1,也就是斷開VM1,VB與HO斷開,不論是VCC還是電容都不再為G極提供電源。如果此時開啟VT3,讓VS接地,則電容一邊是高電平,一邊是低電平,開始充電。然後再斷開VT3,開啟VM1,VT4保持不變,讓電容放電維持VT1導通,就可以迴圈往復,保持電機執行。
即,VT1的導通要依靠電容放電來維持。HIN不能為持續的高電平,佔空比也不能達到100,或低頻的PWM(頻率低,一個週期內放電時間長)。必須是高頻的PWM,保證自舉電容有周期性的,足夠的充電時間,才能維持較高的懸浮電源電壓。
除此之外,還要注意死區問題,由於絕對不可以把同側橋臂的上下半橋同時開啟,而IR2110S,MOS管與電機切換狀態都存在延時,導致從程式命令某半橋關斷,到實際關斷,有一段時間的延遲。例如,在延遲期間,上半橋正在關閉,下半橋暫時還不能開啟,直到上半橋完全關閉,下半橋才能開啟。中間等待的這段時間,就是死區。死區時間與硬體密切相關。筆者手上就有兩個不同型號的電機,一個在3us的死區時可以工作,另一個則不可以。
所以,寫程式要注意到上下橋不能同時導通,高頻,不能是100的佔空比,以及死區這幾個問題。接下來嘗試用STM32的高階定時器,輸出嵌入死區的互補PWM。
以下是定時器1的初始化程式碼,使用兩個通道輸出PWM,一個週期是100us,頻率是10KHz,3us的死區時間。預設通道一的佔空比是50%,通道2的佔空比是0%,讓電機以47%(佔空比減去死區)的速度正轉。
通道2輸出佔空比是0,可以讓右側上半橋總是截止,下半橋總是導通。下半橋沒有自舉電容。如此一來,只需要左側上半橋導通,就可以讓電機正轉。控制左側橋臂的佔空比,就能控制電機的佔空比。
定時器1的通道1引腳是PA8PB13,通道2的輸出引腳是PA9PB14。

void PWM_Configuration(void)
{
    GPIO_InitTypeDef    GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    //開啟TIM和相應埠時鐘
    //啟動GPIO
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA  | RCC_APB2Periph_GPIOB,
                     ENABLE);
    //啟動AFIO
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    //啟動TIM1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

    //GPIO做相應設定,為AF輸出             //PA8,PB13一組互補輸出  A9,PB14一組互補輸出
    //PA.8/9口設定為TIM1的OC1輸出口
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //複用推輓輸出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    //PB.13/14口設定為TIM1_CH1N和TIM1_CH2N輸出口
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA, GPIO_Pin_8 | GPIO_Pin_9);
    GPIO_SetBits(GPIOB, GPIO_Pin_13 | GPIO_Pin_14);

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);    
    NVIC_InitStructure.NVIC_IRQChannel =  TIM1_UP_IRQn;    
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;       
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
    NVIC_Init(&NVIC_InitStructure);

    //TIM1基本計數器設定(設定PWM頻率)10KHz 
    TIM_BaseInitStructure.TIM_Period = 100-1;      //10khz  好計算。按照1%的精確度,理論最大72000/100 = 720KHz
    TIM_BaseInitStructure.TIM_Prescaler = 72-1;
    TIM_BaseInitStructure.TIM_ClockDivision = 0;
    TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上計數
    TIM_BaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_BaseInitStructure);
    //啟用ARR的影子暫存器(直到產生更新事件才更改設定)
    TIM_ARRPreloadConfig(TIM1, ENABLE);

    //TIM1_OC1模組設定(設定1通道佔空比)
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//TIM脈衝寬度調製模式1
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//輸出通道使能
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;//互補輸出
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;//TIM輸出比較極性高
    //TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
    TIM_OCInitStructure.TIM_Pulse = 50;//待裝入捕獲比較暫存器的脈衝值
    TIM_OC1Init(TIM1, &TIM_OCInitStructure);

    //啟用CCR1暫存器的影子暫存器(直到產生更新事件才更改設定)
    TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);


    //TIM1_OC2模組設定(設定2通道佔空比)
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OC2Init(TIM1, &TIM_OCInitStructure);
    //啟用CCR2暫存器的影子暫存器(直到產生更新事件才更改設定)
    TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);

    //OCx輸出訊號與參考訊號相同,只是它的上升沿相對參考訊號的上升沿有一個延遲
    //OCxN輸出訊號與參考訊號相同,只是它的上升沿相對參考訊號的下降沿有一個延遲

    //死區設定
    TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
    TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
    TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_2;
    //bit7~5 = 111,則deadtime = (32 + (bit4~bit0)* 16*1/fosc)ns = (32+31)*16*1/72000000 = 14us
    TIM_BDTRInitStructure.TIM_DeadTime = 0xab; //這裡調整死區大小0-0xff3us
    TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable;
    TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
    TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
    TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);

    //TIM1_OC通道輸出PWM
    TIM_CtrlPWMOutputs(TIM1, ENABLE);

    //TIM1開啟
    TIM_Cmd(TIM1, ENABLE);
}

帶死區的上下半橋驅動訊號波形
從上圖中,可以清楚地看到,微控制器輸出的上、下橋臂控制訊號,不存在同時為高電平的時候,也就是同側上下橋臂不會同時導通。切換狀態時,某半橋臂的控制訊號拉低3us以後,另半橋臂的控制訊號才能拉高,這就是所謂的帶死區的互補PWM。
輸出的電機控制電壓
上圖是輸出的電機控制電壓。可以看出週期是100us,高電平持續時間大約一半。
初始化之後,在程式執行時,可以呼叫

TIM_SetAutoreload(TIM1,xx);

來設定自動重灌值。在初始化的時候把此值設定為了100,如果改為80,效果如下:
72分頻80自動重灌值波形
可以看出,一個週期變成了80us。
也可以使用函式

                TIM_SetCompare1(TIM1,xx);
                TIM_SetCompare2(TIM1,xx);

來分別為通道1與通道2設定比較值。例如,把比較值設定為80,而自動重灌值還是100,那麼佔空比就是80%了。
80%佔空比
下邊是用按鍵控制電機的一個小demo。實現了按鍵1啟停,按鍵2切換正反轉,按鍵34增減轉速的功能。

int main(void)
{    
    static u8 motorValue = 50,oldvalue = 50;
    static u8 startStop = 1,dir = 1;//startStop = 1啟動  =0停止 dir =1 正轉
    volatile u8 key = 0;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設定NVIC中斷分組2:2位搶佔優先順序,2位響應優先順序
    LED_Init();
    KEY_Init();
    delay_init();
    PWM_Configuration();
    Motor_IO_Init();
    LED1 = LED_ON;
    while(1)    
    {
        key = KEY_Scan(0);
        if(key)
        {
            switch(key)
            {
                case KEY1_PRES:
                        startStop = !startStop;
                break;
                case KEY2_PRES:
                        dir = !dir;
                break;
                case KEY3_PRES:
                        motorValue += 5;
                break;
                case KEY4_PRES:
                        motorValue -= 5;
                break;
                default:
                        break;
            }
            if(motorValue>249)//<0
                motorValue = 0;
            else if(motorValue >94)
                motorValue = 94;
            if (startStop)
            {
                if(dir)
                {
                    TIM_SetCompare2(TIM1,0);
                    delay_ms(500);
                    TIM_SetCompare1(TIM1,motorValue);
                }
                else
                {
                    TIM_SetCompare1(TIM1,0);
                    delay_ms(500);
                    TIM_SetCompare2(TIM1,motorValue);            
                }
            }
            else
            {
                TIM_SetCompare1(TIM1,0);
                TIM_SetCompare2(TIM1,0);
            }            
        }

    }
}
//S1 啟動&停止  S2翻轉   S3+5   S4-5