嵌入式 C 語言(上)

目錄

基礎知識

資料型別

const 用法

作用域與 static 用法

extern 用法

基礎知識

嵌入式C語言和普通C語言在語法上幾乎沒有差別,其主要差別在於普通C語言的執行環境是OS之上,有很多的標準庫函式支撐呼叫,分配的記憶體是電腦的記憶體,其處理器就是電腦的CPU;而在嵌入式環境中,會涉及到底層的硬體,而硬體本身是沒有標準庫可以呼叫的,因而就需要開發者使用C語言程式設計除錯硬體,使其可以工作,對於開發某一款晶片,有針對的編譯器(或者交叉編譯環境),可以分配的記憶體則是晶片的RAM、Flash,處理器則是晶片自身帶的MCU,例如ARM、DSP等。

例如C語言程式設計的入門課:列印“Hello World!”,在普通C語言程式設計中,直接呼叫printf()函式即可在PC上打印出;而在嵌入式中,則需要開發者使用C語言去將晶片的串列埠除錯成功,然後將printf()函式重新實現,方可呼叫列印。

嵌入式C語言的基本結構及其特點:

所有的C語言程式都需要包含main()函式,程式碼從main()函式開始執行;這一條在嵌入式中不一定完全正確,在執行main()函式之前也有開發者可以操縱的空間,因而開始函式可以不是main(),例如也可以是myMain()這樣的函式,而這所涉及到的知識已經超過基礎知識的範圍,會在後續詳細說明;

C語言的語句以用分號“;”結束;

C語言的註釋有行註釋(“//”)和段註釋(“/…/”);

函式是C語言的基本結構,每個C程式都是由至少一個函式組成;

C語言的檔案有兩種格式:原始檔。c檔案和標頭檔案。h檔案,通常。c檔案用於功能函式的實現,而。h檔案用於預處理、宏定義和宣告等;在嵌入式中,通常將某個硬體模組的功能實現函式及其宣告和包含的全域性變數宣告分別處理到一個。c和。h檔案中,例如led。c、hello。c和led。h、hello。h就分別對應於LED燈的功能函式及其宣告和hello的功能函式及其宣告;

我們將這種基於某個模組的獨立設計稱之為模組化設計,在一個系統中通常是由許許多多的模組共同組成的,因而模組化設計是一個非常科學且非常值得學習的程式設計方法;

除了模組化設計,通常嵌入式的程式設計設計還有層次化設計。在一個工程系統中,硬體驅動僅僅只是第一步,對硬體的應用則是一個功能豐富的系統的更進一步的設計,通常在這一塊會設計到例如影象處理、資料處理等演算法;我們可以籠統地將一個嵌入式工程系統分為驅動層和應用層。

資料型別

在C語言中,資料型別指的是用於宣告不同型別的變數或函式的一個廣泛的系統,變數的型別決定了變數儲存佔用的空間以及如何解釋儲存的位模式。

在嵌入式系統中,晶片的容量是有限的,且對比於PC機容量通常都是比較小的,因而瞭解變數所佔用的儲存空間是嵌入式開發者應當掌握的一項技能,所以對於不同資料型別在不同位數的晶片中(例如STM32xxx就表示此款晶片是32bit的晶片,STM8xxx表示此款晶片是8bit的晶片)的長度開發者也應該掌握。

C語言中的資料型別有以下幾種:

嵌入式 C 語言(上)

就以STM32F103ZE這一款晶片為例,這是一塊32bit的MCU,基本資料型別在此款晶片中的資料長度,以及在HAL庫函式中的定義(stdint。h檔案中的定義,採用C99標準)如圖 5。3。2 所示。這裡建議開發者在開發過程中使用庫定義的資料型別,來定義變數或函式,比如unsigned char a,使用uint8_t a。

嵌入式 C 語言(上)

const 用法

C語言中const關鍵字是constant的縮寫,譯為常量、常數等,但const關鍵字不僅僅是用於定義常量,還可以用於修飾陣列、指標、函式引數等。

修飾變數

C語言中使用const修飾變數,功能是對變數宣告為只讀特性,並保護變數值以防被修改。例如:

const int i = 5;

這個例子表明整形變數i具有隻讀性,不能夠被修改;若想對其重新賦值,例如i=10則是錯誤的用法。需要注意的是,const定義變數的同時還必須對其初始化,const可以放在資料型別的前面或者後面,比如上述例子也可以寫成:

int const i = 5;

此外,const修飾變數還起到了節約空間的目的,通常編譯器並不給普通const只讀變數分配空間,而是將它們儲存在符號列表中,無需讀寫記憶體操作,程式執行效率也會提高。

修飾陣列

C語言中const還可以修飾陣列,例如:

const

int

array

5

=

{

0

1

2

3

4

};

//

int

const

array

5

=

{

0

1

2

3

4

};

const關鍵字修飾陣列與修飾變數類似,表明此陣列具有隻讀性,不可修改,一旦被更改程式會出錯,例如上述例子如果:

array

1

=

10

則程式將會提示錯誤。

修飾指標

C語言中const修飾指標需要特別注意,共有兩種形式,一種是用來限定指向空間的值不可修改;另一種是限定指標不可修改,例如:

int

i

=

5

int

k

=

10

int

const

*

p1

=

&

i

int

*

const

p2

=

&

k

對於指標p1,const修飾的是p1,即p1指向的空間的值不可改變,例如p1 = 20;就是錯誤的用法;但是p1的值是可以改變的,例如p1 = &k;則沒有任何問題。

對於指標p2,const修飾的是p2,即指標本身p2不可更改,而指標指向空間的值是可以改變的,例如*p2 = 15;是沒有問題的,而p2 = &i;則是錯誤的用法。

修飾函式引數

在C語言中const修飾函式引數對引數起限定作用,防止其在函式內部被意外修改,所限定的引數可以是普通變數也可以是指標變數,如:

void

fun

const

int

x

{

。。。

x

=

10

// 對 x 的值進行了修改,錯誤

}

void

fun1

const

int

*

p

{

。。。

*

p

++

// 對 p 指向空間的值進行了修改,錯誤

}

作用域與 static 用法

在瞭解static關鍵字的用法之前,我們需要先了解C語言中的作用域、區域性變數和全域性變數的概念。

一個C變數的作用域可以是塊作用域、函式作用域、函式原型作用域或檔案作用域。

塊是用一對花括號“{}”括起來的程式碼區域,定義在塊中的變數具有塊作用域。塊作用域的可見範圍是從定義處到包含該定義的塊的末尾。以前,具有塊作用域的變數都必須宣告在塊的開頭,C99標準放寬了這一限制,允許在塊中的任意位置宣告變數。例如不支援C99標準的的for迴圈需要這樣寫:

void

fun1

void

{

int

i

=

0

for

i

=

0

i

<

10

i

++

{

。。。

}

}

在函式fun的開頭定義了局部變數i,然後在for迴圈中呼叫此變數,變數i的作用域是函式fun內,當函式fun執行完畢之後變數i會被釋放。而C99標準下可以這樣寫:

void

fun2

void

{

for

int

i

=

0

i

<

10

i

++

{

。。。

}

}

這樣寫的話,變數i的作用域則在for迴圈體內,當迴圈結束後,變數就會被釋放,可見其作用域縮小了,這樣的好處是增加了安全性和靈活性。

在函式fun1中,變數i被宣告在函式體內,我們稱這樣的變數為區域性變數,其有效範圍是在被定義的函式內,函式執行完畢後變數即被釋放;如果把這個變數定義在函式體外,如:

int

k

=

0

void

fun3

void

{

for

k

=

0

k

<

10

k

++

{

。。。

}

}

我們則將定義在函式體外的變數稱之為全域性變數,其作用範圍為當前原始檔和工程,若其它原始檔想要呼叫用此變數需要在檔案內使用關鍵字extern宣告,如extern int k。簡單的總結下區域性變數和全域性變數的特點:

區域性變數會在每次宣告的時候被重新初始化(如果在宣告的時候有初始化賦值),不具有記憶能力,其作用範圍僅在某個塊作用域可見;

全域性變數只會被初始化一次,之後會在程式的某個地方被修改,其作用範圍可以是當前的整個原始檔或者工程;

鑑於兩種變數的侷限性,就引入了靜態變數(靜態區域性變數和靜態全域性變數),使用關鍵字static來修飾。其中靜態區域性變數滿足區域性變數的作用範圍,但是其擁有記憶能力,不會在每次生命的時候都初始化一次,這個作用在用來實現計數功能的時候非常方便,例如:

void

cnt

void

{

static

int

num

=

0

num

++

}

在這個函式中,變數num就是靜態區域性變數,在第一次進入cnt函式的時候被宣告,然後執行自加操作,num的值就等於1;當第二次進入cnt函式的時候,num不會被重新初始化變成0,而是保持1,再自增則變成了2,以此類推,其作用域仍然是cnt這個函式體內。靜態全域性變數則將全域性變數的作用域縮減到了只當前原始檔可見,其它檔案不可見,簡單例子如下:

static

int

k

=

0

void

set_k

void

{

k

=

1

}

void

reset_k

void

{

k

=

0

}

int

get_k

void

{

return

k

}

靜態全域性變數的優勢是增強了程式的安全性和健壯性,因為對於變數k而言,我們假設我們不期望其它的檔案有修改變數k的能力,但是其它的檔案又需要變數k的值來進行邏輯運算,那我們就可以向上述例子那樣做,在原始檔中定義一個靜態全域性變數,同時使用函式對其的值進行修改和獲取,對外只提供函式介面即可,其它檔案透過函式介面間接的使用這個變數。這樣做同時也可以提高可移植性。

靜態全域性變數只在本檔案可見,因而其它檔案也可以定義相同名字的靜態區域性變數,例如我們可以在source1。c裡面定義static int k = 0;的同時也可以在source2。c裡面也定義一個static int k = 0;這樣做是不會有問題的,但是我們一點都不建議如此做,因為這不利於程式的可讀性和可維護性,也容易讓開發變得混亂。

在C語言中static關鍵字除了用來修飾變數之外,還可以用來修飾函式,讓函式僅在本檔案可見,其它檔案無法對其進行呼叫,例如在example1。c檔案裡面進行了如下定義:

static

void

gt_fun

void

{

。。。

}

那麼gt_fun這個函式就只能在example1。c中被呼叫,在example2。c中就無法呼叫這個函式。而如果不使 用static來修飾這個函式,那麼只需要在example2。c中使用extern關鍵字寫下語句extern void gt_fun(void);即可呼叫gt_fun這個函式。

在嵌入式C語言程式設計中,static是一個非常靈活非常好用的關鍵字,它可以讓程式更簡潔、更安全、更具有可移植性,在嵌入式系統中這三點都是非常重要的程式設計思想,需要認真掌握。

extern 用法

在上一小節有提到過extern這個關鍵字,那麼這節就來詳細說一說這個關鍵字。在C語言中,extern關鍵字用於指明函式或變數定義在其它檔案中,提示編譯器遇到此函式或者變數的時候到其它模組去尋找其定義,這樣被extern宣告的函式或變數就可以被本模組或其它模組使用。因而,extern關鍵字修飾的函式或者變數是一個宣告而不是定義,例如:

嵌入式 C 語言(上)

嵌入式 C 語言(上)

extern關鍵字還有一個重要的作用,就是如果在C++程式中要引用C語言的檔案,則需要用以下格式:

#

ifdef

__cplusplus

extern

“C”

{

#

endif

/* #ifdef __cplusplus */

……

#

ifdef

__cplusplus

}

#

endif

/* #ifdef __cplusplus */

這段程式碼的含義是,如果當前是C++環境(_cplusplus是C++編譯器中定義的宏),要編譯花括號{}裡面的內容需要使用C語言的檔案格式進行編譯,而extern “C”就是向編譯器指明這個功能的語句。