注意:訪問本站需要Cookie和JavaScript支持!請設置您的瀏覽器! • 打開購物車 • 查看留言 • 付款方式 • 聯系我們 |
![]() |
首頁 | 電子入門 | 學單片機 | 免費資源 | 下載中心 | 商品列表 | 象棋在線 | 在線繪圖 | 加盟五一 | 加入收藏 | 設為首頁 |
選擇分類:當前分類——小說笑話 相關聯或者相類似的文章: 23年前,有個年輕的女子~~~~(2191) MY8848網站銷聲匿跡 消費者(1196) 有地下王國嗎?(1182) 七仙女離婚(1127) 電容器的參數與分類(1126) TL431特性及應用(1074) 地球的黑洞-百幕大三角區(1069) 錄取通知書遲一年 家長和學校各執(1018) 三途河之鬼——人類真任性(1014) 白女孩(997) 優勝劣汰的用人原則(989) 何為天堂(987) 知識能否改變命運?(978) 求△V特性鎳氫電池充電單元電路圖(974) 深圳美的驚世絕侖(967) 做一個可愛的人(958) 無聊的笑話~~!!!(945) 三極管的開關作用(924) 簡易FM無線話筒!裝好的成品板(921) 考考你的眼睛——請找出那個輪子在(920) 首頁 前頁 后頁 尾頁 本站推薦: | 書中自有黃金屋,書中自有顏如玉。 書中自有黃金屋,書中自有顏如玉。道理沒錯,可最重要的問題就是現在書店的書一本很少有低于20元的,手冊什么的更是天價。有時候由于囊中羞澀,往往只能望書興嘆。還有就是往往買回去仔細一看,沒看多少就發現很不實用,只能放在一邊了,浪費了自己的血汗錢。要找資料的時候卻又翻箱倒柜的也找不到了。不知道各位兄弟有無類似的感觸。買一些實用的海量資料,只相當于到外面小吃一頓,或者別的類似出去活動一下的錢,或者一本包裝華麗的真書的價格,甚至只是一個月的電話費或者上網費。 我的忠告:做自己喜歡做的事情,同時不要用健康和快樂去換錢。 我的祝福:通過學習和實踐,祝大家都有一個似錦前程!
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13483202 單片機的C 語言輕松入門 隨著單片機開發技術的不斷發展,目前已有越來越多的人從普遍使用匯編語言到逐漸使 用高級語言開發,其中主要是以C 語言為主,市場上幾種常見的單片機均有其C 語言開發 環境。這里以最為流行的80C51 單片機為例來學習單片機的C 語言編程技術。 本書共分六章,每章一個專題,以一些待完成的任務為中心,圍繞該任務介紹C 語言 的一些知識,每一個任務都是可以獨立完成的,每完成一個任務,都能掌握一定的知識,等 到所有的任務都完成后,即可以完成C 語言的入門工作。 第1 章 C 語言概述及其開發環境的建立 學習一種編程語言,最重要的是建立一個練習環境,邊學邊練才能學好。Keil 軟件是目 前最流行開發80C51 系列單片機的軟件,Keil 提供了包括C 編譯器、宏匯編、連接器、庫 管理和一個功能強大的仿真調試器等在內的完整開發方案,通過一個集成開發環境 (μVision)將這些部份組合在一起。 在學會使用匯編語言后,學習C 語言編程是一件比較容易的事,我們將通過一系列的 實例介紹C 語言編程的方法。圖1-1 所示電路圖使用89S52 單片機作為主芯片,這種單片 機性屬于80C51 系列,其內部有8K 的FLASH ROM,可以反復擦寫,并有ISP 功能,支 持在線下載,非常適于做實驗。89S52 的P1 引腳上接8 個發光二極管,P3.2~P3.4 引腳上接 4 個按鈕開關,我們的任務是讓接在P1 引腳上的發光二極管按要求發光。 1.1 簡單的C 程序介紹 例1-1: 讓接在P1.0 引腳上的LED 發光。 /************************************************* 平凡單片機工作室 http://www.mcustudio.com Copyright 2003 pingfan's mcustudio All rights Reserved 作者:周堅 dddl.c 單燈點亮程序 *************************************************/ 圖1-1 接有LED 的單片機基本電路 P1.0 EA/VPP VCC XTAL2 XTAL1 GND RST +5V +5V + R1 E1 10K 10U 27P CY 27P PZ1 1K D8 D1 89××× #include “reg51.h” sbit P1_0=P1^0; void main() { P1_1=0; } 這個程序的作用是讓接在P1.0 引腳上的LED 點亮。下面來分析一下這個C 語言程序包 含了哪些信息。 1)“文件包含”處理。 程序的第一行是一個“文件包含”處理。 所謂“文件包含”是指一個文件將另外一個文件的內容全部包含進來,所以這里的程序 雖然只有4 行,但C 編譯器在處理的時候卻要處理幾十或幾百行。這里程序中包含REG51.h 文件的目的是為了要使用P1 這個符號,即通知C 編譯器,程序中所寫的P1 是指80C51 單 片機的P1 端口而不是其它變量。這是如何做到的呢? 打開reg51.h 可以看到這樣的一些內容: /*------------------------------------------------------------------------- REG51.H Header file for generic 80C51 and 80C31 microcontroller. Copyright (c) 1988-2001 Keil Elektronik GmbH and Keil Software, Inc. All rights reserved. --------------------------------------------------------------------------*/ /* BYTE Register */ sfr P0 = 0x80; sfr P1 = 0x90; sfr P2 = 0xA0; sfr P3 = 0xB0; sfr PSW = 0xD0; sfr ACC = 0xE0; sfr B = 0xF0; sfr SP = 0x81; sfr DPL = 0x82; sfr DPH = 0x83; sfr PCON = 0x87; sfr TCON = 0x88; sfr TMOD = 0x89; sfr TL0 = 0x8A; sfr TL1 = 0x8B; sfr TH0 = 0x8C; sfr TH1 = 0x8D; sfr IE = 0xA8; sfr IP = 0xB8; sfr SCON = 0x98; sfr SBUF = 0x99; /* BIT Register */ /* PSW */ sbit CY = 0xD7; sbit AC = 0xD6; sbit F0 = 0xD5; sbit RS1 = 0xD4; sbit RS0 = 0xD3; sbit OV = 0xD2; sbit P = 0xD0; /* TCON */ sbit TF1 = 0x8F; sbit TR1 = 0x8E; sbit TF0 = 0x8D; sbit TR0 = 0x8C; sbit IE1 = 0x8B; sbit IT1 = 0x8A; sbit IE0 = 0x89; sbit IT0 = 0x88; /* IE */ sbit EA = 0xAF; sbit ES = 0xAC; sbit ET1 = 0xAB; sbit EX1 = 0xAA; sbit ET0 = 0xA9; sbit EX0 = 0xA8; /* IP */ sbit PS = 0xBC; sbit PT1 = 0xBB; sbit PX1 = 0xBA; sbit PT0 = 0xB9; sbit PX0 = 0xB8; /* P3 */ sbit RD = 0xB7; sbit WR = 0xB6; sbit T1 = 0xB5; sbit T0 = 0xB4; sbit INT1 = 0xB3; sbit INT0 = 0xB2; sbit TXD = 0xB1; sbit RXD = 0xB0; /* SCON */ sbit SM0 = 0x9F; sbit SM1 = 0x9E; sbit SM2 = 0x9D; sbit REN = 0x9C; sbit TB8 = 0x9B; sbit RB8 = 0x9A; sbit TI = 0x99; sbit RI = 0x98; 熟悉80C51 內部結構的讀者不難看出,這里都是一些符號的定義,即規定符號名與地 址的對應關系。注意其中有 sfr P1 = 0x90; 這樣的一行(上文中用黑體表示),即定義P1 與地址0x90 對應,P1 口的地址就是0x90 (0x90 是C 語言中十六進制數的寫法,相當于匯編語言中寫90H)。 從這里還可以看到一個頻繁出現的詞:sfr sfr 并標準C 語言的關鍵字,而是Keil 為能直接訪問80C51 中的SFR 而提供了一個新 的關鍵詞,其用法是: sfrt 變量名=地址值。 2)符號P1_0 來表示P1.0 引腳。 在C 語言里,如果直接寫P1.0,C 編譯器并不能識別,而且P1.0 也不是一個合法的C 語言變量名,所以得給它另起一個名字,這里起的名為P1_0,可是P1_0 是不是就是P1.0 呢?你這么認為,C 編譯器可不這么認為,所以必須給它們建立聯系,這里使用了Keil C 的關鍵字sbit 來定義,sbit 的用法有三種: 第一種方法:sbit 位變量名=地址值 第二種方法:sbit 位變量名=SFR 名稱^變量位地址值 第三種方法:sbit 位變量名=SFR 地址值^變量位地址值 如定義PSW 中的OV 可以用以下三種方法: sbit OV=0xd2 (1)說明:0xd2 是OV 的位地址值 sbit OV=PSW^2 (2)說明:其中PSW 必須先用sfr 定義好 sbit OV=0xD0^2 (3)說明:0xD0 就是PSW 的地址值 因此這里用sfr P1_0=P1^0;就是定義用符號P1_0 來表示P1.0 引腳,如果你愿意也可以 起P10 一類的名字,只要下面程序中也隨之更改就行了。 3)main 稱為“主函數”。 每一個C 語言程序有且只有一個主函數,函數后面一定有一對大括號“{}”,在大括號 里面書寫其它程序。 從上面的分析我們了解了部分C 語言的特性,下面再看一個稍復雜一點的例子。 例1-2 讓接在P1.0 引腳上的LED 閃爍發光 /************************************************* 平凡單片機工作室 http://www.mcustudio.com Copyright 2003 pingfan's mcustudio All rights Reserved 作者:周堅 ddss.c 單燈閃爍程序 *************************************************/ #include "reg51.h" #define uchar unsigned char #define uint unsigned int sbit P10=P1^0; /*延時程序 由Delay 參數確定延遲時間 */ void mDelay(unsigned int Delay) { unsigned int i; for(;Delay>0;Delay--) { for(i=0;i<124;i++) {;} } } void main() { for(;;) { P10=!P10; //取反P1.0 引腳 mDelay(1000); } } 程序分析:主程序main 中的第一行暫且不看,第二行是“P1_0=!P1_0;”,在P1_0 前有 一個符號“!”,符號“!”是C 語言的一個運算符,就像數學中的“+”、“-”一樣,是一種 運算任號,意義是“取反”,即將該符號后面的那個變量的值取反。 注意:取反運算只是對變量的值而言的,并不會自動改變變量本身?梢哉J為C 編譯 器在處理“!P1_0”時,將P1_0 的值給了一個臨時變量,然后對這個臨時變量取反,而不 是直接對P1_0 取反,因此取反完畢后還要使用賦值符號(“=”)將取反后的值再賦給P1_0, 這樣,如果原來P1.0 是低電平(LED 亮),那么取反后,P1.0 就是高電平(LED 滅),反之, 如果P1.0 是高電平,取反后,P1.0 就是低電平,這條指令被反復地執行,接在P1.0 上燈就 會不斷“亮”、“滅”。 該條指令會被反復執行的關鍵就在于main 中的第一行程序:for(;;),這里不對此作詳細 的介紹,讀者暫時只要知道,這行程序連同其后的一對大括號“{}”構成了一個無限循環語 句,該大括號內的語句會被反復執行。 第三行程序是:“mDelay(1000);”,這行程序的用途是延時1s 時間,由于單片機執行指 令的速度很快,如果不進行延時,燈亮之后馬上就滅,滅了之后馬上就亮,速度太快,人眼 根本無法分辨。 這里mDelay(1000)并不是由Keil C 提供的庫函數,即你不能在任何情況下寫這樣一行 程序以實現延時。如果在編寫其它程序時寫上這么一行,會發現編譯通不過。那么這里為什 么又是正確的呢?注意觀察,可以發現這個程序中有void mDelay(…)這樣一行,可見, mDelay 這個詞是我們自己起的名字,并且為此編寫了一些程序行,如果你的程序中沒有這 么一段程序行,那就不能使用mDelay(1000)了。有人腦子快,可能馬上想到,我可不可 以把這段程序也復制到我其它程序中,然后就可以用mDelay(1000)了呢?回答是,那當然 就可以了。還有一點需要說明,mDelay 這個名稱是由編程者自己命名的,可自行更改,但 一旦更改了名稱,main()函數中的名字也要作相應的更改。 mDelay 后面有一個小括號,小括號里有數據(1000),這個1000 被稱之“參數”,用它 可以在一定范圍內調整延時時間的長短,這里用1000 來要求延時時間為1000 毫秒,要做到 這一點,必須由我們自己編寫的mDelay 那段程序決定的,詳細情況在后面循環程序中再作 分析,這里就不介紹了。 1.2 Keil 工程的建立 要使用Keil 軟件,首先要正確安裝Keil 軟件,該軟件的Eval 版本可以直接去 http://www.keil.com 下載,安裝時選擇Eval Vision,其它步驟與一般Windows 程序安裝類似, 這里就不再贅述了。安裝完成后,將Ledkey.dll 文件復制到Keil 安裝目錄下的C51\BIN 文 件夾下,這是作者提供的鍵盤與LED 實驗仿真板,可與Keil 軟件配合,在計算機上模擬LED 和按鍵的功能。 啟動μVison,點擊“File?New…”在工程管理器的右側打開一個新的文件輸入窗口, 在這個窗口里輸入例1-2 中的源程序,注意大小寫及每行后的分號,不要錯輸及漏輸。 輸入完畢之后,選擇“File?Save”,給這個文件取名保存,取名字的時候必須要加上擴 展名,一般C 語言程序均以“.C”為擴展名,這里將其命名為exam2.c,保存完畢后可以將 該文件關閉。 Keil 不能直接對單個的C 語言源程序進行處理,還必須選擇單片機型號;確定編譯、匯 編、連接的參數;指定調試的方式;而且一些項目中往往有多個文件,為管理和使用方便, Keil 使用工程(Project)這一概念,將這些參數設置和所需的所有文件都加在一個工程中, 只能對工程而不能對單一的源程序進行編譯和連接等操作。 點擊“Project->New Project…”菜單,出現對話框,要求給將要建立的工程起一個名字, 這里起名為exam2,不需要輸入擴展名。點擊“保存”按鈕,出現第二個對話框,如圖1-2 所示,這個對話框要求選擇目標CPU(即你所用芯片的型號),Keil 支持的CPU 很多,這 里選擇Atmel 公司的89S52 芯片。點擊ATMEL 前面的“+”號,展開該層,點擊其中的89S52, 然后再點擊“確定”按鈕,回到主窗口,此時,在工程窗口的文件頁中,出現了“Target 1”, 前面有“+”號,點擊“+”號展開,可以看到下一層的“Source Group1”,這時的工程還是 一個空的工程,里面什么文件也沒有,需要手動把剛才編寫好的源程序加入,點擊“Source Group1”使其反白顯示,然后,點擊鼠標右鍵,出現一個下拉菜單,如圖1-3 所示,選中其 中的“Add file to Group”Source Group1”,出現一個對話框,要求尋找源文件。 雙擊exam2.c 文件,將文件加入項目,注意,在文件加入項目后,該對話框并不消失, 等待繼續加入其它文件,但初學時常會誤認為操作沒有成功而再次雙擊同一文件,這時會出 現如圖1-4 所示的對話框,提示你所選文件已在列表中,此時應點擊“確定”,返回前一對 話框,然后點擊“Close”即可返回主接口,返回后,點擊“Source Group 1”前的加號,exam3.c 文件已在其中。雙擊文件名,即打開該源程序。 1.3 工程的詳細設置 工程建立好以后,還要對工程進行進一步的設置,以滿足要求。 首先點擊左邊Project 窗口的Target 1,然后使用菜單“Project->Option for target ‘target1’” 即出現對工程設置的對話框,這個對話框共有8 個頁面,大部份設置項取默認值就行了。 Target 頁 圖1-2 選擇單片機型號 圖1-3 加入文件 如圖1-5 所示,Xtal 后面的數值是晶振頻率值,默認值是所選目標CPU 的最高可用頻 率值,該值與最終產生的目標代碼無關,僅用于軟件模擬調試時顯示程序執行時間。正確設 置該數值可使顯示時間與實際所用時間一致,一般將其設置成與你的硬件所用晶振頻率相 同,如果沒必要了解程序執行的時間,也可以不設。 Memory Model 用于設置RAM 使用情況,有三個選擇項: Small: 所有變量都在單片機的內部RAM 中; Compact:可以使用一頁(256 字節)外部擴展RAM; Larget: 可以使用全部外部的擴展RAM。 Code Model 用于設置ROM 空間的使用,同樣也有三個選擇項: Small:只用低于2K 的程序空間; Compact:單個函數的代碼量不能超過2K,整個程序可以使用64K 程序空間; Larget:可用全部64K 空間; 這些選擇項必須根據所用硬件來決定,由于本例是單片應用,所以均不重新選擇,按默 認值設置。 Operating:選擇是否使用操作系統,可以選擇Keil 提供了兩種操作系統:Rtx tiny 和 Rtx full,也可以不用操作系統(None),這里使用默認項None,即不用操作系統。 圖1-5 設置目標 圖1-4 重復加入源程序得到的提示 OutPut 頁 如圖1-6 所示,這里面也有多個選擇項,其中Creat Hex file 用于生成可執行代碼文件, 該文件可以用編程器寫入單片機芯片,其格式為intelHEX 格式,文件的擴展名為.HEX,默 認情況下該項未被選中,如果要寫片做硬件實驗,就必須選中該項。 工程設置對話框中的其它各頁面與C51 編譯選項、A51 的匯編選項、BL51 連接器的連 接選項等用法有關,這里均取默認值,不作任何修改。以下僅對一些有關頁面中常用的選項 作一個簡單介紹。 Listing 頁 該頁用于調整生成的列表文件選項。在匯編或編譯完成后將產生(*.lst)的列表文件, 在連接完成后也將產生(*.m51)的列表文件,該頁用于對列表文件的內容和形式進行細致 的調節,其中比較常用的選項是“C Compile Listing”下的“Assamble Code”項,選中該項 可以在列表文件中生成C 語言源程序所對應的匯編代碼,建議會使用匯編語言的C 初學者 選中該項,在編譯完成后多觀察相應的List 文件,查看C 源代碼與對應匯編代碼,對于提 高C 語言編程能力大有好處。 C51 頁 該頁用于對Keil 的C51 編譯器的編譯過程進行控制,其中比較常用的是“Code Optimization”組,如圖1.7 所示,該組中Level 是優化等級,C51 在對源程序進行編譯時, 可以對代碼多至9 級優化,默認使用第8 級,一般不必修改,如果在編譯中出現一些問題, 可以降低優化級別試一試。Emphasis 是選擇編譯優先方式,第一項是代碼量優化(最終生 成的代碼量小);第二項是速度優先(最終生成的代碼速度快);第三項是缺省。默認采用速 度優先,可根據需要更改。 圖1-6 設置輸出文件 Debug 頁 該頁用于設置調試器,Keil 提供了仿真器和一些硬件調試方法,如果沒有相應的硬件調 試器,應選擇Use Simulator,其余設置一般不必更改,有關該頁的詳細情況將在程序調試部 分再詳細介紹。 至此,設置完成,下面介紹如何編譯、連接程序以獲得目標代碼,以及如何進行程序的 調試工作。 1.4 編譯、連接 下面我們通過一個例子來介紹C 程序編譯、連接的過程。這個例子使P1 口所接LED 以流水燈狀態顯示。 將下面的源程序輸入,命名為exam3.c,并建立名為exam3 的工程文件,將exam3.c 文 件加入該工程中,設置工程,在Target 頁將Xtal 后的值由24.0 改為12.0,以便后面調試時 觀察延時時間是否正確,本項目中還要用到我們所提供的實驗仿真板,為此需在Debug 頁 對Dialog DLL 對話框作一個設置,在進行項目設置時點擊Debug,打開Debug 頁,可以看 到Dialog DLL 對話框后的Parmeter:輸入框中已有默認值-pAT52,在其后鍵入空格后再輸入 -dledkey,如圖1-8 所示。 例1-3 使P1 口所接LED 以流水燈狀態顯示 /************************************************** ; 平凡單片機工作室 ; http://www.mcustudio.com ; Copyright 2003 pingfan's McuStudio ; All rights Reserved 圖1-7 C51 編譯器選項 ;作者:周堅 ;lsd.c ;流水燈程序 **************************************************/ #include "reg51.h" #include "intrins.h" #define uchar unsigned char #define uint unsigned int /*延時程序 由Delay 參數確定延遲時間 */ void mDelay(unsigned int Delay) { unsigned int i; for(;Delay>0;Delay--) { for(i=0;i<124;i++) {;} } } void main() { unsigned char OutData=0xfe; for(;;) { 圖1-8 Debug 選項設置 P1=OutData; OutData=_crol_(OutData,1); //循環左移 mDelay(1000); /*延時1000 毫秒*/ } } 設置好工程后,即可進行編譯、連接。選擇菜單Project->Build target,對當前工程進行 連接,如果當前文件已修改,將先對該文件進行編譯,然后再連接以產生目標代碼;如果選 擇Rebuild All target files 將會對當前工程中的所有文件重新進行編譯然后再連接,確保最終 生產的目標代碼是最新的,而Translate ….項則僅對當前文件進行編譯,不進行連接。以上 操作也可以通過工具欄按鈕直接進行。圖1-9 是有關編譯、設置的工具欄按鈕,從左到右分 別是:編譯、編譯連接、全部重建、停止編譯和對工程進行設置。 編譯過程中的信息將出現在輸出窗口中的Build 頁中,如果源程序中有語法錯誤,會有 錯誤報告出現,雙擊該行,可以定位到出錯的位置,對源程序修改之后再次編譯,最終要得 到如圖1-10 所示的結果,提示獲得了名為exam3.hex 的文件,該文件即可被編程器讀入并 寫到芯片中,同時還可看到,該程序的代碼量(code=63),內部RAM 的使用量(data=9), 外部RAM 的使用量(xdata=0)等一些信息。除此之外,編譯、連接還產生了一些其它相關 的文件,可被用于Keil 的仿真與調試,到了這一步后即進行調試。 1.5 程序的調試 在對工程成功地進行匯編、連接以后,按Ctrl+F5 或者使用菜單Debug->Start/Stop Debug Session 即可進入調試狀態,Keil 內建了一個仿真CPU 用來模擬執行程序,該仿真CPU 功 能強大,可以在沒有硬件和仿真機的情況下進行程序的調試。 進入調試狀態后,Debug 菜單項中原來不能用的命令現在已可以使用了,多出一個用于 運行和調試的工具條,如圖1-11 所示,Debug 菜單上的大部份命令可以在此找到對應的快 捷按鈕,從左到右依次是復位、運行、暫停、單步、過程單步、執行完當前子程序、運行到 當前行、下一狀態、打開跟蹤、觀察跟蹤、反匯編窗口、觀察窗口、代碼作用范圍分析、1 #串行窗口、內存窗口、性能分析、工具按鈕等命令。 點擊菜單Peripherals,即會多出一項“鍵盤LED 仿真板(K)”,選中該項,即會出現如 圖1-9 有關編譯、連接、項目設置的工具條 圖1-11 調試工具條 圖1-10 編譯、連接后得到目標代碼 圖1-12 所示界面。 使用菜單STEP 或相應的命令按鈕或使用快捷鍵F11 可以單步執行程序,使用菜單STEP OVER 或功能鍵F10 可以以過程單步形式執行命令,所謂過程單步,是指把C 語言中的一 個函數作為一條語句來全速執行。 按下F11 鍵,可以看到源程序窗口的左邊出現了一個黃色調試箭頭,指向源程序的第一 行。每按一次F11,即執行該箭頭所指程序行,然后箭頭指向下一行,當箭頭指向 “mDelay(1000);”行時,再次按下F11,會發現,箭頭指向了延時子程序mDelay 的第一行。 不斷按F11 鍵,即可逐步執行延時子程序。 如果mDelay 程序有錯誤,可以通過單步執行來查找錯誤,但是如果mDelay 程序已正 確,每次進行程序調試都要反復執行這些程序行,會使得調試效率很低,為此可以在調試時 使用F10 來替代F11,在main 函數中執行到mDelay(1000)時將該行作為一條語句快速執行 完畢。 Keil 軟件還提供了一些窗口,用以觀察一些系統中重要的寄存器或變量的值,這也是很 重要的調試方法。 以下通過一個對延時程序的延遲時間的調整來對這些調試方法作一個簡單的介紹。 這個程序中用到了延時程序mDelay,如果使用匯編語言編程,每段程序的延遲時間可 以非常精確地計算出來,而使用C 語言編程,就沒有辦法事先計算了。為此,可以使用觀 察程序執行時間的方法了來解。進入調試狀態后,窗口左側是寄存器和一些重要的系統變量 的窗口,其中有一項是sec,即統計從開始執行到目前為止用去的時間。按F10,以過程單 步的形式執行程序,在執行到mDelay(1000)這一行之前停下,查看sec 的值(把鼠標停在sec 后的數值上即可看到完整的數值),記下該數值,然后按下F10,執行完mDelay(1000)后再 次觀察sec 值,如圖1-13 所示,這里前后兩次觀察到的值分別是:0.00040400 和1.01442600, 其差值為1.014022s,如果將該值改為124 可獲得更接近于1s 的數值,而當該值取123 時所 獲得的延時值將小于1s,因此,最佳的取值應該是124。 圖1-12 51 單片機實驗仿真板 1.6 C 語言的一些特點 通過上述的幾個例子,可以得出一些結論: 1、C 程序是由函數構成的,一個C 源程序至少包括一個函數,一個C 源程序有且只有 一個名為main()的函數,也可能包含其它函數,因此,函數是C 程序的基本單位。主程序 通過直接書寫語句和調用其它函數來實現有關功能,這些其它函數可以是由C 語言本身提 供給我們的(如例3 中的_crol_(…)函數),這樣的函數稱之為庫函數,也可以是用戶自 己編寫的(如例2、3 中用的mDelay(…)函數),這樣的函數稱之為用戶自定義函數。那 么庫函數和用戶自定義函數有什么區別呢?簡單地說,任何使用Keil C 語言的人,都可以 直接調用C 的庫函數而不需要為這個函數寫任何代碼,只需要包含具有該函數說明的相應 的頭文件即可;而自定義函數則是完全個性化的,是用戶根據自己需要而編寫的。Keil C 提 供了100 多個庫函數供我們直接使用。 2、一個函數由兩部份組成: (1)函數的首部、即函數的第一行。包括函數名、函數類型、函數屬性、函數參數(形 參)名、參數類型。 例如:void mDelay (unsigned int DelayTime) 一個函數名后面必須跟一對圓括號,即便沒有任何參數也是如此。 (2)函數體,即函數首部下面的大括號“{}”內的部份。如果一個函數內有多個大括 號,則最外層的一對“{}”為函數體的范圍。 函數體一般包括: 聲明部份:在這部份中定義所用到的變量,例1.2 中unsigned char j。 執行部份:由若干個語句組成。 在某此情況下也可以沒有聲明部份,甚至即沒有聲明部份,也沒有執行部份,如: void mDelay() {} 這是一個空函數,什么也不干,但它是合法的。 在編寫程序時,可以利用空函數,比如主程序需要調用一個延時函數,可具體延時多少, 怎么個延時法,暫時還不清楚,我們可以主程序的框架結構弄清,先編譯通過,把架子搭起 來再說,至于里面的細節,可以在以后慢慢地填,這時利用空函數,先寫這么一個函數,這 樣在主程序中就可以調用它了。 3、一個C 語言程序,總是從main 函數開始執行的,而不管物理位置上這個main()放 在什么地方。例1.2 中就是放在了最后,事實上這往往是最常用的一種方式。 圖1-13 觀察sec 確定延時時間 4、主程序中的mDelay 如果寫成mdelay 就會編譯出錯,即C 語言區分大小寫,這一點 往往讓初學者非常困惑,尤其是學過一門其它語言的人,有人喜歡,有人不喜歡,但不管怎 樣,你得遵守這一規定。 5、C 語言書寫的格式自由,可以在一行寫多個語句,也可以把一個語句寫在多行。沒 有行號(但可以有標號),書寫的縮進沒有要求。但是建議讀者自己按一定的規范來寫,可 以給自己帶來方便。 6、每個語句和資料定義的最后必須有一個分號,分號是C 語句的必要組成部份。 7、可以用/*…..*/的形式為C 程序的任何一部份作注釋,在“/*”開始后,一直到“*/” 為止的中間的任何內容都被認為是注釋,所以在書寫特別是修改源程序時特別要注意,有時 無意之中刪掉一個“*/”,結果,從這里開始一直要遇到下一個“*/”中的全部內容都被認 為是注釋了。原本好好的一個程序,編譯已過通過了,稍作修改,一下出現了幾十甚至上百 個錯誤,初學C 的人往往對此深感頭痛,這時就要檢查一下,是不是有這樣的情況,如果 有的話,趕緊把這個“*/”補上。 特別地,Keil C 也支持C++風格的注釋,就是用“//”引導的后面的語句是注釋,例: P1_0=!P1_0; //取反P1.0 這種風格的注釋,只對本行有效,所以不會出現上面的問題,而且書寫比較方便,所以 在只需要一行注釋的時候,我們往往采用這種格式。但要注意,只有Keil C 支持這種格式, 早期的Franklin C 以及PC 機上用的TC 都不支持這種格式的注釋,用上這種注釋,編譯時 通不過,會報告編譯錯誤。 第2 章 分支程序設計 第一部分課程學習了如何建立Keil C 的編程環境,并了解了一些C 語言的基礎知識, 這一部分將通過一個鍵控流水燈程序的分析來學習分支程序設計。 2.1 程序功能與實現 硬件電路描述如下:89S52 單片機的P1 口接有8 個LED,當某一端口輸出為“0”時, 相應的LED 點亮,P3.2、P3.3、P3.4、P3.5 分別接有四個按鈕K1~K4,按下按鈕時,相應 引腳被接地,F要求編寫可鍵控的流水燈程序,當K1 按下時,開始流動,K2 按下時停止 流動,全部燈滅,K3 使燈由上往下流動,K4 使燈由下往上流動。 下面首先給出程序,然后再進行分析。 例2-1:鍵控流水燈的程序 #include "reg51.h" #include "intrins.h" #define uchar unsigned char void mDelay(unsigned int DelayTime) { unsigned int j=0; for(;DelayTime>0;DelayTime--) { for(j=0;j<125;j++) {;} }} uchar Key() { uchar KeyV; uchar tmp; P3=P3|0x3c; //四個按鍵所接位置 KeyV=P3; if((KeyV|0xc3)==0xff) //無鍵按下 return(0); mDelay(10); //延時,去鍵抖 KeyV=P3; if((KeyV|0xc3)==0xff) return(0); else { for(;;){ tmp=P3; if((tmp|0xc3)==0xff) break;} return(KeyV);}} void main() { unsigned char OutData=0xfe; bit UpDown=0; bit Start=0; uchar KValue; for(;;) { KValue=Key(); switch (KValue) { case 0xfb: //P3.2=0,Start { Start=1; break; } case 0xf7: //P3.3=0,Stop { Start=0; break; } case 0xef: //P3.4=0 Up { UpDown=1; break; } case 0xdf: //P3.5=0 Down { UpDown=0; break; } } if(Start) { if(UpDown) OutData=_crol_(OutData,1); else OutData=_cror_(OutData,1); P1=OutData; } else P1=0xff; //否則燈全滅 mDelay(1000); } } 單片機的C 語言輕松入門 19 輸入源程序,保存為exam21.c,建立名為exam21 的工程文件,選擇的CPU 型號為 AT89S52,在Debug 頁加入-ddpj6,以便使用單片機實驗仿真板,其他按默認設置。正確編 譯、鏈接后進入調試模式,點擊Peripherals?51 實驗仿真板,打開實驗仿真板,選擇Run (全速運行),此時實驗仿真板沒有變化,用鼠標點擊上方的K1 按鈕,松開后即可看到Led “流動”起來,初始狀態是由下往上流動,點擊K3 按鈕,可改變LED 的流動方向,改為 由上往下流動,點擊K4 按鈕,又可將流動方向變換回來。點擊K2 按鈕,可使流動停止, 所有LED“熄滅”。 2.1.1 程序分析 本程序中運用到了兩種選擇結構的程序:if 和switch,if 語句最常用的形式是: if(關系表達式)語句1 else 語句2 2.1.2 關系運算符和關系表達式 所謂“關系運算”實際上是兩個值作一個比較,判斷其比較的結果是否符合給定的條件。 關系運算的結果只有2 種可能,即“真”和“假”。例:3>2 的結果為真,而3<2 的結果為 假。 C 語言一共提供了6 種關系運算符:“<”(小于)、“<=”(小于等于)、“>”(大于)、 “>=(大于等于)”、“==”(等于)和“!=”(不等于)。 用關系運算符將兩個表達式連接起來的式子,稱為關系表達式。例: a>b,a+b>b+c,(a=3)>=(b=5)等都是合法的關系表達式。關系表達式的值只有兩種 可能,即“真”和“假”。在C 語言中,沒有專門的邏輯型變量,如果運算的結果是“真”, 用數值“1”表示,而運算的結果是“假”則用數值“0”表示。 如式子:x1=3>2 的結果是x1 等于1,原因是3>2 的結果是“真”,即其結果為1,該結 果被“=”號賦給了x1,這里須注意,“=”不是等于之意(C 語言中等于用“==”表示), 而是賦值號,即將該號后面的值賦給該號前面的變量,所以最終結果是x1 等于1。 式子:x2=3<=2 的結果是x2=0,請自行分析。 2.2 邏輯運算符和邏輯表達式 用邏輯運算符將關系表達式或邏輯量連接起來的式子就是邏輯表達式。C 語言提供了三 種邏輯運算符:“&&”(邏輯與)、“||”(邏輯或)和“!”(邏輯非)。 C 語言編譯系統在給出邏輯運算的結果時,用“1”表示真,而用“0”表示假,但是在 判斷一個量是否是“真”時,以0 代表“假”,而以非0 代表“真”,這一點務必要注意。以 下是一些例子: (1) 若a=10,則!a 的值為0,因為10 被作為真處理,取反之后為假,系統給出 的假的值為0。 (2) 如果a=--2,結果與上完全相同,原因也同上,初學時常會誤以為負值為假, 所以這里特別提醒注意。 (3) 若a=10,b=20,則a&&b 的值為1,a||b 的結果也為1,原因為參于邏輯運算 時不論a 與b 的值究竟是多少,只要是非零,就被當作是“真”,“真”與“真” 相與或者相或,結果都為真,系統給出的結果是1。 單片機的C 語言輕松入門 20 2.3 if 語句 if 語句是用來判定所給定的條件是否滿足根據判定的結果(真或假)決定執行給出的兩 種操作之一。 C 語言提供了三種形式的if 語句 1. if(表達式) 語句 如果表達式的結果為真,則執行語句,否則不執行 2. if(表達式) 語句1 else 語句2 如果表達式的結果為真,則執行語句1,否則執行語句2 3.if(表達式1) 語句1 else if(表達式2) 語句2 else if(表達式3) 語句3 … else if(表達式m) 語句m else 語句n 這條語句執行如圖2 所示。 上述程序中的如下語句: if((KeyV|0xc3)==0xff) //無鍵按下 return(0); 是第一種if 語句的應用。該語句中“|”符號是C 語言中的位運算符,按位相或的意思, 相當于匯編語言中“ORL”指令,將讀取的P3 口的值KeyV 與0xc3(即11000011B)按位 或,如果結果為0xff(即11111111B)說明沒有鍵被按下,因為中間4 位接有按鍵,如果有 鍵按下,那么P3 口值的中間4 位中必然有一位或更多位是“0”。該語句中的“return(0)” 是返回之意,相當于匯編語言中的“ret”指令,通過該語句可以帶返回值,即該號中的數 值,返回值就是這個函數的值,在這個函數被調用時,用了如下的形式:KValue=Key();因 此,返回的結果是該值被賦給Kvalue 這個變量。因此,如果沒有鍵被按下,則直接返回, 并且Kvalue 的值將變為0。如果有鍵被按下,那么return(0)將不會被執行。 程序其他地方還有這樣的用法,請注意觀察與分析。 程序中: if(Start) {… 燈流動顯示的代碼 } else P1=0xff; //否則燈全滅 是if 語句的第二種用法,其中Start 是一個位變量,該變量在main 函數的中被定義,并 賦以初值0,該變量在按鍵K1 被按下后置為1,而K2 按下后被清為0,用來控制燈流動是 否開始。這里就是判斷該變量并決定燈流動是否開始的代碼,觀察if 后面括號中的寫法, 與其他語言中寫法很不一樣,并沒有一個關系表達式,而僅僅只有一個變量名,C 根據這個 量是0 還是1 來決定程序的走向,如果為1 則執行燈流動顯示的代碼,如果為0,則執行 P1=0xff;語句?梢,在C 語言中,數據類型的概念比其他很多的編程語言要“弱化”,或 者說C 更著重從本質的角度去考慮問題,if 后面的括號中不僅可以是關系表達式,也可以是 算術表達式,還可以就是一個變量,甚至是一個常量,不管怎樣,C 總是根據這個表達式的 值是零還是非零來決定程序的走向,這個特點是其他中所沒有的,請注意理解。 if 語句的第三種用法在本程序中沒有出現,下面我們舉一例說明。在上述的鍵盤處理函 單片機的C 語言輕松入門 21 數Key 中,如果沒鍵被按下,返回值是0,如果有鍵被按下,經過去鍵抖的處理,將返回鍵 值,程序中的“return(KeyV);”即返回鍵值。當K1 被按下(P3.2 接地)時,返回值是0xfb (11111011B),而K2 被按下(P3.3 接地)時,返回值是0xf7(11110111B),K3 被按下(P3.4 接地)時,返回值是0xef(11101111B),K4 被按下(P3.5 接地)時,返回值是0xdf(11011111B), 該值將被賦給主程序中調用鍵盤程序的變量KValue。程序用了另一種選擇結構switch 進行 處理,關于switch 將在稍后介紹。下面用if 語句來改寫: if(KValue==0xfb) {Start=1;} else if(KValue==0xf7) {Start=0;} else if(KValue==0xef) {UpDown=1;} else if(KValue==0xdf) {UpDown=0;} else {//意外處理} …… 程序中第一條語句判斷Kvalue 是否等于0xfb,如果是就執行Start=1;執行完畢即退出if 語句,執行if 語句下面的程序,如果Kvalue 不等于0xfb 就轉去下一個else if 即判斷Kvalue 是否等于0xf7,如果等于則執行Start=0;,并退出if 語句…這樣一直到最后一個else if 后面 的條件判斷完畢為止,如果所有的條件都不滿足,那么就去執行else 后面的語句(通常這 意味著出現了異常,在這里來統一處理這種異常情況)。 2.4 if 語句的嵌套 在if 語句中又包含一個或多個語句稱為if 語句的嵌套。一般形式如下 if() if() 語句1 else 語句2 else if() 語句3 else 語句4 應當注意if 與else 的配對關系,else 總是與它上面的最近的if 配對。如果寫成 if() if()語句1 else 語句2 編程者的本意是外層的if 與else 配對,縮進的if 語句為內嵌的if 語句,但實際上else 將 與縮進的那個if 配對,因為兩者最近,從而造邁岐義。為避免這種情況,建議編程時使用 大括號將內嵌的if 語句括起來,這樣可以避免出現這樣的問題。 單片機的C 語言輕松入門 22 2.5 swich 語句 當程序中有多個分支時,可以使用if 嵌套實現,但是當分支較多時,則嵌套的if 語層 數多,程序冗長而且可讀性降低。C 語言提供了switch 語句直接處理多分支選擇。Switch 的一般形式如下: switch(表達式) {case 常量表達式1:語句1 case 常量表達式2:語句2 …… case 常量表達式n:語句n default:語句n+1 } 說明:switch 后面括號內的“表達式”,ANSI 標準允許它為任何類型;當表達式的值與 某一個case 后面的常量表達式相等時,就執行此case 后面的語句,若所有的case 中的常量 表達式的值都沒有與表達式值匹配的,就執行default 后面的語句;每一個case 的常量表達 式的值必須不相同;各個case 和default 的出現次序不影響執行結果。 另外特別需要說明的是,執行完一個case 后面的語句后,并不會自動跳出switch,轉 而去執行其后面的語句,如上述例子中如果這么寫 switch (KValue) { case 0xfb: Start=1; case 0xf7: Start=0; case 0xef: UpDown=1; case 0xdf: UpDown=0; } if(Start) { ……} 假如KValue 的值是0xfb,則在轉到此處執行“Start=1;”后,并不是轉去執行switch 語 句下面的if 語句,而是將從這一行開始,依次執行下面的語句即“Start=0;”、“UpDown=1;” “UpDown=0;”,顯然,這樣不能滿足要求,因此,通常在每一段case 的結束加入“break;” 語句,使流程序退出switch 結構,即終止switch 語句的執行。 單片機的C 語言輕松入門 23 第3 章 數據類型 數據是計算機處理的對象,計算機要處理的一切內容最終將要以數據的形式出現,因此, 程序設計中的數據有著很多種不同的含義,不同的含義的數據往往以不同的形式表現出來, 這些數據在計算機內部進行處理、存儲時往往有著很大的區別。下面我們來了解C 語言數 據類型的有關知識。 3.1 C 語言的數據類型概述 C 語言中常的數據類型有:整型、字符型、實型等。 C 語言中數據有常量與變量之分,它們分別屬于以上這些類型。由以上這此數據類型還 可以構成更復雜的數據結構,在程序中用到的所有的數據都必須為其指定類型。 3.2 常量與變量 在程序運行過程中,其值不能被改變的量稱為常量。常量區分為不同的類型,如12、0 為整型常量,3.14、2.55 為實型常量,‘a’、‘b’是字符型常量。 例1 符號常量的使用,在P1 口接有8 個LED,執行下面的程序: #define LIGHT0 0xfe #include “reg51.h” void main() { P1=LIGHT0; } 程序中用#define LIGHT0 0xfe 來定義符號LIGHT0 等于0xfe,以后程序中所有出現 LIGHT0 的地方均會用0xfe 來替代,因此,這個程序執行結果就是P1=0xfe,即接在P1.0 引腳上的LED 點亮。 這種用標識符代表的常量,稱為符號常量。使用符號常量的好處是: 1.含義清楚。在單片機程序中,常有一些量是具有特定含義的,如某單片機系統擴展 了一些外部芯片,每一塊芯片的地址即可用符號常量定義,如: #define PORTA 0x7fff #define PORTB 0x7ffe 程序中可以用PORTA、PORTB 來對端口進行操作,而不必寫0x7ff、0x7fe。顯然,這 兩個符號比兩個數字更能令人明白其含義。在給符號常量起名字時,盡量要做到“見名知意” 以充分發揮這一特點。 2、在需要改變一個常量時能做到“一改全改”。如果由于某種原因,端口的地址發生了 變化(如修改了硬件),由0x7fff 改成了0x3fff,那么只要將所定義的語句改動一下: #define PORTA 0x3fff 即可,不僅方便,而且能避免出錯。設想一下,如果不用符號常量,要在成百上千行程 序中把所有表示端口地址的0x7fff 找出來并改掉可不是件容易的事。 對于符號常量,初學者往往會和變量的概念混淆起來,它們之間有什么區別呢? 符號常量不等同于變量,它的值在整個作用域范圍內不能改變,也不能被再次賦值。比 單片機的C 語言輕松入門 24 如下面的語句是錯誤的: LIGHT=0x01; 值可以改變的量稱為變量。一個變量應該有一個名字,在內存中占據一定的存儲單元, 在該存儲單元中存放變量的值。請注意變量名與變量值的區別,下面從匯編語言的角度對此 作一個解釋。使用匯編語言編程時,必須自行確定RAM 單元的用途,如某儀表有4 位LED 數碼管,編程時將3CH~3FH 作為顯示緩沖區,當要顯示一個字串“1234”時,匯編語言可 以這樣寫: MOV 3CH,#1 MOV 3DH,#2 MOV 3EH,#3 MOV 3FH,#4 經過顯示程序處理后,在數碼管上顯示1234。這里的3CH 就是一個存儲單元,而送到 該單元中去的“1”是這個單元中的數值,顯示程序中需要的是待顯示的值“1”,但不借助 于3CH 又沒有辦法來用這個1,這就是數與該數據在地址單元的關系。同樣,在高級語言 中,變量名僅是一個符號,需要的是變量的值,但是不借助于該符號又無法用該值。實際上 如果在程序中寫上“x1=5;”這樣的語句,經過C 編譯程序的處理之后,也會變成“MOV 3CH,#5”之類的語句,只是究竟是使用3CH 作為存放x1 內容的單元還是其它如3DH,4FH 等作為存放x1 內容的單元,是由C 編譯器確定的。 用來標識變量名、符號常量名、函數名、數組名、類型名等的有效字符序列稱為標識符。 簡單地說,標識符就是一個名字。 C 語言規定標識符只能由字母、數字和下劃線三種字符組成,且第一個字符必須為字母 或下劃線,要注意的是C 語言中大寫字母與小寫字母被認為是兩個不同的字符,即Sum 與 sum 是兩個不同的標識符。 標準的C 語言并沒有規定標識符的長度,但是各個C 編譯系統有自己的規定,在Keil C 編譯器中可以使用長達數十個字符的標識符。 在C 語言中,要求對所有用到的變量作強制定義,也就是“先定義,后使用”。 初學者往往難于理解常量和變量在程序中各有什么用途,這里再舉個例子加以說明。 前面的課程中我們多次用到延時程序,其中調用延時程序是這么寫的: mDelay(1000); 這其中括號中參數1000 決定了燈流動的速度,在這些程序中我們并未對燈流動的速度 有要求,因此,直接將1000 寫入程序中即可,這就是常量。顯然,這個數據是不能在現場 修改的,如果使用中有人提出希望改變流水燈的速度,那么只能重新編程、寫片才能更改。 如果要求在現場有修改流水燈速度的要求,括號中就不能寫入一個常數,為此可以定義 一個變量(如Speed),寫程序時這么寫:mDelay(Speed);然后再編寫一段程序,使得Speed 的值可以被通過按鍵被修改,那么流水燈流動的速度就可以在現場修改了,顯然這時就需要 用到變量了。 3.3 字符型數據與整型數據 了解了變量與常量的關,再來看一看不同數據類型究竟有什么區別。前面程序中的延時 程序是這么寫的: void mDelay(unsigned int DelayTime) { unsigned int j=0; for(;DelayTime>0;DelayTime--) 單片機的C 語言輕松入門 25 { for(j=0;j<125;j++) {;} } } 在main 函數中用mDelay(1000)的形式調用該函數時,延時時間約為1s。如果將該函數 中的unsigned int j 改為unsigned char j,其他任何地方都不作更改,重新編譯、連接 后,可以發現延遲時間變為約0.38s。int 和char 是C 語言中的兩種不同的數據類型,可見 程序中僅改變數據類型就會得到不同的結果。那么int 和char 型的數據究竟有什么區別呢? 3.3.1 整型數據 1.整型數據在內存中的存放形式 如果定義了一個int 型變量i: int i=10; /*定義i 為整型變量,并將10 賦給該變量*/ 在Keil C 中規定使用二個字節表示int 型數據,因此,變量i 在內存中的實際占用情況 如下: 0000,0000,0000,1010 也就是整型數據總是用2 個字節存放,不足部分用0 補齊。 事實上,數據是以補碼的形式存在的。一個正數的補碼和其原碼的形式是相同的。如果 數值是負的,補碼的形式就不一樣了。求負數的補碼的方法是:將該數的絕對值的二進制形 式取反加1.例如,-10,第一步取-10 的絕對值10,其二進制編碼是1010,由于是整型數占 2 個字節(16 位),所以其二進制形式實為0000,0000,0000,1010,取反,即變為1111, 1111,1111,0101,然后再加1 變成了1111,1111,1111,0110,這個就是數-10 在內存中 的存放形式。這里其實只要搞清一點,就是必須補足16 位,其它的都不難理解。 2.整型變量的分類 整型變量的基本類型是int,可以加上有關數值范圍的修飾符。這些修飾符分兩類,一 類是short 和long,另一類是unsigned,這兩類可以同時使用。下面就來看有關這些修飾符 的內容。 在int 前加上short 或long 是表示數的大小的,對于keil C 來說,加short 和不加short 是一模一樣的(在有一些C 語言編譯系統中是不一樣的),所以,short 就不加討論了。如果 在int 前加上long 的修飾符,那么這個數就被稱之為長整數,在keil C 中,長整數要用4個 字節來存放(基本的int 型是2個字節)。顯然,長整數所能表達的范圍比整數要大,一個 長整數表達的范圍可以有: -231<x<231-1 大概是在正負21 億多。而不加long 修飾的int 型數據的范圍是-32768~32767,可見, 二者相差很遠。 第二類修飾符是unsigned 即無符號的意思,如果加上了這樣的一個修飾符,就說明其 后的數是一個無符號的數,無符號、有符號的差別還是數的范圍不一樣。對于unsigned int 而言,仍是用2 個字節(16 位)表示一個數,但其數的范圍是0~65535,對于unsigned long int 而言,仍是用4 個字節(32 位)表示一個數,但其數的范圍是0~232-1。 單片機的C 語言輕松入門 26 3.3.2 字符型數據 1.字符型數據在內存中的存放形式 數據在內存中是以二進制形式存放的,如果定義了一個char 型變量c: char c=10; /*定義c 為字符型變量,并將10 賦給該變量*/ 十進制數10 的二進制形式為1010,在Keil C 中規定使用一個字節表示char 型數據, 因此,變量c 在內存中的實際占用情如下: 0000,1010 弄明白了整型數據和字符型數據在內存中的存放,兩者在前述程序中引起的差別就不難 主理解了,當使用int 型變量時,程序需要對16 位二進制碼運算,而80C51 是8 位機,一 次只能處理8 位二進制碼,所以就要分次處理,因此延遲時間就變長了。 2.字符型變量的分類 字符型變量只有一個修飾符 unsigned 即無符號的。對于一個字符型變量來說,其表達 的范圍是-128~+127,而加上了unsigned 后,其表達的范圍變為0~255。 加了unsigned 和沒有加究竟有何區別呢?其實對于二進制形式而言,char 型變量表達 的范圍都是0000,0000~1111,1111 , 而int 型變量表達的范圍都是 0000,0000,0000,0000~1111,1111,1111,1111,只是我們對這些二進制數的理解不一樣而已。 使用keil C 時,不論是char 型還是int 型,我們都非常喜歡用unsigned 型的數據,這是 因為在處理有符號的數時,程序要對符號進行判斷和處理,運算的速度會減慢。 對單片機而言,速度比不上PC 機,又工作于實時狀態,任何提高效率的手段都要考慮。 3.字符的處理 在一般的C 語言中,字符型變量常用處理字符,如: char c=’a’; 之類等,即是定義一個字符型的變量c,然后將字符a 賦給該變量。進行這一操作時, 實際是將字符a 的ASCII 碼值賦給變量c,因此,做完這一操作之后,c 的值是97。 既然字符最終也是以數值來存儲的,那么和以下的語句: int i=97; 究竟有多大的區別呢?實際上它們是非常類似的,區別僅僅在于i 是16 位的,而c 是8 位的,當i 的值不超過255 時,兩者完全可以互換。C 語言對定符型數據作這樣的處理使用 得程序設計時增大了自由度。典型地,在C 語言中要將一個大寫字母轉化為一個小寫字母, 只要簡單地將該變量加上32 即可(查ASCII 碼表可以看到任意一個大寫字母比小寫字母小 32)。由于這一點,我們在單片機中往往是把字符型變量當成一個“8 位的整型變量”來用。 4.數的溢出 一個字符型數的最大值是127,一個整型數的最大值是32767,如果再加1,會出現什 么情況呢?下面我們用一個例子來說明。 例:演示字符型數據和整型數據溢出的例子 #includee “reg51.h” void main() { unsigned char a,b; int c,d; a=255; c=32767; 單片機的C 語言輕松入門 27 b=a+1; d=a+1; } 輸入該文件,命名為exam23.c,建立工程,加 入該文件,在C 優化頁將優化級別設為0,避免C 編譯器認為這種程序無意義而自動優化使我們不能 得到想要的結果。編譯、連接后,運行,查看變量, 如圖3-1 所示。 可見,b 和d 在加1 之后分別變成了0 和-32768, 這是為什么呢?這與我們的數學計算顯然不同。其實 只要我們從數字在內存中的二進制存放形式分析,就 不難理解。 首先看變量a,該變量的值是255,類型是無符 號字符型,因此,該變量在內存中以8 位(一個字節) 來存放,將255 轉化為二進制即1111,1111,如果將 該值加1,結果是1,0000,0000,由于該變量只能存放 8 位,所以最高位的1 丟失,于是該數字就變也了 0000,0000,自然就是十進制的0 了。其實這不難理 解,錄音機上有磁帶計數器,共有3 位,當轉到999 后,再轉一圈,本應是1000,但實際 看到的是000,除非你借助于其他方法,否則你是無法判斷其是轉了1000 轉還是根本沒有 動。 在理解了無符號的字符型數據的溢出后,整型變量的溢出也不難理解。32767 在內存中 存放的形式是0111,1111,1111,1111,當其加1 后就變成了1000,0000,0000,0000,而這個二進 制數正是-32768 在內存中的存放形式,所以c 加1 后就變成了-32768。 可見,在出現這樣的問題時C 編譯系統不會給出提示(其他語言中BASIC 等會報告出 錯),這有利于編出靈活的程序來,但也會引起一些副作用,這就要求C 程序員對硬件知識 有較多的了解,對于數在內存中的存放等基本知識必須清楚。 圖3-1 數的溢出 單片機的C 語言輕松入門 28 第4 章 循環程序設計 前面的課程中學習了分支程序的設計,下面學習程序設計中另一種常用的程序結構―― 循環結構。 4.1 循環程序簡介 在一個實用的程序中,循環結構是必不可少的。循環是反復執行某一部分程序行的操作。 有兩類循環結構: (1)當型循環,即當給定的條件成立時,執行循環體部分,執行完畢回來再次判斷條 件,如果條件成立繼續循環,否則退出循環。 (2)直到型循環,即先執行循環體,然后判斷給定的條件,只要條件成立就繼續循環, 直到判斷出給定的條件不成立時退出循環。 下面我們就通過一些例子來看C 語言提供的循環語句,及如何利用這些循環語句寫循 環程序。 例4-1 使P1 口所接LED 以流水燈狀態顯示 #include "reg51.h" #include"intrins.h" //該文件包含有_crol_(…)函數的說明 void mDelay(unsigned int DelayTime) { unsigned int j=0; for(;DelayTime>0;DelayTime--) { for(j=0;j<125;j++) {;} } } void main() { unsigned char OutData=0xfe; while(1) { P1=OutData; OutData=_crol_(OutData,1); //循環左移 mDelay(1000); /*延時1000 毫秒*/ } } 程序分析:輸入源程序,并命名為exam31.c,建立并設置工程,這個例子使用實驗仿 真板演示的過程請自行完成。如果在演示時,發現燈“流動”的速度太快,幾乎不能看清, 那么可以將mDelay(1000)中的1000 改大一些,如2000、3000 或更大。軟件仿真無法實現 硬件實驗一樣的速度,這是軟件仿真的固有弱點,下面介紹如何用具有仿真功能的實驗板來 實現這個例子。 將隨機帶的一根串口電纜一端連接到PC 機的某一個串口,另一端連到本實驗板上,設 置工程,選中Debug 頁,點擊右側的“Use Keil Monitor-51 Drive”,然后選中“Load Application at Start”和“Go Till main”,如圖4-1 所示。選擇完成后,點擊“Setting”按鈕,選擇你所 用的PC 上的串口(COM1 或COM2),波特率(通?梢允褂38400),其他設置一般不需 單片機的C 語言輕松入門 29 要更改,如圖4-2 所示。點擊“OK”回到Debug 頁面后即可完成設置。 編譯、連接正確后,點擊菜單Debug?Start /Stop Debug Session,可以看到在窗口右下 角的命令窗口提示正確連接到了Monitor-51,如圖4-3 所示。此時,即可使用Keil 提供的單 步、過程單步、執行到當前行、設置斷點等調試方法進行程序的調試。如果全速運行程序, 可看到流水燈的實驗效果。 程序分析:這段程序中在兩處用到了循環語句,首先是主程序中使用了: while(1) {……} 這樣的循環語句寫法,在{}中的所有程序將會不斷地循環執行,直到斷電為止;其次是 延時程序,使用了for 循環語句的形式。下面我們就對循環語句作一個介紹。 4.2 while 語句 While 語句用到實現“當型”循環結構,其一般形式如下: while(表達式) 語句 當表達式為非0 值(真)時,執行while 語句中的內嵌語句。其特點是:先判斷表達式, 圖4-2 選擇串口、波特率及其他選項 圖4-1 設置Debug 頁 圖4-3 正確進入程序調試 單片機的C 語言輕松入門 30 后執行語句。 在上述例子中,表達式使用了一個常數“1”,這是一個非零值,即“真”,條件總是滿 足,語句總是會被執行,構成了無限循環。下面再舉一例說明: 例4-2:當K1 鍵被按下時,流水燈工作,否則燈全部熄滅。 #include "reg51.h" #include"intrins.h" //該文件包含有_crol_(…)函數的說明 void mDelay(unsigned int DelayTime) { unsigned int j=0; for(;DelayTime>0;DelayTime--) { for(j=0;j<125;j++) {;} } } void main() { unsigned char OutData=0xfe; while(1) { P3|=0x3c; while((P3|0xfb)!=0xff) { P1=OutData; OutData=_crol_(OutData,1); //循環左移 mDelay(1000); /*延時1000 毫秒*/} P1=0xff; } } 程序分析:這個程序中的第二個while 語句中的表達式用來判斷K1 鍵是否被按下,如 被按下,則執行循環體內的程序,否則執行P1=0xff;程序行。雖然整個程序是在一個無限循 環過程中,但是由于外界條件的變化使得程序執行的過程發生了變化。 4.3 do-while 語句 do-while 語句用來實現“直到型”循環,特點是先執行循環體,然后判斷循環條件是否 成立。其一般形式如下: do 循環體語句 while(表達式) 對同一個問題,既可以用while 語句處理,也可以用do-while 語句處理。但是這兩個語 句是有區別的,下面我們用do-while 語句改寫例2。 例4-3:用do-while 語句實現如下功能:K1 按下,流水燈工作,K2 松開,燈全熄滅。 #include "reg51.h" #include"intrins.h" //該文件包含有_crol_(…)函數的說明 void mDelay(unsigned int DelayTime) { unsigned int j=0; for(;DelayTime>0;DelayTime--) { for(j=0;j<125;j++) 單片機的C 語言輕松入門 31 {;} } } void main() { unsigned char OutData=0xfe; while(1) { P3|=0x3c; do { P1=OutData; OutData=_crol_(OutData,1); //循環左移 mDelay(1000); /*延時1000 毫秒*/ } while((P3|0xfb)!=0xff) P1=0xff; } } 程序分析:這個程序除主程序中將while 用do-while 替代外,沒有其他的變化,初步設 想,如果while()括號中的表達式為“真”即K1 鍵被按下,應該執行程序體,否則不執 行,效果與例4-2 相同。但是事實上,實際做這個練習就會發現,不論K1 是否被按下,流 水燈都在工作。為何會有這么樣的結果呢? 單步運行程序可以發現,如果K1 鍵被按下,的確是在執行循環體內的程序,與設想相 同。而當K1 沒有被按下時,按設想,循環體內的程序不應該被執行,但事實上,do 后面的 語句至少要被執行一次才去判斷條件是否成立,所以程序依然會去執行do 后的循環體部分, 只是在判斷條件不成立(K1 沒有被按下)后,轉去執行P1=0xff;然后又繼續循環,而下一 次循環中又會先執行一次循環體部分,因此,K1 是否被按下的區別僅在于“P1=0xff;”這一 程序行是否會被執行到。 4.4 for 語句 C語言中的for 語句使用最為靈活,不僅可以用于循環次數已經確定的情況,而且可以 用于循環次數不確定而只給出循環結束條件的情況。 for 語句的一般形式為: for(表達式1;表達式2;表達式3) 語句 它的執行過程是: (1) 先求解表達式1 (2) 求解表達式2,其值為真,則執行for 語句中指定的內嵌語句(循環體),然后執行 第(3)步,如果為假,則結束循環。 (3) 求解表達式3 (4) 轉回上面的第(2)步繼續執行。 for 語句典型的應用是這樣一種形式: for(循環變量初值;循環條件;循環變量增值) 語句 例如上述例子中的延時程序有這樣的程序行:“for(j=0;j<125;j++){;} ”,執行這行程序 時,首先執行j=0,然后判斷j 是否小于125,如果小于125 則去執行循環體(這里循環體 沒有做任何工作),然后執行j++,執行完后再去判斷j 是否小于125……如此不斷循環,直 單片機的C 語言輕松入門 32 到條件不滿足(j>=125)為止。 如果用while 語句來改寫,應該這么寫 j=0; while(j<125) { j++; } 可見,用for 語句更簡單、方便。 如果變量初值在for 語句前面賦值,則for 語句中的表達式1 應省略,但其后的分號不 能省略。上述程序中有:“for(;DelayTime>0;DelayTime--){…}”的寫法,省略掉了表達式1, 因為這里的變量DelayTime 是由參數傳入的一個值,不能在這個式子里賦初值。 表達式2 也可以省略,但是同樣不能省略其后的分號,如果省略該式,將不判斷循環條 件,循環無終止地進行下去,也就是認為表達式始終為真。 表達式3 也可以省略,但此時編程者應該另外設法保證循環能正常結束。 表達式1、2 和3 都可以省略,即形成如for(;;)的形式,它的作用相當于是while(1),即 構一個無限循環的過程。 循環可以嵌套,如上述延時程序中就是兩個for 語句嵌套使用構成二重循環,C 語言中 的三種循環語句可以相互嵌套。 4.5 break 語句 在一個循環程序中,可以通過循環語句中的表達式來控制循環程序是否結束,除此之外, 還可以通過break 語句強行退出循環結構。 例4:開機后,全部LED 不亮,按下K1 則從LED1 開始依次點亮,至LED8 后停止并 全部熄滅,等待再次按下K1 鍵,重復上述過程。如果中間K2 鍵被按下,LED 立即全部熄 滅,返回起始狀態。 #include "reg51.h" #include"intrins.h" //該文件包含有_crol_(?)函數的說明 void mDelay(unsigned int DelayTime) { unsigned int j=0; for(;DelayTime>0;DelayTime--) { for(j=0;j<125;j++) {;} } } void main() { unsigned char OutData=0xfe; unsigned char i; while(1) { P3|=0x3c; if((P3|0xfb)!=0xff) //K1 鍵被按下 { OutData=0xfe; for(i=0;i<8;i++) { mDelay(1000); /*延時1000 毫秒*/ tmp=0xfe; 單片機的C 語言輕松入門 33 if((P3|0xf7)!=0xff) //K2 鍵被按下 break; OutData=_crol_(OutData,i); P1&=OutData; } } P1=0xff; } } 請讀者輸入程序、建立工程,使用實驗仿真板或者實驗板來驗證這一功能,注意,K2 按下的時間必須足夠長,因為這里每1s 才會檢測一次K2 是否被按下。 程序分析:開機后,當檢測到K1 鍵被按下,執行一個: for(i=0;i<8;i++) {…} 的循環,即循環8 次后即停止,而在這段循環體中,又用到了如下的程序行:“ if((P3|0xf7)!=0xff) break;”即判斷K2 是否按下,如果K2 被按下,則立即結束本次循 環。 4.6 continue 語句 該語句的用途是結束本次循環,即跳過循環體中下面的語句,接著進行下一次是否執行 循環的判定。 Continue 語句和break 語名的區別是:continue 語句只結束本次循環,而不是終止整個 循環的執行;而break 語句則是結束整個循環過程,不會再去判斷循環條件是否滿足。 例5:將上述例4 中的break 語句改為continue 語句,會有什么結果? 程序分析:開機后,檢測到K1 鍵被按下,各燈開始依次點亮,如果K2 鍵沒有被按下, 將循環8 次,直到所有燈點亮,又加到初始狀態,即所有燈滅,等待K1 按鍵。如果K2 鍵 被按下,不是立即退出循環,而只是結束本次循環,即不執行continue 語句下面的 “OutData=_crol_(OutData,i); P1&=OutData;”語句,但要繼續轉去判斷循環條件是否滿足, 因此,不論K2 鍵是否被按下,循環總是要經過8 次才會終止,差別在于是否執行了上述兩 行程序。如果上述程序行有一次未被執行,意味著有一個LED 未被點亮,因此,如果按下 K2 過一段時間(1、2s)松開,中間將會有一些LED 不亮,直到最后一個LED 被點亮,又 回到全部熄滅的狀態,等待K1 被按下。 練習:基本要求同例5,但不是在按下K2 后有一些燈不亮,而是固定每點亮2 個LED 后,第三個LED 不亮,請編程實現。 單片機的C 語言輕松入門 34 第5 章 單片機內部資源編程 通過前面課程的學習,我們已了解了“通用”的C 語言特性,本課將介紹針對80C51 單片機特性的C 語言編程。 5.1 定時器編程 定時器編程主要是對定時器進行初始化以設置定時器工作模式,確定計數初值等,使用 C 語言編程和使用匯編編程方法非常類似,以下通過一個例子來分析。 例5-1:用定時器實現P1 所接LED 每60ms 亮或滅一次,設系統晶振為12M。 參考圖5-1 輸入源程序,建立并設置工程,本例使用實驗仿真板難以得到理想的結果, 應使用DSB-1A 型實驗板進行練習。 分析:要使用單片機的定時器,首先要設置定時器的工作方式,然后給定時器賦初值, 即進行定時器的初始化。這里選擇定時器0,工作于定時方式,工作方式1,即16 位定時/ 計數的工作方式,不使用門控位。由此可以確定定時器的工作方式字TMOD應為00000001B, 即0x01。定時初值應為65536-60000=5536,由于不能直接給T0 賦值,必須將5536 化為 十六進制即為0x15a0,這樣就可以寫出初始化程序即: TMOD=0x01; TH0=0x15; TL0=0xa0; 初始化定時器后,要定時器工作,必須將TR0 置1,程序中用“TR0=1;”來實現。 可以使用中斷也可以使用查詢的方式來使用定時器,本例使用查詢方式,中斷方式稍后 介紹。 當定時時間到后,TF0 被置為1,因此,只需要查詢TF0 是否等于1 即可得知定時時間 是否到達,程序中用“if(TF0){…}”來判斷,如果TF0=0,則條件不滿足,大括號中的程 序行不會被執行到,當定時時間到TF1=1 后,條件滿足,即執行大括號中的程序行,首先 將TF0 清零,然后重置定時初值,最后是執行規定動作――取反P1.0 的狀態。 圖5-1 用定時器實現LED 閃爍 單片機的C 語言輕松入門 35 5.2 中斷編程 C51 編譯器支持在C 源程序中直接開發中斷過程,使用該擴展屬性的函數定義語法如 下: 返回值 函數名 interrupt n 其中n 對應中斷源的編號,其值從0 開始,以80C51 單片機為例,編號從0~4,分別對 應外中斷0、定時器0 中斷、外中斷1、定時器1 中斷和串行口中斷。 5.2.1 中斷應用實例 下面我們同樣通過一個例子來說明中斷編程的應用。 例5-2 用中斷法實現定時器控制P1.0 所接LED 以60ms 閃爍。 參考圖2 輸入源程序,設置工程,同樣,本例用實驗仿真板難以看到真實的效果,應使 用DSB-1A 型實驗板來完成這一實驗。 分析:本例與例1 的要求相同,唯一的區別是必須用中斷方式來實現。這里仍選用定時 器T0,工作于方式1,無門控,因此,定時器的初始化操作與上例相同。要開啟中斷,必 須將EA(總中斷允許)和ET0(定時器T0 中斷允許)置1,程序中用“EA=1;”和“ET0 =1;”來實現。在做完這些工作以后,就用for(;;){;}讓主程序進入無限循環中,所有工作均 由中斷程序實現。 由于定時器0 的中斷編號為1,所以中斷程序中這樣寫: void timer0() interrupt 1 {…} 可見,用C51 語言寫中斷程序是非常簡單的,只要簡單地在函數名后加上interrupt 關 鍵字和中斷編號就成了。 圖2 用中斷法使用定時器 單片機的C 語言輕松入門 36 5.2.2 寄存器組切換 為進行中斷的現場保護,80C51 單片機除采用堆棧技術外,還獨特地采用寄存器組的方 式,在80C51 中一共有4 組名稱均為R0~R7 的工作寄存器,中斷產生時,可以通過簡單地 設置RS0、RS1 來切換工作寄存器組,這使得保護工作非常簡單和快速。使用匯編語言時, 內存的使用均由編程者設定,編程時通過設置RS0、RS1 來選擇切換工作寄存器組,但使用 C 語言編程時,內存是由編譯器分配的,因此,不能簡單地通過設置RS0、RS1 來切換工作 寄存器組,否則會造成內存使用的沖突。 在C51 中,寄存器組選擇取決于特定的編譯器指令,即使用using n 指定,其中n 的 值是0~3,對應使用四組工作寄存器。 例如上述例子中可以這么樣來寫: void timer0() interrupt 1 using 2 {…} 即表示在該中斷程序中使用第2 組工作寄存器。 5.3 串行口編程 80C51 系列單片機片上有UART 用于串行通信,80C51 中有兩個SBUF,一個用作發送 緩沖器,一個用作接收緩沖器,在完成串口的初始化后,只要將數據送入發送SBUF,即可 按設定好的波特率將數據發送出去,而在接收到數據后,可以從接收BUF 中讀到接收到的 數據。下面我們通過一個例子來了解串行口編程的方法。 例3 單片機P1 口接8 只發光二極管,P3.2~P3.5 接有K1~K4 共四個按鍵,使用串行 口編程,1)由PC 機控制單片機的P1 口,將PC 機送出的數以二進制形式顯示在發光二極 管上;2)按下K1 向主機發送數字0x55,按下K2 向主機發送數字0xAA,使顯示轉下一行。 #define uchar unsigned char #include "string.h" #include "reg51.h" void SendData(uchar Dat) { uchar i=0; SBUF=Dat; while(1){ if(TI) { TI=0; break;}} } void mDelay(unsigned int DelayTime) { unsigned char j=0; for(;DelayTime>0;DelayTime--) { for(j=0;j<125;j++) {;} } } uchar Key() { uchar KValue; 單片機的C 語言輕松入門 37 P3|=0x3e; //中間4 位置高電平 if((KValue=P3|0xe3)!=0xff) { mDelay(10); if((KValue=P3|0xe3)!=0xff) { for(;;) if((P3|0xe3)==0xff) return(KValue); } } return(0); } void main() { uchar KeyValue; P1=0xff; //關閉P1 口接的所有燈 TMOD=0x20; //確定定時器工作模式 TH1=0xFD; TL0=0xFD; //定時初值 PCON&=0x80; //SMOD=1 TR1=1; //開啟定時器1 SCON=0x40; //串口工作方式1 REN=1; //允許接收 for(;;) { if(KeyValue=Key()) { if((KeyValue|0xfb)!=0xff) //K1 按下 SendData(0x55); if((KeyValue|0xf7)!=0xff) SendData(0xaa); } if(RI) { P1=SBUF; RI=0; } } } 單片機的C 語言輕松入門 38 實現過程:輸入程序,命名為exam53.c,建立名為exam53 的工程,將文件加入,設置 工程,使用實驗仿真板進行調試。正確編譯連接后進入調試, 打開實驗仿真板,然后再點擊view?serial #1 打開串行窗口,在 窗口空白處點右鍵,在彈出式菜單中選擇“Hex Mode”如圖3 所示。 單擊實驗仿真板的K1 鍵和K2 鍵,即可看到在串行窗口中 分別出現55 和AA;單擊串行窗口的空白處,使其變為活動窗 口,即可接收鍵盤輸入,按下鍵盤上不同的字符鍵,可見實驗仿真板上的LED 產生相應的 變化。圖5-4 是按下K1 一次、K2 連續兩次、再按一次K1 后看到的串行窗口現象,而實驗 仿真板則是在鍵盤上按下字符1 之后看到的現象,燈亮為“0”,燈滅為“1”,因此燈的組合 為00110001,即0x31,這正是字符1 的ASCII 碼值。 程序分析:本程序使用T1 作為波特率發生器,工作于方式2(8 位自動重裝入方式), 波特率為19200,串行口工作于方式1,根據以上條件不難算出T1 的定時初值為0xfd,TMOD 應初始化為0x20,SMOD 應初始化為0x30,而PCON 中的SMOD 位必須置1,主程序main 的開頭對這些初值進行了設置。設置好初值后,使用“TR1=1;”開啟定時器1,使用“REN =1;”允許接收數據,然后即進入無限循環中開始正常工作。在這個無限循環中首先調用鍵 盤程序,檢測是否有鍵按下,如果有鍵按下,那么檢測是否 K1 被按下,如果K1 被按下,則調用發送數據程序,將數據 0x55 送出,如果K2 被按下,則將數據0xAA 送出。然后檢測 RI 是否等于1,如果RI 等于1,說明接收到字符,清RI,準 備下一次接收,并將接收到的數據送往P1 口顯示。這樣,一 次循環結束,繼續開始下一次循環。 發送函數SendData 中有只有一個參數Dat,即待發送的 字符,函數將待發送的字符送入SBUF 后,使用一個無限循 環等待發送的結束,在循環中通過檢測TI 來判斷數據是否發 送完畢,發送完畢使用break 語句退出循環。 如果使用DSB-1A 型實驗板做實驗,需要用到一個PC 端的串口調試程序,并正確設置該調試程序的有關參數,這 里以“串口調試助手”軟件為例,其參數設置如圖5-5 所示。 圖4 使用實驗仿真板演示串行口操作 圖5-3 選擇顯示模式 圖5-5 設置串口參數 單片機的C 語言輕松入門 39 由于該板占用了串口,因此做串口通訊類實驗只能用下載全速運行的方法,具體步驟如下: 1.設置工程,在Debug 頁將波特率設置為19200 上; 2.進入調試后全速運行程序,然后按Debug->Stop Runing 停止運行,實際上這不會中 斷硬件電路的工作; 3.打開PC 端串口調試軟件,正確設置串口參數,即可正常工作。 單片機的C 語言輕松入門 40 第六章 C 語言編程綜合練習 前面課程中我們學習了單片機C 語言的基本知識,了解了單片機內部資源的C 語言編 程方法,這一節通過若干例子進一步學習C 語言程序的有關知識點。 1. 計數器 要求:編寫一個計數器程序,將T0 作為計數器來使用,對外部信號計數,將所計數字 顯示在數碼管上。 該部分的硬件電路如圖6-1 所示,U1 的P0 口和P2 口的部份引腳構成了6 位LED 數碼 管驅動電路,數碼管采用共陽型,使用PNP 型三極管作為片選端的驅動,所有三極管的發 射極連在一起,接到正電源端,它們的基極則分別連到P2.0?P2.5,當P2.0?P2.5 中某引腳 輸是低電平時,三極管導通,給相應的數碼管供電,該位數碼管點亮哪些筆段,則取決于筆 段引腳是高或低電平。圖中看出,所有6 位數碼管的筆段連在一起,通過限流電阻后接到 P0 口,因此,哪些筆段亮就取決于P0 口的8 根線的狀態。 編寫程序時,首先根據硬件連線寫出LED 數碼管的字形碼、位驅動碼,然后編寫程序 如下: #include "reg51.h" #define uchar unsigned char #define uint unsigned int uchar code BitTab[]={0x7F,0xBF,0xDF,0xEF,0xF7,0xFB}; //位驅動碼 uchar code DispTab[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x8 6,0x8E,0xFF}; //字形碼 uchar DispBuf[6]; //顯示緩沖區 圖6-1 計數器實驗硬件電路圖 單片機的C 語言輕松入門 41 void Timer1() interrupt 3 { uchar tmp; uchar Count; //計數器,顯示程序通過它得知現正顯示哪個數碼管 TH1=(65536-3000)/256; TL1=(65536-3000)%256; //重置初值 tmp=BitTab[Count]; //取位值 P2=P2|0xfc; //P2 與11111100B 相或 P2=P2&tmp; //P2 與取出的位值相與 tmp=DispBuf[Count];//取出待顯示的數 tmp=DispTab[tmp]; //取字形碼 P0=tmp; Count++; if(Count==6) Count=0; } void main() { uint tmp; P1=0xff; P0=0xff; TMOD=0x15; //定時器0 工作于計數方式1,定時器1 工作于定時方式1 TH1=(65536-3000)/256; TL1=(65536-3000)%256; //定時時間為3000 個周期 TR0=1; //計數器0 開始運行 TR1=1; EA=1; ET1=1; for(;;) { tmp=TL0|(TH0<<8); //取T0 中的數值 DispBuf[5]=tmp%10; DispBuf[4]=tmp%10; tmp/=10; tmp/=10; DispBuf[3]=tmp%10; tmp/=10; DispBuf[2]=tmp%10; DispBuf[1]=tmp/10; DispBuf[0]=0; } } 單片機的C 語言輕松入門 42 這個程序中用到了一個新的知識點,即數組,首先作一個介紹。 數組是C51 的一種構造數據類型,數組必須由具有相同數據類型的元素構成,這些數 據的類型就是數組的基本類型,如:數組中的所有元素都是整型,則該數組稱為整型數組, 如所有元素都是字符型,則該數組稱為字符型數組。 數組必須要先定義,后使用,這里僅介紹一維數組的定義,其方式為: 類型說明符 數組名[整型表達式] 定義好數組后,可以通過:數組名[整型表達式]來使用數組元素。 在定義數組時,可以對數組進行初始化,即給其賦予初值,這可用以下的一些方法實現: 1. 在定義數組時對數組的全部元素賦予初值: 例:int a[5]={1,2,3,4,5}; 2. 只對數組的部分元素初始化; 例:int a[5]={1,2}; 上面定義的a 數組共有5 個元素,但只對前兩個賦初值,因此a[0]和a[1]的值是1、2, 而后面3 個元素的值全是0。 3. 在定義數組時對數組元素的全部元素不賦初值,則數組元素值均被初始化為0 4. 可以在定義時不指明數組元素的個數,而根據賦值部分由編譯器自動確定 例:uchar BitTab[]={0x7F,0xBF,0xDF,0xEF,0xF7,0xFB}; 則相當于定義了一個BitTab[6]這樣一個數組。 5.可以為數組指定存儲空間,這個例子中,未指定空間時,將數組定義在內部RAM 中,可以用code 關鍵字將數組元素定義在ROM 空間中。 uchar code BitTab[]={0x7F,0xBF,0xDF,0xEF,0xF7,0xFB}; 用這兩種定義分別編譯,可以看出使用了code 關鍵字后系統占用的RAM 數減少了, 這種方式用于編程中不需要改變內容的場合,如顯示數碼管的字形碼等是很合適的。 6.C 語言并不對越界使用數組進行檢測,例如上例中數組的長度是6,其元素應該是 從BitTab[0]~BitTab[5],但是如果你在程序中寫上BitTab[6],編譯器并不會認為這有語法錯 誤,也不會給出警告(其他語言如BASCI 等則有嚴格的規定,這種情況將視為語法錯誤), 因此,編程者必須自己小心確認這是否是你需要的結果。 程序分析:程序中將定時器T1用作數碼管顯示,通過interrupt 3 關鍵字定義函數Timer1() 為定時器1 中斷服務程序,在這個中斷服務程序中,使用 TH1=(65536-3000)/256; TL1=(65536-3000)%256; 來重置定時器初值,這其中3000 即為定時周期,這樣的寫法可以直觀地看到定時周期數, 是常用的一種寫法。其余程序段分別完成取位碼以選擇數碼管、從顯示緩沖區獲得待顯示數 值、根據該數值取段碼以點亮相應筆段等任務。其中使用了一個計數器,該計數器的值從 0~5 對應第1 到第6 位的數碼管。 主程序的第一部分是做一些初始化的操作,設置定時器工作模式、開啟定時器T1、開 啟計數器T0、開啟T1 中斷及總中斷,隨后進入主循環,主循環首先用unsigned int 型變量 tmp 取出T0 中的數值,這里使用了“tmp=TL0|(TH0<<8);”這樣的形式,這相當于 tmp=TH0*256+TL0,但比之于后一種形式,該方式可以得到更高的效,其后就是將tmp 值 不斷地除10 取整,這樣將int 型數據的各位分離并送入相應的顯示緩沖區。 二、液晶顯示 字符型液晶顯示器用于顯示數字、字母、圖形符號。這類顯示器均把LCD 控制器、點 單片機的C 語言輕松入門 43 陣驅動器、字符存貯器等做在一塊板上,再與液晶屏一起組成一個顯示模塊,因此,這類顯 示器安裝與使用都較簡單。 圖6-2 是DSB-1A 實驗板上字符型液晶的接口電路。要求編寫程序從該顯示器的第二行 第1 列開始顯示“Hello World!”。 由于市面上常見的字符型液晶驅動器均由HD44780 或其兼容芯片構成,因此,這類液 晶屏的驅動程序具有一定的通用性,這里給出用C 語言寫的驅動程序。在設置字符的起始 行、列后,直接調用驅動程序提供的WriteString 函數即可將字符串顯示在指定的位置,使 用非常簡單。在熟悉了程序后,略作改動,可用于2002、2004 等型號的液晶屏。 程序如下: /************************************************** * 平凡單片機工作室 * http://www.mcustudio.com * Copyright 2003 pingfan's McuStudio * All rights Reserved *作者:周堅 *yj.c *連線圖: * DB0---DPROT.0 DB4---DPROT.4 RS-------------P2.5 * DB1---DPROT.1 DB5---DPROT.5 RW-------------P2.6 * DB2---DPROT.2 DB6---DPROT.6 E--------------P2.7 * DB3---DPROT.3 DB7---DPROT.7 VLCD 接10K 可調電阻到GND* *80C51 的晶振頻率為12MHz *液晶顯示程序 ***************************************************/ #include "reg51.h" #include<absacc.h> #include <intrins.h> #define DPORT P0 #define uchar unsigned char sbit RS = P2^5; sbit RW = P2^6; sbit E = P2^7; 圖6-2 字符型液晶與單片機的接線圖 DATA PORT P0.0 DB0 P0.7 DB7 RS R/W E P2.5 P2.6 P2.7 +5V GND VL BLK BLA 80C51 單片機的C 語言輕松入門 44 uchar Xpos; //列方向地址指針 uchar Ypos; //行方向地址指針 #define NoDisp 0 #define NoCur 1 #define CurNoFlash 2 #define CurFlash 3 /*延時程序 由Delay 參數確定延遲時間 */ void LcdWcn(uchar); void LcdWc(uchar); void WriteChar(uchar); void LcdPos(); void LcdWd(uchar); void LcdWdn(uchar); void mDelay(unsigned int Delay) { unsigned int i; for(;Delay>0;Delay--) { for(i=0;i<124;i++) {;} } } /*光標設置命令 Cur 為設定光標參數 */ void SetCur(uchar Cur) { switch(Cur) { case 0x0: { LcdWc(0x08); //關顯示 break; } case 0x1: { LcdWc(0x0c); //開顯示但無光標 break; } case 0x2: { LcdWc(0x0e); //開顯示有光標但不閃爍 break; } case 0x3: 單片機的C 語言輕松入門 45 { LcdWc(0x0f); //開顯示有光標且閃爍 break; } default: break; } } /*清屏命令 */ void ClrLcd() { LcdWc(0x01); } /*在指定的行與列顯示 */ void WriteChar(uchar c) { LcdPos(); LcdWd(c); } /*正常讀寫操作之前檢測LCD 控制器 */ void WaitIdle() { uchar tmp; DPORT=0xff; RS=0; RW=1; E=1; _nop_(); for(;;) { tmp=DPORT; tmp&=0x80; if(tmp==0) break; } E=0; } /*不檢測忙的寫字符子程序 */ void LcdWdn(uchar c) { RS=1; RW=0; DPORT=c; //寫入待寫字符 E=1; _nop_(); 單片機的C 語言輕松入門 46 E=0; } /*帶忙檢測的寫字符子程序 */ void LcdWd(uchar c) { WaitIdle(); LcdWdn(c); } /*檢測忙信號的送控制字子程序*/ void LcdWcn(uchar c) { RS=0; RW=0; DPORT=c; E=1; _nop_(); E=0; } /*檢測忙信號的送控制字子程序*/ void LcdWc(uchar c) { WaitIdle(); LcdWcn(c); } void LcdPos() { uchar tmp; Xpos&=0x0f; //16xx 型液晶的范圍是0~15 Ypos&=0x01; //Y 的范圍是0~1 tmp=Xpos; if(Ypos==1) { tmp+=0x40; } tmp|=0x80; LcdWc(tmp); } /*LCD 的復位程序 */ void RstLcd() { mDelay(15); //延時15ms LcdWcn(0x38); mDelay(5); LcdWcn(0x38); mDelay(5); LcdWcn(0x38); 單片機的C 語言輕松入門 47 LcdWc(0x38); LcdWc(0x08); LcdWc(0x01); Lc; DPORT=c; E=1; _nop_(); E=0; } /*檢測忙信號的送控制字子程序*/ void LcdWc(uchar c) { WaitIdle(); LcdWcn(c); } void LcdPos() { uchar tmp; Xpos&=0x0f; //16xx 型液晶的范圍是0~15 Ypos&=0x01; //Y 的范圍是0~1 tmp=Xpos; if(Ypos==1) { tmp+=0x40; } tmp|=0x80; LcdWc(tmp); } /*LCD 的復位程序 */ void RstLcd() { mDelay(15); //延時15ms LcdWcn(0x38); mDelay(5); LcdWcn(0x38); mDelay(5); LcdWcn(0x38); 單片機的C 語言輕松入門 47 LcdWc(0x38); LcdWc(0x08); LcdWc(0x01); LcdWc(0x06); LcdWc(0x0c); } void WriteString(char s[]) { uchar pS=0; for(;;) { WriteChar(s[pS]); pS++; if(s[pS]==0) break; if(++Xpos>=15) //每行最多顯示16 個字符 break; } } void main() { uchar s1[]="Hellow World!"; RstLcd(); //復位LCD ClrLcd(); SetCur(CurFlash); //光標顯示且閃爍 Xpos=2; Ypos=1; WriteString(s1); for(;;) {;} } 程序分析:本程序中大量使用了函數,在此對函數的功能作一個簡介。 C 語言程序是由一個個函數構成的,從函數定義的形式上劃分,函數有三種形式:無參 數函數、有參數函和空函數。 無參數函數的定義形式為: 返回值類型識別符 函數名(){函數體語句} 如本例中的void WaitIdle(){ …… }就是一個無參數函數 有參數函數的定義形式為: 返回值類型識別符 函數名(形式參數列表){函數體語句} 如本例中的void LcdWdn(uchar c){ …… }就是一個有參數的函數 函數可以返回一個值,也可以什么值也返回,如果函數要返回一個值,在定義這個函數 時要定義好這個值的數據類型,這里所說的數據類型就是指前面課程中介紹到的int、char、 float 等類型,如果在定義函數時沒有定義返回值的類型,系統默認為返回一個int 型的值。 如果明確地知道一個函數將沒有返回值,可以將其定義為void 型,這樣,如果在調用函數 單片機的C 語言輕松入門 48 時錯誤地使用了“變量名=函數名”的方式來調用函數,編譯器就能發現這一錯誤并指出。 本例中就大量地應用到了void 型函數。 C 語言采用函數之間的參數傳遞方式,這使得一個函數能對不同的變量進行功能相同的 處理,使函數具有了通用性。定義函數時,寫在函數名括號中的稱之為形式參數,而在實際 調用函數時寫在函數括號中的稱之為實際參數。本例中: void SetCur(uchar Cur) { …} 函數中Cur 就是一個形式參數,而在主函數中調用時寫的: SetCur(CurFlash); 其中CurFlash 就是一個用符號常量表示的實際參數,在執行該函數時該值被傳遞到函 數內部并執行。 每一個函數所調用的函數必須已被定義,否則就會出現語法錯誤,因此程序中一般要求 在程序的開頭對程序中用到的函數進行統一的說明,然后再分別定義有關函數,本例中有: void WriteChar(uchar); … void LcdWdn(uchar); 就是首先在程序的前方寫一個有關函數的說明,而真正的函數定義則在程序放在后部。 但細心的讀者可能發現有一些函數并未寫其說明,而是直接在程序中定義了,如mDelay 函 數,這是為何呢?這是因為這些函數出現在程序的前面,在還沒有任何函數調用它們之前它 們就被定義了,因此就不需要再單獨寫一個函數說明。讀者可將mDelay 函數的定義移到程 序的后面位置,再次編譯就會出錯。當然,好的編程習慣是不論函數在何處被定義,總是在 寫前面寫一個函數說明。 有關單片機的C 語言編程到此就告一個段落,雖然C 語言很多特性尚未介紹,但通過 上面有關內容的學習,我們已經可以使用C 語言進行一些實際的工程開發工作,大家可以 在工作中繼續學習有關C 語言的知識。
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484210
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484190
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484041
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13483797
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13480591
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13480036 關于幸福人生的經典誤解分類:默認欄目
pS++; if(s[pS]==0) break; if(++Xpos>=15) //每行最多顯示16 個字符 break; } } void main() { uchar s1[]="Hellow World!"; RstLcd(); //復位LCD ClrLcd(); SetCur(CurFlash); //光標顯示且閃爍 Xpos=2; Ypos=1; WriteString(s1); for(;;) {;} } 程序分析:本程序中大量使用了函數,在此對函數的功能作一個簡介。 C 語言程序是由一個個函數構成的,從函數定義的形式上劃分,函數有三種形式:無參 數函數、有參數函和空函數。 無參數函數的定義形式為: 返回值類型識別符 函數名(){函數體語句} 如本例中的void WaitIdle(){ …… }就是一個無參數函數 有參數函數的定義形式為: 返回值類型識別符 函數名(形式參數列表){函數體語句} 如本例中的void LcdWdn(uchar c){ …… }就是一個有參數的函數 函數可以返回一個值,也可以什么值也返回,如果函數要返回一個值,在定義這個函數 時要定義好這個值的數據類型,這里所說的數據類型就是指前面課程中介紹到的int、char、 float 等類型,如果在定義函數時沒有定義返回值的類型,系統默認為返回一個int 型的值。 如果明確地知道一個函數將沒有返回值,可以將其定義為void 型,這樣,如果在調用函數 單片機的C 語言輕松入門 48 時錯誤地使用了“變量名=函數名”的方式來調用函數,編譯器就能發現這一錯誤并指出。 本例中就大量地應用到了void 型函數。 C 語言采用函數之間的參數傳遞方式,這使得一個函數能對不同的變量進行功能相同的 處理,使函數具有了通用性。定義函數時,寫在函數名括號中的稱之為形式參數,而在實際 調用函數時寫在函數括號中的稱之為實際參數。本例中: void SetCur(uchar Cur) { …} 函數中Cur 就是一個形式參數,而在主函數中調用時寫的: SetCur(CurFlash); 其中CurFlash 就是一個用符號常量表示的實際參數,在執行該函數時該值被傳遞到函 數內部并執行。 每一個函數所調用的函數必須已被定義,否則就會出現語法錯誤,因此程序中一般要求 在程序的開頭對程序中用到的函數進行統一的說明,然后再分別定義有關函數,本例中有: void WriteChar(uchar); … void LcdWdn(uchar); 就是首先在程序的前方寫一個有關函數的說明,而真正的函數定義則在程序放在后部。 但細心的讀者可能發現有一些函數并未寫其說明,而是直接在程序中定義了,如mDelay 函 數,這是為何呢?這是因為這些函數出現在程序的前面,在還沒有任何函數調用它們之前它 們就被定義了,因此就不需要再單獨寫一個函數說明。讀者可將mDelay 函數的定義移到程 序的后面位置,再次編譯就會出錯。當然,好的編程習慣是不論函數在何處被定義,總是在 寫前面寫一個函數說明。 有關單片機的C 語言編程到此就告一個段落,雖然C 語言很多特性尚未介紹,但通過 上面有關內容的學習,我們已經可以使用C 語言進行一些實際的工程開發工作,大家可以 在工作中繼續學習有關C 語言的知識。 你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13631365
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484210
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484190
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484041
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13483797
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13480591
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13480036 關于幸福人生的經典誤解分類:默認欄目
LcdWc(0x0c); } void WriteString(char s[]) { uchar pS=0; for(;;) { WriteChar(s[pS]); pS++; if(s[pS]==0) break; if(++Xpos>=15) //每行最多顯示16 個字符 break; } } void main() { uchar s1[]="Hellow World!"; RstLcd(); //復位LCD ClrLcd(); SetCur(CurFlash); //光標顯示且閃爍 Xpos=2; Ypos=1; WriteString(s1); for(;;) {;} } 程序分析:本程序中大量使用了函數,在此對函數的功能作一個簡介。 C 語言程序是由一個個函數構成的,從函數定義的形式上劃分,函數有三種形式:無參 數函數、有參數函和空函數。 無參數函數的定義形式為: 返回值類型識別符 函數名(){函數體語句} 如本例中的void WaitIdle(){ …… }就是一個無參數函數 有參數函數的定義形式為: 返回值類型識別符 函數名(形式參數列表){函數體語句} 如本例中的void LcdWdn(uchar c){ …… }就是一個有參數的函數 函數可以返回一個值,也可以什么值也返回,如果函數要返回一個值,在定義這個函數 時要定義好這個值的數據類型,這里所說的數據類型就是指前面課程中介紹到的int、char、 float 等類型,如果在定義函數時沒有定義返回值的類型,系統默認為返回一個int 型的值。 如果明確地知道一個函數將沒有返回值,可以將其定義為void 型,這樣,如果在調用函數 單片機的C 語言輕松入門 48 時錯誤地使用了“變量名=函數名”的方式來調用函數,編譯器就能發現這一錯誤并指出。 本例中就大量地應用到了void 型函數。 C 語言采用函數之間的參數傳遞方式,這使得一個函數能對不同的變量進行功能相同的 處理,使函數具有了通用性。定義函數時,寫在函數名括號中的稱之為形式參數,而在實際 調用函數時寫在函數括號中的稱之為實際參數。本例中: void SetCur(uchar Cur) { …} 函數中Cur 就是一個形式參數,而在主函數中調用時寫的: SetCur(CurFlash); 其中CurFlash 就是一個用符號常量表示的實際參數,在執行該函數時該值被傳遞到函 數內部并執行。 每一個函數所調用的函數必須已被定義,否則就會出現語法錯誤,因此程序中一般要求 在程序的開頭對程序中用到的函數進行統一的說明,然后再分別定義有關函數,本例中有: void WriteChar(uchar); … void LcdWdn(uchar); 就是首先在程序的前方寫一個有關函數的說明,而真正的函數定義則在程序放在后部。 但細心的讀者可能發現有一些函數并未寫其說明,而是直接在程序中定義了,如mDelay 函 數,這是為何呢?這是因為這些函數出現在程序的前面,在還沒有任何函數調用它們之前它 們就被定義了,因此就不需要再單獨寫一個函數說明。讀者可將mDelay 函數的定義移到程 序的后面位置,再次編譯就會出錯。當然,好的編程習慣是不論函數在何處被定義,總是在 寫前面寫一個函數說明。 有關單片機的C 語言編程到此就告一個段落,雖然C 語言很多特性尚未介紹,但通過 上面有關內容的學習,我們已經可以使用C 語言進行一些實際的工程開發工作,大家可以 在工作中繼續學習有關C 語言的知識。
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484210
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484190
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484041
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13483797
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13480591
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13480036 關于幸福人生的經典誤解分類:默認欄目
pS++; if(s[pS]==0) break; if(++Xpos>=15) //每行最多顯示16 個字符 break; } } void main() { uchar s1[]="Hellow World!"; RstLcd(); //復位LCD ClrLcd(); SetCur(CurFlash); //光標顯示且閃爍 Xpos=2; Ypos=1; WriteString(s1); for(;;) {;} } 程序分析:本程序中大量使用了函數,在此對函數的功能作一個簡介。 C 語言程序是由一個個函數構成的,從函數定義的形式上劃分,函數有三種形式:無參 數函數、有參數函和空函數。 無參數函數的定義形式為: 返回值類型識別符 函數名(){函數體語句} 如本例中的void WaitIdle(){ …… }就是一個無參數函數 有參數函數的定義形式為: 返回值類型識別符 函數名(形式參數列表){函數體語句} 如本例中的void LcdWdn(uchar c){ …… }就是一個有參數的函數 函數可以返回一個值,也可以什么值也返回,如果函數要返回一個值,在定義這個函數 時要定義好這個值的數據類型,這里所說的數據類型就是指前面課程中介紹到的int、char、 float 等類型,如果在定義函數時沒有定義返回值的類型,系統默認為返回一個int 型的值。 如果明確地知道一個函數將沒有返回值,可以將其定義為void 型,這樣,如果在調用函數 單片機的C 語言輕松入門 48 時錯誤地使用了“變量名=函數名”的方式來調用函數,編譯器就能發現這一錯誤并指出。 本例中就大量地應用到了void 型函數。 C 語言采用函數之間的參數傳遞方式,這使得一個函數能對不同的變量進行功能相同的 處理,使函數具有了通用性。定義函數時,寫在函數名括號中的稱之為形式參數,而在實際 調用函數時寫在函數括號中的稱之為實際參數。本例中: void SetCur(uchar Cur) { …} 函數中Cur 就是一個形式參數,而在主函數中調用時寫的: SetCur(CurFlash); 其中CurFlash 就是一個用符號常量表示的實際參數,在執行該函數時該值被傳遞到函 數內部并執行。 每一個函數所調用的函數必須已被定義,否則就會出現語法錯誤,因此程序中一般要求 在程序的開頭對程序中用到的函數進行統一的說明,然后再分別定義有關函數,本例中有: void WriteChar(uchar); … void LcdWdn(uchar); 就是首先在程序的前方寫一個有關函數的說明,而真正的函數定義則在程序放在后部。 但細心的讀者可能發現有一些函數并未寫其說明,而是直接在程序中定義了,如mDelay 函 數,這是為何呢?這是因為這些函數出現在程序的前面,在還沒有任何函數調用它們之前它 們就被定義了,因此就不需要再單獨寫一個函數說明。讀者可將mDelay 函數的定義移到程 序的后面位置,再次編譯就會出錯。當然,好的編程習慣是不論函數在何處被定義,總是在 寫前面寫一個函數說明。 有關單片機的C 語言編程到此就告一個段落,雖然C 語言很多特性尚未介紹,但通過 上面有關內容的學習,我們已經可以使用C 語言進行一些實際的工程開發工作,大家可以 在工作中繼續學習有關C 語言的知識。 你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13631365
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484210
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484190
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13484041
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13483797
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13480591
你可以通過這個鏈接引用該篇文章:http://xiaowei7001.bokee.com/tb.b?diaryId=13480036 關于幸福人生的經典誤解分類:默認欄目
1、 本站不保證以上觀點正確,就算是本站原創作品,本站也不保證內容正確。 2、如果您擁有本文版權,并且不想在本站轉載,請書面通知本站立即刪除并且向您公開道歉! |
本站協議。
版權信息。
關于我們。
本站地圖。
營業執照。
發票說明。
付款方式。
聯系方式
深圳市寶安區西鄉五壹電子商行——粵ICP備16073394號-1;地址:深圳西鄉河西四坊183號;郵編:518102 E-mail:51dz$163.com($改為@);Tel:(0755)27947428 工作時間:9:30-12:00和13:30-17:30和18:30-20:30,無人接聽時可以再打手機13537585389 |