正在準(zhǔn)備做畢業(yè)設(shè)計(jì),配置LED_Config()的時(shí)候,又看到了位帶操作的宏定義,我又嘀咕了,什么是位帶操作,一年前在使用位帶操作的時(shí)候,就查閱過好多資料,Core-M3也看過,但是對(duì)于博主這種“低能兒”來說,你不把它說的白一點(diǎn),就是感覺理解的不夠透徹,于是今天又一次,查閱了各種手冊(cè),也算是基本弄懂了,鑒于博主的個(gè)人特點(diǎn),所以本人的介紹也會(huì)十分淺顯易懂,希望能幫到各位!
首先,拋磚引玉,來兩個(gè)問題:
1)為什么STM32里面會(huì)有位帶操作?
2)STM32里面的位帶操作是什么意思?
我也不想去弄什么官方定義了,來兩個(gè)例子,相信各位心里即使不能給出一個(gè)確切的定義,也不會(huì)再去糾結(jié)這個(gè)問題,
答:
1)51單片機(jī)相信各位都用過,假設(shè)P1.1的IO口上掛了一個(gè)LED,那么你單獨(dú)對(duì)LED的操作就是P1.1 = 0或P1.1 = 1,注意,是你可以單獨(dú)的對(duì)P1端的第一個(gè)IO口進(jìn)行操作,然而STM32是不允許這樣做的,那么為了像51單片機(jī)一樣能夠單獨(dú)的對(duì)某個(gè)端的某一個(gè)IO單獨(dú)操作,就引入了位帶操作這樣的概念,簡(jiǎn)而言之,言而總之,就是為了去單獨(dú)操作32里面PA端的第1個(gè)IO口,所以才有了位帶這樣的操作機(jī)制。
2)打個(gè)形象的比方,以某個(gè)村,就張村把,該村有3戶人家分別為A,B,C,我想給張村的A送禮,但是明文規(guī)定,不能給具體的個(gè)人送禮,但是可以給村委會(huì)送禮,那我該怎么辦呢,OK,即日起,A不叫A了,改名叫做村委會(huì)1,B和C分別改叫做村委會(huì)2和村委會(huì)3,哦了,可以給A送禮了,雖然我送禮的對(duì)象是村委會(huì)1,聽起來好像比個(gè)人級(jí)別高一點(diǎn),但是最終收到禮物的還是個(gè)人A。同理,STM32不允許對(duì)某個(gè)端的某一個(gè)IO口進(jìn)行操作,也就是PA.1 = 0或者PA.1 = 1這樣的操作是非法的,好了,那我就給PA.1起個(gè)別名,將原來PA.1的地址擴(kuò)展成一個(gè)32位的字地址,對(duì)32位的地址進(jìn)行操作,這個(gè)是STM32允許的,必需可以的,STM32對(duì)所有的寄存器配置,都是對(duì)某個(gè)32位地址的操作,因此說白了,就是某個(gè)IO端口進(jìn)行操作,這就是位帶操作。
大白話說完,還是得回歸官方介紹,不過這時(shí)候你在看,應(yīng)該會(huì)好很多了。我們一步一步來,首先你應(yīng)該知道的
位帶區(qū),和位帶別名區(qū),位帶區(qū),就是就是你想單獨(dú)操作的IO的區(qū)域,也就是PA,PB……等這一堆IO口的內(nèi)存所在區(qū),而位帶別名區(qū),就是你給每一位重新起了個(gè)名字的那一片地址區(qū)域。可以看下表,M3內(nèi)核存儲(chǔ)器映射表,你能看到1M內(nèi)存的BitBand區(qū),還有與之對(duì)應(yīng)的32M內(nèi)存的BitBand別名區(qū),因?yàn)槟銓⒚恳晃慌蛎洺蔀榱艘粋€(gè)32位的地址,所以相應(yīng)的別名區(qū)的內(nèi)存也會(huì)是位帶區(qū)的32倍。
OK,現(xiàn)在我們應(yīng)該能夠知道,你想進(jìn)行位帶操作去操作某個(gè)IO口的某一位,那么在STM32的環(huán)境下,你應(yīng)該去找該位對(duì)應(yīng)的別名區(qū)的地址,找到了這個(gè)地址,對(duì)這個(gè)地址進(jìn)行操作,那么實(shí)際上也就是對(duì)該位進(jìn)行操作了,接下來,我們要去找位所對(duì)應(yīng)的地址了。
官方給出了相應(yīng)的計(jì)算公式,我們以外設(shè)部分為例,畢竟用的多的還是外設(shè)部分的端口,具體到PA.1把
AliasAddr= 0x42000000+((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4
AliasAddr是別名區(qū)的地址,A是GPIOA->ODR的地址,n是該端口的上的某一位,這里就是1,通過這個(gè)公式你可以找到對(duì)應(yīng)的別名區(qū)的地址,接下來就是對(duì)這個(gè)地址進(jìn)行操作了,你給他寫1,該位輸出1,寫0,就輸出0。
在這里我想解釋以下,為什么這個(gè)公式是這個(gè)樣子的,因?yàn)槲乙菜伎剂撕芫?!借助于下面這個(gè)圖:
0x42000000是位帶別名區(qū)域的起始地址,A是輸出數(shù)據(jù)寄存器GPIOA->ODR的地址,A的地址先減去位帶區(qū)基地址,得到的是相對(duì)于位帶區(qū)基地址的偏移地址,那么膨脹之后還是一個(gè)偏移地址,是相對(duì)于位帶別名區(qū)基地址的偏移量,加上位帶別名區(qū)域基地址,就得到了其對(duì)應(yīng)的別名區(qū)地址,這是總的原理,
((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4
這部分是膨脹公式,乘8是先把單元內(nèi)的每一位上升到字節(jié)的高度上,這樣,你想設(shè)置第二位,就直接在原來的基地址上+2就可以了,確定完是第幾位,再乘4,就是把位再上升到字的高度上,也就是每一位對(duì)應(yīng)一個(gè)32位的字,這樣最終的地址轉(zhuǎn)換就完成,關(guān)鍵還是要注意兩點(diǎn),一是,兩部分地址的互相轉(zhuǎn)換,主要是每一部分的基地址。二就是位上升的32位地址這樣的一個(gè)方法概念。
說到這里,基本已經(jīng)介紹了80% 了,多數(shù)情況下,大家見到的代碼,應(yīng)該是以下這個(gè)樣子,一共分為三步,
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))
第一步,就是我們上面分析的,得到位帶別名區(qū)域的32位地址,至于第二步嘛,其實(shí)就是一個(gè)轉(zhuǎn)換,給各位舉個(gè)例子,如下,我想直接訪問0x00000001這個(gè)地址,并且給這個(gè)地址寫1,該怎么做呢,
1 # define ADDR 0x00000001 2 3 *(int *)ADDR = 1;
第二步的操作就是將第一步得到的32位地址,給轉(zhuǎn)換成一個(gè)指針變量,并且操作這個(gè)地址里的值,唯一的區(qū)別,就是由于安全的考慮,多加了一個(gè)volatile 這樣的關(guān)鍵字,但是他不會(huì)對(duì)我們產(chǎn)生其他的影響,而第三步,就是將前兩部,結(jié)合在一起,根據(jù)傳入的addr和bit計(jì)算得到32位的地址,然后強(qiáng)制類型轉(zhuǎn)換,使得我們可以去操作這個(gè)地址里的值,OK,大功告成,整個(gè)的思路基本就是這樣,應(yīng)該不是很難把,至此相信各位已經(jīng)能夠理解什么是位帶,以及該怎么去操作位帶。
接下來,再寫一種常用的位帶操作的用法。由于上面的傳入的addr是整個(gè)區(qū)域的基地址,因此,當(dāng)你想去使用不同GPIO口的時(shí)候,采用上面的寫法,你將麻煩需要多寫好幾個(gè)步驟,我自己常用的一種寫法是下面這個(gè)樣子的。
# 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)
短短的三行代碼,就已經(jīng)解決了所有問題,輸出控制小燈泡,即使再換用其他的端口,改動(dòng)括號(hào)內(nèi)的內(nèi)容即可。Reg是操作部分的基地址,Bit就是第幾位了。
原理就是,我已經(jīng)知道,GPIO部分的基地址是0x42000000u ,那么我每次傳入具體的GPIOx->ODR寄存器,在定義中,對(duì)其取地址,這樣可以靈活訪問各個(gè)不用IO輸出,相當(dāng)于把我們的操作給具體化了,<<5,<<2這兩個(gè)就是乘32,乘4這樣的概念,只不過位操作,會(huì)更快一點(diǎn)。
評(píng)論