第13章 GPIO-位帶操作—零死角玩轉STM32-F429系列


第13章     GPIO—位帶操作

全套200集視頻教程和1000PDF教程請到秉火論壇下載:www.firebbs.cn

野火視頻教程優酷觀看網址:http://i.youku.com/firege

 

本章參考資料:《STM32F4xx 中文參考手冊》存儲器和總線構架章節、GPIO章節,《Cortex®-M4內核編程手冊》2.2.5 Bit-banding。學習本章時,配套這些參考資料學習效果會更佳。

13.1 位帶簡介

位操作就是可以單獨的對一個比特位讀和寫,這個在51單片機中非常常見。51單片機中通過關鍵字sbit來實現位定義,F429中沒有這樣的關鍵字,而是通過訪問位帶別名區來實現。

F429中,有兩個地方實現了位帶,一個是SRAM區的最低1MB空間,另一個是外設區最低1MB空間。這兩個1MB的空間除了可以像正常的RAM一樣操作外,他們還有自己的位帶別名區,位帶別名區把這1MB的空間的每一個位膨脹成一個32位的字,當訪問位帶別名區的這些字時,就可以達到訪問位帶區某個比特位的目的。

131 F429位帶地址

13.1.1 外設位帶區

外設位帶區的地址為:0X40000000~0X400F0000,大小為1MB,這1MB的大小包含了APB1/2AHB1上所以外設的寄存器,AHB2/3總線上的寄存器沒有包括。AHB2總線上的外設地址范圍為:0X50000000~0X50060BFFAHB3總線上的外設地址范圍為:0XA0000000~0XA0000FFF。外設位帶區經過膨脹后的位帶別名區地址為:0X42000000~0X43FFFFFF,這部分地址空間為保留地址,沒有跟任何的外設地址重合。

13.1.2 SRAM位帶區

SRAM的位帶區的地址為:0X2000 0000~X200F 0000,大小為1MB,經過膨脹后的位帶別名區地址為:0X2200 0000~0X23FF FFFF,大小為32MB。操作SRAM的比特位這個用得很少。

13.1.3 位帶區和位帶別名區地址轉換

位帶區的一個比特位經過膨脹之后,雖然變大到4個字節,但是還是LSB才有效。有人會問這不是浪費空間嗎,要知道F429的系統總線是32位的,按照4個字節訪問的時候是最快的,所以膨脹成4個字節來訪問是最高效的。

我們可以通過指針的形式訪問位帶別名區地址從而達到操作位帶區比特位的效果。那這兩個地址直接如何轉換,我們簡單介紹一下。

1.    外設位帶別名區地址

對於片上外設位帶區的某個比特,記它所在字節的地址為 A,位序號為 n(0<=n<=7),則該比特在別名區的地址為:

1 AliasAddr= =0x42000000+ (A-0x40000000)*8*4 +n*4 
			

0X42000000是外設位帶別名區的起始地址,0x40000000是外設位帶區的起始地址,(A-0x40000000)表示該比特前面有多少個字節,一個字節有8位,所以*8,一個位膨脹后是4個字節,所以*4n表示該比特在A地址的序號,因為一個位經過膨脹后是四個字節,所以也*4

2.    SRAM位帶別名區地址

對於SRAM位帶區的某個比特,記它所在字節的地址為 A,位序號為 n(0<=n<=7),則該比特在別名區的地址為:

 1 AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4 
			

公式分析同上。

3.    統一公式

為了方便操作,我們可以把這兩個公式合並成一個公式,把"位帶地址+位序號"轉換成別名區地址統一成一個宏。

 1 // "位帶地址+位序號"轉換成別名地址的宏 
 2 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr & 
 0x000FFFFF)<<5)+(bitnum<<2))
			

addr & 0xF0000000是為了區別SRAM還是外設,實際效果就是取出4或者2,如果是外設,則取出的是4+0X02000000之后就等於0X420000000X42000000是外設別名區的起始地址。如果是SRAM,則取出的是2+0X02000000之后就等於0X220000000X22000000SRAM別名區的起始地址。

addr & 0x00FFFFFF 屏蔽了高三位,相當於減去0X20000000或者0X40000000,但是為什么是屏蔽高三位?因為外設的最高地址是:0X2010 0000,跟起始地址0X20000000相減的時候,總是低5位才有效,所以干脆就把高三位屏蔽掉來達到減去起始地址的效果,具體屏蔽掉多少位跟最高地址有關。SRAM同理分析即可。<<5相當於*8*4<<2相當於*4,這兩個我們在上面分析過。

最后我們就可以通過指針的形式操作這些位帶別名區地址,最終實現位帶區的比特位操作。

 1 // 把一個地址轉換成一個指針
			
 2 #define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) 
 3 
			
 4 // 把位帶別名區地址轉換成指針
			
 5 #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum)) 

13.2 GPIO位帶操作

外設的位帶區,覆蓋了全部的片上外設的寄存器,我們可以通過宏為每個寄存器的位都定義一個位帶別名地址,從而實現位操作。但這個在實際項目中不是很現實,也很少人會這么做,我們在這里僅僅演示下GPIOODRIDR這兩個寄存器的位操作。

從手冊中我們可以知道ODRIDR這兩個寄存器對應GPIO基址的偏移是2016,我們先實現這兩個寄存器的地址映射,其中GPIOx_BASE在庫函數里面有定義。

1.    GPIO 寄存器映射

代碼 9 GPIO ODR IDR 寄存器映射

1 // GPIO ODR IDR 寄存器地址映射

 2 #define GPIOA_ODR_Addr (GPIOA_BASE+20) 
 3 #define GPIOB_ODR_Addr (GPIOB_BASE+20) 
 4 #define GPIOC_ODR_Addr (GPIOC_BASE+20) 
 5 #define GPIOD_ODR_Addr (GPIOD_BASE+20) 
 6 #define GPIOE_ODR_Addr (GPIOE_BASE+20) 
 7 #define GPIOF_ODR_Addr (GPIOF_BASE+20) 
 8 #define GPIOG_ODR_Addr (GPIOG_BASE+20) 
 9 #define GPIOH_ODR_Addr (GPIOH_BASE+20) 
10 #define GPIOI_ODR_Addr (GPIOI_BASE+20) 
11 #define GPIOJ_ODR_Addr (GPIOJ_BASE+20) 
12 #define GPIOK_ODR_Addr (GPIOK_BASE+20) 
13 
			
14 #define GPIOA_IDR_Addr (GPIOA_BASE+16) 
15 #define GPIOB_IDR_Addr (GPIOB_BASE+16) 
16 #define GPIOC_IDR_Addr (GPIOC_BASE+16) 
17 #define GPIOD_IDR_Addr (GPIOD_BASE+16) 
18 #define GPIOE_IDR_Addr (GPIOE_BASE+16) 
19 #define GPIOF_IDR_Addr (GPIOF_BASE+16) 
20 #define GPIOG_IDR_Addr (GPIOG_BASE+16) 
21 #define GPIOH_IDR_Addr (GPIOH_BASE+16) 
22 #define GPIOI_IDR_Addr (GPIOI_BASE+16) 
23 #define GPIOJ_IDR_Addr (GPIOJ_BASE+16) 
24 #define GPIOK_IDR_Addr (GPIOK_BASE+16) 
			

現在我們就可以用位操作的方法來控制GPIO的輸入和輸出了,其中宏參數n表示具體是哪一個IO口,n(0,1,2...16)。這里面包含了端口A~K ,並不是每個單片機型號都有這么多端口,使用這部分代碼時,要查看你的單片機型號,如果是176pin的則最多只能使用到I端口。

2.    GPIO位操作

代碼 10 GPIO 輸入輸出位操作

 1 // 單獨操作 GPIO的某一個IO口,n(0,1,2...16),n表示具體是哪一個IO 
 2 #define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //輸出 
 3 #define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //輸入 
 4 
			
 5 #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //輸出 
 6 #define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //輸入 
 7 
			
 8 #define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //輸出 
 9 #define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //輸入 
10 
			
11 #define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //輸出 
12 #define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //輸入 
13 
			
14 #define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //輸出 
15 #define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //輸入 
16 
			
17 #define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //輸出 
18 #define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //輸入 
19 
			
20 #define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //輸出 
21 #define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //輸入 
22 
			
23 #define PHout(n) BIT_ADDR(GPIOH_ODR_Addr,n) //輸出 
24 #define PHin(n) BIT_ADDR(GPIOH_IDR_Addr,n) //輸入 
25 
			
26 #define PIout(n) BIT_ADDR(GPIOI_ODR_Addr,n) //輸出 
27 #define PIin(n) BIT_ADDR(GPIOI_IDR_Addr,n) //輸入 
28 
			
29 #define PJout(n) BIT_ADDR(GPIOJ_ODR_Addr,n) //輸出 
30 #define PJin(n) BIT_ADDR(GPIOJ_IDR_Addr,n) //輸入 
31 
			
32 #define PKout(n) BIT_ADDR(GPIOK_ODR_Addr,n) //輸出 
33 #define PKin(n) BIT_ADDR(GPIOK_IDR_Addr,n) //輸入 

 

3.    主函數

該工程我們直接從LED-庫函數操作移植過來,有關LED GPIO 初始化和軟件延時等函數我們直接用,修改的是控制GPIO輸出的部分改成了位操作。該實驗我們讓相應的IO口輸出高低電平來控制LED的亮滅,負邏輯點亮。具體使用哪一個IO和點亮方式由硬件平台決定。

代碼 11 main 函數

 1 int main(void) 
 2 { 
 3  /* LED 端口初始化 */ 
 4  LED_GPIO_Config(); 
 5 
			
 6  while (1) { 
 7  // PH10 = 0,點亮LED 
 8  PHout(10)= 0; 
 9  SOFT_Delay(0x0FFFFF); 
10 
			
11  // PH10 = 1,熄滅LED 
12  PHout(10)= 1; 
13  SOFT_Delay(0x0FFFFF); 
14  } 
15 
			
16 } 
			

13.3 每課一問

1、利用位帶操作的方法,寫一個GPIO輸入的例程,比如按鍵采集。

2、如果使用的不是GPIO這個外設,而是其他的外設,那么公式該怎么改,比如要使用的外設是ADC。


免責聲明!

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



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