關於嵌入式開發的C語言基礎總結
一、位操作
C語言支援的6種位操作符如下:
1. 不改變其他位的值的狀況下,對某幾個位進行設值。
方法:先對需要設定的位用&操作符(對應位&0)進行清零操作,然後用|操作符設值(對應位|你想要設定的值)。
舉例:改變 GPIOA-> BSRRL 的狀態
GPIOA-> BSRRL &=0XFF0F; //將第 4-7 位清 0
GPIOA-> BSRRL |=0X0040; //設定相應位的值,不改變其他位的值
2. 移位操作提高程式碼的可讀性
位操作在微控制器開發中也非常重要,我們來看看下面一行程式碼
GPIOx->ODR |= (((uint32_t)0x01) << pinpos);
這個操作就是將 ODR 暫存器的第 pinpos 位設定為 1,為什麼要通過左移而不是直接設定一個固定的值呢?
其實,這是為了提高程式碼的可讀性以及可重用性。這行程式碼可以很直觀明瞭的知道,是將第 pinpos 位設定為 1。
如果你寫成GPIOx->ODR =0x0040; 這樣的程式碼就不好看也不好重用了。
3. ~取反操作使用技巧
SR 暫存器的每一位都代表一個狀態,某個時刻我們希望去設定某一位的值為 0,同時其他位都保留為 1,
簡單的作法是直接給暫存器設定一個值:
TIMx->SR=0xFFF7;
這樣的作法設定第 3 位為 0,但是這樣的作法同樣不好看,並且可讀性很差。看看庫函式程式碼中怎樣使用的:
TIMx->SR &= (uint16_t)~TIM_FLAG;
而 TIM_FLAG 是通過巨集定義定義的值:
#define TIM_FLAG ((uint16_t)0x0001)
看這個應該很容易明白,可以直接從巨集定義中看出 TIM_FLAG_ 就是設定的第 0位了,可讀性非常強。二、 define 巨集定義
define 是 C 語言中的預處理命令,它用於巨集定義,可以提高原始碼的可讀性,為程式設計提供
方便。常見的格式:
#define 識別符號 字串
“識別符號”為所定義的巨集名。“字串”可以是常數、表示式、格式串等。例如:#define PLL_M 8
定義識別符號 PLL_M 的值為 8。至於 define 巨集定義的其他一些知識,比如巨集定義帶引數這裡我們就不多講解。
三、 ifdef 條件編譯
微控制器程式開發過程中,經常會遇到一種情況,當滿足某條件時對一組語句進行編譯,而當條件不滿足時則編譯另一組語句。條件編譯命令最常見的形式為:
#ifdef 識別符號
程式段 1
#else
程式段 2
#endif
它的作用是:當識別符號已經被定義過(一般是用#define 命令定義),則對程式段 1 進行編譯,
否則編譯程式段 2。 其中#else 部分也可以沒有,即:
#ifdef
程式段 1
#endif
這個條件編譯在MDK裡面是用得很多的,在stm32f4xx.h這個標頭檔案中經常會看到這樣的語句:
#if defined (STM32F40_41xxx)
STM32F40x 系列和 STM32F41x 系列晶片需要的一些變數定義
#end
而STM32F40_41xxx 則是我們通過 #define 來定義的。
條件編譯經常在標頭檔案中使用,是為了防止一些.c檔案重複包含標頭檔案,導致重複編譯出錯。
具體看我的另外一篇部落格點選開啟連結。
四、 extern 變數申明
C 語言中 extern 可以置於變數或者函式前,以表示變數或者函式的定義在別的檔案中,提示編譯器遇到此變數和函式時在其他模組中尋找其定義。 這裡面要注意,對於 extern 宣告變數可以多
次,但定義只有一次。在我們的程式碼中你會看到看到這樣的語句:
extern u16 USART_RX_STA;
這個語句是申明 USART_RX_STA 變數在其他檔案中已經定義了,在這裡要使用到。所以,你肯定
可以找到在某個地方有變數定義的語句:
u16 USART_RX_STA;
對於函式也是同樣的應用。
五、typedef 類型別名
typedef 用於為現有型別建立一個新的名字,或稱為類型別名,用來簡化變數的定義。typedef 在 MDK 用得最多的就是定義結構體的類型別名和列舉型別了。
struct _GPIO
{
__IO uint32_t MODER;
__IO uint32_t OTYPER;
…
}
上面定義了一個結構體 GPIO,這樣我們定義變數的方式為:struct _GPIO GPIOA;//定義結構體變數 GPIOA
但是這樣很繁瑣,MDK 中有很多這樣的結構體變數需要定義。這裡我們可以為結體定義一個別
名 GPIO_TypeDef,這樣我們就可以在其他地方通過別名 GPIO_TypeDef 來定義結構體變量了。
方法如下:
typedef struct
{
__IO uint32_t MODER;
__IO uint32_t OTYPER;
…
} GPIO_TypeDef
Typedef 為結構體定義一個別名 GPIO_TypeDef,這樣我們可以通過 GPIO_TypeDef 來定義結構體變數:
GPIO_TypeDef _GPIOA,_GPIOB;
這裡的 GPIO_TypeDef 就跟 struct _GPIO 是等同的作用了。
當然typedef在程式程式碼可移植性上也有很大的幫助。比如在一些標頭檔案中使用typedef如下:
typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef signed __INT64 int64_t;
六、結構體
宣告結構體型別:
Struct 結構體名{
成員列表;
}變數名列表;
例如:
Struct U_TYPE {
Int BaudRate
Int WordLength;
}usart1,usart2;
在結構體申明的時候可以定義變數,也可以申明之後定義,方法是:
Struct 結構體名字 結構體變數列表 ;
例如:struct U_TYPE usart1,usart2;
結構體成員變數的引用方法是:
結構體變數名字.成員名
比如要引用 usart1 的成員 BaudRate,方法是:usart1.BaudRate;
結構體指標變數定義也是一樣的,跟其他變數沒有啥區別。
例如:struct U_TYPE *usart3;//定義結構體指標變數 usart1;
結構體指標成員變數引用方法是通過“->”符號實現,比如要訪問 usart3 結構體指標指向的結
構體的成員變數 BaudRate,方法是:
Usart3->BaudRate;
使用結構體的好處是防止函式的入口引數過多,當然也利於增加變數時不用修改函式定義,對於一組描述
同一物件的引數,用結構體使他們形成一個整體,也有利於程式碼的可讀性,不會使變數定義顯得混亂。