位带操作bit band
Cortex-M3内核的ARM芯片,支持位带(bit band)操作。位带操作就是使用普通的加载/存储指令来对单一的比特进行读写。在CM3中,有两个区中实现了位带。其中一个是SRAM区的最低1MB范围,第二个则是片内外设区的最低1MB范围。这两个区中的地址除了可以像普通的RAM一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特“膨胀”成一个32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。
现在以STM32F103的GPIOA为例,对位带操作做如下理解:
如果需要对GPIOA中的某一个IO进行操作,如GPIOA的GPIOA.0,将其作为输出,根据需要置零和置一。GPIOA的输出端口输出数据寄存器(GPIOA_ODR,32bit寄存器,低16位有效)地址为GPIOA的起始地址(0x4001 0800)加上其偏移地址(00Ch),为0x4001 080C;GPIOA_ODR从低到高的16bit分别对应GPIOA.0到GPIOA.15。对于cortex M3,是不能直接对GPIOA_ODR的单一bit进行操作的,所以就不能直接对单个IO进行写操作。也就是说此时对地址为0x4001 080C的寄存器(GPIOA_ODR)写操作时,不能达到写单个IO输出数据的目的。
但有两种方式可解决上述问题:一是通过相关库函数,二就是位带操作。库函数很方便,使用简单,但是执行效率不高;位带操作复杂一点,但是执行效率较高。位带操作其实就是对GPIOA_ODR的一组(16个)等效别名地址进行写操作,每一个等效别名地址,对应于GPIOA_ODR的一个bit,等于是对GPIOA_ODR的每一个bit命名了一个地址,这个地址就叫做等效别名地址。从而实现了对单一IO的位操作。其实,GPIOA_ODR对应的等效别名地址有32个,但是有实际意义的,只有16个,因为GPIOA_ODR只有低16位有效。
GPIOA_ODR的地址位于“位带区”,支持位带操作,因为GPIOA_ODR位于片上外设区中的最低1MB,所以其地址对应的32个等效别名地址是按照下面公式计算得来的:
AliasAddr= 0x42000000+((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4
公式中A是GPIOA_ODR的地址0x4001 080C,n是需要操作的IO的位,现在是GPIOA.0,所以n=0,AliasAddr就是等效别名地址。此时等效的别名地址AliasAddr=0x42210180,对该地址置零和置一操作,即实现了对GPIOA.0的置零和置一。
至于位带操作的C实现,可参阅sys.h文件。现将相关代码分析如下:
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) //1
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) //2
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum)) //3
//IO口地址映射 //4
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C //5
…………………后续代码都是对IO映射地址的定义 //6
//IO口操作,只对单一的IO口! //7
//确保n的值小于16! //8
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出 //9
…………………后续代码是对不同IO输入输出的定义 //10
对于上述代码,应该从IO输出定义(第9行)开始阅读。继续以GPIOA为例,进行说明。首先定义了GPIOA的输出PAout(n),我们在函数中#include "sys.h"后,就可以直接使用PAout(n)来控制GPIOAde的单一bit输出了。
其中n为需要输出的bit。GPIOA_ODR_Addr的地址,就是GPIOA_ODR寄存器的地址,该地址的定义,在第5行实现;对于BIT_ADDR,第3行进行了定义。对于1、2、3行,我们做如下理解:
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
↓
#define BIT_ADDR(addr, bitnum) MEM_ADDR(((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)))
↓
#define BIT_ADDR(addr, bitnum) *((volatile unsigned long *)(((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))))
其实上述步骤,就相当于是一步一步的“代入”。
第1行是计算等效别名地址的公式,无论是SRAM区的最低1MB范围,还是片内外设区的最低1MB范围,该公式都适用;第2行首先将addr转化成指针变量,尔后再操作该指针变量指向的地址的值,等同于操作等效别名地址上的值,进一步理解,就是对GPIOA上单个IO的输出进行控制了。于是对PAout(n)的操作,就是对指针指向的等效别名地址进行的写操作了。
参考文献:
Cortex-M3权威指南Cn:“位带操作”部分;
STM32中文参考手册:“8.2.4 端口输出数据寄存器(GPIOx_ODR) (x=A..E)”部分;

浙公网安备 33010602011771号