嵌入式、C語言位操做的一些技巧彙總

下面分享關於位操做的一些筆記:編程

1、位操做簡單介紹

首先,如下是按位運算符:函數

嵌入式編程中,經常須要對一些寄存器進行配置,有的狀況下須要改變一個字節中的某一位或者幾位,可是又不想改變其它位原有的值,這時就可使用按位運算符進行操做。下面進行舉例說明,假若有一個8位的TEST寄存器:ui

當咱們要設置第0位bit0的值爲1時,可能會這樣進行設置:3d

TEST = 0x01;

可是,這樣設置是不夠準確的,由於這時候已經同時操做到了高7位:bit1~bit7,若是這高7位沒有用到的話,這麼設置沒有什麼影響;可是,若是這7位正在被使用,結果就不是咱們想要的了。code

在這種狀況下,咱們就能夠借用按位操做運算符進行配置。blog

對於二進制位操做來講,無論該位原來的值是0仍是1,它跟0進行&運算,獲得的結果都是0,而跟1進行&運算,將保持原來的值不變;無論該位原來的值是0仍是1,它跟1進行|運算,獲得的結果都是1,而跟0進行|運算,將保持原來的值不變。開發

因此,此時能夠設置爲:it

TEST = TEST | 0x01;

其意義爲:TEST寄存器的高7位均不變,最低位變成1了。在實際編程中,常改寫爲:class

TEST |= 0x01;

這種寫法能夠必定程度上簡化代碼,是 C 語言經常使用的一種編程風格。設置寄存器的某一位還有另外一種操做方法,以上的等價方法如:原理

TEST |= (0x01 << 0);

第幾位要置1就左移幾位。

一樣的,要給TEST的低4位清0,高4位保持不變,能夠進行以下配置:

TEST &= 0xF0;

2、嵌入式中位操做一些常見用法

一、一個32bit數據的位、字節讀取操做

(1)獲取單字節:

#define GET_LOW_BYTE0(x)    ((x >>  0) & 0x000000ff)    /* 獲取第0個字節 */
#define GET_LOW_BYTE1(x)    ((x >>  8) & 0x000000ff)    /* 獲取第1個字節 */
#define GET_LOW_BYTE2(x)    ((x >> 16) & 0x000000ff)    /* 獲取第2個字節 */
#define GET_LOW_BYTE3(x)    ((x >> 24) & 0x000000ff)    /* 獲取第3個字節 */

示例:

(2)獲取某一位:

#define GET_BIT(x, bit) ((x & (1 << bit)) >> bit)   /* 獲取第bit位 */

示例:

二、一個32bit數據的位、字節清零操做

(1)清零某個字節:

#define CLEAR_LOW_BYTE0(x)  (x &= 0xffffff00)   /* 清零第0個字節 */
#define CLEAR_LOW_BYTE1(x)  (x &= 0xffff00ff)   /* 清零第1個字節 */
#define CLEAR_LOW_BYTE2(x)  (x &= 0xff00ffff)   /* 清零第2個字節 */
#define CLEAR_LOW_BYTE3(x)  (x &= 0x00ffffff)   /* 清零第3個字節 */

示例:

(2)清零某一位:

#define CLEAR_BIT(x, bit)   (x &= ~(1 << bit))  /* 清零第bit位 */

示例:

三、一個32bit數據的位、字節置1操做

(1)置某個字節爲1:

#define SET_LOW_BYTE0(x)    (x |= 0x000000ff)   /* 第0個字節置1 */   
#define SET_LOW_BYTE1(x)    (x |= 0x0000ff00)   /* 第1個字節置1 */   
#define SET_LOW_BYTE2(x)    (x |= 0x00ff0000)   /* 第2個字節置1 */   
#define SET_LOW_BYTE3(x)    (x |= 0xff000000)   /* 第3個字節置1 */

示例:

(2)置位某一位:

#define SET_BIT(x, bit) (x |= (1 << bit))   /* 置位第bit位 */

四、判斷某一位或某幾位連續位的值

(1)判斷某一位的值

舉例說明:判斷0x68第3位的值。

也就是說,要判斷第幾位的值,if裏就左移幾位(固然別過頭了)。在嵌入式編程中,可經過這樣的方式來判斷寄存器的狀態位是否被置位。

(2)判斷某幾位連續位的值

/* 獲取第[n:m]位的值 */
#define BIT_M_TO_N(x, m, n)  ((unsigned int)(x << (31-(n))) >> ((31 - (n)) + (m)))

示例:

這是一個查詢連續狀態位的例子,由於有些狀況不止有0、1兩種狀態,可能會有多種狀態,這種狀況下就能夠用這種方法來取出狀態位,再去執行相應操做。

以上是對32bit數據的一些操做進行總結,其它位數的數據相似,可根據須要進行修改。

3、STM32寄存器配置

STM32有幾套固件庫,這些固件庫函數以函數的形式進行1層或者多層封裝(軟件開發中很重要的思想之一:分層思想),可是到了最裏面的一層就是對寄存器的配置。咱們平時都比較喜歡固件庫來開發,大概是由於固件庫用起來比較簡單,用固件庫寫出來的代碼比較容易閱讀。最近一段時間一直在配置寄存器,愈加地發現使用寄存器來進行一些外設的配置也是很容易懂的。使用寄存器的方式編程無非就是往寄存器的某些位置一、清零以及對寄存器一些狀態位進行判斷、讀取寄存器的內容等。

這些基本操做在上面的例子中已經有介紹,咱們依舊以實例來鞏固上面的知識點(以STM32F1xx爲例):

(1)寄存器配置

看一下GPIO功能的端口輸出數據寄存器 (GPIOx_ODR) (x=A..E) :

假設咱們要讓PA10引腳輸出高、輸出低,能夠這麼作:

方法一:

GPIOA->ODR |= 1 << 10;      /* PA10輸出高(置1操做) */
GPIOA->ODR &= ~(1 << 10);  /* PA10輸出低(清0操做) */

也可用咱們上面的置位、清零的宏定義:

SET_BIT(GPIOA->ODR, 10);    /* PA10輸出高(置1操做) */
CLEAR_BIT(GPIOA->ODR, 10);  /* PA10輸出低(清0操做) */

方法二:

GPIOA->ODR |= (uint16_t)0x0400;   /* PA10輸出高(置1操做) */
GPIOA->ODR &= ~(uint16_t)0x0400;  /* PA10輸出低(清0操做) */

貌似第二種方法更麻煩?還得去細心地去構造一個數據。

可是,其實第二種方法實際上是ST推薦咱們用的方法,爲何這麼說呢?由於ST官方已經把這些咱們要用到的值給咱們配好了,在stm32f10x.h中:

這個頭文件中存放的就是外設寄存器的一些位配置。

因此咱們的方法二等價於:

GPIOA->ODR |= GPIO_ODR_ODR10;   /* PA10輸出高(置1操做) */
GPIOA->ODR &= ~GPIO_ODR_ODR10;  /* PA10輸出低(清0操做) */

兩種方法都是很好的方法,但方法一彷佛更好理解。

配置連續幾位的方法也是同樣的,就不介紹了。簡單介紹配置不連續位的方法,以TIM1的CR1寄存器爲例:

設置CEN位爲一、設置CMS[1:0]位爲0一、設置CKD[1:0]位爲10:

TIM1->CR1 |= (0x1 << 1)| (0x1 << 5) |(0x2 << 8);

這是組合的寫法。固然,像上面同樣拆開來寫也是能夠的。

(2)判斷標誌位

以狀態寄存器(USART_SR) 爲例:

判斷RXNE是否被置位:

/* 數據寄存器非空,RXNE標誌置位 */
if (USART1->SR & (1 << 5))
{
    /* 其它代碼 */
    
    USART1->SR &= ~(1 << 5);  /* 清零RXNE標誌 */
}

或者:

/* 數據寄存器非空,RXNE標誌置位 */
if (USART1->SR & USART_SR_RXNE)
{
    /* 其它代碼 */
    
    USART1->SR &= ~USART_SR_RXNE;  /* 清零RXNE標誌 */
}

4、總結

以上就是本次關於位操做的一點總結筆記,有必要掌握。雖說在用STM32的時候有庫函數能夠用,可是最接近芯片內部原理的仍是寄存器。有可能以後有用到其它芯片沒有像ST這樣把寄存器相關配置封裝得那麼好,那就不得不直接操控寄存器了。

此外,使用庫函數的方式代碼佔用空間大,用寄存器的話,代碼佔用空間小。以前有個需求,咱們能用的Flash的空間大小隻有4KB,遇到相似這樣的狀況就不能那麼隨性的用庫函數了。

最後,應用的時候固然是怎麼簡單就怎麼用。學從「難」處學,用從易處用,與君共勉~

END:以上筆記中若有錯誤,歡迎指出!謝謝

相關文章
相關標籤/搜索