PWM產生方式

PWM簡述

PWM為Pulse Width Modulation的縮寫,對於只有high和low的數位訊號來說,如何用high,low比率調整出類似類比訊號為PWM的用處,另外大多數馬達也透過PWM來驅動轉速,LED由於只吃固定電壓,所以常用PWM來調整亮度。

duty cycle(占空比)為整個high時間除以週期時間(D = DT/T)。

PWM產生方式

PWM主要又三種方式產生:硬體內建PWM模組、中斷產生、迴圈。

硬體內建PWM模組

MCU內常內建PWM模組,以PIC系列為例,有CCP(Capture/Compare/PWM)模組、Output Compare模組和PWM模組。透過設定timer和暫存去達成PWM輸出。以下以dsPIC30F4013的Output Compare做示範。

#include <xc.h>  
  
// FOSC
#pragma config FOSFPR = XT_PLL4         // Oscillator (XT w/PLL 4x)
#pragma config FCKSMEN = CSW_FSCM_OFF   // Clock Switching and Monitor (Sw Disabled, Mon Disabled)
  
// FWDT
#pragma config FWPSB = WDTPSB_16        // WDT Prescaler B (1:16)
#pragma config FWPSA = WDTPSA_512       // WDT Prescaler A (1:512)
#pragma config WDT = WDT_OFF            // Watchdog Timer (Disabled)
  
// FBORPOR
#pragma config FPWRT = PWRT_64          // POR Timer Value (64ms)
#pragma config BODENV = BORV20          // Brown Out Voltage (Reserved)
#pragma config BOREN = PBOR_ON          // PBOR Enable (Enabled)
#pragma config MCLRE = MCLR_EN          // Master Clear Enable (Enabled)
  
// FGS
#pragma config GWRP = GWRP_OFF          // General Code Segment Write Protect (Disabled)
#pragma config GCP = CODE_PROT_OFF      // General Segment Code Protection (Disabled)
  
// FICD
#pragma config ICS = ICS_PGD            // Comm Channel Select (Use PGC/EMUC and PGD/EMUD)
  
#define Tcy 10000000            //10MHz oscillator with 4xPLL -> 10'000'000MIPS

  
//------------------------------------------------------------------------------
//  main routine
//------------------------------------------------------------------------------
int main(int argc, char** argv) {
      
//------------------------------------------------------------------------------
//     IO Plan
      
    TRISA = 0x0000;
    TRISB = 0x0000;
    TRISC = 0x0000;
    TRISD = 0x0000;     //RD1,RD2,RD3,RD4 -> PWM OC pin
    TRISF = 0x0000;
    ADPCFG = 0xFFFF;

//------------------------------------------------------------------------------
//   initialize PWM 
      
    //PR2 = 50000;      1:256-> 200 HZ
    PR2 = 25000;
    OC1RS = 1875;
    OC1CON = 0x0006;    //Set PWM mode on OC1,Fault pin disable,Timer2 is source
    OC2RS = 1875;
    OC2CON = 0x0006;    //Set PWM mode on OC2,Fault pin disable,Timer2 is source
    OC3RS = 1875;
    OC3CON = 0x0006;    //Set PWM mode on OC3,Fault pin disable,Timer2 is source
    OC4RS = 1875;
    OC4CON = 0x0006;    //Set PWM mode on OC4,Fault pin disable,Timer2 is source
    //IEC0bits.T2IE = 1;  //enable Timer2 interrupt 
   // T2CON = 0x8000;     //Configure Timer2(Timer on,continue in IDLE,not gate
                        //                      ,1:256 prescaler,internal clock)
    T2CON = 0x8010;
    TMR2 = 0;
    while(1);
}
}

由於每個MCU的PWM實際細節都不一樣,所以讀者請自行閱讀datasheet來應用硬體內建的PWM。
dsPIC30F系列的Output Compare PWM模組要應用時須將OCxCON 暫存器中的OCM設為'110’或'111'。設定順序為:
1.設定PRy(Period register)表示PWM的週期。
2.再來設定OCxRS暫存器表示duty cycle。
3.設定OCM。
4.設定timer,並開啟timer。

PWM 週期 = [(PRy+1)]*Tcy*(TMRx presclaer value)。 (Tcy為振盪器頻率*PLL/4)
PWM 頻率為週期倒數。
詳細設定在http://ww1.microchip.com/downloads/en/DeviceDoc/70157C.pdf中有詳細描述。

中斷產生PWM

利用Timer中斷來切換PWM進入duty cycle,有兩種方法。

一、用一個或兩個timer,先輸出high,計算PWM在high所需時間,設定好Timer 週期(中斷條件),當Timer發生中斷,將輸出切為low,並在計算PWM在low所需時間,同樣設定好Timer週期,當Timer發生中斷回到一開始重頭來過。也可用兩個Timer一個設為high所需時間,一個為period,當high的timer 中斷發生,關閉其中斷與timer,並將輸出設為low,等待period 的Timer發生中斷,在開啟high 的timer中斷。

二、用一個timer中斷,當其中斷將counter加一,一旦couner的值大於duty cycle則切為low,當counter的值大於period則歸零。以下為範例(使用PIC18F452)。

/*
 * File:   newmainXC16.c
 * Author: asus-h
 *
 * Created on 2015?2?9?, ?? 9:49
 */


#include "xc.h"
#pragma config OSC = HS, WDT=OFF, LVP=OFF,PWRT = OFF

int counter,pwm_val_1 = 20,pwm_val_2 = 20,pwm_val_3 = 20,pwm_val_4 = 20;
int change = 1;
void init_timer();
void do_PWM();

int main(void) {
    TRISC = 0xEF;
    TRISB = 0b11111111;
    TRISD = 0x00;
    //TRISCbits.TRISC4 = 0;
    //unsigned int duty = 500;
   // T3CONbits.T3CCP2=0;
   // T3CONbits.T3CCP1=0;
    INTCON2 = 0b00000001;
    
    init_timer();
    return 0;
}



void interrupt ISR()
{
    if(T0IF)
    {

        TMR0 = 65530;
        counter++;

        INTCON &=  ~(1<<T0IF);
        //INTCON = 0b11111110; //clear overflow bit;
        T0IF = 0;
        do_PWM();
    }
}

void init_timer()
{
    TMR0 = 65500;
    TMR1 = 0;
    T0CON = 0b10000001;
    T1CON = 0b10000000;
    //OPTION = 0B00001000;
    INTCON = 0b11111110;
    PIR1 = 0b000000011;
}
void do_PWM()
{
    TMR0 = 65530 ;
        counter++;
        if(counter<=pwm_val_1)
           PORTDbits.RD4 = 1;
        else
          PORTDbits.RD4 = 0;

        if(counter<=pwm_val_2)
            PORTDbits.RD5 = 1;
        else
            PORTDbits.RD5 = 0;

        if(counter<=pwm_val_3)
            PORTDbits.RD6 = 1;
        else
            PORTDbits.RD6 = 0;

        if(counter<=pwm_val_4)
            PORTDbits.RD7 = 1;
        else
            PORTDbits.RD7 = 0;
            
        if(counter == 100)
            counter = 0;
         T0IF = 0;
}

此方法缺點為PWM頻率難以調整,不過對於本身PWM模組缺乏或過少之MCU,此法十分有效。

迴圈產生PWM

以迴圈直接產生PWM,可以用一個counter當作現在情況,當迴圈執行次數超過duty cycle的值時,調整high low,這方法十分簡單,但如果要有準確的頻率需要將程式編成組語計算其所需時間,並用NOP指令挑整週期。這方法會佔走整個CPU,十分無效率,但其可以結合輪詢等固定時間需要執行的功能,對於低階,簡單的系統有其應用空間。

comments powered by Disqus