STM32F429--位帶操作

一、位帶操作的原理

51單片機可以直接對某一位IO進行讀寫操作,而STM32則通過位帶操作來控制一個單獨的IO口。
概念

  • 位帶區:支持位帶操作的地址區。
  • 位帶別名區:對別名地址的訪問最終作用到位帶區的訪問上。位帶別名區對位帶區的訪問有個地址映射過程。
    目的
    對位帶區的比特位進行獨立的讀寫操作,即單獨操作一個位 ,它是通過對位帶別名區的操作來實現。
    具體過程
    對位帶別名區進行讀寫訪問,位帶別名區通過地址映射關係映射到相應的位帶區,對位帶區進行原始比特的讀寫操作。
							   地址映射
對位帶別名區進行讀且操作  ---------------------->對位帶區進行讀且操作(目的)

下面是位帶操作的一些說明

在這裏插入圖片描述
在這裏插入圖片描述

二、計算公式

字節地址爲A,比特位序號爲n(0<=n<=7)
說明:“*4”表示一個字爲4個字節,“*8”表示一個字節中有8個比特。
在這裏插入圖片描述
將上述的外設位帶區和SRAM位帶區進行統一,得到下面的公式:

((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))

其中4*8=32=2的5次方,4=2的2次方,相當於左移了5位和2位
公式解析

  • add &0xF000 0000:目的是取出4和2,用於區分是外設還是SRAM。然後再加上0x2000000就等於外設/SRAM位帶別名區的起始地址。
  • add &0x000FFFFF:屏蔽掉高3位。外設位帶區的最高地址爲0x400F0000,SRAM位帶區的最高地址爲0x200F 0000,(0x400F 0000 - 0X4000 0000)與(0x200F 0000-0x2000 0000)在求偏移地址相減的時候只有低五位有效,所以就把剩下的高3位屏蔽掉,剩下的5位和F做與運算即可。

三、舉例

假如要操作GPIOH的ODR寄存器的10位,就可以通過位帶操作來訪問

//1.宏定義ADDR
#define GPIOH_ODR_ADDR          (GPIOH_BASE+0X14)

//2.求出位帶別名區的地址    傳進兩個參數  參數一:ADDR   參數二:位數(即公式裏面的n)
#define BITBAND(addr,bitnum)    ((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))

//3.把求出來的位帶別名區(立即數)強制類型轉換爲指針,編譯器才知道是地址
//即將BITBAND傳入MEM_ADDR中
#define MEM_ADDR(addr)           (*(volatile unsigned long *)(addr))

假設LED_GPIO_Config()是LED燈初始化程序
此處重點是說明位帶的知識點,詳情可以查看我的上一篇文章:
STM32F429--標準庫點亮LED燈
https://blog.csdn.net/ABCisCOOL/article/details/106170244

此時在main函數裏面直接調用即可,void main(void)
{
	while(1)
	{
	  LED_GPIO_Config()MEM_ADDR(GPIOH_ODR_ADDR,10) == 0}
}

上述的操作是不是相對來說,可讀性還比較差呢,試試下面改進的寫法,是不是有點像51的編程了呢?

//1.宏定義ADDR
#define GPIOH_ODR_ADDR          (GPIOH_BASE+0X14)

//2.求出位帶別名區的地址    傳進兩個參數  參數一:ADDR   參數二:位數(即公式裏面的n)
#define BITBAND(addr,bitnum)    ((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))

//3.把求出來的位帶別名區(立即數)強制類型轉換爲指針,編譯器才知道是地址
//即將BITBAND傳入MEM_ADDR中
#define MEM_ADDR(addr)           (*(volatile unsigned long *)(addr))
//4.嵌套宏
#define BIT_ACTION(addr,bitnum)         MEM_ADDR(BITBAND(addr,bitnum))

//相當於操作PH的端口 如 PH10  ,是不是很接近51了呢
#define PHout(n)                        BIT_ACTION(GPIOH_ODR_ADDR,bitnum) 

void main(void)
{
	while(1)
	{
	  LED_GPIO_Config()PHout(10) == 0}
}

如果我們需要操作多個引腳,可以先將其封裝起來,然後利用
PAout,PBout,…,PHout等方式來對單獨的位進行操作。

附:
本文還說明的還不夠詳細,還是不清楚的朋友,可以看一下火哥的講解視頻:

https://www.bilibili.com/video/BV1Ws411c7A8?p=23

作業

1- 如何實現GPIO端口的ODR寄存器的位操作
2- 如何實現其他GPIO口的IDR寄存器的位操作
3- 重新實現GPIO輸入–按鍵檢測部分的代碼,讀取IO電平的哪一部分代碼,用位帶操作的形式操作
提示:unit16_t temp; temp == PAint(10);
4- 把綠燈和藍燈也點亮

參考答案:

1-
/*GPIOA~GPIOH的ODR寄存器位帶操作,*/
#define GPIOA_ODR_ADDR           (GPIOA_BASE+0X14)
#define GPIOB_ODR_ADDR           (GPIOB_BASE+0X14)
#define GPIOC_ODR_ADDR           (GPIOC_BASE+0X14)
#define GPIOD_ODR_ADDR           (GPIOD_BASE+0X14)
#define GPIOF_ODR_ADDR           (GPIOE_BASE+0X14)
#define GPIOG_ODR_ADDR           (GPIOF_BASE+0X14)
#define GPIOH_ODR_ADDR           (GPIOH_BASE+0X14)


/*中間這三個不需要改動*/
#define BITBAND(addr,bitnum)     ((addr&0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr)           (*(volatile unsigned long *)(addr))
#define BIT_ACTION(addr,bitnum)   MEM_ADDR(BITBAND(addr,bitnum))

#define PAout(n)                  BIT_ACTION(GPIOA_ODR_ADDR,bitnum) 
#define PBout(n)                  BIT_ACTION(GPIOB_ODR_ADDR,bitnum) 
#define PCout(n)                  BIT_ACTION(GPIOC_ODR_ADDR,bitnum) 
#define PDout(n)                  BIT_ACTION(GPIOD_ODR_ADDR,bitnum) 
#define PEout(n)                  BIT_ACTION(GPIOE_ODR_ADDR,bitnum) 
#define PFout(n)                  BIT_ACTION(GPIOF_ODR_ADDR,bitnum) 
#define PGout(n)                  BIT_ACTION(GPIOG_ODR_ADDR,bitnum) 
#define PHout(n)                  BIT_ACTION(GPIOH_ODR_ADDR,bitnum) 

2-
/*GPIOA~GPIOH的IDR寄存器位帶操作,*/
#define GPIOA_IDR_ADDR           (GPIOA_BASE+0X10)
#define GPIOB_IDR_ADDR           (GPIOB_BASE+0X10)
#define GPIOC_IDR_ADDR           (GPIOC_BASE+0X10)
#define GPIOD_IDR_ADDR           (GPIOD_BASE+0X10)
#define GPIOE_IDR_ADDR           (GPIOE_BASE+0X10)
#define GPIOF_IDR_ADDR           (GPIOF_BASE+0X10)
#define GPIOG_IDR_ADDR           (GPIOG_BASE+0X10)
#define GPIOH_IDR_ADDR           (GPIOH_BASE+0X10)

#define PAint(n)                  BIT_ACTION(GPIOA_IDR_ADDR,bitnum) 
#define PBint(n)                  BIT_ACTION(GPIOB_IDR_ADDR,bitnum) 
#define PCint(n)                  BIT_ACTION(GPIOC_IDR_ADDR,bitnum) 
#define PDint(n)                  BIT_ACTION(GPIOD_IDR_ADDR,bitnum) 
#define PEint(n)                  BIT_ACTION(GPIOE_IDR_ADDR,bitnum) 
#define PFint(n)                  BIT_ACTION(GPIOF_IDR_ADDR,bitnum) 
#define PGint(n)                  BIT_ACTION(GPIOG_IDR_ADDR,bitnum) 
#define PHint(n)                  BIT_ACTION(GPIOH_IDR_ADDR,bitnum) 

3-按鍵接的是PA0端口號

uint8_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
  if (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON)
	{
		//如果按下了,KEY_ON 1
		while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);
		//做相應的動作
		return KEY_ON;
		
	}
		else	return KEY_OFF;

}
重新實現如下
uint8_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
  unit16_t temp;
  temp = PAint(0)if (temp== 1)
  {
    printf("有按鍵按下,做相應的動作");
  }

}
4-
紅燈點亮的程序
#define LED_GPIO_PIN   GPIO_Pin_10
#define LED_GPIO_PORT  GPIOH
#define LED_GPIO_CLK   RCC_AHB1Periph_GPIOH

//配置函數
void LED_GPIO_Config(void)
{

	GPIO_InitTypeDef GPIO_InitStruct;
	//0-打開系統時鐘
	RCC_AHB1PeriphClockLPModeCmd(LED_GPIO_CLK,ENABLE);
	
	//1-設置引腳
	GPIO_InitStruct.GPIO_Pin   = LED_GPIO_PIN;
	//2-設置爲輸出模式
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_OUT;
	//3-設置爲推輓輸出類型
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	//4-設置爲上拉
	GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_UP;
	//5-設置速度50MHZ
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
	//把引腳寫進寄存器的函數
	GPIO_Init(LED_GPIO_PORT,&GPIO_InitStruct);
	
}

void  LED_R_LIGHT(void)
{
    PHout(10)==0;
}


綠燈:
#define LED_GPIO_PIN   GPIO_Pin_11

void LED_GPIO_Config(void)
{

	GPIO_InitTypeDef GPIO_InitStruct;
	//0-打開系統時鐘
	RCC_AHB1PeriphClockLPModeCmd(LED_GPIO_CLK,ENABLE);
	
	//1-設置引腳
	GPIO_InitStruct.GPIO_Pin   = LED_GPIO_PIN;
	//2-設置爲輸出模式
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_OUT;
	//3-設置爲推輓輸出類型
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	//4-設置爲上拉
	GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_UP;
	//5-設置速度50MHZ
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
	//把引腳寫進寄存器的函數
	GPIO_Init(LED_GPIO_PORT,&GPIO_InitStruct);
	
}
void  LED_G_LIGHT(void)
{
    PHout(11)==0;
}
藍燈:
#define LED_GPIO_PIN   GPIO_Pin_12

void LED_GPIO_Config(void)
{

	GPIO_InitTypeDef GPIO_InitStruct;
	//0-打開系統時鐘
	RCC_AHB1PeriphClockLPModeCmd(LED_GPIO_CLK,ENABLE);
	
	//1-設置引腳
	GPIO_InitStruct.GPIO_Pin   = LED_GPIO_PIN;
	//2-設置爲輸出模式
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_OUT;
	//3-設置爲推輓輸出類型
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	//4-設置爲上拉
	GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_UP;
	//5-設置速度50MHZ
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
	//把引腳寫進寄存器的函數
	GPIO_Init(LED_GPIO_PORT,&GPIO_InitStruct);
	
}

void  LED_B_LIGHT(void)
{
    PHout(12)==0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章