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