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,十分無效率,但其可以結合輪詢等固定時間需要執行的功能,對於低階,簡單的系統有其應用空間。