微信公眾平臺獲取碼:'單片機入門8',關(guān)注著名的PCB哥微信公眾平臺,,回復(fù)上述獲取碼即可快速獲取本文
C語言,,沒接觸過計算機編程語言的人會把它看的很神秘,感覺非常難,,而在有些人看來,,C語言的邏輯和運算,就是小學(xué)水平,,所以大家不要怕它,,我們盡可能的從小學(xué)數(shù)學(xué)邏輯方式帶著大家學(xué)習(xí)C語言。
這里先簡單介紹一些注意事項,,然后還是從實驗中講解會比較深刻。 1,、十進(jìn)制就不多說了,,逢十進(jìn)位,一個位有十個值:0~9,,我們的生活中到處都是它的身影,。二進(jìn)制就是逢二進(jìn)位,它的一個位只有兩個值:0和1,,但它卻是實現(xiàn)計算機系統(tǒng)的最基本的理論基礎(chǔ),,計算機(包括單片機)芯片是基于成萬上億個的開關(guān)管組合而成的,他們每一個都只能有開和關(guān)兩種狀態(tài),,再難找出第三個狀態(tài)了(不要辯解半開半關(guān)這個狀態(tài),,它是不穩(wěn)定態(tài),是極力避免的),,所以他們只能對應(yīng)于二進(jìn)制的1和0兩個值,,而沒有2、3,、4……,,理解二進(jìn)制對于理解計算機的本質(zhì)很有幫助。書寫二進(jìn)制數(shù)據(jù)時需加前綴0b,,每一位的值只能是0或1,。十六進(jìn)制就是把4個二進(jìn)制位組合為一位來表示,于是它的每一位有0b0000~0b1111共16個值,,用0~9再加上A~F(或a~f)表示,,那么它自然就是逢十六進(jìn)位了,它本質(zhì)上同二進(jìn)制是一樣的,,是二進(jìn)制的一種縮寫形式,,也是我們程序編寫中常用的形式,。書寫十六進(jìn)制數(shù)據(jù)時需加前綴0x,下表是三種進(jìn)制之間的對應(yīng)關(guān)系,。 十進(jìn)制 二進(jìn)制 十六進(jìn)制 0 0b0 0x00 1 0b1 0x01 2 0b10 0x02 3 0b11 0x03 4 0b100 0x04 5 0b101 0x05 6 0b110 0x06 7 0b111 0x07 8 0b1000 0x08 9 0b1001 0x09 10 0b1010 0x0A 11 0b1011 0x0B 12 0b1100 0x0C 13 0b1101 0x0D 14 0b1110 0x0E 15 0b1111 0x0F 16 0b10000 0x10 17 0b10001 0x11 18 0b10010 0x12 …… …… …… 2,、對于二進(jìn)制來說,8位二進(jìn)制我們稱之為一個字節(jié),,二進(jìn)制的表達(dá)范圍值是從0b00000000~0b11111111,,而我們程序中用十六進(jìn)制表示的時候就是從0x00到0xFF,這里教大家一個二進(jìn)制轉(zhuǎn)換十進(jìn)制和十六進(jìn)制的方法,,二進(jìn)制4位一組,,遵循8,4,2,1的規(guī)律比如 1010,那么從最高位開始算,,數(shù)字大小是8*1+4*0+2*1+1*0 = 10,,那么十進(jìn)制就是10,十六進(jìn)制就是0xA,。尤其二進(jìn)制轉(zhuǎn)十六進(jìn)制的時候,,十六進(jìn)制一位剛好是和二進(jìn)制的4位相互對應(yīng)的,這些大家不需要強行記憶,,用幾次就熟練了,。 3、對于進(jìn)制來說,,只是數(shù)據(jù)的表現(xiàn)形式,,而數(shù)據(jù)的大小不會因為進(jìn)制表現(xiàn)形式不同而不同,比如二進(jìn)制的0b1,、十進(jìn)制的1、十六進(jìn)制的0x01,,他們本質(zhì)上數(shù)值大小相等的同一個數(shù)據(jù),。我們在進(jìn)行C語言編程的時候,我們只寫十進(jìn)制和十六進(jìn)制,,那么不帶0x的就是十進(jìn)制,,帶了0x符號的就是十六進(jìn)制。
什么是變量,?變量自然和常量是相對的,。常量比如是1、2,、3……等固定的數(shù)字,,而變量,和我們小學(xué)學(xué)的x是一個概念,,我們可以讓它是1,,也可以讓它是2,,我們想讓它是幾是我們程序說了算的。 那么我們小學(xué)學(xué)的數(shù)學(xué)里邊,,有這么幾類,,正數(shù)、負(fù)數(shù),、整數(shù)和小數(shù),。在C語言里,,名字和我們數(shù)學(xué)里學(xué)的不一樣外,還對數(shù)據(jù)大小進(jìn)行了限制。這個地方有一點復(fù)雜的是,,在C51里邊的數(shù)據(jù)范圍和其他編程環(huán)境還不完全一樣,因此我們下邊的這個圖,,僅僅代表的是C51,,其他編程環(huán)境可能不一樣,大家知道有這回事就可以了,。 C語言的數(shù)據(jù)基本類型分為整型,、字符型以及浮點型,如圖8-1 圖8-1中,,三種基本類型,,每個基本類型又包含了兩個類型。其中字符型和整型,,除了有一定的數(shù)據(jù)大小范圍之外,,只能表達(dá)整數(shù)。而unsigned型的又只能表達(dá)正數(shù),,要表達(dá)負(fù)數(shù)必須用signed型,,表達(dá)小數(shù),必須用浮點型,。 比如上節(jié)課最后給的閃爍小燈的程序,,我們用的是unsigned int i = 0;這個地方i的范圍就是0~65535,我們for語句的寫法,,如果那個30000改成70000的話,,for(i=0;i<70000;i++);大家會發(fā)現(xiàn)小燈會一直亮,而不是閃爍了,,那理解這個問題,,當(dāng)然我們要來了解for語句的用法了。 這里有一個編程宗旨,,就是能用小不用大,。就是說定義能用1個字題的,就不定義成int,,一方面節(jié)省RAM空間可以讓其他變量或者中間運算過程使用,,另外一方面,,占空間小程序運算速度也快一些。
for語句是我們今后編程的一個常用的語句,,這個語句必須得學(xué)會其用法,,他不僅僅可以用來做延時,還可以用來做一些循環(huán)運算,。for語句的一般形式如下:
其執(zhí)行過程是:表達(dá)式1首先執(zhí)行且只執(zhí)行一次,;然后執(zhí)行表達(dá)式2,通常都是一個用于判定條件的表達(dá)式,,如果表達(dá)式2條件成立,,就執(zhí)行(需要執(zhí)行的語句);然后再執(zhí)行表達(dá)式3,;再判斷表達(dá)式2,,再執(zhí)行表達(dá)式3…..一直到表達(dá)式2不成立時,跳出循環(huán)往下執(zhí)行,。舉個例子: for(i = 0; i<2; i++) { j++; } 這里有一個符號++,,這個符號表示加1的意思。假如j最開始初值是0,,首先執(zhí)行表達(dá)式1的i=0,,然后判斷i小于2這個條件成立,就執(zhí)行一次j++,,j的值就是1了,,然后經(jīng)過表達(dá)式3后,i的值也變成1了,,再判斷條件2,,還是符合,j再加一次,,j變成2了,,表達(dá)式3后i也變成2了,再判斷條件2,,發(fā)現(xiàn)2<2這個條件不成立了,所以就不會再執(zhí)行j++這個語句了,。所以執(zhí)行完畢后,,j的值就是2。 for語句除了這種標(biāo)準(zhǔn)用法,,還有幾種特殊用法,,我們上節(jié)課的閃爍小燈對for語句的用法for(i=0; i<30000; i++) ;我們沒有加(需要執(zhí)行的語句),沒有加的話,,就是什么都不操作,。但是什么都不操作的話,,我們這個for語句循環(huán)判斷了30000次,程序執(zhí)行是會用掉時間的,,所以就起到了延時的作用,。比如我們把30000改成20000,會發(fā)現(xiàn)燈的閃爍速度加快了,,因為我們延時時間短了,,當(dāng)然,我們該成40000后會發(fā)現(xiàn),,閃爍慢了,。但是有一點特別注意,C語言的延時時間是不能通過程序看出來的,,也不會成比例,,比如假如我們這個for循環(huán)里邊的表達(dá)式2使用30000的時候延時3秒的話,那么延時40000的時候,,可能不會是4秒,,那如何看實際延時時間呢,一會我再教大家,。 還有一種寫法for( ; ; ),,這樣寫后,這個for循環(huán)就變成了死循環(huán)了,,就不停的執(zhí)行(需要執(zhí)行的語句),,和我們前邊講的while(1)的意思是一樣的。那while這個語法是如何用的呢,?
在我們單片機C語言編程的時候,,每個程序我們都會固定的加一句while(1),這條語句就可以起到死循環(huán)的作用,。對于while語句來說,,他的一般形式是: While (表達(dá)式) { 循環(huán)體語句; } 在C語言里,,通常表達(dá)式符合條件,,我們叫做真,不符合條件,,叫做假,。比如前邊i<30000,當(dāng)i等于0的時候,,那這個條件成立,,就是真,如果i大于30000的時候,,條件不成立,,叫做假,。 while(表達(dá)式)這個括號里的表達(dá)式,為真的時候,,就會執(zhí)行循環(huán)體語句,,當(dāng)為假的時候,就不執(zhí)行,。在這里先不舉例,,后邊遇到時再詳細(xì)說明。 還有另外一種情況,,就是我們C語言里邊,,除了表達(dá)式外,還有常數(shù),,習(xí)慣上,,我們非0的常數(shù)都認(rèn)為是真,只有0認(rèn)為是假,,所以我們程序中加了while(1),,這個數(shù)字1,可以改成2,3,4……等等都可以,,都是一個死循環(huán),,不停的執(zhí)行循環(huán)體的語句,但是如果把這個數(shù)字改成0,,那么就不會執(zhí)行循環(huán)體的語句了,。
函數(shù)定義的一般形式如下: 函數(shù)值類型 函數(shù)名 (形式參數(shù)列表) { 函數(shù)體 } 1、函數(shù)值類型,,就是函數(shù)返回值的類型,。在我們后邊程序使用中,會有很多函數(shù)中有return x這個東西,,這個返回值也就是函數(shù)本身的類型,。還有一種情況,就是這個函數(shù)只執(zhí)行操作,,不需要返回任何值,,那么這個時候它的類型就是空類型void,這個void按道理來說是可以省略的,,但是一旦省略,,Keil軟件會報一個警告,所以我們通常也不省,。 2、函數(shù)名,??梢允侨魏魏戏ǖ臉?biāo)示符,,但是不能與其他函數(shù)或者變量重名,也不能是關(guān)鍵字,。什么是關(guān)鍵字,,后邊我們慢慢接觸,比如char這類,,都是關(guān)鍵字,,是我們程序中具備特殊功能的標(biāo)志符,這種東西不可以命名函數(shù),。 3,、形式參數(shù)列表,我們也叫做形參,,這個是函數(shù)調(diào)用的時候,,相互傳遞數(shù)據(jù)用的。有的函數(shù),,我們不需要傳遞參數(shù),,那么可以用void來替代,void同樣可以省略,,但是那個括號是不能省略的,。 4、函數(shù)體,。函數(shù)體包含了聲明語句部分和執(zhí)行語句部分,。聲明語句部分主要用于聲明函數(shù)內(nèi)部所使用的變量,執(zhí)行語句部分主要是一些函數(shù)需要執(zhí)行的語句,。特別注意,,所有的聲明語句部分必須放在執(zhí)行語句之前,否則編譯的時候會報錯,。 5,、一個工程文件必須有且僅能有一個main函數(shù),程序執(zhí)行的時候,,都是從main函數(shù)開始的,。 6、關(guān)于形參和實參的概念,,我們后邊再總結(jié),,如果遇到程序里有,大家再跟著抄一段時間,。先用,,后講解,這樣更有利于理解。 我們再來回顧一下我們上節(jié)課閃爍LED程序部分 void main() //void即函數(shù)類型 { unsigned int i = 0; //定義一個無符號整數(shù)i,,變量范圍是0~65535 //并且賦一個初值0 ENLED = 0; //先定義變量i,,后寫執(zhí)行部分 ADDR0 = 0; ADDR1 = 1; ADDR2 = 1; ADDR3 = 1; //74HC138開啟三極管 while(1) //程序死循環(huán) { LED = 0; //點亮小燈 for(i=0;i<30000;i++); //for延時操作 LED = 1; //熄滅小燈 for(i=0;i<30000;i++); //for延時操作 } }
C語言常用的延時辦法,有以下4種 圖8-2是我們編程語言常用的4種延時方法,,其中兩種非精確延時,,兩種精確一些的延時。for語句和while語句都可以通過改變i的范圍值來改變延時時間,,但是C語言的時間都是不能通過程序看出來的,。 精確延時有兩個方法,一個方法是用定時器來延時,,這個方法我們后邊課程要詳細(xì)介紹,,定時器是單片機的一個重點。另外一個就是用庫函數(shù)_nop_();,,一個NOP的時間是一個機器周期的時間,,這個后邊也要介紹。 非精確延時,,只是在我們做一些簡單的比如小燈閃爍,,流水燈等簡單實驗中使用,而實際做實際開發(fā)程序中其實這種非精確延時用的極少,,這里我們只是做演示功能使用,。 好了,介紹完了,,我們就要實戰(zhàn)了,。上節(jié)課的LED小燈閃爍的程序,我們用的延時方式是for(i=0;i<30000;i++);大家如果把這里的i改成100,,下載進(jìn)入單片機,,會發(fā)現(xiàn)小燈一直亮,而不是閃爍狀態(tài),,現(xiàn)在大家都把這個程序改一下,,都改成100,然后下載觀察一下現(xiàn)象再繼續(xù),。 觀察完了,,毫無疑問,實際現(xiàn)象和我提到的理論是相符合的,,這是為什么呢,?這里介紹一個常識。我們?nèi)说娜庋蹖﹂W爍的光線有一個最低分辨能力,,通常情況下當(dāng)閃爍的頻率高于50Hz時,,我們看到的信號就是常亮的,。即,延時的時間低于20ms的時候,,我們的肉眼是分辨不出來小燈是在閃爍的,,可能最多看到的是小燈亮暗稍微變化了一下。要想清楚的看到小燈閃爍,,延時的值必須大一點,大到什么程度呢,,不同的亮度的燈不完全一樣,,大家可以自己做實驗。 那么如何觀察延時有多長時間呢,?大家鼠標(biāo)點Keil的Project–>Options for Target ‘Target1’,,或點Target1右側(cè)圖標(biāo),進(jìn)入設(shè)置選項,,如圖8-3所示 首先我們打開Target這個選項卡,,找到里邊的Xtal(MHz)這個位置,這是填寫我們進(jìn)行模擬時間的晶振選項,,我們以晶振是11.0592MHz為例,,所以這個地方我們要填上11.0592。然后找到Debug這個選項,,選擇左側(cè)的Use Simulator,,然后點擊最下邊的OK就可以了,如圖8-4所示,。 點擊Debug菜單里的Start/Stop Debug Session,,或者鼠標(biāo)點做左側(cè)的這個Debug圖標(biāo),會進(jìn)入一個新的頁面,,如圖8-5所示,。 最左側(cè)那一欄是單片機的一些寄存器和系統(tǒng)信息,最上邊那一欄是Keil將C語言轉(zhuǎn)換成匯編的代碼,,下邊就是我們C語言的程序,,還有各種窗口都可以打開,在view菜單可以打開或者關(guān)閉我們的各種窗口,。這節(jié)課我們只關(guān)心我們需要的窗口,,其他窗口用到再說。那么有時候我們覺得這種分布不是特別的好,,所以我們想改變一下窗口分布怎么辦呢,?比如Disassembly(匯編)窗口,我們先用鼠標(biāo)拖動它,,然后中間會出現(xiàn)一個方向符號,,再用鼠標(biāo)點那個方向符號,,他就給我們分布了,如圖8-6所示,。 我們點擊最右邊的那個箭頭,,然后窗口變化成如8-7圖所示?;蛘呶覀?nèi)绻貌坏絽R編的程序,,也可以直接關(guān)掉。 細(xì)心的同學(xué)會看到在C語言的程序里有個黃色的箭頭,,這個箭頭代表的就是這個程序當(dāng)前運行的位置,,在這個Debug里邊,我們可以看到我們的程序運行的過程,。在左上角有這三個圖標(biāo),,第一個是復(fù)位,點擊一下之后,,程序就會跑到最開始的位置運行,,第二個圖標(biāo)是全速運行圖標(biāo),點擊一下程序就會全速運行跑起來,,第三個圖標(biāo)是停止圖標(biāo),,當(dāng)程序全速運行跑起來后,我們可以通過點擊第三個圖標(biāo)來讓程序停止,,觀察程序運行到哪里了,。點擊一下復(fù)位后,我們會發(fā)現(xiàn)C語言程序左側(cè)有的灰色或者綠色,,有的地方還是保持原來的白色,,我們可以在我們灰色的位置雙擊鼠標(biāo)設(shè)置斷點,就是比如程序一共20行,,在第十行設(shè)置斷點后,,點全速運行,程序就會運行到第十行停止,,方便我們觀察運行到這個地方的情況,。 同學(xué)們會發(fā)現(xiàn),有的位置可以設(shè)置斷點,,有的地方不可以設(shè)置斷點,,這是為什么呢?Keil軟件本身具備優(yōu)化我們程序的功能,,如果大家想在所有的位置設(shè)置斷點,,可以把優(yōu)化選項設(shè)置到0位置,就是程序不進(jìn)行優(yōu)化,。如圖8-8所示,。 這節(jié)課我們重點是看看C語言代碼的運行時間,,在最左側(cè)的register那個框內(nèi),有一個sec選項,,這個選項就是單片機運行時間的統(tǒng)計選項,,大家點一下復(fù)位按鈕,會發(fā)現(xiàn)這個sec變成了0,,然后我們在LED = 0; 這一句加一個斷點,,在LED = 1;這個位置加一個斷點,我們點擊全速運行按鈕,,會直接停留在LED = 0,;我們會看到我們的時間變化成0.000197秒,如圖8-9所示,。 我們再點一下全速運行,會發(fā)現(xiàn)sec變成了0.07530650秒,,那么這樣一個for循環(huán)的時間大概有75ms左右,,我們也可以通過改變30000這個數(shù)字來改變這個間隔時間。當(dāng)然了,,大家要注意i的變量范圍,,你如果寫成了大于65535的值以后,程序就會一直運行不下去了,,因為i無論如何變化,,都不會大于這個值,如果要大于這個值正常運行,,必須改變i定義的類型了,。后邊如果我們要求看一段程序運行多長時間,都可以通過這種方式來看,。
我們前邊學(xué)了點亮LED小燈,,然后又學(xué)了LED小燈閃爍,下邊我們要進(jìn)一步了解一下如何讓8個小燈依次一個一個點亮,,流動起來,。 通過前面的課程,我們可以了解到控制引腳P0.0通過了74HC245控制DB0,,P0.1控制DB1……P0.7控制DB7,。我們還學(xué)到一個字節(jié)是8位,我們?nèi)绻麑懸粋€P0,,就代表了P0.0到P0.7的共8個位,。比如我們寫P0 = 0xFE;轉(zhuǎn)換成二進(jìn)制就是0b11111110,,所以點亮LED小燈的程序,,實際上我們可以改成另外一種寫法,,如下所示。 #include <reg52.h> sbit ADDR0 = P1^0; sbit ADDR1 = P1^1; sbit ADDR2 = P1^2; sbit ADDR3 = P1^3; sbit ENLED = P1^4; void main() { ENLED = 0; ADDR0 = 0; ADDR1 = 1; ADDR2 = 1; ADDR3 = 1; //74HC138開啟三極管 P0 = 0xFE; while(1); //程序停止在這里 } 通過上邊這個程序我們可以看出來,,可以通過P0來控制所有的8個LED小燈的亮和滅,。我們下邊要進(jìn)行依次亮和滅,怎么辦呢,?從這里就可以得到方法了,,如果想讓單片機流水燈流動起來,依次要實現(xiàn)的結(jié)果是:0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F,。 在我們的C語言當(dāng)中,有一個移位操作,,其中<<代表的是左移,,>>代表的是右移。比如a = 0x01 << 1就是a 的結(jié)果等于0x01左移一位,。大家注意,,移位都是指二進(jìn)制移位,,那么移位完了,本來在第0位的1移動到了第一位上,,移動完了低位是補0的,。所以a的值最終是等于0x02,。 還要學(xué)習(xí)另外一個運算符~,這個符號是按位取反的意思,,同樣,,按位取反也是針對二進(jìn)制而言,。比如a = ~(0x01),,0x01的二進(jìn)制是0b00000001,按位取反是0b11111110,,那么a的值就是0xFE了,。 學(xué)會了這兩個符號后,那么我們就可以把流水燈的程序?qū)懗鰜?,先把程序貼上,。 #include <reg52.h> sbit ADDR0 = P1^0; sbit ADDR1 = P1^1; sbit ADDR2 = P1^2; sbit ADDR3 = P1^3; sbit ENLED = P1^4; void main() { unsigned char j = 0; unsigned int i = 0; ENLED = 0; ADDR0 = 0; ADDR1 = 1; ADDR2 = 1; ADDR3 = 1; //74HC138開啟三極管Q16 while(1) //程序死循環(huán) { P0 = ~(0x01 << j++); //P0等于1左移j位,并且j++ for(i=0; i<20000; i++); //延時 if(j == 8) //如果j等于8,,重新給j賦值0 { j = 0; } } } 這里我只講兩種情況,,當(dāng)j等于0的時候,1左移0位還是1,,那么寫成二進(jìn)制后就是0b00000001,,對這個數(shù)字按位取反就是0b11111110,亮的是最右邊的小燈,。當(dāng)j等于7的時候,,1左移7位就是0b10000000,按位取反0b01111111,,亮的是最左邊的小燈。 流水燈結(jié)束后,,關(guān)于小燈的講解,,我們暫時告一段落,后邊還有小燈的高級用法,,我們到時候再詳細(xì)講解,。 |
|