關於AVR的PWM的使用

這一段幫同學用AVR128A做畢設,航模遙控控制機械臂工作臺,使用驅動的時候遇到了鎖不住電機的問題。原來解決這個問題是兩個方案:一是設置驅動的截斷的電流,<多少轉速是死區。第二種方法是自己用編碼器寫一個抱死的程序。

    下面是我的程序的設計思路: 這個程序用了兩個定時器:timer0和timer1。
    timer0用來產生pwm。timer0產生pwm信號是這樣實現的:程序中有一個timer0的溢出事件計數器,和兩個保存兩路pwm信號佔空比的變量,當timer0溢出事件計數器計數超過100時,如果某個pwm信號佔空比不爲0,則把相應pwm引腳置高電平,同時清零此計數器,當此計數器等於某個佔空比時,則把相應引腳置低電平,從而實現timer0溢出事件計數器從0計數到100時輸出一個週期的pwm信號。通過調節timer0的溢出頻率,即可調節pwm信號輸出的頻率。
     timer1用來對編碼器的輸出進行計數,同時調整pwm的佔空比,實現對電機的控制。對編碼器的輸出計數是利用了timer1的輸入捕捉功能,由於電機可以正轉,也可以反轉,導致編碼器的CHA和CHB的輸出也不同,所以可以在程序中可以判斷電機是正轉還是反轉,再對編碼器的輸出脈衝進行計數,當電機正轉的時候計數增加,電機反轉的時候計數減少,所以編碼器的計數值是有正負的。從而可以知道什麼時候該通過調整pwm來控制電機。
    下面是我的調試過程,也算是一點經驗吧:以開始的思路是隻要編碼器的計數值不爲0,我就要讓電機反方向轉動,以保持電機抱死,發送給電機的pwm是固定的數值,但是這樣反而是抱不死,它在前後地抖動,而且pwm的佔空比越大,電機抖動得越厲害,這樣顯然不行;後來想了一個辦法,就是如果編碼器的計數值在一定的範圍內,我就不用讓電機反方向轉動。因爲這個電機是變速電機,如果電機裏面只轉動一點點,在外面看來就相當於不動,這樣的話就給電機預留了一部分轉動的空間,用來消除抖動,就是說在這個空間內是不發送pwm給電機的,或者說電機兩極的pwm佔空比都爲0。這樣一來,當pwm佔空比比較低時,是可以消除抖動,但是力氣不大,就是說還是可以用鉗子擰得動,調了很久都無法在抖動和電機力氣之間取得平衡。後來又想了一個辦法,在這個基礎上再改進,因爲之前的pwm佔空比都是不變的,所以很難達到令人滿意的效果,現在的方法是,根據電機被擰動的角度,或者說編碼器的計數值大小來調整pwm的佔空比,編碼器的計數值偏離0越多(正或負得越大),pwm的佔空比就越大,電機的力氣也就越大,從而不會出現電機一旦被擰動就馬上以最大速度轉回去的情況,抖動也就消除了,而且電機力氣很大。

  編譯環境是AVR Studio 5.0,下面是程序代碼:
#include <avr/io.h>
#include <avr/interrupt.h> 
 
 
int forward = 0, reverse = 0;//存儲電機正轉和反轉pwm佔空比的變量
int timer0_count = 0;//timer0溢出事件計數器
int capt_count = 0;//輸入捕捉事件計數器
 
 
void port_init(void)
{
PORTA = 0x00;
DDRA  = 0x00;
PORTB = 0x00;
DDRB  = 0x00;
PORTC = 0x00; //m103 output only
DDRC  = 0x00;
PORTD = 0x00;
DDRD  = 0xC0;
PORTE = 0x00;
DDRE  = 0x00;
PORTF = 0x00;
DDRF  = 0x00;
PORTG = 0x00;
DDRG  = 0x00;
}
 
 
void timer0_init(void)
{
TCCR0 |= 5;//256分頻,普通模式
TIMSK |= 0x01;//timer0溢出中斷
TCNT0 = 0xFE;//TCNT0賦初值
}
 
 
void timer1_init(void)
{
TCCR1B = 0x00;//停止
TCCR1A = 0x00;//普通模式
TCCR1C = 0x00;
TCNT1 = 0;//計數初值
TCCR1B = 0xC4;//啓動定時器,256分頻,使能輸入捕捉噪聲抑制器,輸入捕捉觸發沿選擇:上升沿
TIMSK = 0x24;//輸入捕捉中斷使能,T/C1溢出中斷使能
}
 
/************************************************************************/
/*  timer0溢出中斷函數,產生提供給電機的pwm                             */
/************************************************************************/
ISR(TIMER0_OVF_vect<span style="color: #ff0000;">)//200kHz
</span>{
TCNT0 = 0xFE;//TCNT0重新賦值
//當timer0_count等於100時,如果正轉或反轉的佔空比不爲0,則相應引腳輸出高電平
if(++timer0_count >= 100)//<span style="color: #ff0000;">對timer0溢出事件計數100次,相當於100分頻,最後輸出到電機的pwm頻率是2kHz
</span>{
timer0_count = 0;
if(forward != 0<span style="color: #ff0000;">)//forward, reverse存儲電機正轉和反轉pwm佔空比的變量
</span>{PORTD |= (1<<6);}
if(reverse != 0)
{PORTD |=(1<<7);}
}
//<span style="color: #ff0000;">當timer0_count等於正轉或反轉的佔空比時,相應引腳輸出低電平,實現輸出pwm信號
</span>if(timer0_count == forward)
{PORTD &= ~(1 << 6);}
if(timer0_count == reverse)
{PORTD &= ~(1 << 7);}
}
 
/************************************************************************/
/*  timer1輸入捕捉中斷函數,對編碼器輸出的上升沿進行計數              */
/************************************************************************/
ISR(TIMER1_CAPT_vect)
{
  if(PIND & (1 << 5))//電機反轉
  {capt_count--;}//輸入捕捉計數器減1
  else               //電機正轉
  {capt_count++;}//輸入捕捉計數器加1
}
 
 
/************************************************************************/
/*  <span style="color: #ff0000;">timer1溢出中斷函數,100Hz,用於調整電機轉速和轉動的方向,實現電機抱死</span>*/
/************************************************************************/
ISR(TIMER1_OVF_vect)
{
TCNT1 = 64910;                           //重新給TCNT1賦值
static unsigned char <span style="color: #ff0000;">motor_state = 0;    //電機的狀態,標誌電機是正轉還是反轉,0:正轉,1:反轉
</span>switch(motor_state)
{
case 0<span style="color: #ff0000;">://電機正轉時
</span>if(capt_count > 40)              <span style="color: #ff0000;">//如果編碼器正轉計數超過40,則電機需要反轉,以保持電機不動
</span>{reverse = capt_count - 40;} //直接把編碼器計數值減去40,作爲反轉的佔空比
else if(capt_count < 0)          <span style="color: #ff0000;">//如果編碼器計數值小於0
</span>{motor_state = 1;}           //進入狀態1
else                            <span style="color: #ff0000;"> //如果編碼器計數值在0~40內,爲了不發生抖動,不需要反轉
</span>{reverse = 0;}               //反轉的佔空比爲0,相當於負極接地
forward = 0;                     //正轉的佔空比爲0,相當於正極接地
break;
 
case 1:
if(capt_count < -40)             <span style="color: #ff0000;">//如果編碼器反轉計數超過40,則電機需要正轉</span>,以保持電機不動
{forward = (-capt_count) - 40;}//直接把編碼器計數值減去40,作爲正轉的佔空比
else if(capt_count > 0)          <span style="color: #ff0000;">//如果編碼器計數值大於0
</span>{motor_state = 0;}           //返回狀態0
else                             <span style="color: #ff0000;">//如果編碼器計數值在-40~0內,爲了不發生抖動,不需要正轉
</span>{forward = 0;}               //正轉的佔空比爲0,相當於正極接地
reverse = 0;                     //反轉的佔空比爲0 ,相當於負極接地
break;
 
default:
break;
}
}
 
 
void Init_Devices(void)
{
cli();//關閉全局中斷
port_init();//I/O口初始化
timer1_init();//定時/計數器1初始化
timer0_init();//計時/計數器0初始化
sei();//打開全局中斷
}
 
 
int main(void)
{
    Init_Devices();
    while(1)
    {}
 
    return 0;
}



這是比較麻煩的方法,需要用到編碼器。後來仔細再讀一遍AVR的說明書。發現是PWM模式的選擇有問題。下面是5種模式說明

 1    普通模式 WGM1=0  
      跟51的普通模式差不多,有TOV1溢出中斷標誌,發生於MAX(0xFFFF)時  
      1 採用內部計數時鐘       用於 ICP捕捉輸入場合---測量脈寬/紅外解碼  
          (捕捉輸入功能可以工作在多種模式下,而不單單只是普通模式)  
      2 採用外部計數脈衝輸入    用於 計數,測頻  
      其他的應用,採用其他模式更爲方便,不需要像51般費神  
      
    2 CTC模式 [比較匹配時清零定時器模式] WGM1=4,12  
       跟51的自動重載模式差不多  
       1 用於輸出50%佔空比的方波信號  
       2 用於產生準確的連續定時信號  
       WGM1=4時,最大值由OCR1A設定,TOP時產生OCF1A比較匹配中斷標誌  
       WGM1=12時,最大值由ICF1設定, TOP時產生ICF1輸入捕捉中斷標誌  
             ------如果TOP=MAX,TOP時也會產生TOV1溢出中斷標誌  
       注:WGM=15時,也能實現從OC1A輸出方波,而且具備雙緩衝功能  
       計算公式: fOCn="fclk"_IO/(2*N*(1+TOP))  
                     變量N 代表預分頻因子(1、8、64、256、1024),T2多了(32、128)兩級。       
       
    3 快速PWM模式 WGM1=5,6,7,14,15   
      單斜波計數,用於輸出高頻率的PWM信號(比雙斜波的高一倍頻率)  
      都有TOV1溢出中斷,發生於TOP時[不是MAX,跟普通模式,CTC模式不一樣]  
      比較匹配後可以產生OCF1x比較匹配中斷.  
        WGM1=5時, 最大值爲0x00FF, 8位分辨率  
        WGM1=6時, 最大值爲0x01FF, 9位分辨率  
        WGM1=7時, 最大值爲0x03FF,10位分辨率   
       WGM1=14時,最大值由ICF1設定, TOP時產生ICF1輸入捕捉中斷 (單緩衝)  
       WGM1=15時,最大值由OCR1A設定,TOP時產生OCF1A比較匹配中斷(雙緩衝,但OC1A將沒有PWM能力,最多隻能輸出方波)  
       改變TOP值時必須保證新的TOP值不小於所有比較寄存器的數值  
      注意,即使OCR1A/B設爲0x0000,也會輸出一個定時器時鐘週期的窄脈衝,而不是一直爲低電平  
      計算公式:fPWM=fclk_IO/(N*(1+TOP))  
    4 相位修正PWM模式 WGM1=1,2,3,10,11   
      雙斜波計數,用於輸出高精度的,相位準確的,對稱的PWM信號  
      都有TOV1溢出中斷,但發生在BOOTOM時  
      比較匹配後可以產生OCF1x比較匹配中斷.  
        WGM1=1時, 最大值爲0x00FF, 8位分辨率  
        WGM1=2時, 最大值爲0x01FF, 9位分辨率  
        WGM1=3時, 最大值爲0x03FF,10位分辨率   
       WGM1=10時,最大值由ICF1設定, TOP時產生ICF1輸入捕捉中斷 (單緩衝)  
       WGM1=11時,最大值由OCR1A設定,TOP時產生OCF1A比較匹配中斷(雙緩衝,但OC1A將沒有PWM能力,最多隻能輸出方波)  
      改變TOP值時必須保證新的TOP值不小於所有比較寄存器的數值  
      可以輸出0%~100%佔空比的PWM信號  
      若要在T/C 運行時改變TOP 值,最好用相位與頻率修正模式代替相位修正模式。若TOP保持不變,那麼這兩種工作模式實際沒有區別  
      計算公式:fPWM=fclk_IO/(2*N*TOP)  
    5 相位與頻率修正PWM模式 WGM1=8,9   
      雙斜波計數,用於輸出高精度的、相位與頻率都準確的PWM波形  
      都有TOV1溢出中斷,但發生在BOOTOM時  
      比較匹配後可以產生OCF1x比較匹配中斷.  
       WGM1=8時,最大值由ICF1設定, TOP時產生ICF1輸入捕捉中斷 (單緩衝)  
       WGM1=9時,最大值由OCR1A設定,TOP時產生OCF1A比較匹配中斷(雙緩衝,但OC1A將沒有PWM能力,最多隻能輸出方波)  
      相頻修正修正PWM 模式與相位修正PWM 模式的主要區別在於OCR1x 寄存器的更新時間  
      改變TOP值時必須保證新的TOP值不小於所有比較寄存器的數值  
      可以輸出0%~100%佔空比的PWM信號  
      使用固定TOP 值時最好使用ICR1 寄存器定義TOP。這樣OCR1A 就可以用於在OC1A輸出PWM 波。  
      但是,如果PWM 基頻不斷變化(通過改變TOP值), OCR1A的雙緩衝特性使其更適合於這個應用。  
      計算公式:fPWM=fclk_IO/(2*N*TOP)  


說一下使用的體驗吧。我使用的是四個馮哈伯的24V電機,與一個5相步進電機加5807np驅動器。5相步進除了供電外,如果不設置扭矩,輸出脈衝檢測,步距角,只往CW和CCW輸入脈衝就可以實現正反轉。開始的想法是PWM直接輸出給CW,CCW。發現一直轉,無法停止,猜測應該是有脈衝一直輸出,手頭沒有示波器。採用的是快速PWM模式,問題是在OCRXX設置爲0的時候還是有短暫的脈衝輸出。換成相位修正模式解決問題。相位修正可以直接輸出0和5V。實際上,後來實驗,還沒有直接IO取反順暢,跟PWM的分頻有關係,頻率不夠快。

後面使用到了舵機,因爲2個定時器用來採集,兩個用來輸出PWM。沒有用來定時的資源了。就打算偷懶直接輸PWM。使用快速或者相位修正的10位模式可以做到使其轉動,但是因爲週期不是20ms,對應的時間設置和數字對不上,控制也不準確。如果想準確定時,通過自己的分頻計算,使用相頻可調模式,設置

TCCR1A=0XA0;
TCCR1B=0X13;
TCR1=1152;
OCR1A=87;//1.5MS 360舵機的停止

可準確完成定時,輸出脈衝。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章