STM32位帶操作詳細介紹

前言

//在STM32中,如果我們要使PB0端口輸出低電平,可以用如下語句
GPIOB->ODR |= 0<<0;
//在51單片機中我們是這樣的
P0 = 0xff;   //總線操作
//還有一種方法
sbit LED1 = P0^0;
LED1 = 0;//位操作

在STM32中能不能實現位操作呢,當然是可以的,這種方法我們叫做位帶操作。位操作就是可以單獨的對一個比特位讀和寫。

1.位帶簡介

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

比如位帶區:外設區,這其中的一個位,可以在位帶別名區重新找到4個字節的地址,也就是32位,來重新命名,而這32位,只有最低位有效。所以空間利用不是很充足,但是提供了方便。

例子:
PB的ODR寄存器地址爲0x40010C0C,假設我們就操作PB0,即操作ODR寄存器第0位,那麼這個位新的地址Addr= =0x42000000+ (0x40010C0C-0x40000000)84 +0*4。

給這個地址命名爲PBout(0);

2.計算公式

外設區:

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

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

SRAM區:

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

公式分析同上

統一公式

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

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

addr & 0xF0000000 是爲了區別 SRAM 還是外設,實際效果就是取出 4 或者 2,如果是外設,則取出的是 4, +0X02000000 之後就等於 0X42000000, 0X42000000 是外設別名區的起始地址。如果是 SRAM,則取出的是 2, +0X02000000 之後就等於 0X22000000,0X22000000 是 SRAM 別名區的起始地址。
addr & 0x00FFFFFF 屏蔽了高三位,相當於減去 0X20000000 或者 0X40000000,但是爲什麼是屏蔽高三位?因爲外設的最高地址是: 0X2010 0000, 跟起始地址 0X20000000 相減的時候,總是低 5 位纔有效,所以乾脆就把高三位屏蔽掉來達到減去起始地址的效果,具體屏蔽掉多少位跟最高地址有關。 SRAM 同理分析即可。 <<5 相當於84, <<2 相當於*4,這兩個我們在上面分析過。

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

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

3.GPIO位帶實現

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

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

//GPIO ODR 和 IDR 寄存器地址映射
#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 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

//單獨操作 GPIO 的某一個 IO 口, n(0,1,2...16),n 表示具體是哪一個 IO 口
#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) //輸入

這樣就可以使用PBout(0),相比之下還是很方便的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章