C語言,沒接觸過計算機編程語言的人會把它看的很神秘,感覺非常的難,而在我看來,C語言的邏輯和運算,就是小學水平,所以大家不要怕它,我盡可能的從小學數學邏輯方式帶著大家學習C語言。
1.1 二進制、十進制和十六進制
看似很簡單的東西,但是從同學們學習視頻的情況來看,很多同學不能徹底明白。這里先簡單介紹一些注意事項,然后還是從實驗中講解會比較深刻。
1、十進制就不多說了,逢十進位,一個位有十個值:0~9,我們的生活中到處都是它的身影。二進制就是逢二進位,它的一個位只有兩個值:0和1,但它卻是實現計算機系統的最基本的理論基礎,計算機(包括單片機)芯片是基于成萬上億個的開關管組合而成的,他們每一個都只能有開和關兩種狀態,再難找出第三個狀態了(不要辯解半開半關這個狀態,它是不穩定態,是極力避免的),所以他們只能對應于二進制的1和0兩個值,而沒有2、3、4……,理解二進制對于理解計算機的本質很有幫助。書寫二進制數據時需加前綴0b,每一位的值只能是0或1。十六進制就是把4個二進制位組合為一位來表示,于是它的每一位有0b0000~0b1111共16個值,用0~9再加上A~F(或a~f)表示,那么它自然就是逢十六進位了,它本質上同二進制是一樣的,是二進制的一種縮寫形式,也是我們程序編寫中常用的形式。書寫十六進制數據時需加前綴0x,下表是三種進制之間的對應關系。
十進制
|
二進制
|
十六進制
|
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 進制轉換
2、對于二進制來說,8位二進制我們稱之為一個字節,二進制的表達范圍值是從0b00000000~0b11111111,而我們程序中用十六進制表示的時候就是從0x00到0xFF,這里教大家一個二進制轉換十進制和十六進制的方法,二進制4位一組,遵循8,4,2,1的規律比如 1010,那么從最高位開始算,數字大小是8*1+4*0+2*1+1*0 = 10,那么十進制就是10,十六進制就是0xA。尤其二進制轉十六進制的時候,十六進制一位剛好是和二進制的4位相互對應的,這些大家不需要強行記憶,用幾次就熟練了。
3、對于進制來說,只是數據的表現形式,而數據的大小不會因為進制表現形式不同而不同,比如二進制的0b1、十進制的1、十六進制的0x01,他們本質上數值大小相等的同一個數據。我們在進行C語言編程的時候,我們只寫十進制和十六進制,那么不帶0x的就是十進制,帶了0x符號的就是十六進制。
1.2 C語言變量類型和范圍
什么是變量?變量自然和常量是相對的。常量比如是1、2、3......等固定的數字,而變量,和我們小學學的x是一個概念,我們可以讓它是1,也可以讓它是2,我們想讓它是幾是我們程序說了算的。
那么我們小學學的數學里邊,有這么幾類,正數、負數、整數和小數。在C語言里,名字和我們數學里學的不一樣外,還對數據大小進行了限制。這個地方有一點復雜的是,在C51里邊的數據范圍和其他編程環境還不完全一樣,因此我們下邊的這個圖,僅僅代表的是C51,其他編程環境可能不一樣,大家知道有這回事就可以了。C語言的關鍵字較多語法有些嚴謹我們不必死記硬背只需理解了,用的時候再打開http://www.zg4o1577.cn/mcuteach/150.html 查閱即可.
C語言的數據基本類型分為整型、字符型以及浮點型,如圖4-1
圖4-1 C語言數據類型
圖4-1中,三種基本類型,每個基本類型又包含了兩個類型。其中字符型和整型,除了有一定的數據大小范圍之外,只能表達整數。而unsigned型的又只能表達正數,要表達負數必須用signed型,編寫程序的時候,signed可以省略。表達小數,必須用浮點型。
比如上節課最后給的閃爍小燈的程序,我們用的是unsigned int i = 0;這個地方i的范圍就是0~65535,我們for語句的寫法,如果那個30000改成70000的話,for(i=0;i<70000;i++);大家會發現小燈會一直亮,而不是閃爍了,那理解這個問題,當然我們要來了解for語句的用法了。
這里有一個編程宗旨,就是能用小不用大。就是說定義能用1個字題的,就不定義成int,一方面節省RAM空間可以讓其他變量或者中間運算過程使用,另外一方面,占空間小程序運算速度也快一些。
1.3 for循環語句
for語句是我們今后編程的一個常用的語句,這個語句必須得學會其用法,他不僅僅可以用來做延時,還可以用來做一些循環運算。for語句的一般形式如下:
for(表達式1; 表達式2; 表達式3)
(需要執行的語句);
其執行過程是:表達式1首先執行且只執行一次;然后執行表達式2,通常都是一個用于判定條件的表達式,如果表達式2條件成立,就執行(需要執行的語句);然后再執行表達式3;再判斷表達式2,再執行表達式3.....一直到表達式2不成立時,跳出循環往下執行。舉個例子:
for(i = 0; i<2; i++)
j++;
這里有一個符號++,這個符號表示加1的意思。假如j最開始初值是0,首先執行表達式1的i=0,然后判斷i小于2這個條件成立,就執行一次j++,j的值就是1了,然后經過表達式3后,i的值也變成1了,再判斷條件2,還是符合,j再加一次,j變成2了,表達式3后i也變成2了,再判斷條件2,發現2<2這個條件不成立了,所以就不會再執行j++這個語句了。所以執行完畢后,j的值就是2。
for語句除了這種標準用法,還有幾種特殊用法,我們上節課的閃爍小燈對for語句的用法for(i=0; i<30000; i++) ;我們沒有加(需要執行的語句),沒有加的話,就是什么都不操作。但是什么都不操作的話,我們這個for語句循環判斷了30000次,程序執行是會用掉時間的,所以就起到了延時的作用。比如我們把30000改成20000,會發現燈的閃爍速度加快了,因為我們延時時間短了,當然,我們該成40000后會發現,閃爍慢了。但是有一點特別注意,C語言的延時時間是不能通過程序看出來的,也不會成比例,比如假如我們這個for循環里邊的表達式2使用30000的時候延時3秒的話,那么延時40000的時候,可能不會是4秒,那如何看實際延時時間呢,一會我再教大家。
還有一種寫法for( ; ; ),這樣寫后,這個for循環就變成了死循環了,就不停的執行(需要執行的語句),和我們前邊講的while(1)的意思是一樣的。那while這個語法是如何用的呢?
1.4 while循環語句
在我們單片機C語言編程的時候,每個程序我們都會固定的加一句while(1),這條語句就可以起到死循環的作用。對于while語句來說,他的一般形式是:
While (表達式)
{
循環體語句;
}
在C語言里,通常表達式符合條件,我們叫做真,不符合條件,叫做假。比如前邊i<30000,當i等于0的時候,那這個條件成立,就是真,如果i大于30000的時候,條件不成立,叫做假。
while(表達式)這個括號里的表達式,為真的時候,就會執行循環體語句,當為假的時候,就不執行。在這里先不舉例,后邊遇到時再詳細說明。
還有另外一種情況,就是我們C語言里邊,除了表達式外,還有常數,習慣上,我們非0的常數都認為是真,只有0認為是假,所以我們程序中加了while(1),這個數字1,可以改成2,3,4......等等都可以,都是一個死循環,不停的執行循環體的語句,但是如果把這個數字改成0,那么就不會執行循環體的語句了。
1.5 函數的簡單介紹
函數定義的一般形式如下:
函數值類型 函數名 (形式參數列表)
{
函數體
}
1、函數值類型,就是函數返回值的類型。在我們后邊程序使用中,會有很多函數中有return x這個東西,這個返回值也就是函數本身的類型。還有一種情況,就是這個函數只執行操作,不需要返回任何值,那么這個時候它的類型就是空類型void,這個void按道理來說是可以省略的,但是一旦省略,Keil軟件會報一個警告,所以我們通常也不省。
2、函數名。可以是任何合法的標示符,但是不能與其他函數或者變量重名,也不能是關鍵字。什么是關鍵字,后邊我們慢慢接觸,比如char這類,都是關鍵字,是我們程序中具備特殊功能的標志符,這種東西不可以命名函數。
3、形式參數列表,我們也叫做形參,這個是函數調用的時候,相互傳遞數據用的。有的函數,我們不需要傳遞參數,那么可以用void來替代,void同樣可以省略,但是那個括號是不能省略的。
4、函數體。函數體包含了聲明語句部分和執行語句部分。聲明語句部分主要用于聲明函數內部所使用的變量,執行語句部分主要是一些函數需要執行的語句。特別注意,所有的聲明語句部分必須放在執行語句之前,否則編譯的時候會報錯。
5、一個工程文件必須有且僅能有一個main函數,程序執行的時候,都是從main函數開始的。
6、關于形參和實參的概念,我們后邊再總結,如果遇到程序里有,大家再跟著抄一段時間。先用,后講解,這樣更有利于理解。
我們再來回顧一下我們上節課閃爍LED程序部分
void main() //void即函數類型
{
unsigned int i = 0; //定義一個無符號整數i,變量范圍是0~65535
//并且賦一個初值0
ENLED = 0; //先定義變量i,后寫執行部分
ADDR0 = 0;
ADDR1 = 1;
ADDR2 = 1;
ADDR3 = 1; //74HC138開啟三極管
while(1) //程序死循環
{
LED = 0; //點亮小燈
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語言的時間都是不能通過程序看出來的。
精確延時有兩個方法,一個方法是用定時器來延時,這個方法我們后邊課程要詳細介紹,定時器是單片機的一個重點。另外一個就是用庫函數_nop_();,一個NOP的時間是一個機器周期的時間,這個后邊也要介紹。
非精確延時,只是在我們做一些簡單的比如小燈閃爍,流水燈等簡單實驗中使用,而實際做實際開發程序中其實這種非精確延時用的極少,這里我們只是做演示功能使用。
好了,介紹完了,我們就要實戰了。上節課的LED小燈閃爍的程序,我們用的延時方式是for(i=0;i<30000;i++);大家如果把這里的i改成100,下載進入單片機,會發現小燈一直亮,而不是閃爍狀態,現在大家都把這個程序改一下,都改成100,然后下載觀察一下現象再繼續。
觀察完了,毫無疑問,實際現象和我提到的理論是相符合的,這是為什么呢?這里介紹一個常識。我們人的肉眼對閃爍的光線有一個最低分辨能力,通常情況下當閃爍的頻率高于50Hz時,我們看到的信號就是常亮的。即,延時的時間低于20ms的時候,我們的肉眼是分辨不出來小燈是在閃爍的,可能最多看到的是小燈亮暗稍微變化了一下。要想清楚的看到小燈閃爍,延時的值必須大一點,大到什么程度呢,不同的亮度的燈不完全一樣,大家可以自己做實驗。
那么如何觀察延時有多長時間呢?大家鼠標點Keil的Project-->Options for Target ‘Target1’,或點Target1右側圖標 ,進入設置選項,如圖4-3所示
圖4-3 Options for Target
首先我們打開Target這個選項卡,找到里邊的Xtal(MHz)這個位置,這是填寫我們進行模擬時間的晶振選項,從我們原理圖以及板子上都可以看到,我們單片機所配的晶振是11.0592MHz,所以這個地方我們要填上11.0592。然后找到Debug這個選項,選擇左側的Use Simulator,然后點擊最下邊的OK就可以了,如圖4-4所示。
圖4-4 Debug配置信息
點擊Debug菜單里的Start/Stop Debug Session,或者鼠標點做左側的這個Debug圖標 ,會進入一個新的頁面,如圖4-5所示。
圖4-5 Debug窗口顯示
最左側那一欄是單片機的一些寄存器和系統信息,最上邊那一欄是Keil將C語言轉換成匯編的代碼,下邊就是我們C語言的程序,還有各種窗口都可以打開,在view菜單可以打開或者關閉我們的各種窗口。這節課我們只關心我們需要的窗口,其他窗口用到再說。那么有時候我們覺得這種分布不是特別的好,所以我們想改變一下窗口分布怎么辦呢?比如Disassembly(匯編)窗口,我們先用鼠標拖動它,然后中間會出現一個方向符號,再用鼠標點那個方向符號,他就給我們分布了,如圖4-6所示。

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

圖4-9 斷點設置
我們再點一下全速運行,會發現sec變成了0.07530650秒,那么這樣一個for循環的時間大概有75ms左右,我們也可以通過改變30000這個數字來改變這個間隔時間。當然了,大家要注意i的變量范圍,你如果寫成了大于65535的值以后,程序就會一直運行不下去了,因為i無論如何變化,都不會大于這個值,如果要大于這個值正常運行,必須改變i定義的類型了。后邊如果我們要求看一段程序運行多長時間,都可以通過這種方式來看。
1.7 流水燈程序
我們前邊學了點亮LED小燈,然后又學了LED小燈閃爍,下邊我們要進一步了解一下如何讓8個小燈依次一個一個點亮,流動起來。
圖4-10 LED小燈電路圖
通過前面的課程,我們可以了解到控制引腳P0.0通過了74HC245控制DB0,P0.1控制DB1......P0.7控制DB7。我們還學到一個字節是8位,我們如果寫一個P0,就代表了P0.0到P0.7的共8個位。比如我們寫P0 = 0xFE;轉換成二進制就是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小燈的亮和滅。我們下邊要進行依次亮和滅,怎么辦呢?從這里就可以得到方法了,如果想讓單片機流水燈流動起來,依次要實現的結果是:0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F。
在我們的C語言當中,有一個移位操作,其中<<代表的是左移,>>代表的是右移。比如a = 0x01 << 1就是a 的結果等于0x01左移一位。大家注意,移位都是指二進制移位,那么移位完了,本來在第0位的1移動到了第一位上,移動完了低位是補0的。所以a的值最終是等于0x02。
還要學習另外一個運算符~,這個符號是按位取反的意思,同樣,按位取反也是針對二進制而言。比如a = ~(0x01),0x01的二進制是0b00000001,按位取反是0b11111110,那么a的值就是0xFE了。
學會了這兩個符號后,那么我們就可以把流水燈的程序寫出來,先把程序貼上。
#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) //程序死循環
{
P0 = ~(0x01 << j++); //P0等于1左移j位,并且j++
for(i=0; i<20000; i++); //延時
if(j == 8) //如果j等于8,重新給j賦值0
{
j = 0;
}
}
}
這里我只講兩種情況,當j等于0的時候,1左移0位還是1,那么寫成二進制后就是0b00000001,對這個數字按位取反就是0b11111110,亮的是最右邊的小燈。當j等于7的時候,1左移7位就是0b10000000,按位取反0b01111111,亮的是最左邊的小燈。中間過程大家自己分析一下。
流水燈結束后,關于小燈的講解,我們暫時告一段落,后邊還有小燈的高級用法,我們到時候再詳細講解。
1.8 作業
1、熟練掌握二進制、十進制和十六進制的轉換方法。
2、掌握C語言變量類型和范圍,for、while等基本語句的用法。
3、了解函數的基本結構,能夠獨立進入程序Debug,多多動手操作,熟練Keil軟件環境的一些基本操作。
4、將流水燈左移理解透徹后,獨立完成流水燈右移操作以及流水燈先左移后右移等簡單的花樣操作。
上一課:第三章 硬件基礎知識學習
下一課:第五章 定時器和數碼管
|