stm32之bit-band(位帶)操作


注意:本文中關於STM32的位帶操作原理只適用於Cortex-M3和Cortex-M4(F)內核處理器,Cortex-M系列的其他內核處理器可能不支持位段操作(如Cortex-M0內核處理器就不支持位段操作),詳情請參考相關內核處理器的指南或技術參考手冊(TRM)。       

1、簡介

今天參考了好幾篇博客,總算是對 bit-band操作有些了解了。首先,

  •  什么是位帶操作?

位帶操作:可以實現對某一GPIO口寄存器(或SRAM內存中)的某一bit位直接賦值0或1,達到控制GPIO口輸出(或改變SRAM中這一bit位的值)的目的;就如同51單片機控制GPIO口一樣的方便。比如:

       51:P1^0=1;  //把P1口的第一個引腳設置為高電平

       STM32:PAout(0)=1;   //把PA口的第一個引腳設置成高電平

位帶操作的對象可以是SRAM、I/O和外設空間。要實現對這些地方的某一位的操作。它是這樣做的:在尋址空間(32位對應的地址空間為 4GB )的另一地方,取個別名區空間,從這個地址開始處,每一個字(32BIT)對應SRAM或I/O的一位。這樣,1MB SRAM 就可以有 32MB 的對應別名區空間,就是1位膨脹到32位(1 BIT 變為1個字節)。我們對這個別名區空間內的某一字操作(置0或置1),就等於它映射的 SRAM 或 I/O 相應的某地址的某一位的操作。 

那么,使用如下術語來表示位帶存儲的相關地址

位帶區:支持位帶操作的地址區,分別是SRAM區的最低1MB范圍和外設區的最低1MB范圍。
位帶別名:對別名地址的訪問最終作用到位帶區的訪問上(注意:這中途有一個地址映射的過程) 
  •  使用位帶操作的好處 
    簡單來說,可以把代碼縮小, 速度更快,效率更高,更安全。 一般操作要6條指令,而使用位帶別名區只要4條指令。一般操作是 讀-改-寫 的方式, 而位帶別名區是 寫 操作。防止中斷對 讀-改-寫 的方式的影響。

 

                                         圖1.1

 

  

2、進入正題,別名區中的字與 bit-band 區中對應位或目標位是如何關聯在一起的呢

位帶別名區就是把每個比特(BIT)膨脹成一個 32 位的字。 每個比特膨脹成一個32 位的字,就是把 1M 擴展為 32M 。在位帶區中,每個比特都映射到別名地址區的一個字——這是只有 LSB (低位先行或最低有效)有效的字。當一個別最低名地址被訪問時,會先把該地址變換成位帶地址。於是,位於 RAM 地址 0X20000000 的一個字節擴展為8個32 位的字,擴展后每位相對應的的地址是:0X22000000,0X22000004,0X22000008,0X2200000C,0X22000010,0X22000014,0X22000018,0X2200001C。

由圖1.1可知,

  • SRAM的位帶區地址范圍:0x20000000-0x200FFFFF(1M 尋址空間的地址范圍)
  • SARM的位帶別名區地址范圍:0x22000000-0x23FFFFFF(32M 尋址空間的地址范圍) 
  • 片上外設的位帶區地址范圍:0x40000000-0x400FFFFFF(1M)
  • 片上外設的位帶別名區地址范圍:0x42000000-0x43FFFFFF(32M)

     

                                                                           圖 1.2

結合圖5.3B和圖1.2中的16進制序列表,可得別名區中的字與 bit-band 區中對應位或目標位的關聯公式,映射公式如下:

     對於 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 個比特。

也等效於

  • 位帶別名區的字地址 = 0x22000000 + (位帶區目標位所在的字地址偏移 << 5) + (位帶區目標位序號 << 2) ①   (注:位帶區目標位序號范圍為 0-7)
  • 位帶別名區的字地址 = 0x42000000 + (位帶區目標位所在的字地址偏移 << 5) + (位帶區目標位序號 << 2) ②   (注:位帶區目標位序號范圍為 0-7)

為了方便起見,所以將式①和式②合並成一個公式:

  • 位帶別名區的字地址 =(位帶區目標位所在的字地址 & 0xF0000000)+ 0x02000000 + [(位帶區目標位所在的字地址 & 0xFFFFF << 5 ] +(位帶區目標位編號 << 2) ③(注:位帶區目標位序號范圍為 0-31)

下面解釋一下該公式的含義:

這部分是膨脹公式,位帶別名區就是把每個比特(BIT)膨脹成一個 32 位的字,乘以8是先把單元內的每一位上升到字節的高度上,這樣,你想設置第二位,就直接在原來的基地址上+2就可以了,確定完是第幾位,再乘4,就是把位上升到字的高度上,也就是每一位對應一個32位的字,這樣最終的地址轉換就完成.字節和字區別什么意思?我想的是把1m的bitband區域的一個比特位膨脹為32位的stm32中地址。在stm32中無法直接對位直接訪問,只能訪問32位的址(所謂的寄存器映射的地址),故將要控制的GPIO口的一個bit位,就要這個bit位膨脹為32位的字節0地址。

3、下面舉例 SRAM bit-band 別名區和 SRAM bit-band 區之間的 bit-band 映射的例子:

運用位帶訪問GPIOB_BSRR的bit 5.

GPIOB_BSRR地址為0x4001 0C00 + 0x10,位帶區的外設的基地址為0x4000 0000,所以GPIOB_BSRR與外設的基地址相差(0x4001 0C10 - 0x4000 0000)個字節(byte),每個字節8bit,額外加上5個bit,於是 

 =(0x4001 0C10-0x4000 0000)*8+5個bit

回到位帶運算中,位帶區一個32bit,即4byte代表一個bit,增加一個bit,位帶別名區的地址增加4個字節(即增加32bit),那么最終位帶別名區的地址是:

0x4200 0000+((0x4001 0C10-0x4000 0000)*8+5)*4 = 0x4221 8014

0x4200 0000+((0x4001 0C10-0x4000 0000)*32 + 5*4 = 0x4221 8014

在程序中可以這樣定義:

#define BITBAND(addr, bitnum)   ((addr & 0xF0000000)+0x02000000+((addr & 0xFFFFF) << 5)+(bitnum << 2))

#define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) 

#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))  

同理

  • 地址 0x23FFFFE0 的別名字映射為 0x200FFFFF 的bit-band 字節的位 0:
    0x23FFFFE0 = 0x22000000+((0x200FFFFF -  0x20000000)*32)+0*4 
  • 地址 0x23FFFFFC 的別名字映射為 0x200FFFFF 的bit-band 字節的位 7: 
    0x23FFFFFC = 0x22000000+(0xFFFFF*32)+7*4 
  • 地址 0x22000000 的別名字映射為 0x20000000 的bit-band 字節的位 0:
    0x22000000 = 0x22000000+(0*32)+0*4 
  • 地址 0x220001C 的別名字映射為 0x20000000 的bit-band 字節的位 0:
    0x2200001C = 0x22000000+(0*32)+7*4 

以下是一個根據bit-band區域地址和目標位來計算bit-band alias region映射的對應字節的宏(來源於Atmel ASF的bit-banding Example):

1 #define BITBAND_ALIAS_ADDRESS(addr, bit) \
2     ((volatile uint32_t*)((((uint32_t)(addr) & 0xF0000000) + 0x02000000) \ 3                           +((((uint32_t)(addr)&0xFFFFF)*32)\ 4                           +(  (uint32_t)(bit)*4))))

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

//把“位帶地址+位序號”轉換成別名地址的宏

#define BITBAND(addr, bitnum)    ((addr & 0xF0000000) + 0x02000000 + ((addr & 0xFFFFF) << 5) + (bit<<2));

//把該地址轉換成一個指針

#define MEM_ADDR(addr) *((volatile unsigned long *) (adr));

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

MEM_ADDR(DEVICE REG0) = 0xAB; //使用正常地址訪問寄存器,即把0xAB作為DEVICE REG0地址上的值

MEM_ADDR(DEVICE_REG0) = MEM_ADDR(DEVICE_REG0) | 0x2; //傳統做法

MEM_ADDR(BITBAND(DEVICE_REG0, 1)) = 0x1; //使用位帶別名地址

或者,

//將 “位帶地址 + 位序號” 轉換成位帶別名地址的宏定義,並強制轉換成指針

1 #define Bit_Band(addr,num)  *((volatile unsigned long*)((addr&0xF0000000)+0x02000000+((addr&0xFFFFF)<<5)+(num<<2)))

 以下是應用在32單片機中的具體的例子:

 1 ///////////////////////////////////////////////////////////////  2 //位帶操作,實現51類似的GPIO控制功能  3 //具體實現思想,參考<<CM3權威指南>>第五章(87頁~92頁).  4 //IO口操作宏定義
 5 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
 6 #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
 7 #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
 8 //IO口地址映射
 9 #define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
10 #define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
11 #define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
12 #define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
13 #define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
14 #define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C 
15 #define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C 
16  
17 #define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
18 #define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
19 #define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
20 #define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
21 #define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
22 #define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
23 #define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
24  
25 //IO口操作,只對單一的IO口! 26 //確保n的值小於16!
27 #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //輸出 
28 #define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //輸入 
29  
30 #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //輸出 
31 #define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //輸入 
32  
33 #define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //輸出 
34 #define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //輸入 
35  
36 #define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //輸出 
37 #define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //輸入 
38  
39 #define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //輸出 
40 #define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //輸入
41  
42 #define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //輸出 
43 #define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //輸入
44  
45 #define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //輸出 
46 #define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //輸入

————————————————
聲明:本文是參考多篇博客刪減而來,在此非常感謝原作者!^-^

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM