STM32的位帶操作

STM32的位帶操作

對於位帶操作可以使用普通的加載/存儲指令來對單一的比特進行讀寫,在CM3中,有兩個區中實現了位帶。其中一個是 SRAM 區的最低 1MB 範圍,第二個則是片內外設區的最低 1MB 範圍。這兩個區中的地址除了可以像普通的 RAM 一樣使用外,它們還都有自己的“位帶別名區”,位帶別名區把位帶區中的每個比特膨脹成一個 32 位的字。即位帶別名區中的4個字節對應位帶區中的一位,當對位帶別名區進行操作時即對相應的位帶區中的某一位進行操作。位帶區與位帶別名區的膨脹關係如下圖


CM3 使用如下術語來表示位帶存儲的相關地址
* 位帶區: 支持位帶操作的地址區
* 位帶別名: 對別名地址的訪問最終作用到位帶區的訪問上(注意:這中途有一個地址映射過程)。
在位帶區中,每個比特都映射到別名地址區的一個字——這是隻有 LSB 有效的字。當一個別名地址被訪問時,會先把該地址變換成位帶地址。對於讀操作,讀取位帶地址中的一個字,再把需要的位右移到 LSB,並把 LSB 返回。對於寫操作,把需要寫的位左移至對應的位序號處,然後執行一個原子的“讀-改-寫”過程。
支持位帶操作的兩個內存區的範圍是:
0x2000_0000‐0x200F_FFFF( SRAM 區中的最低 1MB)
0x4000_0000‐0x400F_FFFF(片上外設區中的最低 1MB)
對 SRAM 位帶區的某個比特,記它所在字節地址爲 A,位序號爲n(0<=N<=7),則該別名區的地址爲

AliasAddr= 0x22000000+((A‐0x20000000)*8+n)*4                                                     =0x22000000+ (A‐0x20000000)*32 + n*4

對於片上外設位帶區的某個比特,記它所在字節的地址爲 A,位序號爲 n(0<=n<=7),則該比特在別名區的地址爲:

AliasAddr= 0x42000000+((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4

上式中,“ *4”表示一個字爲 4 個字節,“ *8”表示一個字節中有 8 個比特。
對於 SRAM 內存區,位帶別名的重映射如下表:

對於片上外設,位帶別名的重映射如下表:

舉例:
1》 在地址 0x20000000 處寫入 0x3355
2》讀取地址 0x22000008。本次讀訪問將讀取 0x20000000,並提取比特 2,值爲 1。
3》往地址 0x22000008 處寫 0。把比特 2 清 0。
4》現在再讀取 0x20000000,將返回 0x3355AAC8( bit[2]已清零)。
不管使用哪一種長度的數據位帶別名區的字只有 LSB 有意義。另外,在訪問位帶別名區時,傳送指令(字/半字/字節),都把地址對齊到字的邊界上,否則會產生不可預料的結果。
不幸的是,在 C 編譯器中並沒有直接支持位帶操作。比如, C 編譯器並不知道同一塊內存能夠使用不同的地址來訪問,也不知道對位帶別名區的訪問只對 LSB 有效。欲在 C 中使用位帶操作,最簡單的做法就是#define 一個位帶別名區的地址。例如:

#define DEVICE_REG0 ((volatile unsigned long *) (0x40000000))
#define DEVICE_REG0_BIT0 ((volatile unsigned long *) (0x42000000))
#define DEVICE_REG0_BIT1 ((volatile unsigned long *) (0x42000004))
...
*DEVICE_REG0 = 0xAB; //地址訪問寄存器
...
*DEVICE_REG0 = *DEVICE_REG0 | 0x2; //使用傳統方法設置 bit1
...
*DEVICE_REG0_BIT1 = 0x1; // 通過位帶別名地址設置 bit1

爲簡化位帶操作,也可以定義一些宏。比如,我們可以建立一個把“位帶地址+位序號”轉換成別名地址的宏,再建立一個把別名地址轉換成指針類型的宏:

//把“位帶地址+位序號”轉換成別名地址的宏
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
//把該地址轉換成一個指針
#define MEM_ADDR(addr) *((volatile unsigned long *) (adr))

在此基礎上,我們就可以如下改寫代碼:

MEM_ADDR(DEVICE_REG0) = 0xAB; //使用正常地址訪問寄存器
MEM_ADDR(DEVICE_REG0)= MEM_ADDR(DEVICE_REG0) | 0x2; //傳統做法
MEM_ADDR(BITBAND(DEVICE_REG0,1)) = 0x1; //使用位帶別名地址

請注意:當你使用位帶功能時,要訪問的變量必須用 volatile 來定義。因爲 C 編譯器並不知道同一個比特可以有兩個地址。所以就要通過 volatile,使得編譯器每次都如實地把新數值寫入存儲器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章