簡介 學習嵌入式第一個例子通常都是控制一個 LED 亮滅,然后是花樣繁多的流水燈,但不管燈的花樣如何變化,單個 LED 的亮度沒有變化,只有亮、滅兩個狀態,本章我們實現如何控制 LED 的亮度。 1 什么是 PWM脈沖寬度調制(Pulse Width Modulation,簡稱 PWM),是利用微處理器的數字輸出來對模擬電路進行控制的一種技術。在本章的應用中可以認為 PWM 就是一種方波。比如圖 1: file:///C:\Users\楊磊\AppData\Local\Temp\ksohtml\wpsCB4D.tmp.jpg 圖 1 方 波是周期為 10ms,占空比為 60%的 PWM。 占空比:高電平在一個周期之內所占的時間比率。 2 硬件設計在例說 51 單片機的第三章,我們講過如何控制開發板上 LED 的亮滅。首先譯碼器輸出端 LEDS6 為低,T10 導通,給 8 個 LED 供電,然后通過緩沖器 8 個輸出端 BD0~BD7 的控制 LED 的亮滅(低亮高滅)。 file:///C:\Users\楊磊\AppData\Local\Temp\ksohtml\wpsCB6D.tmp.png 圖 2 LED 硬件連接
如果 BD 口輸出高低不斷變化,則 LED 會閃爍;如果這種高低電平變化非常快,由于人的視覺暫留現象,LED 就會出現不同的亮度。 3 軟件設計3.1 PWM 能否控制亮度 下面我們就用實踐驗證 PWM 是否能夠控制 LED 的亮度,測試代碼如下: 程序清單 L1: 驗證 PWM 能否控制 LED 的亮度#include <reg52.h> #include "my_type.h" #include "hw_config.h" void main(void) { u8 i = 0; //使能獨立 LED 的供電,即 LEDS6 輸出低電平LEDEN = 0; ADDR0 = 0; ADDR1 = 1; ADDR2 = 1; ADDR3 = 1; //第一個 LED 亮P0 = 0xFE; while(1) { for(i=0; i<250; i++) //第 22 行 { if(i<10) { P0 &= 0xFD; //第二個燈亮 } else
{ P0 |= 0x02; //第二個燈滅 } } //第 32 行 } } L1(22-32) :這段代碼實現 P0.1 輸出占空比為 96%的方波,而 P0.0 恒為低。 P0.1 輸出如圖 3 所示(受紙張限制,圖中高低電平長度比例和實際有偏差)。 file:///C:\Users\楊磊\AppData\Local\Temp\ksohtml\wpsCB6E.tmp.jpg 圖 3 下載驗證:從開發板上可以看到運行效果,D1 比 D2 亮。(這里說明一點:當 P0 輸出低電平時, LED 亮,所以,PWM 的占空比越小越亮)。 3.2 產生 8 個亮度級別3.1 節的例子證實了我們的設想,PWM 可控制 LED 的亮度,下面我們設計幾組占空比不同的 PWM,看看對 LED 亮度的控制效果。代碼如下: 程序清單 L2:不同占空比對 LED 亮度的控制#include <reg52.h> #include "hw_config.h" #include "my_type.h" //亮度級別表 code u8 LightLevel[8]={0,1,2,4,8,16,32,64}; void main(void) { u8 i = 0; u8 j = 0; u8 k = 0; u8 temp = 0;
//使能獨立 LED 的供電,即 LEDS6 輸出低電平LEDEN = 0; ADDR0 = 0; ADDR1 = 1; ADDR2 = 1; ADDR3 = 1; //開始全滅P0 = 0xFF; while(1) { //P0 端口輸出 8 組占空比不同的 PWM for(i=0; i<64; i++) //第 29 行 { for( j=0; j<8; j++) { if(LightLevel[ j] <= i) {
} else { } } temp |= (1<<j); temp &= ~(1<<j);
P0 = temp; } } //第 45 行 } L2(29-45).此段程序是讓 P0 口輸出 8 組占空比不同的 PWM,如圖 4:
file:///C:\Users\楊磊\AppData\Local\Temp\ksohtml\wpsCB8F.tmp.jpg 圖 4 下載驗證:從開發板上可以看到運行效果,從 D1 到 D8 的亮度逐漸增大。 3.3 水滴下落效果根據 PWM 可控制 LED 亮度的原理,我們用 8 個 LED 實現水滴下落的效果。第一步,水滴逐漸變大,用 D1 從暗變亮模擬;第二步,水滴下落,帶有拖尾效果,LED 逐個亮,移動速度加快,且越靠前的 LED 亮度越大。 程序清單 L3 水滴流水燈#include <reg52.h> #include "hw_config.h" #include "my_type.h" //亮度級別表 code u8 LightLevel[8]={0,1,2,4,8,16,32,64}; //水滴時間,實現加速效果 code u8 LightTime[16]={16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}; void main(void) { u8 i,j,k;
u8 temp,count; u8 state; //使能獨立 LED 的供電,即 LEDS6 輸出低電平LEDEN = 0; ADDR0 = 0; ADDR1 = 1; ADDR2 = 1; ADDR3 = 1; while(1) { //開始全滅P0 = 0xFF; //---------------水滴逐漸變大(第一個 LED 亮度逐漸變大)--------- for(i=0; i<64; i++) { //一個亮度級別發送 64 個脈沖for( j=0; j<64; j++) { P0 = 0xFE; //以 i 為亮度級別,隨著 i 的增大,占空比增大for(k=0; k<64; k++) { if(k > i) { P0 = 0xFF; } } } } //----------------------水滴降落過程--------------------- for(state=0; state<16; state++) {
//每一狀態維持 LightTime[state]個脈沖for(count=0; count<=LightTime[state]; count++) { //temp 記錄 8 個 LED 的狀態,0 代表亮,1 代表滅temp = 0x00; //一個脈沖長度 j 從 0 到 63 for( j=0; j<64; j++) { //根據亮度表,依次確定 8 個 LED 當前狀態,亮或滅for(k=0; k<8; k++) { //以 j 為亮度級別,每個 LED 亮度不一樣 if(LightLevel[k] == j) { temp |= (1 << k); } } if(state <= 7) {
} else { } } } file:///C:\Users\楊磊\AppData\Local\Temp\ksohtml\wpsCB90.tmp.png} } } P0 = ~((~temp) >> (7-state)); P0 = ~((~temp) << (state-7));
L2(31-46).實現水滴變大效果,這段代碼的作用可用圖形表達,如圖 5:
file:///C:\Users\楊磊\AppData\Local\Temp\ksohtml\wpsCB91.tmp.jpg 圖 5 控制 D1 由暗變亮,用了 64 個亮度級別,每個級別發送 64 個脈沖。 L2(49-81).實現水滴下落。代碼就不逐行解釋了,大家可根據注釋自己分析,主要說一下實現的方法。 定義 LED 有 8 個亮度級別,若用開發板上的 8 個 LED 表示,如圖 6: file:///C:\Users\楊磊\AppData\Local\Temp\ksohtml\wpsCB92.tmp.png 圖 6 圖中的紅色面積代表亮度程度。實現流水效果的方法就是:讓所有的亮度依次經過在所有 LED, 如圖 7:
file:///C:\Users\楊磊\AppData\Local\Temp\ksohtml\wpsCB93.tmp.png 圖 7 狀態的持續時間從 0-15 逐漸減小,以模擬水滴加速。下載驗證:下載到開發板上,可以看到水滴下落效果。 3.4 定時器產生 PWM前面 3 個例子中,我們用循環語句雖然能產生占空比不同的 PWM,但 PWM 的周期不好控制,對此,我們學習如何用定時器產生特定周期 PWM。關于 8051 定時器的使用方法,大家可以參考例說 51 單片機的 4 章和 5 章。 我們用定時器 0 產生 PWM,代碼如下: 程序清單 L4 定時器 0 產生 PWM#include <reg52.h> #include "hw_config.h" #include "my_type.h"
//亮度級別表 code u8 LightLevel[8]={1,2,4,8,16,28,50,64}; //函數聲明 void timer0_init(void); void main(void) { //使能獨立 LED 的供電,即 LEDS6 輸出低電平LEDEN = 0; ADDR0 = 0; ADDR1 = 1; ADDR2 = 1; ADDR3 = 1; timer0_init(); while(1) { } } /********************************************************** 函數名稱:timer0_init 功 能:初始化定時器 0 **********************************************************/ void timer0_init(void) { TMOD = 0x01; //運行模式 1 TH0 = 0xFF; //10us 中斷TL0 = 0xFA; EA = 1; //開啟中斷ET0 = 1; TR0 = 1; //啟動定時器
} /************************************************************ 函數名稱:timer0_overflow 功 能:定時器 0 溢出中斷 ************************************************************/ void timer0_overflow(void) interrupt TIMER0_OVERFLOW { u8 i,temp = 0; static u8 count = 0; count++; count %= 64; for(i=0; i<8; i++) { if(LightLevel <= count) {
} else { } } temp |= (1<<i); temp &= ~(1<<i);
P0 = temp; TR0 = 0; TH0 = 0xFF; //重新賦值TL0 = 0xF7; TR0 = 1; } L4(32).初始化定時器 0,沒 10us 產生一次中斷。 L4(55-65).控制輸出 8 組不同占空比的 PWM。這段代碼功能和程序清單 2 中的功 能一致。
下載驗證:下載到開發板上,可以看到 D1 到 D8 亮度逐漸增大。 3.5 亮度不同的點陣學習了用定時器產生 PWM,我們可以控制更多的 LED,比如 LED 點陣的亮度。下面的例子實現 LED 點陣每行的亮度都不同。 程序清單 5 亮度不同的點陣 #include <reg52.h> #include "hw_config.h" #include "my_type.h" //亮度級別表 code u8 LightLevel[8]={1,2,4,8,16,32,50,64}; //函數聲明 void timer0_init(void); void main(void) { //使能控制點陣的譯碼器 LEDEN = 0; ADDR3 = 0; timer0_init(); while(1) {} } /***************************************************************** 函數名稱:timer0_init 功 能:初始化定時器 0 *****************************************************************/ void timer0_init(void) { TMOD = 0x01; //運行模式 1 TH0 = 0xFF; //中斷時間 10us
/***************************************************************** 函數名稱:timer0_overflow 功 能:定時器 0 溢出中斷 *****************************************************************/ void timer0_overflow(void) interrupt TIMER0_OVERFLOW { u8 i; u8 p1_value = 0; static u8 state = 0; //點陣狀態(掃描行數) static u8 count = 0; TR0 = 0; count++; if(count == 64) { state++; state %= 8; count = 0; } if(count < LightLevel[state]) {
} else { } P0 = 0x00; P0 = 0xFF;
p1_value = P1 & 0xf8; p1_value |= state; P1 = p1_value; TH0 = 0xFF; //重新賦值TL0 = 0xFA; TR0 = 1; } L5(28).初始化定時器,每 10us 中斷一次。 L5(51-57).每中斷 64 次,點陣掃描移動到下一行,用 state 記錄當前行數。 L5(59-66).掃描每一行輸出的 PWM 都不一樣,使用的方式和處理獨立 LED 一致。L5(68-70).輸出點陣對應的位碼。 下載驗證:下載到開發板上,可以看到運行效果,點陣第一行最暗,越往下越亮。 3.6 點陣模擬音樂頻譜分析效果在很多音樂播放軟件上,都有頻譜分析的圖形,如圖 8: file:///C:\Users\楊磊\AppData\Local\Temp\ksohtml\wpsCB94.tmp.png 圖 8 我們用也可以模擬相似的圖形,代碼如下: 程序清單 6:點陣模擬音樂頻譜分析#include <reg52.h> #include "hw_config.h" #include "my_type.h" // 頻 譜 波 形 表 code u8 Wave[16][8]= { {0xFF,0xFF,0xFF,0xFF,0xFE,0xBB,0xFE,0xAA}, {0xFF,0xFF,0xFF,0xFE,0xFB,0xAE,0xFA,0xAA}, {0xFF,0xFF,0xFF,0xFE,0xEB,0xBE,0xEA,0xAA}, {0xFF,0xFF,0xFE,0xFB,0xAF,0xFE,0xAA,0xAA}, {0xFF,0xFE,0xFB,0xBE,0xEA,0xBA,0xAA,0xAA}, {0xFF,0xFE,0xBB,0xEE,0xBA,0xBA,0xAA,0xAA},
{0xFE,0xBB,0xEE,0xBA,0xAA,0xAA,0xAA,0xAA}, {0xBA,0xEF,0xBE,0xAA,0xAA,0xAA,0xAA,0xAA}, {0xEE,0xBB,0xFE,0xAA,0xAA,0xAA,0xAA,0xAA}, {0xEE,0xBB,0xFE,0xEA,0xAA,0xAA,0xAA,0xAA}, {0xFE,0xEB,0xBE,0xFE,0xAA,0xAA,0xAA,0xAA}, {0xFF,0xEE,0xBB,0xFF,0xAE,0xAA,0xAA,0xAA}, {0xFF,0xFE,0xAF,0xFB,0xEE,0xAA,0xAA,0xAA}, {0xFF,0xFF,0xFE,0xBB,0xEF,0xBA,0xAA,0xAA}, {0xFF,0xFF,0xFF,0xFE,0xAB,0xFF,0xEE,0xAA}, {0xFF,0xFF,0xFF,0xFF,0xFE,0xEB,0xBE,0xAA} }; //亮度級別表 code u8 LightLevel[8]={1,2,4,8,16,32,50,64}; //函數聲明 void timer0_init(void); void main(void) { //使能控制點陣的譯碼器 LEDEN = 0; ADDR3 = 0; timer0_init(); while(1) { } } /***************************************************************** 函數名稱:timer0_init 功 能:初始化定時器 0 *****************************************************************/ void timer0_init(void)
{ TMOD = 0x01; //運行模式 1 TH0 = 0xFF; //10us 中斷TL0 = 0xFA; EA = 1; //開啟中斷ET0 = 1; TR0 = 1; //啟動定時器 } /***************************************************************** 函數名稱:timer0_overflow 功 能:定時器 0 溢出中斷 *****************************************************************/ void timer0_overflow(void) interrupt TIMER0_OVERFLOW { u8 i; u8 p1_value = 0; static u8 state = 0; //點陣狀態(掃描行數) static u8 count = 0; static u8 wave_state = 0; //波形狀態static u16 wave_count = 0; TR0 = 0; //每中斷 1000 次,改變波形狀態wave_count++; if(wave_count == 1000) { wave_count = 0; wave_state++; wave_state %= 16; } //每中斷 64 次,改變掃描的行
count++; if(count == 64) { state++; state %= 8; count = 0; } if(count < LightLevel[state]) {
} else { } P0 = Wave[wave_state][state]; P0 = 0xFF;
//輸出位碼 p1_value = P1 & 0xf8; p1_value |= state; P1 = p1_value; //定時器重新賦值 TH0 = 0xFF; TL0 = 0xF7; TR0 = 1; } L6(6).波形表,共 16 個狀態,每個狀態下有 8 個數據,即一個點陣界面。掃描點 陣時循環發送,實現動態效果。 L6(77-83).每中斷 1000 次,更換一個狀態。 L6(86-101).和 L5 的功能一致,只是點陣段碼輸出的數據變成了波形表。下載驗證:下載程序到開發板,可以看到,點陣顯示的頻譜分析效果。
|