STM32 IO口位带操作

M4中有4GB的访问空间,访问空间有两个比较重要的地址,寄存器映射地址,又叫别名地址(范围32MB),寄存器地址(范围1MB,固定的)
使用库函数对IO引脚操作比较费时间,需要进行现场保护和现场恢复操作,不能一步到位。使用位带操作能够一步到位,方便快捷。
每个端口都有对应的寄存器地址,查看库函数可以看到对寄存器的的操作。
如:

void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));

  GPIOx->ODR ^= GPIO_Pin;		
}

GPIOx->ODR ^= GPIO_Pin; 就是对ODR寄存器的操作,通过右键 go to definition of 'ODR’可以追寻到寄存器所在的结构体,注释中有说明该寄存器在结构体中的偏移量,ODR的地址是结构体首地址偏移0x14.

宏定义 GPIOF 实际上是一个地址,通过右键 go to definition of …可以逐步追寻到端口的寄存器地址

#define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOF_BASE            (AHB1PERIPH_BASE + 0x1400)
#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000)
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region         

所以GPIOF的地址为
GPIOF = 0x40000000 + 0x00020000 + 0x1400 = 0x40021400
GPIOF的ODR寄存器地址还需要偏移0x14个字节,所以
GPIOF->ODR = GPIOF + 0x14 = 0x40021414

GPIOF地址是寄存器地址,将该地址转换为寄存器映射地址,可以实现端口的位操作,即直接对引脚操作。
转换公式

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

说明:0x42000000是外设位带别名区的起始地址,0x40000000是外设位带区的起始地址,A - 0x40000000指该比特前面有多少个字节,一个字节有8位,所以8,一个位膨胀后是4字节,所以4,A表示端口地址,n表示端口的引脚号。引脚号膨胀后4个字节,所以也*4。

以GPIOF9为例,根据公式可以得出GPIOF9的映射地址为
GPIOF9_AliasAddr = 0x42000000 + (GPIOF - 0x40000000) * 8 * 4 + 9 * 4
= 0x42000000 + (0x40021414 - 0x40000000) * 8 * 4 + 9 * 4

示例,用位带操作实现4个LED的控制
说明:4个LED灯对应的引脚为GPIOF9,GPIOF10,GPIOE13,GPIOE14

/*该代码需要对应的库函数支撑,需要提前添加库函数*/
#include "stm32f4xx.h"

static GPIO_InitTypeDef  GPIO_InitStructure;			//GPIO初始化结构体

/*下列为延时函数代码 参考文章  [STM32系统定时器SysTick,delay的精确编写](https://mp.csdn.net/mdeditor/85646124#)*/
void delay_ms(uint32_t n)
{

	while(n--)
	{
		SysTick->CTRL =0;					//关闭系统定时器
		
		SysTick->LOAD = (21000);			//设置定时时间为1ms
		
		SysTick->VAL  = 0;					//清空标志位
		
		SysTick->CTRL = 1;   				//使能系统定时器工作,开始计数,同时使用的时钟源为系统时钟(21MHz)
		
		while((SysTick->CTRL & 0x00010000)==0);//等待系统定时器计数完毕
		
		SysTick->CTRL =0;					//关闭系统定时器		
	}

}
/*延时函数结束*/

int main(void)
{
	//获取 GPIOF 和 GPIOE 的 ODR 寄存器地址
	uint32_t PF_ODR_Addr = GPIOF_BASE+0x14;		//有现成的宏定义可以使用,直接获取地址,再偏移14字节得到ODR寄存器地址
	uint32_t PE_ODR_Addr = GPIOE_BASE+0x14;		//有现成的宏定义可以使用,直接获取地址,再偏移14字节得到ODR寄存器地址

	//转换为映射地址,共有4个引脚,转换公式  AliasAddr = 0x42000000 + (A - 0x40000000) * 8 * 4 + n * 4
	uint32_t *PF9 = (uint32_t *)(0x42000000+(PF_ODR_Addr - 0x40000000)*8*4+9*4);			//用指针存放地址
	uint32_t *PF10 = (uint32_t *)(0x42000000+(PF_ODR_Addr - 0x40000000)*8*4+10*4);		//用指针存放地址
	uint32_t *PE13 = (uint32_t *)(0x42000000+(PE_ODR_Addr - 0x40000000)*8*4+13*4);		//用指针存放地址
	uint32_t *PE14 = (uint32_t *)(0x42000000+(PE_ODR_Addr - 0x40000000)*8*4+14*4);		//用指针存放地址

/*GPIO口的配置 可参考[STM32 GPIO的配置](https://mp.csdn.net/mdeditor/85568612#)*/
	
	/* 使能端口 E F的时钟,说白点就上为端口E F提供电源,端口F才能工作 */
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOF, ENABLE);
	
	/* 配置端口F第9 10根引脚为输出推挽模式 */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10;			//第9 10根引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;					//配置为输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;					//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;			//控制IO引脚的最大工作速度,工作速度越大,功耗就越高,但是性能也越高
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;				//不需要上下拉电阻
	GPIO_Init(GPIOF, &GPIO_InitStructure);							//初始化端口F
	
	/* 配置端口E第13 14根引脚为输出推挽模式 */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14;			//第13 14根引脚
	GPIO_Init(GPIOE, &GPIO_InitStructure);							//初始化端口E
	
/*GPIO配置结束*/

	//将所有led熄灭,高电平熄灭
	*PF9 = 1;
	*PF10 = 1;
	*PE13 = 1;
	*PE14 = 1;
	
	//实现led的闪烁
	while(1)
	{
		*PF9 ^= 1;
		*PF10 ^= 1;
		*PE13 ^= 1;
		*PE14 ^= 1;
		delay_ms(500);
	}	
	
}
//结束

在开发过程中,可以将比较常用到的IO口的位带操作封装成一个头文件,在正确配置对应的IO口后,能够简便的对引脚进行操作。

/*代码来自粤嵌--温老师*/
#ifndef __SYS_H__
#define __SYS_H__

//位带操作,实现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+20) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414 
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814 
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14 
#define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014 
#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414    
#define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814   
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14    
#define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014     

#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010 
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410 
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10 
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010 
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810 
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10 
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010 
 
//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 PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  //输入

#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)  //输出 
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)  //输入

#endif

你可能感兴趣的:(STM32)