C語(yǔ)言是單片機(jī)開(kāi)發(fā)中的必備基礎(chǔ)知識(shí),本文列舉了部分 STM32 學(xué)習(xí)中比較常見(jiàn)的一些C語(yǔ)言基礎(chǔ)知識(shí)。
1位操作
下面我們先講解幾種位操作符,然后講解位操作使用技巧。C語(yǔ)言支持以下六種位操作:
下面,重點(diǎn)講解一下位操作在單片機(jī)開(kāi)發(fā)中的一些實(shí)用技巧。
在不改變其他位的值的狀況下,對(duì)某幾個(gè)位進(jìn)行設(shè)值
這個(gè)場(chǎng)景在單片機(jī)開(kāi)發(fā)中經(jīng)常使用,方法就是我們先對(duì)需要設(shè)置的位用&操作符進(jìn)行清零操作,然后用 | 操作符設(shè)值。
比如,我要改變 GPIOA 的狀態(tài),可以先對(duì)寄存器的值進(jìn)行&清零操作:
?
?
GPIOA->CRL?&=?0xFFFFFF0F;?/*?將第?4~7?位清零?*/
?
?
然后再與需要設(shè)置的值進(jìn)行 | 或運(yùn)算:
?
?
GPIOA->CRL?¥=?0xFFFFFF0F;?/*?設(shè)置相應(yīng)位的值(4),不改變其他位的值?*/
?
?
移位操作提高代碼的可讀性
移位操作在單片機(jī)開(kāi)發(fā)中非常重要,下面是?delay_init?函數(shù)的一行代碼:
?
?
SysTick->CTRL?|=?1?<1;
?
?
這個(gè)操作就是將?CTRL?寄存器的第 1 位(從 0 開(kāi)始算起)設(shè)置為 1,為什么要通過(guò)左移而不是直接設(shè)置一個(gè)固定的值呢?
其實(shí)這是為了提高代碼的可讀性以及可重用性。這行代碼可以很直觀(guān)明了的知道,是將第1位設(shè)置為1。如果寫(xiě)成:
?
?
SysTick->CTRL?|=?0X0002;
?
?
這個(gè)雖然也能實(shí)現(xiàn)同樣的效果,但是可讀性稍差,而且修改也比較麻煩。
~按位取反操作使用技巧
按位取反在設(shè)置寄存器的時(shí)候經(jīng)常被使用,常用于清除某一個(gè)/某幾個(gè)位。下面是?delay_us?函數(shù)的一行代碼:
?
?
SysTick->CTRL?&=?~(1?<0)?;????/*?關(guān)閉SYSTICK?*/
?
?
該代碼可以解讀為:僅設(shè)置?CTRL?寄存器的第 0 位(最低位)為 0,其他位的值保持不變。
同樣我們也不使用按位取反,將代碼寫(xiě)成:
?
?
SysTick->CTRL?&=?0XFFFFFFFE;????????/*?關(guān)閉SYSTICK?*/
?
?
可見(jiàn),前者的可讀性及可維護(hù)性都要比后者好很多。
^按位異或操作使用技巧
該功能非常適合用于控制某個(gè)位翻轉(zhuǎn),常見(jiàn)的應(yīng)用場(chǎng)景就是控制 LED 閃爍,如下:
?
?
GPIOB->ODR?^=?1?<5;
?
?
執(zhí)行一次該代碼,就會(huì)使PB5的輸出狀態(tài)翻轉(zhuǎn)一次,如果我們的 LED 接在 PB5 上,就可以看到 LED 閃爍了。
2define宏定義
define 是 C 語(yǔ)言中的預(yù)處理命令,它用于宏定義(定義的是常量),可以提高源代碼的可讀性,為編程提供方便。常見(jiàn)的格式:
?
?
#define????標(biāo)識(shí)符????字符串
?
?
“標(biāo)識(shí)符”為所定義的宏名。“字符串”可以是常數(shù)、表達(dá)式、格式串等。例如:
?
?
#define??HSE_VALUE???8000000U
?
?
定義標(biāo)識(shí)符?HSE_VALUE?的值為 8000000,數(shù)字后的?U?表示?unsigned?的意思。至于?define?宏定義的其他一些知識(shí),比如宏定義帶參數(shù),這里就不多講解了。
3ifdef條件編譯
單片機(jī)程序開(kāi)發(fā)過(guò)程中,經(jīng)常會(huì)遇到一種情況,當(dāng)滿(mǎn)足某條件時(shí)對(duì)一組語(yǔ)句進(jìn)行編譯,而當(dāng)條件不滿(mǎn)足時(shí)則編譯另一組語(yǔ)句。
條件編譯命令最常見(jiàn)的形式為:
?
?
#ifdef??標(biāo)識(shí)符 程序段1 #else 程序段2#endif
?
?
它的作用是:當(dāng)標(biāo)識(shí)符已經(jīng)被定義過(guò)(一般是用?#define?命令定義),則對(duì)程序段1進(jìn)行編譯,否則編譯程序段2。
其中?#else?部分也可以沒(méi)有,即:
?
?
#ifdef???? 程序段1 #endif
?
?
條件編譯在 HAL 庫(kù)里面是用得很多,在?stm32mp1xx_hal_conf.h?這個(gè)頭文件中經(jīng)常會(huì)看到這樣的語(yǔ)句:
?
?
#if?!defined??(HSE_VALUE)?????? #define?HSE_VALUE???????24000000U???? #endif
?
?
如果沒(méi)有定義?HSE_VALUE?這個(gè)宏,則定義?HSE_VALUE?宏,并且?HSE_VALUE的值為 24000000U。條件編譯也是 C 語(yǔ)言的基礎(chǔ)知識(shí)吧。
這里提一下,24000000U 中的 U 表示無(wú)符號(hào)整型,常見(jiàn)的,UL 表示無(wú)符號(hào)長(zhǎng)整型,F(xiàn) 表示浮點(diǎn)型。
這里加了 U 以后,系統(tǒng)編譯時(shí)就不進(jìn)行類(lèi)型檢查,直接以 U 的形式把值賦給某個(gè)對(duì)應(yīng)的內(nèi)存,如果超出定義變量的范圍,則截取。
4extern變量聲明
C語(yǔ)言中?extern?可以置于變量或者函數(shù)前,以表示變量或者函數(shù)的定義在別的文件中,提示編譯器遇到此變量和函數(shù)時(shí)在其他模塊中尋找其定義。
這里面要注意,對(duì)于?extern?申明變量可以多次,但定義只有一次。在我們的代碼中你會(huì)看到看到這樣的語(yǔ)句:
?
?
extern?uint16_t?g_usart_rx_sta;
?
?
這個(gè)語(yǔ)句是聲明?g_usart_rx_sta?變量在其他文件中已經(jīng)定義了,在這里要使用到。
所以,你肯定可以找到在某個(gè)地方有變量定義的語(yǔ)句:
?
?
?uint16_t?g_usart_rx_sta;
?
?
extern?的使用比較簡(jiǎn)單,但是也會(huì)經(jīng)常用到,需要掌握。
5typedef類(lèi)型別名
typedef?用于為現(xiàn)有類(lèi)型創(chuàng)建一個(gè)新的名字,或稱(chēng)為類(lèi)型別名,用來(lái)簡(jiǎn)化變量的定義。typedef?在 HAL 庫(kù)用得最多的就是定義結(jié)構(gòu)體的類(lèi)型別名和枚舉類(lèi)型了。
?
?
struct?_GPIO???? { ?__IO?uint32_t?CRL;???????? ?__IO?uint32_t?CRH; ??… };
?
?
定義了一個(gè)結(jié)構(gòu)體?GPIO,這樣我們定義結(jié)構(gòu)體變量的方式為:
?
?
struct??_GPIO??gpiox;???????/*?定義結(jié)構(gòu)體變量gpiox?*/
?
?
但這樣很繁瑣,HAL?庫(kù)中有很多這樣的結(jié)構(gòu)體變量需要定義。
這里我們可以為結(jié)構(gòu)體定義一個(gè)別名?GPIO_TypeDef,這樣我們就可以在其他地方通過(guò)別名?GPIO_TypeDef?來(lái)定義結(jié)構(gòu)體變量了,方法如下:
?
?
typedef?struct { ?__IO?uint32_t?CRL; ??__IO?uint32_t?CRH; ??…???? }?GPIO_TypeDef;
?
?
Typedef?為結(jié)構(gòu)體定義一個(gè)別名?GPIO_TypeDef,這樣我們可以通過(guò)?GPIO_TypeDef?來(lái)定義結(jié)構(gòu)體變量:
?
?
GPIO_TypeDef?gpiox;
?
?
這里的?GPIO_TypeDef?就跟?struct _GPIO?是等同的作用了,但是?GPIO_TypeDef?使用起來(lái)方便很多。
編輯:黃飛
?
評(píng)論