C語言,沒接觸過計算機(jī)編程語言的人會把它看的很神秘,感覺非常的難,而在我看來,C語言的邏輯和運(yùn)算,就是小學(xué)水平,所以大家不要怕它,我盡可能的從小學(xué)數(shù)學(xué)邏輯方式帶著大家學(xué)習(xí)C語言。
1.1 二進(jìn)制、十進(jìn)制和十六進(jìn)制
看似很簡單的東西,但是從同學(xué)們學(xué)習(xí)視頻的情況來看,很多同學(xué)不能徹底明白。這里先簡單介紹一些注意事項,然后還是從實(shí)驗中講解會比較深刻。
1、十進(jìn)制就不多說了,逢十進(jìn)位,一個位有十個值:0~9,我們的生活中到處都是它的身影。二進(jìn)制就是逢二進(jìn)位,它的一個位只有兩個值:0和1,但它卻是實(shí)現(xiàn)計算機(jī)系統(tǒng)的最基本的理論基礎(chǔ),計算機(jī)(包括單片機(jī))芯片是基于成萬上億個的開關(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)制對于理解計算機(jī)的本質(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
|
……
|
……
|
……
|
表4-1 進(jìn)制轉(zhuǎn)換
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)的,這些大家不需要強(qiá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 C語言變量類型和范圍
什么是變量?變量自然和常量是相對的。常量比如是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)行了限制。這個地方有一點(diǎn)復(fù)雜的是,在C51里邊的數(shù)據(jù)范圍和其他編程環(huán)境還不完全一樣,因此我們下邊的這個圖,僅僅代表的是C51,其他編程環(huán)境可能不一樣,大家知道有這回事就可以了。C語言的關(guān)鍵字較多語法有些嚴(yán)謹(jǐn)我們不必死記硬背只需理解了,用的時候再打開http://www.torrancerestoration.com/mcuteach/150.html 查閱即可.
C語言的數(shù)據(jù)基本類型分為整型、字符型以及浮點(diǎn)型,如圖4-1
圖4-1 C語言數(shù)據(jù)類型
圖4-1中,三種基本類型,每個基本類型又包含了兩個類型。其中字符型和整型,除了有一定的數(shù)據(jù)大小范圍之外,只能表達(dá)整數(shù)。而unsigned型的又只能表達(dá)正數(shù),要表達(dá)負(fù)數(shù)必須用signed型,編寫程序的時候,signed可以省略。表達(dá)小數(shù),必須用浮點(diǎn)型。
比如上節(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空間可以讓其他變量或者中間運(yùn)算過程使用,另外一方面,占空間小程序運(yùn)算速度也快一些。
1.3 for循環(huán)語句
for語句是我們今后編程的一個常用的語句,這個語句必須得學(xué)會其用法,他不僅僅可以用來做延時,還可以用來做一些循環(huán)運(yùn)算。for語句的一般形式如下:
for(表達(dá)式1; 表達(dá)式2; 表達(dá)式3)
(需要執(zhí)行的語句);
其執(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),閃爍慢了。但是有一點(diǎn)特別注意,C語言的延時時間是不能通過程序看出來的,也不會成比例,比如假如我們這個for循環(huán)里邊的表達(dá)式2使用30000的時候延時3秒的話,那么延時40000的時候,可能不會是4秒,那如何看實(shí)際延時時間呢,一會我再教大家。
還有一種寫法for( ; ; ),這樣寫后,這個for循環(huán)就變成了死循環(huán)了,就不停的執(zhí)行(需要執(zhí)行的語句),和我們前邊講的while(1)的意思是一樣的。那while這個語法是如何用的呢?
1.4 while循環(huán)語句
在我們單片機(jī)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)體的語句了。
1.5 函數(shù)的簡單介紹
函數(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)于形參和實(shí)參的概念,我們后邊再總結(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; //點(diǎn)亮小燈
for(i=0;i<30000;i++); //for延時操作
LED = 1; //熄滅小燈
for(i=0;i<30000;i++); //for延時操作
}
}
1.6 Keil軟件延時
C語言常用的延時辦法,有以下4種
圖4-2 C語言延時
圖4-2是我們編程語言常用的4種延時方法,其中兩種非精確延時,兩種精確一些的延時。for語句和while語句都可以通過改變i的范圍值來改變延時時間,但是C語言的時間都是不能通過程序看出來的。
精確延時有兩個方法,一個方法是用定時器來延時,這個方法我們后邊課程要詳細(xì)介紹,定時器是單片機(jī)的一個重點(diǎn)。另外一個就是用庫函數(shù)_nop_();,一個NOP的時間是一個機(jī)器周期的時間,這個后邊也要介紹。
非精確延時,只是在我們做一些簡單的比如小燈閃爍,流水燈等簡單實(shí)驗中使用,而實(shí)際做實(shí)際開發(fā)程序中其實(shí)這種非精確延時用的極少,這里我們只是做演示功能使用。
好了,介紹完了,我們就要實(shí)戰(zhàn)了。上節(jié)課的LED小燈閃爍的程序,我們用的延時方式是for(i=0;i<30000;i++);大家如果把這里的i改成100,下載進(jìn)入單片機(jī),會發(fā)現(xiàn)小燈一直亮,而不是閃爍狀態(tài),現(xiàn)在大家都把這個程序改一下,都改成100,然后下載觀察一下現(xiàn)象再繼續(xù)。
觀察完了,毫無疑問,實(shí)際現(xiàn)象和我提到的理論是相符合的,這是為什么呢?這里介紹一個常識。我們?nèi)说娜庋?/span>對閃爍的光線有一個最低分辨能力,通常情況下當(dāng)閃爍的頻率高于50Hz時,我們看到的信號就是常亮的。即,延時的時間低于20ms的時候,我們的肉眼是分辨不出來小燈是在閃爍的,可能最多看到的是小燈亮暗稍微變化了一下。要想清楚的看到小燈閃爍,延時的值必須大一點(diǎn),大到什么程度呢,不同的亮度的燈不完全一樣,大家可以自己做實(shí)驗。
那么如何觀察延時有多長時間呢?大家鼠標(biāo)點(diǎn)Keil的Project-->Options for Target ‘Target1’,或點(diǎn)Target1右側(cè)圖標(biāo) ,進(jìn)入設(shè)置選項,如圖4-3所示
圖4-3 Options for Target
首先我們打開Target這個選項卡,找到里邊的Xtal(MHz)這個位置,這是填寫我們進(jìn)行模擬時間的晶振選項,從我們原理圖以及板子上都可以看到,我們單片機(jī)所配的晶振是11.0592MHz,所以這個地方我們要填上11.0592。然后找到Debug這個選項,選擇左側(cè)的Use Simulator,然后點(diǎn)擊最下邊的OK就可以了,如圖4-4所示。
圖4-4 Debug配置信息
點(diǎn)擊Debug菜單里的Start/Stop Debug Session,或者鼠標(biāo)點(diǎn)做左側(cè)的這個Debug圖標(biāo) ,會進(jìn)入一個新的頁面,如圖4-5所示。
圖4-5 Debug窗口顯示
最左側(cè)那一欄是單片機(jī)的一些寄存器和系統(tǒng)信息,最上邊那一欄是Keil將C語言轉(zhuǎn)換成匯編的代碼,下邊就是我們C語言的程序,還有各種窗口都可以打開,在view菜單可以打開或者關(guān)閉我們的各種窗口。這節(jié)課我們只關(guān)心我們需要的窗口,其他窗口用到再說。那么有時候我們覺得這種分布不是特別的好,所以我們想改變一下窗口分布怎么辦呢?比如Disassembly(匯編)窗口,我們先用鼠標(biāo)拖動它,然后中間會出現(xiàn)一個方向符號,再用鼠標(biāo)點(diǎn)那個方向符號,他就給我們分布了,如圖4-6所示。

圖4-6 Keil窗口移動(一)
我們點(diǎn)擊最右邊的那個箭頭,然后窗口變化成如4-7圖所示�;蛘呶覀�?nèi)绻貌坏絽R編的程序,也可以直接關(guān)掉。
圖4-7所示 Keil窗口移動(二)
細(xì)心的同學(xué)會看到在C語言的程序里有個黃色的箭頭,這個箭頭代表的就是這個程序當(dāng)前運(yùn)行的位置,在這個Debug里邊,我們可以看到我們的程序運(yùn)行的過程。在左上角有這三個圖標(biāo) ,第一個是復(fù)位,點(diǎn)擊一下之后,程序就會跑到最開始的位置運(yùn)行,第二個圖標(biāo)是全速運(yùn)行圖標(biāo),點(diǎn)擊一下程序就會全速運(yùn)行跑起來,第三個圖標(biāo)是停止圖標(biāo),當(dāng)程序全速運(yùn)行跑起來后,我們可以通過點(diǎn)擊第三個圖標(biāo)來讓程序停止,觀察程序運(yùn)行到哪里了。點(diǎn)擊一下復(fù)位后,我們會發(fā)現(xiàn)C語言程序左側(cè)有的灰色或者綠色,有的地方還是保持原來的白色,我們可以在我們灰色的位置雙擊鼠標(biāo)設(shè)置斷點(diǎn),就是比如程序一共20行,在第十行設(shè)置斷點(diǎn)后,點(diǎn)全速運(yùn)行,程序就會運(yùn)行到第十行停止,方便我們觀察運(yùn)行到這個地方的情況。
同學(xué)們會發(fā)現(xiàn),有的位置可以設(shè)置斷點(diǎn),有的地方不可以設(shè)置斷點(diǎn),這是為什么呢?Keil軟件本身具備優(yōu)化我們程序的功能,如果大家想在所有的位置設(shè)置斷點(diǎn),可以把優(yōu)化選項設(shè)置到0位置,就是程序不進(jìn)行優(yōu)化。如圖4-8所示。
圖4-8 優(yōu)化選項設(shè)置
這節(jié)課我們重點(diǎn)是看看C語言代碼的運(yùn)行時間,在最左側(cè)的register那個框內(nèi),有一個sec選項,這個選項就是單片機(jī)運(yùn)行時間的統(tǒng)計選項,大家點(diǎn)一下復(fù)位按鈕,會發(fā)現(xiàn)這個sec變成了0,然后我們在LED = 0; 這一句加一個斷點(diǎn),在LED = 1;這個位置加一個斷點(diǎn),我們點(diǎn)擊全速運(yùn)行按鈕,會直接停留在LED = 0;我們會看到我們的時間變化成0.000197秒,如圖4-9所示。

圖4-9 斷點(diǎn)設(shè)置
我們再點(diǎn)一下全速運(yùn)行,會發(fā)現(xiàn)sec變成了0.07530650秒,那么這樣一個for循環(huán)的時間大概有75ms左右,我們也可以通過改變30000這個數(shù)字來改變這個間隔時間。當(dāng)然了,大家要注意i的變量范圍,你如果寫成了大于65535的值以后,程序就會一直運(yùn)行不下去了,因為i無論如何變化,都不會大于這個值,如果要大于這個值正常運(yùn)行,必須改變i定義的類型了。后邊如果我們要求看一段程序運(yùn)行多長時間,都可以通過這種方式來看。
1.7 流水燈程序
我們前邊學(xué)了點(diǎn)亮LED小燈,然后又學(xué)了LED小燈閃爍,下邊我們要進(jìn)一步了解一下如何讓8個小燈依次一個一個點(diǎn)亮,流動起來。
圖4-10 LED小燈電路圖
通過前面的課程,我們可以了解到控制引腳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,所以點(diǎn)亮LED小燈的程序,實(shí)際上我們可以改成另外一種寫法,如下所示。
#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)行依次亮和滅,怎么辦呢?從這里就可以得到方法了,如果想讓單片機(jī)流水燈流動起來,依次要實(shí)現(xiàn)的結(jié)果是:0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F。
在我們的C語言當(dāng)中,有一個移位操作,其中<<代表的是左移,>>代表的是右移。比如a = 0x01 << 1就是a 的結(jié)果等于0x01左移一位。大家注意,移位都是指二進(jìn)制移位,那么移位完了,本來在第0位的1移動到了第一位上,移動完了低位是補(bǔ)0的。所以a的值最終是等于0x02。
還要學(xué)習(xí)另外一個運(yùn)算符~,這個符號是按位取反的意思,同樣,按位取反也是針對二進(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ì)講解。
1.8 作業(yè)
1、熟練掌握二進(jìn)制、十進(jìn)制和十六進(jìn)制的轉(zhuǎn)換方法。
2、掌握C語言變量類型和范圍,for、while等基本語句的用法。
3、了解函數(shù)的基本結(jié)構(gòu),能夠獨(dú)立進(jìn)入程序Debug,多多動手操作,熟練Keil軟件環(huán)境的一些基本操作。
4、將流水燈左移理解透徹后,獨(dú)立完成流水燈右移操作以及流水燈先左移后右移等簡單的花樣操作。
上一課:第三章 硬件基礎(chǔ)知識學(xué)習(xí)
下一課:第五章 定時器和數(shù)碼管
|