(四)位带操作
1.位带操作原理
-
STM32将每个比特位膨胀成为32字,访问这些字就实现了访问位带别名区。膨胀的新地址使用的是未使用的Reserved区域。
-
Bit band alias 位带别名区,大小为2000000;Bit band region 位带区域,大小为100000。二者之间呈32倍关系。
注:1字节(Byte)=8位(bit);位带区的1位经过膨胀后在位带别名区变为32位(即4Byte)。
1.1.STM32位带及位带别名区域
- 支持位带操作的区域是 SRAM区的最低1MB范围(APB1/2、AHB1外设)和片内外设区的最低1MB范围。
- 一般是对GPIO进行位带操作。
1.2.位带区和位带别名区地址转换计算公式
-
外设位带区与外设位带别名区
AliasAddr = 0x42000000+ (A-0x40000000)*8*4 +n*4
-
SRAM位带区与SRAM位带别名区
AliasAddr = 0x22000000+ (A-0x20000000)*8*4 +n*4
其中,A表示我们要操作的那个位所在的寄存器地址,n是位序号。
综上,总结出统一公式为:
((A & 0xF0000000)+0x02000000+((A &0x000FFFFF)<<5)+(n<<2))
2.位带操作编程
使用KEIL5新建相关文件
-
在KEIL5中新建文件夹与程序文件夹中,命名为“Public”,并在其内部新建文件名为“system.c”和“system.h”的两个文件。
-
“system.h”编写如下:
#ifndef _system_H #define _system_H #include "stm32f4xx.h" #endif
-
将如下路径中的TXT文件内容复制到“system.h”,最终效果如下:
#ifndef _system_H #define _system_H #include "stm32f4xx.h" #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
__路径为:__D:\BaiduNetdiskDownload\32单片机\普中STM32-PZ6808L-F4开发板资料\普中STM32-PZ6808L-F4开发板资料–B\普中STM32-PZ6808L-F4开发板视频教程\视频讲解PPT\15. STM32位带操作
注:该部分内容完成的是位带操作的膨胀及GPIO端口定义。
-
在“system.c”内编写如下:
#include "system.h"
-
在“zs_pro_standard.h”内补充对"system.h"的引用,并删除对"stm32f4xx.h"的引用,最终效果如下:
#ifndef _zs_pro_standard_H #define _zs_pro_standard_H #include "system.h" #endif
-
全部完成后应在main.c中引用"system.h",而在"system.h"中引用过"stm32f4xx.h",所以在main.c程序中替换即可。
#include "system.h" #include "zs_pro_standard.h" int main() { LED_Init();//调用该函数对其进行初始化 while(1) { GPIO_ResetBits(GPIOF,GPIO_Pin_9);//低电平设置函数 } }
-
在工程组中将“Public”文件夹添加进入、在魔术锤的C/C++中将头文件所在文件夹(即“Public”文件夹)添加进入。
-
在"zs_pro_standard.h"中定义所使用的的管脚,最终效果如下:
#ifndef _zs_pro_standard_H #define _zs_pro_standard_H #include "stm32f4xx.h" void LED_Init(void); //声明时添加本行即可 #define led1 PFout(9) #define led2 PFout(10) #endif
-
对应于步骤8,修改"main.c"中对应的函数
#include "system.h" #include "zs_pro_standard.h" int main() { LED_Init();//调用该函数对其进行初始化 while(1) { led1=!led1; delay(0xFFFFFF);//低电平设置函数 } }
但是"zs_pro_standard.c"文件中的函数是__不用改变的__,依然如下。
#include "zs_pro_standard.h" //以点亮LED为例,写如下程序 void LED_Init() { GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量,此行必须要在GPIO端口时钟使能程序的上方 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);//使能对应的GPIO端口时钟 GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //输出模式 GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//管脚设置F9 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度为100M GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出 GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉 GPIO_Init(GPIOF,&GPIO_InitStructure); //初始化结构体 GPIO_SetBits(GPIOF,GPIO_Pin_9)//此行用于事先预置管脚的高低电平 }
注:使用位带操作与51单片机相同,可使用下面的程序来控制端口电平的高低
led1=1; led2=0;