1、在不改變其他位的值的狀況下,對(duì)某幾個(gè)位進(jìn)行設(shè)值
這個(gè)場(chǎng)景在單片機(jī)開發(fā)中經(jīng)常使用,方法就是先對(duì)需要設(shè)置的位用&操作符進(jìn)行清零操作, 然后用|操作符設(shè)值。
比如我要改變 GPIOA 的 CRL 寄存器 bit6(第 6 位)的值為 1,可以先對(duì)寄 存器的值進(jìn)行&清零操作:
GPIOA->CRL &= 0XFFFFFFBF; /* 將第 bit6 清 0 */
?然后再與需要設(shè)置的值進(jìn)行|或運(yùn)算:
GPIOA->CRL |= 0X00000040; /* 設(shè)置 bit6 的值為 1,不改變其他位的值 */
?2、移位操作提高代碼的可讀性
?移位操作在單片機(jī)開發(fā)中非常重要,下面是 delay_init 函數(shù)的一行代碼:
SysTick->CTRL |= 1 << 1;
?這個(gè)操作就是將 CTRL 寄存器的第 1 位(從 0 開始算起)設(shè)置為 1,為什么要通過左移而 不是直接設(shè)置一個(gè)固定的值呢?其實(shí)這是為了提高代碼的可讀性以及可重用性。這行代碼可以 很直觀明了的知道,是將第 1 位設(shè)置為 1。如果寫成:
SysTick->CTRL |= 0X0002;
?這個(gè)雖然也能實(shí)現(xiàn)同樣的效果,但是可讀性稍差,而且修改也比較麻煩。
3、~按位取反操作使用技巧 ?
按位取反在設(shè)置寄存器的時(shí)候經(jīng)常被使用,常用于清除某一個(gè) /某幾個(gè)位。下面是 delay_us
函數(shù)的一行代碼:
SysTick->CTRL &= ~(1 << 0) ; /* 關(guān)閉 SYSTICK */
該代碼可以解讀為 僅設(shè)置 CTRL 寄存器的第 0 位(最低位)為 0,其他位的值保持不變。 同樣我們也不使用按位取反,將代碼寫成:
SysTick->CTRL &= 0XFFFFFFFE; /* 關(guān)閉 SYSTICK */
?可見前者的可讀性,及可維護(hù)性都要比后者好很多。
4、^按位異或操作使用技巧?
該功能非常適合用于控制某個(gè)位翻轉(zhuǎn),常見的應(yīng)用場(chǎng)景就是控制 LED 閃爍,如:
GPIOB->ODR ^= 1 << 5;
執(zhí)行一次該代碼,就會(huì)使 PB5 的輸出狀態(tài)翻轉(zhuǎn)一次,如果我們的 LED 接在 PB5 上,就可 以看到 LED 閃爍了。?
5、ifdef 條件編譯
單片機(jī)程序開發(fā)過程中,經(jīng)常會(huì)遇到一種情況,當(dāng)滿足某條件時(shí)對(duì)一組語句進(jìn)行編譯,而 當(dāng)條件不滿足時(shí)則編譯另一組語句。條件編譯命令最常見的形式為:
#ifdef 標(biāo)識(shí)符
程序段 1
#else
程序段 2
#endif
它的作用是:當(dāng)標(biāo)識(shí)符已經(jīng)被定義過(一般是用#define 命令定義),則對(duì)程序段 1 進(jìn)行編譯, 否則編譯程序段 2。 其中#else 部分也可以沒有,即:
#ifdef
程序段 1
#endif
?條件編譯在 MDK 里面是用得很多,在 stm32f10x.h 這個(gè)頭文件中經(jīng)常會(huì)看到這樣的語句:
#if !defined (STM32F1) //在這里也可以換成是#ifndef
#define STM32F1
#endif
?如果沒有定義 STM32F1 這個(gè)宏,則定義 STM32F1 宏。條件編譯也是 c 語言的基礎(chǔ)知識(shí), 這里也就點(diǎn)到為止吧。
6、extern 外部申明
C 語言中 extern 可以置于變量或者函數(shù)前,以表示變量或者函數(shù)的定義在別的文件中,提示編 譯器遇到此變量和函數(shù)時(shí)在其他模塊中尋找其定義。這里面要注意,對(duì)于 extern 申明變量可以多 次,但定義只有一次。在我們的代碼中你會(huì)看到這樣的語句: ?
extern uint16_t g_usart_rx_sta;
?這個(gè)語句是申明 g_usart_rx_sta 變量在其他文件中已經(jīng)定義了,在這里要使用到。所以,你 肯定可以找到在某個(gè)地方有變量定義的語句:
uint16_t g_usart_rx_sta;
?在這里大家可以覺得有點(diǎn)繞,但是其思想就是:
例如我在filec文件當(dāng)中定義了uint16_t g_usart_rx_sta;
然后這個(gè)時(shí)候我要在filec文件當(dāng)中使用這個(gè)變量,就是說我想直接從filec文件當(dāng)中將uint16_t g_usart_rx_sta取過來,那么我可以在filec文件當(dāng)中在變量前面加上
extern uint16_t g_usart_rx_sta;
7、typedef 類型別名
typedef 用于為現(xiàn)有類型創(chuàng)建一個(gè)新的名字,或稱為類型別名,用來簡化變量的定義。typedef
在 MDK 用得最多的就是定義結(jié)構(gòu)體的類型別名和枚舉類型了。
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 */
?但是這樣很繁瑣,MDK 中有很多這樣的結(jié)構(gòu)體變量需要定義。這里我們可以為結(jié)體定義一 個(gè)別名 GPIO_TypeDef,這樣我們就可以在其他地方通過別名 GPIO_TypeDef 來定義結(jié)構(gòu)體變 量了,方法如下:
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
…
} GPIO_TypeDef;
Typedef 為結(jié)構(gòu)體定義一個(gè)別名 GPIO_TypeDef,這樣我們可以通過 GPIO_TypeDef 來定義 結(jié)構(gòu)體變量:
GPIO_TypeDef gpiox;
這里的 GPIO_TypeDef 就跟 struct _GPIO 是等同的作用了,但是 GPIO_TypeDef 使用起來 方便很多。
8、結(jié)構(gòu)體
經(jīng)常很多用戶提到,他們對(duì)結(jié)構(gòu)體使用不是很熟悉,但是 MDK 中太多地方使用結(jié)構(gòu)體以及 結(jié)構(gòu)體指針,這讓他們一下子摸不著頭腦,學(xué)習(xí) STM32 的積極性大大降低,其實(shí)結(jié)構(gòu)體并不是 那么復(fù)雜,這里我們稍微提一下結(jié)構(gòu)體的一些知識(shí),還有一些知識(shí)我們會(huì)在下面的“寄存器映 射”中講到一些。
聲明結(jié)構(gòu)體類型:
struct 結(jié)構(gòu)體名
{
成員列表;
}變量名列表;
例如:
struct U_TYPE
{
int BaudRate
int WordLength;
}usart1, usart2;
在結(jié)構(gòu)體申明的時(shí)候可以定義變量,也可以申明之后定義,方法是:
struct 結(jié)構(gòu)體名字 結(jié)構(gòu)體變量列表 ;
例如:
struct U_TYPE usart1,usart2;
結(jié)構(gòu)體成員變量的引用方法是:
結(jié)構(gòu)體變量名字.成員名?
比如要引用 usart1 的成員 BaudRate,方法是:usart1.BaudRate;結(jié)構(gòu)體指針變量定義也是 一樣的,跟其他變量沒有啥區(qū)別。
例如:
struct U_TYPE *usart3; /* 定義結(jié)構(gòu)體指針變量 usart3 */
結(jié)構(gòu)體指針成員變量引用方法是通過“->”符號(hào)實(shí)現(xiàn),比如要訪問 usart3 結(jié)構(gòu)體指針指向 的結(jié)構(gòu)體的成員變量 BaudRate,方法是:
usart3->BaudRate;
上面講解了結(jié)構(gòu)體和結(jié)構(gòu)體指針的一些知識(shí),其他的什么初始化這里就不多講解了。 講到 這里,有人會(huì)問,結(jié)構(gòu)體到底有什么作用呢?為什么要使用結(jié)構(gòu)體呢?下面我們將簡單的通過 一個(gè)實(shí)例回答一下這個(gè)問題。
在我們單片機(jī)程序開發(fā)過程中,經(jīng)常會(huì)遇到要初始化一個(gè)外設(shè)比如串口,它的初始化狀態(tài) 是由幾個(gè)屬性來決定的,比如串口號(hào),波特率,極性,以及模式。對(duì)于這種情況,在我們沒有 學(xué)習(xí)結(jié)構(gòu)體的時(shí)候,我們一般的方法是:
void usart_init(uint8_t usartx, uiut32_t BaudRate, uint32_t Parity,
uint32_t Mode);
這種方式是有效的同時(shí)在一定場(chǎng)合是可取的。但是試想,如果有一天,我們希望往這個(gè)函 數(shù)里面再傳入一個(gè)/幾個(gè)參數(shù),那么勢(shì)必我們需要修改這個(gè)函數(shù)的定義,重新加入新的入口參數(shù), 隨著開發(fā)不斷的增多,那么是不是我們就要不斷的修改函數(shù)的定義呢?這是不是給我們開發(fā)帶 來很多的麻煩?那又怎樣解決這種情況呢?
我們使用結(jié)構(gòu)體參數(shù),就可以在不改變?nèi)肟趨?shù)的情況下,只需要改變結(jié)構(gòu)體的成員變量, 就可以達(dá)到改變?nèi)肟趨?shù)的目的。
結(jié)構(gòu)體就是將多個(gè)變量組合為一個(gè)有機(jī)的整體,上面的函數(shù),usartx,BaudRate,Parity, Mode
等這些參數(shù),他們對(duì)于串口而言,是一個(gè)有機(jī)整體,都是來設(shè)置串口參數(shù)的,所以我們可以將 他們通過定義一個(gè)結(jié)構(gòu)體來組合在一個(gè)。MDK 中是這樣定義的:
typedef struct
{
uint32_t BaudRate;
uint32_t WordLength;
uint32_t StopBits;
uint32_t Parity;
uint32_t Mode;
uint32_t HwFlowCtl;
uint32_t OverSampling;
} UART_InitTypeDef;
?這樣,我們?cè)诔跏蓟诘臅r(shí)候入口參數(shù)就可以是 USART_InitTypeDef 類型的變量或者指 針變量了,于是我們可以改為:
void usart_init(UART_InitTypeDef *huart);
這樣,任何時(shí)候,我們只需要修改結(jié)構(gòu)體成員變量,往結(jié)構(gòu)體中間加入新的成員變量,而 不需要修改函數(shù)定義就可以達(dá)到修改入口參數(shù)同樣的目的了。這樣的好處是不用修改任何函數(shù) 定義就可以達(dá)到增加變量的目的。 理解了結(jié)構(gòu)體在這個(gè)例子中間的作用嗎?在以后的開發(fā)過程中,如果你的變量定義過多, 如果某幾個(gè)變量是用來描述某一個(gè)對(duì)象,你可以考慮將這些變量定義在結(jié)構(gòu)體中,這樣也許可 以提高你的代碼的可讀性。
使用結(jié)構(gòu)體組合參數(shù),可以提高代碼的可讀性,不會(huì)覺得變量定義混亂。當(dāng)然結(jié)構(gòu)體的作 用就遠(yuǎn)遠(yuǎn)不止這個(gè)了,同時(shí),MDK 中用結(jié)構(gòu)體來定義外設(shè)也不僅僅只是這個(gè)作用,這里我們只 是舉一個(gè)例子,通過最常用的場(chǎng)景,讓大家理解結(jié)構(gòu)體的一個(gè)作用而已。后面一節(jié)我們還會(huì)講 解結(jié)構(gòu)體的一些其他知識(shí)。
9、指針
指針是一個(gè)值指向地址的變量(或常量),其本質(zhì)是指向一個(gè)地址,從而可以訪問一片內(nèi) 存區(qū)域。在編寫 STM32 代碼的時(shí)候,或多或少都要用到指針,它可以使不同代碼共享同一片內(nèi) 存數(shù)據(jù),也可以用作復(fù)雜的鏈接性的數(shù)據(jù)結(jié)構(gòu)的構(gòu)建,比如鏈表,鏈?zhǔn)蕉鏄涞?,而且,有?地方必須使用指針才能實(shí)現(xiàn),比如內(nèi)存管理等。
申明指針我們一般以 p 開頭,如:
char * p_str = “This is a test!”;
這樣,我們就申明了一個(gè) p_str 的指針,它指向 This is a test!這個(gè)字符串的首地址。我們 編寫如下代碼:
int main(void)
{
uint8_t temp = 0X88; /* 定義變量 temp */
uint8_t *p_num = &temp; /* 定義指針 p_num,指向 temp 的地址 */
HAL_Init(); /* 初始化 HAL 庫 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 設(shè)置時(shí)鐘,72M */
delay_init(72); /* 延時(shí)初始化 */
usart_init(115200); /* 初始化串口 */
printf("temp:0X%X\r\n", temp); /* 打印 temp 的值 */
printf("*p_num: 0X %X\r\n", *p_num); /* 打印*p_num 的值 */
printf("p_num: 0X %X\r\n", (uint32_t)p_num); /* 打印 p_num 的值 */
printf("&p_num: 0X %X\r\n", (uint32_t)&p_num); /* 打印&p_num 的值 */
while (1);
}
此代碼的輸出結(jié)果為:
p_num:是uint8_t類型指針,指向temp變量的地址,其值等于temp變量的地址。
*p_num:取p_num指向的地址所存儲(chǔ)的值,即temp的值。
&p_num:取p_num指針的地址,即指針自身的地址。
以上,就是指針的簡單使用和基本概念說明,指針的詳細(xì)知識(shí)和使用范例大家可以百度學(xué)習(xí),網(wǎng)上有非常多的資料可供參考。指針是C語言的精髓,在后續(xù)的代碼中我們將會(huì)大量用到各種指針,大家務(wù)必好好學(xué)習(xí)和了解指針的使用。文章來源:http://www.zghlxwxcb.cn/news/detail-429860.html
?文章來源地址http://www.zghlxwxcb.cn/news/detail-429860.html
到了這里,關(guān)于STM32 基礎(chǔ)知識(shí)入門 (C語言基礎(chǔ)鞏固)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!