從單片機(jī)初學(xué)者邁向單片機(jī)工程師(對(duì)初學(xué)者非常有用)
從單片機(jī)初學(xué)者邁向單片機(jī)工程師
目錄:
一、LED 主題討論周第一章----寫(xiě)在前面......................................................... 1
二、LED 主題討論周第二章----學(xué)會(huì)釋放CPU................................................. 2
三、LED 主題討論周第三章----模塊化編程初識(shí)..............................................8
四、LED 主題討論周第四章----漸明漸暗的燈................................................25
五、LED 主題討論周第五章----多任務(wù)環(huán)境下的數(shù)碼管編程設(shè)計(jì)................. 28
六、KEY 主題討論第一章——按鍵程序編寫(xiě)的基礎(chǔ)..................................... 37
七、KEY 主題討論第二章——基于狀態(tài)轉(zhuǎn)移的獨(dú)立按鍵程序設(shè)計(jì).............. 40
八、綜合應(yīng)用之一——如何設(shè)計(jì)復(fù)雜的多任務(wù)程序...................................... 47
九、綜合應(yīng)用之二——DS1320/DS18B20 應(yīng)用...............................................60
----------------------------------------------------------------------------------------------------------------------------------------
一、LED 主題討論周第一章----寫(xiě)在前面
學(xué)習(xí)單片機(jī)也已經(jīng)有幾年了,藉此機(jī)會(huì)和大家聊一下我學(xué)習(xí)過(guò)程中的一些經(jīng)歷和想法吧。也感謝一線(xiàn)工人
提供了這個(gè)機(jī)會(huì)。希望大家有什么好的想法和建議都直接跟帖說(shuō)出來(lái)。畢竟只有交流才能夠碰撞出火花來(lái)
^_^。
“賣(mài)弄”也好,“吹噓”也罷,我只是想認(rèn)真的寫(xiě)寫(xiě)我這一路走來(lái)歷經(jīng)的總總,把其中值得注意,以及經(jīng)
驗(yàn)的地方寫(xiě)出來(lái),權(quán)當(dāng)是我對(duì)自己的一個(gè)總結(jié)吧。而作為看官的你,如果看到了我的錯(cuò)誤,還請(qǐng)一定指正,
這樣對(duì)我以及其它讀者都有幫助,而至于你如果從中能夠收獲到些許,那便是我最大的欣慰了。姑妄言之,
姑妄聽(tīng)之。如果有啥好的想法和建議一定要說(shuō)出來(lái)。ϑ幾年前,和眾多初學(xué)者一樣,我接觸到了單片機(jī),立
刻被其神奇的功能所吸引,從此不能自拔。很多個(gè)日夜就這樣陪伴著它度過(guò)了。期間也遇到過(guò)非常多的問(wèn)
題,也一度被這些問(wèn)題所困惑……等到回過(guò)頭來(lái),看到自己曾經(jīng)走過(guò)的路,唏噓不已。經(jīng)常混跡于論壇里,
也看到了很多初學(xué)者發(fā)的求助帖子,看到他們走在自己曾走過(guò)的彎路上,忽然想到了自己的那段日子,心
里竟然莫名的沖動(dòng),凡此總總,我總是盡自己所能去回帖。很多時(shí)候,都想寫(xiě)一點(diǎn)什么東西出來(lái),希望對(duì)
廣大的初學(xué)者有一點(diǎn)點(diǎn)幫助。但總是不知從何處寫(xiě)起。今天借一線(xiàn)工人的臺(tái),唱一唱我的戲
一路學(xué)習(xí)過(guò)來(lái)的過(guò)程中,幫助最大之一無(wú)疑來(lái)自于網(wǎng)絡(luò)了。很多時(shí)候,通過(guò)網(wǎng)絡(luò),我們都可以獲取到所
需要的學(xué)習(xí)資料。但是,隨著我們學(xué)習(xí)的深入,我們會(huì)慢慢發(fā)現(xiàn),網(wǎng)絡(luò)提供的東西是有限度的,好像大部
分的資料都差不多,或者說(shuō)是適合大部分的初學(xué)者所需,而當(dāng)我們想更進(jìn)一步提高時(shí),卻發(fā)現(xiàn)能夠獲取到
的資料越來(lái)越少,相信各位也會(huì)有同感,鋪天蓋地的單片機(jī)資料中大部分不是流水燈就是LED,液晶,而
且也只是僅僅作功能性的演示。于是有些人選擇了放棄,或者是轉(zhuǎn)移到其他興趣上面去了,而只有少部分
人選擇了繼續(xù)摸索下去,結(jié)合市面上的書(shū)籍,然后在網(wǎng)絡(luò)上鍥而不舍的搜集資料,再?gòu)呐H说闹谎云Z(yǔ)中
去體會(huì),不斷動(dòng)手實(shí)踐,慢慢的,也摸索出來(lái)了自己的一條路子。當(dāng)然這個(gè)過(guò)程必然是艱辛的,而他學(xué)會(huì)
了之后也不會(huì)在網(wǎng)絡(luò)上輕易分享自己的學(xué)習(xí)成果。如此惡性循環(huán)下去,也就不難理解為什么初級(jí)的學(xué)習(xí)資
料滿(mǎn)天飛,而深入一點(diǎn)的學(xué)習(xí)資料卻很少的原因了。相較于其他領(lǐng)域,單片機(jī)技術(shù)的封鎖更加容易。盡管
已經(jīng)問(wèn)世了很多年了,有價(jià)值的資料還是相當(dāng)?shù)那啡保蟛糠值馁Y料都是止于入門(mén)階段或者是簡(jiǎn)單的演示
實(shí)驗(yàn)。但是在實(shí)際工程應(yīng)用中卻是另外一回事。有能力的高手無(wú)暇或者是不愿公開(kāi)自己的學(xué)習(xí)經(jīng)驗(yàn)。
很多時(shí)候,我也很困惑,看到國(guó)外愛(ài)好者毫不保留的在網(wǎng)絡(luò)上發(fā)布自己的作品,我忽然感覺(jué)到一絲絲的
悲哀。也許,我們真的該轉(zhuǎn)變一下思路了,幫助別人,其實(shí)也是在幫助自己。啰啰嗦嗦的說(shuō)了這么多,相
信大家能夠明白說(shuō)的是什么意思。在接下來(lái)的一段日子里,我將會(huì)結(jié)合電子工程師之家舉辦的主題周活動(dòng)
寫(xiě)一點(diǎn)自己的想法。盡可能從實(shí)用的角度去講述。希望能夠幫助更多的初學(xué)者更上一層樓。而關(guān)于這個(gè)主
題周的最大主題我想了這樣的一個(gè)名字“從單片機(jī)初學(xué)者邁向單片機(jī)工程師”。名字挺大挺響亮,給我的壓
力也挺大的,但我會(huì)努力,爭(zhēng)取使這樣的一系列文章能夠帶給大家一點(diǎn)幫助,而不是看后大跌眼鏡。這樣
的一系列文章主要的對(duì)象是初學(xué)者,以及想從初學(xué)者更進(jìn)一步提高的讀者。而至于老手,以及那些牛XX
的人,希望能夠給我們這些初學(xué)者更多的一些指點(diǎn)哈~@_@~.
二、LED 主題討論周第二章----學(xué)會(huì)釋放CPU
從這一章開(kāi)始,我們開(kāi)始邁入單片機(jī)的世界。在我們開(kāi)始這一章具體的學(xué)習(xí)之前,有必要給大家先說(shuō)明一
下。在以后的系列文章中,我們將以51 內(nèi)核的單片機(jī)為載體,C 語(yǔ)言為編程語(yǔ)言,開(kāi)發(fā)環(huán)境為KEIL uv3。
至于為什么選用C 語(yǔ)言開(kāi)發(fā),好處不言而喻,開(kāi)發(fā)速度快,效率高,代碼可復(fù)用率高,結(jié)構(gòu)清晰,尤其是
在大型的程序中,而且隨著編譯器的不斷升級(jí),其編譯后的代碼大小與匯編語(yǔ)言的差距越來(lái)越小。而關(guān)于
C 語(yǔ)言和匯編之爭(zhēng),就像那個(gè)啥,每隔一段時(shí)間總會(huì)有人挑起這個(gè)話(huà)題,如果你感興趣,可以到網(wǎng)上搜索
相關(guān)的帖子自行閱讀。不是說(shuō)匯編不重要,在很多對(duì)時(shí)序要求非常高的場(chǎng)合,需要利用匯編語(yǔ)言和C 語(yǔ)言
----------------------------------------------------------------------------------------------------------------------------------------
混合編程才能夠滿(mǎn)足系統(tǒng)的需求。在我們學(xué)習(xí)掌握C 語(yǔ)言的同時(shí),也還需要利用閑余的時(shí)間去學(xué)習(xí)了解匯
編語(yǔ)言。
1.從點(diǎn)亮LED(發(fā)光二極管)開(kāi)始
在市面上眾多的單片機(jī)學(xué)習(xí)資料中,最基礎(chǔ)的實(shí)驗(yàn)無(wú)疑于點(diǎn)亮LED 了,即控制單片機(jī)的I/O 的電平的變化。
如同如下實(shí)例代碼一般
void main(void)
{
LedInit() ;
While(1)
{
LED = ON ;
DelayMs(500) ;
LED = OFF ;
DelayMs(500) ;
}
}
程序很簡(jiǎn)單,從它的結(jié)構(gòu)可以看出,LED 先點(diǎn)亮500MS,然后熄滅500MS,如此循環(huán)下去,形成的效
果就是LED 以1HZ 的頻率進(jìn)行閃爍。下面讓我們分析上面的程序有沒(méi)有什么問(wèn)題。
看來(lái)看出,好像很正常的啊,能有什么問(wèn)題呢?這個(gè)時(shí)候我們應(yīng)該換一個(gè)思路去想了。試想,整個(gè)程序除
了控制LED = ON ; LED = OFF; 這兩條語(yǔ)句外,其余的時(shí)間,全消耗在了DelayMs(500)這兩個(gè)函數(shù)
上。而在實(shí)際應(yīng)用系統(tǒng)中是沒(méi)有哪個(gè)系統(tǒng)只閃爍一只LED 就其它什么事情都不做了的。因此,在這里我們
要想辦法,把CPU 解放出來(lái),讓它不要白白浪費(fèi)500MS 的延時(shí)等待時(shí)間。寧可讓它一遍又一遍的掃描看
有哪些任務(wù)需要執(zhí)行,也不要讓它停留在某個(gè)地方空轉(zhuǎn)消耗CPU 時(shí)間。
從上面我們可以總結(jié)出
(1) 無(wú)論什么時(shí)候我們都要以實(shí)際應(yīng)用的角度去考慮程序的編寫(xiě)。
(2) 無(wú)論什么時(shí)候都不要讓CPU 白白浪費(fèi)等待,尤其是延時(shí)(超過(guò)1MS)這樣的地方。
下面讓我們從另外一個(gè)角度來(lái)考慮如何點(diǎn)亮一顆LED。
先看看我們的硬件結(jié)構(gòu)是什么樣子的。
我手上的單片機(jī)板子是電子工程師之家的開(kāi)發(fā)的學(xué)習(xí)板。就以它的實(shí)際硬件連接圖來(lái)分析吧。如下圖所
示
----------------------------------------------------------------------------------------------------------------------------------------
一般的LED 的正常發(fā)光電流為10~20MA 而低電流LED 的工作電流在2mA 以下(亮度與普通發(fā)光管
相同)。在上圖中我們可知,當(dāng)Q1~Q8 引腳上面的電平為低電平時(shí),LED 發(fā)光。通過(guò)LED 的電流約為(VCC
- Vd)/ RA2 。其中Vd 為L(zhǎng)ED 導(dǎo)通后的壓降,約為1.7V 左右。這個(gè)導(dǎo)通壓降根據(jù)LED 顏色的不同,以
及工作電流的大小的不同,會(huì)有一定的差別。下面一些參數(shù)是網(wǎng)上有人測(cè)出來(lái)的,供大家參考。
紅色的壓降為1.82-1.88V,電流5-8mA,
綠色的壓降為1.75-1.82V,電流3-5mA,
橙色的壓降為1.7-1.8V,電流3-5mA
蘭色的壓降為3.1-3.3V,電流8-10mA,
白色的壓降為3-3.2V,電流10-15mA,
(供電電壓5V,LED 直徑為5mm)
74HC573 真值表如下:
通過(guò)這個(gè)真值表我們可以看出。當(dāng)OutputEnable 引腳接低電平的時(shí)候,并且LatchEnable 引腳為高電
平的時(shí)候,Q 端電平與D 端電平相同。結(jié)合我們的LED 硬件連接圖可以知道LED_CS 端為高電平時(shí)候,
----------------------------------------------------------------------------------------------------------------------------------------
P0 口電平的變化即Q 端的電平的變化,進(jìn)而引起LED 的亮滅變化。由于單片機(jī)的驅(qū)動(dòng)能力有限,在此,
74HC573 的主要作用就是起一個(gè)輸出驅(qū)動(dòng)的作用。需要注意的是,通過(guò)74HC573 的最大電流是有限制的,
否則可能會(huì)燒壞74HC573 這個(gè)芯片。
上面這個(gè)圖是從74HC573 的DATASHEET 中截取出來(lái)的,從上可以看出,每個(gè)引腳允許通過(guò)的最大電流
為35mA 整個(gè)芯片允許通過(guò)的最大電流為75mA。在我們?cè)O(shè)計(jì)相應(yīng)的驅(qū)動(dòng)電路時(shí)候,這些參數(shù)是相當(dāng)重要
的,而且是最容易被初學(xué)者所忽略的地方。同時(shí)在設(shè)計(jì)的時(shí)候,要留出一定量的余量出來(lái),不能說(shuō)單個(gè)引
腳允許通過(guò)的電流為35mA,你就設(shè)計(jì)為35mA,這個(gè)時(shí)候你應(yīng)該把設(shè)計(jì)的上限值定在20mA 左右才能保
證能夠穩(wěn)定的工作。
(設(shè)計(jì)相應(yīng)驅(qū)動(dòng)電路時(shí)候,應(yīng)該仔細(xì)閱讀芯片的數(shù)據(jù)手冊(cè),了解每個(gè)引腳的驅(qū)動(dòng)能力,以及整個(gè)芯片的驅(qū)
動(dòng)能力)
了解了相應(yīng)的硬件后,我們?cè)賮?lái)編寫(xiě)驅(qū)動(dòng)程序。
首先定義LED 的接口
#define LED P0
然后為亮滅常數(shù)定義一個(gè)宏,由硬件連接圖可以,當(dāng)P0 輸出為低電平時(shí)候LED 亮,P0 輸出為高
電平時(shí),LED 熄滅。
#define LED_ON() LED = 0x00 ; //所有LED 亮
#define LED_OFF() LED = 0xff ; //所有LED 熄滅
下面到了重點(diǎn)了,究竟該如何釋放CPU,避免其做延時(shí)空等待這樣的事情呢。很簡(jiǎn)單,我們?yōu)橄到y(tǒng)產(chǎn)
生一個(gè)1MS 的時(shí)標(biāo)。假定LED 需要亮500MS,熄滅500MS,那么我們可以對(duì)這個(gè)1MS 的時(shí)標(biāo)進(jìn)行計(jì)數(shù),
當(dāng)這個(gè)計(jì)數(shù)值達(dá)到500 時(shí)候,清零該計(jì)數(shù)值,同時(shí)把LED 的狀態(tài)改變。
unsigned int g_u16LedTimeCount = 0 ; //LED 計(jì)數(shù)器
unsigned char g_u8LedState = 0 ; //LED 狀態(tài)標(biāo)志, 0 表示亮,1 表示熄滅
void LedProcess(void)
{
if(0 == g_u8LedState) //如果LED 的狀態(tài)為亮,則點(diǎn)亮LED
{
LED_ON() ;
}
else //否則熄滅LED
{
LED_OFF() ;
}
}
void LedStateChange(void)
{
----------------------------------------------------------------------------------------------------------------------------------------
if(g_bSystemTime1Ms) //系統(tǒng)1MS 時(shí)標(biāo)到
{
g_bSystemTime1Ms = 0 ;
g_u16LedTimeCount++ ; //LED 計(jì)數(shù)器加一
if(g_u16LedTimeCount >= 500) //計(jì)數(shù)達(dá)到500,即500MS 到了,改變LED 的狀態(tài)。
{
g_u16LedTimeCount = 0 ;
g_u8LedState = ! g_u8LedState ;
}
}
}
上面有一個(gè)變量沒(méi)有提到,就是g_bSystemTime1Ms 。這個(gè)變量可以定義為位變量或者是其它變量,在
我們的定時(shí)器中斷函數(shù)中對(duì)其置位,其它函數(shù)使用該變量后,應(yīng)該對(duì)其復(fù)位(清0) 。
我們的主函數(shù)就可以寫(xiě)成如下形式(示意代碼)
void main(void)
{
while(1)
{
LedProcess() ;
LedStateChange() ;
}
}
因?yàn)長(zhǎng)ED 的亮或者滅依賴(lài)于LED 狀態(tài)變量(g_u8LedState)的改變,而狀態(tài)變量的改變,又依賴(lài)于LED 計(jì)
數(shù)器的計(jì)數(shù)值(g_u16LedTimeCount ,只有計(jì)數(shù)值達(dá)到一定后,狀態(tài)變量才改變)所以,兩個(gè)函數(shù)都沒(méi)有堵
塞CPU 的地方。讓我們來(lái)從頭到尾分析一遍整個(gè)程序的流程。
程序首先執(zhí)行LedProcess() ;函數(shù)
因?yàn)間_u8LedState 的初始值為0 (見(jiàn)定義,對(duì)于全局變量,在定義的時(shí)候最好給其一個(gè)確定的值)所以L(fǎng)ED
被點(diǎn)亮,然后退出LedStateChange()函數(shù),執(zhí)行下一個(gè)函數(shù)LedStateChange()
在函數(shù)LedStateChange()內(nèi)部首先判斷1MS 的系統(tǒng)時(shí)標(biāo)是否到了,如果沒(méi)有到就直接退出函數(shù),如果到
了,就把時(shí)標(biāo)清0 以便下一個(gè)時(shí)標(biāo)消息的到來(lái),同時(shí)對(duì)LED 計(jì)數(shù)器加一,然后再判斷LED 計(jì)數(shù)器是否到
達(dá)我們預(yù)先想要的值500,如果沒(méi)有,則退出函數(shù),如果有,對(duì)計(jì)數(shù)器清0,以便下次重新計(jì)數(shù),同時(shí)把
LED 狀態(tài)變量取反,然后退出函數(shù)。
由上面整個(gè)流程可以知道,CPU 所做的事情,就是對(duì)一些計(jì)數(shù)器加一,然后根據(jù)條件改變狀態(tài),再根據(jù)這
個(gè)狀態(tài)來(lái)決定是否點(diǎn)亮LED。這些函數(shù)執(zhí)行所花的時(shí)間都是相當(dāng)短的,如果主程序中還有其它函數(shù),則
CPU 會(huì)順次往下執(zhí)行下去。對(duì)于其它的函數(shù)(如果有的話(huà))也要采取同樣的措施,保證其不堵塞CPU,如果
全部基于這種方法設(shè)計(jì),那么對(duì)于不是非常龐大的系統(tǒng),我們的系統(tǒng)依舊可以保證多個(gè)任務(wù)(多個(gè)函數(shù))同
時(shí)執(zhí)行。系統(tǒng)的實(shí)時(shí)性得到了一定的保證,從宏觀(guān)上看來(lái),就是多個(gè)任務(wù)并發(fā)執(zhí)行。
好了,這一章就到此為止,讓我們總結(jié)一下,究竟有哪些需要注意的吧。
(1) 無(wú)論什么時(shí)候我們都要以實(shí)際應(yīng)用的角度去考慮程序的編寫(xiě)。
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 7
(2) 無(wú)論什么時(shí)候都不要讓CPU 白白浪費(fèi)等待,尤其是延時(shí)(超過(guò)1MS)這樣的地方。
(3) 設(shè)計(jì)相應(yīng)驅(qū)動(dòng)電路時(shí)候,應(yīng)該仔細(xì)閱讀芯片的數(shù)據(jù)手冊(cè),了解每個(gè)引腳的驅(qū)動(dòng)能力,
以及整個(gè)芯片的驅(qū)動(dòng)能力
(4) 最重要的是,如何去釋放CPU(參考本章的例子),這是寫(xiě)出合格程序的基礎(chǔ)。
附完整程序代碼(基于電子工程師之家的單片機(jī)開(kāi)發(fā)板)
#include
sbit LED_SEG = P1^4; //數(shù)碼管段選
sbit LED_DIG = P1^5; //數(shù)碼管位選
sbit LED_CS11 = P1^6; //led 控制位
sbit ir=P1^7;
#define LED P0 //定義LED 接口
bit g_bSystemTime1Ms = 0 ; // 1MS 系統(tǒng)時(shí)標(biāo)
unsigned int g_u16LedTimeCount = 0 ; //LED 計(jì)數(shù)器
unsigned char g_u8LedState = 0 ; //LED 狀態(tài)標(biāo)志, 0 表示亮,1 表示熄滅
#define LED_ON() LED = 0x00 ; //所有LED 亮
#define LED_OFF() LED = 0xff ; //所有LED 熄滅
void Timer0Init(void)
{
TMOD &= 0xf0 ;
TMOD |= 0x01 ; //定時(shí)器0 工作方式1
TH0 = 0xfc ; //定時(shí)器初始值
TL0 = 0x66 ;
TR0 = 1 ;
ET0 = 1 ;
}
void LedProcess(void)
{
if(0 == g_u8LedState) //如果LED 的狀態(tài)為亮,則點(diǎn)亮LED
{
LED_ON() ;
}
else //否則熄滅LED
{
LED_OFF() ;
}
}
void LedStateChange(void)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 8
{
if(g_bSystemTime1Ms) //系統(tǒng)1MS 時(shí)標(biāo)到
{
g_bSystemTime1Ms = 0 ;
g_u16LedTimeCount++ ; //LED 計(jì)數(shù)器加一
if(g_u16LedTimeCount >= 500) //計(jì)數(shù)達(dá)到500,即500MS 到了,改變LED 的狀態(tài)。
{
g_u16LedTimeCount = 0 ;
g_u8LedState = ! g_u8LedState ;
}
}
}
void main(void)
{
Timer0Init() ;
EA = 1 ;
LED_CS11 = 1 ; //74HC595 輸出允許
LED_SEG = 0 ; //數(shù)碼管段選和位選禁止(因?yàn)樗鼈兒蚅ED 共用P0 口)
LED_DIG = 0 ;
while(1)
{
LedProcess() ;
LedStateChange() ;
}
}
void Time0Isr(void) interrupt 1
{
TH0 = 0xfc ; //定時(shí)器重新賦初值
TL0 = 0x66 ;
g_bSystemTime1Ms = 1 ; //1MS 時(shí)標(biāo)標(biāo)志位置位
}
實(shí)際效果圖如下
點(diǎn)亮
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 9
熄滅
三、LED 主題討論周第三章----模塊化編程初識(shí)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 10
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 11
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 12
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 13
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 14
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 15
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 16
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 17
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 18
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 19
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 20
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 21
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 22
OK ,到此一個(gè)簡(jiǎn)單的工程模板就建立起來(lái)了,以后我們?cè)傩陆ㄔ次募皖^文件的時(shí)候,
就可以直接保存到src 文件目錄下面了。
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 23
下面我們開(kāi)始編寫(xiě)各個(gè)模塊文件。
首先編寫(xiě)Timer.c 這個(gè)文件主要內(nèi)容就是定時(shí)器初始化,以及定時(shí)器中斷服務(wù)函數(shù)。其內(nèi)容
如下。
#include
bit g_bSystemTime1Ms = 0 ; // 1MS 系統(tǒng)時(shí)標(biāo)
void Timer0Init(void)
{
TMOD &= 0xf0 ;
TMOD |= 0x01 ; //定時(shí)器0 工作方式1
TH0 = 0xfc ; //定時(shí)器初始值
TL0 = 0x66 ;
TR0 = 1 ;
ET0 = 1 ;
}
void Time0Isr(void) interrupt 1
{
TH0 = 0xfc ; //定時(shí)器重新賦初值
TL0 = 0x66 ;
g_bSystemTime1Ms = 1 ; //1MS 時(shí)標(biāo)標(biāo)志位置位
}
由于在Led.c 文件中需要調(diào)用我們的g_bSystemTime1Ms 變量。同時(shí)主函數(shù)需要調(diào)用
Timer0Init()初始化函數(shù),所以應(yīng)該對(duì)這個(gè)變量和函數(shù)在頭文件里作外部聲明。以方便其它
函數(shù)調(diào)用。
Timer.h 內(nèi)容如下。
#ifndef _TIMER_H_
#define _TIMER_H_
extern void Timer0Init(void) ;
extern bit g_bSystemTime1Ms ;
#endif
完成了定時(shí)器模塊后,我們開(kāi)始編寫(xiě)LED 驅(qū)動(dòng)模塊。
Led.c 內(nèi)容如下:
#include
#include "MacroAndConst.h"
#include "Led.h"
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 24
#include "Timer.h"
static uint16 g_u16LedTimeCount = 0 ; //LED 計(jì)數(shù)器
static uint8 g_u8LedState = 0 ; //LED 狀態(tài)標(biāo)志, 0 表示亮,1 表示熄滅
#define LED P0 //定義LED 接口
#define LED_ON() LED = 0x00 ; //所有LED 亮
#define LED_OFF() LED = 0xff ; //所有LED 熄滅
void LedProcess(void)
{
if(0 == g_u8LedState) //如果LED 的狀態(tài)為亮,則點(diǎn)亮LED
{
LED_ON() ;
}
else //否則熄滅LED
{
LED_OFF() ;
}
}
void LedStateChange(void)
{
if(g_bSystemTime1Ms) //系統(tǒng)1MS 時(shí)標(biāo)到
{
g_bSystemTime1Ms = 0 ;
g_u16LedTimeCount++ ; //LED 計(jì)數(shù)器加一
if(g_u16LedTimeCount >= 500) //計(jì)數(shù)達(dá)到500,即500MS 到了,改變LED 的狀態(tài)。
{
g_u16LedTimeCount = 0 ;
g_u8LedState = ! g_u8LedState ;
}
}
}
這個(gè)模塊對(duì)外的借口只有兩個(gè)函數(shù),因此在相應(yīng)的Led.h 中需要作相應(yīng)的聲明。
Led.h 內(nèi)容:
#ifndef _LED_H_
#define _LED_H_
extern void LedProcess(void) ;
extern void LedStateChange(void) ;
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 25
#endif
這兩個(gè)模塊完成后,我們將其C 文件添加到工程中。然后開(kāi)始編寫(xiě)主函數(shù)里的代碼。
如下所示:
#include
#include "MacroAndConst.h"
#include "Timer.h"
#include "Led.h"
sbit LED_SEG = P1^4; //數(shù)碼管段選
sbit LED_DIG = P1^5; //數(shù)碼管位選
sbit LED_CS11 = P1^6; //led 控制位
void main(void)
{
LED_CS11 = 1 ; //74HC595 輸出允許
LED_SEG = 0 ; //數(shù)碼管段選和位選禁止(因?yàn)樗鼈兒蚅ED 共用P0 口)
LED_DIG = 0 ;
Timer0Init() ;
EA = 1 ;
while(1)
{
LedProcess() ;
LedStateChange() ;
}
}
整個(gè)工程截圖如下:
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 26
至此,第三章到此結(jié)束。
一起來(lái)總結(jié)一下我們需要注意的地方吧
[color=#FF0000]1. C語(yǔ)言源文件(*.c)的作用是什么
2. C語(yǔ)言頭文件(*.h)的作用是什么
3. typedef 的作用
4. 工程模板如何組織
5. 如何創(chuàng)建一個(gè)多模塊(多文件)的工程
四、LED 主題討論周第四章----漸明漸暗的燈
看著學(xué)習(xí)板上的LED 按照我們的意愿開(kāi)始閃爍起來(lái),你心里是否高興了,我相信你會(huì)的。但是很快你就會(huì)
感覺(jué)到太單調(diào),總是同一個(gè)頻率在閃爍,總是同一個(gè)亮度在閃爍。如果要是能夠由暗逐漸變亮,然后再由
亮變暗該多漂亮啊。嗯,想法不錯(cuò),可以該從什么地方入手呢。
在開(kāi)始我們的工程之前,首先來(lái)了解一個(gè)概念:PWM。
PWM(Pulse Width Modulation)是脈沖寬度調(diào)制的英文單詞的縮寫(xiě)。下面這段話(huà)是通信百科中對(duì)其的定義:
脈沖寬度調(diào)制(PWM)是利用微處理器的數(shù)字輸出來(lái)對(duì)模擬電路進(jìn)行控制的一種非常有效的技術(shù),廣泛應(yīng)用
在從測(cè)量、通信到功率控制與變換的許多領(lǐng)域中。脈寬調(diào)制是開(kāi)關(guān)型穩(wěn)壓電源中的術(shù)語(yǔ)。這是按穩(wěn)壓的控
制方式分類(lèi)的,除了PWM 型,還有PFM 型和PWM、PFM 混合型。脈寬調(diào)制式開(kāi)關(guān)型穩(wěn)壓電路是在控制
電路輸出頻率不變的情況下,通過(guò)電壓反饋調(diào)整其占空比,從而達(dá)到穩(wěn)定輸出電壓的目的。
讀起來(lái)有點(diǎn)晦澀難懂。其實(shí)簡(jiǎn)單的說(shuō)來(lái),PWM 技術(shù)就是通過(guò)調(diào)整一個(gè)周期固定的方波的占空比,來(lái)調(diào)節(jié)
輸出電壓的平均當(dāng)電壓,電流或者功率等被控量。我們可以用一個(gè)水龍頭來(lái)類(lèi)比,把1S 時(shí)間分成50 等份,
即每一個(gè)等份20MS。在這20MS 時(shí)間里如果我們把水龍頭水閥一直打開(kāi),那么在這20MS 里流過(guò)的水肯
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 27
定是最多的,如果我們把水閥打開(kāi)15MS,剩下的5MS 關(guān)閉水閥,那么流出的水相比剛才20MS 全開(kāi)肯定
要小的多。同樣的道理,我們可以通過(guò)控制20MS 時(shí)間里水閥開(kāi)啟的時(shí)間的長(zhǎng)短來(lái)控制流過(guò)的水的多少。
那么在1S 內(nèi)平均流出的水流量也就可以被控制了。
當(dāng)我們調(diào)整PWM 的占空比時(shí),就會(huì)引起電壓或者電流的改變,LED 的明暗狀態(tài)就會(huì)隨之發(fā)生相應(yīng)的變化,
聽(tīng)起來(lái)好像可以通過(guò)這種方法來(lái)實(shí)現(xiàn)我們想要的漸明漸暗的效果。讓我們來(lái)試一下吧。
大家都知道人眼有一個(gè)臨界頻率,當(dāng)LED 的閃爍頻率達(dá)到一定的時(shí)候,人眼就分辨不出LED 是否在閃爍
了。就像我們平常看電視一樣,看起來(lái)畫(huà)面是連續(xù)的,實(shí)質(zhì)不是這個(gè)樣子,所有連續(xù)動(dòng)作都是一幀幀靜止
的畫(huà)面在1S 的時(shí)間里快速播放出來(lái),譬如每秒24 幀的速度播放,由于人眼的視覺(jué)暫留效應(yīng),看起來(lái)畫(huà)面
就是連續(xù)的了。同樣的道理,為了讓我們的LED 在變化的過(guò)程中,我們感覺(jué)不到其在閃爍,可以將其閃爍
的頻率定在50Hz 以上。同時(shí)為了看起來(lái)明暗過(guò)渡的效果更加明顯,我們?cè)谶@里定義其變化范圍為0~99(100
等分).即最亮的時(shí)候其灰度等級(jí)為99,為0 的時(shí)候最暗,也就是熄滅了。
于是乎我們定義PWM 的占空比上限為99, 下限定義為0
#define LED_PWM_LIMIT_MAX 99
#define LED_PWM_LIMIT_MIN 0
假定我們LED 的閃爍頻率為50HZ,而亮度變化的范圍為0~99 共100 等分。則每一等分所占用的時(shí)間為
1/(50*100) = 200us 即我們?cè)诟淖僉ED 的亮滅狀態(tài)時(shí),應(yīng)該是在200us 整數(shù)倍時(shí)刻時(shí)。在這里我們用
單片機(jī)的定時(shí)器產(chǎn)生200us 的中斷,同時(shí)每20MS 調(diào)整一次LED 的占空比。這樣在20MS * 100 = 2S 的
時(shí)間內(nèi)LED 可以從暗逐漸變亮,在下一個(gè)2S 內(nèi)可以從亮逐漸變暗,然后不斷循環(huán)。
由于大部分的內(nèi)容都可以在中斷中完成,因此,我們的大部分代碼都在Timer.c 這個(gè)文件中編寫(xiě),主函數(shù)
中除了初始化之外,就是一個(gè)空的死循環(huán)。
Timer.c 內(nèi)容如下。
#include
#include "MacroAndConst.h"
#define LED P0 //定義LED 接口
#define LED_ON() LED = 0x00 ; //所有LED 亮
#define LED_OFF() LED = 0xff ; //所有LED 熄滅
#define LED_PWM_LIMIT_MAX 99
#define LED_PWM_LIMIT_MIN 0
static uint8 s_u8TimeCounter = 0 ; //中斷計(jì)數(shù)
static uint8 s_u8LedDirection = 0 ; //LED 方向控制0 :漸亮1 :漸滅
static int8 s_s8LedPWMCounter = 0 ; //LED 占空比
void Timer0Init(void)
{
TMOD &= 0xf0 ;
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 28
TMOD |= 0x01 ; //定時(shí)器0 工作方式1
TH0 = 0xff ; //定時(shí)器初始值(200us 中斷一次)
TL0 = 0x47 ;
TR0 = 1 ;
ET0 = 1 ;
}
void Time0Isr(void) interrupt 1
{
static int8 s_s8PWMCounter = 0 ;
TH0 = 0xff ; //定時(shí)器重新賦初值
TL0 = 0x47 ;
if(++s_u8TimeCounter >= 100) //每20MS 調(diào)整一下LED 的占空比
{
s_u8TimeCounter = 0 ;
//如果是漸亮方向變化,則占空比遞增
if((s_s8LedPWMCounter <= LED_PWM_LIMIT_MAX) &&(0 == s_u8LedDirection))
{
s_s8LedPWMCounter++ ;
if(s_s8LedPWMCounter > LED_PWM_LIMIT_MAX)
{
s_u8LedDirection = 1 ;
s_s8LedPWMCounter = LED_PWM_LIMIT_MAX ;
}
}
//如果是漸暗方向變化,則占空比遞漸
if((s_s8LedPWMCounter >= LED_PWM_LIMIT_MIN) &&(1 == s_u8LedDirection))
{
s_s8LedPWMCounter-- ;
if(s_s8LedPWMCounter < LED_PWM_LIMIT_MIN)
{
s_u8LedDirection = 0 ;
s_s8LedPWMCounter = LED_PWM_LIMIT_MIN ;
}
}
s_s8PWMCounter = s_s8LedPWMCounter ; //獲取LED 的占空比
}
if(s_s8PWMCounter > 0) //占空比大于0,則點(diǎn)亮LED,否則熄滅LED
{
LED_ON() ;
s_s8PWMCounter-- ;
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 29
}
else
{
LED_OFF();
}
}
其實(shí)PWM 技術(shù)在我們實(shí)際生活中應(yīng)用的非常多。比較典型的應(yīng)用就是控制電機(jī)的轉(zhuǎn)速,控制充電電流的
大小,等等。而隨著技術(shù)的發(fā)展,也出現(xiàn)了其他類(lèi)型的PWM 技術(shù),如相電壓PWM,線(xiàn)電壓PWM,SPWM
等等,如果有興趣可以到網(wǎng)上去獲取相應(yīng)資料學(xué)習(xí)。
關(guān)于漸明漸暗的燈就簡(jiǎn)單的講到這里。
五、LED 主題討論周第五章----多任務(wù)環(huán)境下的數(shù)碼管編程
設(shè)計(jì)
[post]數(shù)碼管在實(shí)際應(yīng)用中非常廣泛,尤其是在某些對(duì)成本有限制的場(chǎng)合。編寫(xiě)一個(gè)好用的
LED 程序并不是那么的簡(jiǎn)單。曾經(jīng)有人這樣說(shuō)過(guò),如果用數(shù)碼管和按鍵,做一個(gè)簡(jiǎn)易的可
以調(diào)整的時(shí)鐘出來(lái),那么你的單片機(jī)就算入門(mén)了60%了。此話(huà)我深信不疑。我遇到過(guò)很多
單片機(jī)的愛(ài)好者,他們問(wèn)我說(shuō)單片機(jī)我已經(jīng)掌握了,該如何進(jìn)一步的學(xué)習(xí)下去呢?我并不急
于回答他們的問(wèn)題,而是問(wèn)他們:會(huì)編寫(xiě)數(shù)碼管的驅(qū)動(dòng)程序了吧?“嗯”。會(huì)編寫(xiě)按鍵程序了
吧?“嗯”。好,我給你出一個(gè)小題目,你做一下。用按鍵和數(shù)碼管以及單片機(jī)定時(shí)器實(shí)現(xiàn)一
個(gè)簡(jiǎn)易的可以調(diào)整的時(shí)鐘,要求如下:
8 位數(shù)碼管顯示,顯示格式如下
時(shí)-分-秒
XX-XX-XX
要求:系統(tǒng)有四個(gè)按鍵,功能分別是調(diào)整,加,減,確定。在按下調(diào)整鍵時(shí)候,顯示時(shí)的
兩位數(shù)碼管以1 Hz 頻率閃爍。如果再次按下調(diào)整鍵,則分開(kāi)始閃爍,時(shí)恢復(fù)正常顯示,依
次循環(huán),直到按下確定鍵,恢復(fù)正常的顯示。在數(shù)碼管閃爍的時(shí)候,按下加或者減鍵可以調(diào)
整相應(yīng)的顯示內(nèi)容。按鍵支持短按,和長(zhǎng)按,即短按時(shí),修改的內(nèi)容每次增加一或者減小一,
長(zhǎng)按時(shí)候以一定速率連續(xù)增加或者減少。
結(jié)果很多人,很多愛(ài)好者一下子都理不清楚思路。其實(shí)問(wèn)題的根源在于沒(méi)有以工程化的角度
去思考程序的編寫(xiě)。很多人在學(xué)習(xí)數(shù)碼管編程的時(shí)候,都是照著書(shū)上或者網(wǎng)上的例子來(lái)進(jìn)行
試驗(yàn)。殊不知,這些例子代碼僅僅只是具有一個(gè)演示性的作用,拿到實(shí)際中是很難用的。舉
一個(gè)簡(jiǎn)單的例子。
下面這段程序是在網(wǎng)上隨便搜索到的:
while(1)
{
for(num=0;num<9;num++)
{
P0=table[num];
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 30
P2=code[num] ;
delayms(2) ;
}
}
看出什么問(wèn)題來(lái)了沒(méi)有,如果沒(méi)有看出來(lái)請(qǐng)仔細(xì)想一下,如果還沒(méi)有想出來(lái),請(qǐng)回過(guò)頭去,
認(rèn)真再看一遍“學(xué)會(huì)釋放CPU”這一章的內(nèi)容。這個(gè)程序作為演示程序是沒(méi)有什么問(wèn)題的,但
是實(shí)際應(yīng)用的時(shí)候,數(shù)碼管顯示的內(nèi)容經(jīng)常變化,而且還有很多其它任務(wù)需要執(zhí)行,因此這
樣的程序在實(shí)際中是根本就無(wú)法用的,更何況,它這里也調(diào)用了delayms(2)這個(gè)函數(shù)來(lái)延
時(shí)2 ϑms 這更是令我們深?lèi)和唇^
本章的內(nèi)容正是探討如何解決多任務(wù)環(huán)境下(不帶OS)的數(shù)碼管程序設(shè)計(jì)的編寫(xiě)問(wèn)題。理解
了其中的思想,無(wú)論要求我們顯示的形式怎么變化(如數(shù)碼管閃爍,移位等),我們都可以很方
便的解決問(wèn)題。
數(shù)碼管的顯示分為動(dòng)態(tài)顯示和靜態(tài)顯示兩種。靜態(tài)顯示是每一位數(shù)碼管都用一片獨(dú)立的驅(qū)動(dòng)
芯片進(jìn)行驅(qū)動(dòng)。比較常見(jiàn)的有74LS164,74HC595 等。利用這類(lèi)芯片的好處就是可以級(jí)聯(lián),
留給單片機(jī)的接口只需要時(shí)鐘線(xiàn),數(shù)據(jù)線(xiàn),因此比較節(jié)省I/O 口。如下圖所示:
利用74LS164 級(jí)聯(lián)驅(qū)動(dòng)8 個(gè)單獨(dú)的數(shù)碼管
靜態(tài)顯示的優(yōu)點(diǎn)是程序編寫(xiě)簡(jiǎn)單。但是由于涉及到的驅(qū)動(dòng)芯片數(shù)量比較多,同時(shí)考慮到PCB
的布線(xiàn)等等因素,在低成本要求的開(kāi)發(fā)環(huán)境下,單純的靜態(tài)驅(qū)動(dòng)并不合適。這個(gè)時(shí)候就可以
考慮到動(dòng)態(tài)驅(qū)動(dòng)了。
動(dòng)態(tài)驅(qū)動(dòng)的圖如下所示(以EE21 開(kāi)發(fā)板為例)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 31
由上圖可以看出。8 個(gè)數(shù)碼管的段碼由一個(gè)單獨(dú)的74HC573 驅(qū)動(dòng)。同時(shí)每一個(gè)數(shù)碼管的公
共端連接在另外一個(gè)74HC573 的輸出上。當(dāng)送出第一位數(shù)碼管的段碼內(nèi)容時(shí)候,同時(shí)選通
第一位數(shù)碼管的位選,此時(shí),第一位數(shù)碼管就顯示出相應(yīng)的內(nèi)容了。一段時(shí)間之后,送出第
二位數(shù)碼管段碼的內(nèi)容,選通第二位數(shù)碼管的位選,這時(shí)顯示的內(nèi)容就變成第二位數(shù)碼管的
內(nèi)容了……依次循環(huán)下去,就可以看到了所有數(shù)碼管同時(shí)顯示了。事實(shí)上,任意時(shí)刻,只有
一位數(shù)碼管是被點(diǎn)亮的。由于人眼的視覺(jué)暫留效應(yīng)以及數(shù)碼管的余輝效應(yīng),當(dāng)數(shù)碼管掃描的
頻率非?斓臅r(shí)候,人眼已經(jīng)無(wú)法分辨出數(shù)碼管的變化了,看起來(lái)就是同時(shí)點(diǎn)亮的。我們假
設(shè)數(shù)碼管的掃描頻率為50 Hz, 則完成一輪掃描的時(shí)間就是1 / 50 = 20 ms 。我們的系統(tǒng)共
有8 位數(shù)碼管,則每一位數(shù)碼管在一輪掃描周期中點(diǎn)亮的時(shí)間為20 / 8 = 2.5 ms 。
動(dòng)態(tài)掃描對(duì)時(shí)間要求有一點(diǎn)點(diǎn)嚴(yán)格,否則,就會(huì)有明顯的閃爍。
假設(shè)我們程序中所有任務(wù)如下:
while(1)
{
LedDisplay() ; //數(shù)碼管動(dòng)態(tài)掃描
ADProcess() ; //AD 采集處理
TimerProcess() ; //時(shí)間相關(guān)處理
DataProcess() ; //數(shù)據(jù)處理
}
LedDisplay() 這個(gè)任務(wù)的執(zhí)行時(shí)間,如同我們剛才計(jì)算的那樣,50 Hz 頻率掃描,則該函數(shù)
執(zhí)行的時(shí)間為20 ms 。假設(shè)ADProcess()這個(gè)任務(wù)執(zhí)行的的時(shí)間為2 ms ,TimerProcess()
這個(gè)函數(shù)執(zhí)行的時(shí)間為1 ms ,DataProcess() 這個(gè)函數(shù)執(zhí)行的時(shí)間為10 ms 。那么整個(gè)
主函數(shù)執(zhí)行一遍的總時(shí)間為20 + 2 + 1 + 10 = 33 ms 。即LedDisplay() 這個(gè)函數(shù)的掃描頻
率已經(jīng)不為50 Hz 了,而是1 / 33 = 30.3 Hz 。這個(gè)頻率數(shù)碼管已經(jīng)可以感覺(jué)到閃爍了,
因此不符合我們的要求。為什么會(huì)出現(xiàn)這種情況呢? 我們剛才計(jì)算的50 Hz 是系統(tǒng)只有
LedDisplay()這一個(gè)任務(wù)的時(shí)候得出來(lái)的結(jié)果。當(dāng)系統(tǒng)添加了其它任務(wù)后,當(dāng)然系統(tǒng)循環(huán)執(zhí)
行一次的總時(shí)間就增加了。如何解決這種現(xiàn)象了,還是離不開(kāi)我們第二章所講的那個(gè)思想。
系統(tǒng)產(chǎn)生一個(gè)2.5 ms 的時(shí)標(biāo)消息。LedDisplay() , 每次接收到這個(gè)消息的時(shí)候, 掃描一位
數(shù)碼管。這樣8 個(gè)時(shí)標(biāo)消息過(guò)后,所有的數(shù)碼管就都被掃描一遍了?赡苡信笥褧(huì)有這樣
的疑問(wèn):ADProcess() 以及DataProcess() 等函數(shù)執(zhí)行的時(shí)間還是需要十幾ms 啊,在這
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 32
十幾ms 的時(shí)間里,已經(jīng)產(chǎn)生好幾個(gè)2.5 ms 的時(shí)標(biāo)消息了,這樣豈不是漏掉了掃描,顯示
起來(lái)還是會(huì)閃爍。能夠想到這一點(diǎn),很不錯(cuò),這也就是為什么我們要學(xué)會(huì)釋放CPU 的原因。
對(duì)于ADProcess(),TimerProcess(),DataProcess(),等任務(wù)我們依舊要采取此方法對(duì)CPU
進(jìn)行釋放,使其執(zhí)行的時(shí)間盡可能短暫,關(guān)于如何做到這一點(diǎn),在以后的講解如何設(shè)計(jì)多任
務(wù)程序設(shè)計(jì)的時(shí)候會(huì)講解到。
下面我們基于此思路開(kāi)始編寫(xiě)具體的程序。
首先編寫(xiě)Timer.c 文件。該文件中主要為系統(tǒng)提供時(shí)間相關(guān)的服務(wù)。必要的頭文件包含。
#include
#include "MacroAndConst.h"
為了方便計(jì)算,我們?nèi)?shù)碼管掃描一位的時(shí)間為2 ms。設(shè)置定時(shí)器0 為2 ms 中斷一次。
同時(shí)聲明一個(gè)位變量,作為2 ms 時(shí)標(biāo)消息的標(biāo)志
bit g_bSystemTime2Ms = 0 ; // 2msLED 動(dòng)態(tài)掃描時(shí)標(biāo)消息
初始化定時(shí)器0
void Timer0Init(void)
{
TMOD &= 0xf0 ;
TMOD |= 0x01 ; //定時(shí)器0 工作方式1
TH0 = 0xf8 ; //定時(shí)器初始值
TL0 = 0xcc ;
TR0 = 1 ;
ET0 = 1 ;
}
在定時(shí)器0 中斷處理程序中,設(shè)置時(shí)標(biāo)消息。
void Time0Isr(void) interrupt 1
{
TH0 = 0xf8 ; //定時(shí)器重新賦初值
TL0 = 0xcc ;
g_bSystemTime2Ms = 1 ; //2MS 時(shí)標(biāo)標(biāo)志位置位
}
然后我們開(kāi)始編寫(xiě)數(shù)碼管的動(dòng)態(tài)掃描函數(shù)。
新建一個(gè)C 源文件,并包含相應(yīng)的頭文件。
#include
#include "MacroAndConst.h"
#include "Timer.h"
先開(kāi)辟一個(gè)數(shù)碼管顯示的緩沖區(qū)。動(dòng)態(tài)掃描函數(shù)負(fù)責(zé)從這個(gè)緩沖區(qū)中取出數(shù)據(jù),并掃描顯示。
而其它函數(shù)則可以修改該緩沖區(qū),從而改變顯示的內(nèi)容。
uint8 g_u8LedDisplayBuffer[8] = {0} ; //顯示緩沖區(qū)
然后定義共陽(yáng)數(shù)碼管的段碼表以及相應(yīng)的硬件端口連接。
code uint8 g_u8LedDisplayCode[]=
{
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,
0xbf, //'-'號(hào)代碼
} ;
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 33
sbit io_led_seg_cs = P1^4 ;
sbit io_led_bit_cs = P1^5 ;
#define LED_PORT P0
再分別編寫(xiě)送數(shù)碼管段碼函數(shù),以及位選通函數(shù)。
static void SendLedSegData(uint8 dat)
{
LED_PORT = dat ;
io_led_seg_cs = 1 ; //開(kāi)段碼鎖存,送段碼數(shù)據(jù)
io_led_seg_cs = 0 ;
}
static void SendLedBitData(uint8 dat)
{
uint8 temp ;
temp = (0x01 << dat ) ; //根據(jù)要選通的位計(jì)算出位碼
LED_PORT = temp ;
io_led_bit_cs = 1 ; //開(kāi)位碼鎖存,送位碼數(shù)據(jù)
io_led_bit_cs = 0 ;
}
下面的核心就是如何編寫(xiě)動(dòng)態(tài)掃描函數(shù)了。
如下所示:
void LedDisplay(uint8 * pBuffer)
{
static uint8 s_LedDisPos = 0 ;
if(g_bSystemTime2Ms)
{
g_bSystemTime2Ms = 0 ;
SendLedBitData(8) ; //消隱,只需要設(shè)置位選不為0~7 即可
if(pBuffer[s_LedDisPos] == '-') //顯示'-'號(hào)
{
SendLedSegData(g_u8LedDisplayCode[16]) ;
}
else
{
SendLedSegData(g_u8LedDisplayCode[pBuffer[s_LedDisPos]]) ;
}
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 34
SendLedBitData(s_LedDisPos);
if(++s_LedDisPos > 7)
{
s_LedDisPos = 0 ;
}
}
}
函數(shù)內(nèi)部定義一個(gè)靜態(tài)的變量s_LedDisPos,用來(lái)表示掃描數(shù)碼管的位置。每當(dāng)我們執(zhí)行
該函數(shù)一次的時(shí)候,s_LedDisPos 的值會(huì)自加1,表示下次掃描下一個(gè)數(shù)碼管。然后判斷
g_bSystemTime2Ms 時(shí)標(biāo)消息是否到了。如果到了,就開(kāi)始執(zhí)行相關(guān)掃描,否則就直接跳
出函數(shù)。SendLedBitData(8) ;的作用是消隱。因?yàn)槲覀兊南到y(tǒng)的段選和位選是共用P0 口的。
在送段碼之前,必須先關(guān)掉位選,否則,因?yàn)樯洗挝贿x是選通的,在送段碼的時(shí)候會(huì)造成相
應(yīng)數(shù)碼管的點(diǎn)亮,盡管這個(gè)時(shí)間很短暫。但是因?yàn)槲覀兊臄?shù)碼管是不斷掃描的,所以看起來(lái)
還是會(huì)有些微微亮。為了消除這種影響,就有必要再送段碼數(shù)據(jù)之前關(guān)掉位選。
if(pBuffer[s_LedDisPos] == '-') //顯示'-'號(hào)這行語(yǔ)句是為了顯示’-’符號(hào)特意加上去的,大
家可以看到在定義數(shù)碼管的段碼表的時(shí)候,我多加了一個(gè)字節(jié)的代碼0xbf:
code uint8 g_u8LedDisplayCode[]=
{
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,
0xbf, //'-'號(hào)代碼
} ;
通過(guò)SendLedSegData(g_u8LedDisplayCode[pBuffer[s_LedDisPos]]) ;送出相應(yīng)的段碼數(shù)
據(jù)后,然后通過(guò)SendLedBitData(s_LedDisPos);打開(kāi)相應(yīng)的位選。這樣對(duì)應(yīng)的數(shù)碼管就被
點(diǎn)亮了。
if(++s_LedDisPos > 7)
{
s_LedDisPos = 0 ;
}
然后s_LedDisPos 自加1,以便下次執(zhí)行本函數(shù)時(shí),掃描下一個(gè)數(shù)碼管。因?yàn)槲覀兊南到y(tǒng)
共有8 個(gè)數(shù)碼管,所以當(dāng)s_LedDisPos > 7 后,要對(duì)其進(jìn)行清0 。否則,沒(méi)有任何一個(gè)數(shù)
碼管被選中。這也是為什么我們可以用
SendLedBitData(8) ; //消隱,只需要設(shè)置位選不為0~7 即可
對(duì)數(shù)碼管進(jìn)行消隱操作的原因。
下面我們來(lái)編寫(xiě)相應(yīng)的主函數(shù),并實(shí)現(xiàn)數(shù)碼管上面類(lèi)似時(shí)鐘的效果,如顯示10-20-30
即10 點(diǎn)20 分30 秒。
Main.c
#include
#include "MacroAndConst.h"
#include "Timer.h"
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 35
#include "Led7Seg.h"
sbit io_led = P1^6 ;
void main(void)
{
io_led = 0 ; //發(fā)光二極管與數(shù)碼管共用P0 口,這里禁止掉發(fā)光二極管的鎖存輸
出
Timer0Init() ;
g_u8LedDisplayBuffer[0] = 1 ;
g_u8LedDisplayBuffer[1] = 0 ;
g_u8LedDisplayBuffer[2] = '-' ;
g_u8LedDisplayBuffer[3] = 2 ;
g_u8LedDisplayBuffer[4] = 0 ;
g_u8LedDisplayBuffer[5] = '-' ;
g_u8LedDisplayBuffer[6] = 3 ;
g_u8LedDisplayBuffer[7] = 0 ;
EA = 1 ;
while(1)
{
LedDisplay(g_u8LedDisplayBuffer) ;
}
}
ϑ將整個(gè)工程進(jìn)行編譯,看看效果如何
動(dòng)起來(lái)
既然我們想要模擬一個(gè)時(shí)鐘,那么時(shí)鐘肯定是要走動(dòng)的,不然還稱(chēng)為什么時(shí)鐘撒。下面我們
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 36
在前面的基礎(chǔ)之上,添加一點(diǎn)相應(yīng)的代碼,讓我們這個(gè)時(shí)鐘走動(dòng)起來(lái)。
我們知道,之前我們以及設(shè)置了一個(gè)掃描數(shù)碼管用到的2 ms 時(shí)標(biāo)。如果我們?cè)賹?duì)這個(gè)時(shí)
標(biāo)進(jìn)行計(jì)數(shù),當(dāng)計(jì)數(shù)值達(dá)到500,即500 * 2 = 1000 ms 時(shí)候,即表示已經(jīng)逝去了1 S 的時(shí)
間。我們?cè)俑鶕?jù)這個(gè)1 S 的時(shí)間更新顯示緩沖區(qū)即可。聽(tīng)起來(lái)很簡(jiǎn)單,讓我們實(shí)現(xiàn)它吧。
首先在Timer.c 中聲明如下兩個(gè)變量:
bit g_bTime1S = 0 ; //時(shí)鐘1S 時(shí)標(biāo)消息
static uint16 s_u16ClockTickCount = 0 ; //對(duì)2 ms 時(shí)標(biāo)進(jìn)行計(jì)數(shù)
再在定時(shí)器中斷函數(shù)中添加如下代碼:
if(++s_u16ClockTickCount == 500)
{
s_u16ClockTickCount = 0 ;
g_bTime1S = 1 ;
}
從上面可以看出,s_u16ClockTickCount 計(jì)數(shù)值達(dá)到500 的時(shí)候,g_bTime1S 時(shí)標(biāo)消息產(chǎn)
生。然后我們根據(jù)這個(gè)時(shí)標(biāo)消息刷新數(shù)碼管顯示緩沖區(qū):
void RunClock(void)
{
if(g_bTime1S )
{
g_bTime1S = 0 ;
if(++g_u8LedDisplayBuffer[7] == 10)
{
g_u8LedDisplayBuffer[7] = 0 ;
if(++g_u8LedDisplayBuffer[6] == 6)
{
g_u8LedDisplayBuffer[6] = 0 ;
if(++g_u8LedDisplayBuffer[4] == 10)
{
g_u8LedDisplayBuffer[4] = 0 ;
if(++g_u8LedDisplayBuffer[3] == 6)
{
g_u8LedDisplayBuffer[3] = 0 ;
if( g_u8LedDisplayBuffer[0]<2)
{
if(++g_u8LedDisplayBuffer[1]==10)
{
g_u8LedDisplayBuffer[1] = 0 ;
g_u8LedDisplayBuffer[0]++;
}
}
else
{
if(++g_u8LedDisplayBuffer[1]==4)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 37
{
g_u8LedDisplayBuffer[1] = 0 ;
g_u8LedDisplayBuffer[0] = 0 ;
}
}
}
}
}
}
}
}
這個(gè)函數(shù)的作用就是對(duì)每個(gè)數(shù)碼管緩沖位的值進(jìn)行判斷,判斷的標(biāo)準(zhǔn)就是我們熟知的24 小
時(shí)制。如秒的個(gè)位到了10 就清0,同時(shí)秒的十位加1….諸如此類(lèi),我就不一一詳述了。
同時(shí),我們?cè)倬帉?xiě)一個(gè)時(shí)鐘初始值設(shè)置函數(shù),這樣,可以很方便的在主程序開(kāi)始的時(shí)候修改
時(shí)鐘初始值。
void SetClock(uint8 nHour, uint8 nMinute, uint8 nSecond)
{
g_u8LedDisplayBuffer[0] = nHour / 10 ;
g_u8LedDisplayBuffer[1] = nHour % 10 ;
g_u8LedDisplayBuffer[2] = '-' ;
g_u8LedDisplayBuffer[3] = nMinute / 10 ;
g_u8LedDisplayBuffer[4] = nMinute % 10 ;
g_u8LedDisplayBuffer[5] = '-' ;
g_u8LedDisplayBuffer[6] = nSecond / 10 ;
g_u8LedDisplayBuffer[7] = nSecond % 10 ;
}
然后修改下我們的主函數(shù)如下:
void main(void)
{
io_led = 0 ; //發(fā)光二極管與數(shù)碼管共用P0 口,這里禁止掉發(fā)光二極管的鎖存輸出
Timer0Init() ;
SetClock(10,20,30) ; //設(shè)置初始時(shí)間為10 點(diǎn)20 分30 秒
EA = 1 ;
while(1)
{
LedDisplay(g_u8LedDisplayBuffer) ;
RunClock();
}
}
編譯好之后,下載到我們的實(shí)驗(yàn)板上,怎么樣,一個(gè)簡(jiǎn)單的時(shí)鐘就這樣誕生了。
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 38
至此,本章所訴就告一段落了。至于如何完成數(shù)碼管的閃爍顯示,就像本章開(kāi)頭所說(shuō)的那個(gè)
數(shù)碼管時(shí)鐘的功能,就作為一個(gè)思考的問(wèn)題留給大家思考吧。
同時(shí)整個(gè)LED 篇就到此結(jié)束了,在以后的文章中,我們將開(kāi)始學(xué)習(xí)如何編寫(xiě)實(shí)用的按鍵掃
描程序。
[/post
本章所附例程在EE21 學(xué)習(xí)板上調(diào)試通過(guò),擁有板子的朋友可以直接下載附件對(duì)照學(xué)習(xí)
六、KEY 主題討論第一章——按鍵程序編寫(xiě)的基礎(chǔ)
從這一章開(kāi)始,我們步入按鍵程序設(shè)計(jì)的殿堂。在基于單片機(jī)為核心構(gòu)成的應(yīng)用系統(tǒng)中,用
戶(hù)輸入是必不可少的一部分。輸入可以分很多種情況,譬如有的系統(tǒng)支持PS2 鍵盤(pán)的接口,
有的系統(tǒng)輸入是基于編碼器,有的系統(tǒng)輸入是基于串口或者USB 或者其它輸入通道等等。
在各種輸入途徑中,更常見(jiàn)的是,基于單個(gè)按鍵或者由單個(gè)鍵盤(pán)按照一定排列構(gòu)成的矩陣鍵
盤(pán)(行列鍵盤(pán))。我們這一篇章主要討論的對(duì)象就是基于單個(gè)按鍵的程序設(shè)計(jì),以及矩陣鍵盤(pán)
的程序編寫(xiě)。
◎按鍵檢測(cè)的原理
常見(jiàn)的獨(dú)立按鍵的外觀(guān)如下,相信大家并不陌生,各種常見(jiàn)的開(kāi)發(fā)板學(xué)習(xí)板上隨處可以看到
他們的身影。
總共有四個(gè)引腳,一般情況下,處于同一邊的兩個(gè)引腳內(nèi)部是連接在一起的,如何分辨
兩個(gè)引腳是否處在同一邊呢?可以將按鍵翻轉(zhuǎn)過(guò)來(lái),處于同一邊的兩個(gè)引腳,有一條突起的
線(xiàn)將他們連接一起,以標(biāo)示它們倆是相連的。如果無(wú)法觀(guān)察得到,用數(shù)字萬(wàn)用表的二極管擋
位檢測(cè)一下即可。搞清楚這點(diǎn)非常重要,對(duì)于我們畫(huà)PCB 的時(shí)候的封裝很有益。
它們和我們的單片機(jī)系統(tǒng)的I/O 口連接一般如下:
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 39
對(duì)于單片機(jī)I/O 內(nèi)部有上拉電阻的微控制器而言,還可以省掉外部的那個(gè)上拉電阻。簡(jiǎn)
單分析一下按鍵檢測(cè)的原理。當(dāng)按鍵沒(méi)有按下的時(shí)候,單片機(jī)I/O 通過(guò)上拉電阻R 接到VCC,
我們?cè)诔绦蛑凶x取該I/O 的電平的時(shí)候,其值為1(高電平); 當(dāng)按鍵S 按下的時(shí)候,該I/O
被短接到GND,在程序中讀取該I/O 的電平的時(shí)候,其值為0(低電平) 。這樣,按鍵的按
下與否,就和與該按鍵相連的I/O 的電平的變化相對(duì)應(yīng)起來(lái)。結(jié)論:我們?cè)诔绦蛑型ㄟ^(guò)檢測(cè)到
該I/O 口電平的變化與否,即可以知道按鍵是否被按下,從而做出相應(yīng)的響應(yīng)。一切看起來(lái)很美好,是
這樣的嗎?
◎現(xiàn)實(shí)并非理想
在我們通過(guò)上面的按鍵檢測(cè)原理得出上述的結(jié)論的時(shí)候,其實(shí)忽略了一個(gè)重要的問(wèn)題,那就
是現(xiàn)實(shí)中按鍵按下時(shí)候的電平變化狀態(tài)。我們的結(jié)論是基于理想的情況得出來(lái)的,就如同下
面這幅按鍵按下時(shí)候?qū)?yīng)電平變化的波形圖一樣:
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 40
而實(shí)際中,由于按鍵的彈片接觸的時(shí)候,并不是一接觸就緊緊的閉合,它還存在一定的
抖動(dòng),盡管這個(gè)時(shí)間非常的短暫,但是對(duì)于我們執(zhí)行時(shí)間以u(píng)s 為計(jì)算單位的微控制器來(lái)說(shuō),
它太漫長(zhǎng)了。因而,實(shí)際的波形圖應(yīng)該如下面這幅示意圖一樣。
這樣便存在這樣一個(gè)問(wèn)題。假設(shè)我們的系統(tǒng)有這樣功能需求:在檢測(cè)到按鍵按下的時(shí)候,將
某個(gè)I/O 的狀態(tài)取反。由于這種抖動(dòng)的存在,使得我們的微控制器誤以為是多次按鍵的按下,
從而將某個(gè)I/O 的狀態(tài)不斷取反,這并不是我們想要的效果,假如該I/O 控制著系統(tǒng)中某個(gè)
重要的執(zhí)行的部件,那結(jié)果更不是我們所期待的。于是乎有人便提出了軟件消除抖動(dòng)的思想,
道理很簡(jiǎn)單:抖動(dòng)的時(shí)間長(zhǎng)度是一定的,只要我們避開(kāi)這段抖動(dòng)時(shí)期,檢測(cè)穩(wěn)定的時(shí)候的電
平不久可以了嗎?聽(tīng)起來(lái)確實(shí)不錯(cuò),而且實(shí)際應(yīng)用起來(lái)效果也還可以。于是,各種各樣的書(shū)
籍中,在提到按鍵檢測(cè)的時(shí)候,總也不忘說(shuō)道軟件消抖。就像下面的偽代碼所描述的一樣。
(假設(shè)按鍵按下時(shí)候,低電平有效)
If(0 == io_KeyEnter) //如果有鍵按下了
{
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 41
Delayms(20) ; //先延時(shí)20ms 避開(kāi)抖動(dòng)時(shí)期
If(0 == io_KeyEnter) //然后再檢測(cè),如果還是檢測(cè)到有鍵按下
{
return KeyValue ; //是真的按下了,返回鍵值
}
else
{
return KEY_NULL //是抖動(dòng),返回空的鍵值
}
while(0 == io_KeyEnter) ; //等待按鍵釋放
}
乍看上去,確實(shí)挺不錯(cuò),實(shí)際中呢?在實(shí)際的系統(tǒng)中,一般是不允許這么樣做的。為什么呢?
首先,這里的Delayms(20) , 讓微控制器在這里白白等待了20 ms 的時(shí)間,啥也沒(méi)干,考
慮我在《學(xué)會(huì)釋放CPU》一章中所提及的幾點(diǎn),這是不可取的。其次while(0 == io_KeyEnter)
所以合理的分配好微控制的處理時(shí)間,是編寫(xiě)按鍵程序的基礎(chǔ)。ϑ;更是程序設(shè)計(jì)中的大忌(極
少的特殊情況例外)。任何非極端情況下,都不要使用這樣語(yǔ)句來(lái)堵塞微控制器的執(zhí)行進(jìn)程。
原本是等待按鍵釋放,結(jié)果CPU 就一直死死的盯住該按鍵,其它事情都不管了,那其它事
情不干了嗎?你同意別人可不會(huì)同意
◎消除抖動(dòng)有必要嗎?
的確,軟件上的消抖確實(shí)可以保證按鍵的有效檢測(cè)。但是,這種消抖確實(shí)有必要嗎?有人提
出了這樣的疑問(wèn)。抖動(dòng)是按鍵按下的過(guò)程中產(chǎn)生的,如果按鍵沒(méi)有按下,抖動(dòng)會(huì)產(chǎn)生嗎?如
果沒(méi)有按鍵按下,抖動(dòng)也會(huì)在I/O 上出現(xiàn),我會(huì)立刻把這個(gè)微控制器錘了,永遠(yuǎn)不用這樣一
款微控制器。所以抖動(dòng)的出現(xiàn)即意味著按鍵已經(jīng)按下,盡管這個(gè)電平還沒(méi)有穩(wěn)定。所以只要
我們檢測(cè)到按鍵按下,即可以返回鍵值,問(wèn)題的關(guān)鍵是,在你執(zhí)行完其它任務(wù)的時(shí)候,再次
執(zhí)行我們的按鍵任務(wù)的時(shí)候,抖動(dòng)過(guò)程還沒(méi)有結(jié)束,這樣便有可能造成重復(fù)檢測(cè)。所以,如
何在返回鍵值后,避免重復(fù)檢測(cè),或者在按鍵一按下就執(zhí)行功能函數(shù),當(dāng)功能函數(shù)的執(zhí)行時(shí)
間小于抖動(dòng)時(shí)間時(shí)候,如何避免再次執(zhí)行功能函數(shù),就成為我們要考慮的問(wèn)題了。這是一個(gè)
仁者見(jiàn)仁,智者見(jiàn)智的問(wèn)題,就留給大家去思考吧。所以消除抖動(dòng)的目的是:防止按鍵一次
按下,多次響應(yīng)。
七、KEY 主題討論第二章——基于狀態(tài)轉(zhuǎn)移的獨(dú)立按鍵程序
設(shè)計(jì)
本章所描述的按鍵程序要達(dá)到的目的:檢測(cè)按鍵按下,短按,長(zhǎng)按,釋放。即通過(guò)按鍵的返
回值我們可以獲取到如下的信息:按鍵按下(短按),按鍵長(zhǎng)按,按鍵連發(fā),按鍵釋放。不知
道大家還記得小時(shí)候玩過(guò)的電子鐘沒(méi)有,就是外形類(lèi)似于CALL 機(jī)(CALL )的那種,有一個(gè)
小液晶屏,還有四個(gè)按鍵,功能是時(shí)鐘,鬧鐘以及秒表。在調(diào)整時(shí)間的時(shí)候,短按+鍵每次
調(diào)整值加一,長(zhǎng)按的時(shí)候調(diào)整值連續(xù)增加。小的時(shí)候很好奇,這樣的功能到底是如何實(shí)現(xiàn)的
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 42
呢,今天就讓我們來(lái)剖析它的原理吧。ϑ機(jī),好像是很古老的東西了
狀態(tài)在生活中隨處可見(jiàn)。譬如早上的時(shí)候,鬧鐘把你叫醒了,這個(gè)時(shí)候,你便處于清醒的狀
態(tài),馬上你就穿衣起床洗漱吃早餐,這一系列事情就是你在這個(gè)狀態(tài)做的事情。做完這些后
你會(huì)去等車(chē)或者開(kāi)車(chē)去上班,這個(gè)時(shí)候你就處在上班途中的狀態(tài)…..中午下班時(shí)間到了,你
就處于中午下班的狀態(tài),諸如此類(lèi)等等,在每一個(gè)狀態(tài)我們都會(huì)做一些不同的事情,而總會(huì)
有外界條件促使我們轉(zhuǎn)換到另外一種狀態(tài),譬如鬧鐘叫醒我們了,下班時(shí)間到了等等。對(duì)于
狀態(tài)的定義出發(fā)點(diǎn)不同,考慮的方向不同,或者會(huì)有些許細(xì)節(jié)上面的差異,但是大的狀態(tài)總
是相同的。生活中的事物同樣遵循同樣的規(guī)律,譬如,用一個(gè)智能充電器給你的手機(jī)電池充
電,剛開(kāi)始,它是處于快速充電狀態(tài),隨著電量的增加,電壓的升高,當(dāng)達(dá)到規(guī)定的電壓時(shí)
候,它會(huì)轉(zhuǎn)換到恒壓充電。總而言之,細(xì)心觀(guān)察,你會(huì)發(fā)現(xiàn)生活中的總總都可以歸結(jié)為一個(gè)
個(gè)的狀態(tài),而狀態(tài)的變換或者轉(zhuǎn)移總是由某些條件引起同時(shí)伴隨著一些動(dòng)作的發(fā)生。我們的
按鍵亦遵循同樣的規(guī)律,下面讓我們來(lái)簡(jiǎn)單的描繪一下它的狀態(tài)流程轉(zhuǎn)移圖。
下面對(duì)上面的流程圖進(jìn)行簡(jiǎn)要的分析。
首先按鍵程序進(jìn)入初始狀態(tài)S1,在這個(gè)狀態(tài)下,檢測(cè)按鍵是否按下,如果有按下,則進(jìn)入
按鍵消抖狀態(tài)2,在下一次執(zhí)行按鍵程序時(shí)候,直接由按鍵消抖狀態(tài)進(jìn)入按鍵按下?tīng)顟B(tài)3,
在此狀態(tài)下檢測(cè)按鍵是否按下,如果沒(méi)有按鍵按下,則返回初始狀態(tài)S1,如果有則可以返
回鍵值,同時(shí)進(jìn)入長(zhǎng)按狀態(tài)S4,在長(zhǎng)按狀態(tài)下每次進(jìn)入按鍵程序時(shí)候?qū)Π存I時(shí)間計(jì)數(shù),當(dāng)
計(jì)數(shù)值超過(guò)設(shè)定閾值時(shí)候,則表明長(zhǎng)按事件發(fā)生,同時(shí)進(jìn)入按鍵連發(fā)狀態(tài)S5。如果按鍵鍵
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 43
值為空鍵,則返回按鍵釋放狀態(tài)S6,否則繼續(xù)停留在本狀態(tài)。在按鍵連發(fā)狀態(tài)下,如果按
鍵鍵值為空鍵則返回按鍵釋放狀態(tài)S6,如果按鍵時(shí)間計(jì)數(shù)超過(guò)連發(fā)閾值,則返回連發(fā)按鍵
值,清零時(shí)間計(jì)數(shù)后繼續(xù)停留在本狀態(tài)。
看了這么多,也許你已經(jīng)有一個(gè)模糊的概念了,下面讓我們趁熱打鐵,一起來(lái)動(dòng)手編寫(xiě)按鍵
驅(qū)動(dòng)程序吧。
下面是我使用的硬件的連接圖。
硬件連接很簡(jiǎn)單,四個(gè)獨(dú)立按鍵分別接在P3^0------P3^3 四個(gè)I/O 上面。
因?yàn)?1 單片機(jī)I/O 口內(nèi)部結(jié)構(gòu)的限制,在讀取外部引腳狀態(tài)的時(shí)候,需要向端口寫(xiě)1.在51
單片機(jī)復(fù)位后,不需要進(jìn)行此操作也可以進(jìn)行讀取外部引腳的操作。因此,在按鍵的端口沒(méi)
有復(fù)用的情況下,可以省略此步驟。而對(duì)于其它一些真正雙向I/O 口的單片機(jī)來(lái)說(shuō),將引腳
設(shè)置成輸入狀態(tài),是必不可少的一個(gè)步驟。
下面的程序代碼初始化引腳為輸入。
void KeyInit(void)
{
io_key_1 = 1 ;
io_key_2 = 1 ;
io_key_3 = 1 ;
io_key_4 = 1 ;
}
根據(jù)按鍵硬件連接定義按鍵鍵值
#define KEY_VALUE_1 0x0e
#define KEY_VALUE_2 0x0d
#define KEY_VALUE_3 0x0b
#define KEY_VALUE_4 0x07
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 44
#define KEY_NULL 0x0f
下面我們來(lái)編寫(xiě)按鍵的硬件驅(qū)動(dòng)程序。
根據(jù)第一章所描述的按鍵檢測(cè)原理,我們可以很容易的得出如下的代碼:
static uint8 KeyScan(void)
{
if(io_key_1 == 0)return KEY_VALUE_1 ;
if(io_key_2 == 0)return KEY_VALUE_2 ;
if(io_key_3 == 0)return KEY_VALUE_3 ;
if(io_key_4 == 0)return KEY_VALUE_4 ;
return KEY_NULL ;
}
其中io_key_1 等是我們按鍵端口的定義,如下所示:
sbit io_key_1 = P3^0 ;
sbit io_key_2 = P3^1 ;
sbit io_key_3 = P3^2 ;
sbit io_key_4 = P3^3 ;
KeyScan()作為底層按鍵的驅(qū)動(dòng)程序,為上層按鍵掃描提供一個(gè)接口,這樣我們編寫(xiě)的上層
按鍵掃描函數(shù)可以幾乎不用修改就可以拿到我們的其它程序中去使用,使得程序復(fù)用性大大
提高。同時(shí),通過(guò)有意識(shí)的將與底層硬件連接緊密的程序和與硬件無(wú)關(guān)的代碼分開(kāi)寫(xiě),使得
程序結(jié)構(gòu)層次清晰,可移植性也更好。對(duì)于單片機(jī)類(lèi)的程序而言,能夠做到函數(shù)級(jí)別的代碼
重用已經(jīng)足夠了。
在編寫(xiě)我們的上層按鍵掃描函數(shù)之前,需要先完成一些宏定義。
//定義長(zhǎng)按鍵的TICK 數(shù),以及連發(fā)間隔的TICK 數(shù)
#define KEY_LONG_PERIOD 100
#define KEY_CONTINUE_PERIOD 25
//定義按鍵返回值狀態(tài)(按下,長(zhǎng)按,連發(fā),釋放)
#define KEY_DOWN 0x80
#define KEY_LONG 0x40
#define KEY_CONTINUE 0x20
#define KEY_UP 0x10
//定義按鍵狀態(tài)
#define KEY_STATE_INIT 0
#define KEY_STATE_WOBBLE 1
#define KEY_STATE_PRESS 2
#define KEY_STATE_LONG 3
#define KEY_STATE_CONTINUE 4
#define KEY_STATE_RELEASE 5
接著我們開(kāi)始編寫(xiě)完整的上層按鍵掃描函數(shù),按鍵的短按,長(zhǎng)按,連按,釋放等等狀態(tài)的判
斷均是在此函數(shù)中完成。對(duì)照狀態(tài)流程轉(zhuǎn)移圖,然后再看下面的函數(shù)代碼,可以更容易的去
理解函數(shù)的執(zhí)行流程。完整的函數(shù)代碼如下:
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 45
void GetKey(uint8 *pKeyValue)
{
static uint8 s_u8KeyState = KEY_STATE_INIT ;
static uint8 s_u8KeyTimeCount = 0 ;
static uint8 s_u8LastKey = KEY_NULL ; //保存按鍵釋放時(shí)候的鍵值
uint8 KeyTemp = KEY_NULL ;
KeyTemp = KeyScan() ; //獲取鍵值
switch(s_u8KeyState)
{
case KEY_STATE_INIT :
{
if(KEY_NULL != (KeyTemp))
{
s_u8KeyState = KEY_STATE_WOBBLE ;
}
}
break ;
case KEY_STATE_WOBBLE : //消抖
{
s_u8KeyState = KEY_STATE_PRESS ;
}
break ;
case KEY_STATE_PRESS :
{
if(KEY_NULL != (KeyTemp))
{
s_u8LastKey = KeyTemp ; //保存鍵值,以便在釋放按鍵狀態(tài)返回鍵值
KeyTemp |= KEY_DOWN ; //按鍵按下
s_u8KeyState = KEY_STATE_LONG ;
}
else
{
s_u8KeyState = KEY_STATE_INIT ;
}
}
break ;
case KEY_STATE_LONG :
{
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 46
if(KEY_NULL != (KeyTemp))
{
if(++s_u8KeyTimeCount > KEY_LONG_PERIOD)
{
s_u8KeyTimeCount = 0 ;
KeyTemp |= KEY_LONG ; //長(zhǎng)按鍵事件發(fā)生
s_u8KeyState = KEY_STATE_CONTINUE ;
}
}
else
{
s_u8KeyState = KEY_STATE_RELEASE ;
}
}
break ;
case KEY_STATE_CONTINUE :
{
if(KEY_NULL != (KeyTemp))
{
if(++s_u8KeyTimeCount > KEY_CONTINUE_PERIOD)
{
s_u8KeyTimeCount = 0 ;
KeyTemp |= KEY_CONTINUE ;
}
}
else
{
s_u8KeyState = KEY_STATE_RELEASE ;
}
}
break ;
case KEY_STATE_RELEASE :
{
s_u8LastKey |= KEY_UP ;
KeyTemp = s_u8LastKey ;
s_u8KeyState = KEY_STATE_INIT ;
}
break ;
default : break ;
}
*pKeyValue = KeyTemp ; //返回鍵值
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 47
}
關(guān)于這個(gè)函數(shù)內(nèi)部的細(xì)節(jié)我并不打算花過(guò)多筆墨去講解。對(duì)照著按鍵狀態(tài)流程轉(zhuǎn)移圖,然后
去看程序代碼,你會(huì)發(fā)現(xiàn)其實(shí)思路非常清晰。最能讓人理解透徹的,莫非就是將整個(gè)程序自
己看懂,然后想象為什么這個(gè)地方要這樣寫(xiě),抱著思考的態(tài)度去閱讀程序,你會(huì)發(fā)現(xiàn)自己的
程序水平會(huì)慢慢的提高。所以我更希望的是你能夠認(rèn)認(rèn)真真的看完,然后思考。也許你會(huì)收
獲更多。
不管怎么樣,這樣的一個(gè)程序已經(jīng)完成了本章開(kāi)始時(shí)候要求的功能:按下,長(zhǎng)按,連按,釋
放。事實(shí)上,如果掌握了這種基于狀態(tài)轉(zhuǎn)移的思想,你會(huì)發(fā)現(xiàn)要求實(shí)現(xiàn)其它按鍵功能,譬如,
多鍵按下,功能鍵等等,亦相當(dāng)簡(jiǎn)單,在下一章,我們就去實(shí)現(xiàn)它。
在主程序中我編寫(xiě)了這樣的一段代碼,來(lái)演示我實(shí)現(xiàn)的按鍵功能。
void main(void)
{
uint8 KeyValue = KEY_NULL;
uint8 temp = 0 ;
LED_CS11 = 1 ; //流水燈輸出允許
LED_SEG = 0 ;
LED_DIG = 0 ;
Timer0Init() ;
KeyInit() ;
EA = 1 ;
while(1)
{
Timer0MainLoop() ;
KeyMainLoop(&KeyValue) ;
if(KeyValue == (KEY_VALUE_1 | KEY_DOWN)) P0 = ~1 ;
if(KeyValue == (KEY_VALUE_1 | KEY_LONG)) P0 = ~2 ;
if(KeyValue == (KEY_VALUE_1 | KEY_CONTINUE)) { P0 ^= 0xf0;}
if(KeyValue == (KEY_VALUE_1 | KEY_UP)) P0 = 0xa5 ;
}
}
按住第一個(gè)鍵,可以清晰的看到P0 口所接的LED 的狀態(tài)的變化。當(dāng)按鍵按下時(shí)候,第
一個(gè)LED 燈亮,等待2 S 后第二個(gè)LED 亮,第一個(gè)熄滅,表示長(zhǎng)按事件發(fā)生。再過(guò)500 ms
第5~8 個(gè)LED 閃爍,表示連按事件發(fā)生。當(dāng)釋放按鍵時(shí)候,P0 口所接的LED 的狀態(tài)為:
滅亮滅亮亮滅亮滅,這也正是P0 = 0xa5 這條語(yǔ)句的功能
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 48
八、綜合應(yīng)用之一——如何設(shè)計(jì)復(fù)雜的多任務(wù)程序
我們?cè)谌腴T(mén)階段,一般面對(duì)的設(shè)計(jì)都是單一的簡(jiǎn)單的任務(wù),流程圖可以如圖1 所示,通
常會(huì)用踏步循環(huán)延時(shí)來(lái)滿(mǎn)足任務(wù)需要。
面對(duì)多任務(wù),稍微復(fù)雜的程序設(shè)計(jì),沿用圖1 的思想,我們會(huì)做出如圖2 所示的程序,
在大循環(huán)體中不斷增加任務(wù),通常還要用延時(shí)來(lái)滿(mǎn)足特定任務(wù)節(jié)拍,這種程序設(shè)計(jì)思想它有
明顯的不足,主要是各個(gè)任務(wù)之間相互影響,增加新的任何之后,以前很好的運(yùn)行的任務(wù)有
可能不正常,例如數(shù)碼管動(dòng)態(tài)掃描,本來(lái)顯示效果很好的驅(qū)動(dòng)函數(shù),在增加新的任務(wù)后出現(xiàn)
閃爍,顯示效果變差了。
(原文件名:1.JPG)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 49
引用圖片
圖1 單一任務(wù)簡(jiǎn)單流程圖圖2 多任務(wù)簡(jiǎn)單流程圖
很明顯,初學(xué)者在設(shè)計(jì)程序時(shí),需要從程序構(gòu)架思想上下功夫,在做了大量基本模塊練
習(xí)之后,需要總結(jié)提煉自己的程序設(shè)計(jì)思路(程序架構(gòu)思想)。
首先我們來(lái)理解“任務(wù)”,所謂任務(wù),就是需要CPU 周期“關(guān)照”的事件,絕大多數(shù)任
務(wù)不需要CPU 一直“關(guān)照” ,例如啟動(dòng)ADC 的啟動(dòng)讀取。甚至有些任務(wù)“害怕”CPU 一直
“關(guān)照”例如LCD 的刷新,因?yàn)長(zhǎng)CD 是顯示給人看的,并不需要高速刷新,即便是顯示的
內(nèi)容在高速變化,也不需要高速刷新,道理是一樣的。這樣看來(lái),讓CPU 做簡(jiǎn)單任務(wù)一定
很
浪費(fèi),事實(shí)也是如此,絕大多數(shù)簡(jiǎn)單任務(wù),CPU 都是在“空轉(zhuǎn)” (循環(huán)踏步延時(shí)) 。對(duì)任務(wù)
總
結(jié)還可以知道,很多任務(wù)需要CPU 不斷“關(guān)照” ,其實(shí)這種“不斷”也是有極限的,比如數(shù)
碼管動(dòng)態(tài)掃描,能夠做到40Hz 就可以了,又如鍵盤(pán)掃描,能夠做到20Hz(經(jīng)驗(yàn)值),基本
上
也就不會(huì)丟有效按鍵鍵值了,再如LCD 刷新,我覺(jué)得做到10Hz 就可以了,等等。看來(lái),
絕
大多數(shù)任務(wù)都是工作在低速頻度。而我們的CPU 一旦運(yùn)行起來(lái),速度又很快,CPU 本身就
是
靠很快的速度執(zhí)行很簡(jiǎn)單的指令來(lái)勝任復(fù)雜的任務(wù)(邏輯)的。如果有辦法把“快”的CPU
分成多個(gè)慢的CPU,然后給不同的任務(wù)分配不同速度的CPU,這種設(shè)想是不是很好呢!確
實(shí)
很好,下面就看如何將“快”的CPU 劃分成多個(gè)“慢”的CPU。
根據(jù)這種想法,我們需要合理分配CPU 資源來(lái)“關(guān)照”不同的任務(wù),最好能夠根據(jù)任務(wù)
本身合理占用CPU 資源,首先看如圖3 所示的流程圖,各個(gè)任務(wù)流程獨(dú)立,各任務(wù)通過(guò)全
局
變量來(lái)交互信息,在流程中有一個(gè)重要的模塊“任務(wù)切換”,就是任務(wù)切換模塊實(shí)現(xiàn)CPU 合
理分配,這個(gè)任務(wù)切換模塊是怎么實(shí)現(xiàn)的呢?
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 50
(原文件名:2.JPG)
引用圖片
圖3 多任務(wù)復(fù)雜流程圖
首先需要理解,CPU 一旦運(yùn)行起來(lái),就無(wú)法停止(硬件支持時(shí)鐘停止的不在這里討論),
誰(shuí)能夠控制一批脫韁的馬呢?對(duì)了,有中斷,中斷能夠讓CPU 回到特定的位置,設(shè)想,能
不
能用一個(gè)定時(shí)中斷,周期性的將CPU 這匹運(yùn)行著的脫韁的馬召喚回來(lái),重新給它安排特定
的
任務(wù),事實(shí)上,任務(wù)切換就是這樣實(shí)現(xiàn)的。
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 51
(原文件名:3.JPG)
引用圖片
圖4 定時(shí)中斷實(shí)現(xiàn)任務(wù)切換
如圖4A 所示,CPU 在空閑任務(wù)循環(huán)等待,定時(shí)中斷將CPU 周期性喚回,根據(jù)任務(wù)設(shè)計(jì)
了不同的響應(yīng)頻度,滿(mǎn)足條件的任務(wù)將獲得CPU 資源,CPU 為不同任務(wù)“關(guān)照”完成后,再
次返回空閑任務(wù),如此周而復(fù)始,對(duì)于各個(gè)任務(wù)而言,好像各自擁有一個(gè)獨(dú)立的CPU,各
自
獨(dú)立運(yùn)行。用這種思想構(gòu)建的程序框架,最大的好處是任務(wù)很容易裁剪,系統(tǒng)能夠做得很復(fù)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 52
雜。
在充分考慮單片機(jī)中斷特性(在哪里中斷就返回到哪里)后,實(shí)際可行的任務(wù)切換如圖
4B 所示,定時(shí)中斷可能發(fā)生在任務(wù)調(diào)度,隨機(jī)任務(wù)執(zhí)行的任何時(shí)候,圖中最大的框框所示,
不管中斷在何時(shí)發(fā)生,它都會(huì)正常返回,定時(shí)中斷所產(chǎn)生的影響只在任務(wù)調(diào)度模塊起作用,
即依次讓不同的任務(wù)按不同的節(jié)拍就緒。任務(wù)調(diào)度會(huì)按一定的優(yōu)先級(jí)執(zhí)行就緒任務(wù)。
總結(jié)不同的任務(wù)需要CPU 關(guān)照的頻度,選擇最快的那個(gè)頻度來(lái)設(shè)定定時(shí)器中斷的節(jié)拍,
一般選擇200Hz,或者100Hz 都可以。另外再給每個(gè)任務(wù)設(shè)定一個(gè)節(jié)拍控制計(jì)數(shù)器C,也就
是定時(shí)器每中斷多少次后執(zhí)行任務(wù)一次。例如取定時(shí)中斷節(jié)拍為200Hz,給任務(wù)設(shè)定的C=
10,
則任務(wù)執(zhí)行頻度為200/10=20Hz,如果是數(shù)碼管掃描,按40Hz 不閃爍規(guī)律,則任務(wù)節(jié)拍控
制
計(jì)數(shù)器C=5 即可。在程序設(shè)計(jì)中,C 代表著任務(wù)運(yùn)行的節(jié)拍控制參數(shù),我們習(xí)慣用delay 來(lái)
描述,不同的任務(wù)用task0,task1……來(lái)描述。
明天繼續(xù)寫(xiě)如何用代碼實(shí)現(xiàn)!2009-6-29
下面我們來(lái)用代碼實(shí)現(xiàn)以上多任務(wù)程序設(shè)計(jì)思想。
首先是任務(wù)切換
while(1)
{
if(task_delay[0]==0) task0(); //task0 就緒,
if(task_delay[1]==0) task1(); //task1 就緒,
……
}
很顯然,執(zhí)行任務(wù)的條件是任務(wù)延時(shí)量task_delay=0,那么任務(wù)延時(shí)量誰(shuí)來(lái)控制呢?定時(shí)
器啊!定時(shí)器中斷對(duì)任務(wù)延時(shí)量減一直到歸零,標(biāo)志任務(wù)就緒。當(dāng)沒(méi)有任務(wù)就緒時(shí),任務(wù)切
換本身就是一個(gè)Idle 任務(wù)。
void timer0(void) interrupt 1
{
if(task_delay[0]) task_delay[0]--;
if(task_delay[1]) task_delay[1]--;
……
}
例如timer0 的中斷節(jié)拍為200Hz,task0_delay 初值為10,則task0()執(zhí)行頻度為
200/10=20Hz。
有了以上基礎(chǔ),我們來(lái)設(shè)計(jì)一個(gè)簡(jiǎn)單多任務(wù)程序,進(jìn)一步深入理解這種程序設(shè)計(jì)思想。
任務(wù)要求:用單片機(jī)不同IO 腳輸出1Hz,5Hz,10Hz,20Hz 方波信號(hào),這個(gè)程序很短,將
直接給出。
#include "reg51.h"
#define TIME_PER_SEC 200 //定義任務(wù)時(shí)鐘頻率,200Hz
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 53
#define CLOCK 22118400 //定義時(shí)鐘晶振,單位Hz
#define MAX_TASK 4 //定義任務(wù)數(shù)量
extern void task0(void); //任務(wù)聲明
extern void task1(void);
extern void task2(void);
extern void task3(void);
sbit f1Hz = P1^0; //端口定義
sbit f5Hz = P1^1;
sbit f10Hz = P1^2;
sbit f20Hz = P1^3;
unsigned char task_delay[4]; //任務(wù)延時(shí)變量定義
//定時(shí)器0 初始化
void timer0_init(void)
{
unsigned char i;
for(i=0;i
TMOD = (TMOD & 0XF0) | 0X01; //定時(shí)器0 工作在模式1, 16Bit 定時(shí)器模
式
TH0 = 255-CLOCK/TIME_PER_SEC/12/256;
TL0 = 255-CLOCK/TIME_PER_SEC/12%256;
TR0 =1;
ET0 =1; //開(kāi)啟定時(shí)器和中斷
}
// 系統(tǒng)OS 定時(shí)中斷服務(wù)
void timer0(void) interrupt 1
{
unsigned char i;
TH0 = 255-CLOCK/TIME_PER_SEC/12/256;
TL0 = 255-CLOCK/TIME_PER_SEC/12%256;
for(i=0;i
//每節(jié)拍對(duì)任務(wù)延時(shí)變量減1 ,減至0 后,任務(wù)就緒。
}
/*main 主函數(shù)*/
void main(void)
{
timer0_init();
EA=1;//開(kāi)總中斷
while(1)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 54
{
if(task_delay[0]==0) {task0(); task_delay[0] = TIME_PER_SEC/ 2;}
//要產(chǎn)生1hz 信號(hào),翻轉(zhuǎn)周期就是2Hz,以下同
if(task_delay[1]==0) {task1(); task_delay[1] = TIME_PER_SEC/10;}
//要產(chǎn)生5hz 信號(hào),翻轉(zhuǎn)周期就是10Hz,以下同
if(task_delay[2]==0) {task2(); task_delay[2] = TIME_PER_SEC/20;}
if(task_delay[3]==0) {task3(); task_delay[3] = TIME_PER_SEC/40;}
}
}
void task0(void)
{
f1Hz = !f1Hz;
}
void task1(void)
{
f5Hz = !f5Hz;
}
void task2(void)
{
f10Hz = !f10Hz;
}
void task3(void)
{
f20Hz = !f20Hz;
}
仿真效果如圖5 所示。
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 55
(原文件名:4.JPG)
引用圖片
圖5 仿真波形圖
同樣的程序,同學(xué)們可以考慮用圖2 所示的思想設(shè)計(jì),看看容易不容易,如果你的程序
實(shí)現(xiàn)了相同的功能,如果我改變要求,改變信號(hào)的頻率,你的程序容易修改嗎?
要進(jìn)一步完善這種程序設(shè)計(jì)思想,有幾個(gè)問(wèn)題還需要考慮:
對(duì)任務(wù)本身有什么要求?
不同任務(wù)之間有沒(méi)有優(yōu)先級(jí)?(不同的事情總有個(gè)輕重緩急吧!)
任務(wù)間如何延時(shí)?
……
為了回答這些問(wèn)題,下面我們來(lái)分析CPU 的運(yùn)行情況。
(原文件名:5.JPG)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 56
引用圖片
圖6 CPU 運(yùn)行情況示意圖
CPU 運(yùn)行情況如圖6 所示,黑色區(qū)域表示CPU 進(jìn)程,系統(tǒng)啟動(dòng)后, CPU 將無(wú)休止的運(yùn)行,
CPU 資源將如何分配呢?程序首先進(jìn)入“任務(wù)切換”進(jìn)程,如果當(dāng)前沒(méi)有任務(wù)就緒,就在任
務(wù)切換進(jìn)程循環(huán)(也可以理解為空閑進(jìn)程),定時(shí)中斷將CPU 當(dāng)前進(jìn)程打斷,在定時(shí)中斷進(jìn)
程可能讓某些任務(wù)就緒,中斷返回任務(wù)切換進(jìn)程,很快會(huì)進(jìn)入就緒任務(wù)0,CPU“關(guān)照”完
任務(wù)0,再次回到任務(wù)切換進(jìn)程,如果還有其它任務(wù)就緒,還會(huì)再次進(jìn)入其它任務(wù),沒(méi)有任
務(wù)就循環(huán)等待,定時(shí)中斷會(huì)不斷讓新的任務(wù)就緒,CPU 也會(huì)不斷進(jìn)入任務(wù)“關(guān)照” 。這樣不
同的任務(wù)就會(huì)獲得不同的CPU 資源,每一個(gè)任務(wù)都像是擁有一個(gè)獨(dú)立的CPU 為之服務(wù)。
從這種進(jìn)程切換我們可以看出,在定時(shí)中斷和任務(wù)切換過(guò)程中,額外的占用了一些CPU
資源, 這就是定時(shí)中斷頻度不宜太快, 否則將大大降低CPU 的有效資源率, 當(dāng)然太慢也
不行。
另外就是CPU 每次關(guān)照任務(wù)的時(shí)間不能太長(zhǎng),如果超過(guò)一個(gè)中斷周期,就會(huì)影響到其它任
務(wù)
的實(shí)時(shí)性。所謂的實(shí)時(shí)性就是按定時(shí)中斷設(shè)定的節(jié)拍,準(zhǔn)時(shí)得到CPU 關(guān)照。這樣,每一個(gè)
子
任務(wù)就必須簡(jiǎn)單,每次“關(guān)照”時(shí)間最好不要超過(guò)定時(shí)中斷節(jié)拍周期(5ms 或10ms,初學(xué)者
要對(duì)ms 有一個(gè)概念,機(jī)器周期為us 級(jí)的單片機(jī),1ms 可以執(zhí)行上千條指令,對(duì)于像數(shù)碼管
掃描,鍵盤(pán)掃描,LCD 顯示等常規(guī)任務(wù)都是綽綽有余的,只是遇到大型計(jì)算,數(shù)據(jù)排序就
顯
得短了)
關(guān)于任務(wù)優(yōu)先級(jí)的問(wèn)題:一個(gè)復(fù)雜系統(tǒng),多個(gè)任務(wù)之間總有“輕重緩急”之區(qū)別,那些
需要嚴(yán)格實(shí)時(shí)的任務(wù)通常用中斷實(shí)現(xiàn),中斷能夠保證第一時(shí)間相應(yīng),我們這里討論的不是那
種實(shí)時(shí)概念,是指在最大允許時(shí)差內(nèi)能夠得到CPU“關(guān)照” ,例如鍵盤(pán)掃描,為了保證較好
的操作效果,快的/慢的/長(zhǎng)的/短的(不同人按鍵不一樣)都能夠正確識(shí)別,這就要保證足夠
的掃描速度,這種掃描速度對(duì)不同的按鍵最好均等,如果我們按50Hz 來(lái)設(shè)計(jì),那么就要保
證鍵盤(pán)掃描速度在任何情況下都能夠做到50Hz 掃描頻度,不會(huì)因?yàn)槟硞(gè)新任務(wù)的開(kāi)啟而被
破壞,如果確實(shí)有新的任務(wù)有可能破壞這個(gè)50Hz 掃描頻度,我們就應(yīng)該在優(yōu)先級(jí)安排上讓
鍵盤(pán)掃描優(yōu)先級(jí)高于那個(gè)可能影響鍵盤(pán)掃描的任務(wù)。這里體現(xiàn)的就是當(dāng)同時(shí)多個(gè)任務(wù)就緒
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 57
時(shí),
最先執(zhí)行哪個(gè)的問(wèn)題,任務(wù)調(diào)度時(shí)要優(yōu)先執(zhí)行級(jí)別高的任務(wù)。
關(guān)于“長(zhǎng)”任務(wù)的問(wèn)題:有些任務(wù)雖然很獨(dú)立,但完成一次任務(wù)執(zhí)行需要很長(zhǎng)時(shí)間,例
如DS18B20,從復(fù)位初始化到讀回溫度值,最長(zhǎng)接近1s,這主要是DS18B20 溫度傳感器完
成一次溫度轉(zhuǎn)換需要500 到750ms,這個(gè)時(shí)間對(duì)CPU 而言,簡(jiǎn)直是太長(zhǎng)了,就像一件事情
需
要我們?nèi)说却?0 年一樣,顯然這樣的任務(wù)是其它任務(wù)所耽擱不起的。像類(lèi)似DS18B20 這樣
的器件(不少ADC 也是這樣) ,怎么設(shè)計(jì)任務(wù)體解決“長(zhǎng)”的問(wèn)題。進(jìn)一步研究這些器件發(fā)
現(xiàn),真正需要CPU“關(guān)照”它們的時(shí)間并不長(zhǎng),關(guān)鍵是等待結(jié)果要很長(zhǎng)時(shí)間。解決的辦法就
是把類(lèi)似的器件驅(qū)動(dòng)分成多個(gè)段:初始化段、啟動(dòng)段、讀結(jié)果段,而在需要花長(zhǎng)時(shí)間等待時(shí)
間段,不要CPU 關(guān)照,允許CPU 去關(guān)照其它任務(wù)。
將一個(gè)任務(wù)分成若干段,確保每段需要CPU 關(guān)照時(shí)長(zhǎng)小于定時(shí)器
中斷節(jié)拍長(zhǎng),這樣CPU 在處理這些長(zhǎng)任務(wù)時(shí),就不會(huì)影響到其它任務(wù)的執(zhí)行。
Easy51RTOS
正是基于以上程序設(shè)計(jì)思想,總結(jié)完善后提出一種耗費(fèi)資源特別少并且不使用堆棧的多
線(xiàn)程操作系統(tǒng),這個(gè)操作系統(tǒng)以純C 語(yǔ)言實(shí)現(xiàn),無(wú)硬件依賴(lài)性,需要單片機(jī)的資源極少。
起
名為Easy51RTOS,特別適合初學(xué)者學(xué)習(xí)使用。有任務(wù)優(yōu)先級(jí),通過(guò)技巧可以任務(wù)間延時(shí),
缺點(diǎn)是高優(yōu)先級(jí)任務(wù)不具有搶占功能,一個(gè)具有搶占功能的操作系統(tǒng),一定要涉及到現(xiàn)場(chǎng)保
護(hù)與恢復(fù),需要更多的RAM 資源,涉及到堆棧知識(shí),文件系統(tǒng)將很復(fù)雜,初學(xué)者學(xué)習(xí)難度
大。
為了便于初學(xué)者學(xué)習(xí),將代碼文件壓縮至4 個(gè)文件。
Easy51RTOS.Uv2 Keil 工程文件,KEIL 用戶(hù)很熟悉的
main.c main 函數(shù)和用戶(hù)任務(wù)task 函數(shù)文件
os_c.c Easy51RTOS 相關(guān)函數(shù)文件
os_cfg.h Easy51RTOS 相關(guān)配置參數(shù)頭文件
文件解讀如下:
os_cfg.h
#include "reg51.h"
#define TIME_PER_SEC 200 //定義任務(wù)時(shí)鐘頻率,200Hz
#define CLOCK 22118400 //定義時(shí)鐘晶振,單位Hz
#define MAX_TASK 4 //定義任務(wù)數(shù)量
//函數(shù)變量聲明,在需要用以下函數(shù)或變量的文件中包含此頭文件即可
extern void task0(void);
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 58
extern void task1(void);
extern void task2(void);
extern void task3(void);
extern unsigned char task_delay[MAX_TASK];
extern void run(void (*ptask)());
extern void os_timer0_init(void);
os_c.c
#include "os_cfg.h"
unsigned char task_delay[MAX_TASK]; //定義任務(wù)延時(shí)量變量
//定時(shí)器0 初始化
void os_timer0_init(void)
{
unsigned char i;
for(i=0;i
TMOD = (TMOD & 0XF0) | 0X01; //定時(shí)器0 工作在模式1,16Bit 定時(shí)器模式
TH0 = 255-CLOCK/TIME_PER_SEC/12/256;
//CRY_OSC,TIME_PER_SEC 在os_cfg.h 中定義
TL0 = 255-CLOCK/TIME_PER_SEC/12%256;
TR0 =1;
ET0 =1; //開(kāi)啟定時(shí)器和中斷
}
// 系統(tǒng)OS 定時(shí)中斷服務(wù)
void os_timer0(void) interrupt 1
{
unsigned char i;
TH0 = 255-CLOCK/TIME_PER_SEC/12/256;
TL0 = 255-CLOCK/TIME_PER_SEC/12%256;
for(i=0;i
//每節(jié)拍對(duì)任務(wù)延時(shí)變量減1 ,減至0 后,任務(wù)就緒。
}
//指向函數(shù)的指針函數(shù)
void run(void (*ptask)())
{
(*ptask)();
}
main.c
#include "os_cfg.h"
#define TASK_DELAY0 TIME_PER_SEC/1 //任務(wù)執(zhí)行頻度為1Hz
#define TASK_DELAY1 TIME_PER_SEC/2 //任務(wù)執(zhí)行頻度為2Hz
#define TASK_DELAY2 TIME_PER_SEC/10 //任務(wù)執(zhí)行頻度為10Hz
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 59
#define TASK_DELAY3 TIME_PER_SEC/20 //任務(wù)執(zhí)行頻度為20Hz
void (* code task[])() = {task0,task1,task2,task3}; //獲得任務(wù)PC 指針
sbit LED0 = P1^0; //演示用LED 接口定義
sbit LED1 = P1^1;
sbit LED2 = P1^2;
sbit LED3 = P1^3;
/*main 主函數(shù)*/
void main(void)
{
unsigned char i;
os_timer0_init(); //節(jié)拍發(fā)生器定時(shí)器初始化
EA = 1; //開(kāi)總中斷
while(1)
{
for(i=0;i
if (task_delay[i]==0) {run(task[i]); break;} //就緒任務(wù)調(diào)度
} //上一行break 有特殊作用,詳細(xì)解釋見(jiàn)后文
}
void task0(void) //任務(wù)0
{
LED0 = !LED0;
task_delay[0] = TASK_DELAY0;
}
void task1(void) //任務(wù)1
{
LED1 = !LED1;
task_delay[1] = TASK_DELAY1;
}
void task2(void) //任務(wù)2
{
LED2 = !LED2;
task_delay[2] = TASK_DELAY2;
}
void task3(void) //任務(wù)內(nèi)分段設(shè)計(jì)
{
static unsigned char state=0; //定義靜態(tài)局部變量
switch (state)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 60
{
case 0:
LED3 = !LED3;
state = 1;
task_delay[3] = TASK_DELAY3;
break;
case 1:
LED3 = !LED3;
state = 2;
task_delay[3] = TASK_DELAY3*2;
break;
case 2:
LED3 = !LED3;
state = 0;
task_delay[3] = TASK_DELAY3*4;
break;
default:
state = 0;
task_delay[3] = TASK_DELAY3;
break;
}
}
仿真圖如圖8 所示
(原文件名:6.JPG)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 61
引用圖片
圖8 仿真波形圖
主程序巧妙實(shí)現(xiàn)優(yōu)先級(jí)設(shè)定:
for(i=0;i
if (task_delay[i]==0) {run(task[i]); break;} //就緒任務(wù)調(diào)度
這里的break 將跳出for 循環(huán),使得每次重新任務(wù)調(diào)度總是從task0 開(kāi)始,就意味著優(yōu)先
級(jí)高的任務(wù)就緒會(huì)先執(zhí)行。這樣task0 具有最高優(yōu)先級(jí),task1、task2、task3 優(yōu)先級(jí)依次降
低。
特別是void task3(void)用switch(state)狀態(tài)機(jī)實(shí)現(xiàn)了任務(wù)分段,這也是任務(wù)內(nèi)系統(tǒng)延時(shí)的
一種方法。
我會(huì)繼續(xù)更新的。。。。。。。。。。。
九、綜合應(yīng)用之二——DS1320/DS18B20 應(yīng)用
好幾天沒(méi)有更新了,呵呵~~今天我把咱們常用的傳感器DS1320 DS18B20 給大家介紹下。
對(duì)于市面上的大多數(shù)51 單片機(jī)開(kāi)發(fā)板來(lái)說(shuō)。ds1302 和ds18b20 應(yīng)該是比較常見(jiàn)的兩種外圍
芯片。ds1302 是具有SPI 總線(xiàn)接口的時(shí)鐘芯片。ds18b20 則是具有單總線(xiàn)接口的數(shù)字溫度傳
感器。下面讓我們分別來(lái)認(rèn)識(shí)并學(xué)會(huì)應(yīng)用這兩種芯片。
首先依舊是看DS1302 的datasheet 中的相關(guān)介紹。
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 62
(原文件名:1.jpg)
引用圖片
上面是它的一些基本的應(yīng)用介紹。
下面是它的引腳的描述。
(原文件名:2.jpg)
引用圖片
下面是DS1302 的時(shí)鐘寄存器。我們要讀取的時(shí)間數(shù)據(jù)就是從下面這些數(shù)據(jù)寄存器中讀取出
來(lái)的。當(dāng)我們要想調(diào)整時(shí)間時(shí),可以把時(shí)間數(shù)據(jù)寫(xiě)入到相應(yīng)的寄存器中就可以了。
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 63
(原文件名:3.jpg)
引用圖片
這是DS1302 內(nèi)部的31 個(gè)RAM 寄存器。在某些應(yīng)用場(chǎng)合我們可以應(yīng)用到。如我們想要做
一個(gè)帶定時(shí)功能的鬧鐘。則可以把鬧鐘的時(shí)間寫(xiě)入到31 個(gè)RAM 寄存器中的任意幾個(gè)。當(dāng)
單片機(jī)掉電時(shí),只要我們的DS1302 的備用電池還能工作,那么保存在其中的鬧鐘數(shù)據(jù)就不
會(huì)丟失~~
(原文件名:4.jpg)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 64
引用圖片
由于對(duì)于這些器件的操作基本上按照數(shù)據(jù)手冊(cè)上面提供的時(shí)序圖和相關(guān)命令字來(lái)進(jìn)行操作
就可以了。因此在我們應(yīng)用這些器件的時(shí)候一定要對(duì)照著手冊(cè)上面的要求來(lái)進(jìn)行操作。如果
覺(jué)得還不夠放心的話(huà)。可以到網(wǎng)上下載一些參考程序。對(duì)著手冊(cè)看別人的程序,看別人的思
路是怎么樣的。
DS1302 和單片機(jī)的連接很簡(jiǎn)單。只需一根復(fù)位線(xiàn),一根時(shí)鐘線(xiàn),一根數(shù)據(jù)線(xiàn)即可。同時(shí)
它本身還需要接一個(gè)32.768KHz 的晶振來(lái)提供時(shí)鐘源。對(duì)于晶振的兩端可以分別接一個(gè)6PF
左右的電容以提高晶振的精確度。同時(shí)可以在第8 腳接上一個(gè)3.6V 的可充電的電池。當(dāng)系
統(tǒng)正常工作時(shí)可以對(duì)電池進(jìn)行涓流充電。當(dāng)系統(tǒng)掉電時(shí),DS1302 由這個(gè)電池提供的能量繼
續(xù)工作。
下面讓我們來(lái)驅(qū)動(dòng)它。
sbit io_DS1302_RST = P2^0 ;
sbit io_DS1302_IO = P2^1 ;
sbit io_DS1302_SCLK = P2^2 ;
//-------------------------------------常數(shù)宏---------------------------------//
#define DS1302_SECOND_WRITE 0x80 //寫(xiě)時(shí)鐘芯片的寄存器位置
#define DS1302_MINUTE_WRITE 0x82
#define DS1302_HOUR_WRITE 0x84
#define DS1302_WEEK_WRITE 0x8A
#define DS1302_DAY_WRITE 0x86
#define DS1302_MONTH_WRITE 0x88
#define DS1302_YEAR_WRITE 0x8C
#define DS1302_SECOND_READ 0x81 //讀時(shí)鐘芯片的寄存器位置
#define DS1302_MINUTE_READ 0x83
#define DS1302_HOUR_READ 0x85
#define DS1302_WEEK_READ 0x8B
#define DS1302_DAY_READ 0x87
#define DS1302_MONTH_READ 0x89
#define DS1302_YEAR_READ 0x8D
//-----------------------------------操作宏----------------------------------//
#define DS1302_SCLK_HIGH io_DS1302_SCLK = 1 ;
#define DS1302_SCLK_LOW io_DS1302_SCLK = 0 ;
#define DS1302_IO_HIGH io_DS1302_IO = 1 ;
#define DS1302_IO_LOW io_DS1302_IO = 0 ;
#define DS1302_IO_READ io_DS1302_IO
#define DS1302_RST_HIGH io_DS1302_RST = 1 ;
#define DS1302_RST_LOW io_DS1302_RST = 0 ;
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 65
/******************************************************
* 保存時(shí)間數(shù)據(jù)的結(jié)構(gòu)體*
******************************************************/
struct
{
uint8 Second ;
uint8 Minute ;
uint8 Hour ;
uint8 Day ;
uint8 Week ;
uint8 Month ;
uint8 Year ;
}CurrentTime ;
/******************************************************************************
* Function: static void v_DS1302Write_f( uint8 Content ) *
* Description:向DS1302 寫(xiě)一個(gè)字節(jié)的內(nèi)容*
* Parameter:uint8 Content : 要寫(xiě)的字節(jié)*
* *
******************************************************************************/
static void v_DS1302Write_f( uint8 Content )
{
uint8 i ;
for( i = 8 ; i > 0 ; i-- )
{
if( Content & 0x01 )
{
DS1302_IO_HIGH
}
else
{
DS1302_IO_LOW
}
Content >>= 1 ;
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 66
DS1302_SCLK_HIGH
DS1302_SCLK_LOW
}
}
/******************************************************************************
* Function: static uint8 v_DS1302Read_f( void ) *
* Description: 從DS1302 當(dāng)前設(shè)定的地址讀取一個(gè)字節(jié)的內(nèi)容*
* Parameter: *
* Return: 返回讀出來(lái)的值(uint8) *
******************************************************************************/
static uint8 v_DS1302Read_f( void )
{
uint8 i, ReadValue ;
DS1302_IO_HIGH
for( i = 8 ; i > 0 ; i-- )
{
ReadValue >>= 1 ;
if( DS1302_IO_READ )
{
ReadValue |= 0x80 ;
}
else
{
ReadValue &= 0x7f ;
}
DS1302_SCLK_HIGH
DS1302_SCLK_LOW
}
return ReadValue ;
}
/******************************************************************************
* Function: void v_DS1302WriteByte_f( uint8 Address, uint8 Content ) *
* Description: 從DS1302 指定的地址寫(xiě)入一個(gè)字節(jié)的內(nèi)容*
* Parameter: Address: 要寫(xiě)入數(shù)據(jù)的地址*
* Content: 寫(xiě)入數(shù)據(jù)的具體值*
* Return: *
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 67
******************************************************************************/
void v_DS1302WriteByte_f( uint8 Address, uint8 Content )
{
DS1302_RST_LOW
DS1302_SCLK_LOW
DS1302_RST_HIGH
v_DS1302Write_f( Address ) ;
v_DS1302Write_f( Content ) ;
DS1302_RST_LOW
DS1302_SCLK_HIGH
}
/******************************************************************************
* Function: uint8 v_DS1302ReadByte_f( uint8 Address ) *
* Description:從DS1302 指定的地址讀出一個(gè)字節(jié)的內(nèi)容*
* Parameter:Address: 要讀出數(shù)據(jù)的地址*
* *
* Return: 指定地址讀出的值(uint8) *
******************************************************************************/
uint8 v_DS1302ReadByte_f( uint8 Address )
{
uint8 ReadValue ;
DS1302_RST_LOW
DS1302_SCLK_LOW
DS1302_RST_HIGH
v_DS1302Write_f( Address ) ;
ReadValue = v_DS1302Read_f() ;
DS1302_RST_LOW
DS1302_SCLK_HIGH
return ReadValue ;
}
/******************************************************************************
* Function: void v_ClockInit_f( void ) *
* Description:初始化寫(xiě)入DS1302 時(shí)鐘寄存器的值(主程序中只需調(diào)用一次即可) *
* Parameter:
*
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 68
* *
* Return: *
******************************************************************************/
void v_ClockInit_f( void )
{
if( v_DS1302ReadByte_f( 0xc1) != 0xf0 )
{
v_DS1302WriteByte_f( 0x8e, 0x00 ) ; //允許寫(xiě)操作
v_DS1302WriteByte_f( DS1302_YEAR_WRITE, 0x08 ) ; //年
v_DS1302WriteByte_f( DS1302_WEEK_WRITE, 0x04 ) ; //星期
v_DS1302WriteByte_f( DS1302_MONTH_WRITE, 0x12 ) ; //月
v_DS1302WriteByte_f( DS1302_DAY_WRITE, 0x11 ) ; //日
v_DS1302WriteByte_f( DS1302_HOUR_WRITE, 0x13 ) ; //小時(shí)
v_DS1302WriteByte_f( DS1302_MINUTE_WRITE, 0x06 ) ; //分鐘
v_DS1302WriteByte_f( DS1302_SECOND_WRITE, 0x40 ) ; //秒
v_DS1302WriteByte_f( 0x90, 0xa5 ) ; //充電
v_DS1302WriteByte_f( 0xc0, 0xf0 ) ; //判斷是否初始化一次標(biāo)識(shí)寫(xiě)入
v_DS1302WriteByte_f( 0x8e, 0x80 ) ; //禁止寫(xiě)操作
}
}
/******************************************************************************
* Function: void v_ClockUpdata_f( void ) *
* Description:讀取時(shí)間數(shù)據(jù),并保存在結(jié)構(gòu)體CurrentTime 中*
* Parameter:
*
* *
* Return: *
******************************************************************************/
void v_ClockUpdata_f( void )
{
CurrentTime.Second = v_DS1302ReadByte_f( DS1302_SECOND_READ ) ;
CurrentTime.Minute = v_DS1302ReadByte_f( DS1302_MINUTE_READ ) ;
CurrentTime.Hour = v_DS1302ReadByte_f( DS1302_HOUR_READ ) ;
CurrentTime.Day = v_DS1302ReadByte_f( DS1302_DAY_READ ) ;
CurrentTime.Month = v_DS1302ReadByte_f( DS1302_MONTH_READ ) ;
CurrentTime.Week = v_DS1302ReadByte_f( DS1302_WEEK_READ ) ;
CurrentTime.Year = v_DS1302ReadByte_f( DS1302_YEAR_READ ) ;
}
有了上面的這些函數(shù)我們就可以對(duì)DS1302 進(jìn)行操作了。當(dāng)我們想要獲取當(dāng)前時(shí)間時(shí),只需
要調(diào)用v_ClockUpdata_f( void )這個(gè)函數(shù)即可。讀取到的時(shí)間數(shù)據(jù)保存在CurrentTime 這個(gè)結(jié)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 69
構(gòu)體中。至于如何把時(shí)間數(shù)據(jù)在數(shù)碼管或者是液晶屏上顯示出來(lái)我相信大家應(yīng)該都會(huì)了吧
^_^.
看看顯示效果如何~~
(原文件名:5.jpg)
引用圖片
下面再讓我們看看DS18B20 吧。
DS18B20 是單總線(xiàn)的數(shù)字溫度傳感器。其與單片機(jī)的接口只需要一根數(shù)據(jù)線(xiàn)即可。當(dāng)然連
線(xiàn)簡(jiǎn)單意味著軟件處理上可能要麻煩一點(diǎn)。下面來(lái)看看它的優(yōu)點(diǎn):
(原文件名:1.jpg)
引用圖片
看看它的靚照。外形和我們常用的三極管沒(méi)有什么兩樣哦。
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 70
(原文件名:2.jpg)
引用圖片
DS18B20 的內(nèi)部存儲(chǔ)器分為以下幾部分
ROM:存放該器件的編碼。前8 位為單線(xiàn)系列的編碼(DS18B20 的編碼是19H)后面48 位為芯
片的唯一序列號(hào)。在出場(chǎng)的時(shí)候就已經(jīng)設(shè)置好,用戶(hù)無(wú)法更改。最后8 位是以上56 位的CRC
碼。
(原文件名:3.jpg)
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 71
引用圖片
RAM:DS18B20 的內(nèi)部暫存器共9 個(gè)字節(jié)。其中第一個(gè)和第二個(gè)字節(jié)存放轉(zhuǎn)換后的溫度值。
第二個(gè)和第三個(gè)字節(jié)分別存放高溫和低溫告警值。(可以用RAM 指令將其拷貝到EEPROM
中)第四個(gè)字節(jié)為配置寄存器。第5~7 個(gè)字節(jié)保留。第9 個(gè)字節(jié)為前8 個(gè)字節(jié)的CRC 碼。
DS18B20 的溫度存放如上圖所示。其中S 位符號(hào)位。當(dāng)溫度值為負(fù)值時(shí),S = 1 ,反之則S = 0 。
我們把得到的溫度數(shù)據(jù)乘上對(duì)應(yīng)的分辨率即可以得到轉(zhuǎn)換后的溫度值。
DS18B20 的通訊協(xié)議:
在對(duì)DS18B20 進(jìn)行讀寫(xiě)編程時(shí),必須嚴(yán)格保證讀寫(xiě)的時(shí)序。否則將無(wú)法讀取測(cè)溫結(jié)果。
根據(jù)DS18B20 的通訊協(xié)議,主機(jī)控制DS18B20 完成溫度轉(zhuǎn)換必須經(jīng)過(guò)3 個(gè)步驟:每一次讀
寫(xiě)之前都要對(duì)DS18B20 進(jìn)行復(fù)位,復(fù)位成功后發(fā)送一條ROM 指令,最后發(fā)送RAM 指令。
這樣才能對(duì)DS18B20 進(jìn)行預(yù)定的操作。
復(fù)位要求主機(jī)將數(shù)據(jù)線(xiàn)下拉500us,然后釋放,DS18B20 收到信號(hào)后等待16~160us 然后發(fā)
出60~240us 的存在低脈沖,主機(jī)收到此信號(hào)表示復(fù)位成功。
(原文件名:4.jpg)
引用圖片
上圖即DS18B20 的復(fù)位時(shí)序圖。
下面是讀操作的時(shí)序圖
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 72
(原文件名:5.jpg)
引用圖片
這是寫(xiě)操作的時(shí)序圖
(原文件名:6.jpg)
引用圖片
下面讓我們來(lái)看看它的驅(qū)動(dòng)程序如何寫(xiě)吧。
sbit io_DS18B20_DQ = P2^3 ;
#define DS18B20_DQ_HIGH io_DS18B20_DQ = 1 ;
#define DS18B20_DQ_LOW io_DS18B20_DQ = 0 ;
#define DS18B20_DQ_READ io_DS18B20_DQ
/*******************************************************************
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 73
* 保存溫度值的數(shù)組.依次存放正負(fù)標(biāo)志,溫度值十位,個(gè)位,和小數(shù)位*
*******************************************************************/
uint8 Temperature[ 4 ] ;
void v_Delay10Us_f( uint16 Count )
{
while( --Count )
{
_nop_();
}
}
/**************************************************************************
* Function: uint8 v_Ds18b20Init_f( void ) *
* Description: 初始化DS18B20 *
* Parameter: *
* *
* Return: 返回初始化的結(jié)果(0:復(fù)位成功1:復(fù)位失敗) *
**************************************************************************/
uint8 v_Ds18b20Init_f( void )
{
uint8 Flag ;
DS18B20_DQ_HIGH //稍作延時(shí)
v_Delay10Us_f( 3 ) ;
DS18B20_DQ_LOW //總線(xiàn)拉低
v_Delay10Us_f( 80 ) ; //延時(shí)大于480us
DS18B20_DQ_HIGH //總線(xiàn)釋放
v_Delay10Us_f( 15 ) ;
Flag = DS18B20_DQ_READ ; //如果Flag 為0,則復(fù)位成功,否則復(fù)位失敗
return Flag ;
}
/******************************************************************************
* Function: void v_Ds18b20Write_f( uint8 Cmd ) *
* Description: 向DS18B20 寫(xiě)命令*
* Parameter: Cmd: 所要寫(xiě)的命令*
* *
* Return: *
******************************************************************************/
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 74
void v_Ds18b20Write_f( uint8 Cmd )
{
uint8 i ;
for( i = 8 ; i > 0 ; i-- )
{
DS18B20_DQ_LOW //拉低總線(xiàn),開(kāi)始寫(xiě)時(shí)序
DS18B20_DQ_READ = Cmd & 0x01 ; //控制字的最低位先送到總線(xiàn)
v_Delay10Us_f( 5 ) ; //稍作延時(shí),讓DS18B20 讀取總線(xiàn)上的數(shù)據(jù)
DS18B20_DQ_HIGH //拉高總線(xiàn),1bit 寫(xiě)周期結(jié)束
Cmd >>= 1 ;
}
}
/******************************************************************************
* Function: uint8 v_Ds18b20Read_f( void ) *
* Description: 向DS18B20 讀取一個(gè)字節(jié)的內(nèi)容*
* Parameter: *
* *
* Return: 讀取到的數(shù)據(jù)*
******************************************************************************/
uint8 v_Ds18b20Read_f( void )
{
uint8 ReadValue, i ;
for( i = 8 ; i > 0 ; i-- )
{
DS18B20_DQ_LOW
ReadValue >>= 1 ;
DS18B20_DQ_HIGH
if( DS18B20_DQ_READ == 1 )
ReadValue |= 0x80 ;
v_Delay10Us_f( 3 ) ;
}
return ReadValue ;
}
/******************************************************************************
* Function: uint16 v_Ds18b20ReadTemp_f( void ) *
* Description: 讀取當(dāng)前的溫度數(shù)據(jù)(只保留了一位小數(shù)) *
* Parameter: *
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 75
* *
* Return: 讀取到的溫度值*
******************************************************************************/
uint16 v_Ds18b20ReadTemp_f( void )
{
uint8 TempH, TempL ;
uint16 ReturnTemp ;
/* if( v_Ds18b20Init_() ) return ; //復(fù)位失敗,在這里添加錯(cuò)誤處理的代碼*/
v_Ds18b20Init_f() ; /復(fù)位DS18B20
v_Ds18b20Write_f( 0xcc ) ; //跳過(guò)ROM
v_Ds18b20Write_f( 0x44 ) ; //啟動(dòng)溫度轉(zhuǎn)換
v_Ds18b20Init_f() ;
v_Ds18b20Write_f( 0xcc ) ; //跳過(guò)ROM
v_Ds18b20Write_f( 0xbe ) ; //讀取DS18B20 內(nèi)部的寄存器內(nèi)容
TempL = v_Ds18b20Read_f() ; //讀溫度值低位(內(nèi)部RAM 的第0 個(gè)字節(jié))
TempH = v_Ds18b20Read_f() ; //讀溫度值高位(內(nèi)部RAM 的第1 個(gè)字節(jié))
ReturnTemp = TempH ;
ReturnTemp <<= 8 ;
ReturnTemp |= TempL ; //溫度值放在變量ReturnTemp 中
return ReturnTemp ;
}
/******************************************************************************
* Function: void v_TemperatureUpdate_f( void ) *
* Description:讀取當(dāng)前的溫度數(shù)據(jù)并轉(zhuǎn)化存放在數(shù)組Temperature(只保留了一位小數(shù)) *
* Parameter:
*
* *
* Return: *
******************************************************************************/
void v_TemperatureUpdate_f( void )
{
uint8 Tflag = 0 ;
uint16 TempDat ;
float Temp ;
TempDat = v_Ds18b20ReadTemp_f() ;
if( TempDat & 0xf000 )
{
Tflag = 1 ;
TempDat = ~TempDat + 1 ;
--------------------------cn---------------------------------------------------www.ourDev.cn-------------------------- 76
}
Temp = TempDat >> 4; (TempDat * 0.0625 ) 請(qǐng)大家不要用乘以,不知道為什么可以看我
上面的貼子
TempDat = Temp * 10 ; ;小數(shù)部用可以用查表法,大家有什么好辦法來(lái)討論
下,呵呵
Temperature[ 0 ] = Tflag ; //溫度正負(fù)標(biāo)志
Temperature[ 1 ] = TempDat / 100 + '0' ; //溫度十位值
Temperature[ 2 ] = TempDat % 100 / 10 + '0' ; //溫度個(gè)位值
Temperature[ 3 ] = TempDat % 10 + '0' ;//溫度小數(shù)位
}
如果想獲取當(dāng)前的溫度數(shù)據(jù),在主函數(shù)中調(diào)用v_TemperatureUpdate_f( void )就可以了。溫度
數(shù)據(jù)就保存到Temperature 中去了。至于如何顯示,就不用多說(shuō)了吧~@_@~
時(shí)間和溫度一起顯示出來(lái)看看
(原文件名:7.jpg)
引用圖片
OK,至此ds18b20 和ds1302 的應(yīng)用告一段落。如果有不懂的,記得多看datasheet,多交流。
編輯:admin 最后修改時(shí)間:2018-05-18