51單片機(jī)的另類入門與編程思想

在寫之前我先說一下為什么寫這篇文章迁客,其實(shí)主要是出于這么一些考慮:

1檀何、一個人力量有限,資源有限刹勃,了解到的東西就更加有限堪侯,現(xiàn)代社會講究團(tuán)隊(duì)協(xié)作,SO......希望以此為契機(jī)荔仁,號召大家參與其中伍宦,將這些東西不斷完善
2、開源軟件乏梁,開源硬件次洼,開源教程,其實(shí)這方面早有先河遇骑,但是更多的是單人收集的卖毁,單人寫的,而本教程希望是集大眾力量寫教程服務(wù)于大眾
3落萎、這里面主要從基礎(chǔ)單片機(jī)開始亥啦,然后是模塊化編程編程規(guī)范练链,從零開始構(gòu)造單片機(jī)操作系統(tǒng)這些方面跟大家一起分享翔脱,其實(shí)還有很多非常優(yōu)秀的編程思想,像狀態(tài)機(jī)媒鼓,PID算法等等届吁,大家都可以一一添加進(jìn)去
4错妖、基礎(chǔ)篇主要參考《愛上單片機(jī)》--杜洋、《電子設(shè)計(jì)從零開始》疚沐; 模塊化主要參考uCOS作者《嵌入式系統(tǒng)構(gòu)件》暂氯,然后結(jié)合個人的一些經(jīng)驗(yàn); 編程規(guī)范參考華為的規(guī)范文檔亮蛔,單片機(jī)系統(tǒng)參考《51時間系統(tǒng)》晾剖、實(shí)時系統(tǒng)uCos及阿莫電子論壇、正點(diǎn)原子論壇
5灯节、個人學(xué)習(xí)技術(shù)性東西的原則:
先入門,有一個整體感知(也就是學(xué)習(xí)一些基礎(chǔ)性的東西)->實(shí)踐炎疆,發(fā)現(xiàn)問題,解決問題->再次學(xué)習(xí)基礎(chǔ)性東西->由易到難做一些有挑戰(zhàn)性的工程

說明:由于本人了解的東西有限国裳,這里面如果有什么錯誤的地方形入,誤導(dǎo)到家的地方,希望大家在看了本教程之后缝左,大家多多參與亿遂,提出寶貴的意見。本篇主要以思路渺杉,方法與編程思想為主蛇数,層層深入,將復(fù)雜的問題,深奧的東西進(jìn)行剖析天梧,以便于大家更好的吸收、理解這些東西,至于怎么去用編程軟件鸵赖,下載等我會給出已經(jīng)講得很詳細(xì)教程居触。
時間倉促弊予,內(nèi)容難免措辭不當(dāng),懇請大家不吝賜教

第一篇

51單片機(jī)基礎(chǔ)篇

我們先來看下下圖的一個對比


人與單片機(jī)對比圖

首先說一說我個人對單片機(jī)的理解,如果把我們?nèi)祟惖拇竽X比作MCU里面的核心CPU的話宏娄,那么人的手腳就相當(dāng)于外設(shè)中的基本輸入輸出端口(Input and Output),人的耳朵相當(dāng)于MCU的通信輸入,嘴巴相當(dāng)于通信輸出,這里只是做一個比較簡單的比喻鳖宾。單片機(jī)是一個人為的給它安排事情去做的這么一個單片型微型計(jì)算機(jī)赖瞒,其實(shí)我們非常熟悉電腦,電腦有內(nèi)存箍土,顯卡侧但,聲卡等柏锄。單片機(jī)也有類似的這些東西茫舶,比如說有些功能強(qiáng)一點(diǎn)單片機(jī)內(nèi)部自帶AD轉(zhuǎn)換模塊古程、USB控制器茁裙、CAN總線控制器,為什么單片機(jī)只有這么些低端的東西呢?我們我們可以成本和需求方面考慮下赏僧,如果說在單片機(jī)里面集成個聲卡你用的上么唉堪,集成個USB控制器大部分情況你又用的上么灶搜,還有這么一個東西集成在里面需不需要額外的成本罢维。其實(shí)講到這里,大家可以去了解下你們常用的手機(jī)凳怨,里面那塊主芯片,市面上現(xiàn)在主要是聯(lián)發(fā)科與高通,其余的就是三星的,還有我們國家華為的铃剔,手機(jī)廠商在發(fā)布新產(chǎn)品時,經(jīng)常提到什么GPS,藍(lán)牙夷磕,什么全網(wǎng)通(GSM/TDSCDMA/WCDMA/LTE)等等陡鹃,基本上是宣傳芯片的功能,并不是手機(jī)的功能,這也就是想告訴大家,手機(jī)上的那塊主芯片也是單片機(jī),只不過它的功能更強(qiáng)大一點(diǎn)赶么,集成的東西更多一點(diǎn);把這些問題想清楚了从橘,然后我們就得到描述單片機(jī)常用的一句話:
其實(shí)我們從自面上也可以理解一下踩萎,單片機(jī):就是單獨(dú)的一個芯片的機(jī)器锭碳,大家試想一下歧沪,一個芯片的機(jī)器嬉橙,那么這個芯片里面應(yīng)該需要些什么呢枫振??都可以大膽的去想予权。還有一個就是大家經(jīng)常聊起的嵌入式攒至,首先發(fā)表一下個人的看法,在我看來只要是上面帶了單片機(jī)的败去,不論大小,不論功能強(qiáng)弱,都可以稱為嵌入式,但是往往我們談的更多的就是功能強(qiáng)大型的,能跑LINUX的.....個人覺得這是一個誤區(qū)晓锻。
說到這里說點(diǎn)題外話歌焦,經(jīng)常有人說232,485,CAN總線接口,但是跟很多人交流發(fā)現(xiàn)很多人沒有把通信協(xié)議與這些物理接口區(qū)分清楚砚哆,我在這里用一個簡單的比喻來跟大家說明下纷铣,上面我們所說的232,485,CAN總線都是物理接口启搂,也就是我們每個人都有一個嘴巴胜榔、鼻子全庸、耳朵一樣铐拐,中國人有這些篙耗,美國人也有這些,同樣非洲人也有這些東西(肢體殘缺的不算)赫冬,但是我們中國人主要用漢語交流腐缤,美國人主要用英語交流杰标,德國用德語等等绒净,我們要想順利的與外國人溝通,那我們只能說他們的母語,這就是協(xié)議爱只,像典型的協(xié)議在單片機(jī)中有很多,工業(yè)控制里面的modbus,openbus,工程車上面有SAEJ1939,然后再互聯(lián)網(wǎng)領(lǐng)域里面我們眾所周知的TCP/IP協(xié)議等等记某,modbus可以存在于232上,同樣也可以通過485接口通信资柔,CAN總線接口也行焙贷,是獨(dú)立于物理接口的,是為了解決特定問題而誕生的贿堰。大家有空可以去看下計(jì)算機(jī)整個誕生的歷史辙芍,關(guān)于這方面的書籍非常多,包括很多優(yōu)秀的算法的誕生,軟件的誕生故硅,都是為了解決特定問題而出現(xiàn)的庶灿。學(xué)習(xí)單片機(jī)我們始終要明白,單片機(jī)的誕生也是為了方便人類生活的吃衅,說道這里我又不得不提一下計(jì)算機(jī)的語言往踢,其實(shí)我們學(xué)過一點(diǎn)計(jì)算機(jī)的人都曉得,最初的計(jì)算機(jī)編程直接是二進(jìn)制捐晶,但是慢慢的這些計(jì)算機(jī)科學(xué)家發(fā)現(xiàn)他們每天做的這些事情很多是重復(fù)的菲语,并且二進(jìn)制對于其它人來說非常難懂,然后就出現(xiàn)了匯編語言惑灵,也就是我們熟悉的A語言山上,A語言是接近計(jì)算機(jī)底層的,我們要用計(jì)算機(jī)的思考方式去編程英支,這時期的計(jì)算機(jī)也就只能少部分人能玩佩憾,總有一些“不安分”的計(jì)算機(jī)科學(xué)家在想辦法解決這些問題,然后就出現(xiàn)了Basic語言,也就是B語言干花,B語言就有一點(diǎn)接近人的思考方式了妄帘,本人只是了解了一些基本的匯編語言,Basic語言不是太了解池凄,我就不多說了抡驼,以免誤導(dǎo)大家。后面有出現(xiàn)了C語言肿仑,接本上接近人的思考方式致盟,我們也稱這些語言是高級語言,之所以高級尤慰,是因?yàn)榉先祟惖乃伎挤绞搅笪祟愂歉呒墑游锫?.....這兩年學(xué)習(xí)C++與JAVA語言的人非常多,也非常熱伟端,我只學(xué)習(xí)了一點(diǎn)點(diǎn)的C++語言杯道,我用的最多是C語言,為什么沒去學(xué)習(xí)C++ OR JAVA語言责蝠,如果認(rèn)真去了解這些語言我們會發(fā)現(xiàn)党巾,C++與JAVA乃至后面的D、E霜医、F等語言都是越來越接近人的思考方式的昧港,語言本身只是一個工具,真正核心的東西是思想支子,編程思想创肥,就像我們解決一個問題有不同的方法,我們肯定會選擇簡單易行的方法,我給大家舉個例子:我們從小都玩過俄羅斯方塊叹侄,俄羅斯方塊用匯編語言可以實(shí)現(xiàn)巩搏,用C語言也可以實(shí)現(xiàn),同樣用C++ JAVA也可以實(shí)現(xiàn)趾代,但是它的核心算法卻都是一樣的贯底,這一點(diǎn)大家可以去證實(shí)。C++的核心思想是面向?qū)ο笕銮浚裁词敲嫦驅(qū)ο笪覜]有深入的去學(xué)習(xí)禽捆,只是了解了一些。但是我可以告訴大家一些東西飘哨,或許大家對語言本身會有一些感悟胚想。假設(shè)我們?nèi)ヱ{駛一輛汽車,我想大家所關(guān)心的應(yīng)該是怎么去把它開動芽隆,以自動檔為例浊服,我們只要曉得怎么掛檔,松手剎胚吁,踩剎車牙躺,打方向盤的基本操作就行了;大家可以想象一下如果我們?nèi)ヱ{駛一輛汽車腕扶,在駕駛的過程中孽拷,我們關(guān)心這些問題,發(fā)動機(jī)怎么運(yùn)行半抱,里面的電路怎么工作脓恕,我踩剎車,哪個剎車鉗在工作代虾,我打方向盤的時候關(guān)注電子助力轉(zhuǎn)向在怎么工作进肯,在細(xì)化一點(diǎn)激蹲,發(fā)動的幾個缸在工作棉磨,曲軸在轉(zhuǎn)動等等。我想大家開車一定會非常辛苦学辱。其實(shí)在這里汽車本身就是一個對象乘瓤,對象里面的東西我們就不需要關(guān)注太多,我們只要關(guān)心我們要使用到的東西就可以了策泣,對于汽車本身衙傀,發(fā)動機(jī)是一個對象,電子設(shè)備也是一個對象萨咕,我們駕駛汽車统抬,操作這些東西,只需關(guān)注我們要用到的接口就行了。再近一點(diǎn)聪建,我們看下我么學(xué)校的管理模式钙畔,整個學(xué)校是屬于校長管理蜈敢,但是校長并不是一對一的對我們進(jìn)行管理差牛,而是一級一級的往下通知兽泣,至于下面的細(xì)節(jié)他不會太多的去關(guān)心疼蛾。就像我們要找別人幫忙一樣药磺,如果我們還關(guān)心別人用什么方法給我解決問題奥务,我想別人一定非常反感卓鹿,明白這一點(diǎn)我們對于我們以后學(xué)習(xí)這方面選擇什么語言非常有幫助檩电。其實(shí)不單單語言方面有這種面向?qū)ο笈镂痢⒛K化思想现斋,在電路設(shè)計(jì)當(dāng)中這種模塊化思想也體現(xiàn)的淋漓盡致,在這里我們一典型的工業(yè)PLC為例:PLC里面基本上帶有哪些功能模塊呢解取?有電源模塊步责、AD轉(zhuǎn)換模塊、DA轉(zhuǎn)換模塊禀苦、開關(guān)量檢測模塊蔓肯、控制繼電器輸出模塊,通信模塊有CAN振乏、RS232蔗包、RS485、以太網(wǎng)接口慧邮。明白電路這方面的功能调限,對于基本電路檢修非常有利。
由于本人了解的東西有限误澳,這里面如果有什么錯誤的地方耻矮,誤導(dǎo)到家的地方,希望大家在看了本教程之后忆谓,大家多多參與裆装,提出寶貴的意見,也可以通過黃顏色的字在不刪除原來的基礎(chǔ)上修改倡缠,以便于完善本教程哨免,寫出一份適合我們自己學(xué)習(xí)的教程。還有就是本教程可能更多的以思路昙沦,方法與編程思想為主琢唾,層層深入,將復(fù)雜的問題盾饮,深奧的東西進(jìn)行剖析采桃,以便于大家更好的吸收這些方式方法懒熙,至于怎么去用編程軟件,下載等我會給出相關(guān)書籍普办。煌珊。


STC89系列(摘自官方數(shù)據(jù)手冊)
聯(lián)發(fā)科MT6795功能描述(摘自官網(wǎng))
驍龍820描述(摘自官網(wǎng))

一、如何學(xué)習(xí)單片機(jī)

發(fā)表一下個人的觀點(diǎn):書要看泌豆,但是不要過多的看定庵,把它作為一個知識的補(bǔ)充,我們讀了這么多年的書踪危,其實(shí)只有在自己靜下心來看書的時候效率才最高
首先蔬浙,我們不能像以往學(xué)習(xí)其他課程一樣,又是背贞远,又是拿筆計(jì)算畴博,學(xué)什么寄存器,從那難懂的匯編語言入手....我直接告訴大家蓝仲,這樣是達(dá)不到效果的俱病,反而會喪失學(xué)習(xí)單片機(jī)的興趣。那該如何去學(xué)呢袱结,我們要充分利用自己的興趣去學(xué)亮隙,去“玩”單片機(jī),而不是被它所“玩”垢夹,在此過程中切記浮躁溢吻,不要跟著自己的情緒走,想學(xué)就學(xué)一下果元,不想學(xué)就不學(xué)促王,要持之以恒。

①剛開始可以對照著別人的代碼抄寫而晒,但是后期只能借鑒別人的蝇狼,不要一味的CTRL+C,CTRL+V,我曉得大家這兩個東西用的很熟倡怎,多借鑒別人的編程方法迅耘,用自己的思路去寫

②不要蜻蜓點(diǎn)水,得過且過诈胜,細(xì)微之處體現(xiàn)實(shí)力

③把時髦的技術(shù)掛在嘴邊豹障,不如把過時的技術(shù)記在心里

④經(jīng)典的書時不時的去重新看一遍冯事,多看國外的書

⑤網(wǎng)上的資源要多利用焦匈,但是不要瞎逛,要有目的

⑥單片機(jī)十天是學(xué)不會的昵仅,只能入門缓熟,要打持久戰(zhàn)

⑦思想很重要累魔,方法很重要!够滑!

二垦写、學(xué)習(xí)的一個基本流程

1、了解單片機(jī)是個什么東西彰触,主要出現(xiàn)在那些領(lǐng)域梯投,弄清自己為什么要學(xué)?

2况毅、基本開發(fā)軟件安裝分蓖,代碼下載(Keil是最基本的)

3、跟著教程從零開始建立第一個工程尔许,一次不會多試幾次:跟著教程把代碼一個一個的敲上去么鹤,然后編譯,出現(xiàn)錯誤不要立馬詢問他人味廊,先自行嘗試去解決蒸甜,實(shí)在搞不懂再去問別人,當(dāng)控制了LED的亮滅工作后余佛,就要去分析一下這個LED到底是怎么點(diǎn)亮的柠新,里面相應(yīng)的代碼又是怎么回事,只要要去了解一遍辉巡。

4登颓、到了這個階段,就可以學(xué)習(xí)單片機(jī)的其他東西红氯,對照著代碼一個一個字母的抄框咙,不要覺得繁瑣,沒抄完一個功能痢甘,正確無誤后喇嘱,先做好備份,然后再去修改塞栅,看是否與你的預(yù)期相符者铜,抄著抄著你就有感覺了,有了感覺就可以嘗試著不參考別人的程序自己從零去建立自己想要實(shí)現(xiàn)的功能放椰,從最簡單的開始作烟。

5、當(dāng)把單片機(jī)的基本功能都學(xué)完后砾医,就可以以工程的形式進(jìn)一步學(xué)習(xí)了拿撩;用單片機(jī)實(shí)現(xiàn)一個電子鐘,用單片機(jī)控制一輛小車.....在做這種工程的過程中我們會發(fā)現(xiàn)很多問題如蚜,解決很多問題压恒,這樣我們就上升了一個層次了影暴。

6、編程規(guī)范探赫,本來想將編程規(guī)范放在最前面型宙,考慮到可能會讓大家喪失學(xué)習(xí)單片機(jī)的熱情。這階段就要看人家寫的代碼伦吠,為什么看上去那么舒服妆兑,并且很容易閱讀,函數(shù)一看就知道它用來干嘛......從規(guī)范著手毛仪,編寫能重復(fù)利用箭跳,便于維護(hù)的代碼

7、工程也做了潭千,規(guī)范也有了谱姓,是不是有點(diǎn)感覺單片機(jī)不怎么好玩了?其實(shí)還有大把的東西需要你了解刨晴,隨著工程一個比一個大屉来,我們就要從全局開始思考這些問題了。要對一個復(fù)雜的工程分成一個個的模塊狈癞,比如說在電子鐘工程里茄靠,我們可以嘗試著把它分為按鍵模塊,顯示模塊蝶桶,時鐘模塊......

Keil:軟件不要漢化慨绳,英語沒有那么可怕,它本身也自帶了說明書真竖,遇到有些問題可以去看它

Pretous軟件

Pretous:個人建議只用來做一些功能性的檢驗(yàn)脐雪,像算法類型的,切不可在實(shí)際硬件中用其作為真實(shí)參考

![Notepade++]
image.png

Notepade++:平時查看恢共,編寫代碼非常方


Notepad++軟件截面

三战秋、了解51單片的的基本東西及C語言
單片機(jī):硬件設(shè)施,軀殼首先要了解基本的51單片機(jī)知識讨韭,實(shí)驗(yàn)室一般以國產(chǎn)宏晶公司的STC89C52RC為例脂信,要知道怎么給單片機(jī)供電,有哪些基本的I/O口透硝,I/O口的基本內(nèi)部結(jié)構(gòu)狰闪,串口登(這些東西只做基本了解,不需要刻意去記住濒生,在后續(xù)的練習(xí)過程中埋泵,大家會慢慢記住的)
C語言:靈魂對于學(xué)習(xí)單片機(jī)C語言,個人建議剛開始可以直接跟著抄寫代碼就行了甜攀,至于為什么是那樣寫的不必糾結(jié)太多秋泄,練習(xí)一段時間后再去看那些單片機(jī)的C語言書籍,這樣大家更深刻些
四规阀、Keil軟件的安裝以及怎么用ISP軟件下載
①Keil軟件怎么安裝恒序,以及怎么破解,基本的設(shè)置谁撼,怎么使用歧胁,大家網(wǎng)上去搜索,如果這一點(diǎn)都做不到厉碟,不要說你會用電腦(關(guān)于使用這一塊了解就行喊巍,后面在寫代碼的過程中會反反復(fù)復(fù)的用到的,不必刻意去記)

keil軟件圖標(biāo)

keil軟件界面截圖

程序燒寫ISP(ISP--In System Programming是在線編程的意思)軟件可直接從宏晶公司官www.stcmcu.com下載STC-ISP箍鼓,這里面也有很多值得參考的東西崭参,有事沒事可以去看看,也可以百度款咖,怎么安裝USB轉(zhuǎn)串口的驅(qū)動(USB轉(zhuǎn)串口常用芯片:CP2102,PL2303,CH341)何暮,它的功能也就是將我們編寫好的代碼下載到我們的單片機(jī)當(dāng)中,怎么從ISP軟件中找相應(yīng)單片機(jī)的型號進(jìn)行代碼的下載

STC單片機(jī)燒寫軟件圖標(biāo)
ISP燒寫截圖

燒寫步驟:
(1)铐殃、在燒寫前先斷開單片機(jī)的電源(注意)
(2)海洼、首先選中單片機(jī)的型號,根據(jù)自己用的單片機(jī)選定富腊,我這里是STC89C52RC
(3)坏逢、打開要燒寫的文件:如LED.HEX
(4)、選擇當(dāng)前有效的串口
(5)赘被、點(diǎn)擊下載按鈕是整。
(6)、接通單片機(jī)的電源
關(guān)于下載我總結(jié)了一下民假,如果要重復(fù)下載的話贰盗,最好裝個那種自鎖的開關(guān),然后每次去點(diǎn)擊下載阳欲,直接按開關(guān)
③將別人驗(yàn)證成功簡單功能(etc:LED燈閃爍)HEX文件下載進(jìn)單片機(jī)以便驗(yàn)證自己的思路是否正確

五舵盈、點(diǎn)亮一顆LED燈

LED燈實(shí)物

LED燈其實(shí)我們平時到處都可以看到,不同的LED燈的驅(qū)動電壓有區(qū)別球化,這個我就不多說了秽晚,百度上一大堆,大家只要記住一點(diǎn)LED燈的那個限流電阻的值是怎么來的就行了筒愚,比如說紅色LED燈的驅(qū)動電壓是3V赴蝇,電源是5V,一般的驅(qū)動電流大約10mA就足夠了 R = (5V-3V)/10mA*1000 = 200Ω巢掺,我這只是舉個例子句伶,其他的大家都可以根據(jù)數(shù)據(jù)手冊劲蜻,或者某寶賣家提供的參數(shù)進(jìn)行電阻的計(jì)算。
硬件部分
如圖所示D1連接在P0.0端口考余,我們?yōu)槭裁匆捎眠@種方式連接呢先嬉?可不可以將LED燈反向呢?對于這個問題我給出的回答是楚堤,有些可以疫蔓,有些不可以。為什么是這樣呢身冬?
這個我們就要了解STC89C52RC這款單片機(jī)的GPIO內(nèi)部的基本結(jié)構(gòu)了衅胀,在本實(shí)驗(yàn)中所使用的端口為P0口,內(nèi)部為開漏輸出酥筝,什么是開漏輸出滚躯?度娘.....因?yàn)樵谶@個教程中我只教大家學(xué)習(xí)單片機(jī)知識,其它的不過多的說嘿歌,以免牽扯太多哀九,大家消化不了。P1~P3口是準(zhǔn)雙向上拉搅幅,這款單片機(jī)(5V)I/O口的驅(qū)動能力的灌電流20mA阅束;弱上拉時,拉電流能力是200uA茄唐,設(shè)置成強(qiáng)推挽時息裸,拉電流能力也可達(dá)20mA。
灌電流:即MCU被動輸入電流沪编。
拉電流:即MCU主動輸出電流呼盆。
那下圖就不用多解釋了.....


原理圖.png

代碼部分:

/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: LED.C
 * 描 述:點(diǎn)亮一顆LED
 * 作 者: wllis
 * 日 期: 2016/01/15
 * ****************************************************/
 #include <reg52.h>
 typedef unsigned char UINT8;
 typedef unsigned int UINT16;
 sbit LED = P0^0;
/*
 * 函數(shù)名:void DelayMS( UINT16 n )
 * 描 述:延時 * 輸 入:UINT16
 * 輸 出:無
 */
 void DelayMS( UINT16 n )
 {
    UINT8 a;
        while(--n)
       {
           for( a=114; a>0; a--); }
       } 
/*
 * 函數(shù)名:void main()
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main()
 {
     P0 = 0XFF; // 將P0端口全部初始化為高電平
     while(1)
     {
         LED = 1; // LED滅
         DelayMS(500); // 延時500毫秒
         LED = 0; // LED亮
         DelayMS(500); // 延時500毫秒 
      }
 }
 /********************end of file ***********************/

代碼分析:
首先我們看下我們要點(diǎn)亮的LED怎么實(shí)現(xiàn),先在代碼中定義[圖片上傳失敗sbit LED0 = P0^0;也就是P0口的最低位蚁廓,這句代碼大家可能是初次接觸访圃,不怎么好懂,sbit是Keil開發(fā)環(huán)境中的關(guān)鍵字相嵌,是直接用來進(jìn)行位定義的腿时,它的用法大家參考我后面給出的書籍宿礁。這么P0又是怎么來的呢硝全?我們來看下REG52.H這個頭文件

REG52.H頭文件

在里面是不是看到有關(guān)于P0的定義,再看下那個英文單詞BYTE Registers字節(jié)寄存器蜀撑,一個字節(jié)是多少位看铆?八位徽鼎。sfr又是怎么回事呢?它的全稱是Special Function Register這個是Keil軟件規(guī)定的用來定義特殊寄存器的,這是規(guī)定否淤,大家也沒必要了解為什么了悄但,感興趣的話自行去了解。然后再來看下P0后面的0X80是什么意思石抡?檐嚣,這個其實(shí)就是P0端口的地址,講到這里大家可能又會問汁雷,地址又是怎么回事净嘀?這個問題我給大家打個比方报咳,比如說你到賓館去住宿侠讯,你上服務(wù)臺那邊花錢開了一間房,服務(wù)員告訴你的房間在112號暑刃,這個112號也就是賓館房間的一個地址厢漩,有了這個地址你就可以在你的房間自由進(jìn)出了,看電視岩臣,睡覺等等溜嗜。如果說沒有地址那豈不是亂了套了,大家到處亂走架谎,很容易走錯別人的房間炸宵。單片機(jī)也是如此,有了這個地址我們就可以對它里面的東西進(jìn)行操作谷扣,變換高低電平土全,讀取高低電平,這個地址是怎么來的会涎,從單片機(jī)的數(shù)據(jù)手冊上可以找到裹匙,這個就不要問為什么了,當(dāng)初造這塊單片機(jī)的時候就早已經(jīng)固定好了末秃。
特殊功能寄存器映像(摘自STC80C52RC數(shù)據(jù)手冊.png

大家看下最下面的80h是不是有個P0 ....._概页,功能越強(qiáng)大的單片機(jī),它的寄存器就越多练慕,以至于它們的數(shù)據(jù)手冊寫了厚厚的一本書惰匙,像STM32、S3C2410大家可以去找它們的數(shù)據(jù)手冊看看铃将。再來看看當(dāng)中的延時函數(shù)徽曲,如果我們把它進(jìn)行拆解,外面一個500次的循環(huán)麸塞,內(nèi)部一個114次的循環(huán)秃臣,500*114我的天吶,500多萬次的減法,不停的對其中的數(shù)進(jìn)行減法運(yùn)算奥此,一直到運(yùn)算條件不成立為止弧哎,也就是CPU每運(yùn)行一個指令都要花費(fèi)一點(diǎn)時間,將這些時間全部加起來達(dá)到我們需要時間稚虎。說道這里撤嫩,大家是不是感覺太TM浪費(fèi)了,一個小小的延時蠢终,這么占用MCU的資源序攘,以至于如果還有其他的東西需要運(yùn)行的話都有困難,那有沒有別的方法可以將這種方式的延時資源占用釋放出來呢寻拂?答案肯定是有的程奠,只要大家堅(jiān)持往下看就會學(xué)到自己想學(xué)的東西....
六、流水燈實(shí)驗(yàn)
硬件電路
流水燈硬件電路原理圖

代碼1:

/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: Flow_LED.C
 * 描 述:LED流水燈實(shí)驗(yàn)
 * 作 者: wllis
 * 日 期: 2016/01/21
 *
 ****************************************************/
 #include <reg52.h>
 typedef unsigned char UINT8;
 typedef unsigned int UINT16;
/*
 * 函數(shù)名:void DelayMS( UINT16 n )
 * 描 述:簡單的延時
 * 輸 入:UINT16
 * 輸 出:無
 */
 void DelayMS( UINT16 n )
 {
     UINT8 a;
     while(--n)
     {
      for( a=114; a>0; a--);
     }
 }
/*
 * 函數(shù)名:void main()
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main()
 {
     UINT8 i = 0;
     P0 = 0XFF; // 將P0端口全部初始化為高電平 0XFF化為二進(jìn)制為 1111  1111
     while(1)
     {
         for( i=0; i<8; i++ )
         {
             P0 = 0XFE;       // 1111 1110
             DelayMS(100);    // 延時100毫秒
              P0 = 0XFD;       // 1111 1101
              DelayMS(100);    // 延時100毫秒
              P0 = 0XFB;       // 1111 1011
              DelayMS(100);    // 延時100毫秒
              P0 = 0XF7;       // 1111 0111
              DelayMS(100);    // 延時100毫秒
              P0 = 0XEF;       // 1110 1111
              DelayMS(100);    // 延時100毫秒
              P0 = 0XDF;       // 1101 1111
              DelayMS(100);    // 延時100毫秒
              P0 = 0XBF;       // 1011 1111
              DelayMS(100);    // 延時100毫秒
              P0 = 0X7F;       // 0111 1111
              DelayMS(100);    // 延時100毫秒
          }
     }
 }
代碼2:
/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: Flow_LED.C
 * 描 述:LED流水燈實(shí)驗(yàn)
 * 作 者: wllis
 * 日 期: 2016/01/21
 *
 ****************************************************/
 #include <reg52.h>
 typedef unsigned char UINT8;
 typedef unsigned int UINT16;
/*
 * 函數(shù)名:void DelayMS( UINT16 n )
 * 描 述:簡單的延時
 * 輸 入:UINT16
 * 輸 出:無
 */
 void DelayMS( UINT16 n )
 {
     UINT8 a;
     while(--n)
     {
        for( a=114; a>0; a--);
     }
 }
/*
 * 函數(shù)名:void main()
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main()
 {
     UINT8 i = 0;
     P0 = 0XFF; // 將P0端口全部初始化為高電平 0XFF化為二進(jìn)制為 1111 1111
     while(1)
     {
         for( i=0; i<8; i++ )
         {
              P0 = ~(0X01<<i); // 1111 1110
              DelayMS(100);    // 延100ms
         }
     }
 }
 /********************end of file ***********************/

比較一下代碼1及代碼二祭钉,實(shí)現(xiàn)的功能是一樣的瞄沙,但是代碼2精簡了很多,到后面講到定時器時我還將給大家介紹另外一種實(shí)現(xiàn)該功能的代碼
代碼分析:
首先將P0全部初始化為高電平慌核,這么做的目的主要是為了方便后面的操作距境,打個簡答的比方,如果我們起跑沒有起跑線的話垮卓,那么我們在100米賽跑中怎么確定終點(diǎn)線呢垫桂,同樣在這里我們只有初始化了,后面我們才曉得哪個位為高電平粟按,怎么去操作诬滩,以免導(dǎo)致誤觸發(fā),影響我們想要的結(jié)果钾怔。這個東西這么做大家現(xiàn)在可能不理解碱呼,但是慢慢的隨著大家經(jīng)歷的多了,就能理解了宗侦。
流水的核心思想就是每個端口開啟一段時間的低電平愚臀,然后把它關(guān)掉,在下一個位開啟低電平這么循環(huán)操作矾利,從而達(dá)到我們想要的效果姑裂,我給大家畫個框圖解釋下。代碼二用的相當(dāng)簡潔男旗,我們首先來分解下(0X01<<i),變成二進(jìn)制就是(0000 0001<<i),如果此時i等于0的話舶斧,取反后得到1111 1110,賦值給P0端口察皇,P0.0輸出低電平茴厉,LED1亮泽台,其余全滅;i等于1矾缓,則移動一位變成000 0010,取反得到1111 1101怀酷,賦值給P0端口,P0.1輸出低電平嗜闻,LED2亮蜕依,其余全滅;以此類推琉雳,達(dá)到我么想要的效果样眠,大家在以后的編程中可充分利用位操作去解決一些問題。
P0端口 P0.7 P0.6 P0.5 P0.4 P0.3 P0.2 P0.1 P0.0
~(0X01)<<0 1 1 1 1 1 1 1 0
~(0X01)<<1 1 1 1 1 1 1 0 1
~(0X01)<<2 1 1 1 1 1 0 1 1
~(0X01)<<3 1 1 1 1 0 1 1 1
~(0X01)<<4 1 1 1 0 1 1 1 1
~(0X01)<<5 1 1 0 1 1 1 1 1
~(0X01)<<6 1 0 1 1 1 1 1 1
~(0X01)<<7 0 1 1 1 1 1 1 1
七翠肘、數(shù)碼管

數(shù)碼管

單位數(shù)碼管.png

多位數(shù)碼管

對于數(shù)碼管其實(shí)我們可以這么理解檐束,它就是幾個LED燈拼湊起來的,我們通過控制其相應(yīng)LED燈的亮滅就可以實(shí)現(xiàn)我們想要顯示的數(shù)字或字母锯茄,至于它的分類厢塘,共陰茶没、共陽大家自行去了解
在本實(shí)驗(yàn)中我只是演示了一個基本的顯示0~9的一個功能
硬件原理圖
從原理圖我們可以了解到這是一個共陽的數(shù)碼管肌幽,相應(yīng)的每一段連接在P2端口,如下圖所示
數(shù)碼管原理圖

代碼部分:

/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: SEG.C
 * 描 述:數(shù)碼管從‘0’至‘9’循環(huán)顯示
 * 作 者: wllis
 * 日 期: 2016/01/21
 *
 ****************************************************/
 #include <reg52.h>
 typedef unsigned char UINT8;
 typedef unsigned int UINT16;
 /**************************************************
    硬件連接        a
            xxxxxxxx        a->P2.0
            x      x        b->P2.1
         f  x   g   x  b    c->P2.2
            xxxxxxxx        d->P2.3
            x      x        e->P2.4
         e  x      x  c     f->P2.5
            xxxxxxxx        g->P2.6
                 d
 **************************************************/
 // 對數(shù)碼管進(jìn)行編碼
 code UINT8 Seg_Code[15] = { ~0X3F,// '0'
 ~0X06, // '1'
 ~0X5B, // '2'
 ~0X4F, // '3'
 ~0X66, // '4'
 ~0X6D, // '5'
 ~0X7D, // '6'
 ~0X07, // '7'
 ~0X7F, // '8'
 ~0X6F  // '9'
 };
/*
 * 函數(shù)名:void DelayMS( UINT16 n )
 * 描 述:簡單的延時
 * 輸 入:UINT16
 * 輸 出:無
 */
 void DelayMS( UINT16 n )
 {
     UINT8 a;
     while(--n)
     {
        for( a=114; a>0; a--);
     }
 }
 /*
 * 函數(shù)名:void main()
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main()
 {
     UINT8 i = 0;
     P2      = 0XFF;            // 將P2端口全部初始化為高電平 0XFF化為二進(jìn)制為 1111 1111
     while(1)
     {
         for( i=0; i<10; i++ )
         {
            P2 = Seg_Code[i]; // 數(shù)碼管顯示0~9
            DelayMS(1000);  // 延1000ms
         }
     }
 }
 /********************end of file ***********************/

代碼分析:

數(shù)碼管編碼

首先我們看這個數(shù)碼管編碼比如0X3F我們來分解為二進(jìn)制碼(0011 1111)抓半,最后得到1100 0000,然后通過這段P2 = Seg_Code[i];
將值賦給P2端口喂急,最高位可以不用管,我們可以看到如果對應(yīng)P2口的話P2.7,P2.6為高電平笛求,其余為低電平廊移,對應(yīng)到數(shù)碼管上是abcdef亮,也就是中間的那段g不亮探入,其余的六段都亮狡孔,顯示一個’0’;再來看下(0X06)蜂嗽,二進(jìn)制為(0000 0110)苗膝,再進(jìn)一步變化得到1111 1001,對應(yīng)到數(shù)碼管上為bc亮植旧,顯示一個’1’辱揭,其他的都按照這個方法去分析就能明白數(shù)碼管到底是怎么顯示你想要的數(shù)字或字母。

八病附、按鍵程序

按鍵的真實(shí)狀況

這是我們常用的按鈕式開關(guān)问窃,從圖中可以看出我們真正想要的是中間那平整的線,假如我們實(shí)在開始時的回彈和結(jié)束時的回彈那中間去檢測開關(guān)完沪,大家想像一下會出現(xiàn)什么效果域庇,大家盡管動手去驗(yàn)證,我反正是屢試不爽。


按鍵電路

代碼:

/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: Button.C
 * 描 述:用按鍵控制LED燈的亮滅
 * 作 者: wllis
 * 日 期: 2016/01/30
 *
 * 按鍵->P1.0 LED->P0.0
 *
 ****************************************************/
 #include <reg52.h>
 typedef unsigned char UINT8;
 typedef unsigned int UINT16;
 // 定義按鍵與LED燈
 sbit LED = P0^0;
 sbit SW  = P1^0;
/*
 * 函數(shù)名:void DelayMS( UINT16 n )
 * 描 述:簡單的ms延時
 * 輸 入:UINT16
 * 輸 出:無
 */
 void DelayMS( UINT16 n )
 {
     UINT8 a;
     while(--n)
     {
        for( a=114; a>0; a--);
     }
 }
/*
 * 函數(shù)名:void main()
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main()
 {
     // 初始化按鍵與LED燈
     LED = 1;
     SW = 1; //這個地方為什么這么做听皿?
     while(1)
     {
         if( 0==SW )
         {
             DelayMS(15);
             if( 0==SW )
             {
                 while(!SW);
                 LED = ~LED;
             }
         }
     }
 }
 /********************end of file ***********************/

代碼分析:我們首先來看下這段代碼SW = 1;咕别,這段代碼為什么置1,自行去看書写穴,看什么書我會給大家介紹的惰拱,我只告訴大家這個地方置為1是方便后面的檢測,檢測的整個過程是這樣進(jìn)行的首先檢測按鍵月有沒有按下去也就是有沒有接地if(0==SW)啊送,然后對抖動部分進(jìn)行短暫的延時偿短,一般10~15ms,然后再次檢測是否是誤觸發(fā)馋没,如果不是就接著檢測按鍵是否彈上來昔逗,彈上來之后就算是一個完整的按鍵過程了,接著執(zhí)行按鍵要做的工作篷朵。
思考一下:
大家學(xué)到這里有沒有發(fā)現(xiàn)什么問題勾怒?我們在LED閃爍,數(shù)碼管0~9循環(huán)計(jì)數(shù)声旺,以及按鍵控制LED燈亮滅的程序中都有用到延時函數(shù)笔链,

延時函數(shù)

也就是這個函數(shù),我們來分析一下這個函數(shù)腮猖,是不是就是在里面循環(huán)做著一些我們不需要的事情鉴扫,循環(huán)的減減減,一直減到零才繼續(xù)執(zhí)行下一步程序:是不是有點(diǎn)浪費(fèi)澈缺,如果說要讓幾個LED燈實(shí)現(xiàn)不同頻率的閃爍坪创,而且都用次函數(shù)延時,大家可以思考一下姐赡,能不能實(shí)現(xiàn)莱预??如果再加上按鍵呢项滑,好不好實(shí)現(xiàn)依沮?大家可以盡情的去試一下,我在這里可以提前告訴大家杖们,用這種方法幾乎不可能實(shí)現(xiàn)悉抵,那么我們有沒有別的辦法去實(shí)現(xiàn)呢?答案肯定是有的摘完,并且在延時的過程中我們還可以用來做其他的事情姥饰,大家接著往下學(xué)。孝治。列粪。很多很多的驚喜_
九审磁、定時器
定時器無論是在單片機(jī)還是操作系統(tǒng)當(dāng)中是非常的重要,用途也非常廣泛岂座,廢話我就不多說态蒂,怎么個有用法,我們用實(shí)驗(yàn)來證明费什。
本實(shí)驗(yàn)我將用定時器給大家實(shí)現(xiàn)很多不同的功能
實(shí)驗(yàn)一:用T/C0中斷精確的實(shí)現(xiàn)數(shù)碼管0~9計(jì)數(shù)
硬件原理圖:
定時器實(shí)驗(yàn)原理圖

代碼:

/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: Timer0_SegCount.C
 * 描 述:用T/C0實(shí)現(xiàn)數(shù)碼管0~9的精確秒計(jì)數(shù)
 * 作 者: wllis
 * 日 期: 2016/01/30
 *
 * ****************************************************/
 #include <reg52.h>
 typedef unsigned char UINT8;
 typedef unsigned int UINT16;
 /**************************************************
    硬件連接        a
            xxxxxxxx        a->P2.0
            x      x         b->P2.1
         f  x   g  x  b     c->P2.2
            xxxxxxxx        d->P2.3
            x      x        e->P2.4
         e  x      x  c     f->P2.5
            xxxxxxxx        g->P2.6
                d
 **************************************************/
 // 對數(shù)碼管進(jìn)行編碼
 code UINT8 Seg_Code[15] = { ~0X3F, // '0' 
                             ~0X06, // '1' 
                             ~0X5B, // '2' 
                             ~0X4F, // '3' 
                             ~0X66, // '4' 
                             ~0X6D, // '5' 
                             ~0X7D, // '6' 
                             ~0X07, // '7' 
                             ~0X7F, // '8' 
                             ~0X6F // '9'
                            };
 // 秒計(jì)數(shù)器
 UINT8 Sec_Count;
 UINT8 i;
/*
 * 函數(shù)名: void Timer0_Init( void )
 * 描 述:T/C0初始化函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void Timer0_Init( void )
 {
      TH0  = (65536-50000)/256; // 計(jì)數(shù)寄存器高8位
      TL0  = (65536-50000)%256; // 計(jì)數(shù)寄存器低8位
      TMOD = TMOD&0xf0;         // 清除T/C0有關(guān)的位钾恢,其它的位不變
      TMOD = TMOD|0X01;         // 設(shè)置T/C0的位,16位定時器方式工作
      ET0  = 1;                 // 允許T/C0中斷
      EA   = 1;                 // 開總中斷
      TR0  = 1;                 // 啟動定時器T/C0
 }
/*
 * 函數(shù)名:void main()
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main()
 {
      Sec_Count = 0; // 清零
      i = 0;
      Timer0_Init(); // T/C0初始化
      while(1);
 }
/*
 * 函數(shù)名:void Timer0IRQ(void)
 * 描 述:定時器T/C0中斷函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void Timer0IRQ(void) interrupt 1
 {
      // 重新載入定時器的初值
      TH0 = (65536-50000)/256; // 計(jì)數(shù)寄存器高8位
      TL0 = (65536-50000)%256; // 計(jì)數(shù)寄存器低8位
      i++;
      if( 20==i )
      {
          i = 0;
          Sec_Count++;
          if( 10==Sec_Count )
          { 
             Sec_Count = 0;
          }
      }
      P2 = Seg_Code[Sec_Count];
 }
 /********************end of file ***********************/ 

代碼分析:
我們還是按照以往的思路鸳址,從main函數(shù)開始看瘩蚪,先對全局變量進(jìn)行初始化,接下來執(zhí)行T/C0的初始化稿黍,我們來分析一下

定時器配置

這段代碼疹瘦,首先對T/C0的高八位與第八位寄存器賦初值,這個有什么用呢巡球?我們在這個實(shí)驗(yàn)當(dāng)中是實(shí)現(xiàn)每秒的計(jì)數(shù)言沐,在后面的代碼當(dāng)中我們只進(jìn)行了20次的累加達(dá)到一秒的效果,說明我們的定時器中斷時50ms一次酣栈,我們再看下
定時器賦值

一個50000险胰,方便計(jì)算用的,也就是我們需要的50ms钉嘹,還有就是東西在中斷中也出現(xiàn)了鸯乃,在這里我給大家解釋下鲸阻,我們的定時器是進(jìn)行加1的計(jì)數(shù)跋涣,在這個例程當(dāng)中我們用的是16位定時器模式,也就是一個16位的二進(jìn)制鸟悴,它相當(dāng)于一個容器陈辱,裝滿了之后就產(chǎn)生中斷,為了方便下次再進(jìn)行裝載细诸,我們必須將里面的東西進(jìn)行重置沛贪,以實(shí)現(xiàn)我們需要的時間中斷;再看這兩句
定時器模式設(shè)置

TMOD = TMOD&0xf0這句實(shí)現(xiàn)的也就是一個清楚T/C0相應(yīng)位的工作,后面這句TMOD = TMOD|0x01是對T/C0的模式進(jìn)行設(shè)置震贵,為什么是這樣呢利赋,我們來看下圖:
定時器手冊寄存器

這就是TMOD的全部高4位是T/C1的,低4位是T/C0的猩系,在我們的設(shè)置當(dāng)中相當(dāng)于T/C0的GATE位媚送,C/T位為零,然后對應(yīng)到數(shù)據(jù)手冊上可以找到
寄存器說明

我就不多解釋寇甸;后面兩位是模式位塘偎,我也給大家附張數(shù)據(jù)手冊上的截圖疗涉,大家一看就懂
模式說明

接著看
定時器、中斷配置

這幾句是干什么用的吟秩,我們再來分析一下這個控制寄存器
定時器中斷配置寄存器

兩組一模一樣的咱扣,TR0是用來啟動定時器T/C0的,其它的我不多解釋涵防,你們對照著數(shù)據(jù)手冊去看去實(shí)驗(yàn)闹伪,然后還有個ET0,EA,下圖是中斷控制寄存器
全局中斷寄存器

EA是開總中斷用的壮池,ET0就是允許T/C0中斷祭往。然后大家還有一個疑惑就是50000怎么就等于50ms了,我給大家附上下圖就明白了
計(jì)算公式

實(shí)驗(yàn)二:用定時器實(shí)現(xiàn)4個LED不同頻率的閃爍
硬件原理圖:
定時器LED燈閃爍原理圖

代碼:

/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: LED_Flash.C
 * 描 述:通過定時器0實(shí)現(xiàn)4個LED的頻率的閃爍
 * 作 者: wllis
 * 日 期: 2016/02/3
 *
 * LED0->P0.0
 * LED1->P0.1
 * LED2->P0.2
 * LED3->P0.3
 *
 ****************************************************/
 #include <reg52.h>
 /*************** TYPEDEFINES ***********************/
 typedef unsigned char UINT8;
 typedef unsigned int UINT16;
 /************** LED DEFINES ***********************/
 sbit LED0 = P0^0;
 sbit LED1 = P0^1;
 sbit LED2 = P0^2;
 sbit LED3 = P0^3;
 // 全局變量定義
 UINT8 LED0_Delay;
 UINT8 LED1_Delay;
 UINT8 LED2_Delay;
 UINT8 LED3_Delay;
/*
 * 函數(shù)名: void Timer0_Init( void )
 * 描 述:T/C0初始化函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void Timer0_Init( void )
 {
      TH0  = (65536-50000)/256; // 計(jì)數(shù)寄存器高8位
      TL0  = (65536-50000)%256; // 計(jì)數(shù)寄存器低8位
      TMOD = TMOD&0xf0;         // 清除T/C0有關(guān)的位火窒,其它的位不變
      TMOD = TMOD|0X01;         // 設(shè)置T/C0的位硼补,16位定時器方式工作
      ET0  = 1;                 // 允許T/C0中斷
      EA   = 1;                 // 開總中斷
      TR0  = 1;                 // 啟動定時器T/C0
 }
/*
 * 函數(shù)名:void main()
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main()
 {
      LED0 = 1;
      LED1 = 1;
      LED2 = 1;
      LED3 = 1;
      LED0_Delay = 0;
      LED1_Delay = 0;
      LED2_Delay = 0;
      LED3_Delay = 0;

      Timer0_Init();
      while(1);
 }
/*
 * 函數(shù)名:void Timer0IRQ(void)
 * 描 述:定時器T/C0中斷函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void Timer0IRQ(void) interrupt 1
 {
      // 重新載入定時器的初值
      TH0 = (65536-50000)/256; // 計(jì)數(shù)寄存器高8位
      TL0 = (65536-50000)%256; // 計(jì)數(shù)寄存器低8位
      LED0_Delay++;
      LED1_Delay++;
      LED2_Delay++;
      LED3_Delay++;

      // LED0 100ms
      if( 2==LED0_Delay )
      {
           LED0_Delay=0;
           LED0 = ~LED0;
      }
      // LED1 500ms
      if( 10==LED1_Delay )
      {
           LED1_Delay=0;
           LED1 = ~LED1;
      }
      // LED2 1000ms
      if( 20==LED2_Delay )
      {
           LED2_Delay=0;
           LED2 = ~LED2;
      }
      // LED3 1500ms
      if( 30==LED3_Delay )
      {
           LED3_Delay=0;
           LED3 = ~LED3;
      }
 }
 /*************** end of file *************************/ 

上面兩個例程中我們都沒看到任何的延時函數(shù),但是實(shí)現(xiàn)了我們想要的同樣的效果熏矿,此時我們又在想已骇,這么做有什么好處,比如說在性能上有沒有什么提升票编,或者功耗有沒有降低褪储。。慧域。等等鲤竹。我們上面實(shí)現(xiàn)的功能都比較單一,要是多亮幾個LED燈昔榴,還有其它的還需要處理那處理起來就感覺很復(fù)雜辛藻,我們有沒有通用一點(diǎn)的方法呢?答案肯定是有的互订,還是用上面的四個LED燈吱肌,我們用一種更加巧妙的方法來解決延時的問題,在解決這個問題的過程中仰禽,我們將看到我們一直感覺很深奧的系統(tǒng)原形....
用系統(tǒng)模型實(shí)現(xiàn)四個LED燈不同頻率的閃爍
在講解這個模型之前我們先來了解一些系統(tǒng)的基本知識
我們在寫51系列單片機(jī)程序時肯定有很多人跟我一樣氮墨,只是把基礎(chǔ)模塊一個一個的實(shí)現(xiàn),而不會很深入的去寫吐葵,或許你能將它們組合规揪,一般都是一個大循環(huán)while, 加一些延時Delay,但是你想過沒温峭,在你用while和Delay時你很犯了一個很大的錯誤猛铅,因?yàn)槲覀冏孧CU在處理Delay時白白的浪費(fèi)了很多時間,因?yàn)镸CU在等待Delay,也許你會說只是延時幾s,或者幾ms,但是你要知道我們的MCU是以us為單位工作的诚镰,在MCU等待的這段時間它可以處理很多事情了奕坟,所以如果在處理這些事情時加入一個實(shí)時操作系統(tǒng)祥款,讓RTOS幫我們合理分配MCU的時間。如果我們把這些事情當(dāng)做RTOS的幾個任務(wù)月杉,并合理設(shè)置分配
RTOS的節(jié)拍和設(shè)置這些任務(wù)的執(zhí)行頻度蚌成,這就可以大大的提升了系統(tǒng)的速度和性能穴吹,這是以后寫實(shí)用程序的最好開始熟掂。好了廢話少說讲弄,進(jìn)入主題~
在進(jìn)入主題之前先說說我理解的幾個概念:
系統(tǒng)任務(wù):所謂任務(wù),就是需要CPU 周期“關(guān)照”的事件腌歉,就是我們需要MCU做的事情蛙酪。
實(shí)時操作系統(tǒng):就是當(dāng)外部產(chǎn)生事件時能及時快速處理,且根據(jù)處理結(jié)果在規(guī)定的時間之內(nèi)作出相應(yīng)的控制和響應(yīng)翘盖,并控制所有實(shí)時任務(wù)協(xié)調(diào)一致運(yùn)行的操作系統(tǒng)桂塞。
系統(tǒng)原理:單片機(jī)定時器延時中斷來產(chǎn)生系統(tǒng)任務(wù)調(diào)度節(jié)拍,設(shè)置各個任務(wù)的執(zhí)行頻度馍驯,
來調(diào)度各任務(wù)阁危。以實(shí)現(xiàn)系統(tǒng)多線 實(shí)時操作。
代碼:

/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: RTOS_LED_Flash.c
 * 描 述:通過簡單的系統(tǒng)實(shí)現(xiàn)4個LED的不同頻率的閃爍
 * 作 者: wllis
 * 日 期: 2016/02/04
 *
 * LED0->P0.0
 * LED1->P0.1
 * LED2->P0.2
 * LED3->P0.3
 *
 ****************************************************/
 #include <reg52.h>
 /*************** TYPEDEFINES ***********************/
 typedef unsigned char UINT8;
 typedef unsigned int UINT16;
 // 系統(tǒng)全局變量
 #define OS_CLOCK 12000000 // 系統(tǒng)晶振頻率汰瘫,單位Hz
 #define TASK_CLOCK 200 // 任務(wù)中斷節(jié)拍狂打,單位Hz
 #define TASK_MAX 4 // 任務(wù)數(shù)目
 // 設(shè)置系統(tǒng)任務(wù)執(zhí)行頻度
 #define TASK_DELAY0 TASK_CLOCK/1 // 每秒1次
 #define TASK_DELAY1 TASK_CLOCK/2 // 每秒2次
 #define TASK_DELAY2 TASK_CLOCK/4 // 每秒4次
 #define TASK_DELAY3 TASK_CLOCK/8 // 每秒8次
 /************** LED DEFINES ***********************/
 sbit LED0 = P0^0;
 sbit LED1 = P0^1;
 sbit LED2 = P0^2;
 sbit LED3 = P0^3;
 /****************** Function defines **************/
 void OS_Timer0_Init( void );
 void OS_Task_Run( void (*ptask)() );
 void OS_Run( void );
 void task0( void );
 void task1( void );
 void task2( void );
 void task3( void );
 // 任務(wù)指針
 void ( *const task[] )() = { task0,task1,task2,task3 };
 // 系統(tǒng)任務(wù)執(zhí)行頻度參數(shù)
 UINT8 Task_Delay[TASK_MAX];
/*
 * 函數(shù)名:void main()
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main()
 {
      // 端口初始化
      LED0 = 1;
      LED1 = 1;
      LED2 = 1;
      LED3 = 1;
      // 開總中斷
      EA = 1;
      OS_Timer0_Init(); // 系統(tǒng)定時器初始化
      while(1)
      {
            OS_Run();
      }
 }
/*
 * 函數(shù)名: void OS_Timer0_Init( void )
 * 描 述:T/C0初始化函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void OS_Timer0_Init( void )
 {
      UINT8 i;
      for( i=0;i<TASK_MAX;i++ )
      {
            Task_Delay[i] = 0; // 復(fù)位系統(tǒng)任務(wù)執(zhí)行頻度參數(shù)
      }
      TMOD = (TMOD&0xf0)|0x01; //設(shè)置定時器0方式1
      TH0 = 256-(OS_CLOCK/TASK_CLOCK)/12/256; //賦初值200Hz
      TL0 = 256-(OS_CLOCK/TASK_CLOCK)/12%256;
      TR0 = 1; //使能定時器0
      ET0 = 1; //使能定時器0中斷
 }
/*
 * 函數(shù)名:void OS_Task_Run( void (*ptask)() )
 * 描 述:系統(tǒng)任務(wù)調(diào)度函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void OS_Task_Run( void (*ptask)() )
 {
    (*ptask)();
 }
/*
 * 函數(shù)名:void OS_Run( void )
 * 描 述:系統(tǒng)運(yùn)行函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void OS_Run( void )
 {
      UINT8 i;
      for ( i=0; i<TASK_MAX; i++ )
      {
           if ( Task_Delay[i] == 0 )
           {
                OS_Task_Run( task[i] );
                break;
           }
      }
 }
/*
 * 函數(shù)名:void task0( void )
 * 描 述:任務(wù)0函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void task0( void )
 {
      Task_Delay[0] = TASK_DELAY0;
      LED0 = ~LED0;
 }
/*
 * 函數(shù)名:void task1( void )
 * 描 述:任務(wù)1函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void task1( void )
 {
      Task_Delay[1] = TASK_DELAY1;
      LED1 = ~LED1;
 }
/*
 * 函數(shù)名:void task2( void )
 * 描 述:任務(wù)2函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void task2( void )
 {
      Task_Delay[2] = TASK_DELAY2;
      LED2 = ~LED2;
 }
/*
 * 函數(shù)名:void task3( void )
 * 描 述:任務(wù)3函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void task3( void )
 {
      Task_Delay[3] = TASK_DELAY3;
      LED3 = ~LED3;
 }
/*
 * 函數(shù)名:void Timer0IRQ(void)
 * 描 述:定時器T/C0中斷函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void Timer0IRQ(void) interrupt 1
 {
      UINT8 i;
      TH0 = 256-(OS_CLOCK/TASK_CLOCK)/12/256; //賦初值200Hz
      TL0 = 256-(OS_CLOCK/TASK_CLOCK)/12%256;
      for( i=0;i<TASK_MAX;i++ )
      {
           if(Task_Delay[i])
           {
               Task_Delay[i]--;
           }
      }
 }
 /*************** end of file *************************/

我們從這個例程中可以看到,如果在執(zhí)行LED燈閃爍的任務(wù)當(dāng)中是其他的東西混弥,比如說按鍵處理趴乡、數(shù)碼管顯示OR 顯示屏刷新、串口通信等等蝗拿。晾捏。。那樣我們可以在不耽誤時間的同時還可以多做很多的事情蛹磺,我在這里僅僅是起到拋磚引玉的作用粟瞬,具體更深一層的運(yùn)用需要大家自己學(xué)習(xí)。我會在本教程的后面給大家介紹幾本個人覺得寫的不錯的書籍萤捆,并附上PDF給大家。
同時俗批,在上面的例程當(dāng)中我們可以看到代碼量也隨之增多了很多俗或,如果項(xiàng)目在大一點(diǎn)的話,是不是感覺維護(hù)起來非常的困難岁忘,我會在后面跟大家一起分享模塊化的編程思想辛慰,這里不做過多的介紹。

十干像、串口發(fā)送

在學(xué)習(xí)單片機(jī)的過程中帅腌,串口我們必須掌握驰弄,為什么一定要掌握?我給大家稍微說一下速客,首先每一款單片機(jī)里面都帶有串口戚篙,二其他的接口不一定有;其二串口在工業(yè)控制里面是用的最多的通信接口溺职,像PLC岔擂,HMI(人機(jī)界面,也就是PLC的顯示屏)浪耘,另外還有廠家專門做有非常實(shí)用的乱灵,能快速的進(jìn)行二次開發(fā)的串口屏幕;另外一個非常實(shí)用的功能就是我們在調(diào)試程序的過程中七冲,經(jīng)常需要用到串口打出一些調(diào)試信息痛倚,比如說我現(xiàn)在有個采集電壓的數(shù)據(jù)不對,但是我沒有額外的顯示模塊澜躺,那么這時我就可以簡單的配置下串口就可以將我需要的信息打印出來状原;還有串口結(jié)合穩(wěn)定的通信協(xié)議,那將是你的得力助手苗踪。


常見的DB9接口實(shí)物

RS232硬件參考原理圖

在工業(yè)控制里面我們常用上圖左所示的接口颠区,相應(yīng)的實(shí)驗(yàn)室電路上圖右所示,也就是我們常說的RS232接口通铲。


串口實(shí)驗(yàn)原理圖

代碼:
/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: USART_TX.C
 * 描 述:通過串口每隔一秒發(fā)送字母“A”
 * 作 者: wllis
 * 日 期: 2016/02/05
 *
 ****************************************************/
 #include <reg52.h>
 /******************** DATA TYPES *******************/
 typedef unsigned char UINT8;
 typedef unsigned int UINT16;
/*
 * 函數(shù)名:void DelayMS( UINT16 n )
 * 描 述:簡單的延時
 * 輸 入:UINT16
 * 輸 出:無
 */
 void DelayMS( UINT16 n )
 {
      UINT8 a;
      while(--n)
      {
         for( a=114; a>0; a--);
      }
 }
/*
 * 函數(shù)名:void USART_Init( void )
 * 描 述:串口初始化函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void USART_Init( void )
 {
      SCON = 0x40; //serial mode 1, 8-bits
      UART TMOD = 0x20; //timer 1, mode 2, 8-bits reload
      PCON = 0x80; //SMOD = 1; double bps
      TH1 = 0xfa; //Baund: 9600bps fosc=11.0592MHz
      TL1 = 0xfa;
      TR1 = 1; //timer1 run
 }
/*
 * 函數(shù)名:void main()
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main()
 {
      // 串口初始化
      USART_Init();
      while(1)
      {
           SBUF = 'A'; // 發(fā)送字母‘A’ 
           while(!TI); // 等待發(fā)送完成
           DelayMS(1000);
      }
 }
 /********************* end of file *********************/

代碼分析:
USART全稱:Universal Asynchronous Receiver -Transmitter,異步串行接口,
跟所有外設(shè)一樣毕莱,串口也有相應(yīng)的控制寄存器。
首先我們來看SCON,在程序中我們設(shè)置的位0x40颅夺,也就是0100 0000,對照下圖

串口寄存器

TI是發(fā)送中斷標(biāo)志位朋截,當(dāng)我們發(fā)送字符時,先將字符放到緩沖區(qū)SBUF吧黄,然后檢測TI標(biāo)志位是否為0以判斷是否發(fā)送完畢
RI是接收中斷標(biāo)志位部服,當(dāng)我們判斷是否有字符發(fā)送過來時,通過檢測RI位拗慨,然后讀取緩沖區(qū)的數(shù)據(jù)廓八。
從上圖可以得到串口有四種工作模式,在我們的程序當(dāng)中對應(yīng)模式1:
該模式下赵抢,USART作為異步通信口剧蹂,每一幀發(fā)送或接收10位數(shù)據(jù),這10個位分別是1一個起始位0,8個數(shù)據(jù)位和一個停止為1烦却;單片機(jī)的TXD為發(fā)送管腳宠叼,RXD為接收管腳,該模式下通信的波特率是可變的其爵,一般由Timer1工作在模式2下冒冬,通過載入TH1和TL1的計(jì)數(shù)初值來設(shè)置波特率伸蚯。Timer工作在模式2下,是一個8位自動重新裝載的定時器简烤,需要向TH1剂邮、TL1同時裝載相同的計(jì)數(shù)器初始值。單片機(jī)會根據(jù)Timer1的設(shè)置情況是UART工作在特定的波特率下乐埠。模式1下波特率TH1(TL1)中載入計(jì)數(shù)初始值之間的關(guān)系如下圖所示:
波特率計(jì)算公式

第二篇

編程思想

在思想篇之前抗斤,先說幾句廢話,其實(shí)我們隨遍在學(xué)習(xí)那個學(xué)科之前丈咐,我們經(jīng)常聽老師說基礎(chǔ)很重要瑞眼,“基礎(chǔ)不牢,地動山搖”棵逊;我想說的是這些話都沒錯伤疙,但是對于我們剛開始接觸這些東西的人來說,是感受不到的辆影。如果我們在學(xué)這些東西的過程中徒像,遇到過一些問題,解決過一些問題蛙讥,再回過頭來看這些基礎(chǔ)性的東西锯蛀,我們就能體會老師之前的良苦用心了。就像我們剛開始學(xué)習(xí)C語言次慢,老是想著學(xué)一些很炫旁涤,很酷的東西,想用C語言來做個游戲迫像,做個能真真實(shí)實(shí)玩的圖形界面小游戲劈愚,這些想法非常好,個人推薦在有一點(diǎn)C語言基礎(chǔ)之后去學(xué)習(xí)WIN32編程闻妓,它能實(shí)現(xiàn)你的一些想法菌羽。在這個過程中我們就會慢慢體會到,語言本身只是個工具由缆,我們而是借助于這個工具加上我們的想法來實(shí)現(xiàn)我們想要的東西注祖,工具我們只要學(xué)會如何使用就行了,比如說一把斧頭我們可以拿它來劈柴犁功,木工拿它來做家具氓轰,藝術(shù)家拿它來做藝術(shù)品,在這過程中浸卦,斧頭始終是斧頭,只是不同思想的人使用它而已案糙;在回過頭來看下C語言限嫌,我們可以用它在我們的桌面操作系統(tǒng)上編寫程序靴庆,也可以編寫單片機(jī)程序,無論是計(jì)算機(jī)程序還是單片機(jī)程序怒医,這里面主要是我們的思想在里面炉抒。我們再來看下硬件設(shè)計(jì),這部分個人沒有太多經(jīng)驗(yàn)稚叹,只是分享個人的一些看法焰薄,
單片機(jī)的基礎(chǔ)學(xué)習(xí)這方面的資料非常多,大部分人非常熟悉的《郭天祥10天學(xué)會單片機(jī)》扒袖,我了解的杜洋老師的《愛上單片機(jī)》塞茅,還有很多的論壇都有這方面的基礎(chǔ)教程,淘寶網(wǎng)上隨遍花個幾塊錢就可以買一大堆的學(xué)習(xí)資料等等季率。
很多人學(xué)單片機(jī)學(xué)了基礎(chǔ)篇后野瘦,在很長的一段時間里一直停留在初級階段,個人也有這么一段經(jīng)歷飒泻,有一段時間感覺沒什么提升鞭光,還有一個就是編寫的程序非常混亂泞遗,可重復(fù)性利用非常低惰许。其實(shí)無論學(xué)習(xí)電子設(shè)計(jì)還是單片機(jī)的程序,一開始我們就可以積累一些我們以后會用到的東西史辙。比如說我們用51單片機(jī)模擬了一個IIC程序汹买,經(jīng)過自己的實(shí)驗(yàn)驗(yàn)證非常穩(wěn)定實(shí)用,但是當(dāng)我們再次用到IIC時髓霞,大部分人是又得重新閱讀好幾遍代碼卦睹,并且還不一定懂,有的甚至重新開始復(fù)制方库,粘貼结序,調(diào)試,大家有沒有感覺浪費(fèi)很多時間纵潦,這就是一個重復(fù)利用的問題徐鹤。硬件設(shè)計(jì)也是一樣,成熟穩(wěn)定的功能電路我們就可以自己保留存檔邀层,必要時可以加上注釋返敬,以便于讓自己再次用到時能短時間看懂。
在這里面我會主要以模塊化寥院、可重復(fù)性利用劲赠、程序規(guī)范為主,其實(shí)這方面的書籍也非常多,我在這里僅僅給大家起到拋磚引玉的作用凛澎,更深層次的學(xué)習(xí)需要大家自主的去看這些書霹肝,將書里面的實(shí)驗(yàn)認(rèn)真去做,不斷的去體會塑煎,總結(jié)沫换。

第一章模塊化

我們前面學(xué)習(xí)的都是一些實(shí)現(xiàn)一些簡單功能的程序,代碼量不大最铁,百把多行讯赏,我們基本上能兼顧,能很好的維護(hù)好冷尉。后面我在用一個精簡的系統(tǒng)實(shí)現(xiàn)四個LED燈不同頻率的閃爍時漱挎,代碼量就大了差不多有200多行,維護(hù)起來就有一點(diǎn)點(diǎn)困難了网严。如果我們做一個系統(tǒng)的工程识樱,代碼量肯定不止這么點(diǎn),如果還用一個xxx.c的文件來寫的話震束,估計(jì)以后維護(hù)起來非常困難怜庸,這個時候如何組織一個工程的代碼就顯得尤為重要了。計(jì)算機(jī)的發(fā)展史就是這樣垢村,我們能遇到的問題割疾,那些科學(xué)家們也曾經(jīng)經(jīng)歷過。所以模塊化思想早就有嘉栓,并且我們也有接觸到宏榕,只是我們沒發(fā)現(xiàn)而已,感覺不到而已侵佃。學(xué)過C語言的人都知道我們?nèi)绻幚頂?shù)字性的東西時會包含math.h文件麻昼,字符串則包含string.h,但是很少有人去深層次的去了解這些東西馋辈。
我們首先來了解一下.H與.C文件的一些基本構(gòu)造抚芦,在.H文件最開始的#ifndef _xx_xx #define _xx_xx,#endif是用來防止重復(fù)包含,里面的內(nèi)容主要有#define的宏定義迈螟,結(jié)構(gòu)體struct{},函數(shù)的定義void xxx_xxx( void ;而.C文件中主要包含在改文件中用到的變量叉抡,數(shù)組定義,其它模塊要用到的全局變量盡量少定義答毫,如果有定義褥民,在.H文件中要進(jìn)行extern聲明。
我們先來改寫一下之前實(shí)現(xiàn)的LED燈亮滅的程序,將原來的一個.C文件拆分成四個小文件洗搂,分別是main.h消返、main.c载弄、LED.c、LED.h,考慮到在LED.C中沒有函數(shù)侦副,臨時加了一個改變LED燈狀態(tài)的函數(shù)侦锯,我們來看下內(nèi)部是如何實(shí)現(xiàn)的:
main.c源碼

/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: main.c
 * 描 述:main.c模塊文件
 * 作 者: wllis
 * 日 期: 2016/02/11
 *
 ****************************************************/
 #include "LED.H"
/*
 * 函數(shù)名:void main()
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main()
 {
      LED = 1; // 初始化LED燈
      while(1)
      {
            LED_State( ); // 改變LED燈的狀態(tài)
            DelayMS(500); // 延時500毫秒
      }
 }
/*
 * 函數(shù)名:void DelayMS( INT16U n )
 * 描 述:延時
 * 輸 入:INT16U
 * 輸 出:無
 */
 void DelayMS( INT16U n )
 {
      INT8U a;
      while(--n)
      {
           for( a=114; a>0; a--);
      }
 }
 /********************end of file ***********************/
main.h源碼
/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: mian.h
 * 描 述:點(diǎn)亮一顆LED
 * 作 者: wllis
 * 日 期: 2016/02/11
 *
 ****************************************************/
 #ifndef _MAIN_H
 #define _MAIN_H
 #include <reg52.h>
 /***************** TYPEDEF DATA *******************/
 typedef unsigned char INT8U;
 typedef unsigned int INT16U;
 /******************* 函數(shù)原型 *********************/
 void DelayMS( INT16U n );
 #endif
 /****************** end of file ********************/
LED.C源碼
/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: LED.C
 * 描 述:LED.C模塊文件
 * 作 者: wllis
 * 日 期: 2016/02/11
 *
 ****************************************************/
 #include "LED.H"
/*
 * 函數(shù)名:void LED_State( void )
 * 描 述:LED狀態(tài)改變函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void LED_State( void )
 {
       if(LED)
       LED = 0;
       else
       LED = 1;
 }
 /****************** eSnd of file *******************/
LED.H源碼
/************ (C) COPYRIGHT 2016 wllis **************
 *
 * 文件名: LED.H
 * 描 述:點(diǎn)亮一顆LED
 * 作 者: wllis
 * 日 期: 2016/02/11
 *
 ****************************************************/
 #ifndef _LED_H
 #define _LED_H
 #include "main.h"
 /****************** LED定義 ************************/
 sbit LED = P0^0;
 void LED_State( void );
 #endif
 /**************** end of file *********************/ 

實(shí)現(xiàn)這么一個簡單的LED燈亮滅這么做是有點(diǎn)繁瑣驼鞭,但是當(dāng)你的工程越來越大時秦驯,這種模式就非常便于維護(hù),如果彼此模塊之間只存在被調(diào)用功能挣棕,全局變量比較少的或者沒有的話译隘,那么移植到別的單片機(jī)系統(tǒng)中是非常方便的;要想用好這種模式洛心,我們要學(xué)會拆分固耘,這種拆分思想,大家只有在實(shí)踐中才能慢慢體會到词身,到了論壇上多逛逛厅目,多看看別人的工程代碼,自己動手去搭建一個簡單的工程法严;下面給大家看一下專門寫這個東西的一本書上的資料:


《嵌入式系統(tǒng)構(gòu)件》源碼

這是書本附帶的源代碼损敷,這里面主要包括數(shù)模轉(zhuǎn)換模塊(AIO),串口通信(COMM),開關(guān)量輸入輸出(DIO),液晶顯示模塊(LCD),LED燈等等,
里面的文件是這樣的一個XXX.C與配套的XXX.H的文件


模塊化外設(shè)文件

我么再來進(jìn)一步打開里面的文件看下:
首先來看下AIO.H深啤、AIO.C源文件
AIO.H源碼-1

AIO.H源碼-2

AIO.H源碼-3

AIO.C源碼-1

AIO.C源碼-2

AIO.C源碼-3

再來看下DIO.C拗馒、DIO.H源文件


DIO.H源碼-1

DIO.H源碼-2

DIO.H源碼-3
DIO.C源碼-1

DIO.C源碼-2

DIO.C源碼-3

這些源文件主要是針對uCOSII在PC機(jī)上模擬移植的,源代碼我會一起分享給大家溯街,現(xiàn)在看這些代碼可能有點(diǎn)困難诱桂,但是大家只要持之以恒的學(xué)習(xí),到時候一起交流移植這些模塊代碼或者編寫類似的代碼不是什么問題呈昔。本人在空暇的時候在STM32上移植了AIO挥等、KEY相關(guān)的兩個模塊,結(jié)合《嵌入式系統(tǒng)構(gòu)件這本書》堤尾,你會發(fā)現(xiàn)這些模塊代碼寫的非常優(yōu)秀肝劲,里面有我們很多值得學(xué)習(xí)的編程思想,在KEY代碼處理中主要用到的思想是狀態(tài)機(jī)哀峻,這個思想我會單獨(dú)寫一個程序分析涡相。給大家看代碼的主要目的就是讓大家感受一下這種規(guī)范、模塊化代碼的美剩蟀。

我們再以之前用到的一個小型系統(tǒng)實(shí)現(xiàn)一個完整的數(shù)碼時鐘為例催蝗,來進(jìn)一步了解這種模塊化思想:

數(shù)碼時鐘硬件原理圖

代碼:
來看下代碼組織結(jié)構(gòu)


代碼結(jié)構(gòu)

main.c

/************ (C) COPYRIGHT 2016 JZHG1992 **************
 *
 * 文件名: Main.C
 * 描 述:按鍵處理模塊
 * 作 者: JZHG1992
 * 整理日期: 2016/02/08 by wllis
 *
 *****************************************************/
 #include "OS.h"
/*
 * 函數(shù)名:void main( void )
 * 描 述:主函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void main( void )
 {
      OS_Init( );   // 系統(tǒng)初始化
      while(1)
      {
         OS_Run( ); // RTOS系統(tǒng)運(yùn)行函數(shù)
      }
 }
 /***************** end of file *********************/ 
os.c
/************ (C) COPYRIGHT 2016 JZHG1992 **************
 *
 * 文件名: OS.C
 * 描 述:按鍵處理模塊
 * 作 者: JZHG1992
 * 整理日期: 2016/02/08 by wllis
 *
 *****************************************************/
 #include "OS.H"
 /************************************
 * 系統(tǒng)任務(wù)執(zhí)行頻度參數(shù)
 *************************************/
 INT8U Task_Delay[TASK_MAX];
/*
 * 函數(shù)名:void OS_Timer0_Init( void )
 * 描 述:T/C0初始化函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void OS_Timer0_Init( void )
 {
      INT8U i;
      for ( i=0; i<TASK_MAX; i++ )  //將所有的任務(wù)延時初始化為0
      {
         Task_Delay[i] = 0;          // 復(fù)位系統(tǒng)任務(wù)執(zhí)行頻度參數(shù)
      }
      TMOD = (TMOD&0xf0)|0x01;                 // 設(shè)置定時器0方式1
      TH0  = 256-(OS_CLOCK/TASK_CLOCK)/12/256; // 賦初值200Hz
      TL0  = 256-(OS_CLOCK/TASK_CLOCK)/12%256;
      TR0  = 1; // 使能定時器0
      ET0  = 1; // 使能定時器0中斷
      EA   = 1; // 開總中斷
 }
/*
 * 函數(shù)名:void OS_Task_Run( void (*ptask)() )
 * 描 述:系統(tǒng)任務(wù)調(diào)度函數(shù)
 * 輸 入:void (*ptask)()
 * 輸 出:無
 */
 void OS_Task_Run( void (*ptask)() )
 {
    (*ptask)();
 }
/*
 * 函數(shù)名:void OS_ISR( void ) interrupt 1
 * 描 述:T/C0中斷服務(wù)函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void OS_ISR( void ) interrupt 1
 {
      INT8U i;
      TH0 = 256-(OS_CLOCK/TASK_CLOCK)/12/256; // 賦初值200Hz
      TL0 = 256-(OS_CLOCK/TASK_CLOCK)/12%256;
      for ( i=0; i<TASK_MAX; i++ )
      {
         if ( Task_Delay[i] )
            Task_Delay[i] --;
      }
 }
 /******************** end of file *******************/ 
OS.H
/************ (C) COPYRIGHT 2016 JZHG1992 **************
 *
 * 文件名: OS.H
 * 描 述:按鍵處理模塊
 * 作 者: JZHG1992
 * 整理日期: 2016/02/08 by wllis
 *
 *****************************************************/ 
/********************************
 *系統(tǒng)原理:單片機(jī)定時器延時中斷來產(chǎn)
 * 生系統(tǒng)任務(wù)調(diào)度節(jié)拍,設(shè)置
 * 各個任務(wù)的執(zhí)行頻度育特,來調(diào)
 * 度各任務(wù)丙号。以實(shí)現(xiàn)系統(tǒng)多線
 * 操作先朦。
 *********************************/
 #ifndef _OS_H_
 #define _OS_H_
 #include<reg52.h>
 /************************************
 * 配置系統(tǒng)參數(shù)
 *這里你可以根據(jù)你的需要修改
 *************************************/
 #define OS_CLOCK 12000000 // 系統(tǒng)晶振頻率,單位Hz
 #define TASK_CLOCK 200 // 任務(wù)中斷節(jié)拍犬缨,單位Hz
 #define TASK_MAX 3 // 任務(wù)數(shù)目
 /************************************
 * 定義變量類型
 *************************************/
 typedef unsigned char INT8U; // 宏定義INT8U
 typedef unsigned int INT16U; // 宏定義INT16U
 /************************************
 * 系統(tǒng)任務(wù)外調(diào)函數(shù)與參數(shù)
 *************************************/
 extern INT8U Task_Delay[TASK_MAX];      // 系統(tǒng)任務(wù)執(zhí)行頻度參數(shù)
 extern void OS_Timer0_Init( void );         // 系統(tǒng)定時器時鐘初始化
 extern void OS_Task_Run( void(*ptask)()); // 系統(tǒng)任務(wù)調(diào)度函數(shù)
 extern void ( *const task[] )();          // 獲得任務(wù)指針
 extern void OS_Init( void );              // RTOS系統(tǒng)初始化
 extern void OS_Run( void );               // RTOS系統(tǒng)運(yùn)行函數(shù)
 #endif
 /**************** end of file ***********************/ 
OS_TASK.C
/************ (C) COPYRIGHT 2016 JZHG1992 **************
 *
 * 文件名: OS_TASK.C
 * 描 述:按鍵處理模塊
 * 作 者: JZHG1992
 * 整理日期: 2016/02/08 by wllis
 *
 *****************************************************/
 #include "OS.H"
 #include "SMG.h"
 #include "key.h"
/************************************
 * 設(shè)置系統(tǒng)任務(wù)執(zhí)行頻度
 *************************************/
 #define TASK_DELAY0 TASK_CLOCK/200 // 任務(wù)1的執(zhí)行頻度
 #define TASK_DELAY1 TASK_CLOCK/100 // 任務(wù)2的執(zhí)行頻度
 #define TASK_DELAY2 TASK_CLOCK/200 // 任務(wù)3的執(zhí)行頻度
/*
 * 函數(shù)名:void task1( void )
 * 描 述:數(shù)碼管顯示
 * 輸 入:無
 * 輸 出:無
 */
 void task0( void )
 {
      Task_Delay[0] = TASK_DELAY0;   // 設(shè)置任務(wù)執(zhí)行度
      /* 你的任務(wù) */
      SMG_Display( SMG_Dcode_Buff ); // 數(shù)碼管顯示函數(shù)
 }
/*
 * 函數(shù)名:void task2( void )
 * 描 述:數(shù)碼管時間函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void task1( void )
 {
      Task_Delay[1] = TASK_DELAY1; // 設(shè)置任務(wù)執(zhí)行度
      SMGW_EN =0;
      SMGD_EN =0;
      /* 你的任務(wù) */
      SMG_Run_Time();             // 數(shù)碼管時間函數(shù)
 } 
/*
 * 函數(shù)名:void task3( void )
 * 描 述:按鍵調(diào)時
 * 輸 入:無
 * 輸 出:無
 */
 void task2( void )
 {
      Task_Delay[2] = TASK_DELAY2; // 設(shè)置任務(wù)執(zhí)行度
      /* 你的任務(wù) */
      KEY_AdjustTime( );           // 按鍵調(diào)時
 }
 /************************************
 * 獲得任務(wù)指針
 * 添加你的任務(wù)指針
 *************************************/
 void ( *const task[] )() = { task0, task1, task2 };
/*
 * 函數(shù)名:void OS_Init( void )
 * 描 述: RTOS初始化函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void OS_Init( void )
 {
      OS_Timer0_Init(); // 系統(tǒng)定時器時鐘初始化
      KEY_Init();       // 獨(dú)立按鍵端口初始化
      Clock_Init();
 }
/*
 * 函數(shù)名:void OS_Run( void )
 * 描 述:RTOS運(yùn)行函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void OS_Run( void )
 {
      INT8U i;
      for ( i=0; i<TASK_MAX; i++ )
      {
           if ( Task_Delay[i] == 0 )
           {
                OS_Task_Run( task[i] );
                break; 
           }
      }
 } 
KEY.C
/************ (C) COPYRIGHT 2016 JZHG1992 ************
 *
 * 文件名: KEY.C
 * 描 述:按鍵處理模塊
 * 作 者: JZHG1992
 * 整理日期: 2016/02/08 by wllis
 *
 *****************************************************/
 #include "KEY.h"
 #include "SMG.h"
/*
 * 函數(shù)名:void KEY_Init( void )
 * 描 述:獨(dú)立按鍵端口初始化
 * 輸 入:無
 * 輸 出:無
 */
 void KEY_Init( void )
 {
      key0 = 1; // 按鍵上拉輸入
      key1 = 1;
      key2 = 1;
      key3 = 1;
 }
/*
 * 函數(shù)名:static INT8U KEY_Scan( void )
 * 描 述:獨(dú)立按鍵端口掃描
 * 輸 入:無
 * 輸 出:static INT8U
 */
 static INT8U KEY_Scan( void )
 {
      if ( key0==0 )return KEY_VALUE0; // 鍵值0
      if ( key1==0 ) return KEY_VALUE1; // 鍵值1
      if ( key2==0 ) return KEY_VALUE2; // 鍵值2
      if ( key3==0 ) return KEY_VALUE3; // 鍵值3
      return KEY_NULL; // 無
 } 
/*
 * 函數(shù)名:uchar KEY_Way( INT8U *pBuff )
 * 描 述:獨(dú)立按鍵方式掃描
 * 輸 入:INT8U *pBuff
 * 輸 出:INT8U
 */
 INT8U KEY_Way( INT8U *pBuff )
 {
      INT8U temp = KEY_NULL;               // 鍵值變量
      static INT8U state = KEY_STATE_INIT; // 按鍵狀態(tài)變量
      static INT8U key_r = KEY_NULL;       // 鍵值寄存器
      static INT8U key_count = 0;          // 按鍵計(jì)數(shù)器
      temp = KEY_Scan();                   // 得到鍵值
      switch( state )
      {
      case KEY_STATE_INIT: // 初始狀態(tài)
      {
           if ( temp != KEY_NULL ) // 如果不為空
           {
              state = KEY_STATE_DEBOUNCE;
           }
      }
      break;
      case KEY_STATE_DEBOUNCE: // 去抖狀態(tài)
      {
         state = KEY_STATE_PRESS;
      }
      break;
      case KEY_STATE_PRESS: // 按下狀態(tài)
      {
           if ( temp != KEY_NULL )
           {
                key_r = temp;
                temp &= KEY_NULL;
                temp |= KEY_DOWN;
                state = KEY_STATE_LONG;
           }
           else
       state = KEY_STATE_RELEASE;
      }
      break;
      case KEY_STATE_LONG: // 長按狀態(tài)
      {
           if ( temp != KEY_NULL )
           {
                if ( ++key_count >KEY_LONG_PERIOD)
                {
                     key_count =0;
                     temp &= KEY_NULL;
                     temp |= KEY_LONG;
                     state = KEY_STATE_CONTINUE;
                }
      }
      else
      state = KEY_STATE_RELEASE;
 }
break;
 case KEY_STATE_CONTINUE: // 連擊狀態(tài)
 {
      if ( temp != KEY_NULL )
      {
           if ( ++key_count >KEY_CONTINUE_PERIOD)
           {
                key_count =0;
                temp &= KEY_NULL;
                temp |= KEY_CONTINUE;
           }
      }
      else
     state = KEY_STATE_RELEASE;
 }
break;
case KEY_STATE_RELEASE: // 釋放狀態(tài)
{
      key_r |= KEY_UP;
      temp = key_r;
      state = KEY_STATE_INIT;
 }
break;
default:
break;
 }
 return *pBuff = temp;
 }
/*
 * 函數(shù)名:void KEY_AdjustTime( void )
 * 描 述:按鍵調(diào)時
 * 輸 入:無
 * 輸 出:無
 */
 INT8U time_flag=0;
 void KEY_AdjustTime( void )
 {
     INT8U key=KEY_NULL;
     static INT8U Num=0;
     KEY_Way( &key );
     switch ( key )
     {
         case (KEY_VALUE0|KEY_DOWN): // 如果是按下設(shè)定鍵
         {
            if ( ++Num >3 )
            Num=0;
       }
       break; 
      case (KEY_VALUE1|KEY_DOWN): // 如果是按下加值鍵
      {
          if ( Num==1 )
          {
              CurrentTime.Hour++;
              if( 24==CurrentTime.Hour ) CurrentTime.Hour = 0;
           }
           if ( Num==2 )
           {
              CurrentTime.Minute++;
              if( 60==CurrentTime.Minute )CurrentTime.Minute = 0;
           }
           if ( Num==3 )
           {
              CurrentTime.Second++;
              if( 60==CurrentTime.Second )CurrentTime.Second = 0;
           }
     }
     break;
     case ( KEY_VALUE2|KEY_DOWN): // 如果是按下減值鍵
     {
         if ( Num==1 )
         {
             CurrentTime.Hour--;
             if( 0==CurrentTime.Hour )CurrentTime.Hour = 23;
         }
         if ( Num==2 )
         {
            CurrentTime.Minute--;
            if( 0==CurrentTime.Minute )CurrentTime.Minute = 59;
         }
       if ( Num==3 )
       {
           CurrentTime.Second--;
           if( 0==CurrentTime.Second )CurrentTime.Second = 59;
         }
     } 
     break;
         case ( KEY_VALUE3|KEY_DOWN): // 如果是按下退出鍵
         { 
             Num=0;
         }
          break;
         case ( KEY_VALUE3|KEY_LONG): // 如果是長按退出鍵
         {
            time_flag=!time_flag; // 標(biāo)志取反
         }
         break;
         default:
         break;
     }
 }
 /******************* end of file *******************************/ 
KEY.H
/************ (C) COPYRIGHT 2016 JZHG1992 **************
 *
 * 文件名: KEY.H
 * 描 述:按鍵處理模塊
 * 作 者: JZHG1992
 * 整理日期: 2016/02/08 by wllis
 *
 *****************************************************/
 #ifndef _KEY_H_
 #define _KEY_H_
 #include"OS.h"
 /*********************************
 * 獨(dú)立按鍵端口定義
 **********************************/
 sbit key0 = P3^0;
 sbit key1 = P3^1;
 sbit key2 = P3^2;
 sbit key3 = P3^3;
/*********************************
 * 獨(dú)立按鍵鍵值
 **********************************/
 #define KEY_VALUE0 0x0e
 #define KEY_VALUE1 0x0d
 #define KEY_VALUE2 0x0b
 #define KEY_VALUE3 0x07
 #define KEY_NULL   0x0f
/*********************************
 * 獨(dú)立按鍵方式
 * 短按喳魏、長按、連擊怀薛、釋放
 **********************************/
 #define KEY_LONG_PERIOD 100 // 長按時間標(biāo)志
 #define KEY_CONTINUE_PERIOD 25 // 連擊時間標(biāo)志
 #define KEY_DOWN 0x80
 #define KEY_LONG 0x40
 #define KEY_CONTINUE 0x20
 #define KEY_UP 0x10
 /*********************************
 * 獨(dú)立按鍵狀態(tài)值
 **********************************/
 #define KEY_STATE_INIT 0
 #define KEY_STATE_DEBOUNCE 1
 #define KEY_STATE_PRESS 2
 #define KEY_STATE_LONG 3
 #define KEY_STATE_CONTINUE 4
 #define KEY_STATE_RELEASE 5
/*********************************
 * 獨(dú)立按鍵API函數(shù)
 **********************************/
 extern INT8U time_flag;
 void KEY_Init( void );             // 獨(dú)立按鍵端口初始化
 INT8U KEY_Way( INT8U *pBuff );// 獨(dú)立按鍵方式掃描
 void KEY_AdjustTime( void );  // 按鍵調(diào)時
 #endif 
/***************** end of file **********************/
SMG.C
/************ (C) COPYRIGHT 2016 JZHG1992 **************
 *
 * 文件名: SMG.C
 * 描 述:按鍵處理模塊
 * 作 者: JZHG1992
 * 整理日期: 2016/02/08 by wllis
 *
 *****************************************************/
 #include"SMG.H"
 #include"key.h"
/************************************
 * 數(shù)碼管段碼值和緩沖區(qū)
 *************************************/
 code INT8U SMG_Dcode[]= // 數(shù)碼管段碼
 { 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
   0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,
   0xbf, //'-'號代碼
 };
 INT8U SMG_Dcode_Buff[8]={1,2,'-',4,0,'-',3,0}; // 顯示緩沖區(qū)
 TIME_TYPE CurrentTime; // 定義一個時間結(jié)構(gòu)體
/*
 * 函數(shù)名:static void Clock_Init( void )
 * 描 述:時間初始化函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 void Clock_Init( void )
 {
      CurrentTime.Hour   = SMG_Dcode_Buff[0]*10+SMG_Dcode_Buff[1];
      CurrentTime.Minute = SMG_Dcode_Buff[3]*10+SMG_Dcode_Buff[4];
      CurrentTime.Second = SMG_Dcode_Buff[6]*10+SMG_Dcode_Buff[7];
 }
/*
 * 函數(shù)名:static void SMG_Dcode_Send( INT8U dat )
 * 描 述:數(shù)碼管段碼發(fā)送函數(shù)
 * 輸 入:INT8U dat
 * 輸 出:無
 */
 static void SMG_Dcode_Send( INT8U dat )
 {
      SMG_PORT = dat;
      SMGD_EN = 1; // 使能位鎖存段碼
      SMGD_EN = 0;
 }
/*
 * 函數(shù)名:static void SMG_Wcode_Send( INT8U dat )
 * 描 述:數(shù)碼管位碼發(fā)送函數(shù)
 * 輸 入:INT8U dat
 * 輸 出:無
 */
 static void SMG_Wcode_Send( INT8U dat )
 {
      INT8U temp;
      temp = (0x01<<dat); // 根據(jù)位數(shù)計(jì)算位碼
      SMG_PORT = temp;
      SMGW_EN = 1;        // 使能位鎖存位碼
      SMGW_EN = 0;
 }
/*
 * 函數(shù)名:void SMG_Display( INT8U *pBuff )
 * 描 述:數(shù)碼管顯示函數(shù)
 * 輸 入:INT8U *pBuff
 * 輸 出:無
 */
 void SMG_Display( INT8U *pBuff )
 {
      static INT8U Num=0;
      SMG_Wcode_Send(8);       // 位碼消影 只要不是0-7
      if ( pBuff[Num] == '-' ) // 發(fā)送‘-’碼
      {
         SMG_Dcode_Send( SMG_Dcode[16]);
      }
      else
      {
         SMG_Dcode_Send( SMG_Dcode[pBuff[Num]] );// 發(fā)送段碼
      }
      SMG_Wcode_Send(Num); // 發(fā)送位碼
      if ( ++Num>7 ) Num = 0;
 }
/*
 * 函數(shù)名:void SMG_Run_Time( void )
 * 描 述:數(shù)碼管時間函數(shù)
 * 輸 入:無
 * 輸 出:無
 */
 INT8U count = 0;
 void SMG_Run_Time( void )
 {
      SMG_Dcode_Buff[0] = CurrentTime.Hour/10;
      SMG_Dcode_Buff[1] = CurrentTime.Hour%10;
      SMG_Dcode_Buff[3] = CurrentTime.Minute/10;
      SMG_Dcode_Buff[4] = CurrentTime.Minute%10;
      SMG_Dcode_Buff[6] = CurrentTime.Second/10;
      SMG_Dcode_Buff[7] = CurrentTime.Second%10;
      count++;
      if( 100==count )
      {
           count = 0;
           CurrentTime.Second++;
           if( CurrentTime.Second>=60 )
           {
                CurrentTime.Second = 0;
                CurrentTime.Minute++;
                if( CurrentTime.Minute>=60 )
                {
                     CurrentTime.Minute = 0;
                     CurrentTime.Hour++;
                     if( CurrentTime.Hour>=24 )
                     {
                        CurrentTime.Hour = 0;
                     }
                }
           }
      }
 }
 /******************** end of file *****************************/ 
SMG.H
/************ (C) COPYRIGHT 2016 JZHG1992 **************
 *
 * 文件名: SMG.H
 * 描 述:按鍵處理模塊
 * 作 者: JZHG1992
 * 整理日期: 2016/02/08 by wllis
 *
 *****************************************************/
 #ifndef _SMG_H_
 #define _SMG_H_
 #include"OS.H"
/************************************
 * 數(shù)碼管端口參數(shù)
 *************************************/
 #define SMG_PORT P1  // 數(shù)碼管端口
 sbit SMGD_EN = P0^1; // 數(shù)碼管段碼使能端
 sbit SMGW_EN = P0^2; // 數(shù)碼管位碼使能端
 /************************************
 * 保存時間數(shù)據(jù)的結(jié)構(gòu)體
 *************************************/
 typedef struct
 {
     INT8U Second;
     INT8U Minute;
     INT8U Hour ;
 }TIME_TYPE;
 extern TIME_TYPE CurrentTime; // 定義一個時間結(jié)構(gòu)體 /************************************
 * 數(shù)碼管API函數(shù)申明
 *************************************/
 extern INT8U SMG_Dcode_Buff[8];   // 顯示緩沖區(qū)
 void Clock_Init( void );          // 時間初始化函數(shù)
 void SMG_Display( INT8U *pBuff ); // 數(shù)碼管顯示函數(shù)
 void SMG_Run_Time( void );        // 數(shù)碼管時間函數(shù)
 #endif 
/******************** end of file ********************/

這個工程主要是在正點(diǎn)原子論壇上找到的刺彩,實(shí)現(xiàn)的是一個電子鐘的功能,可以用按鍵進(jìn)行調(diào)時間枝恋,按鍵用到的是狀態(tài)機(jī)思想创倔,硬件方面還用到兩片鎖存器74HC573,關(guān)于這個芯片本來想結(jié)合數(shù)據(jù)手冊跟大家分享一下怎么用焚碌,但是苦于家里沒有網(wǎng)絡(luò)畦攘,希望大家去完善一下。

第二章程序規(guī)范

為什么要說規(guī)范十电,俗話說“沒有規(guī)矩知押,不成方圓”,在任何行業(yè)都是一樣的鹃骂,如果我們都按照自己的思想去做事台盯,那么行業(yè)溝通起來就非常困難。就像電子行業(yè)用的是電子行業(yè)符號偎漫,建筑行業(yè)用的是建筑行業(yè)的符號爷恳,互聯(lián)網(wǎng)通信遵循TCP/IP協(xié)議一樣,我們都按照大家約定俗成的規(guī)范做事象踊,才使得我們能進(jìn)行有效的溝通温亲,可見規(guī)范的重要性。
華為的編程規(guī)范原文從排版杯矩、注釋栈虚、標(biāo)識符命名、可讀性史隆、變量魂务、結(jié)構(gòu)、函數(shù)泌射、過程粘姜、可測性、程序效率熔酷、質(zhì)量保證孤紧、代碼編輯、編譯拒秘、審查号显、代碼測試臭猜、維護(hù)、宏這些方面闡述代碼規(guī)范押蚤,原文有50多頁蔑歌,我這里主要介紹一些我們經(jīng)常用到的東西
注釋:
文件注釋:在.C文件以及.h文件開頭

/************ (C) COPYRIGHT 2016 wllis ************** 
 *
 * 文件名: USART_TX.C
 * 描 述:通過串口每隔一秒發(fā)送字母“A”
 * 作 者: wllis
 * 日 期: 2016/02/05
 * 
****************************************************/ 

主要是用來描述這個文件是干什么用的,編寫的日期揽碘,版本次屠,還有編寫者的大名,有的還會有修改記錄等等钾菊,根據(jù)自己的需要可以去添加一些東西帅矗。
函數(shù)注釋:

/*
 * 函數(shù)名:void DelayMS( UINT16 n )
 * 描 述:簡單的延時
 * 輸 入:UINT16
 * 輸 出:無
 */ 

在有些參數(shù)非常多的函數(shù)當(dāng)中,還專門有對每個參數(shù)的說明煞烫,具體舉例應(yīng)用等等。
它主要用來描述這個函數(shù)用來干什么累颂,以及輸入的參數(shù)至会,返回值攀圈,基本功能描述等,我學(xué)過一段時間的Win32,里面的函數(shù)這方面就做的非常好顺呕,有個時候這方面的注釋比函數(shù)本身還長。微軟有個專門的MSDN專門做這方面的工作饭豹,你能在里面查到每個函數(shù)的用法蛇捌,有些還有具體的實(shí)例,是不是非常方便赫编⊙舱海可能你們當(dāng)中有一些學(xué)過STM32的,它里面的庫函數(shù)就是意法半導(dǎo)體(STM)公司專門開發(fā)的擂送,以方便開發(fā)人員快速的使用悦荒,另外我們所使用的STC系列單片機(jī)也就是宏晶公司的51單片機(jī),它的官網(wǎng)也在編寫類似STM32庫函數(shù)的東西嘹吨,大家可以去它的官網(wǎng)看一看搬味。
必要的地方給予注釋,有些難以看出的變量蟀拷,語句碰纬;注釋的地方盡量保持對齊,注釋風(fēng)格盡量保持一致问芬,比如我這里全部用 “// “ 模式悦析,如果用”/**/”這種模式就盡量用這種模式:

TMOD = TMOD&0XF0;  // 將與定時器0有關(guān)的部分清零
TMOD = TMOD|0X01;  // 定時器0工作在16位定時器模式
P0 = ~(0X01<<i);   // 將P0口的第i位設(shè)置為低電平

對于簡單的東西可以多使用typedef、#define定義愈诚,宏定義使用大寫:
示例:

typedef  unsigned char INT8U;
typedef  unsigned int  INT16U;

這種定義在uCosII里面用的非常好她按,也為大部分開發(fā)人員所使用

 #define    MAX_TASK    4

其實(shí)#define 還有很多用途:
示例:

#define     ON      0
#define         OFF     1
#define         TRUE    1
#define     FALSE   0

這樣做會使得我們在閱讀程序的過程中非常方便
函數(shù)牛隅、變量命名:
變量的命名我們一般遵循易于識別的原則比如INT8U,我們大概得知它是一個8位無符號的這么一個東西酌泰,Count是計(jì)數(shù)的意思媒佣,這方面可以參考微軟的匈牙利命名法,微軟在這方面是做的非常優(yōu)秀的陵刹。
函數(shù):函數(shù)命名應(yīng)準(zhǔn)確描述函數(shù)的功能默伍,延時函數(shù)DelayMs()我們一看就知道是毫秒機(jī)延時函數(shù)。
示例:

void print_record( unsigned int rec_ind );
int  input_record( void );
unsigned char get_current_color( void );

縮進(jìn)衰琐、對齊:
一般我們在每個大括號里面的東西縮進(jìn)四個英文字符的間隔也糊,大括號一般按照如下方式對其

void main()
 {
  xxxxwhile(1)
    {
     xxxx// your code here
    }
 }

說明:里面的x代表空格
其實(shí)在華為編程規(guī)范里面要求縮進(jìn)只能用空格,這是為什么呢羡宙?因?yàn)槲覀冇貌煌拇a編輯器打開時它里面的tab對應(yīng)的都是不同的
無論語句長短狸剃,單獨(dú)成行:
示例:
不規(guī)范的寫法

 Flag1 = 0; Flag2 = 0;

規(guī)范的寫法

Flag1 = 0;
 Flag2 = 0;

善于運(yùn)用括號,特別是我們不太清楚運(yùn)算符的優(yōu)先級時:
示例:

word = (high << 8) | low
if((a | b) && (a & c))
if((a | b) < (c & d))

說明:主要是防止在閱讀程序時產(chǎn)生誤解狗热,另外一個我們可以不必話太多時間去了解每一個運(yùn)算符的優(yōu)先級
if钞馁、for、do匿刮、while僧凰、case、switch熟丸、default等語句鴿子獨(dú)占一行训措,且if、for光羞、do绩鸣、while等語句的執(zhí)行語句無論多少都要加括號{}。
不規(guī)范

if(xxx) return; 

規(guī)范

if(xxx)
{ 
    return;
}

程序的分界符(C語言的大括號‘{’和‘}’)應(yīng)各自獨(dú)占一行且位于同一列狞山,同時與引用他們的語句對齊全闷。在函數(shù)體的開始、類的定義萍启、結(jié)構(gòu)的定義总珠、枚舉的定義以及if、for勘纯、do局服、while、swithc驳遵、case語句中的程序都要采用如上的縮進(jìn)方式淫奔。
不規(guī)范:

for(...){
// code...
 }
 if(...)
 {
// code...
}
 void example_fun( void )
{
// code...
 }

規(guī)范:

 for(...)
 {
    // code...
 }

 if(...)
 {
    // code...
 }

 void example_fun( void )
 {
     // code...
 }

在兩個以上的關(guān)鍵字、變量堤结、常量進(jìn)行對等操作時唆迁,他們之間的操作符之前鸭丛、之后或者前后要加空格;進(jìn)行非對等操作時唐责,果果是關(guān)系密切的立即操作符(如->),后不應(yīng)加空格鳞溉。
示例:
(1)逗號、分號只在后面加空格

int a, b, c;

(2)比較操作符鼠哥,賦值操作符“=”熟菲、“+=”,算術(shù)操作符“+”朴恳、“%”抄罕,邏輯操作符“&&”、
“&”于颖,位域操作符“<<”,“^”等雙目操作符的前后加空格呆贿。

if(current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b^2;

(3)“->”、“.”前后不加空格恍飘。

P->id = pid;

空格用的要在一定程度可以讓你寫出的代碼美觀榨崩,整潔,讓人一看上去不感覺凌亂章母。
函數(shù)的規(guī)模盡量控制在可維護(hù)范圍,如果代碼量過大翩剪,應(yīng)對其進(jìn)行拆分乳怎,華為給出的建議是低于200行,但是對于我們大部分單片機(jī)開發(fā)這來說前弯,建議代碼量控制在幾十行蚪缀,連同注釋建議200行左右。
編寫高內(nèi)斂低耦合的函數(shù)恕出,這方面?zhèn)€人是這么理解的询枚,其實(shí)在我們模塊化編程里面也有這方面的思想,也就是盡量編寫函數(shù)時不要過多的用其他模塊的變量以及函數(shù)調(diào)用浙巫,那樣只會是日后的維護(hù)困難金蜀,另外一個就是不便于移植。

第三章

從零玩轉(zhuǎn)單片機(jī)系統(tǒng)

講到系統(tǒng)我們了解最多的可能莫過于windows操作系統(tǒng)的畴,開源的就是linux,完整的來說linux只是一個系統(tǒng)的內(nèi)核渊抄,在工業(yè)領(lǐng)域里包括書本上最多的是uCosII,但是對于初學(xué)51單片機(jī)的朋友,這款系統(tǒng)并不是很適合大家丧裁,一方面代碼量有點(diǎn)大护桦,另一方面里面有太多我們目前暫時不需要用到的東西(比如說:隊(duì)列,消息...),還有一些讓我們望而生畏的數(shù)據(jù)結(jié)構(gòu)煎娇,綜合以上幾點(diǎn)二庵,他并不適合初學(xué)者贪染。所以本章主要參考《時間觸發(fā)嵌入式系統(tǒng)設(shè)計(jì)模式》,這本書是一本非常適合從零開始學(xué)習(xí)系統(tǒng)的書催享,里面的一些概念很容易讓以前沒有接觸過嵌入式RTOS的人在51單片機(jī)上實(shí)踐杭隙,它的代碼量很少,并且直接可以運(yùn)行在51單片機(jī)上睡陪,另外這本書里面沒有太多復(fù)雜的東西寺渗,只是一些很實(shí)用的東西。由于本人時間有限兰迫,另外就是在系統(tǒng)方面很多問題理解的不是太深刻信殊,我就簡單的講一講我對單片機(jī)系統(tǒng)的理解,具體的東西請大家參考我給出的幾個教程《51系統(tǒng)調(diào)度》汁果、《如何設(shè)計(jì)復(fù)雜的多任務(wù)程序》涡拘、以及《時間觸發(fā)嵌入式系統(tǒng)設(shè)計(jì)模式》。
我們大部分時間設(shè)計(jì)的程序就是圍繞著while(1)大循環(huán)工作的据德,可能在簡單的工程里面并不能發(fā)現(xiàn)什么問題鳄乏,但是隨著我們的工程越來越復(fù)雜,這種方法就相當(dāng)不適用了棘利。比如說我們一個工程里面有用到LED燈橱野,液晶顯示屏,按鍵善玫,串口通信水援;LED燈每秒閃爍一次,液晶屏每秒刷新兩次茅郎,按鍵用狀態(tài)機(jī)沒15毫秒輪循一次蜗元,串口每秒發(fā)送一次給上位機(jī)。如果里面的延時還采用之前的消耗MCU資源的模式系冗,那這個工程將很難實(shí)現(xiàn)奕扣,系統(tǒng)的出現(xiàn)就是為了解決這些問題而生的。它很巧妙的把時間延時利用起來掌敬,每個系統(tǒng)都有一個心臟(定時器)惯豆,有些單片機(jī)(像STM32)還專門給它造了個心臟(系統(tǒng)定時器),它在里面起到非常重要的監(jiān)督作用涝开。它不斷的檢查每個任務(wù)的延時是否到了循帐,如果到了就再次運(yùn)行該任務(wù)。包括大名鼎鼎的uCosII,里面


實(shí)驗(yàn)中用到的系統(tǒng)定時器代碼截圖
某個系統(tǒng)的定時器部分代碼截圖
時間觸發(fā)系統(tǒng)設(shè)計(jì)模式中的定時器中斷服務(wù)函數(shù)代碼

實(shí)驗(yàn)中用到的小型系統(tǒng)定時器0中斷服務(wù)函數(shù)所做的事情舀武,是不是做的就是對要進(jìn)行的延時不斷的做減法運(yùn)算拄养,因?yàn)槎〞r器是每秒執(zhí)行200次,也就是5ms一次,所以相對使用直接延時來說節(jié)約了大量的資源瘪匿。其實(shí)我們也可以想到跛梗,我們最終要做的就是解決延時的過程中的資源浪費(fèi)問題,所以大部分系統(tǒng)用定時器以一定頻率去處理延時問題棋弥,解決了延時問題就解決了系統(tǒng)資源浪費(fèi)問題核偿。當(dāng)然了,系統(tǒng)的奧妙不僅僅是這么點(diǎn)東西顽染,但是對于我們剛學(xué)習(xí)系統(tǒng)來說漾岳,關(guān)注的太多,只會分散我們的精力粉寞,感覺系統(tǒng)復(fù)雜尼荆,讓我們望而止步。所以我們要從看的見的著手唧垦。


《時間觸發(fā)嵌入式系統(tǒng)設(shè)計(jì)模式》封面

后記:
縱觀電子技術(shù)捅儒,互聯(lián)網(wǎng),開源的硬件振亮、軟件巧还,無一不是從國外引進(jìn)的。我想說的是這是個資源的時代坊秸,要善于掌握資源麸祷,利用資源,注重團(tuán)隊(duì)合作褒搔,多與你周圍的人溝通摇锋、在溝通中產(chǎn)生思想,在溝通中進(jìn)步站超。不要再虛幻的世界里尋找成就感與自信,多在現(xiàn)實(shí)生活中找回你的自信乖酬,下一個輝煌定將屬于我們死相。

注意:以上所有要求光看不練假把式,需要每個人一個一個的落實(shí)到位咬像,身體力行算撮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市县昂,隨后出現(xiàn)的幾起案子肮柜,更是在濱河造成了極大的恐慌,老刑警劉巖倒彰,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件审洞,死亡現(xiàn)場離奇詭異,居然都是意外死亡待讳,警方通過查閱死者的電腦和手機(jī)芒澜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門仰剿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人痴晦,你說我怎么就攤上這事南吮。” “怎么了誊酌?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵部凑,是天一觀的道長。 經(jīng)常有香客問我碧浊,道長涂邀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任辉词,我火速辦了婚禮必孤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瑞躺。我一直安慰自己敷搪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布幢哨。 她就那樣靜靜地躺著赡勘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捞镰。 梳的紋絲不亂的頭發(fā)上闸与,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音岸售,去河邊找鬼践樱。 笑死,一個胖子當(dāng)著我的面吹牛凸丸,可吹牛的內(nèi)容都是我干的拷邢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼屎慢,長吁一口氣:“原來是場噩夢啊……” “哼瞭稼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腻惠,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤环肘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后集灌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悔雹,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了荠商。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寂恬。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖莱没,靈堂內(nèi)的尸體忽然破棺而出初肉,到底是詐尸還是另有隱情,我是刑警寧澤饰躲,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布牙咏,位于F島的核電站,受9級特大地震影響嘹裂,放射性物質(zhì)發(fā)生泄漏妄壶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一寄狼、第九天 我趴在偏房一處隱蔽的房頂上張望丁寄。 院中可真熱鬧,春花似錦泊愧、人聲如沸伊磺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屑埋。三九已至,卻和暖如春痰滋,著一層夾襖步出監(jiān)牢的瞬間摘能,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工敲街, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留团搞,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓多艇,卻偏偏與公主長得像莺丑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子墩蔓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容