本章詳細介紹單片機程序常用編譯軟件 Keil 的用法,用一個完整的 C51 程序來操作發光二極管的點亮與熄滅,然后調用 C51 庫函數來方 便地實現流水燈,從這一章開始我們將手把手地講解單片機 C 語言編 程。認真學好本章,對于初學者來說將會是一個非常好的開頭。
2 元件特性 發光二極管的原理
正極這邊為高電平,負極為低電平,發光二極管就行,兩邊為高電平, 發光二極管就不亮。
4 程序的書寫與調試。 我們要書寫程序,需要一個 keil 軟件。
Keil 的操作步驟。 詳細操作步驟:
從圖片中不難看出,我們寫的程序(子項目)都是圍繞總項目轉的, 然后從總項目輸出我們想要的單片機程序。
當 uv2 項目產生的時候,會自動附帶很多其它 uv2 需要的文件,為了 防止我們產生瀏覽混亂,這個時候我們就需要“新建一個文件夾”, 把 uv2 存在里面,那么附帶的文件也會自己存進去這個文件夾里面去 方便我們瀏覽和操作了。
下面圖片指針頭指的是,點亮一盞燈的總項目,通過上面的操作,已 經創建完成。
為總項目添加子項目之前,我們需要新建一個文件(程序編輯窗口), 點擊軟件 file 下面那個文本圖標 create a newfile(創建一個新的文件)
#include Main()
{ P1=0xfe;
} 寫好后,我們還不知道子項目里的程序有沒有錯,現在我們需要把這 個子項目添加到“點亮一只燈.uv2”的總項目里,去讓軟件查錯,方 便我們修正,輸出我們想要的單片機代碼去讓單片機工作,那需要如 何做呢?
需要點擊如下圖的圖標 save the active document(保存活動文檔),就 是保存當前編輯中的程序編輯窗口(子項目)。
是,記得文件名后綴必須是“.c”,第二次:文件名后綴必須是“.c” 第三次:文件名后綴必須是“.c”,重要是事情說三遍,這樣后面的 操作就會萬無一失了。
言程序(就是我們剛剛保存的.c 文件) 然后點窗口上的 add(添加),
如果總項目只有一個子項目的時候(就像點亮一只燈.c),我們就點 built target,如果有二個或以上的子項目,就點 built all target files, 就兩個按鈕就是這樣用,這就為什么叫總項目和子項目的原因,因為 總項目中可以包含多個子項目的。
這樣在編譯沒有錯誤成功后,它就會產生一個 hex 文件(單片機需要 的代碼),我們就可以用這個 hex 文件讓我們的單片機工作啦!
需要用的時候,我們需要到這個文件夾找出來,這樣我們 P1.0 口的 發光二極管就可以點亮了。
如果你是用仿真軟件。 先雙擊 89C52
再點擊 program file 到點亮一盞燈文件夾里面,把點亮一只燈.hex 文 件,找出來就行。(如下圖)
你可以復制下面程序去編程軟件,熟悉上面的功能,如果想要靠自己 記憶去寫,也可以嘗試一下。
{ P1=0xff;
單片機里面的地址命名字,方便我們直接用名字使用(每個程序的開 頭肯定有頭文件的)
Main()這個是主函數入口,程序在花括號{ }里面執行,之于 main 旁邊 的括號是什么東西,我們以后用到再講解,現在明白它是固定格式 main(),mian 括號就行。
需要注意的是,這里的 P1 不可隨意寫,P 是大寫,若寫成 p,編譯程 序時將報錯,因為編譯器并不認識 p1,它只認識 P1,這是因為我們 在頭文件中定義的是“sfr P1= 0x90;”。
(1)// 兩個斜扛后面跟著的為注釋語句。這種寫法只能注釋一行, 當換行時,又必須在新行上重新寫兩個斜扛。
(2)/*…*/ 斜扛與星號結合使用,這種寫法可以注釋任意行,即斜 扛星號與星號斜扛之間的所有文字都作為注釋。
釋的目的是為了我們讀程序方便,一般在編寫較大的程序時,分段加 入注釋,這樣當我們回過頭來再次讀程序時,因為有了注釋,其代碼 的意義便一目了然了。若無注釋,我們不得不特別費力地將程序 重新閱讀一遍方可知道代碼含義。養成良好的書寫代碼格式的習慣, 經常為自己編寫的代碼加入注釋,以后定能方便許多。
while語句 通過上面一節的學習,想必大家已經對點亮實驗板上的任意發光二極 管非常熟悉了,但是,先不要高興得太早,上面的程序并不完善,任 何一個程序都要有頭有尾才對,而上面我們寫的程序似乎只有頭而無 尾。我們分析一下看,當程序運行時,首先進入主函數,順序執行里 面的所有語句,因為主函數中只有一條語句,當執行完這條語句后,
因為我們沒有給單片機明確指示下一步該做什么,所以單片機在運行 時就很有可能會出錯。
根據經驗,當 Keil 編譯器遇到這種情況時,它會自動從主函數開始處 重新執行語句,所以單片機在運行上面兩個程序時,實際上是在不斷 地重復點亮發光二極管的操作,而我們的意圖是讓單片機點亮二極管 后就結束,也就是讓程序停止在某處,這樣一個有頭有尾的程序才完
是 while(1),while(2),while(3 或以上),即為真,那么執行 while 花括 號的內部語句,如果是 while(0)即為假,跳過while,不執行 while 花 括號的內部語句。
1 如果語句只有一條。 直接用表達式+執行語句+分號結束就行。 如:
While(1)是表達式,用來判斷是真是假。 因為這里是真,所以語句就無限循環于 P1=0xfe,后面再加上一個分 號表示這是一條結束。
2 如果有兩條以上表達式。 如:
While(1) P1=0xfe; P1=0xfa;
樣的程序格式顯然是滿足不了多少功能,沒有什么意義的,那我們要 怎么寫才能讓 while 執行多些語句呢?
從上面可以觀察到,我們還有花括號可用。 是的,
{ P1=0xfe; P1=0xfa;
果只有一條語句,直接在這條語句加分號就行,如果有兩條語句或以 上就需要加{}花括號。
如果 while(0) P1=0xfe; P1=0xfa;
因為現在 while 為假,所以它不會執行 P1=0xfe,而往下執行 P1=0xfa, 因為 P1=0xfa;是不屬于 while 的內部語句。
{ P1=0xfe; P1=0xfa;
} P1=0x0f;
通過這些認識 我們來編寫一個完整的點亮第一個發光二極管的程序。
然后到 P1=0xfe,再停止在 while(1);這里,while 里面,如果有一條語 句就執行完這個語句,停止到分號,如果沒有語句就直接在分號這里 無限循環,相當于停止標記,所以以后一看到 while(1);,就知道是停 止標記了。
知識點:for語句 格式:
{ P1=0xfe;
從上面圖片可以看到 第一輪:
第 1 步:初始化 i=0(i 賦值等于 0)。 第 2 步:判斷 i 是否少于 3。
第 5 步:因為上面 i 從 0 加了一次,現在 i=1。 第 6 步:判斷 i 是否少于 3。
第 9 步:因為上面 i 從 1 加了一次,現在 i=2。 第 10 步:判斷 i 是否少于 3。
第 11 步:現在 i=1 是少于 3,就執行 for 花括號的內部語句一次。 第 12 步:當執行完一次內部語句后,i++(i++的意思就是 i 自己加 1)。
第 13 步:因為上面 i 從 2 加了一次,現在 i=3。 第 14 步:判斷 i 是否少于 3。
第 15 步:現在 i=3 不少于 3,就不執行 for 內部語句了,退出 for 語 句繼續往下面執行。
如果上面的理解。 那么
{ P1=0xfe;
也是一樣的原理,這里是 i 首先等于 3,如果 i>0,就執行內部語句, 然后 i--,這里是 i 自減 1 次。這個也是共執行 3 次,執行完后就退出 for 語句
很多初學者容易犯的錯誤是,想用 for 語句寫一個延時比較長的語句, 那么他可能會這樣寫:
unsigned char i;(unsigned char 無符號字符型,這是是定義 i 為無 符號字符型,數值范圍是 0~255)
但是結果卻發現這樣寫并不能達到延長時間的效果,因為在這里 i 是 一個字符型變量,它的最大值為 255,當你給它賦一個比最大值都大 的數時,這里 i 賦值是 3000,程序自然就出錯誤了。
因此我們尤其要注意,每次給變量賦初值時,都要首先考慮變量類型, 然后根據變量類型賦一個合理的值,我們所指的變量類型,就是字符
如果我們想用 for 語句做一個秒的延時,我們該怎么寫呢? 秒是我們日常用的時間單位,如果單片機也用秒的時間來一句句執行 程序語句,這樣就非常沒有效率,還不如直接用人手操作,我們創造 出單片機代替人手的根本原因,就是讓它自動化,而且快速。
我們用 s(秒),單片機用的時間是 us(微秒) 而且
1 秒=1000 毫秒=1 百萬微秒。 很明顯,單片機用的時間是我們的時間的 1 百萬倍。
如果我們知道單片機執行一條語句需要多少時間,就可以用 for 語句 編寫出一個一秒的延時程序了。
我們來看看執行一個分號需要多少時間。因為晶振是決定時間的,我 們單片機常用的是 11.0592M,想計算準確的時間,先在軟件里面設 置一下。
然后通過 keil 軟件——start/stop debug 可以監測到程序執行流程,進 入 debug(調試)后(看下圖),按鍵盤F10 或者 F11 一步步執行, F10 是跨越式執行,F11 是細節語句執行,說的多不如動手試一試, 就會明白很多了。
從上圖可以看到,執行一個分號之前的時間是,0.00044162 s(左邊 的 sec 那里看到),0.00044162 s=0.00044162s*100 萬=441.62us(小數 點向右移六位就行)
分號之前的時間=441.62us 分號之后的時間=445.96us 分號用的時間=445.96us - 441.62us=4.34us。 所以執行一次分號的時間是 4.34 微秒。
這個 for 語法是執行了 1ms(1 毫秒)的時間,但是這還沒有達到我 們想要的 1S 的時間,或許有些朋友很聰明可以想出,把這個 1ms 執 行多 1000 次不就是 1S 的時間了?
很明顯,C 語言是可以用于內嵌語句執行的,從上面圖片不難看到, 執行完 230 次分號后(花 1ms),還要重復執行 1000 次 1ms 就是 1s 了。
2 元件特性 發光二極管的原理
正極這邊為高電平,負極為低電平,發光二極管就行,兩邊為高電平, 發光二極管就不亮。
從上面圖片可以看到,讓一只燈亮滅的原理還是比如容易的,首先是 點亮一只燈,然后一秒延時,再滅燈,再一秒延時,再回到點亮一只 燈這樣無限循環的重復。
#define uint unsigned int //把 unsigned int 命名字為 uchar uint i; //整型 i 變量
#include //頭文件 頭文件每個程序開頭肯定有的。
而我們覺得太長不好寫,就有了#define uchar unsigned char ,把 unsigned char(無符號字符型)命名字為uchar,#define uint unsigned int 也是同樣的道理,uint(無符號整型)
還記得我們上面學過的 8 位,16 位嗎?8 個位的二進制最大的數是十 進制 255,當超過 255 后,它就又會回歸到 0重新開始,而我們的 uchar 就是用于表明是 8 個位的水杯(最大十進制數是 255)。
聲明完后,I, j 這些變量位置哪里來?當你定義好,單片機 RAM(動 態存儲器)自動分配的,這不用你操心。
sbit led=P1^0; 也很好理解的,聲名特殊功能寄存器的位,把 P1.0 這 個位命名字為 led,現在看到^這個符號了吧?很常用。
命名字方式,我們學過四種。 特殊功能寄存器的位命名:sbit 特殊功能寄存器 8 位地址命名:sfr P1=0x90;
特殊功能寄存器 16 位地址命名:sfr16 TC=0x91;(連續用 0x91,0x92 這兩個地址,只聲名第一個地址就可以)Define:把什么名字命什么名字(多數用于英文字母的定義,方便我 們記憶運用)
2 Define:把什么名字命什么名字(多數用于英文字母的定義,方便 我們記憶運用)
如何區分特殊功能寄存器和普通寄存器?原理很簡單的,當你這個寄 存器是特殊功能的,比如 P1(廠家告訴你這個寄存器有什么用,就
如果廠家沒有告訴你這個寄存器具體有什么用,就是普通寄存器,比 如 uchar i, uint j,等等,當你聲明好后,單片機會自動分配寄存器的, 如果你想用普通的位,直接用 bit 就行,如 bit flag,為 flag 這個名字 定義為位功能,這個時候 flag 也是只能在 0~1,這兩個數內變化,現 在稍微了解就行,以后用就的時候你就明白了。
同樣的代碼放在別處,需要的時候才調用出來,這樣就可以減少很多 代碼重寫,不用浪費那么多單片機內部資源。
有兩種方式: 第一種是在主程序(main)上面書寫。 第二種是在主程序(main)下面書寫。
從上面圖片可以看到,當定義好頭文件后和其它相應的名字后,子程 序就可以開始書寫了。
Void 是空的意思,相當于從這個子程序中,沒有東西(數值)返回的 意思吧,現在不理解,你可以暫時忽略它,直接書寫就行,以后你見 到有東西(數值)返回的,對比一下,就一目了然了。
del_ms 是子程序名字, del 是我們把英文單詞 delay(延時)的縮寫, 加_符號,再加 ms(毫秒),這樣從字面上就可以直接理解到是延時 1
叫做 ms 的原因是現在我們沒有像上面那樣直接賦值 1000 次的一毫 秒延時,而是把這個 1000 變成可變的數字,如下圖所示的 uint k:
可以看到,現在我們上面的 1000 改變成可變的變量整型 k,uint(無符 號整型)可在 0~65535 之間隨意賦值,定義完后,k 就可以在 0~65535 隨意賦值。
如果是少于 255 的話,用無符號字符型(uchar)就行,這里大于 255, 就用了整型,而且這個 k,只能在延時子程序花括號范圍內使用,就 是
你在主程序(main)內定義,就是主程序(main)花括號內可以使用,其 它范圍無效,如果你是一開頭就定義了,子程序和主程序都可以使用。
就是下面的 uint i 和 ichar j 可以在子程序和主程序的花括號里隨意使 用,理解沒?
記得后面不要漏了分號,這樣做只要是告訴軟件,你已經寫完一條語 句,不然的話,程序會出錯。
序外,其它一樣,如果你在主程序下面寫子程序而沒有首先告訴單片 機,程序編譯中會出錯的,你可以自己試試,試過就知道了。
歡迎光臨 (http://m.zg4o1577.cn/bbs/) | Powered by Discuz! X3.1 |