為什么需要位帶操作?
因為編程需要操作某個bit位來達到我們想要的功能,比如點燈需要操作GPIOA->ODR
的某個bit假設是第2bit,寫1就可以讓GPIO輸出一個高電平。
GPIOA- >ODR |= 1< 2;
這樣寫其實有三個隱含的操作:
//1.讀取ODR寄存器的值到內(nèi)存//2.改寫第2bit的值//3.再把改寫后的值寫進ODR寄存器
這樣的缺點:效率低
位帶操作就是為了解決這個問題,前提是硬件支持這么做。
位操作就是可以單獨的對一個比特位讀和寫,這個在 51 單片機中非常常見。51 單片機中通過關鍵字 sbit 來實現(xiàn)位定義,STM32沒有這樣的關鍵字,而是通過訪問位帶別名區(qū)來實現(xiàn),例如
sbit LED P1^2
LED = 1;//輸出高電平
LED = 0;//輸出低電平
這樣的優(yōu)點:效率高
什么是位帶別名區(qū)?
STM32本身不支持位操作,它發(fā)明了一種位帶操作來讓32的某些資源支持位操作。
這兩個區(qū)域一個是 SRAM 區(qū)的最低 1MB 空間,令一個是外設區(qū)最低 1MB 空間。
這兩個 1MB 的空間除了可以像正常的 RAM 一樣操作外,他們還有自己的 位帶別名區(qū) ,位帶別名區(qū)把這 1MB 的空間的 每一個位膨脹成一個 32 位的字 ,當訪問位帶別名區(qū)的這些字時,就可以達到訪問位帶區(qū)某個比特位的目的。
位帶別名區(qū)就是就是就是本來位的區(qū)域,變成了字的區(qū)域。
這里有個形象的解釋:
打個形象的比方,以某個村,就張村把,該村有3戶人家分別為A,B,C,我想給張村的A送禮,但是明文規(guī)定,不能給具體的個人送禮,但是可以給村委會送禮,那我該怎么辦呢,OK,即日起,A不叫A了,改名叫做村委會1,B和C分別改叫做村委會2和村委會3,哦了,可以給A送禮了,雖然我送禮的對象是村委會1,聽起來好像比個人級別高一點,但是最終收到禮物的還是個人A。
同理,STM32不允許對某個端的某一個IO口進行操作,也就是PA.1 = 0或者PA.1 = 1這樣的操作是非法的,好了,那我就給PA.1起個別名,將原來PA.1的位地址擴展成一個32位的 字地址 ,對32位的地址進行操作,這個是STM32允許的,肯定是可以的,STM32對所有的寄存器配置,都是對某個32位地址的操作,因此說白了,操作一個32位寄存器來影響某個位的操作叫做位帶操作。
什么是位帶區(qū)?
我們可以看到下面圖中有兩個位帶區(qū),分別是SRAM區(qū)里的0x20000000-0x200FFFFF地址段和片內(nèi)外設區(qū)里的0x40000000-0x400FFFFF地址段(圖中標號①處),它們的地址空間大小都是1M字節(jié),在SRAM段內(nèi)和外設地址段內(nèi)的這1M大小的空間就是位帶區(qū),說白了就是支持位帶操作的區(qū)域就是位帶區(qū)。
位帶區(qū)跟位帶別名區(qū)有怎樣的關系?
從上面映射圖上可以看到,SRAM區(qū)里的0x22000000-0x23FFFFFF地址段和外設區(qū)里0x42000000-0x43FFFFFF地址段都是位帶別名區(qū),兩個別名區(qū)空間大小都是32MB。那么,這32MB的位帶別名區(qū)地址空間是怎么與1MB的位帶區(qū)地址空間對應起來的呢?
答案:地址映射
那么問題來了?將1M字節(jié)里面的每一個bit映射到32M字節(jié)里面去,那么怎么映射呢?
首先明確一些概念:
1字節(jié)= 8bit
1字 = 4字節(jié) = 32bit
看圖
將1bit映射到1個字空間(擴大了32倍)
映射前的1個字節(jié) = 映射后的8個字(擴大了32倍 8 * 4 = 32字節(jié))
那么就得出以下結論:
映射前的1個字節(jié) = 映射后的32個字節(jié)
映射前的1M字節(jié) = 映射后的32M字節(jié)
0x40000000地址處的1個bit變成了0x42000010地址處的32個bit
為什么要將1bit空間要映射到一個字空間里去呢?我映射到1字節(jié)或者2字節(jié)的地址空間不行嗎?我只能說,STM32是一個32位的機器,內(nèi)核按字尋址的話尋址速度是最快的,所以別問這么多為什么,如果問了,答案就是為了速度。就好比你買個電腦用一個小箱子裝著但是順豐快遞發(fā)貨走的是集裝箱,理論上來說裝到集裝箱里空運是最快的,要不然沒辦法上飛機啊......各位想想好像是這么個道理哈
位帶操作該怎么用?
我們已經(jīng)知道了位帶區(qū)就是支持位操作的地址段,位帶別名區(qū)就是位帶區(qū)的地址映射,操作位帶別名區(qū)就等價于操作位帶區(qū),并且我們知道了大致的映射過程,那么在STM32實際使用中又是怎么應用的呢?
在《Cortex M3權威指南》中,前人已經(jīng)整理出了位帶別名區(qū)與位帶區(qū)地址對應關系的表達式,使用的時候只要套用公式就可以,如下圖
將兩個公式合并一下就得到:
AliasAddr = ((A & 0xF0000000)+0x02000000+((A &0x00FFFFFF)<<5)+(n<<2))
式中A為位帶區(qū)地址,n為位序號
<<5 <<2又是什么鬼
2進制左移5位就相當于乘以2^5次方 就是擴大32倍的意思 為什么不寫成*32 問就是效率 <<2同理擴大4倍
使用以下開源代碼即可完成映射
// 把“位帶地址+位序號”轉換成別名地址的宏
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr & 0x000FFFFF)< 5)+(bitnum< 2))
// 把一個地址轉換成一個指針
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
// 把位帶別名區(qū)地址轉換成指針
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
// GPIO ODR 和 IDR 寄存器地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+20)
#define GPIOB_ODR_Addr (GPIOB_BASE+20)
#define GPIOC_ODR_Addr (GPIOC_BASE+20)
#define GPIOD_ODR_Addr (GPIOD_BASE+20)
#define GPIOE_ODR_Addr (GPIOE_BASE+20)
#define GPIOF_ODR_Addr (GPIOF_BASE+20)
#define GPIOG_ODR_Addr (GPIOG_BASE+20)
#define GPIOH_ODR_Addr (GPIOH_BASE+20)
#define GPIOI_ODR_Addr (GPIOI_BASE+20)
#define GPIOJ_ODR_Addr (GPIOJ_BASE+20)
#define GPIOK_ODR_Addr (GPIOK_BASE+20)
#define GPIOA_IDR_Addr (GPIOA_BASE+16)
#define GPIOB_IDR_Addr (GPIOB_BASE+16)
#define GPIOC_IDR_Addr (GPIOC_BASE+16)
#define GPIOD_IDR_Addr (GPIOD_BASE+16)
#define GPIOE_IDR_Addr (GPIOE_BASE+16)
#define GPIOF_IDR_Addr (GPIOF_BASE+16)
#define GPIOG_IDR_Addr (GPIOG_BASE+16)
#define GPIOH_IDR_Addr (GPIOH_BASE+16)
#define GPIOI_IDR_Addr (GPIOI_BASE+16)
#define GPIOJ_IDR_Addr (GPIOJ_BASE+16)
#define GPIOK_IDR_Addr (GPIOK_BASE+16)
// 單獨操作 GPIO的某一個IO口,n(0,1,2...16),n表示具體是哪一個IO口
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //輸出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //輸入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //輸出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //輸入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //輸出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //輸入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //輸出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //輸入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //輸出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //輸入
#define