單片機開發之C語言編程基本規範

單片機開發之C語言編程基本規範

1 人贊了文章

為了提高源程序的質量和可維護性,從而最終提高軟體產品生產力,特編寫此規範。本標準規定了程序設計人員進行程序設計時必須遵循的規範。本規範主要針對單片機編程語言和08編譯器而言,包括排版、注釋、命名、變數使用、代碼可測性、程序效率、質量保證等內容。

1.基本規則

格式清晰、注釋簡明扼要、命名規範易懂、函數模塊化、程序易讀易維護、功能準確實現、代碼空間效率和時間效率高、適度的可擴展性、單片機編程規範-標識符命名

2.標識符命名

2.1 命名基本原則

(1)命名清晰明了,有明確含義,使用完整單詞或約定俗成的縮寫。通常,較短的單詞可通過去掉母音字母形成縮寫;較長的單詞可取單詞的頭幾個字母形成縮寫。即"見名知意"。(2)命名風格要自始至終保持一致。

(3)命名中若使用特殊約定或縮寫,要有注釋說明。

(4)同一軟體產品內模塊之間介面部分的標識符名稱之前加上模塊標識。

2.2 宏和常量命名

宏和常量用全部大寫字母來命名,詞與詞之間用下劃線分隔。對程序中用到的數字均應用有意義的枚舉或宏來代替。

2.3 變數命名

變數名用小寫字母命名,每個詞的第一個字母大寫。類型前綴(u8s8 etc.)全局變數另加前綴g_。

局部變數應簡明扼要。局部循環體控制變數優先使用i、j、k等;局部長度變數優先使用len、num等;臨時中間變數優先使用temp、tmp等。

2.4 函數命名

函數名用小寫字母命名,每個詞的第一個字母大寫,並將模塊標識加在最前面。

2.5 文件命名

一個文件包含一類功能或一個模塊的所有函數,文件名稱應清楚表明其功能或性質。

每個.c文件應該有一個同名的.h文件作為頭文件。

3.注釋 3.1 注釋基本原則

有助於對程序的閱讀理解,說明程序在"做什麼",解釋代碼的目的、功能和採用的方法。

一般情況源程序有效注釋量在30%左右。

注釋語言必須準確、易懂、簡潔。

邊寫代碼邊注釋,修改代碼同時修改相應的注釋,不再有用的注釋要刪除。

彙編和C中都用"//",取消";" 不使用段注釋"/* */ "(調試時可用)

3.2 文件注釋

文件注釋必須說明文件名、函數功能、創建人、創建日期、版本信息等相關信息。

修改文件代碼時,應在文件注釋中記錄修改日期、修改人員,並簡要說明此次修改的目的。所有修改記錄必須保持完整。

文件注釋放在文件頂端,用"/*……*/"格式包含。

注釋文本每行縮進4個空格;每個注釋文本分項名稱應對齊。

/***********************************************************

文件名稱:

作 者:

版 本:

說 明:

修改記錄:

***********************************************************/

3.3 函數注釋

3.3.1 函數頭部注釋

函數頭部注釋應包括函數名稱、函數功能、入口參數、出口參數等內容。如有必要還可增加作者、創建日期、修改記錄(備註)等相關項目。

函數頭部注釋放在每個函數的頂端,用"/*……*/"的格式包含。其中函數名稱應簡寫為Name(),不加入、出口參數等信息。

/***********************************************************

函數名稱:

函數功能:

入口參數:

出口參數:

備 註:

***********************************************************/

3.3.2 代碼注釋

代碼注釋應與被注釋的代碼緊鄰,放在其上方或右方,不可放在下面。如放於上方則需與其上面的代碼用空行隔開。一般少量注釋應該添加在被注釋語句的行尾,一個函數內的多個注釋左對齊;較多注釋則應加在上方且注釋行與被注釋的語句左對齊。

函數代碼注釋用"//…//"的格式。

通常,分支語句(條件分支、循環語句等)必須編寫注釋。其程序塊結束行"}"的右方應加表明該程序塊結束的標記"end of……", 尤其在多重嵌套時。

3.4 變數、常量、宏的注釋

同一類型的標識符應集中定義,並在定義之前一行對其共性加以統一注釋。對單個標識符的注釋加在定義語句的行尾。

全局變數一定要有詳細的注釋,包括其功能、取值範圍、哪些函數或過程存取它以及存取時的注意事項等。

注釋用"//…//"的格式。

4.函數 4.1 函數設計原則

函數的基本要求:

1)封裝性

1) 正確性:程序要實現設計要求的功能。

2) 穩定性和安全性:程序運行穩定、可靠、安全。

3) 可測試性:程序便於測試和評價。

4) 規範/可讀性:程序書寫風格、命名規則等符合規範。

5) 擴展性:代碼為下一次升級擴展留有空間和介面。

6) 全局效率:軟體系統的整體效率高。

7) 局部效率:某個模塊/子模塊/函數的本身效率高。

編製函數的基本原則:

1) 單個函數的規模盡量限制在200行以內(不包括注釋和空行)。一個函數只完成一個功能。

2) 函數局部變數的數目一般不超過5~10個。

3) 函數內部局部變數定義區和功能實現區(包含變數初始化)之間空一行。

4) 函數名應準確描述函數的功能。通常使用動賓片語為執行某操作的函數命名。

5) 函數的返回值要清楚明了,尤其是出錯返回值的意義要準確無誤。

6) 不要把與函數返回值類型不同的變數,以編譯系統默認的轉換方式或強制的轉換方式作為返回值返回。

7) 減少函數本身或函數間的遞歸調用。

8) 盡量不要將函數的參數作為工作變數。

4.2 函數定義

1) 函數若沒有入口參數或者出口參數,應用void明確申明。

2) 函數名稱與出口參數類型定義間應該空一格且只空一格。

3) 函數名稱與括弧()之間無空格。

4) 函數形參必須給出明確的類型定義。

5) 多個形參的函數,後一個形參與前一個形參的逗號分割符之間添加一個空格。

6) 函數體的前後花括弧"{}" 各獨佔一行。

4.3 局部變數定義

1) 同一行內不要定義過多變數。

2) 同一類的變數在同一行內定義,或者在相鄰行定義。

3) 先定義data型變數,再定義idtata型變數,再定義xdata型變數.(?)

4) 數組、指針等複雜類型的定義放在定義區的最後。

5) 變數定義區不做較複雜的變數賦值。

4.4 功能實現區規範

1) 一行只寫一條語句。

2) 注意運算符的優先順序,並用括弧明確表達式的操作順序,避免使用默認優先順序。

3) 各程序段之間使用一個空行分隔,加以必要的注釋。程序段指能完一個較具體的功能的一行或多行代碼。程序段內的各行代碼之間相互依賴性較強。(1、2、3方式)

4) 不要使用難懂的技巧性很高的語句。

5) 源程序中關係較為緊密的代碼應儘可能相鄰。

6) 完成簡單功能、關係非常密切的一條或幾條語句可編寫為函數或定義為宏。

5. 單片機編程規範-排版

5.1 縮進

代碼的每一級均往右縮進4個空格的位置。不使用Tab鍵

5.2 分行

每行語句(?????超過80個字元)要分成多行書寫;長表達式要在低優先順序操作符處劃分新行,操作符放在新行之首,劃分出的新行要進適當的縮進,使排版整齊,語句可讀。避免把注釋插入分行中。

5.3 空行

1) 文件注釋區、頭文件引用區、函數間應該有且只有一行空行。

2) 相鄰函數之間應該有且只有一行空行。

3) 函數體內相對獨立的程序塊之間可以用一行空行或注釋來分隔。

4) 函數注釋和對應的函數體之間不應該有空行。

5) 文件末尾有且只有一行空行。

5.4 空格

1) 函數語句尾部或者注釋之後不能有空格。

2) 括弧內側(即左括弧後面和右括弧前面)不加空格,多重括弧間不加空格。

3) 函數形參之間應該有且只有一個空格(形參逗號後面加空格)。

4) 同一行中定義的多個變數間應該有且只有一個空格(變數逗號後面加空格)。

5) 表達式中,若有多個操作符連寫的情況,應使用空格對它們分隔:

6) 在兩個以上的關鍵字、變數、常量進行對等操作時,它們之間的操作符前後均加一個空格;在兩個以上的關鍵字、變數、常量進行非對等操作時,其前後均不應加空格;

7) 逗號只在後面加空格;

8) 雙目操作符,如比較操作符, 賦值操作符"="、"+=",算術操作符"+"、"%",邏輯操作符"&&"、"&",位操作符"<<"、"^"等,前後均加一個空格;

9) 單目操作符,如"!"、"~"、"++"、"-"、"&"(地址運算符)等,前後不加空格;

10) "->"、"."前後不加空格;

11) if、for、while、switch等關鍵字與後面的括弧間加一個空格;

5.5 花括弧

1) if、else if、else、for、while語句無論其執行體是一條語句還是多條語句都必須加花括弧,且左右花括弧各獨佔一行。

2) do{}while()結構中,"do"和"{"均各佔一行,"}"和"while();"共同佔用一行。

if ( ) do

{ {

} }while( );

else

{

}

嵌套越少越好,{}不準超過3層

5.6 switch語句

1) 每個case和其判據條件獨佔一行。

2) 每個case程序塊需用break結束。特殊情況下需要從一個case塊順序執行到下一個case塊的時候除外,但需要花括弧在交界處明確注釋如此操作的原因,以防止出錯。

3) case程序塊之間空一行,且只空一行。

4) 每個case程序塊的執行語句保持4個空格的縮進。

5) 一般情況下都應該包含default分支。

Switch ( )

{

case x:

break;

case x:

break;

default:

break;

}

6.程序結構 6.1 基本要求

1) 有main()函數的.c文件應將main()放在最前面,並明確用void聲明參數和返回值。

2) 對由多個.c文件組成的模塊程序或完整監控程序,建立公共引用頭文件,將需要引用的庫頭文件、標準寄存器定義頭文件、自定義的頭文件、全局變數等均包含在內,供每個文件引用。通常,標準函數庫頭文件採用尖角號< >標誌文件名,自定義頭文件採用雙撇號″″標誌文件名。

3) 每個.c文件有一個對應的.h文件,.c文件的注釋之後首先定義一個唯一的文件標誌宏,並在對應的.h文件中解析該標誌。

在.c文件中:

#define FILE_FLAG

在.h文件中:&

6.程序結構 6.1 基本要求

1) 有main()函數的.c文件應將main()放在最前面,並明確用void聲明參數和返回值。

2) 對由多個.c文件組成的模塊程序或完整監控程序,建立公共引用頭文件,將需要引用的庫頭文件、標準寄存器定義頭文件、自定義的頭文件、全局變數等均包含在內,供每個文件引用。通常,標準函數庫頭文件採用尖角號< >標誌文件名,自定義頭文件採用雙撇號″″標誌文件名。

3) 每個.c文件有一個對應的.h文件,.c文件的注釋之後首先定義一個唯一的文件標誌宏,並在對應的.h文件中解析該標誌。

在.c文件中:

#define FILE_FLAG

在.h文件中:

#ifdef FILE_FLAG

#define XXX

#else

#define XXX extern

#endif

4) 對於確定只被某個.c文件調用的定義可以單獨列在一個頭文件中、單獨調用。

6.2 可重入函數

可重入函數中若使用了全局變數,應通過關中斷、信號量等操作手段對其加以保護。

6.3 函數的形參

1) 由函數調用者負責檢查形參的合法性。

2) 盡量避免將形參作為工作變數使用。

6.4 循環

1) 盡量減少循環嵌套層數

2) 在多重循環中,應將最忙的循環放在最內層

3) 循環體內工作量最小

4) 盡量避免循環體內含有判斷語句

7.工程中所包含的文件 7.1 頭文件

7.1.1 頭文件的形式

MCU程序中的頭文件包括面向硬體對象頭文件、公共頭文件和總頭文件。

MCU C工程編程是面向硬體對象的。例如,要用MCU控制電機(Motor),在這樣一個系統中,「面向硬體對象」概念體現在,工程中會創建「Motor.c」的源程序文件專門用於電機控制。相應的,也要創建一個同名頭文件「Motor.h」,用於控制電機的MCU引腳定義、相關宏定義和電機控制函數聲明等。像這樣的頭文件,就是面向硬體對象頭文件。與之同名的「*.c」文件可以包含它,來完成控制此硬體對象的MCU引腳定義和相關宏定義;調用該硬體對象控制函數的文件也可以通過調用它來進行函數聲明。

還有一類頭文件不是專門針對於特定的硬體對象的,而是有一定的通用性。這類頭文件被稱為公共頭文件。如工程中包含的「Type.h」文件,該文件用於C語言中類型的別名定義,用戶還可以根據自己的需要,隨時在該文件中添加條目。在工程的任一文件中,需要用到這些別名時,都要包含「Type.h」。可見公共頭文件並不拘泥於具體的硬體對象,它是為整個工程的和諧運作而建立的。

總頭文件(includes.h)是一個較特殊的頭文件。它只被主函數文件包含,用於包含主函數文件中需要的頭文件,宏定義,函數聲明等。它使得主函數文件能夠盡量避免改動,結構更加清晰。

7.1.2 頭文件的命名

總的來說頭文件的命名應盡量做到簡短易懂,見名知意。

面向硬體對象頭文件的名稱一定要與相應的硬體對象驅動文件同名。例???

公共頭文件,如果對應於相應的源程序文件而建立,必須與之同名。如,「GeneralFun.c」

是工程中的通用函數定義文件,(像內存數據移動函數,延時函數都屬於通用函數),其他文件在用到這些函數之前,必須進行函數原型聲明,從而建立與之同名的「GeneralFun.h」文件,專門用於相應的函數聲明。其它的公共頭文件沒有同名要求,只要表清文件含義即可,如「Type.h」,「GP32C.h」等。

總頭文件在一個工程中只有一個,它的名稱較為固定,一般取為「Includes.h」。

7.1.3 頭文件注意事項

1) 為了防止重複定義需要使用偽指令 #ifndef VarType……

例:

#ifndef VarType

#define VarType

typedef unsigned char INT8U; //無符號8位數

typedef signed char INT8S; //有符號8位數

typedef unsigned int INT16U; //無符號16位數

typedef signed int INT16S; //有符號16位數

typedef unsigned long INT32U; //無符號32位數

typedef signed long INT32S; //有符號32位數

typedef float FP32; //單精度浮點數

typedef double FP64; //雙精度浮點數

#endif

2) 對於一個項目中的頭文件與晶元相關的寄存器映像文件不可擅自改動,如果的確存在需要改動的地方另外開闢頭文件。

3) typedef和#define的用法

① typedef的用法

在C/C++語言中,typedef常用來定義一個標識符及關鍵字的別名,它是語言編譯過程的一部分,但它並不實際分配內存空間,實例像:

typedef int INT;

typedef int ARRAY[10];

typedef (int*) pINT;

typedef可以增強程序的可讀性,以及標識符的靈活性,但它也有「非直觀性」等缺點。

② #define的用法

#define為一宏定義語句,通常用它來定義常量(包括無參量與帶參量),以及用來實現那些「表面似和善、背後一長串」的宏,它本身並不在編譯過程中進行,而是在這之前(預處理過程)就已經完成了,但也因此難以發現潛在的錯誤及其它代碼維護問題,它的實例像:

#define INT int

#define TRUE 1

#define Add(a,b) ((a)+(b));

#define Loop_10 for (int i=0; i<10; i++)

③ typedef與#define的區別

從以上的概念便也能基本清楚,typedef只是為了增加可讀性而為標識符另起的新名稱(僅僅只是個別名),而#define原本在C中是為了定義常量,到了C++,const、enum、inline的出現使它也漸漸成為了起別名的工具。為了儘可能地兼容,一般都遵循#define定義「可讀」的常量以及一些宏語句的任務,而typedef則常用來定義關鍵字、冗長的類型的別名。

宏定義只是簡單的字元串代換(原地擴展),而typedef則不是原地擴展,它的新名字具有一定的封裝性,以致於新命名的標識符具有更易定義變數的功能。請看上面第一大點代碼的第三行:

typedef (int*) pINT;

以及下面這行:

#define pINT2 int*

效果相同?實則不同!實踐中見差別:

pINT a,b;的效果同int *a; int *b;

表示定義了兩個整型指針變數。

而pINT2 a,b;的效果同int *a, b;表示定義了一個整型指針變數a和整型變數b。

注意:兩者還有一個行尾;號的區別哦!(???)

7.2 源程序文件

源程序文件包括主函數文件、通用函數文件、硬體對象控制文件、晶元初始化文件、中斷向量定義文件和中斷使能文件。

源程序文件的分類和命名類同於頭文件,但也有它自己的特點。

7.2.1 主程序文件

(Main.s 或 Main.c)(?????)

工程中有且僅有一個主程序文件,它包含了工程的主處理流程。

主函數文件中包含:

(1)工程描述

①工程名

工程名中每個意義單詞(或單詞縮寫)的首字母大寫,後綴為.prj。

②硬體連接索引

工程所要控制的硬體對象索引,詳細描述在相應的硬體對象控制文件中給出。

③工程的功能、目的和說明

④注意要點

可以註明編程要點和心得

⑤日期

註明工程完成日期

(2)總頭文件

(3)主函數

如:

7.2.2 晶元初始化文件(「SetUp.c」或 「SetUp.s」)

該文件與具體的晶元型號有關,並且只包含一個晶元初始化函數,若想由編譯器自動調用晶元初始化函數,其函數名必須為"_HC08Setup",否則編譯器會自動建立並調用一個空的"__HC08Setup"彙編子程序,而不理會用戶創建的晶元初始化函數。為了統一,將該函數起名為"MCUInit",並在主函數中調用該函數。

7.2.3 通用函數頭文件和通用函數文件

通用函數頭文件和通用函數文件,「GenneralFun.h」和「GeneralFun.c」。

//[GenneralFun.h]通用函數頭文件---------------------------------------------#include"Type.h" //類型別名定義voidDelay(INT16U); //延時函數聲明

「GenneralFun.h」中包含:

(1)文件名

(2)通用函數所需用到的頭文件

(3)通用函數用到的宏定義

(4)通用函數聲明

外部函數要用到通用函數時,可包含這個頭文件進行函數聲明。

7.2.4 對象控制文件

7.2.5 中斷處理函數和中斷向量表文件

9. 硬體封裝的思想

1) 與硬體相關的程序文件

與某個硬體相關的子程序放到1個程序文件中,該硬體的頭文件放到一個文件中。

程序文件的開始處是有關說明:本文件所包含的子程序及簡要的功能說明,子程序分為內部調用和外部調用;硬體的連接說明。

2) 中斷的開放和禁止

使用宏定義方式開放或禁止中斷,宏定義語句放在EnDisInt.h頭文件中。宏名的定義方法:

開放中斷以Enable標識,宏名中包含中斷名,宏名最後以Int結束。如:開放串列接收中斷的宏名為:EnableSCIReInt。

禁止中斷以Disable標識,宏名中包含中斷名,宏名最後以Int結束。如:禁止串列接收中斷的宏名為:DisableSCIReInt。

開放所有中斷宏名:EnableMCUInt。

禁止所有中斷宏名:DisableMCUInt。


推薦閱讀:

使用Arduino開發板和ESP8266從互聯網讀取數據
樹莓派探索日誌——入門
第七一〇步 「 不就是調個2.4Ghz無線通信么,誒,怎麼不通" 解決之道
FPGA學習(一)——產生頻率可控的正弦波
從複雜到明晰, 嵌入式視覺帶你突破挑戰,化繁為簡

TAG:自動化 | 單片機 |