我相信很多朋友在學習單片機之前都學習過51單片機,假設在51單片機的P1.1的IO口上掛了一個LED,那么你單獨對LED的操作就是P1.1 = 0或P1.1 = 1,這樣你就可以單獨的對P1端的第一個IO口進行上下拉操作,然而對于STM32,是沒有這種操作的,那么為了像51單片機一樣能夠單獨的對某個端的某一個IO單獨操作,就引入了__位帶操作__,簡而言之,就是為了去單獨操作STM32里面PA的第1個IO口,所以才有了位帶這樣的操作機制。
1 什么是位帶操作
在講解位帶操作之前,首先要搞清楚什么是位帶操作。我們知道,32位的處理器的32位地址總線提供了4G的地址空間,幾乎所有的嵌入式產品是足夠用的。 Cortex-M就利用了額外的空間實現了稱為位帶(Bit-Banding)操作的硬件屬性,該技術使用地址空間的兩個不同區域來指向同一物理地址 。在主位帶區域,每個地址對應一個字節的數據,在“位帶別名”區域中,每個地址對應同一個數據的一個位。
如下圖所示。在CM3的寄存器映射圖中有1MB的 bit band區,這里被稱為位帶區,與之對應的是32MB的bit band別名區,這里被稱為位帶別名區。
STM32的位帶別名區會把位帶區中的每一位膨脹成一個32位的字,所以相應的別名區的內存也會是位帶區的32倍。從上圖可以看出,位帶操作同時支持SRAM和片上外設,支持位帶操作的兩個內存區域范圍如下:
SRAM區:0x20000000 ~ 0x200FFFFF,最低1M的范圍;
片上外設區: 0x40000000 ~ 0x400FFFFF,最低1M的范圍;
位帶操作就是把位帶區中一個地址的8個位分別映射到位帶別名區的8個地址(LSB有效,即最低位有效),通過操作相應地址的方式實現操作某個位。以SRAM為例,位帶區和位帶別名區的映射如下圖所示:
位帶區里每個地址的每1位膨脹為別名區里一個32位的字(32位處理器中,1字=4字節),例如:0x20000000的第0位對應0x22000000,第1位對應0x22000004等。
2 位帶操作的計算公式
既然位帶操作屬于Cortex-M內核的一部分,那么在Cortex-M官方手冊也是給出了相應的計算公式的,其通用公式如下:
別名區地址 = 別名區起始地址 + (位字節地址偏移量 * 8 + n) * 4
其中,8表示一個字節有8位,4表示膨脹了4個字節,因此位帶區和位帶別名區也就是32倍的關系。
兩個區的計算公式為:
SRAM區:AliasAddr = 0x22000000 + (A - 0x20000000) * 32 + n * 4
片上外設區:AliasAddr = 0x42000000 + (A - 0x40000000)* 32 + n * 4
其中,AliasAddr是別名區的地址,A是位帶區的地址,n是該端口的上的某一位。
接下來就是對這個地址進行操作了,寫1,該位輸出1,寫0,就輸出0。
3 位帶操作代碼實現
這里STM32F1為例,根據STM32的《RM0008 Reference manual》手冊,其GPIO的地址映射如下:
GPIOx_ODR 寄存器如下:
每個寄存器32位,占4個地址,在訪問或修改某個寄存器時,是從首地址開始的,邏輯運算則是直接可涵蓋到32bit,offset 為 0x0C。GPIOA 的首地址為0x40010800,因此GPIOx_ODR 寄存器的地址為0x4001080C。則所有的GPIO映射如下:
//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
上述只是位帶區的地址,根據位帶操作的計算公式,則操作位帶別名區的地址方法如下:
//IO口操作宏定義
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
以上代碼的第一句是轉換的關鍵,當然相對的前面的計算公式做了優化,也就是將SRAM和片上外設合并在一起。addr & 0XF0000000 得到SRAM和片上外設的首地址,然后加0x2000000表示位帶別名區相對位帶區的偏移量,(addr &0xFFFFF)<<5)和(bitnum<<2)就是前面“*32”和“*4”,只是換成了移位操作,因為移位操作相對乘法運算速度更快。
好了,接下來使用位帶操作來寫一個GPIO流水燈,同時使用庫函數來做比較。
【main.c】
/* Includes ------------------------------------------------------------------*/
#include "stm32f1_bsp_led.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/*簡單延時函數*/
void Delay(uint32_t xms);
/* Private functions ---------------------------------------------------------*/
/**
* @brief 主函數
* @param None
* @retval
*/
int main(void)
{
/* LED 初始化 */
LED_GPIO_Config();
while (1)
{
#if 0
GPIO_SetBits(GPIOB,GPIO_Pin_0); // 亮
Delay(0xfFfff);
GPIO_ResetBits(GPIOB,GPIO_Pin_0); // 滅
GPIO_SetBits(GPIOG,GPIO_Pin_6); // 亮
Delay(0xfFfff);
GPIO_ResetBits(GPIOG,GPIO_Pin_6); // 滅
GPIO_SetBits(GPIOG,GPIO_Pin_7); // 亮
Delay(0xffFff);
GPIO_ResetBits(GPIOG,GPIO_Pin_7); // 滅
#else
PBout(0) = 1; // 亮
Delay(0xfFfff);
PBout(0) = 0; // 滅
PGout(6) = 1; // 亮
Delay(0xfFfff);
PGout(6) = 0; // 滅
PGout(7) = 1; // 亮
Delay(0xffFff);
PGout(7) = 0; // 滅
#endif
}
}
/**
* @brief 延時函數
* @param
xms 延時長度
* @retval None
*/
void Delay( uint32_t xms)
{
//for(; nCount != 0; nCount--);(方法一)
while(xms--);//(方法二)
}
【stm32f1_bsp_led.c】
/* Includes ------------------------------------------------------------------*/
#include "stm32f1_bsp_led.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/**
* @brief 初始化LED的GPIO
* @param None
* @retval None
*/
void LED_GPIO_Config(void)
{
/*定義一個GPIO_InitTypeDef類型的結構體*/
GPIO_InitTypeDef GPIO_InitStructure;
/*開啟LED的外設時鐘*/
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOG, ENABLE);
/*設置IO口*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //設置引腳模式為通用推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //設置引腳速率為50MHz
/*調用庫函數,初始化GPIOB0*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //選擇要控制的GPIOB引腳
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;/*選擇要控制的引腳*/
GPIO_Init(GPIOG, &GPIO_InitStructure);
/* 開啟所有led燈*/
GPIO_SetBits(GPIOB, GPIO_Pin_0);
GPIO_SetBits(GPIOG, GPIO_Pin_6|GPIO_Pin_7);
}
【stm32f1_bsp_led.h】
#ifndef __STM32F1_BSP_LED_H__
#define __STM32F1_BSP_LED_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
//位帶操作,實現51類似的GPIO控制功能
//具體實現思想,參考<
//IO口操作宏定義
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//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
//IO口操作,只對單一的IO口!
//確保n的值小于16!
#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) //輸入
#define ON 1
#define OFF 0
/* 帶參宏,可以像內聯函數一樣使用 */
#define LED1(a) if (a) \\
GPIO_SetBits(GPIOB,GPIO_Pin_0);\\
else \\
GPIO_ResetBits(GPIOB,GPIO_Pin_0)
#define LED2(a) if (a) \\
GPIO_SetBits(GPIOG,GPIO_Pin_6);\\
else \\
GPIO_ResetBits(GPIOG,GPIO_Pin_6)
#define LED3(a) if (a) \\
GPIO_SetBits(GPIOG,GPIO_Pin_7);\\
else \\
GPIO_ResetBits(GPIOG,GPIO_Pin_7)
/* 直接操作寄存器的方法控制IO */
#define digitalHi(p,i) {p->BSRR=i;} //設置為高電平
#define digitalLo(p,i) {p->BRR=i;} //輸出低電平
#define digitalToggle(p,i) {p->ODR ^=i;} //輸出反轉狀態
/* 定義控制IO的宏 */
#define LED1_TOGGLE digitalToggle(GPIOB,GPIO_Pin_0)
#define LED1_ON digitalHi(GPIOB,GPIO_Pin_0)
#define LED1_OFF digitalLo(GPIOB,GPIO_Pin_0)
#define LED2_TOGGLE digitalToggle(GPIOC,GPIO_Pin_4)
#define LED2_ON digitalHi(GPIOG,GPIO_Pin_6)
#define LED2_OFF digitalLo(GPIOG,GPIO_Pin_6)
#define LED3_TOGGLE digitalToggle(GPIOC,GPIO_Pin_3)
#define LED3_ON digitalHi(GPIOG,GPIO_Pin_7)
#define LED3_OFF digitalLo(GPIOG,GPIO_Pin_7)
/* Exported functions ------------------------------------------------------- */
void LED_GPIO_Config(void);
#ifdef cplusplus
}
#endif
#endif /* __STM32F1_BSP_LED_H__ */
不管使用哪種方式,其實驗現象都是一樣的,但是使用位帶操作更方便些,操作者步驟更少,下面舉例說明。
實例:欲設置地址 0x2000_0000 中的比特 2,則使用普通操作和位帶操作的設置過程如下圖所示:
普通操作和位帶操作的匯編對比代碼如下:
位帶讀操作相對簡單,普通操作和位帶操作的設置過程如下圖所示:
普通操作和位帶操作的匯編對比代碼如下:
可以看出位帶操作的步驟更少,相對普通操作更簡潔。
而且位帶操作屬于原子操作,在多任務系統中,位帶操作可以解決共享資源中的紊亂危象,關于該部分內容可以參看《Cortex-M3權威指南》。
__總的來說,位帶的主要優點__是數據的一個單獨位可以通過一條指令來讀或者寫,而不需要操作一些利的寄存器。例如,一條從位帶別名區域地址進行讀操作的LDR指令會將值0或者1加1載入寄存器。類似的,一條STR指令在向位帶別名區的地址寫入時,只是修改主區域中數據的一位。當然修改需要由硬件來執行讀寫操作,但是只有一條指令(STR)被取指并執行。
審核編輯:湯梓紅
-
處理器
+關注
關注
68文章
19259瀏覽量
229651 -
單片機
+關注
關注
6035文章
44554瀏覽量
634634 -
嵌入式
+關注
關注
5082文章
19104瀏覽量
304804 -
寄存器
+關注
關注
31文章
5336瀏覽量
120230 -
Cortex-M
+關注
關注
2文章
229瀏覽量
29752
發布評論請先 登錄
相關推薦
評論