對(duì)STM32的GPIO位帶操作的理解
支持了位帶操作后,便可以使用普通的加載/儲(chǔ)存指令來對(duì)單一的比特進(jìn)行讀寫操作了。簡單而言,就是可以單獨(dú)的對(duì)一個(gè)比特位讀和寫。在F103中,有兩個(gè)地方實(shí)現(xiàn)了位帶操作,其中一個(gè)是SRAM區(qū)的最低1MB范圍,第二個(gè)則是片內(nèi)外設(shè)區(qū)的最低1MB范圍。這兩個(gè)區(qū)中的地址除了可以像普通的RAM一樣使用外,它們還都有自己的“位帶別名區(qū)”,位帶別名區(qū)把每個(gè)比特膨脹成一個(gè)32位的字。當(dāng)你通過位帶別名區(qū)訪問這些字時(shí),就可以達(dá)到訪問原始比特的目的。
圖1.1位帶區(qū)與位帶別名區(qū)的膨脹對(duì)應(yīng)關(guān)系圖
支持位帶操作的兩個(gè)內(nèi)存區(qū)的范圍是:0x2000_0000‐0x200F_FFFF(SRAM區(qū)中的最低1MB);0x4000_0000‐0x400F_FFFF(片上外設(shè)區(qū)中的最低1MB)。外設(shè)位帶區(qū)經(jīng)過膨脹后的位帶別名區(qū)地址為:0X42000000~0X43FFFFFC,這部分地址空間為保留地址,沒有跟任何的外設(shè)地址重合。SRAM區(qū)經(jīng)過膨脹后的位帶別名區(qū)地址為:0X2200 0000~0X23FF FFFC,大小為32MB。
位帶區(qū)的一個(gè)比特位經(jīng)過膨脹之后,雖然變大到4個(gè)字節(jié),但是還是LSB才有效。有人會(huì)問這不是浪費(fèi)空間嗎,要知道F103的系統(tǒng)總線是32位的,按照4個(gè)字節(jié)訪問的時(shí)候是最快的,所以膨脹成4個(gè)字節(jié)來訪問是最高效的。我們可以通過指針的形式訪問位帶別名區(qū)地址從而達(dá)到操作位帶區(qū)比特位的效果。那這兩個(gè)地址直接如何轉(zhuǎn)換,下面簡單地介紹一下。
一、對(duì)于片上外設(shè)位帶區(qū)的某個(gè)比特,記它所在字節(jié)的地址為A,位序號(hào)為n(0<=n<=7),則該比特在別名區(qū)的地址為:
1.AliasAddr==0x42000000+ (A-0x40000000)*8*4 +n*4
0X42000000是外設(shè)位帶別名區(qū)的起始地址,0x40000000是外設(shè)位帶區(qū)的起始地址,(A-0x40000000)表示該比特位前面有多少個(gè)字節(jié),一個(gè)字節(jié)有8位,所以*8,一個(gè)位膨脹后是4個(gè)字節(jié),所以*4,n表示該比特在A地址的序號(hào),因?yàn)橐粋€(gè)位經(jīng)過膨脹后是四個(gè)字節(jié),所以也*4。
二、對(duì)于SRAM位帶區(qū)的某個(gè)比特,記它所在字節(jié)的地址為A,位序號(hào)為n(0<=n<=7),則該比特在別名區(qū)的地址為:
1.AliasAddr==0x22000000+ (A-0x20000000)*8*4 +n*4
公式的分析同上所示
三、上面公式的統(tǒng)一:
為了方便操作,我們可以把這兩個(gè)公式合并成一個(gè)公式,把"位帶地址+位序號(hào)"轉(zhuǎn)換成別名區(qū)地址統(tǒng)一成一個(gè)宏。
//把"位帶地址+位序號(hào)"轉(zhuǎn)換成別名地址的宏
2 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr &
0x000FFFFF)<<5)+(bitnum<<2))
addr&0xF0000000是為了區(qū)別SRAM還是外設(shè),實(shí)際效果就是取出4或者2,如果是外設(shè),則取出的是4,+0X02000000之后就等于0X42000000,0X42000000是外設(shè)別名區(qū)的起始地址。如果是SRAM,則取出的是2,+0X02000000之后就等于0X22000000,0X22000000是SRAM別名區(qū)的起始地址。
addr & 0x00FFFFFF屏蔽了高三位,相當(dāng)于減去0X20000000或者0X40000000,但是為什么是屏蔽高三位?因?yàn)橥庠O(shè)的最高地址是:0X2010 0000,跟起始地址0X20000000相減的時(shí)候,總是低5位才有效,所以干脆就把高三位屏蔽掉來達(dá)到減去起始地址的效果,具體屏蔽掉多少位跟最高地址有關(guān)。SRAM同理分析即可。<<5相當(dāng)于前面公式中的*8*4,<<2相當(dāng)于*4。
最后我們就可以通過指針的形式操作這些位帶別名區(qū)地址,最終實(shí)現(xiàn)位帶區(qū)的比特位操作。
//把一個(gè)地址轉(zhuǎn)換成一個(gè)指針
#defineMEM_ADDR(addr)*((volatileunsignedlong*)(addr))
//把位帶別名區(qū)地址轉(zhuǎn)換成指針
#defineBIT_ADDR(addr,bitnum)MEM_ADDR(BITBAND(addr,bitnum))
(后文有附上對(duì)*((volatileunsignedlong*)的理解)。
從手冊(cè)中我們可以知道ODR和IDR這兩個(gè)寄存器對(duì)應(yīng)GPIO基址的偏移是20和16,我們先實(shí)現(xiàn)這兩個(gè)寄存器的地址映射,其中GPIOx_BASE在庫函數(shù)里面有定義。接著根據(jù)下圖便可理解GPIO ODR和IDR寄存器映射代碼。
圖1.2寄存器起始地址
//IO口地址映射
#define GPIOA_ODR_Addr(GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr(GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr(GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr(GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr(GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr(GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr(GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr(GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr(GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr(GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr(GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr(GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr(GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr(GPIOG_BASE+8) //0x40011E08
其中:
#define GPIOA_BASE(APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE(APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE(APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE(APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE(APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE(APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE(APB2PERIPH_BASE + 0x2000)
#define APB2PERIPH_BASE(PERIPH_BASE + 0x10000)
#define PERIPH_BASE((uint32_t)0x40000000)
//GPIO輸入輸出位操作
#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 PFout(n)BIT_ADDR(GPIOF_ODR_Addr,n)//輸出
#define PFin(n)BIT_ADDR(GPIOF_IDR_Addr,n)//輸入
#define PGout(n)BIT_ADDR(GPIOG_ODR_Addr,n)//輸出
#define PGin(n)BIT_ADDR(GPIOG_IDR_Addr,n)//輸入
附:對(duì)*((volatile unsigned long*)的理解:
對(duì)于不同的計(jì)算機(jī)體系結(jié)構(gòu),設(shè)備可能是端口映射,也可能是內(nèi)存映射的。如果系統(tǒng)結(jié)構(gòu)支持獨(dú)立的IO地址空間,并且是端口映射,就必須使用匯編語言完成實(shí)際對(duì)設(shè)備的控制,因?yàn)镃語言并沒有提供真正的“端口”的概念。如果是內(nèi)存映射,那就方便的多了。
以#define IOPIN(*((volatile unsigned long *) 0xE0028000))為例:作為一個(gè)宏定義語句,define是定義一個(gè)變量或常量的偽指令。首先(volatile unsigned long *)的意思是將后面的那個(gè)地址強(qiáng)制轉(zhuǎn)換成volatile unsigned long *,unsigned long *是無符號(hào)長整形,volatile是一個(gè)類型限定符,如const一樣,當(dāng)使用volatile限定時(shí),表示這個(gè)變量是依賴系統(tǒng)實(shí)現(xiàn)的,以為著這個(gè)變量會(huì)被其他程序或者計(jì)算機(jī)硬件修改,由于地址依賴于硬件,volatile就表示他的值會(huì)依賴于硬件。
volatile類型是這樣的,其數(shù)據(jù)確實(shí)可能在未知的情況下發(fā)生變化。比如,硬件設(shè)備的終端更改了它,現(xiàn)在硬件設(shè)備往往也有自己的私有內(nèi)存地址,比如顯存,他們一般是通過映象的方式,反映到一段特定的內(nèi)存地址當(dāng)中,這樣,在某些條件下,程序就可以直接訪問這些私有內(nèi)存了。另外,比如共享的內(nèi)存地址,多個(gè)程序都對(duì)它操作的時(shí)候。你的程序并不知道,這個(gè)內(nèi)存何時(shí)被改變了。如果不加這個(gè)voliatile修飾,程序是利用catch當(dāng)中的數(shù)據(jù),那個(gè)可能是過時(shí)的了,加了voliatile,就在需要用的時(shí)候,程序重新去那個(gè)地址去提取,保證是最新的。歸納起來如下:
1. volatile變量可變?cè)试S除了程序之外的比如硬件來修改他的內(nèi)容2.訪問該數(shù)據(jù)任何時(shí)候都會(huì)直接訪問該地址處內(nèi)容,即通過cache提高訪問速度的優(yōu)化被取消。