寫在前面:
此例程使用間隔時間固定的定時器中斷。比如此處使用100Us的定時器中斷。下一篇寫使用另一個方式實現。
一、PWM介紹
PWM(Pulse Width Modulation)控制——脈衝寬度調製技術。一般使用方式是通過數字電路信號的佔空比來模擬達到輸出連續電壓的目的。
比如在數字電路中,IO能輸出的電壓爲0V或3.3V,但是如果想要輸出1.65V,該怎麼做呢?此時可通過在一定時間T內輸出(1/2)T時間的0V,(1/2)T時間的3.3V,那麼平均電壓爲1.65V,如果時間T足夠小,則認爲輸出電壓爲1.65V,此時高電平“佔空比”爲50%。實際使用中,這裏所說的一定時間T爲週期。
如下圖所示,在左邊時,高電平時間爲0,此時對應的電壓爲0,隨着佔空比的增加,對應的電壓增加。在中間時佔空比爲100%(即一定時間T內,全部輸出高電平),此時電壓爲最大值。隨後又減小。
如下圖爲一連續變化佔空比的示意圖。
二、應用場景
PWM在各個領域應用廣泛,通過調整佔空比達到模擬輸出不同電壓的目的,一般可用於呼吸燈、PID調速等場景。
三、實例介紹——舵機控制:
舵機控制原理是週期爲20Ms,通過調整0.5Ms~1.5Ms的高電平佔空比,從而達到舵機不同的角度的目的
一般芯片都帶有PWM硬件模塊,只需設置頻率和佔空比,則在IO自動輸出相應的信號。但也有芯片沒有PWM硬件模塊,則可通過定時器來實現。原理爲在定時器中斷中判斷閾值,調整IO電平,從而達到實現PWM信號。
原理詳解:設定一個每隔100Us的定時器中斷,設定週期爲200次,當閾值爲10次,在每次定時器中斷中判斷前10次輸出高電平,後190次輸出低電平。則輸出的IO電平現象是:週期爲100Us*200次=20Ms,高電平時間爲100Us*10次=1Ms,低電平時間爲100Us*190次=19Ms的信號。
以下爲操作實例
1、定義相關參數,關鍵參數爲PWM週期、佔空比。
2、初始化IO和相關參數,同時也需自定義一個定時器中斷,此處我定義了一個100Us的定時器中斷。
3、定時器中斷中調用判斷閾值函數,用於刷新IO電平。
4、在主循環中可定時刷新佔空比閾值,從而達到呼吸燈效果等目的
以下爲程序全文,實現週期爲20Ms,高電平佔比時間1.5Ms~2.5Ms循環:
#include "Main.h"
#include "Pwm.h"
#define PWM_IO_PORT GPIO_PORT0
#define PWM_IO_PIN BIT4
#define PWM_IO_SET do{GpioOutDataSetBits(PWM_IO_PORT, PWM_IO_PIN);}while(0)
#define PWM_IO_CLR do{GpioOutDataClrBits(PWM_IO_PORT, PWM_IO_PIN);}while(0)
typedef struct{
/*關鍵參數*/
INT16 cycle; //Pwm週期
INT16 dutyThd; //佔空比
INT16 statieCount; //狀態計數值
/*以下做呼吸燈等需要的參數*/
INT16 thdMax; //佔空比Max
INT16 thdMin; //佔空比Min
INT16 addDir; //調整方向
INT16 adjInterval; //主循環中調整閾值的時間間隔
INT16 adjSpeed; //主循環中調整閾值的速度
UINT32 timerCount; //時間計數值
}pwmPara;
pwmPara pwmParaVal;
/*定時器中斷調用函數*/
void PwmIntProcess(void)
{
if (pwmParaVal.statieCount < pwmParaVal.dutyThd)
{
PWM_IO_SET; //Set 1
}
else
{
PWM_IO_CLR; //Set 0
}
pwmParaVal.statieCount++;
if(pwmParaVal.statieCount >= pwmParaVal.cycle)
{//滿一個週期重新計數
pwmParaVal.statieCount = 0;
}
}
/*可在主循環中隨時修改閾值和週期*/
void PwmLoopProcess(void)
{
UINT32 tempData;
tempData = GetMs(0);
if((tempData - pwmParaVal.timerCount) > pwmParaVal.adjInterval)
{//每隔一定時間進入調整佔空比值
pwmParaVal.timerCount = tempData;
//調整閾值
pwmParaVal.dutyThd += pwmParaVal.addDir;
if(pwmParaVal.dutyThd >= pwmParaVal.thdMax)
{//判斷是否需要改變方向
pwmParaVal.addDir = -pwmParaVal.adjSpeed;
}
else if((pwmParaVal.dutyThd <= pwmParaVal.thdMin))
{
pwmParaVal.addDir = pwmParaVal.adjSpeed;
}
}
}
/*初始化函數*/
void PwmInit(void)
{
GpioFunSetAll(PWM_IO_PORT, GPIO_FUN1, PWM_IO_PIN);
GpioOutSet(PWM_IO_PORT, PWM_IO_PIN);
/*此處根據需求還需初始化定時器中斷,我這裏設置了100Us定時器中斷*/
pwmParaVal.cycle = 200; //設置週期 = 200*100Us=20Ms;
pwmParaVal.dutyThd = 20; //佔空比20*100Us=2Ms;
pwmParaVal.statieCount = 0;
pwmParaVal.thdMax = 25; //佔空比最大25*100Us=2.5Ms;
pwmParaVal.thdMin = 15; //佔空比最小15*100Us=1.5Ms;
pwmParaVal.addDir = 0;
pwmParaVal.adjInterval = 100; //100Ms調整一次閾值
pwmParaVal.adjSpeed = 1; //每次調整閾值單位爲1
pwmParaVal.timerCount = 0;
}