延時的概念
前面的程序中我們只是點一下燈,熄一下燈,或者點亮另幾個燈,再熄滅,有這編程的功夫還不如裝個開關吧。你還想咋地?“寶寶想看那種五顏六色還能一閃一閃亮晶晶的”。如果你的女朋友(或男朋友)看到你學單片機只會點燈時這么對你說,你該咋辦?毫無疑問,咱無論如何也要滿足她(他)呀,還不止只給她看一閃一閃亮晶晶呢,你應該要給她看星星,看月亮,看整個宇宙的。畢竟現在這社會競爭這么大,你不給她,另一個朋友能給她,到時你該怎么辦?都到談戀愛這份上了,為了這點小事難道你要“善罷甘休”,真不想以后交換戒指的是你和她?
想清楚了吧,為了未來的那一大幕,我們現在是不是要先來研究研究LED怎么一閃一閃這一小步呢?
我們要讓單片機控制的LED一閃一閃要怎么做呢,是不是讓它亮一會再滅一會?那要怎么讓它亮了再滅呢?難道寫一個點亮程序,再寫一個熄滅程序不停交替下載這兩程序?要是這樣,你女朋友早跟樓下小王跑了。
前面的內容中我們說了數字芯片大多都是需要時序來控制的吧,單片機是數字芯片的集合體,它的時鐘源就是我們上節課說的晶振電路,晶振電路產生的時鐘信號輸入到其內部,然后其內部電路就根據這個信號按照一定的規律進行運行,如果給到的程序有什么功能它內部就隨著這個時鐘一步一步實現我們的功能,如果我們給了一個"空白"程序(空白不是什么都沒有,用無意義來形容可能更貼切,就是不處理正經事單純消耗系統時鐘的命令,實現這一點的原理是因為單片機中任何命令執行都會消耗時間,類比人休息但時間也在流失道理一樣),那它內部在這一段時鐘信號內就保持原樣什么都不變動。就像我們人一樣,當我們在走路時,大腦控制我們的神經,帶動肌肉往復運動,我們休息時就讓肌肉放松下來。那我們要怎么控制芯片讓它暫停一下呢,看完上面內容是不是就有答案了?給它一段“空白"命令,不行就2段,或者N段。
那在程序中要怎么實現讓單片機執行一段空白命令呢?
前面接收C語言基礎時我們是不是講到了循環語句?我們在循環語句中不執行任何功能,這樣就可以解決這個問題了吧。一個“暫停”功能的程序在腦海中應該有了吧。這種情況我們一般稱為延時,和暫停是一個意思,但不這不是意味著單片機就真的是停下來的吧,就像我們休息的時候心跳也要照樣跳動一樣,“暫停”都是假象。
下面來看一下我們平時常用的延時代碼:
while(n<100)
{
n--;
}
我們也可以改寫成for循環的方式吧。
for(n=0;n<100;n++)
{
;
}
當然,以上程序還可以進行嵌套,如果你需要設置的延時時間很久的話。另外,熟練編程之后我們也可以把大括號對{}省略不寫,至于為什么?自己思考一下。
這里我們在“暫停”時用一個變量n來控制“空白”命令的次數,如果短了就把n加大,如果長了就減小。但到底多少合適呢,也就是我們要怎樣實現可控的延時時間呢,這里我們需要來了解幾個關于51單片機的新概念,即時鐘周期,機器周期,指令周期。
時鐘周期就是指晶振振蕩的周期,它是晶振頻率的倒數,我們仿真電路中使用的是12MHz的晶振那它就是(1/12M)秒。
機器周期在51單片機中機器周期就是12個時鐘周期,即(1/1M)秒,也就是1微秒(us)。
指令周期就是單片機執行一條指令的時間,它由若干個機器周期組成,在單片機中一些簡單指令是1個機器周期,一部分復雜指令需要2個或多個機器周期。
當然我們項目開發時這些內容需要參考芯片數據手冊,因為這不是固定的定義,現在很多增強型51單片機的時鐘都不是這樣的了,其他更高級的芯片就不用說了。
那我們到底要怎么確定我們延時程序中的n,能延時多久呢?我們可以通過程序的調試功能進行仿真確定(畢竟是仿真與實際會有一些出入)。最死板的辦法是我們可以把程序下載到單片機中進行測試分析呀,用示波器測量單片機引腳電平變化周期就不出來了,然后大概記住一些典型時間,如0.1s,0.5s,1s等延時n的值,以后直接使用也可以。但我們肯定還有更簡單又準確的延時辦法,因為設計到定時器的使用,所以今天就先不講解,后面學習定時器是在做具體介紹。
寫了這么多內容,終于把基礎寫完了,老司機們是不是早就迫不及待的發車了?那我們先上車開起來吧
閃燈程序
看了以上原理,現在我們想來實現讓一個燈閃起來吧。我們來看看程序內容,跟著注釋看一遍是不是就很明了了。
#include //這是52單片機的頭文件
#include //這也是編譯器的一個頭文件后面會用到
typedef unsigned char u8; //typedef 是別名關鍵字
typedef unsigned int u16;
void main()
{
u8 i,j; //定義變量
P1 = 0xfe;//點亮第一個燈
for(i=0;i< 200;i++) //第一段延時
{
for(j=0;j< 200;j++)
{
;
}
}
P1 = 0xff;//熄滅第一個燈
for(i=0;i< 200;i++)//第二段延時
{
for(j=0;j< 200;j++)
{
;
}
}
}
這個代碼里我新引入一個頭文件#include ,它也是編譯軟件自帶的一個頭文件,里面包含對寄存器的循環操作接下來的程序中我們會使用到。
typedef這個關鍵字就是取別名的意思,就是把一長串名稱給一個簡短的名字,編程時可以巧用它來簡化某些程序。
使用上一節內容中對P1端口整體賦值的方式進行點燈。
點完燈后延時一會,接下來講燈熄滅,熄滅后再進行一個延時,這樣就實現了我們的閃燈效果。
創建函數,做配件
如果我們把延時程序打包成一個函數以后就可以重復使用了,這是不是爽歪歪?
再來看看修改好的代碼:
#include //這是52單片機的頭文件
#include //這也是編譯器的一個頭文件
typedef unsigned char u8;
typedef unsigned int u16;
//void delay();//聲明函數
void delay(u8 ms);
void main()
{
P1 = 0xfe;//將LED1點亮
delay(50);//調用函數
P1 = 0xff; //將LED1關閉
delay(50);
}
void delay(u8 ms) //定義函數
{
u8 i,j;//定義這個延時函數中的i,j變量
for(i=0;i< ms;i++)
{
for(j=0;j< 200;j++)
{
;
}
}
}
還是同樣的意思,這么一對比是不是可以看出程序有層次感就出來了,代碼瞬間就好看了很多吧。當然把延時函數打包成void delay()還是void delay(u8 ms)就看個人了,要想使用更靈活那當然是后者咯。
循環流水燈
只是知道閃燈未免太單調,也打動不了人心吧,接下來我們再整點復雜的,使用循環位移操作實現流水燈變化,現在先來看看程序。
#include
#include
typedef unsigned char u8;
typedef unsigned int u16;
void delay(u8 ms);
void main()
{
u8 rol = 0xfe;
u8 i;
for(i=0;i< 8;i++)
{
//先將P1端口初始化,先設定P1.0點亮
P1 = rol;
//延時一會
delay(100);
//將變量移位
rol = _cror_(rol,1);
//移位完成在下一個循環,rol的值將重新賦值給P1寄存器
}
}
void delay(u8 ms) //定義函數
{
u8 i,j;//定義這個延時函數中的i,j變量
for(i=0;i< ms;i++)
{
for(j=0;j< 200;j++)
{
;
}
}
}
這個程序對著視頻仿真效果一起看是不是還好理解呢?
_cror_函數相當于一個環形位移 11111110 向右移動一位就變成 01111111,其高位是將末位補充過來。
如果換成_iror_結果就不一樣了。
_iror_是單向位移,11111110 向右移動一位變成01111111,其高位是補0。
當然左移就和右移剛好相反了。
_crol_函數是環形移動,11111110 移動一位就變成 11111101,其末尾是將最高位補充過來。
_irol_中11111110 向左移動一位變成11111100,其末尾是補0。
這兩種循環反方式在編程時各有用途,所以要區分得開來,如果覺得不好掌握就多編程嘗試一下。
評論
查看更多