目录
什么是位带操作
什么是位带区
什么是位带别名区
位带操作的实际意义
如何实现位带操作
整体思路
核心代码(以LED与键盘为例)
寄存器封装代码
LED与键盘初始化代码
前言
本文将从以下几点进行讲解:1.什么是位带操作?2.位带操作的实际意义。3.如何实现位带操作。分别对应哲学三问,是什么?为什么?怎么做?
正文
什么是位带操作
位带操作,指的就是单独对一个bit位进行读和写。在51单片机中通过sbit来实现位定义,STM32没有这样的关键字,而是通过访问位带别名区来实现。
什么是位带区
我们可以看到图中有两个位带区,分别是SRAM区里的0X20000000-0X200FFFFF地址段和片内外设区里的0X40000000-0X400FFFFF地址段(图中标号①处),它们的地址空间大小都是1M字节,在SRAM和外设地址段内的这1M大小的空间就是位带区,说白了就是支持位带操作的区域就是位带区。我们上面已经说过了,内核的最小寻址单位是字节,那么怎么将寻址单位缩小到bit?要弄明白这个问题,那就要先弄明白什么是位带别名区了。
什么是位带别名区
从位带别名区名字上理解,感觉它像是别人的替身,实际使用上它就是别人的替身(电影里出名的武打明星都有自己的替身,STM32这么出名,肯定也有自己的替身啦,危险的操作都让替身去做,自己躺着就行了),位带别名区就是为位带区服务的,对位带别名区的操作最终都会反映在位带区上,我们操作位带别名区的时候就等价于在操作位带区地址,那么位带别名区与位带区又是什么样的关联关系呢?
从上面映射图上可以看到,SRAM区里的0X22000000-0X23FFFFFF地址段和外设区里0X42000000-0X43FFFFFF地址段都是位带别名区,两个别名区空间大小都是32M。那么,这32M的位带别名区地址空间是怎么与1M的位带区地址空间对应起来的呢?其实,工程师们想出了一个很好的办法,地址映射,将1M字节里面的每一个bit映射到32M字节里面去,那么怎么映射呢?看到这里可能有些小伙伴就亿脸懵逼了,懵逼的话可以看一下下面的演算。
各种单位运算关系
1字节 = 8bit
1字 = 4字节
如果对这些单位没有什么概念,可以看下图
将1bit映射到1个字空间里(如下图)
映射前的一个字节 = 映射后的8个字
那么就有
映射前的一个字节 = 映射后的32个字节
映射前的1M字节 = 映射后的32M字节
好奇的小伙伴可能就要问了,为什么要将1bit空间要映射到一个字空间里去呢?我映射到1字节或者2字节的地址空间不行吗?我只能说,STM32是一个32位的机器,内核按字寻址的话寻址速度是最快的,所以别问这么多为什么,如果问了,答案就是为了速度。
位带操作的实际意义
学过51的话,理解起来就简单了,我们都知道使用51时,我们可以直接读写gpio的某一位,像这样就属于位操作,如下:
sbit led0 = P3 ^ 0; sbit led1 = P3 ^ 1; led0 = 1; led1 = 0;
这样操作起来就非常方便,但是STM32没有这样的关键字,只能通过访问位带别名区的位带操作来实现。也就是上面介绍的两个概念,这样一来,STM32的全部寄存器都可以通过访问位带别名区的方式来达到访问原始寄存器位的效果,这比51单片机强大很多。因为51单片机里面并不是所有的寄存器都可以进行位操作,有些寄存器还是得用字节操作,比如SBUF。还有一个就是上面说的,STM32安字寻址速度很快。
如何实现位带操作
整体思路
1.对GPIO的IDR和ODR寄存器位操作进行封装
2.编写对应模块的驱动程序
2.1.编写头文件:宏定义连接模块的端口、端口引脚、端口时钟、引脚位带,函数声明
2.2.编写驱动文件
3.主函数使用
核心代码(以LED与键盘为例)
寄存器封装代码
//system.c文件 #include "system.h" //头文件中把GPIO的IDR和ODR寄存器位操作进行了封装 //system.h文件 #ifndef __system_H__ #define __system_H__ #include "stm32f10x.h" //位带操作,实现51类似的GPIO控制功能 //具体实现思想,参考<<CM3权威指南>>第五章(87页~92页). //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) //输入 #endif
LED与键盘初始化代码
void LED0_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; //使能GPIOB/能GPIOE/AFIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE|RCC_APB2Periph_AFIO, ENABLE); //RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); //RCC->APB2ENR|=1<<0; //使能AFIO 时钟 WRITE_REG(AFIO->MAPR, AFIO_MAPR_SWJ_CFG_JTAGDISABLE); //允许SW-DP调试,禁止JYAG //AFIO->MAPR |= 0x02000000; //JTAG-DP Disabled and SW-DP Enabled //配置GPIOB-->推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //3号引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //快速 GPIO_Init(GPIOB, &GPIO_InitStructure); //根据GPIO_InitStructure中指定的参数初始化外设 GPIOB 寄存器 //配置GPIOE-->推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //快速 GPIO_Init(GPIOE, &GPIO_InitStructure); //根据GPIO_InitStructure中指定的参数初始化外设 GPIOB 寄存器 GPIO_ResetBits(GPIOE, GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); GPIO_SetBits(GPIOB, GPIO_Pin_3); //LED_SEL=1 // GPIOB->ODR |= (1<<3); // PBout(3)=1; // GPIOE->ODR |= 1; // PEout(1) = 1; }