本文主要是關(guān)于單片機(jī)的相關(guān)介紹,并著重對(duì)單片機(jī)模塊化編程串口中斷的處理進(jìn)行了詳盡的闡述。
模塊化編程
單片機(jī)編程時(shí),如果代碼量不多,可以將所有的函數(shù)和定義等放在一個(gè)main.c文件中,但是隨著代碼量的增加,如果將所有代碼都放在同一個(gè).C文件中,會(huì)使得程序結(jié)構(gòu)混亂、可讀性與可移植性變差,而模塊化編程就是解決這個(gè)問題的常用而有效的方法。
模塊化設(shè)計(jì)的原則
“高內(nèi)聚,低耦合”
高內(nèi)聚:一個(gè)C文件里面的函數(shù),只有相互之間的調(diào)用,而沒有調(diào)用其它文件里面的函數(shù),這樣可以視為高內(nèi)聚。盡量減小不同文件里函數(shù)的交叉引用。
低耦合:一個(gè)完整的系統(tǒng),模塊與模塊之間,盡可能的使其獨(dú)立存在。也就是說,讓每一個(gè)模塊,盡可能的獨(dú)立完成某個(gè)特定的子功能。模塊與模塊之間的接口,盡量的少而簡(jiǎn)單。
模塊化編程的方法
1.創(chuàng)建一個(gè).c源文件和一個(gè).h頭文件
原則上文件可以任意命名;但強(qiáng)烈推薦如下原則:.c文件與.h文件同名;文件名要有意義,最好能夠體現(xiàn)該文件代碼的功能定義。
例如:IIC通信源文件與頭文件命名為IIC.c與IIC.h。
2.防重復(fù)包含
頭文件中需要防重復(fù)包含處理,防止頭文件在被多個(gè)文件引用的時(shí)候,讓編譯器在編譯時(shí)不會(huì)多次編譯。
在.h文件中加入如下代碼
#ifndef XXX
#define XXX
//Your Code
#endif
其中的XXX原則上可以是任意字符,在同一個(gè)工程中各個(gè).h文件的XXX不能相同,因此強(qiáng)烈推薦如下的規(guī)則:將.h文件的文件名全部都大寫,“。”替換成下劃線”_”,首尾各添加2個(gè)下劃線”__”作為XXX。
例如IIC.h中的寫法:
#ifndef __IIC_H__
#define __IIC_H__
//code
#endif
3.代碼的封裝
.c文件中通常是:
函數(shù)的定義\
只被本.c文件調(diào)用的宏定義
.h文件中通常是
函數(shù)的聲明
被外部調(diào)用的宏定義
4.添加到工程中
只需要將。文件添加到工程中,.h文件不同添加到工程里,同時(shí)在.c文件里把對(duì)應(yīng)的.h文件包含進(jìn)來。
以下是一個(gè)IIC.h和IIC.c文件的內(nèi)容
IIC.h的內(nèi)容
復(fù)制代碼
#ifndef __I2C_H__
#define __I2C_H__
#include 《reg52.h》
#define uchar unsigned char
sbit SDA=P2^0;
sbit SCL=P2^1;
void delay();
void start();
void stop();
void ack();
void nack();
void write_byte(uchar date);
uchar read_byte();
void write_at24c02(uchar address ,uchar date);
uchar read_at24c02(uchar address);
#endif
復(fù)制代碼
IIC.c的內(nèi)容
#include “i2c.h”
void delay()
{
;;
}
/*各個(gè)函數(shù)的定義*/
main.c內(nèi)容
#include 《reg52.h》
#include “i2c.h”《br》
void main()
{
//code
}
單片機(jī)模塊化編程
在剛開始接觸到C語言程序的時(shí)候,由于學(xué)習(xí)內(nèi)容所限,寫的程序都不是很大,一般也就幾百行而矣。所以所有的程序都完成在一個(gè)源文件里面。記得那時(shí)候大一參加學(xué)校里的一個(gè)電子設(shè)計(jì)大賽,調(diào)試了一個(gè)多星期,所有程序加起來大概將近1000行,長(zhǎng)長(zhǎng)的一個(gè)文件,從上瀏覽下來都要好半天。出了錯(cuò)誤簡(jiǎn)單的語法錯(cuò)誤還好定位,其它一些錯(cuò)誤,往往找半天才找的到。那個(gè)時(shí)候開始知道了模塊化編程這個(gè)東西,也嘗試著開始把程序分模塊編寫。最開始是把相同功能的一些函數(shù)(譬如1602液晶的驅(qū)動(dòng))全部寫在一個(gè)頭文件(.h)文件里面,然后需要調(diào)用的地方包含進(jìn)去,但是很快發(fā)現(xiàn)這種方法有其局限性,很容易犯重復(fù)包含的錯(cuò)誤。
而且調(diào)用起來也很不方便。很快暑假的電子設(shè)計(jì)大賽來臨了,學(xué)校對(duì)我們的單片機(jī)軟件編程進(jìn)行了一些培訓(xùn)。由于學(xué)校歷年來參加國(guó)賽和省賽,因此積累了一定數(shù)量的驅(qū)動(dòng)模塊,那些日子,老師每天都會(huì)布置一定量的任務(wù),讓我們用這些模塊組合起來,完成一定功能。而正是那些日子模塊化編程的培訓(xùn),使我對(duì)于模塊化編程有了更進(jìn)一步的認(rèn)識(shí)。并且程序規(guī)范也開始慢慢注意起來。此后的日子,無論程序的大小,均采用模塊化編程的方式去編寫。很長(zhǎng)一段時(shí)間以來,一直有單片機(jī)愛好者在QQ上和我一起交流。有時(shí)候,他們會(huì)發(fā)過來一些有問題的程序源文件,讓我?guī)兔π薷囊幌?。同樣是長(zhǎng)長(zhǎng)的一個(gè)文件,而且命名極不規(guī)范,從頭看下來,著實(shí)是痛苦,說實(shí)話,還真不如我重新給他們寫一個(gè)更快一些,此話到不假,因?yàn)槭诸^積累了一定量的模塊,在完成一個(gè)新的系統(tǒng)時(shí)候,只需要根據(jù)上層功能需求,在底層模塊的支持下,可以很快方便的完成。而不需要從頭到尾再一磚一瓦的重新編寫。藉此,也可以看出模塊化編程的一個(gè)好處,就是可重復(fù)利用率高。下面讓我們揭開模塊化神秘面紗,一窺其真面目。
C語言源文件 *.c
提到C語言源文件,大家都不會(huì)陌生。因?yàn)槲覀兤匠懙?a href="http://www.asorrir.com/v/tag/1780/" target="_blank">程序代碼幾乎都在這個(gè)XX.C文件里面。編譯器也是以此文件來進(jìn)行編譯并生成相應(yīng)的目標(biāo)文件。作為模塊化編程的組成基礎(chǔ),我們所要實(shí)現(xiàn)的所有功能的源代碼均在這個(gè)文件里。理想的模塊化應(yīng)該可以看成是一個(gè)黑盒子。即我們只關(guān)心模塊提供的功能,而不管模塊內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)。好比我們買了一部手機(jī),我們只需要會(huì)用手機(jī)提供的功能即可,不需要知曉它是如何把短信發(fā)出去的,如何響應(yīng)我們按鍵的輸入,這些過程對(duì)我們用戶而言,就是是一個(gè)黑盒子。
在大規(guī)模程序開發(fā)中,一個(gè)程序由很多個(gè)模塊組成,很可能,這些模塊的編寫任務(wù)被分配到不同的人。而你在編寫這個(gè)模塊的時(shí)候很可能就需要利用到別人寫好的模塊的借口,這個(gè)時(shí)候我們關(guān)心的是,它的模塊實(shí)現(xiàn)了什么樣的接口,我該如何去調(diào)用,至于模塊內(nèi)部是如何組織的,對(duì)于我而言,無需過多關(guān)注。而追求接口的單一性,把不需要的細(xì)節(jié)盡可能對(duì)外部屏蔽起來,正是我們所需要注意的地方。
C語言頭文件 *.h
談及到模塊化編程,必然會(huì)涉及到多文件編譯,也就是工程編譯。在這樣的一個(gè)系統(tǒng)中,往往會(huì)有多個(gè)C文件,而且每個(gè)C文件的作用不盡相同。在我們的C文件中,由于需要對(duì)外提供接口,因此必須有一些函數(shù)或者是變量提供給外部其它文件進(jìn)行調(diào)用。
假設(shè)我們有一個(gè)LCD.C文件,其提供最基本的LCD的驅(qū)動(dòng)函數(shù)
LcdPutChar(char cNewValue) ; //在當(dāng)前位置輸出一個(gè)字符
而在我們的另外一個(gè)文件中需要調(diào)用此函數(shù),那么我們?cè)撊绾巫瞿兀?/p>
頭文件的作用正是在此??梢苑Q其為一份接口描述文件。其文件內(nèi)部不應(yīng)該包含任何實(shí)質(zhì)性的函數(shù)代碼。我們可以把這個(gè)頭文件理解成為一份說明書,說明的內(nèi)容就是我們的模塊對(duì)外提供的接口函數(shù)或者是接口變量。同時(shí)該文件也包含了一些很重要的宏定義以及一些結(jié)構(gòu)體的信息,離開了這些信息,很可能就無法正常使用接口函數(shù)或者是接口變量。但是總的原則是:不該讓外界知道的信息就不應(yīng)該出現(xiàn)在頭文件里,而外界調(diào)用模塊內(nèi)接口函數(shù)或者是接口變量所必須的信息就一定要出現(xiàn)在頭文件里,否則,外界就無法正確的調(diào)用我們提供的接口功能。因而為了讓外部函數(shù)或者文件調(diào)用我們提供的接口功能,就必須包含我們提供的這個(gè)接口描述文件----即頭文件。同時(shí),我們自身模塊也需要包含這份模塊頭文件(因?yàn)槠浒四K源文件中所需要的宏定義或者是結(jié)構(gòu)體),好比我們平常所用的文件都是一式三份一樣,模塊本身也需要包含這個(gè)頭文件。
下面我們來定義這個(gè)頭文件,一般來說,頭文件的名字應(yīng)該與源文件的名字保持一致,這樣我們便可以清晰的知道哪個(gè)頭文件是哪個(gè)源文件的描述。
于是便得到了LCD.C的頭文件LCD.h 其內(nèi)容如下。
#ifndef _LCD_H_
#define _LCD_H_
extern LcdPutChar(char cNewValue) ;
#endif
這與我們?cè)谠次募卸x函數(shù)時(shí)有點(diǎn)類似。不同的是,在其前面添加了extern 修飾符表明其是一個(gè)外部函數(shù),可以被外部其它模塊進(jìn)行調(diào)用。
#ifndef _LCD_H_
#define _LCD_H_
#endif
這個(gè)幾條條件編譯和宏定義是為了防止重復(fù)包含。假如有兩個(gè)不同源文件需要調(diào)用LcdPutChar(char cNewValue)這個(gè)函數(shù),他們分別都通過#include “Lcd.h”把這個(gè)頭文件包含了進(jìn)去。在第一個(gè)源文件進(jìn)行編譯時(shí)候,由于沒有定義過 _LCD_H_ 因此 #ifndef _LCD_H_ 條件成立,于是定義_LCD_H_ 并將下面的聲明包含進(jìn)去。在第二個(gè)文件編譯時(shí)候,由于第一個(gè)文件包含時(shí)候,已經(jīng)將_LCD_H_定義過了。因此#ifndef _LCD_H_ 不成立,整個(gè)頭文件內(nèi)容就沒有被包含。假設(shè)沒有這樣的條件編譯語句,那么兩個(gè)文件都包含了extern LcdPutChar(char cNewValue) ; 就會(huì)引起重復(fù)包含的錯(cuò)誤。
不得不說的typedef
很多朋友似乎了習(xí)慣程序中利用如下語句來對(duì)數(shù)據(jù)類型進(jìn)行定義
#define uint unsigned int
#define uchar unsigned char
然后在定義變量的時(shí)候 直接這樣使用
uint g_nTimeCounter = 0 ;
不可否認(rèn),這樣確實(shí)很方便,而且對(duì)于移植起來也有一定的方便性。但是考慮下面這種情況你還會(huì) 這么認(rèn)為嗎?
#define PINT unsigned int * //定義unsigned int 指針類型
PINT g_npTimeCounter, g_npTimeState ;
那么你到底是定義了兩個(gè)unsigned int 型的指針變量,還是一個(gè)指針變量,一個(gè)整形變量呢?而你的初衷又是什么呢,想定義兩個(gè)unsigned int 型的指針變量嗎?如果是這樣,那么估計(jì)過不久就會(huì)到處抓狂找錯(cuò)誤了。
慶幸的是C語言已經(jīng)為我們考慮到了這一點(diǎn)。typedef 正是為此而生。為了給變量起一個(gè)別名我們可以用如下的語句
typedef unsigned int uint16 ; //給指向無符號(hào)整形變量起一個(gè)別名 uint16
typedef unsigned int * puint16 ; //給指向無符號(hào)整形變量指針起一個(gè)別名 puint16
在我們定義變量時(shí)候便可以這樣定義了:
uint16 g_nTimeCounter = 0 ; //定義一個(gè)無符號(hào)的整形變量
puint16 g_npTimeCounter ; //定義一個(gè)無符號(hào)的整形變量的指針
在我們使用51單片機(jī)的C語言編程的時(shí)候,整形變量的范圍是16位,而在基于32的微處理下的整形變量是32位。倘若我們?cè)?位單片機(jī)下編寫的一些代碼想要移植到32位的處理器上,那么很可能我們就需要在源文件中到處修改變量的類型定義。這是一件龐大的工作,為了考慮程序的可移植性,在一開始,我們就應(yīng)該養(yǎng)成良好的習(xí)慣,用變量的別名進(jìn)行定義。
如在8位單片機(jī)的平臺(tái)下,有如下一個(gè)變量定義
uint16 g_nTimeCounter = 0 ;
如果移植32單片機(jī)的平臺(tái)下,想要其的范圍依舊為16位。
可以直接修改uint16 的定義,即
typedef unsigned short int uint16 ;
這樣就可以了,而不需要到源文件處處尋找并修改。
將常用的數(shù)據(jù)類型全部采用此種方法定義,形成一個(gè)頭文件,便于我們以后編程直接調(diào)用。
文件名 MacroAndConst.h
其內(nèi)容如下:
#ifndef _MACRO_AND_CONST_H_
#define _MACRO_AND_CONST_H_
typedef unsigned int uint16;
typedef unsigned int UINT;
typedef unsigned int uint;
typedef unsigned int UINT16;
typedef unsigned int WORD;
typedef unsigned int word;
typedef int int16;
typedef int INT16;
typedef unsigned long uint32;
typedef unsigned long UINT32;
typedef unsigned long DWORD;
typedef unsigned long dword;
typedef long int32;
typedef long INT32;
typedef signed char int8;
typedef signed char INT8;
typedef unsigned char byte;
typedef unsigned char BYTE;
typedef unsigned char uchar;
typedef unsigned char UINT8;
typedef unsigned char uint8;
typedef unsigned char BOOL;
#endif
至此,似乎我們對(duì)于源文件和頭文件的分工以及模塊化編程有那么一點(diǎn)概念了。那么讓我們趁熱打鐵,將上一章的我們編寫的LED閃爍函數(shù)進(jìn)行模塊劃分并重新組織進(jìn)行編譯。
在上一章中我們主要完成的功能是P0口所驅(qū)動(dòng)的LED以1Hz的頻率閃爍。其中用到了定時(shí)器,以及LED驅(qū)動(dòng)模塊。因而我們可以簡(jiǎn)單的將整個(gè)工程分成三個(gè)模塊,定時(shí)器模塊,LED模塊,以及主函數(shù)
對(duì)應(yīng)的文件關(guān)系如下
main.c
Timer.c --?Timer.h
Led.c --?Led.h
單片機(jī)模塊化編程串口中斷怎么處理
只要進(jìn)行如下程序即可。
#include《reg51.h》
#define u16 unsigned int
#define u8 unsigned char
code u16 sj_tab[]={800,400,200,100};//每個(gè)檔位對(duì)應(yīng)的時(shí)間
u8 setsj=0;//檔位
u16 jsflag;
#define LED P2
code u8 led_tab[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f,};//LED亮的方式
u8 ledflag=0;
/****************************************/
void init() //初始化函數(shù)
{ TMOD=0x01;
TH0=(65536-1000)/256;
TL0=(65536-1000)%256;
EA=1;
ET0=1;
}
/*********按鍵掃描******/
sbit k=P3^2;
u8 key(){
static u8 ms;
if(k==0){//檢測(cè)到0
if(ms《10)ms++;
if(ms==5)return 0;//連續(xù)5次掃描都為0,
}
else ms=0;
return 1;
}
/***********************************/
u8 count=0; //計(jì)數(shù)
void main()//主函數(shù)
{
init();//系統(tǒng)初始化
jsflag=sj_tab[setsj];//初始加載時(shí)間
TR0=1; //定時(shí)器開始計(jì)時(shí)
while(1){
if(key()==0){ //按鍵按下
if(++setsj》=4)setsj=0;//檔位+1,加到最大后歸0
}
LED=led_tab[ledflag];
}
}
/****************************/
void timer0() interrupt 1//1ms定時(shí)器
{
TH0=(65536-1000)/256;//重載初值
TL0=(65536-1000)%256;
if(jsflag》0)jsflag--;//1ms減1
if(jsflag==0){//減到0
jsflag=sj_tab[setsj];//重載時(shí)間
if(++ledflag》=8)ledflag=0;
}
}
結(jié)語
關(guān)于單片機(jī)模塊化編程的相關(guān)介紹就到這了,如有不足之處歡迎指正。
-
單片機(jī)
+關(guān)注
關(guān)注
6061文章
44914瀏覽量
646626 -
模塊化編程
+關(guān)注
關(guān)注
4文章
17瀏覽量
7798
發(fā)布評(píng)論請(qǐng)先 登錄
第6章單片機(jī)定時(shí)器串口中斷(20150709213857)
單片機(jī)學(xué)習(xí)教程之外部中斷和定時(shí)器及串口中斷的資料和程序說明

51單片機(jī)串口中斷功能的設(shè)置

【單片機(jī)】Keil+Proteus流水燈(模塊化編程)

51單片機(jī)-矩陣鍵盤模塊-長(zhǎng)短按鍵&數(shù)碼管顯示-模塊化編程模板

經(jīng)驗(yàn)分享|十年老司機(jī)的單片機(jī)模塊化編程

單片機(jī)工程模塊化操作_適用單片機(jī)編程新手

單片機(jī)零基礎(chǔ)入門(8-5)模塊化編程

評(píng)論