02.14 STM32位帶(綁定)操作解釋——淺顯易懂

STM32位帶(綁定)操作解釋——淺顯易懂

過年了,首先祝大家新年快樂!!但也不能忘了學習hahahaha..剛找到幾篇極好的文章,加了一些自己的理解,整合了一下分享給大家,希望大家喜歡哦!!!

首先,拋磚引玉,來兩個問題:

  1)為什麼STM32裡面會有位帶操作?

  2)STM32裡面的位帶操作是什麼意思?

我也不想去弄什麼官方定義了,來兩個例子,相信各位心裡即使不能給出一個確切的定義,也不會再去糾結這個問題,

  1)51單片機相信各位都用過,假設P1.1的IO口上連了一個LED,那麼你單獨對LED的操作就是P1.1 = 0或P1.1 = 1,注意,是你可以單獨的對P1端的第一個IO口進行操作,然而STM32是不允許這樣做的,那麼為了像51單片機一樣能夠單獨的對某個端的某一個IO單獨操作,就引入了位帶操作這樣的概念,簡而言之,就是為了去單獨操作32裡面PA端的第1個IO口,所以才有了位帶這樣的操作機制。

  2)打個形象的比方,以張村,該村有3戶人家分別為A,B,C,我想給張村的A送禮,但是明文規定,不能給具體的個人送禮,但是可以給村委會送禮,那我該怎麼辦呢,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位地址的操作,因此說白了,就是某個IO端口進行操作,這就是位帶操作。

大白話說完,還是得迴歸官方介紹,不過這時候你在看,應該會好很多了。我們一步一步來,首先你應該知道的

  位帶區,和位帶別名區,位帶區,就是就是你想單獨操作的IO的區域,也就是PA,PB……等這一堆IO口的內存所在區,而位帶別名區,就是你給每一位重新起了個名字的那一片地址區域。可以看下錶,M3內核存儲器映射表,你能看到1M內存的BitBand區,還有與之對應的32M內存的BitBand別名區,因為你將每一位膨脹成為了一個32位的地址,所以相應的別名區的內存也會是位帶區的32倍。

STM32位帶(綁定)操作解釋——淺顯易懂

 OK,現在我們應該能夠知道,你想進行位帶操作去操作某個IO口的某一位,那麼在STM32的環境下,你應該去找該位對應的別名區的地址,找到了這個地址,對這個地址進行操作,那麼實際上也就是對該位進行操作了,接下來,我們要去找位所對應的地址了。

  官方給出了相應的計算公式,我們以外設部分為例,畢竟用的多的還是外設部分的端口,具體到PA.1把

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

  AliasAddr是別名區的地址,A是GPIOA->ODR的地址,n是該端口的上的某一位,這裡就是1,通過這個公式你可以找到對應的別名區的地址,接下來就是對這個地址進行操作了,你給他寫1,該位輸出1,寫0,就輸出0。

  在這裡我想解釋以下,為什麼這個公式是這個樣子的,因為我也思考了很久!藉助於下面這個圖:

STM32位帶(綁定)操作解釋——淺顯易懂

 0x42000000是位帶別名區域的起始地址,A是輸出數據寄存器GPIOA->ODR的地址,A的地址先減去位帶區基地址,得到的是相對於位帶區基地址的偏移地址,那麼膨脹之後還是一個偏移地址,是相對於位帶別名區基地址的偏移量,加上位帶別名區域基地址,就得到了其對應的別名區地址,這是總的原理,

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

  這部分是膨脹公式,乘8是先把單元內的每一位上升到字節的高度上,這樣,你想設置第二位,就直接在原來的基地址上+2就可以了,確定完是第幾位,再乘4,就是把位再上升到字的高度上,也就是每一位對應一個32位的字,這樣最終的地址轉換就完成,關鍵還是要注意兩點,一是,兩部分地址的互相轉換,主要是每一部分的基地址。二就是位上升的32位地址這樣的一個方法概念。

  說到這裡,基本已經介紹了80% 了,多數情況下,大家見到的代碼,應該是以下這個樣子,一共分為三步,

1 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
2 #define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
3 #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))

   第一步,就是我們上面分析的,得到位帶別名區域的32位地址,至於第二步嘛,其實就是一個轉換,給各位舉個例子,如下,我想直接訪問0x00000001這個地址,並且給這個地址寫1,該怎麼做呢,

1 # define ADDR 0x000000012 3 *(int *)ADDR = 1;

  第二步的操作就是將第一步得到的32位地址,給轉換成一個指針變量,並且操作這個地址裡的值,唯一的區別,就是由於安全的考慮,多加了一個volatile 這樣的關鍵字,但是他不會對我們產生其他的影響,而第三步,就是將前兩部,結合在一起,根據傳入的addr和bit計算得到32位的地址,然後強制類型轉換,使得我們可以去操作這個地址裡的值,OK,大功告成,整個的思路基本就是這樣,應該不是很難把,至此相信各位已經能夠理解什麼是位帶,以及該怎麼去操作位帶。

  接下來,再寫一種常用的位帶操作的用法。由於上面的傳入的addr是整個區域的基地址,因此,當你想去使用不同GPIO口的時候,採用上面的寫法,你將麻煩需要多寫好幾個步驟,我自己常用的一種寫法是下面這個樣子的。

# define BITBAND_REG(Reg,Bit) (*((uint32_t volatile*)(0x42000000u + (((uint32_t)&(Reg) - (uint32_t)0x40000000u)<<5) + (((uint32_t)(Bit))<<2))))

  # define LED0 BITBAND_REG(GPIOF->ODR,9)

  # define LED1 BITBAND_REG(GPIOF->ODR,10)

  短短的三行代碼,就已經解決了所有問題,輸出控制小燈泡,即使再換用其他的端口,改動括號內的內容即可。Reg是操作部分的基地址,Bit就是第幾位了。

  原理就是,我已經知道,GPIO部分的基地址是0x42000000u ,那麼我每次傳入具體的GPIOx->ODR寄存器,在定義中,對其取地址,這樣可以靈活訪問各個不用IO輸出,相當於把我們的操作給具體化了,<<5,<<2這兩個就是乘8,乘4這樣的概念,只不過微操作,會更快一點。


分享到:


相關文章: