下面分享關於位操作的一些筆記:
首先,以下是按位運算子:
在嵌入式程式設計
中,常常需要對一些暫存器進行配置,有的情況下需要改變乙個位元組中的某一位或者幾位,但是又不想改變其它位原有的值,這時就可以使用按位運算子進行操作。下面進行舉例說明,假如有乙個8位的test暫存器:
當我們要設定第0位bit0的值為1時,可能會這樣進行設定:
test = 0x01;
但是,這樣設定是不夠準確的,因為這時候已經同時操作到了高7位:bit1~bit7
,如果這高7位沒有用到的話,這麼設定沒有什麼影響;但是,如果這7位正在被使用,結果就不是我們想要的了。
在這種情況下,我們就可以借用按位操作運算子進行配置。
對於二進位制位操作來說,不管該位原來的值是0還是1,它跟0進行&運算,得到的結果都是0,而跟1進行&運算,將保持原來的值不變;不管該位原來的值是0還是1,它跟1進行|運算,得到的結果都是1,而跟0進行|運算,將保持原來的值不變。
所以,此時可以設定為:
test = test | 0x01;
其意義為:test暫存器
的高7位均不變,最低位變成1了。在實際程式設計中,常改寫為:
test |= 0x01;
這種寫法可以一定程度上簡化**,是 c 語言常用的一種程式設計風格。設定暫存器的某一位還有另一種操作方法,以上的等價方法如:
test |= (0x01 << 0);
第幾位要置1就左移幾位。
同樣的,要給test
的低4位清0,高4位保持不變,可以進行如下配置:
test &= 0xf0;
(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位 */
示例:
(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位 */
示例:
(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資料的一些操作進行總結,其它位數的資料類似,可根據需要進行修改。
stm32有幾套韌體庫,這些韌體庫函式以函式的形式進行1層或者多層封裝(軟體開發中很重要的思想之一:分層思想),但是到了最裡面的一層就是對暫存器的配置。我們平時都比較喜歡韌體庫來開發,大概是因為韌體庫用起來比較簡單,用韌體庫寫出來的**比較容易閱讀。最近一段時間一直在配置暫存器,越發地發現使用暫存器來進行一些外設的配置也是很容易懂的。使用暫存器的方式程式設計無非就是往暫存器的某些位置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操作) */
貌似第二種方法更麻煩?還得去細心地去構造乙個資料。
這個標頭檔案中存放的就是外設暫存器的一些位配置。
所以我們的方法二等價於:
gpioa->odr |= gpio_odr_odr10; /* pa10輸出高(置1操作) */
gpioa->odr &= ~gpio_odr_odr10; /* pa10輸出低(清0操作) */
兩種方法都是很好的方法,但方法一似乎更好理解。
配置連續幾位的方法也是一樣的,就不介紹了。簡單介紹配置不連續位的方法,以tim1的cr1暫存器為例:
設定cen位為1、設定cms[1:0]位為01、設定ckd[1:0]位為10:
tim1->cr1 |= (0x1 << 1)| (0x1 << 5) |(0x2 << 8);
這是組合的寫法。當然,像上面一樣拆開來寫也是可以的。
(2)判斷標誌位
以狀態暫存器(usart_sr) 為例:
判斷rxne是否被置位:
/* 資料暫存器非空,rxne標誌置位 */
if (usart1->sr & (1 << 5))
或者:
/* 資料暫存器非空,rxne標誌置位 */
if (usart1->sr & usart_sr_rxne)
以上就是本次關於位操作的一點總結筆記,有必要掌握。雖然說在用stm32的時候有庫函式可以用,但是最接近晶元內部原理的還是暫存器。有可能之後有用到其它晶元沒有像st這樣把暫存器相關配置封裝得那麼好,那就不得不直接操控暫存器了。
此外,使用庫函式的方式**占用空間大,用暫存器的話,**占用空間小。之前有個需求,我們能用的flash的空間大小只有4kb,遇到類似這樣的情況就不能那麼隨性的用庫函式了。
最後,應用的時候當然是怎麼簡單就怎麼用。學從「難」處學,用從易處用,與君共勉~
嵌入式中C語言的位操作
位運算構建特定的二進位制數 技術公升級 使用巨集定義完成位運算 總結在操作中使用 將暫存器某些 特定位變成0,但是不影響其他位,可以進行如下操作,假設原來的暫存器reg1中的值為0xaaaaaaaa,希望將bit8 bit15清零並且其他位不進行改變,將這個數和0xffff00ff進行位與即可。re...
嵌入式中C和彙編的一些技巧
arm彙編部分 a.條件執行 cmp r0,5 beq bypass add r1,r1,r0 sub r1,r1,r2 bypass 可以替代為 cmp r0,5 addne r1,r1,r0 subne r1,r1,r2 如果被跳過的指令序列並不進行複雜的操作,使用條件執行都要比使用轉移好,因為...
嵌入式程式設計 c位操作
在學習c語言位操作前需要具備十六進製制和二進位制的知識以及從二進位制與十六進製制的相互轉換,相應的教程請移步新增鏈結描述 現在掌握了十六進製制和二進位制之間的相互轉換知識,我們可以從c中的按位 或位級別 運算開始。基本上有6種型別的按位運算子。這些是 1.以 表示 或 運算子 2.以 表示 與 運算...