對STM32所用位帶操作宏的詳細剖析


在原子例程的sys.h中,使用宏定義建立了位帶操作的基礎,
使得操作IO端口可以像51一樣實現位操作。
其實深入了解了位帶操作的原理,幾乎就可以實現對STM32所有外設寄存器的訪問,
極端情況下,什么庫函數版本,什么寄存器版本都可以不用,直接精准地操控所有寄存器的每一位的讀寫!!!

知道了STM32將所有外設寄存器的每一位都建立了位帶別名區,
你只要再花一點點時間,徹底搞明白下面的三句宏定義,位帶操作就都不在話下了:
#define BITBAND(addr, bitnum)          ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr)                *((volatile unsigned long  *)(addr))
#define BIT_ADDR(addr, bitnum)       MEM_ADDR(BITBAND(addr, bitnum))

 

這三句是一環套一環的,
首先第一句:
#define    BITBAND(addr, bitnum)          ((addr & 0xF000 0000)+0x200 0000+((addr &0xF FFFF)<<5)+(bitnum<<2))
這一句定義了位帶存儲地址的計算方法,
知道了寄存器的地址,以及我們關心的寄存器的某一比特位,就可以根據此計算方法算出其對應的別名區地址
這個計算公式不僅對外設寄存器對應的別名區計算有用,對用戶SRAM對應的別名區一樣適用。
addr & 0xF000 0000 只取絕對地址的最高4位,實際上是用來區分段的,是寄存器段還是SRAM段。
+0x200 0000(值為32M)是別名區相對位段區的地址偏移量,別名區在相應位段上方的32M處;
(addr &0xF FFFF)<<5) 位段地址膨脹32倍,左移5位即可;
(bitnum<<2)由於每1比特膨脹為32位,32位占用4個字節的存儲位置,所以計算地址時要乘以4,左移2位即是;

然后是第二句
#define   MEM_ADDR(addr)            *((volatile unsigned long  *)(addr))
上一句計算出來的地址只是一個數值,要將它強制轉化成一個地址(並且聲明這個地址存儲的是一個32位的long型變量)
用(unsigned long  *)(addr) 即可,這樣就成了一個真正的有血有肉的地址了。
前面再加一個*號,就可以訪問這個地址得到其中的變量值了。
在C語言中,unsigned char *p; 定義p為一個指向unsigned char的地址指針;而 *p=1;就是向這個指針指向的地址所存儲的變量賦值為1了。
至於中間加一個volatile關鍵字,則指示編譯器不要自作主張對此進行優化,必須每次老老實實地去直接訪問這個地址!!!

第三句呢?毫無難度,就是以前兩句宏為基礎的結合
#define BIT_ADDR(addr, bitnum)       MEM_ADDR(BITBAND(addr, bitnum))
給定寄存器的絕對地址addr,以及我們關心的比特位號bitnum,
先用BITBAND宏算出別名區對應的地址值
再用MEM_ADDR宏去訪問這個地址

簡單吧,這就是所有的位操作的奧秘了!!
有了這三句,你就可以完成所有的位操作,讓我們舉一個實例,比方說要置位GPIO A口的第9位,即讓PA9輸出高電平。
我們只須知道控制GPIO A的寄存器ODR的地址就行了,這個去查一下用戶手冊就行了,
一般手冊上會給出兩項,一是外設寄存器的基址,GPIOA的基址是0x4001 0800, 再找ODR,手冊上一般給出其偏移量0C,
也就是說,GPIOA的ODR寄存器是0x4001 0800+0C=0x4001 080C
什么?你不知道寄存器的地址怎么查? 哈哈,早有人替你查好了,並且為你查好,定義了下列宏:
#define GPIOA_ODR_Addr    (0x4001 0800+0C) //0x4001080C
並且一切為你着想,好事做到底,還定義了宏:
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)
簡單到你想置位GPIO A口的第9位,只須使用語句:PAout(9)=1;就行了。

怎么是這樣的呢?因為有前面這些宏定義為基礎,
反正閑着沒事兒,我就當一回編譯器,把這句PAout(9)=1一步步地編譯出來,宏的展開就是一個替換的過程:
PAout(9)=1;因為定義了PAout(n) 要替換成 BIT_ADDR(GPIOA_ODR_Addr,n),所以展開成:
BIT_ADDR(GPIOA_ODR_Addr,9)=1;因為定義了BIT_ADDR(addr, bitnum) 要替換成 MEM_ADDR(BITBAND(addr, bitnum)),所以展開成:
MEM_ADDR(BITBAND(GPIOA_ODR_Addr, 9))=1;因為定義了BIT_ADDR(addr, bitnum) 要替換成 MEM_ADDR(BITBAND(addr, bitnum)),所以展開成:
MEM_ADDR((GPIOA_ODR_Addr & 0xF0000000)+0x2000000+((GPIOA_ODR_Addr &0xFFFFF)<<5)+(9<<2))=1;
最后一步,因為定義了MEM_ADDR(addr)要替換成 *((volatile unsigned long  *)(addr))
所以展開成為如下的語句,不要暈倒哦,*((volatile unsigned long  *)((GPIOA_ODR_Addr & 0xF0000000)+0x2000000+((GPIOA_ODR_Addr &0xFFFFF)<<5)+(9<<2)))=1;

神奇吧?
一句  PAout(9)=1;
與     *((volatile unsigned long  *)((GPIOA_ODR_Addr & 0xF0000000)+0x2000000+((GPIOA_ODR_Addr &0xFFFFF)<<5)+(9<<2)))=1;
是完全等效的。
而這,就是宏定義的效能和魅力!

 

還有:因為定義了GPIOA_ODR_Addr就是(0x4001 0800+0C),哦,等一下,我先算出數值來吧,GPIOA_ODR_Addr就是0x4001 080C,得到:   
*((volatile unsigned long  *)((0x4001 080C & 0xF0000000)+0x2000000+((0x4001 080C &0xFFFFF)<<5)+(9<<2)))=1;

看着很長,其實有了具體數值,算出結果就短了:
解&運算符:得到*((volatile unsigned long  *)(0x4000 0000 +0x200 0000+(0x0001 080C<<5)+(9<<2)))=1;
即:*((volatile unsigned long  *)(0x4200 0000 +(0x0001 080C<<5)+(9<<2)))=1;
解移位運算符:9=1001 經過<<2得到 100100 即0x24;
1 080C=0001 0000 1000 0000 1100經過<<5得到0010 0001 0000 0001 1000 0000 即0x21 0180
所以語句變成:*((volatile unsigned long  *)(0x4200 0000 + 0x21 0180 + 0x24)=1;
最后結果就是如下語句(以上這些過程都只是預編譯器干的話,實際交付編譯器的也就是下面這一句):
*((volatile unsigned long  *)(0x4221 01A4)=1;
說成大白話,就是給0x4221 01A4這個地址中所存儲的變量賦值為1.  
   
(注意:這個變量是一個long型的,32位,占用從0x4221 01A4開始的連續4個存儲單元)
但是ARM的設計師們並沒有在物理上設計這些存儲單元(也永遠不允許這些存儲單元實際存在!!!),取而代之的是設計了位映射機制:
凡是訪問別名區域地址的操作,都被轉換為訪問其所映射對應的比特位
*(0x4221 01A4)=1的執行結果就是:GPIOA的ODR寄存器第9位=1

 

除了可以操作STM32所有片上外設寄存器的每一比特位外,
我們還可以操作SRAM區的每一比特位。
因為,片上SRAM的全部,無一例外地都落在位帶區。
我們正常程序中聲明的所有變量,因此也都被分配在這一個位帶區。
只要我們知道了變量的地址,也就可以通過其相應的別名區地址按比特訪問。
比如:我們可以將程序中用到的標志位集中定義到一個變量(8-32位均可)
給這個變量分配一個固定地址的單元,
然后在程序中按位來訪問這些標志,
這樣可以提高軟件的效率。

 

另外還要指出的是:
雖然位段區的每一位都被映射到別名區膨脹到了32位,
但這32位只是個名頭而已,實際只有最低位有效。

對別名區的訪問,是雙向的:
對別名區的讀:結果非0即1,反應的是對應位段的某一比特位的值。
對別名區的寫:只有最低位有效,效果是將對應位段區的某一比特位置1或清0.    寫入0和寫入FE效果是完全一樣的。

 

前面的分析我們是以寄存器區的位段操作為例來剖析的。
SRAM區的位段操作也是一樣的機制,
我前面提到,
我們所定義的變量,都被分配在SRAM區,
只不過我們一般不關心這些變量的具體地址,
而現在我們必須知道地址,才能進行位段操作。


免責聲明!

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



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