51單片機(jī)基礎(chǔ)剖析(基于C語言)
在編寫應(yīng)用程序時(shí),定義一個(gè)變量,一個(gè)數(shù)組,或是說一個(gè)固定表格,到底存儲(chǔ)在什么地方;當(dāng)定義變量大小超過MCU的內(nèi)存范圍時(shí)怎么辦;如何控制變量定義不超過存儲(chǔ)范圍;
本文引用地址:http://m.butianyuan.cn/article/201611/318636.htm以及如何定義變量才能使得變量訪問速度最快,寫出的程序運(yùn)行效率最高。以下將一一解答。
1.六類存儲(chǔ)類型 code data idata xdata pdata bdata
code:程序存儲(chǔ)器,也即只讀存儲(chǔ)器,用來保存常量或是程序,采用16位地址線編碼,可以是在片內(nèi),或是片外,大小被限制在64KB。
作用:定義常量,如八段數(shù)碼表或是編程使用的常,在定義時(shí)加上code或明確指明定義的常量保存到code memory(只讀。)比如:
char code table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
此關(guān)鍵字的使用方法等同于const。
data:數(shù)據(jù)存儲(chǔ)區(qū),只能用于聲明變量,不能用來聲明函數(shù),該區(qū)域位于片內(nèi),采用8位地址線編碼,具有最快的存儲(chǔ)速度,但是數(shù)量被限制在128byte或更少。
使用方法:unsigned char data fast_variable=0;
Idata:數(shù)據(jù)存儲(chǔ)區(qū),只能用于聲明變量,不能用來聲明函數(shù)。該區(qū)域位于片內(nèi),采用8位地址線編碼,內(nèi)存大小被限制在256byte或更少。該區(qū)域的低地址區(qū)與data區(qū)地址一致,高地址區(qū)域是52系列在51系列基礎(chǔ)上擴(kuò)展的并與特殊功能寄存器具有相同地址編碼的區(qū)域。即:data memory是idata memory的一個(gè)子集。
xdata:只能用于聲明變量,不能用來聲明函數(shù),該區(qū)域位于MCU外部,采用16位地址線進(jìn)行編碼,存儲(chǔ)大小被限制在64KB以內(nèi)。如:unsigned char xdata count=0;
pdata:只能用于聲明變量,不能用來聲明函數(shù),該區(qū)域位于MCU外部,采用8位地址線進(jìn)行編碼。存儲(chǔ)大小限制在256byte,是xdata memory的低256byte。為其子集。如:unsigned char pdata count=0;
bdata:只能用于聲明變量,不能用來聲明函數(shù)。該區(qū)域位于8051內(nèi)部位數(shù)據(jù)地址。定義的量保存在內(nèi)部位地址空間,可用位指令直接讀寫。使用方法:unsigned char bdata varab=0。
注:一般情況下,定義字符型變量時(shí),在缺省unsigned的情況下,默認(rèn)為無符號(hào)。但是本人在Keil uV3中遇到并非如此的案例。在缺省的情況下默認(rèn)為有符號(hào)。要注意一下,或許不同的編譯器規(guī)則不同。所以我們?cè)趯懗绦虻臅r(shí)候,還是最好把unsigned signed加上。
2.函數(shù)的參數(shù)和局部變量的存儲(chǔ)模式
C51 編譯器允許采用三種存儲(chǔ)器模式:SMALL,COMPACT 和LARGE。一個(gè)函數(shù)的存儲(chǔ)器模式確定了函數(shù)的參數(shù)的局部變量在內(nèi)存中的地址空間。處于SMALL模式下的函數(shù)參數(shù)和局部變量位于8051單片機(jī)內(nèi)部RAM中,處于COMPACT和LARGE模式下的函數(shù)參數(shù)和局部變量則使用單片機(jī)外部RAM。在定義一個(gè)函數(shù)時(shí)可以明確指定該函數(shù)的存儲(chǔ)器模式。方法是在形參表列的后面加上一存儲(chǔ)模式。
示例如下:
#pragma large //此預(yù)編譯必須放在所有頭文前面
int func0(char x,y) small;
char func1(int x) large;
int func2(char x);
注:上面例子在第一行用了一個(gè)預(yù)編譯命令#pragma,它的意思是告訴c51編譯器在對(duì)程序進(jìn)行編譯時(shí),按該預(yù)編譯命令后面給出的編譯控制指令LARGE進(jìn)行編譯,即本例程序編譯時(shí)的默認(rèn)存儲(chǔ)模式為LARGE。隨后定義了三個(gè)函數(shù),第一個(gè)定義為SMALL存儲(chǔ)模式,第二個(gè)函數(shù)定義為LARGE第三個(gè)函數(shù)未指定,在用C51進(jìn)行編譯時(shí),只有最后一個(gè)函數(shù)按LARGE存儲(chǔ)器模式處理,其它則分別按它們各自指定的存儲(chǔ)器模式處理。
本例說明,C51編譯器允許采用所謂的存儲(chǔ)器混合模式,即允許在一個(gè)程序中將一些函數(shù)使用一種存儲(chǔ)模式,而其它一些則按另一種存儲(chǔ)器模式,采用存儲(chǔ)器混合模式編程,可以充分利用8051系列單片機(jī)中有限的存儲(chǔ)器空間,同時(shí)還可以加快程序的執(zhí)行速度。
3.絕對(duì)地址訪問(頭文件為:absacc.h(相當(dāng)重要))
#define CBYTE ((unsigned char volatile code *) 0)
#define DBYTE ((unsigned char volatile data *) 0)
#define PBYTE ((unsigned char volatile pdata *) 0)
#define XBYTE ((unsigned char volatile xdata *) 0)
功能:CBYTE尋址CODE區(qū)
DBYTE尋址DATA區(qū)
PBYTE尋址XDATA(低256)區(qū)
XBYTE尋址XDATA區(qū)
例:如下指令在對(duì)外部存儲(chǔ)器區(qū)域訪問地址0x1000
xvar=XBYTE[0x1000];
XBYTE[0x1000]=20;
#define CWORD ((unsigned int volatile code *) 0)
#define DWORD ((unsigned int volatile data *) 0)
#define PWORD ((unsigned int volatile pdata *) 0)
#define XWORD ((unsigned int volatile xdata *) 0)
功能:與前面的一個(gè)宏相似,只是它們指定的數(shù)據(jù)類型為unsigned int。
通過靈活運(yùn)用不同的數(shù)據(jù)類型,所有的8051地址空間都是可以進(jìn)行訪問。例如:
DWORD[0x0004]=0x12F8;// 即內(nèi)部數(shù)據(jù)存儲(chǔ)器中(0x08)=0x12; (0x09)=0xF8
注:用以上八個(gè)函數(shù),可以完成對(duì)單片機(jī)內(nèi)部任意ROM和RAM進(jìn)行訪問,非常方便。還有一種方法,那就是用指鐘,后面會(huì)對(duì)C51的指針有詳細(xì)的介紹。
4.寄存器變量(register)
為了提高程序的執(zhí)行效率,C語言允許將一些頻率最高的那些變量,定義為能夠直接使用硬件寄存器的所謂的寄存器變量。定義一個(gè)變量時(shí),在變量類型名前冠以“register” 即將該變量定義成為了寄存器變量。寄存器變量可以認(rèn)為是一自動(dòng)變量的一種。有效作用范圍也自動(dòng)變量相同。由于計(jì)算機(jī)寄存器中寄存器是有限的。不能將所有變量都定義成為寄存器變量,通常在程序中定義寄存器變量時(shí),只是給編譯器一個(gè)建議,該變量是否真正成為寄存器變量,要由編譯器根據(jù)實(shí)際情況來確定。另一方面,C51編譯器能夠識(shí)別程序中使用頻率最高的變量,在可能的情況下,即使程序中并未將該變量定義為寄存器變量,編譯器也會(huì)自動(dòng)將其作為寄存器變量處理。被定義的變量是否真正能成為寄存器變量,最終是由編譯器決定的。
5.內(nèi)存訪問的實(shí)現(xiàn)
(1)指鐘
指鐘本身是一個(gè)變量,其中存放的內(nèi)容是變量的地址,也即特定的數(shù)據(jù)。8051的地址是16位的,所以指針變量本身占用兩個(gè)存儲(chǔ)單元。指針的說明與變量的說明類似,僅在指針名前加上“*”即可。
如: int *int_point; //聲明一個(gè)整型指針
char *char_point; //聲明一個(gè)字符型指針
利用指針可以間接存取變量。實(shí)現(xiàn)這一點(diǎn)要用到兩個(gè)特殊運(yùn)算符
& 取變量地址
* 取指針指向單元的數(shù)據(jù)
示例一:
int a=15,b;
int *int_point; //定義一個(gè)指向整型變量的指針
int_point=&a; //int_point指向 a
*int_point=5; //給int_point指向的變量a 賦值5 等同于a=5;
示例二:
char i,table[6],*char_point;
char_point=table;
for(i=0;i<6;i++)
{
char_point=i;
char_point++;
}
注:指針可以進(jìn)行運(yùn)算,它可以與整數(shù)進(jìn)行加減運(yùn)算(移動(dòng)指針)。但要注意,移動(dòng)指針后,其地址的增減量是隨指針類型而異的,如,浮點(diǎn)指針進(jìn)行自增后,其內(nèi)部將在原有的基礎(chǔ)上加4,而字符指針當(dāng)進(jìn)生自增的時(shí)候,其內(nèi)容將加1。原因是浮點(diǎn)數(shù),占4個(gè)內(nèi)存單元,而字符占一個(gè)字節(jié)。
宏晶科技最新一代STC12C5A360S2系列,每一個(gè)單片機(jī)出廠時(shí)都有全球唯一身份證號(hào)碼(ID號(hào)),用戶可以在單片機(jī)上電后讀取內(nèi)部RAM單元F1H~F7H的數(shù)值,來獲取此單片機(jī)的唯一身份證號(hào)碼。使用MOV @Ri指令來讀取。下面介紹C51獲取方法:
char id[7]={0};
char i;
char idata *point;
for(i=0;i<7;i++)
{
id[i]=*point;
point++;
}
(此處只是對(duì)指針做一個(gè)小的介紹,達(dá)到訪問內(nèi)部任何空間的方式,后述有對(duì)指針使用的詳細(xì)介紹)
(2)對(duì)SFR,RAM ,ROM的直接存取
C51提供了一組可以直接對(duì)其操作的擴(kuò)展函數(shù)
若源程序中,用#include包含頭文件,io51.h 后,就可以在擴(kuò)展函數(shù)中使用特殊功能寄存器的地址名,以增強(qiáng)程序的可讀性:
注 此方法對(duì)SFR,RAM,ROM的直接存取不建議使用.因?yàn)?淡io51.h這個(gè)頭文件在KEIL中無法打開,可用指針,或是采用absacc.h頭文件,
(3) PWM與PCA
STC12系列有兩路PWM/PCA
PWM:(Pulse Width Modulation)脈寬調(diào)制,是一種使用程序來控制波形占空比,周期,相位波形的技術(shù)。
PCA:(Programmable Counter Array)可編程計(jì)數(shù)陣列,它比通常的定時(shí)/計(jì)數(shù)器的定時(shí)能力強(qiáng),需要CPU的干預(yù)少。其優(yōu)勢一是軟件簡單,二是精度大有提高。
*6.動(dòng)態(tài)內(nèi)存分配的實(shí)現(xiàn)
在單片機(jī)的實(shí)際開發(fā)中,很多情況下我么需要開辟一塊內(nèi)存,但是具體開辟多大,也就是內(nèi)存的字節(jié)數(shù)我們還無法確定,比如可能要等到上位機(jī)的指令發(fā)送下來才能確定,這個(gè)時(shí)候我們就得動(dòng)態(tài)分配內(nèi)存。注意,單片機(jī)內(nèi)部存儲(chǔ)資源是極其有限的,不允許開發(fā)人員開辟出一塊很大的存儲(chǔ)區(qū)來備用。在VC 6.0環(huán)境下很容易用malloc()來得到一塊RAM,但是由于單片機(jī)內(nèi)部沒有操作系統(tǒng)(如何在51上跑uC/OS-II我以后會(huì)寫出來),所以在51上實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存分配就是個(gè)難點(diǎn)也是一個(gè)重點(diǎn)問題。下面給出代碼,詳細(xì)分析大家可以參考求是科技編的《8051系列單片機(jī)C程序設(shè)計(jì)完全手冊(cè)》這本書。
#include
#include
……
void main (void)
{
char *ptr1;
init_mempool (0x1000,0x500); //內(nèi)存池初始化,0x1000為起始地址,0x500為內(nèi)存大小
ptr1=malloc(30); /*動(dòng)態(tài)為指針變量分配長度為30字節(jié)的存儲(chǔ)空間*/
……
//此處為你的代碼
……
free(ptr1) ; //注意,動(dòng)態(tài)內(nèi)存用完之后務(wù)必要釋放,否則程序?qū)?huì)出錯(cuò)
while (1);
}
二、變量類型及其作用域剖析
變量可分為 1.局部變量;2.全局變量(按變量的有效作用范圍劃分)
1.局部變量
是指函數(shù)內(nèi)部(包括main函數(shù))定義的變量,僅在定義它的那個(gè)函數(shù)范圍內(nèi)有效,不同函數(shù)可使用相同的局部變量名,函數(shù)的形式參數(shù)也屬于局部變量,在一個(gè)函數(shù)的內(nèi)部復(fù)合語句中也可以定義局部變量,該局部變量只在該復(fù)合語合中有效。
2.全局變量
是指函數(shù)外部定義的變量,以稱外部變量??蔀槎鄠€(gè)函數(shù)共同使用,其有效作用范圍是從它定義開始到整個(gè)程序文件結(jié)束。如果全局變量,定義在一個(gè)程序文件的開始處,則在整個(gè)程序文件范圍都可以使用它,如果一個(gè)全局變量不是在程序文件的開始處定義,但又希望在它定義之前的函數(shù)中引用該變量,這時(shí)應(yīng)在引用該變量的函數(shù)中用關(guān)鍵字extern將其聲明為“外部變量”。另個(gè),如果在一個(gè)程序模塊文件中引用另一個(gè)程序模塊文件中定義的變量時(shí),也必須用extern進(jìn)行說明。
外部變量的說明與外部變量的定義是不同的,外部變量定義只能有一次,定義的位置在所有函數(shù)之外,而同一個(gè)程序文件中(不是指模塊文件)的外部變量聲明可以有多次,聲明的置在需要引用該變量的函數(shù)之內(nèi),外部變量的聲明的作用只是聲明該變量是一個(gè)已經(jīng)在外部定義過了的變量而已。
如在同一個(gè)程序文件中,全局變量與局部變量同名,則在局部變量的有效作用范圍之內(nèi),全局變量不起作用,也就是說,局部變量的優(yōu)先級(jí)比全局變量高。
在編寫C語言程序時(shí),不是特別必要的地方一般不要使用全局變量,而應(yīng)當(dāng)盡可能的使用局部變量。因?yàn)榫植孔兞恐辉谑褂盟臅r(shí)候,才為其分配內(nèi)存單元,而全局變量在整個(gè)程序的執(zhí)行過程中都要占用內(nèi)存單元,且當(dāng)全局變量使用過多時(shí),會(huì)降低程序的可讀性。
變量的存儲(chǔ)種類
(1).自動(dòng)變量(auto)
定義變量時(shí),在變量類型名前加上 “auto” ,自動(dòng)變量是C語言中使用最為廣泛的一類變量,在函數(shù)體內(nèi)部或是復(fù)合語句內(nèi)部定義的變量,如果省略了存儲(chǔ)種類說明,則該變量默認(rèn)為自動(dòng)變量。
例如:
{ 等價(jià)于 {
char x; auto char x;
int y; auto int y;
…… ……
} }
注:自動(dòng)變量的作用范圍在定義它的函數(shù)體或是復(fù)合語句內(nèi)部,只有在定義它的函數(shù)內(nèi)被調(diào)用,或是定義它的復(fù)合語句被執(zhí)行時(shí),編譯器才會(huì)為其分配內(nèi)存空間,開始其生存期。當(dāng)函數(shù)調(diào)用結(jié)束返回,或復(fù)合語句執(zhí)行結(jié)束,自動(dòng)變量所占用的內(nèi)存空間就被釋放,變量的值當(dāng)然也就不復(fù)存在,其生存期結(jié)束。當(dāng)函數(shù)再次調(diào)用,或是復(fù)合語句被再次執(zhí)行時(shí),編譯器又會(huì)為其內(nèi)部的自動(dòng)變量重新分配內(nèi)存空間。但不會(huì)保留上一次運(yùn)行的值。而必須被重新分配。因此自動(dòng)變量始終是相對(duì)于函數(shù)或復(fù)合語句的局部變量。
(2).外部變量(extern)
用說明符“extern”定義的變量稱為外部變量。按缺省規(guī)則,凡是在所有函數(shù)之前,在函數(shù)外部定義的變量都是外部變量,定義時(shí)可以不寫extern說明符,但是一個(gè)函數(shù)體內(nèi)說明一個(gè)已在該函數(shù)體外或別的程序模塊文件中定義過的外部變量時(shí),剛必須要使用extern說明符。外部變量定義后,它就被分配了固定的內(nèi)存空間。外部變量的生存期為程序的整個(gè)執(zhí)行時(shí)間。 外部變量的存儲(chǔ)不會(huì)隨函數(shù)或復(fù)合語句執(zhí)行完畢而釋放,因此外部變量屬于全局變量。
C語言允許將大型程序分解為若干個(gè)獨(dú)立的程序模塊文件,各個(gè)模塊可分別進(jìn)行編譯,然后再將它們連接在一起,如果某個(gè)變量需要在所有程序模塊文件中使用,只要在一個(gè)程序模塊文件中將該變量定義成全局變量,而在其它程序模塊文件中用extern聲明該變量是已被定義過的外部變量就可以了。
函數(shù)是可以相互調(diào)用的,定義函數(shù)時(shí),如果冠以關(guān)鍵字extern 即將其明確定義為一個(gè)外部函數(shù)。例如 extern int func2(char a,b) 。如果在定義函數(shù)時(shí)省略關(guān)鍵字extern,則隱含為外部函數(shù)。如果在調(diào)用一個(gè)在本程序模塊文件以外的其它模塊文件所定義的函數(shù),則必須要用關(guān)鍵字extern說明被調(diào)用的函數(shù)是一個(gè)外部函數(shù)。對(duì)于具有外部函數(shù)相互調(diào)用的多模塊程序,可用C51編譯器分別對(duì)各個(gè)模塊文件進(jìn)行編譯,最后再用L51連接定位器將它們連接成為一個(gè)完整的程序。如下為一個(gè)多模塊程序:
程序模塊1,文件名為file1.c
#include
int x=5;
void main()
{
extern void fun1( );
extern viod fun2(int y);
fun1( );
fun1( );
fun1( );
printf( “n%d %dn”,x,fun2(x));
}
程序模塊2,文件名為file2.c
#include
extern int x;
void fun1( )
{
static int a=5; //靜態(tài)變量只在第一次調(diào)用函數(shù)時(shí)賦值,退出函數(shù)時(shí)
//會(huì)保留上次的值,下次調(diào)用不再重新賦值。
int b=5;
printf(“%d %d %d |”,a,b,x);
a-=2;
b-=2
x-=2;
printf(“%d %d %d |”,a,b,x);
}
int fun2(int y)
{
return(35*x*y);
}
程序執(zhí)行如果如下:
5 5 5 | 3 3 3
3 5 3 | 1 3 1
1 5 1 | -1 3 1
-1 35
注:C語言不允許在一個(gè)函數(shù)內(nèi)嵌套定義另一個(gè)函數(shù)。為了能夠訪問不同文件中各個(gè)函數(shù)的變量,除了可以采用參數(shù)傳遞的方法外,還可以采用外部變量的方法,上面的例子就說了這一點(diǎn)。不過,盡管使用外部變量在不同函數(shù)之間傳遞數(shù)據(jù)有時(shí)比使用函數(shù)參數(shù)傳遞更為方便,不過當(dāng)外部變量過多時(shí),會(huì)增加程序的調(diào)試排錯(cuò)的困難。使得程序不便于維護(hù)。別外不通過參數(shù)傳遞直接在函數(shù)中改變?nèi)肿兞康闹?,有時(shí)還會(huì)發(fā)生一些意想不到的副作用。因些最好還是使用函數(shù)參數(shù)來傳遞數(shù)據(jù)。
(3).寄存器變量(register)
為了提高程序的執(zhí)行效率,C語言允許將一些頻率最高的那些變量,定義為能夠直接使用硬件寄存器的所謂的寄存器變量。定義一個(gè)變量時(shí),在變量類型名前冠以“register” 即將該變量定義成為了寄存器變量。寄存器變量可以認(rèn)為是一自動(dòng)變量的一種。有效作用范圍也自動(dòng)變量相同。由于計(jì)算機(jī)寄存器中寄存器是有限的。不能將所有變量都定義成為寄存器變量,通常在程序中定義寄存器變量時(shí),只是給編譯器一個(gè)建議,該變量是否真正成為寄存器變量,要由編譯器根據(jù)實(shí)際情況來確定。另一方面,C51編譯器能夠識(shí)別程序中使用頻率最高的變量,在可能的情況下,即使程序中并未將該變量定義為寄存器變量,編譯器也會(huì)自動(dòng)將其作為寄存器變量處理。被定義的變量是否真正能成為寄存器變量,最終是由編譯器決定的。
(4).靜態(tài)變量(static)
使用存儲(chǔ)種類說明符“static”定義的變量為靜態(tài)變量,在上面模塊2程序文件中使用了一個(gè)靜態(tài)變量:static int a =5 ;由于這個(gè)變量是在函數(shù)fun1( )內(nèi)部定義,因此稱為內(nèi)部靜態(tài)變量或局部靜態(tài)變量。局部靜態(tài)變量始終都是存在的,但只有在定義它的函數(shù)內(nèi)部進(jìn)行訪問,退出函數(shù)之后,變量的值仍然保持,但不能進(jìn)行訪問。
還有一種全局靜態(tài)變量,它是在函數(shù)外部被定義的。作用范圍從它的定義點(diǎn)開始,一直到程序結(jié)束,當(dāng)一個(gè)C語言程序由若干個(gè)模塊文件所組成時(shí),全局靜態(tài)變量始終存在,但它只能在被定義的模塊文件中訪問,其數(shù)據(jù)值可為該模塊文件內(nèi)的所有函數(shù)共享,退出該文件后,雖然變量的值仍然保持著,但不能被其它模塊文件訪問。在一個(gè)較大的程序中,這就方便了多人設(shè)計(jì)時(shí),各自寫的程序模塊不會(huì)被別的模塊文件所引用。全局靜態(tài)變量和單純的全局變量,在編譯時(shí)就已經(jīng)為期分配了固定的內(nèi)存空間,只是他們的作用范圍不同而已。局部靜態(tài)變量是一種在兩次函數(shù)調(diào)用之間仍能保持其值的局部變量。如下,局部變量的使用——計(jì)算度輸出1~5的階乘值。
#include
int fac( int n)
{
static int f=1;
f=f*n;
return(f);
}
main( )
{
int i;
for(i=1;i<=5;i++)
printf(“%d!=%dn”,i,fac(i));
}
程序執(zhí)行結(jié)果
1!=1
2!=2
3!=6
4!=24
5!=120
注:在這個(gè)程序中一共調(diào)用了5次計(jì)算階乘的函數(shù)fac(i),每次調(diào)用后輸出一個(gè)階乘值i!,同時(shí)保留了這個(gè)i!值,以便下次再乘(i+1).由此可見,如果要保留函數(shù)上一次調(diào)用結(jié)束時(shí)的值,或是在初始化之后變量只被引用而不改變其值,則這時(shí)使用局部靜態(tài)變量;較為方便,以免在每調(diào)用時(shí)都要重新進(jìn)行賦值,但是,使用局部靜態(tài)變量需要占用較多的內(nèi)存空間,而且降低了程序的可讀性,因此并不建議多用局部靜態(tài)變量。
靜態(tài)函數(shù):
對(duì)于函數(shù)也可以定義成為具為靜態(tài)存儲(chǔ)種類的屬性,定義函數(shù)時(shí)在函數(shù)名前冠以關(guān)鍵字static即將其定義為一個(gè)靜態(tài)函數(shù)。例如static int func1(char x, y)函數(shù)是外部型的,使用靜態(tài)函數(shù)可以使該函數(shù)只局限于當(dāng)前定義它的模塊文件中。其它模塊文件是不能調(diào)用它的。換名話說,就是在其它模塊文件中可以定義與靜態(tài)函數(shù)完全同名的另一個(gè)函數(shù)。不會(huì)因?yàn)槌绦蛑写嬖谙嗤暮瘮?shù)名而發(fā)生函數(shù)調(diào)用時(shí)的混亂。 這一點(diǎn)對(duì)于進(jìn)行模塊化程序設(shè)計(jì)是很有用的。
三、中斷淺談
0 | 外中斷0 |
1 | 定時(shí)器0 |
2 | 外中斷1 |
3 | 定時(shí)器1 |
4 | 串行口 |
定義中斷函數(shù)如下
void timer1() interrupt 3
{
……
……
}
強(qiáng)烈建議:如上所述,定義中斷函數(shù)時(shí)不要加using n選項(xiàng)。除非你對(duì)你的程序以及單片機(jī)的工作過程非常熟悉,否則會(huì)帶來不必要的麻煩。具體原因由于篇幅的限制暫不討論。
C51中斷程序編寫要求:
1.中斷函數(shù)不能進(jìn)行參數(shù)傳遞,否則,將導(dǎo)致編譯出錯(cuò)
2.中斷中,不能包含任何參數(shù)聲明,否則,將導(dǎo)致編譯出錯(cuò)。
3.中斷函數(shù)沒有返回值,如果企圖定義一個(gè)返回值將得到不正確的結(jié)果,因些建議在定義中斷函數(shù)的時(shí)將其定義為void 類型,明確說明沒有返回值。
4.任何情況下都不能直接調(diào)用中斷函數(shù),否則會(huì)主生編譯出錯(cuò)。
5.如果中斷函數(shù)中用到了浮點(diǎn)運(yùn)算,必須保存浮點(diǎn)寄存器的狀態(tài)。當(dāng)沒有其它的程序執(zhí)行浮點(diǎn)運(yùn)算時(shí)(即只有中斷中用到浮點(diǎn)運(yùn)算),可以不用保存。
6.如果中斷函數(shù)中調(diào)用了其它函數(shù),則被調(diào)用的函數(shù)所使用的寄存器組必須與中斷函數(shù)相同,用戶必須保證按要求使用相同的寄存器組,否則會(huì)產(chǎn)生不正確的結(jié)果,這一點(diǎn)必須引起足夠的注意,如果定義中斷函數(shù)時(shí)沒有使用using選項(xiàng),則由編譯器選擇一個(gè)寄存器組作絕對(duì)寄存器訪問。另外,不斷的產(chǎn)生不可預(yù)測,中斷函數(shù)對(duì)其它函數(shù)的調(diào)用可能形成遞規(guī)調(diào)用,需要時(shí),可將被中斷調(diào)用的其它函數(shù)定義為再入函數(shù)。
淺析函數(shù)的遞規(guī)調(diào)用與再入函數(shù):
函數(shù)的遞規(guī)調(diào)用: 在調(diào)用一個(gè)函數(shù)的過程中雙直接或間接的調(diào)用該函數(shù)本身;
再入函數(shù):一種可以在函數(shù)體內(nèi)直接或間接調(diào)用其自身的一種函數(shù)。
C51編譯器采用一個(gè)擴(kuò)展關(guān)鍵字reentrant 作為定義函數(shù)時(shí)的選項(xiàng),需要將一個(gè)函數(shù)定義為再入函數(shù)時(shí),只要在函數(shù)名后加上關(guān)鍵字reentrant即可??詹豢崭褚约翱諑赘穸紵o所謂。
再入函數(shù)剖析:
再入函數(shù)可被遞歸調(diào)用,無論何時(shí),包括中斷服務(wù)函數(shù)在內(nèi)的任何函數(shù)都可調(diào)用再入函數(shù)。與非再入函數(shù)的參數(shù)傳遞和局部就是的存儲(chǔ)分配方法不同,C51編譯器為每個(gè)再入函數(shù)都生成一個(gè)模擬棧。模擬棧所在的存儲(chǔ)器空間根據(jù)再入函數(shù)的存儲(chǔ)模式的不同,可以分配到DATA,PDATA 或XDATA。
對(duì)再入函數(shù)有如下規(guī)定:
1.再入函數(shù)不能傳送bit類型的參數(shù)。也不能定義一個(gè)局部位變量,再入函數(shù)不能包括位操作以及8051系列單片機(jī)的可位尋址區(qū)。
2.與PL/ M51兼容的函數(shù),不能具有reentrant屬性,也不能調(diào)用再入函數(shù)。
3.編譯時(shí),在存儲(chǔ)器模式的基礎(chǔ)上,為再入函數(shù)在內(nèi)部或外部存儲(chǔ)中建立一個(gè)模擬堆棧區(qū),稱為再入棧,再入函數(shù)的局部變量及參數(shù)被放在再入棧中,從而使得再入函數(shù)可以進(jìn)行遞規(guī)調(diào)用。再非再入函數(shù)的局部變量被放在再入棧之外的暫存區(qū)內(nèi),如果對(duì)非再入函數(shù)進(jìn)行遞規(guī)調(diào)用,則上次調(diào)用時(shí)使用的局部變量數(shù)據(jù)將被覆蓋。
4.在同一個(gè)程序中可以定義和使用不同存儲(chǔ)器模式的再入函數(shù),任意模式的再入函數(shù)不能調(diào)用不同模式的再入函數(shù),但可以任意調(diào)用非再入函數(shù)。
5.在參數(shù)的傳遞上,實(shí)際參數(shù),可以傳遞給間接調(diào)用的再入函數(shù),無再入屬性的間接調(diào)用函數(shù)不能包含調(diào)用參數(shù)。但是可以使用定義的全局變量來進(jìn)行參數(shù)傳遞。
四、C51指針深度剖析(非常重要,嵌入式系統(tǒng)開發(fā)人員必須要掌握的內(nèi)容)
注意:由于篇幅所限,本人暫時(shí)不打算討論抽象指針的內(nèi)容。但是你必須上網(wǎng)或去圖書館找找關(guān)于抽象指針的資料好好看看,抽象指針很有用的。
指針是C語言中的一個(gè)重要概念,使用也十分普遍,正確使用指針類型數(shù)據(jù)可以有效的表示復(fù)雜的數(shù)據(jù)結(jié)構(gòu),直接處理內(nèi)存地址,而且可以更為有效的使用數(shù)組。
在C語言中,為了能夠?qū)崿F(xiàn)直接對(duì)內(nèi)存單元的操作,引入了指針類型的數(shù)據(jù),指針類型數(shù)據(jù)是專門用來確定其它數(shù)據(jù)類型的地址的,因此一個(gè)變量的地址就被稱為該變量的指針如: 一個(gè)整形變量i 存放在內(nèi)存單元40H中,則該內(nèi)存單元地址40H就是變量i 的指針。如果有一個(gè)變量專門用來存放另一個(gè)變量的地址,則稱之為“指針變量”
變量指針與指針變量
變量的指針: 是指某個(gè)變量的地址,而一個(gè)指針變量里面存放的是另一個(gè)變量在內(nèi)存中的地址。擁有這個(gè)地址的變量則稱為該指針變量所指向的變量。 所以每個(gè)變量都有它自己的指針(地址),而每一個(gè)指針變量都是指向另一個(gè)變量的。C語言中用符號(hào)“*”來表示“指向”,如下:
i=50;
*ip=50;
如果指針ip這個(gè)指針變量指向i那么,兩個(gè)賦值表達(dá)或同義,第二個(gè)表達(dá)式可以解釋為“給指針變量ip所指向的變量賦值50”。
(1).指針變量的定義
指針變量的定義與一般變量的定義類似,其一般形式如下:
數(shù)據(jù)類型 [存儲(chǔ)器類型] * 標(biāo)識(shí)符;
標(biāo)識(shí)符, 是所定義的指針變量名
數(shù)據(jù)類型, 說明了該指針變量所指向的變量類型
存儲(chǔ)器類型,是可選的,它是C51編譯器的一種擴(kuò)展,如果帶有此選項(xiàng),指針被定義為基于存儲(chǔ)器的指針,無此選項(xiàng)時(shí),被定義為一般指針,這兩種指針的區(qū)別在于它們的存儲(chǔ)字節(jié)不同。
一般指針:占用三個(gè)字節(jié),第一個(gè)字節(jié)存放該指針存儲(chǔ)器類型的編碼,第二和第三個(gè)字節(jié)分別存放該指針的高位和低位地址的偏移量
存儲(chǔ)器類型 | IDATA | XDATA | PDATA | DATA | CODE |
編碼值 | 1 | 2 | 3 | 4 | 5 |
基于存儲(chǔ)器指針:則該指針長度可為一個(gè)字節(jié),也可為兩字節(jié)
一個(gè)字節(jié): (存儲(chǔ)器類型 idata data pdata)
兩個(gè)字節(jié): (存儲(chǔ)器類型為code xdata)
注:在定義指針變量時(shí)最好指定其為基于存儲(chǔ)器的指針,這個(gè)生成的匯編代碼長精 練一些,而且也節(jié)省空間(讀者可自行到C51中寫一個(gè)程序,查看其反匯編程序)但在一些函數(shù)調(diào)用的參數(shù)中指針需要采用一般指針,為此C51編譯器允許這兩種指針相互轉(zhuǎn)換,轉(zhuǎn)換規(guī)則如下:
一般指針轉(zhuǎn)換成基于存儲(chǔ)器指針,采取截?cái)?,基于存?chǔ)器類型指針轉(zhuǎn)換成一般指針采用擴(kuò)展的。
(2).指針變量的引用
指針變量是含有一個(gè)數(shù)據(jù)對(duì)象地址的特殊變量,指針變量中只能存放地址與指針變量有關(guān)的兩個(gè)運(yùn)算符:
& 取地址運(yùn)算符
* 間接訪問運(yùn)算符
&a為取變量a的地址,*P為指針變量P所指向的變量。如下:
int i , x, y;
int *pi,*px,*py;
pi=&i; //將變量i的地址賦給指針變量pi,也即pi指向i
px=&x;
py=&py;
*pi=0; //等價(jià)于i=0
*px+=6; //等價(jià)于 i+=6
(*py)++; //等價(jià)于 i++
注:指向同類數(shù)據(jù)的指針之間可以相互賦值。如pi=px;
(3).指針變量作為函數(shù)的參數(shù)
函數(shù)的參數(shù)不僅可以是整型,字符型等數(shù)據(jù),還可以是指針類型,指針變量作為函數(shù)的參數(shù)的作用是將一個(gè)變量的地址傳到另一個(gè)函數(shù)中去,地址傳遞是雙向的,即主調(diào)用函數(shù)不僅可以向被調(diào)用函數(shù)傳遞參數(shù),而且還可以從被調(diào)用函數(shù)返回其結(jié)果。下面通過一個(gè)簡單的示例來進(jìn)行說明。
#include
swap(int *pi,int *pj)
{
int temp;
temp=*pi;
*pi=*pj; //把指針變量pj所指向的變量的值送給pi所指向的變量
*pj=temp;
}
main( )
{
int a,b;
int *pa, *pb;
a=9;
b=7;
pa=&a;
pb=&b;
if(a
printf(“n max=%d,min=%d n”,a,b);
}
上程序上定義了一個(gè)swap( )函數(shù),兩個(gè)形參為指針變量,在調(diào)用函數(shù)時(shí),所用的實(shí)參也是指針變量,在調(diào)用開始,實(shí)參變量將它的值傳遞給形參變量,采取的仍然是“值傳遞”方式,但這時(shí)傳遞的是指針的值(地址),傳遞后,形參pi的值為&a,pj的值為&b,即指針變量*pi 和*pa都指向了a, *pj和*pb指向了b。接著使*pj與*pi的值互換,從而達(dá)到了實(shí)現(xiàn)了a,和b值的互換。雖然函數(shù)返回時(shí),pi pj被釋放而不存在,但main函數(shù)中a 與b的值已經(jīng)交換。
(4).?dāng)?shù)組的指針
在C語言中,指針與數(shù)組有著十分密切的關(guān)系,任何能夠用數(shù)組實(shí)現(xiàn)的運(yùn)算都可以通過指針來完成,例如定義一個(gè)具有十個(gè)元素的整形數(shù)據(jù)可以寫成:
int a[10];
數(shù)組名a表示元素a[0]的地址,而*a 則表示a所代表地址中的內(nèi)容,即a[0].
如果定義一個(gè)指向整形變量的指針pa并賦以數(shù)組a中的第一個(gè)元素a[0]的地址;
int *pa;
pa=&a[0]; //也可寫成pa=a;
則可通過指針pa來操作數(shù)組a了,即可用*pa代表a[0];*(pa+i)代表a[i],也可以上pa[0];pa[1];pa[2]……pa[9]的形式
(5).字符數(shù)組的指針
用指針來描述一個(gè)字符數(shù)組是十分方便的,字符串是以字符數(shù)組的形式給出的,并且每個(gè)字符數(shù)組都是以轉(zhuǎn)義字符‘