datasheet

STM32 IO口位带操作

2019-07-12来源: eefocus关键字: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  IO口  位带操作

编辑:什么鱼 引用地址:http://www.eeworld.com.cn/mcu/ic467548.html
本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:STM32学习---位带操作总结
下一篇:STM32F1(Cortex M3内核)位带操作

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

STM32开发笔记71: 解决FreeRTOS任务的内存分配问题

单片机型号:STM32F091RCT6在使用FreeRTOS进行程序设计时,遇到任务不能运行的问题,具体程序如下: DebugOutput("启动USB通讯线程...rnrn"); osThreadDef(usbTask, StartUsbTask, osPriorityNormal, 0, 128); usbTaskHandle = osThreadCreate(osThread(usbTask), NULL); DebugOutput("启动雷达红外通讯线程...rnrn"); osThreadDef(irdaTask, St
发表于 2019-07-13

STM32开发笔记72: 使用命名空间解决类名冲突问题

单片机型号:STM32L053R8T6在程序设计中,使用了两个类,这两个类都有引脚定义并同名,程序如下:#ifndef E32_400T20S_H_#define E32_400T20S_H_ #include "io.h"#include "mini_uart.h" #ifdef __cplusplusextern "C"{ class CM0:public CIO_Output{public: CM0(void);}; class CM1:public CIO_Output{public: CM1(void);}; 
发表于 2019-07-13

STM32开发笔记73: C++中子类调用父类同名函数的处理方法

单片机型号:STM32L053R8T61、问题父类有1方法:Enable_RS485,如下所示:class CUart{public: uint8_t u8_UartNumber; //端口号1-8 uint32_t u32_BaudRate; //波特率 uint8_t u8_Parity; //效验位 CC0 C0; //485控制引脚C0 CC1 C1; //485控制引脚C1 UART_HandleTypeDef hUART; uint8_t u8_UartReceiveBuffer[1];public: CUart(uint8_t
发表于 2019-07-13

STM32开发笔记74: STM32L0低功耗唤醒后的时钟选择

本文介绍STM32L0系列单片机低功耗唤醒后的时钟选择。参看已有的低功耗例程,发现都使能了HSI时钟,一致没有深究其中的具体原因,今天把它搞明白了,现记录如下:先看一下,使能低功耗的函数:void CTarget::EnableLowPower(void){ HAL_PWREx_EnableUltraLowPower(); HAL_PWREx_EnableFastWakeUp(); __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI); DisableAllIO();}第1句使能超低功耗,第2句使能快速唤醒,第3句选择唤醒后的主始终,第4句将所有IO引脚设置为低功耗状态
发表于 2019-07-13

STM32开发笔记75: 使用STM32CubeMX点亮一个LED

今天调试在自己的程序框架下调试RTC始终不成功,只要初始化RTC就进入死机状态。现在重温一下STM32CubeMX的使用方法,看STM32CubeMX生成的程序是否有RTC初始化不成功的问题。本日志从工程的建立讲到点亮一个LED。1、启动STM32CubeMX,我现在使用的版本是5.2.1。2、File-New Project,选择相应的芯片类型。3、双击相应的芯片类型后,进入配置界面。进行SYS配置,选中Debug Serial Wire,由于我习惯于使用FreeRTOS所以在我的项目中Timebase Source都选择定时器。4、进行RCC设置。5、时钟设置如下:6、在芯片引脚图中,将连接LED的引脚设置
发表于 2019-07-13
STM32开发笔记75: 使用STM32CubeMX点亮一个LED

STM32开发笔记76: 初始化RTC后死机的原因

项目开发中只要初始化RTC,则系统死机。其初始化步骤可参考日志:STM32开发笔记44:RTC驱动程序的移植。按照日志STM32开发笔记75: 使用STM32CubeMX点亮一个LED使用STM32CubeMX直接生成程序则运行正常。分析原因在于,少移植了2个函数:HAL_RTC_MspInit和HAL_RTC_MspDeInit。这两个函数的实现非常简单,可以靠STM32CubeMX直接生成。void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc){  __HAL_RCC_RTC_ENABLE();   HAL_NVIC_SetPriority(RTC_IRQn
发表于 2019-07-13

小广播

何立民专栏

单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2019 EEWORLD.com.cn, Inc. All rights reserved