位带操作的应用场景,通常在于对IO口进行输入输出读取和控制。
这就和51单片机中直接的端口赋值是一样的。
比如
P0.0 = 0;
P0.0 = 1;
直接就是对端口P0.0输出0和1。
如何实现呢?
什么是位带操作
位带操作简单的说,就是把每个比特膨胀为一个32位的字,当访问这些字的时候就达到了访问比特的目的,比如说GPIO的ODR寄存器有32个位,那么可以映射到32个地址上,我们去访问这32个地址就达到访问32个比特的目的。这样我们往某个地址写1就达到往对应比特位写1的目的,同样往某个地址写0就达到往对应的比特位写0的目的。
对于上图,我们往Address0地址写入1,那么就可以达到往寄存器的第0位Bit0赋值1的目的。这里我们不讲得过于复杂,因为位带操作在实际开发中可能只是用来IO口的输入输出还比较方便,其他操作在日常开发中也基本很少用。
手册中的相关介绍
Cortex™-M4F 存储器映射包括两个位段区域。这些区域将存储器别名区域中的每个字映射 到存储器位段区域中的相应位。在别名区域写入字时,相当于对位段区域的目标位执行读-修
改-写操作。
在 STM32F4xx 器件中,外设寄存器和 SRAM 均映射到一个位段区域,这样可实现单个位段的读写操作。这些操作仅适用于Cortex™-M4F访问,对于其它总线主接口(如 DMA)无效。
可通过一个映射公式说明别名区域中的每个字与位段区域中各个位之间的对应关系。映射公
式为:
bit_word_addr = bit_band_base + (byte_offset x 32) + (bit_number × 4)
其中:
— bit_word_addr 代表别名区域中将映射到目标位的字的地址
— bit_band_base 代表别名区域的起始地址
— byte_offset 代表目标位所在位段区域中的字节编号
— bit_number 代表目标位的位位置 (0-7)
下例说明如何将 SRAM 地址 0x20000300 处字节的位 2 映射到别名区域:
0x22006008 = 0x22000000 + (0x300*32) + (2*4)
对地址 0x22006008 执行写操作相当于在 SRAM 地址 0x20000300 处字节的位 2 执行读-修
改-写操作。
对地址 0x22006008 执行读操作将返回 SRAM 地址 0x20000300 处字节的位 2 的值(0x01
表示位置位,0x00 表示位复位)。
有关位段的详细信息,请参见Cortex™-M4F编程手册。
具体定义
映射关系如下所示
具体不深究了,大概知道映射公式是这样的就行
膨胀关系示意图
通常可在头文件中定义
//位带操作,实现51类似的GPIO控制功能 //具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).M4同M3类似,只是寄存器地址变了. //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) //输入
以上代码的便是 GPIO 位带操作的具体实现,位带操作的详细说明,在权威指南中有详细讲解,请参考<<CM3 权威指南>>第五章(87 页~92 页)。比如说,我们调用 PAout(1)=1是设置了 GPIOA 的第一个管脚 GPIOA.1 为 1,实际是设置了寄存器的某个位,但是我们的定义中可以跟踪过去看到却是通过计算访问了一个地址。上面一系列公式也就是计算GPIO 的某个 io 口对应的位带区的地址了。有了上面的代码,我们就可以像51/AVR一样操作STM32的IO口了。比如,我要PORTA的第七个 IO 口输出 1,则可以使用 PAout(6)=1;即可实现。我要判断 PORTA 的第15个位是否等于 1,则可以使用 if(PAin(14)==1)…;就可以了。
注意:
使用前必须先进行IO口时钟的使能和 IO口功能初始化。
补充了解