【說在前面的話】
單片機技術是現代工業自動化、電子電氣及物聯網等的一門必不可少的主流技術。隨著人們生活智能化的提高,單片機技術也幾乎融入了我們生活的各個角落,比如智能電飯煲、智能音箱、等等。
由此,《 重學51單片機 》系列文章意在幫助初學者入門單片機技術。我們會從最簡單的點亮一個燈開始,一步一步實現按鍵、lcd1602、ds18b20、ds1302、雙機通信等模塊,同時,還會講一些硬件通信協議,比如uart、IIC、SPI等。并結合C語言的編程技巧,以實際的工程項目來給大家講解編程思路,讓大家靈活運用C語言的指針與結構體,實現編程模塊化。
言歸正傳,接下來我們就開始進入今天的主題,用51單片機控制LED實現呼吸燈的效果。
【呼吸燈原理】
我們先看一下呼吸燈的效果下
呼吸燈就是先漸漸變亮再漸漸變暗,如此循環就像呼吸一樣。可是單片機的管腳要么輸出1(亮)要么輸出0(滅),怎么會有漸變的效果呢?
這就和我們的眼睛 觀看圖像會有滯留時間引起的 。當我們在看東西時,眼睛成像后會滯留0.04s(這個標準是網上找的)。
我們按照0.04s計算,就等于40ms,也就是亮滅都是20ms時,看到的LED就是一直在亮。如下圖:
那LED 20ms亮20ms滅和一直常亮的效果一樣嗎?
哈哈,肯定是不一樣的。交替20ms亮20ms滅我們看到的效果要比一直常亮的效果暗。我們假設一直常亮的亮度為100%,那么交替20ms亮20ms滅的亮度就是50%,基于此, 我們就可以調節LED的亮度了 。如下圖
到此,我們就可以調節LED燈的亮度了(就是40ms內設置高電平的持續時間),這個就是大名鼎鼎的PWM調節亮度的原理了,而設置高電平的持續時間就是調節 占空比 (即高電平的時間除以周期數:20/40=50%)。
這里,我們 最重要的還是這個占空比, 比如周期是20ms,交替10ms亮10ms滅,我們看到的亮度還是50%(即占空比為10/20=50%)
接下來我們就看看程序怎么實現吧。
【程序實現】
點亮一個LED
首先,我們先從點亮一個LED燈開始,然后再一步一步實現一個呼吸燈的效果。我們使用的硬件如下:
開發板 | 零壹單片機培訓開發板 |
---|---|
單片機型號 | STC89C52 |
LED接口 | P4^4腳 |
由原理圖我們知道,LED燈接到了單片機的P4^4腳,單片機輸出1,LED亮,輸出0,LED滅。由此,點亮一個LED的程序就很簡單了,如下
sbit LED1 = P4^4;
void LED_ON(){//LED亮
LED1 = 1;
}
void LED_OFF(){//LED滅
LED1 = 0;
}
點亮LED燈的程序還是很簡單,相信大家都會。
調節LED亮度
接下來我們就實現一個可以調節亮度的函數(即調節占空比),如下
//調節LED亮度
void set_led_luminance(unsigned int pwm_duty_cycle)
{
static unsigned int s_Counter = 0;//計時
//調節占空比
if (pwm_duty_cycle >= s_Counter) {
LED_ON();
} else {
LED_OFF();
}
//計數器開始計時
s_Counter++;
if(s_Counter > 255){
//40ms時間到,清零重新計時
s_Counter = 0;
}
}
void main(){
while(1){
set_led_luminance(128);
}
}
- 我們定義了一個靜態變量s_Counter作為 軟件定時器 ,s_Counter加到255后清零(這里相當于是一個周期的時間40ms,當然不是嚴格的40ms,只要周期小于40ms我們就看不到閃爍)
- 函數的參數就是我們要調節的占空比,比如傳入的是128,占空比為128/255=50%
現在有了設置LED亮度的函數,那怎么讓它漸漸變亮再漸漸變暗,實現呼吸燈的效果呢?
實現呼吸燈
這個也很簡單,我們只要給set_led_luminance函數傳的參數從0慢慢加到255然后再從255慢慢減到0就可以了,如下
void breath_led(void){
static int duty_cycle = 0;
static char flag = 1;
//設置亮度
set_led_luminance(duty_cycle);
if(flag == 1){//占空比增加
duty_cycle++;
if(duty_cycle > 255){//大于255開始減少
duty_cycle = 255;
flag = 0;
}
}else{//占空比減少
duty_cycle--;
if(duty_cycle < 0){//小于0開始增加
duty_cycle = 0;
flag = 1;
}
}
}
void main(){
while(1){
breath_led();
}
}
- 定義一個靜態變量duty_cycle來保存占空比,當flag為1時,占空比慢慢增加到255,然后把flag設置成0 ,duty_cycle再從255慢慢減為0,如此循環就可以了。
哈哈,到這里你是不是覺得呼吸燈已經實現了, 然而,并不是 。不相信的同學可以自己試試上面的代碼哦。
那到底是哪里有問題呢?
這就是我們直接調用設置亮度的函數set_led_luminance()是有問題的,因為此函數執行一個周期需要40ms,也就是在這40ms期間是不可以改變占空比的,否則可就調不了亮度了。再來看看breath_led函數,每次調用完set_led_luminance()函數設置占空比后就立即改變了duty_cycle的值,并沒有延時40ms。
此時,我們還需要再加一個軟件定時器,定時超過40ms后修改duty_cycle的值,修改后的程序如下
void breath_led(void){
static unsigned int s_breathCounter = 0;
static int duty_cycle = 0;
static char flag = 1;
//設置亮度
set_led_luminance(duty_cycle);
//計時器增加
s_breathCounter++;
//超過40ms
if(s_breathCounter > 511){
//計時器設置為0,重新計時
s_Counter = 0;
if(flag == 1){//占空比增加
duty_cycle++;
if(duty_cycle > 255){//大于255開始減少
duty_cycle = 255;
flag = 0;
}
}else{//占空比減少
duty_cycle--;
if(duty_cycle < 0){//小于0開始增加
duty_cycle = 0;
flag = 1;
}
}
}
}
- 注意: 我們定時器的時間要大于40ms(也就是s_breathCounter的值要大于255)就可以, 但最好是周期的倍數 ,比如我們的周期為255(即0 ~ 255共256個數),我們設置成它的兩倍即256*2-1=511(即0~511共512個數)。
到這里,我們的呼吸燈就制作好了,是不是很簡單(* ̄︶ ̄)
接下來就是今天的彩蛋環節
雖然實現了呼吸燈的效果,但是代碼還是不夠簡潔和優雅,用了一堆if和else,我們想辦法再簡化一下。
首先化簡set_led_luminance()函數中的這一部分,如下圖所示
化簡之前,先講一個C語言的小知識,就是 按位與運算 。
1 & 0 = 0;
1 & 1 = 1;
0 & 0 = 0;
0 & 1 = 0;
由此,我們知道:不論是1還是0,和0做與運算都為0,
不論是1還是0,和1做與運算還是它本身 ,
為了方便計算,我們采用16進制數(即前面加0x),比如0xff對應的十進制就是255;那么
一個小于等于0xff的數和0xff做與運算,結果還是它本身 ,如下
0x03 & 0xff = 0x03;
0xff & 0xff = 0xff;
那一個大于0xff的數和0xff做與運算呢?
0x100 & 0xff = 0x00;
0x1ff & 0xff = 0xff;
- 結果就是這個數除以(0xff+1)的余數(即結果還是在0~0xff之間)
有了這個與運算,上面的代碼就可以化簡為
s_Counter++;
s_Counter = s_Counter & 0xff;
- 這樣s_Counter的取值范圍就一直在0x00~0xff了
同樣的,上面breath_led函數中的軟件定時器也可以化簡一下,如下
//計時器增加
s_breathCounter++;
if (!(s_breathCounter & (0x1ff))) {
duty_cycle++;
duty_cycle &= 0x1ff;
}
if(duty_cycle > (0xff)){
set_led_luminance(duty_cycle - (0xff));
}else{
set_led_luminance( (0xff) - duty_cycle );
}
-
第3行,0x1ff就是十進制的511,當s_breathCounter的值為(0x1ff+1)即512時條件成立,因為512&0x1ff=0,前面還有個感嘆號就是取非運算(準確的說就是s_breathCounter的值為512的倍數時條件成立,這樣都省去了s_breathCounter清零的操作了,不知道給大家解釋清楚了沒,大家要多想想),條件成立則占空比開始增加或減少
-
那占空比的范圍不是0~ 255嗎,第5行怎么也變成0x1ff(511)了,別急,你再往下看第8行,我們又減去了0xff,所以占空比的范圍還是0~255
-
第7~ 10行代碼的意思為:當duty_cycle > (0xff)時,即256~511,減去0xff,就相當于從1增加到255,亮度漸漸變亮
當duty_cycle <= (0xff)時,即duty_cycle 從0增加到255,而設置的亮度為255 - duty_cycle ,相當于從255降到0,亮度 漸漸變暗 ,這樣就實現了呼吸燈的效果。
哈哈,你以為我們的簡化就結束了嗎?
no,no,no
其實第7到10行還可以更簡單一些。此時就需要用到取絕對值的函數了。
什么,用絕對值干嘛?
你看第10行0xff- duty_cycle就相當于duty_cycle - 0xff然后再取絕對值,好了,化簡后的代碼如下
set_led_luminance(ABS(duty_cycle - (0xff)));
取絕對值的宏函數如下
#define ABS(N) ((N) < 0 ? -(N) : (N))
最后我把整個化簡后的代碼也貼到下面
#define ABS(N) ((N) < 0 ? -(N) : (N))
static void set_led_luminance(unsigned int pwm_duty_cycle)
{
static unsigned int s_Counter = 0;
if (pwm_duty_cycle >= s_Counter) {
LED1 = 1;
} else {
LED1 = 0;
}
s_Counter++;
s_Counter = s_Counter & 0xff;
}
void breath_led(void){
static unsigned int s_breathCounter = 0;
static int duty_cycle = 0;
s_breathCounter++;
if (!(s_breathCounter & (0x1ff))) {
duty_cycle++;
duty_cycle &= 0x1ff;
}
set_led_luminance(ABS(duty_cycle - (0xff)));
}
void main(){
while(1){
breath_led();
}
}
怎么樣,是不是很簡潔(* ̄︶ ̄)
好了,到這里今天的呼吸燈就講完了,想和我一起重學51單片機的同學記得 點贊+關注, 這會加速我對文章更新的速度哦
-
led
+關注
關注
242文章
23253瀏覽量
660598 -
單片機
+關注
關注
6035文章
44554瀏覽量
634693 -
51單片機
+關注
關注
274文章
5702瀏覽量
123498 -
C語言
+關注
關注
180文章
7604瀏覽量
136704 -
呼吸燈
+關注
關注
10文章
110瀏覽量
42723
發布評論請先 登錄
相關推薦
評論