1. 程式人生 > >關於嵌入式開發的C語言基礎總結

關於嵌入式開發的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;

使用結構體的好處是防止函式的入口引數過多,當然也利於增加變數時不用修改函式定義,對於一組描述

同一物件的引數,用結構體使他們形成一個整體,也有利於程式碼的可讀性,不會使變數定義顯得混亂。