對於PWM的概念這裡就不過多贅述,大家可以直接檢視一些文章對於pwm的介紹,下面給一段我認為比較好的說法:
脈衝寬度調變(PWM)是一種對模擬訊號電平進行數位編碼的方法。通過高解析度計數器的使用,方波的佔空比被調變用來對一個具體模擬訊號的電平進行編碼。PWM訊號仍然是數位的,因為在給定的任何時刻,滿幅值的直流供電要麼完全有(ON),要麼完全無(OFF)。電壓或電流源是以一種通(ON)或斷(OFF)的重複脈衝序列被加到模擬負載上去的。通的時候即是直流供電被加到負載上的時候,斷的時候即是供電被斷開的時候。只要頻寬足夠,任何模擬值都可以使用PWM進行編碼。–參考百度百科
這是一個什麼意思呢?
為了後續理解的方便,我先給出PWM的兩個重要指標:
先來看一幅圖:
下圖是一個週期訊號,他的週期是3.70ms,其高電平所佔時間為1.85ms
我們就說這個訊號的週期為3.70ms,佔空比( 高電平時間/總週期時間)為50%
如果高電平是5V,低電平為0V,那麼這個訊號,在一個週期有一半時間是5V,一半時間是0V,它的電壓可以這樣計算
v
=
(
5
+
0
)
∗
50
%
=
2.5
伏
v = (5 + 0)*50\% = 2.5伏
v=(5+0)∗50%=2.5伏
那麼如果我們要產生4V的電壓如何算呢?
4
=
(
5
+
0
)
∗
m
4 = (5+0)*m
4=(5+0)∗m
可以解得
m
=
80
%
m= 80\%
m=80%,也就是說高電平需要佔整個週期得4/5。這樣說來我們可以通過佔空比來調節輸出電壓的大小,但是週期
的大小又對pwm來說意味著什麼呢?
我們先看如下實驗,週期為20us,佔空比為10%
然後週期為20us,佔空比為90%
然後是週期為4ms,佔空比為10%
可以看到週期太長,會導致閃爍,這個很好理解,如何你把週期設定為1s,那麼不久是在1s開燈,1s關燈嘛,所以說週期必須設定要夠小才能有效果,根據我的測試,週期為1ms以下效果就還是不錯的
瞭解原理後,我們如何實現pwm呢?因為pwm涉及到定時,因此我們可以利用定時器來實現!
如果我們設定一個1us的定時器中斷,那麼每過1us,就會進入中斷,我把這個定時器1us叫做最低步進
設pwm引數為一個結構體,其定義如下
其中cnt為計數值,max表示計數的最大值,highCnt表示高電平所佔計數值
typedef struct pwm
{
uint cnt;
uint max;
uint highCnt;
}pwm_t;
pwm_t pwm1 = {0, 20, 2};
在定時器中斷這樣寫:
void Timer0Handle() interrupt 1
{
//如果cnt為0表示週期開始,那麼週期開始的時候是亮燈(也即低電平亮)
if(pwm1.cnt == 0)
{
SL(LED, 0);
}
//如果cnt和highCnt相等,表示高電平的時間已經到了,因此這裡要滅燈(也即高電平滅)
else if(pwm1.cnt == pwm1.highCnt)
{
SL(LED, 0xff);
}
//讓cnt變數在0-max之間不斷執行
if(pwm1.cnt++ < pwm1.max);
else
{
pwm1.cnt = 0;
}
}
在這個程式碼中,我只需要修改highCnt的值就是修改佔空比了,因此要想實現呼吸燈的效果,我只需要在一段時間修改佔空比的值就行了,比如
效果如下
話說其實每次考試的時候,它都會說讓你pwm設定幾個等級,其實這個也就是說佔空比在整個週期中設定為幾個值,比如你是4個等級,如果週期為100份,那麼這四個等級的佔空比就是[1,25,50,100].
因此呀,pwm佔空比的值其實是整個週期的每一份都是可以用的。所以在保證週期足夠小的基礎上面,我們儘量選擇更細一點,讓我們有更多的週期份數可以用。
比如上面說的週期有100份,如果1份的時間是1s顯然是不行的!為1us的話,100份就是100us,而這個一份的時間就是我們定時器來提供的。
比如我們可以直接實現以下效果:
從左到右亮度逐漸增加
其中核心程式碼如下
uint pwm_cnt = 0;
uint pwm_max = 105;
uint pwm_value[] = {5, 10, 15, 20, 30, 40, 50, 105};
uchar ledValue = 0x00;
void Timer0Handle() interrupt 1
{
//如果cnt為0表示週期開始,那麼週期開始的時候是亮燈(也即低電平亮)
//因為都是佔空比的不同,但是起始的時候都是高電平,不同在於低電平來到的時間
uchar i = 0;
if(pwm_cnt == 0)
{
SL(LED, 0);
}
//讓cnt變數在0-max之間不斷執行
if(pwm_cnt++ < pwm_max);
else
{
//結束週期,ledValue全部亮起
ledValue = 0x00;
pwm_cnt = 0;
}
//遍歷8個燈的情況
for(i=0; i<8; i++)
{
if(pwm_cnt == pwm_value[i])
{
//如果cnt等於對應燈的佔空比了,那麼這個燈就應該被關閉了
ledValue |= (1 << i);
SL(LED, ledValue);
}
}
}
同時上述程式碼也可以改為流水燈,效果如下
(實現亮度不同的狀態下,實現流水燈)
核心程式碼:
就是要在每次pwm初始亮燈的時候考慮到led狀態的變數,而這個變數決定了到底led應該如何亮!
uchar maskValue = 0xff;
void Timer0Handle() interrupt 1
{
//如果cnt為0表示週期開始,那麼週期開始的時候是亮燈(也即低電平亮)
//因為都是佔空比的不同,但是起始的時候都是高電平,不同在於低電平來到的時間
uchar i = 0;
if(pwm_cnt == 0)
{
SL(LED, ledValue);
}
//讓cnt變數在0-max之間不斷執行
if(pwm_cnt++ < pwm_max);
else
{
//結束週期,注意:(如果要外部控制每個LED,那麼這裡就不是全部亮起了!)
ledValue = maskValue;
pwm_cnt = 0;
}
//遍歷8個燈的情況
for(i=0; i<8; i++)
{
if(pwm_cnt == pwm_value[i])
{
//如果cnt等於對應燈的佔空比了,那麼這個燈就應該被關閉了
ledValue |= (1 << i);
SL(LED, ledValue);
}
}
TimeRun(&delay1s);
}
void liushui()
{
if(delay1s.ok)
{
static uchar i;
delay1s.ok = 0;
if(i++ < 8);
else
{
i = 0;
}
maskValue = ~(1 << i);
}
}
相比經過上述的介紹,對pwm的使用,各位都應該有了一個比較清晰的認識了,我想說的是pwm是一個非常常規的知識,但是想要用好它,還是需要一定的思考的,對於藍橋杯歷年的題來說,pwm也是一個比較難的考點,大家平時還是要按照題目要求,多總結一下,多練練!
#include "STC15F2K60S2.h"
#define uchar unsigned char
#define uint unsigned int
#define SELE(x) P2 = P2 & 0x1f | x << 5; P2 = P2 & 0x1f
code enum{LED = 4, EXT, SEL, CODE};
typedef struct pwm
{
uint cnt;
uint max;
uint highCnt;
uchar ok;
}pwm_t;
pwm_t pwm1 = {0, 20, 2, 0};
void SL(uchar _dev, uchar _data)
{
P0 = _data;
SELE(_dev);
}
void Timer0Handle() interrupt 1
{
if(pwm1.cnt == 0)
{
SL(LED, 0);
}
else if(pwm1.cnt == pwm1.highCnt)
{
SL(LED, 0xff);
}
if(pwm1.cnt++ < pwm1.max);
else
{
pwm1.cnt = 0;
}
}
void Timer0Init(void) //1微秒@12.000MHz
{
AUXR |= 0x80; //定時器時鐘1T模式
TMOD &= 0xF0; //設定定時器模式
TL0 = 0xF4; //設定定時初值
TH0 = 0xFF; //設定定時初值
TF0 = 0; //清除TF0標誌
TR0 = 1; //定時器0開始計時
ET0 = 1;
}
void main()
{
SL(LED, 0x0);
Timer0Init();
EA = 1;
while(1)
{
}
}
#include "STC15F2K60S2.h"
#define uchar unsigned char
#define uint unsigned int
#define SELE(x) P2 = P2 & 0x1f | x << 5; P2 = P2 & 0x1f
code enum{LED = 4, EXT, SEL, CODE};
typedef struct pwm
{
uint cnt;
uint max;
uint highCnt;
}pwm_t;
typedef struct Delay
{
uint max;
uint cnt;
uchar ok;
}t_delay;
pwm_t pwm1 = {0, 20, 2};
t_delay delay5000 = {5000, 0, 0};
void SL(uchar _dev, uchar _data)
{
P0 = _data;
SELE(_dev);
}
void TimeRun(t_delay* _time)
{
if(_time->cnt++ < _time->max);
else
{
_time->cnt = 0;
_time->ok = 1;
}
}
void Timer0Handle() interrupt 1
{
//如果cnt為0表示週期開始,那麼週期開始的時候是亮燈(也即低電平亮)
if(pwm1.cnt == 0)
{
SL(LED, 0);
}
//如果cnt和highCnt相等,表示高電平的時間已經到了,因此這裡要滅燈(也即高電平滅)
else if(pwm1.cnt == pwm1.highCnt)
{
SL(LED, 0xff);
}
//讓cnt變數在0-max之間不斷執行
if(pwm1.cnt++ < pwm1.max);
else
{
pwm1.cnt = 0;
}
TimeRun(&delay5000);
}
void Timer0Init(void) //1微秒@12.000MHz
{
AUXR |= 0x80; //定時器時鐘1T模式
TMOD &= 0xF0; //設定定時器模式
TL0 = 0xF4; //設定定時初值
TH0 = 0xFF; //設定定時初值
TF0 = 0; //清除TF0標誌
TR0 = 1; //定時器0開始計時
ET0 = 1;
}
void huxi()
{
//每5ms讓佔空比加
if(delay5000.ok)
{
delay5000.ok = 0;
pwm1.highCnt = (pwm1.highCnt + 1)%pwm1.max;
}
}
void main()
{
SL(LED, 0x0);
Timer0Init();
EA = 1;
while(1)
{
huxi();
}
}
#include "STC15F2K60S2.h"
#define uchar unsigned char
#define uint unsigned int
#define SELE(x) P2 = P2 & 0x1f | x << 5; P2 = P2 & 0x1f
code enum{LED = 4, EXT, SEL, CODE};
uint pwm_cnt = 0;
uint pwm_max = 105;
uint pwm_value[] = {5, 10, 15, 20, 30, 40, 50, 105};
uchar ledValue = 0x00;
void SL(uchar _dev, uchar _data)
{
P0 = _data;
SELE(_dev);
}
void Timer0Handle() interrupt 1
{
//如果cnt為0表示週期開始,那麼週期開始的時候是亮燈(也即低電平亮)
//因為都是佔空比的不同,但是起始的時候都是高電平,不同在於低電平來到的時間
uchar i = 0;
if(pwm_cnt == 0)
{
SL(LED, 0);
}
//讓cnt變數在0-max之間不斷執行
if(pwm_cnt++ < pwm_max);
else
{
//結束週期,ledValue全部亮起
ledValue = 0x00;
pwm_cnt = 0;
}
//遍歷8個燈的情況
for(i=0; i<8; i++)
{
if(pwm_cnt == pwm_value[i])
{
//如果cnt等於對應燈的佔空比了,那麼這個燈就應該被關閉了
ledValue |= (1 << i);
SL(LED, ledValue);
}
}
}
void Timer0Init(void) //1微秒@12.000MHz
{
AUXR |= 0x80; //定時器時鐘1T模式
TMOD &= 0xF0; //設定定時器模式
TL0 = 0xF4; //設定定時初值
TH0 = 0xFF; //設定定時初值
TF0 = 0; //清除TF0標誌
TR0 = 1; //定時器0開始計時
ET0 = 1;
}
void main()
{
SL(LED, 0x0);
Timer0Init();
EA = 1;
while(1)
{
}
}
#include "STC15F2K60S2.h"
#define uchar unsigned char
#define uint unsigned int
#define SELE(x) P2 = P2 & 0x1f | x << 5; P2 = P2 & 0x1f
code enum{LED = 4, EXT, SEL, CODE};
uint pwm_cnt = 0;
uint pwm_max = 105;
uint pwm_value[] = {3, 20, 30, 50, 60, 90, 100, 105};
uchar ledValue = 0x00;
uchar maskValue = 0xff;
typedef struct Delay
{
unsigned long max;
unsigned long cnt;
uchar ok;
}t_delay;
t_delay delay1s = {10000, 0, 0};
void SL(uchar _dev, uchar _data)
{
P0 = _data;
SELE(_dev);
}
void TimeRun(t_delay* _time)
{
if(_time->cnt++ < _time->max);
else
{
_time->cnt = 0;
_time->ok = 1;
}
}
void Timer0Handle() interrupt 1
{
//如果cnt為0表示週期開始,那麼週期開始的時候是亮燈(也即低電平亮)
//因為都是佔空比的不同,但是起始的時候都是高電平,不同在於低電平來到的時間
uchar i = 0;
if(pwm_cnt == 0)
{
SL(LED, ledValue);
}
//讓cnt變數在0-max之間不斷執行
if(pwm_cnt++ < pwm_max);
else
{
//結束週期,注意:(如果要外部控制每個LED,那麼這裡就不是全部亮起了!)
ledValue = maskValue;
pwm_cnt = 0;
}
//遍歷8個燈的情況
for(i=0; i<8; i++)
{
if(pwm_cnt == pwm_value[i])
{
//如果cnt等於對應燈的佔空比了,那麼這個燈就應該被關閉了
ledValue |= (1 << i);
SL(LED, ledValue);
}
}
TimeRun(&delay1s);
}
void liushui()
{
if(delay1s.ok)
{
static uchar i;
delay1s.ok = 0;
if(i++ < 8);
else
{
i = 0;
}
maskValue = ~(1 << i);
}
}
void Timer0Init(void) //1微秒@12.000MHz
{
AUXR |= 0x80; //定時器時鐘1T模式
TMOD &= 0xF0; //設定定時器模式
TL0 = 0xF4; //設定定時初值
TH0 = 0xFF; //設定定時初值
TF0 = 0; //清除TF0標誌
TR0 = 1; //定時器0開始計時
ET0 = 1;
}
void main()
{
SL(LED, 0x0);
Timer0Init();
EA = 1;
while(1)
{
liushui();
}
}