嵌入式寄存器位操作

位操作

在嵌入式編程中,常常需要涉及到寄存器的位操作,使能某個功能,設(shè)置 gpio select, 配置外設(shè)等。

回顧一下 C 語言的位運算

  • ~ 按位取反運算符,按二進制位進行"取反"運算。~0b001=0b110; ~0b00=0b11; 1=-2;0=-1
  • ^ 按位異或運算符,按二進制位進行"異或"運算。0^0=0; 0^1=1; 1^0=1; 1^1=0;
  • | 按位或運算符,按二進制位進行"或"運算。0|0=0; 0|1=1; 1|0=1; 1|1=1;
  • & 按位與操作,按二進制位進行"與"運算。0&0=0; 0&1=0; 1&0=0; 1&1=1;
  • << 二進制左移運算符。將一個運算對象的各二進制位全部左移若干位(左邊的二進制位丟棄,右邊補 0)。
  • >> 二進制右移運算符。將一個數(shù)的各二進制位全部右移若干位,正數(shù)左補 0,負(fù)數(shù)左補 1,右邊丟棄。

對寄存器的訪問是通過內(nèi)存地址進行,一個地址可訪問一個字 4byte(4*8=32bit) 的寄存器數(shù)據(jù)。

  • 字 = 4byte
  • 半字 = 2byte
  • 字節(jié) = byte = 8bit
  • 位 = 1/8 字節(jié)

需要注意的是,所有涉及寄存器值修改的操作。必須遵循以下三步流程:

  1. 獲取該地址上的值
  2. 按需修改 bit 位上的值
  3. 將修改后的值設(shè)置至該地址

寄存器訪問需要注意大端小端的問題,大小端問題也稱為字節(jié)序問題。關(guān)于大小端問暫時不做闡述。

  • Big-Endian: 低地址存放高位;
  • Little-Endian: 低地址存放低位;

bit 操作

本節(jié)描述了如何將一個 bit 設(shè)置成 0 或者 1。

若需要修改 bit 31 為 1,則可以 a = a|(1<<31);

  • 對于 bit 16 ,無論其值為何,均會被設(shè)置為 1
  • 對于 其余各位,無論其值為何均保持不變。
0x ???? ???? ???? ???? ???? ???? ???? ????
|
0x 0100 0000 0000 0000 0000 0000 0000 0000
=
0x ?1?? ???? ???? ???? ???? ???? ???? ????

若需要修改 bit 31 為 0,稍微復(fù)雜一點,可以 a = a& (~(1<<31))

  • 使用 ~(1<<31) 構(gòu)造操作數(shù)
  • & 操作后,對于 bit31,無論其值為何,均會被設(shè)置為 0
  • & 操作后,對于其余各位,無論其值為何均保持不變。
0x ???? ???? ???? ???? ???? ???? ???? ????
&
(
~
0x 0100 0000 0000 0000 0000 0000 0000 0000
)
=
0x ?0?? ???? ???? ???? ???? ???? ???? ????

0x ???? ???? ???? ???? ???? ???? ???? ????
&
0x 1011 1111 1111 1111 1111 1111 1111 1111
=
0x ?0?? ???? ???? ???? ???? ???? ???? ????

示例

以配置 uart0 為例,配置 uart 中有如下兩個步驟,下文以這兩個步驟進行舉例:

  1. 使能 uart 時鐘
  2. 配置 gpio 選擇 uart 模式

為了使能 uart0 時鐘,需要設(shè)置寄存器BUS_CLK_GATING_REG3(默認(rèn)值 0x00000000) 第 16 bit 為 1

0b 0000 0000 0000 0000 0000 0000 0000 0000
;設(shè)置 bit 16 為1
0b 0000 0000 0000 0000 1000 0000 0000 0000
=
0x 0    0    0    0    8    0    0    0

可以直接將值 0x00008000 設(shè)置至寄存器 BUS_CLK_GATING_REG3

writel(BUS_CLK_GATING_REG3,0x8000)

但是!這樣做會有一些問題,我們僅需要操作的是 bit16, 如果其他 bit 上已經(jīng)配置了值,經(jīng)過上述操作后,就會被覆蓋。
所以在進行修改時要確保其他位不被改變。

所以需要使用位運算進行修改。并且必須遵循寄存器修改三步驟。若需要修改 bit 16 為 1,則可以 a = a|(1<<16);

  • 對于 bit 16 ,無論其值為何,均會被設(shè)置為 1
  • 對于 其余各位,無論其值為何均保持不變。

那么上述操作可以變成:

reg = readl(BUS_CLK_GATING_REG3)
reg = reg & (1<<16)
writel(BUS_CLK_GATING_REG3,reg)

解釋一下:

  1. 使用 readl 宏獲取到該寄存器值,如果沒有宏定義也可以直接使用 *((volatile unsigned int *)(addr)),addr 為內(nèi)存地址。
  2. 使用位操作 & 對 寄存器值和操作數(shù) (1<<16) 進行按位與運算,將第 16 位設(shè)置為 1. 如果對 1<<16 有疑問,請看例 2.
  3. 使用 writel 宏設(shè)置該寄存器值,如果沒有宏定義也可以直接使用 *((volatile unsigned int *)(addr)) = (value),addr 為內(nèi)存地址,value 為值。

相關(guān)參考: 關(guān)鍵字volatile

上述代碼僅作為示例,在實際的編程中通常使用如下簡寫方式:

readl(BUS_CLK_GATING_REG3) &= (1<<16)

或者使用匯編語言編寫:

<略>

多 bit 操作

多 bit 操作思路與單 bit 操作一樣,只是在計算修改至?xí)r使用了不同的操作數(shù).

為了使能 uart0 gpio,需要設(shè)置寄存器 PA_CFG0_REG(默認(rèn)值 0x77777777) bit 22:20(PA5_SELECT) 為 0x2(0b010),設(shè)置 bit 18:16(PA4_SELECT) 為 0x2(0b010)

如果是默認(rèn)值 0x77777777

0x 7    7    7    7    7    7    7    7
0x 0111 0111 0111 0111 0111 0111 0111 0111

則需要修改為 0x77772277

0x 7    7    7    2    2    7    7    7
0x 0111 0111 0111 0010 0010 0111 0111 0111

但需要注意的是,在該地址的其他位上還存在其他配置,在進行修改時要確保其他位不被改變。需要遵循寄存器修改三步驟。

需要設(shè)置寄存器 bit 22:20 18:16 均為 0x2(0b010),則是:

  • 設(shè)置 bit 21,17 為 1, 可以使用 a|= (0b10001<<17)
  • 設(shè)置 bit 22,20 18,16 為 0,可以使用 a&(~0b1010101<<16)
int reg
reg = readl(PA_CFG0_REG)
/* 設(shè)置bit _,21,_,_,_,17_, 為1 */
/* 同等于 reg|=(0x22<<16);reg|=(0b100010<<16) */
/* 同等于 reg|=(0x11<<17);reg|=(0b010001<<17) */
/* 同等于 reg|=(0b00000000000100010000000000000000) */
reg|=0x00220000

/* 設(shè)置bit 22,_,20, 18,_,16 為0 */
/* 同等于 reg|=(~0b00000000001010101000000000000000) */
/* 同等于 reg|=(~0x55<<16);reg|=(0b1010101<<16) */
reg&=(~0x550000)
writl(PA_CFG0_REG,reg)

更好的, 由于 GPIO 的一個引腳選擇由 4bit 控制,在配置時可以將兩個位操作視為整體。
上述操作也可視為將 bit 24:21 bit 20:17 均設(shè)置為 0x2,然后使用

  • 清除,readl(PA_CFG0_REG)&=0xff<<16
  • 設(shè)置,readl(PA_CFG0_REG)|=0x22<<16

的方式進行寄存器修改。

值得注意的是,在無法一次到位(非原子)的寄存器修改中,可能存在競態(tài)或者中間狀態(tài)。

例如在 a&=0xff<<16 執(zhí)行完成,下一步還未開始時,GPIO 寄存器被設(shè)置為 0x000 其代表了設(shè)置 GPIO 為 out 狀態(tài)而非 uart0 。

所以在涉及多 bit 操作時,需要使用內(nèi)存對寄存器值進行中轉(zhuǎn),計算完成后再寫入。

int tmp
tmp = readl(PA_CFG0_REG)
tmp &=(0xff<<16)
tmp |=(0x22<<16)
writel(PA_CFG0_REG,tmp)

在實際的編程中,通常使用類似 reg&= (1<<16) 的可讀性更高的方式,(1<<16) 會在編譯優(yōu)化時被優(yōu)化為常量。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容